feat: harden isolated Tornado test workflow
This commit is contained in:
@@ -4,14 +4,29 @@ using MBN_STOCK_WEBVIEW.Core.Playout;
|
||||
using MBN_STOCK_WEBVIEW.Playout;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Configuration;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Registration;
|
||||
using MBN_STOCK_WEBVIEW.PlayoutSmoke;
|
||||
|
||||
var command = args.Length == 0 ? "--probe" : args[0].ToLowerInvariant();
|
||||
|
||||
return command switch
|
||||
var parsed = SmokeCommandLine.Parse(args);
|
||||
if (!parsed.IsValid)
|
||||
{
|
||||
"--probe" => Probe(),
|
||||
"--dry-run" => await DryRunAsync(),
|
||||
_ => Usage()
|
||||
return Usage(parsed.ErrorCode);
|
||||
}
|
||||
|
||||
using var cancellation = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (_, eventArgs) =>
|
||||
{
|
||||
eventArgs.Cancel = true;
|
||||
cancellation.Cancel();
|
||||
};
|
||||
|
||||
return parsed.Invocation.Kind switch
|
||||
{
|
||||
SmokeCommandKind.Probe => Probe(),
|
||||
SmokeCommandKind.DryRun => await DryRunAsync(),
|
||||
SmokeCommandKind.TestPlan => TestPlan(parsed.Invocation),
|
||||
SmokeCommandKind.TestConnect or SmokeCommandKind.TestSequence =>
|
||||
await RunIsolatedTestCommandAsync(parsed.Invocation, cancellation.Token),
|
||||
_ => Usage("unknown-command")
|
||||
};
|
||||
|
||||
static int Probe()
|
||||
@@ -42,8 +57,7 @@ static int Probe()
|
||||
|
||||
tornadoProcessCount++;
|
||||
var title = process.MainWindowTitle;
|
||||
if (title.Contains("PGM", StringComparison.OrdinalIgnoreCase) ||
|
||||
title.Contains("PROGRAM", StringComparison.OrdinalIgnoreCase))
|
||||
if (SmokeCommandLine.IsUnsafeTornadoWindowTitle(title))
|
||||
{
|
||||
programWindowDetected = true;
|
||||
}
|
||||
@@ -58,9 +72,12 @@ static int Probe()
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(new
|
||||
WriteJson(new
|
||||
{
|
||||
command = "probe",
|
||||
architecture = Environment.Is64BitProcess ? "x64" : "x86",
|
||||
connectRequestIssued = false,
|
||||
comActivationAttempted = false,
|
||||
runtimeRegistration = new
|
||||
{
|
||||
ready = runtimeRegistration.IsReady,
|
||||
@@ -76,9 +93,9 @@ static int Probe()
|
||||
programWindowDetected
|
||||
},
|
||||
safety = programWindowDetected
|
||||
? "PROGRAM window detected; no COM activation was attempted."
|
||||
? "PROGRAM or ambiguous Tornado window detected; no COM activation was attempted."
|
||||
: "Read-only probe complete; no COM activation was attempted."
|
||||
}, new JsonSerializerOptions { WriteIndented = true }));
|
||||
});
|
||||
|
||||
return runtimeRegistration.IsReady && Environment.Is64BitProcess
|
||||
? 0
|
||||
@@ -101,24 +118,124 @@ static async Task<int> DryRunAsync()
|
||||
await engine.DisconnectAsync()
|
||||
};
|
||||
|
||||
Console.WriteLine(JsonSerializer.Serialize(new
|
||||
WriteJson(new
|
||||
{
|
||||
command = "dry-run",
|
||||
mode = engine.Status.Mode.ToString(),
|
||||
state = engine.Status.State.ToString(),
|
||||
connectRequestIssued = false,
|
||||
comActivationAttempted = false,
|
||||
commands = results.Select(result => new
|
||||
{
|
||||
operation = result.Operation.ToString(),
|
||||
code = result.Code.ToString(),
|
||||
message = result.Message
|
||||
})
|
||||
}, new JsonSerializerOptions { WriteIndented = true }));
|
||||
});
|
||||
|
||||
return results.All(result => result.IsSuccess) ? 0 : 3;
|
||||
}
|
||||
|
||||
static int Usage()
|
||||
static int TestPlan(SmokeCommandInvocation invocation)
|
||||
{
|
||||
Console.Error.WriteLine("Usage: MBN_STOCK_WEBVIEW.PlayoutSmoke [--probe|--dry-run]");
|
||||
Console.Error.WriteLine("This tool never enables Test or Live mode and never activates K3D COM in dry-run.");
|
||||
try
|
||||
{
|
||||
var plan = IsolatedTestCommandPreflight.Create(invocation);
|
||||
WriteJson(new
|
||||
{
|
||||
command = "test-plan",
|
||||
mode = "Test",
|
||||
preflightValidated = true,
|
||||
runtimeProcessGateChecked = false,
|
||||
connectRequestIssued = false,
|
||||
comActivationAttempted = false,
|
||||
sceneCount = plan.PrepareCue is null ? 0 : 2,
|
||||
automaticReconnectEnabled = plan.Options.ReconnectEnabled,
|
||||
safety = "Configuration and scene assets validated only; no engine or COM was created."
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
catch (IsolatedTestPreflightException exception)
|
||||
{
|
||||
WritePreflightFailure("test-plan", exception.ErrorCode);
|
||||
return 65;
|
||||
}
|
||||
catch
|
||||
{
|
||||
WritePreflightFailure("test-plan", "internal-preflight-failure");
|
||||
return 70;
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<int> RunIsolatedTestCommandAsync(
|
||||
SmokeCommandInvocation invocation,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = invocation.Kind == SmokeCommandKind.TestConnect
|
||||
? "test-connect"
|
||||
: "test-sequence";
|
||||
IsolatedTestCommandPlan plan;
|
||||
try
|
||||
{
|
||||
plan = IsolatedTestCommandPreflight.Create(invocation);
|
||||
}
|
||||
catch (IsolatedTestPreflightException exception)
|
||||
{
|
||||
WritePreflightFailure(command, exception.ErrorCode);
|
||||
return 65;
|
||||
}
|
||||
catch
|
||||
{
|
||||
WritePreflightFailure(command, "internal-preflight-failure");
|
||||
return 70;
|
||||
}
|
||||
|
||||
var report = await IsolatedTestCommandExecutor.ExecuteAsync(plan, cancellationToken);
|
||||
WriteJson(report);
|
||||
if (report.Completed && !report.OutcomeUnknown)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return report.OutcomeUnknown ? 5 : 4;
|
||||
}
|
||||
|
||||
static void WritePreflightFailure(string command, string errorCode) =>
|
||||
WriteJson(new
|
||||
{
|
||||
command,
|
||||
mode = "Test",
|
||||
preflightValidated = false,
|
||||
runtimeProcessGateChecked = false,
|
||||
connectRequestIssued = false,
|
||||
comActivationAttempted = false,
|
||||
errorCode,
|
||||
safety = "Rejected before engine creation; no COM activation was attempted."
|
||||
});
|
||||
|
||||
static void WriteJson<T>(T value) =>
|
||||
Console.WriteLine(JsonSerializer.Serialize(value, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
}));
|
||||
|
||||
static int Usage(string? errorCode)
|
||||
{
|
||||
WriteJson(new
|
||||
{
|
||||
command = "invalid",
|
||||
connectRequestIssued = false,
|
||||
comActivationAttempted = false,
|
||||
errorCode = errorCode ?? "invalid-command-line"
|
||||
});
|
||||
Console.Error.WriteLine(
|
||||
"Usage: MBN_STOCK_WEBVIEW.PlayoutSmoke [--probe|--dry-run|--test-plan|--test-connect|--test-sequence]");
|
||||
Console.Error.WriteLine(
|
||||
$"Test commands require {SmokeCommandLine.AcknowledgementFlag} and --config <absolute-path>.");
|
||||
Console.Error.WriteLine(
|
||||
"--test-plan and --test-sequence also require --prepare <scene-code> --next <scene-code> --observe-ms <1000..30000>.");
|
||||
Console.Error.WriteLine(
|
||||
"Only --test-connect and --test-sequence may request COM, after all Test safety gates pass.");
|
||||
return 64;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user