diff --git a/src/DiffEngine.Tests/InlineStagingTests.cs b/src/DiffEngine.Tests/InlineStagingTests.cs index 844182fd..f9313658 100644 --- a/src/DiffEngine.Tests/InlineStagingTests.cs +++ b/src/DiffEngine.Tests/InlineStagingTests.cs @@ -159,6 +159,33 @@ public async Task ClearRemovesEveryFrameworksTrio() await Assert.That(project.StagedFiles()).IsEmpty(); } + /// + /// A settle that names the framework it came from takes only that framework's trio. The queue + /// has always scoped a settle this way; staging is the same situation and had no way to say + /// it, so in a net8;net10 run where net10 started passing and net8 did not, net10's settle + /// deleted net8's still-failing snapshot and it was then pending nowhere. + /// + [Test] + public async Task ClearScopedToAnOriginLeavesTheOtherFrameworkStaged() + { + using var project = new TempProject(); + var source = project.Source("SampleTests.cs"); + InlineStaging.Persist( + [ + new( + [ + new(Patch(source, "from net8", framework: "net8.0"), ["net8.0"]), + new(Patch(source, "from net10", framework: "net10.0"), ["net10.0"]), + ]) + ]); + + var cleared = InlineStaging.Clear(source, 42, null, origin: "net10.0"); + + await Assert.That(cleared).IsEqualTo(1); + // net8's trio is still there, and still reviewable + await Assert.That(project.StagedFiles().Count).IsEqualTo(3); + } + [Test] public async Task ClearFindsACallSiteWhoseLineHasMovedByMember() { diff --git a/src/DiffEngine/Inline/InlineStaging.cs b/src/DiffEngine/Inline/InlineStaging.cs index 6f3098e7..c78c4dec 100644 --- a/src/DiffEngine/Inline/InlineStaging.cs +++ b/src/DiffEngine/Inline/InlineStaging.cs @@ -74,18 +74,34 @@ public static int Persist(IEnumerable pending) /// of its own. Verify's own fallback writes under the project's intermediate directory, which /// is normally inside that obj and found anyway, but does not have to be. /// - public static int Clear(string sourceFile, int line, string? memberName, string? extraDirectory = null) + /// + /// The framework moniker of the run that settled, which scopes the clear to that run's own + /// trio. This is what already does with a + /// queue, and staging is the same situation with the same answer: one call site stages one + /// trio per framework, so a multi-framework run where one framework starts passing and another + /// does not would otherwise have the passing one delete the failing one's staged snapshot. + /// + /// Null clears every framework's trio for the call site, which is right for a retire - the + /// call site is not an inline snapshot in any framework any more - and is the behaviour of + /// every caller that does not pass this. + /// + /// + /// A trio staged without a framework label is cleared either way, since there is nothing to + /// scope it by and leaving it would strand it forever. + /// + /// + public static int Clear(string sourceFile, int line, string? memberName, string? extraDirectory = null, string? origin = null) { var cleared = 0; foreach (var directory in StagingDirectories(sourceFile, extraDirectory)) { - cleared += ClearIn(directory, sourceFile, line, memberName); + cleared += ClearIn(directory, sourceFile, line, memberName, origin); } return cleared; } - static int ClearIn(string directory, string sourceFile, int line, string? memberName) + static int ClearIn(string directory, string sourceFile, int line, string? memberName, string? origin) { var staged = ReadStaged(directory) .Where(_ => SamePath(_.Patch.SourceFile, sourceFile)) @@ -111,6 +127,17 @@ static int ClearIn(string directory, string sourceFile, int line, string? member } } + if (origin != null) + { + // One call site stages one trio per framework, so a settle that names the framework it + // came from must take only that one. Without it a net9 run that started passing + // deleted net8's still-failing trio beside its own, and that snapshot was then pending + // nowhere: not staged, and not in a queue either, since nothing had one + matching = matching + .Where(_ => _.Patch.Framework == null || _.Patch.Framework == origin) + .ToList(); + } + var cleared = 0; foreach (var entry in matching) {