public void randomized_checking_of_ordered_versions_mapping_and_extended_successors_and_predecessors(int seed, int count, int span) { Random r = seed >= 0 ? new Random(seed) : new Random(); while (--count > 0) { long start = (long)decimal.Ceiling(r.NextDecimal() * (CSVersion.VeryLastVersion.OrderedVersion + 1) + 1); CSVersion rStart = CheckMapping(start); Assert.That(rStart, Is.Not.Null); CSVersion rCurrent; for (int i = 1; i < span; ++i) { rCurrent = CheckMapping(start + i); if (rCurrent == null) { break; } Assert.That(rStart < rCurrent); } for (int i = 1; i < span; ++i) { rCurrent = CheckMapping(start - i); if (rCurrent == null) { break; } Assert.That(rStart > rCurrent); } } //Console.WriteLine( "Greatest successors count = {0}.", _greatersuccessorCount ); }
/// <summary> /// Initializes a new <see cref="CommitAssemblyBuildInfo"/> for a release version. /// </summary> /// <param name="releaseVersion">The release version.</param> /// <param name="commitSha">The commit sha.</param> /// <param name="commitDateUtc">The commit date Utc.</param> public CommitAssemblyBuildInfo(CSVersion releaseVersion, string commitSha, DateTime commitDateUtc) { if (releaseVersion == null || !releaseVersion.IsValid) { throw new ArgumentNullException(nameof(releaseVersion)); } if (String.IsNullOrWhiteSpace(commitSha)) { throw new ArgumentNullException(nameof(commitSha)); } if (commitDateUtc.Kind != DateTimeKind.Utc) { throw new ArgumentException("Must be Utc.", nameof(commitSha)); } Version = releaseVersion.ToNormalizedForm(); CommitDateUtc = commitDateUtc; CommitSha = commitSha; BuildConfiguration = releaseVersion.Prerelease.Length == 0 || releaseVersion.Prerelease == "rc" ? "Release" : "Debug"; AssemblyVersion = $"{releaseVersion.Major}.{releaseVersion.Minor}"; FileVersion = releaseVersion.ToStringFileVersion(false); InformationalVersion = Version.GetInformationalVersion(CommitSha, CommitDateUtc); }
public void multiple_version_tags_on_the_same_commit() { var repoTest = TestHelper.TestGitRepository; var cRealDevInAlpha = repoTest.Commits.Single(sc => sc.Message.StartsWith("Real Dev in Alpha.")); var overrides = new TagsOverride().MutableAdd(cRealDevInAlpha.Sha, "1.0.0") .MutableAdd(cRealDevInAlpha.Sha, "2.0.0"); { RepositoryInfo i = repoTest.GetRepositoryInfo(new RepositoryInfoOptions { StartingCommitSha = cRealDevInAlpha.Sha, OverriddenTags = overrides.Overrides, }); i.ValidReleaseTag.Should().BeNull(); i.Error.Trim().Should().Be($"Commit '{cRealDevInAlpha.Sha}' has 2 different released version tags. Delete some of them or create +invalid tag(s) if they are already pushed to a remote repository."); } { RepositoryInfo i = repoTest.GetRepositoryInfo(new RepositoryInfoOptions { StartingCommitSha = cRealDevInAlpha.Sha, OverriddenTags = overrides.Overrides, StartingVersionForCSemVer = "2.0.0" }); i.ValidReleaseTag.Should().Be(CSVersion.Parse("2.0.0")); } }
/// <summary> /// Initializes a new valid <see cref="CommitVersionInfo"/>. /// </summary> /// <param name="commitSHA1">The commit SHA1. Must not be null.</param> /// <param name="releaseVersion">See <see cref="ReleaseVersion"/>.</param> /// <param name="releaseContentVersion">See <see cref="ReleaseContentVersion"/>.</param> /// <param name="previousVersion">See <see cref="PreviousVersion"/>.</param> /// <param name="nextPossibleVersions">See <see cref="NextPossibleVersions"/>.</param> /// <param name="possibleVersions">See <see cref="PossibleVersions"/>.Must not be null.</param> /// <param name="assemblyBuildInfo">See <see cref="AssemblyBuildInfo"/>. Must not be null.</param> public CommitVersionInfo( string commitSha, CSVersion releaseVersion, CSVersion releaseContentVersion, CSVersion previousVersion, string previousVersionCommitSha, IReadOnlyList <CSVersion> nextPossibleVersions, IReadOnlyList <CSVersion> possibleVersions, ICommitAssemblyBuildInfo assemblyBuildInfo) { CommitSha = commitSha ?? throw new ArgumentNullException(nameof(commitSha)); ReleaseVersion = releaseVersion; ReleaseContentVersion = releaseContentVersion; if ((previousVersion == null) != (previousVersionCommitSha == null)) { if (previousVersion == null) { throw new ArgumentException("Must be null.", nameof(previousVersionCommitSha)); } throw new ArgumentNullException(nameof(previousVersionCommitSha)); } PreviousVersion = previousVersion; PreviousVersionCommitSha = previousVersionCommitSha; NextPossibleVersions = nextPossibleVersions ?? throw new ArgumentNullException(nameof(nextPossibleVersions)); PossibleVersions = possibleVersions ?? throw new ArgumentNullException(nameof(possibleVersions)); AssemblyBuildInfo = assemblyBuildInfo ?? throw new ArgumentNullException(nameof(assemblyBuildInfo)); }
public TagCommit(Commit c, CSVersion first) { Debug.Assert(c != null && first != null && first.IsValid); _commitSha = c.Sha; _contentSha = c.Tree.Sha; _thisTag = first; }
void RegisterOneTag(StringBuilder errors, Commit c, string tagName, int?singleMajor, ref bool startingVersionForCSemVerFound) { CSVersion v = CSVersion.TryParse(tagName); if (v.IsValid && (!singleMajor.HasValue || v.Major == singleMajor.Value)) { if (_startingVersionForCSemVer != null) { int cmp = _startingVersionForCSemVer.CompareTo(v); if (cmp == 0) { startingVersionForCSemVerFound = true; } else if (cmp > 0) { // This version is smaller than the StartingVersionForCSemVer: // we ignore it. return; } } TagCommit tagCommit; if (_collector.TryGetValue(c.Sha, out tagCommit)) { tagCommit.AddCollectedTag(v); } else { _collector.Add(c.Sha, tagCommit = new TagCommit(c, v)); } } }
private static bool DoBuild( IActivityMonitor m, ISolutionDriver driver, CSVersion targetVersion, out bool tagCreated) { tagCreated = false; try { var git = driver.GitRepository; if (targetVersion.PackageQuality == PackageQuality.Release) { if (!git.SwitchDevelopToMaster(m)) { return(false); } driver = driver.GetCurrentBranchDriver(); } if (!git.SetVersionTag(m, targetVersion)) { return(false); } tagCreated = true; if (!driver.Build(m, withUnitTest: true, withZeroBuilder: true, withPushToRemote: false)) { return(false); } return(true); } catch (Exception ex) { m.Error("Build failed.", ex); return(false); } }
public void CSVersion_Parse_with_long_forms(string tag, bool isLongForm) { CSVersion t = CSVersion.Parse(tag); Assert.That(t.IsValid); Assert.That(t.IsLongForm, Is.EqualTo(isLongForm)); }
internal RepositoryVersions( IEnumerable <TagCommit> collected, StringBuilder errors, CSVersion startingVersionForCSemVer, bool checkCompactExistingVersions) { Debug.Assert(collected.All(c => c.ThisTag != null)); _versions = collected.OrderBy(t => t.ThisTag).ToList(); if (_versions.Count > 0) { var first = _versions[0].ThisTag; if (checkCompactExistingVersions && startingVersionForCSemVer == null && !first.IsDirectPredecessor(null)) { errors.AppendFormat($"First existing version is '{first}' (on '{_versions[0].CommitSha}'). One or more previous versions are missing.") .AppendLine(); } for (int i = 0; i < _versions.Count - 1; ++i) { var prev = _versions[i].ThisTag; var next = _versions[i + 1].ThisTag; if (next.Equals(prev)) { errors.AppendFormat($"Version '{prev}' is defined on '{_versions[i].CommitSha}' and '{_versions[i + 1].CommitSha}'.") .AppendLine(); } else if (checkCompactExistingVersions && !next.IsDirectPredecessor(prev)) { errors.AppendFormat($"Missing one or more version(s) between '{prev}' and '{next}'.") .AppendLine(); } } } }
/// <summary> /// Creates a <see cref="ReleaseInfo"/> from a <see cref="XElement"/> (see <see cref="ToXml"/>). /// </summary> /// <param name="e">The Xml element.</param> public ReleaseInfo(XElement e) { var sV = (string)e.Attribute("Version"); Version = sV == null || sV.Length == 0 ? null : CSVersion.Parse(sV); Level = e.AttributeEnum("Level", ReleaseLevel.None); Constraint = e.AttributeEnum("Constraint", ReleaseConstraint.None); }
/// <summary> /// Gets the best commit tag for this commit, skipping the given version. /// Null if and only if <see cref="ITagCommit.ThisTag"/> is equal to <paramref name="v"/> /// and there is no better <see cref="GetContentTagCommits"/>. /// </summary> /// <param name="v">The version to ignore. Can be null (BestCommit is returned).</param> /// <returns>The best commit tag or null.</returns> public ITagCommit GetBestCommitExcept(CSVersion v) { var best = BestCommit; if (best.ThisTag != v) { return(best); } return(_headSameTree != null ? _headSameTree._altBestTagCommit : null); }
void SetNumericalVersionValues(CSVersion t, bool isCIBuild) { Major = t.Major; Minor = t.Minor; Patch = t.Patch; PreReleaseName = t.PrereleaseName; PreReleaseNumber = t.PrereleaseNumber; PreReleaseFix = t.PrereleasePatch; FileVersion = t.ToStringFileVersion(isCIBuild); OrderedVersion = t.OrderedVersion; }
/// <summary> /// Returns a valid new <see cref="ReleaseInfo"/> with the version set. /// </summary> /// <param name="v">The version to set. Can not be null.</param> /// <returns>A valid ReleaseInfo.</returns> public ReleaseInfo WithVersion(CSVersion v) { if (v == null) { throw new ArgumentNullException(nameof(v)); } var c = v.IsPrerelease && v.Major != 0 ? Constraint | ReleaseConstraint.MustBePreRelease : Constraint; return(new ReleaseInfo(Level, c, v)); }
/// <summary> /// Computes the final release tag: +invalid hides any other version tags. /// If multiple versions exist on this commit, an error is raised. /// </summary> /// <param name="errors">Errors collector.</param> /// <returns>False it this tag is invalid.</returns> public bool CloseCollect(StringBuilder errors) { var t = DoCloseCollect(errors); if (t != null && t.IsValid) { _thisTag = t; return(true); } _thisTag = null; return(false); }
public void parsing_valid_release(string tag) { CSVersion t = CSVersion.Parse(tag); Assert.That(t.IsValid); Assert.That(t.IsLongForm, Is.False); Assert.That(t.IsPrerelease, Is.False); Assert.That(t.IsPreReleasePatch, Is.False); Assert.That(t.ToString(CSVersionFormat.LongForm), Is.EqualTo(tag)); Assert.That(t.ToString(), Is.EqualTo(tag)); Assert.That(t.NormalizedText, Is.EqualTo(tag)); }
public ReleaseNoteInfo(XElement e) { SolutionName = (string)e.AttributeRequired(XmlNames.xSolutionName); Current = new ReleaseInfo(e.Element(XmlNames.xReleaseInfo)); var p = e.Element(XmlNames.xPreviousVersion); if (p != null) { PreviousVersion = CSVersion.Parse(p.Value); } ReleaseNote = e.Element(XmlNames.xReleaseNote).Value; }
public void parsing_valid_release(string tag) { CSVersion t = CSVersion.TryParse(tag); Assert.That(t.IsValid); Assert.That(t.IsPrerelease, Is.False); Assert.That(t.IsPreReleasePatch, Is.False); Assert.That(t.ToNuGetPackageString(), Is.EqualTo(tag)); Assert.That(t.ToString(), Is.EqualTo(tag)); Assert.That(t.NormalizedText, Is.EqualTo(tag)); Assert.That(t.NormalizedTextWithBuildMetaData, Is.EqualTo(tag)); }
CIReleaseInfo( CSVersion ciBaseTag, int ciBaseDepth, SVersion ciBuildVersion, SVersion ciBuildVersionNuGet, bool isZeroTimed) { BaseTag = ciBaseTag; Depth = ciBaseDepth; BuildVersion = ciBuildVersion; BuildVersionNuGet = ciBuildVersionNuGet; IsZeroTimed = isZeroTimed; }
public void pre_release_with_standard_names_nugetV2_mappings(string tag, string nuget, bool isPrereleasePatch) { CSVersion fromShortForm = CSVersion.Parse(nuget); CSVersion t = CSVersion.TryParse(tag); Assert.That(t, Is.EqualTo(fromShortForm)); Assert.That(t.IsValid); Assert.That(t.IsPrerelease); Assert.That(t.IsPreReleasePatch, Is.EqualTo(isPrereleasePatch)); Assert.That(t.ToString(CSVersionFormat.LongForm), Is.EqualTo(tag)); Assert.That(t.ToString(CSVersionFormat.Normalized), Is.EqualTo(nuget)); Assert.That(SVersion.Parse(nuget).Prerelease.Length, Is.LessThanOrEqualTo(20)); }
static void DumpVersionInfo(CIBuildDescriptor buildInfo, CSVersion t) { var nugetV2Build = t.ToString(CSVersionFormat.Normalized, buildInfo); int nugetV2BuildSNLen = SVersion.Parse(nugetV2Build).Prerelease.Length; Console.WriteLine("{0}, CI = {1}, NuGet = {2}, NuGet CI = {3}, NugetV2Build.SpecialName.Length = {4}", t, t.ToString(CSVersionFormat.Normalized, buildInfo), t.ToString(CSVersionFormat.Normalized), nugetV2Build, nugetV2BuildSNLen ); Assert.That(nugetV2BuildSNLen, Is.LessThanOrEqualTo(20)); }
internal static CIReleaseInfo Create( Commit commit, CIBranchVersionMode ciVersionMode, string ciBuildName, StringBuilder errors, BasicCommitInfo info) { var actualBaseTag = info?.MaxCommit.ThisTag; CSVersion ciBaseTag = actualBaseTag ?? CSVersion.VeryFirstVersion; SVersion ciBuildVersionNuGet = null, ciBuildVersion = null; // If there is no base release found, we fall back to ZeroTimedBased mode. if (ciVersionMode == CIBranchVersionMode.ZeroTimed || actualBaseTag == null) { DateTime timeRelease = commit.Committer.When.ToUniversalTime().UtcDateTime; string vS = CIBuildDescriptor.CreateSemVerZeroTimed(ciBuildName, timeRelease); string vN = CIBuildDescriptor.CreateShortFormZeroTimed(ciBuildName, timeRelease); if (actualBaseTag != null) { string buildMetaData = "+v" + actualBaseTag; vS += buildMetaData; vN += buildMetaData; } ciBuildVersion = SVersion.Parse(vS); ciBuildVersionNuGet = SVersion.Parse(vN, false); return(new CIReleaseInfo(ciBaseTag, 0, ciBuildVersion, ciBuildVersionNuGet, true)); } Debug.Assert(ciVersionMode == CIBranchVersionMode.LastReleaseBased && actualBaseTag != null); CIBuildDescriptor ci = new CIBuildDescriptor { BranchName = ciBuildName, BuildIndex = info.BelowDepth }; if (!ci.IsValidForShortForm) { errors.AppendLine("Due to ShortForm (NuGet V2 compliance) limitation, the branch name must not be longer than 8 characters. "); errors.Append("Adds a VersionName attribute to the branch element in RepositoryInfo.xml with a shorter name: ") .AppendLine() .Append($@"<Branch Name=""{ci.BranchName}"" VersionName=""{ci.BranchName.Substring( 0, 8 )}"" ... />.") .AppendLine(); } else { ciBuildVersion = SVersion.Parse(actualBaseTag.ToString(CSVersionFormat.Normalized, ci)); ciBuildVersionNuGet = SVersion.Parse(actualBaseTag.ToString(CSVersionFormat.NuGetPackage, ci), false); } Debug.Assert(ciBuildVersion == null || errors.Length == 0); return(ciBuildVersion != null ? new CIReleaseInfo(ciBaseTag, info.BelowDepth, ciBuildVersion, ciBuildVersionNuGet, false) : null); }
public void check_predecessors(string predS, string vS, bool isPredecessor) { var pred = CSVersion.Parse(predS); var v = CSVersion.Parse(vS); if (isPredecessor) { pred.GetDirectSuccessors().Should().Contain(v); } else { pred.GetDirectSuccessors().Should().NotContain(v); } v.IsDirectPredecessor(pred).Should().Be(isPredecessor); }
public void pre_release_with_standard_names_and_fix_number_nugetV2_mappings(string tag, string nuget) { CSVersion fromShortForm = CSVersion.Parse(nuget); CSVersion t = CSVersion.TryParse(tag); Assert.That(t, Is.EqualTo(fromShortForm)); Assert.That(t.IsValid); Assert.That(t.IsPrerelease); Assert.That(t.IsPreReleasePatch); Assert.That(t.PrereleasePatch, Is.GreaterThan(0)); Assert.That(t.NormalizedText, Is.EqualTo(tag)); Assert.That(t.ToString(CSVersionFormat.NuGetPackage), Is.EqualTo(nuget)); Assert.That(SVersion.Parse(nuget).Prerelease.Length, Is.LessThanOrEqualTo(20)); }
private static void NullIsAlwaysSmaller(CSVersion v) { Assert.That(null != v); Assert.That(null == v, Is.False); Assert.That(null >= v, Is.False); Assert.That(null <= v); Assert.That(null > v, Is.False); Assert.That(null < v); Assert.That(v != null); Assert.That(v == null, Is.False); Assert.That(v >= null); Assert.That(v <= null, Is.False); Assert.That(v > null); Assert.That(v < null, Is.False); }
/// <summary> /// Initializes a new <see cref="TagCollector"/>. /// Errors may be appended to the collector that can be syntaxic errors or multiple different versions applied to the same commit point. /// </summary> /// <param name="errors">A collector of errors. One line per error.</param> /// <param name="repo">The Git repository.</param> /// <param name="startingVersionForCSemVer">Vesion tags lower than this version will be ignored.</param> /// <param name="overriddenTags">Optional commits with associated tags that are applied as if they exist in the repository.</param> /// <param name="singleMajor">Optional major filter.</param> /// <param name="checkValidExistingVersions"> /// When true, existing versions are checked: one of the valid first version must exist and exisitng versions /// must be compact. /// </param> public TagCollector( StringBuilder errors, Repository repo, string startingVersionForCSemVer = null, IEnumerable <KeyValuePair <string, IReadOnlyList <string> > > overriddenTags = null, int?singleMajor = null, bool checkValidExistingVersions = false) { Debug.Assert(errors != null && repo != null); _collector = new Dictionary <string, TagCommit>(); if (startingVersionForCSemVer != null) { _startingVersionForCSemVer = CSVersion.TryParse(startingVersionForCSemVer, true); if (!_startingVersionForCSemVer.IsValid) { errors.Append("Invalid StartingVersionForCSemVer. ").Append(_startingVersionForCSemVer.ErrorMessage).AppendLine(); return; } if (singleMajor.HasValue && _startingVersionForCSemVer.Major != singleMajor) { errors.Append("StartingVersionForCSemVer '") .Append(_startingVersionForCSemVer) .Append("': since it is defined, its major must be ").Append(singleMajor).Append(" that is the SingleMajor set.") .AppendLine(); return; } } // Register all tags. RegisterAllTags(errors, repo, overriddenTags, singleMajor); // Resolves multiple tags on the same commit. CloseCollect(errors); // Sorts TagCommit, optionally checking the existing versions. _repoVersions = new RepositoryVersions(_collector.Values, errors, _startingVersionForCSemVer, checkValidExistingVersions); // Register content. if (errors.Length == 0) { foreach (var tc in _repoVersions.TagCommits) { RegisterContent(tc); } } }
//static int _greatersuccessorCount = 0; CSVersion CheckMapping(long v) { if (v < 0 || v > CSVersion.VeryLastVersion.OrderedVersion) { Assert.Throws <ArgumentException>(() => CSVersion.Create(v)); return(null); } var t = CSVersion.Create(v); Assert.That((v == 0) == !t.IsValid); Assert.That(t.OrderedVersion, Is.EqualTo(v)); var sSemVer = t.NormalizedText; var tSemVer = CSVersion.TryParse(sSemVer); var tNormalized = CSVersion.TryParse(t.ToString(CSVersionFormat.Normalized)); Assert.That(tSemVer.OrderedVersion, Is.EqualTo(v)); Assert.That(tNormalized.OrderedVersion, Is.EqualTo(v)); Assert.That(tNormalized.Equals(t)); Assert.That(tSemVer.Equals(t)); Assert.That(tNormalized.Equals((object)t)); Assert.That(tSemVer.Equals((object)t)); Assert.That(tNormalized.CompareTo(t) == 0); Assert.That(tSemVer == t); Assert.That(tSemVer.ToString(), Is.EqualTo(t.ToString())); Assert.That(tNormalized.ToString(), Is.EqualTo(t.ToString())); // Successors/Predecessors check. var vSemVer = SVersion.Parse(sSemVer); int count = 0; foreach (var succ in t.GetDirectSuccessors(false)) { ++count; Assert.That(succ.IsDirectPredecessor(t)); var vSemVerSucc = SVersion.Parse(succ.NormalizedText); Assert.That(vSemVer < vSemVerSucc, "{0} < {1}", vSemVer, vSemVerSucc); } //if( count > _greatersuccessorCount ) //{ // Console.WriteLine( " -> - found {0} successors for '{1}':", count, t ); // Console.WriteLine( " " + string.Join( ", ", t.GetDirectSuccessors( false ).Select( s => s.ToString() ) ) ); // var closest = t.GetDirectSuccessors( true ).Select( s => s.ToString() ).ToList(); // Console.WriteLine( " - {0} closest successors:", closest.Count, t ); // Console.WriteLine( " " + string.Join( ", ", closest ) ); // _greatersuccessorCount = count; //} return(t); }
public void checking_extreme_version_ordering(string tag, bool atEnd, int expectedRank) { var t = CSVersion.TryParse(tag); if (atEnd) { Assert.That(t.OrderedVersion - (CSVersion.VeryLastVersion.OrderedVersion - expectedRank), Is.EqualTo(0)); } else { Assert.That(t.OrderedVersion - (CSVersion.VeryFirstVersion.OrderedVersion + expectedRank), Is.EqualTo(0)); } var t2 = CSVersion.Create(t.OrderedVersion); Assert.That(t2.ToString(), Is.EqualTo(t.ToString())); Assert.That(t.Equals(t2)); }
public void display_name_and_successors_samples(string v) { CSVersion t = CSVersion.TryParse(v); var succ = t.GetDirectSuccessors(false); Console.WriteLine(" -> - found {0} successors for '{1}' (NuGetV2 = {2}, Ordered Version = {3}, File = {4}.{5}.{6}.{7}):", succ.Count(), t, t.ToString(), t.OrderedVersion, t.OrderedVersionMajor, t.OrderedVersionMinor, t.OrderedVersionBuild, t.OrderedVersionRevision ); Console.WriteLine(" " + string.Join(", ", succ.Select(s => s.ToString()))); }
public void version_ordering_starts_at_1_for_the_very_first_possible_version(string tag, int oMajor, int oMinor, int oBuild, int oRevision) { var t = CSVersion.TryParse(tag, true); Assert.That(t.IsValid); Assert.That(t.OrderedVersionMajor, Is.EqualTo(oMajor)); Assert.That(t.OrderedVersionMinor, Is.EqualTo(oMinor)); Assert.That(t.OrderedVersionBuild, Is.EqualTo(oBuild)); Assert.That(t.OrderedVersionRevision, Is.EqualTo(oRevision)); long vf = t.OrderedVersion << 1; Assert.That(t.ToStringFileVersion(false), Is.EqualTo(string.Format("{0}.{1}.{2}.{3}", vf >> 48, (vf >> 32) & 0xFFFF, (vf >> 16) & 0xFFFF, vf & 0xFFFF))); vf |= 1; Assert.That(t.ToStringFileVersion(true), Is.EqualTo(string.Format("{0}.{1}.{2}.{3}", vf >> 48, (vf >> 32) & 0xFFFF, (vf >> 16) & 0xFFFF, vf & 0xFFFF))); }
public void display_successors_samples(string v) { CSVersion t = CSVersion.TryParse(v); var succ = t.GetDirectSuccessors(false); Console.WriteLine(" -> - found {0} successors for '{1}' (Ordered Version = {2}, File = {3}):", succ.Count(), t, t.OrderedVersion, t.ToStringFileVersion(false)); Console.WriteLine(" " + string.Join(", ", succ.Select(s => s.ToString()))); var closest = t.GetDirectSuccessors(true).Select(s => s.ToString()).ToList(); Console.WriteLine(" - {0} next fixes:", closest.Count, t); Console.WriteLine(" " + string.Join(", ", closest)); }