fix: track Tornado program windows across focus changes

This commit is contained in:
2026-07-11 13:15:26 +09:00
parent 8864440ac7
commit c71ea425e8
2 changed files with 334 additions and 22 deletions

View File

@@ -1,5 +1,6 @@
using MBN_STOCK_WEBVIEW.Playout.Configuration;
using MBN_STOCK_WEBVIEW.Playout.Runtime;
using System.Text.RegularExpressions;
namespace MBN_STOCK_WEBVIEW.Playout.Tests;
@@ -53,6 +54,136 @@ public sealed class PlayoutSafetyValidationTests : IDisposable
Assert.Equal(expected, TornadoProcessProbe.HasExplicitTestMarker(title));
}
[Fact]
public void ClassifyWindowTitles_EditorAndPgmRemainTheSameProgramGeneration_WhenFocusOrderChanges()
{
string[] editorFirst = ["Tornado2 - [5001.t2s]", "PGM"];
string[] pgmFirst = ["PGM", "Tornado2 - [5001.t2s]"];
var first = TornadoProcessProbe.ClassifyWindowTitles(editorFirst, testWindowPattern: null);
var second = TornadoProcessProbe.ClassifyWindowTitles(pgmFirst, testWindowPattern: null);
Assert.True(first.IsProgram);
Assert.True(first.IsEligible);
Assert.Equal(first, second);
var identity = new TornadoProcessIdentity(42, 123456789);
var firstGeneration = TornadoProcessGeneration.FromIdentities(
"program",
first.IsProgram ? [identity] : []);
var secondGeneration = TornadoProcessGeneration.FromIdentities(
"program",
second.IsProgram ? [identity] : []);
Assert.Equal(firstGeneration, secondGeneration);
}
[Fact]
public void ClassifyWindowTitles_TestModeAcceptsExplicitTestTitleFromAnyTopLevelWindow()
{
var pattern = new Regex(
"^Tornado2 TEST$",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100));
var classification = TornadoProcessProbe.ClassifyWindowTitles(
["Tornado2 - [test-editor]", "Tornado2 TEST"],
pattern);
Assert.False(classification.IsProgram);
Assert.True(classification.IsEligible);
}
[Fact]
public void ClassifyWindowTitles_TestModeRejectsProcessThatAlsoOwnsPgmWindow()
{
var pattern = new Regex(
"^Tornado2 TEST$",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100));
var classification = TornadoProcessProbe.ClassifyWindowTitles(
["Tornado2 TEST", "PGM"],
pattern);
Assert.True(classification.IsProgram);
Assert.False(classification.IsEligible);
}
[Fact]
public void ClassifyWindowTitles_NoMeaningfulTitlesFailsClosedAsProgram()
{
var pattern = new Regex(
"^Tornado2 TEST$",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100));
var classification = TornadoProcessProbe.ClassifyWindowTitles(
[null, "", " "],
pattern);
Assert.True(classification.IsProgram);
Assert.False(classification.IsEligible);
}
[Fact]
public void TryClassifyWindowSnapshot_InspectionFailureFailsClosed()
{
var snapshot = new TornadoWindowTitleSnapshot(
["PGM"],
InspectionSucceeded: false);
var succeeded = TornadoProcessProbe.TryClassifyWindowSnapshot(
snapshot,
testWindowPattern: null,
out var classification);
Assert.False(succeeded);
Assert.Equal(default, classification);
}
[Fact]
public void TryClassifyWindowSnapshot_NoMeaningfulTitlesFailsIdentityInspection()
{
var snapshot = new TornadoWindowTitleSnapshot(
["", " "],
InspectionSucceeded: true);
var succeeded = TornadoProcessProbe.TryClassifyWindowSnapshot(
snapshot,
testWindowPattern: null,
out var classification);
Assert.False(succeeded);
Assert.Equal(default, classification);
}
[Fact]
public void TryClassifyWindowSnapshot_PgmTitleSucceedsForLiveIdentityInspection()
{
var snapshot = new TornadoWindowTitleSnapshot(
["Tornado2 - [5001.t2s]", "PGM"],
InspectionSucceeded: true);
var succeeded = TornadoProcessProbe.TryClassifyWindowSnapshot(
snapshot,
testWindowPattern: null,
out var classification);
Assert.True(succeeded);
Assert.True(classification.IsProgram);
Assert.True(classification.IsEligible);
}
[Fact]
public void WindowTitleProbe_InvalidProcessIdFailsClosedWithoutNativeInspection()
{
var snapshot = new Win32TornadoWindowTitleProbe().Capture(0);
Assert.False(snapshot.InspectionSucceeded);
Assert.Empty(snapshot.Titles);
}
[Theory]
[InlineData(0, false)]
[InlineData(1, true)]