Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,97 @@ namespace PackageCascade.Tests.Unit.Internal.Planning.SemVer;

public class DirectoryBumpCalculatorTests
{
[Test]
[Arguments("Directory.Build.props")]
[Arguments("Directory.Build.targets")]
[Arguments("Directory.Packages.props")]
[Arguments("global.json")]
[Arguments("NuGet.Config")]
public async Task RootSharedFileChangeBumpsEveryDirectory(string path)
{
GitCommitDetails commit = new("commit", "build: update shared settings", [path]);

Dictionary<string, SemverBump> bumps = new DirectoryBumpCalculator().Calculate([commit], ["src/A", "tools/B"]);

using (Assert.Multiple())
{
await Assert.That(bumps["src/A"]).IsEqualTo(SemverBump.Patch);
await Assert.That(bumps["tools/B"]).IsEqualTo(SemverBump.Patch);
}
}

[Test]
[Arguments("Update shared settings", "Patch")]
[Arguments("fix: correct shared settings", "Patch")]
[Arguments("feat: enable new build feature", "Minor")]
[Arguments("build!: change shared settings", "Major")]
public async Task RootSharedFileUsesCommitBumpWithPatchMinimum(string message, string expected)
{
GitCommitDetails commit = new("commit", message, ["Directory.Build.props"]);

Dictionary<string, SemverBump> bumps = new DirectoryBumpCalculator().Calculate([commit], ["src/A", "src/B"]);

using (Assert.Multiple())
{
await Assert.That(bumps["src/A"].ToString()).IsEqualTo(expected);
await Assert.That(bumps["src/B"].ToString()).IsEqualTo(expected);
}
}

[Test]
[Arguments("src/A/Directory.Build.props")]
[Arguments("src/A/Directory.Build.targets")]
[Arguments("src/A/Directory.Packages.props")]
[Arguments("src/A/global.json")]
[Arguments("src/A/NuGet.Config")]
public async Task NestedSharedFilenameOnlyAffectsItsOwnDirectory(string path)
{
GitCommitDetails commit = new("commit", "fix: correct settings", [path]);

Dictionary<string, SemverBump> bumps = new DirectoryBumpCalculator().Calculate([commit], ["src/A", "src/B"]);

using (Assert.Multiple())
{
await Assert.That(bumps["src/A"]).IsEqualTo(SemverBump.Patch);
await Assert.That(bumps["src/B"]).IsEqualTo(SemverBump.None);
}
}

[Test]
[Arguments("README.md")]
[Arguments(".editorconfig")]
[Arguments("Example.slnx")]
[Arguments("Directory.Build.props.bak")]
public async Task OtherRootFilesDoNotBumpEveryDirectory(string path)
{
GitCommitDetails commit = new("commit", "fix: update repository file", [path]);

Dictionary<string, SemverBump> bumps = new DirectoryBumpCalculator().Calculate([commit], ["src/A"]);

await Assert.That(bumps["src/A"]).IsEqualTo(SemverBump.None);
}

[Test]
[Arguments(false)]
[Arguments(true)]
public async Task CombinesSharedAndPackageChangesUsingStrongestBump(bool reverseOrder)
{
GitCommitDetails[] commits =
[
new("shared", "feat: enable shared feature", ["global.json"]),
new("package", "feat!: change A API", ["src/A/Api.cs"]),
];

Dictionary<string, SemverBump> bumps = new DirectoryBumpCalculator().Calculate(
reverseOrder ? commits.Reverse() : commits, ["src/A", "src/B"]);

using (Assert.Multiple())
{
await Assert.That(bumps["src/A"]).IsEqualTo(SemverBump.Major);
await Assert.That(bumps["src/B"]).IsEqualTo(SemverBump.Minor);
}
}

[Test]
[Arguments("fix: repair", "Patch")]
[Arguments("perf(core): speed up", "Patch")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,43 @@ public Dictionary<string, SemverBump> Calculate(IEnumerable<GitCommitDetails> co
foreach (GitCommitDetails commit in commits)
{
SemverBump bump = GetBump(commit.Message);
bool changesRootSharedFile = commit.ChangedPaths.Any(IsRootSharedFile);
if (changesRootSharedFile && bump < SemverBump.Patch)
{
bump = SemverBump.Patch;
}

if (bump == SemverBump.None)
{
continue;
}

ApplyBumpToAffectedDirectories(commit, bump, directoryPrefixes, bumps);
ApplyBumpToAffectedDirectories(commit, bump, changesRootSharedFile, directoryPrefixes, bumps);
}

return bumps;
}

private static void ApplyBumpToAffectedDirectories(GitCommitDetails commit, SemverBump bump,
private static void ApplyBumpToAffectedDirectories(GitCommitDetails commit, SemverBump bump, bool changesRootSharedFile,
IReadOnlyDictionary<string, string> directoryPrefixes, Dictionary<string, SemverBump> bumps)
{
foreach ((string directory, string prefix) in directoryPrefixes)
{
if (commit.ChangedPaths.Any(path => path.StartsWith(prefix, StringComparison.Ordinal))
if ((changesRootSharedFile || commit.ChangedPaths.Any(path => path.StartsWith(prefix, StringComparison.Ordinal)))
&& bump > bumps[directory])
{
bumps[directory] = bump;
}
}
}

private static bool IsRootSharedFile(string path) => path is
"Directory.Build.props" or
"Directory.Build.targets" or
"Directory.Packages.props" or
"global.json" or
"NuGet.Config";

private static SemverBump GetBump(string message)
{
Match header = HeaderPattern().Match(message);
Expand Down