242 lines
7.1 KiB
C#
242 lines
7.1 KiB
C#
using System.Diagnostics;
|
|
using System.Text.Json;
|
|
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 parsed = SmokeCommandLine.Parse(args);
|
|
if (!parsed.IsValid)
|
|
{
|
|
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()
|
|
{
|
|
var runtimeRegistration = new K3dRegistrationProbe().Probe();
|
|
var processInspectionAvailable = true;
|
|
var tornadoProcessCount = 0;
|
|
var programWindowDetected = false;
|
|
Process[] processes;
|
|
try
|
|
{
|
|
processes = Process.GetProcesses();
|
|
}
|
|
catch
|
|
{
|
|
processes = [];
|
|
processInspectionAvailable = false;
|
|
}
|
|
|
|
foreach (var process in processes)
|
|
{
|
|
try
|
|
{
|
|
if (!process.ProcessName.StartsWith("Tornado2", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
tornadoProcessCount++;
|
|
var title = process.MainWindowTitle;
|
|
if (SmokeCommandLine.IsUnsafeTornadoWindowTitle(title))
|
|
{
|
|
programWindowDetected = true;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// A process may exit or deny inspection between enumeration and reading.
|
|
}
|
|
finally
|
|
{
|
|
process.Dispose();
|
|
}
|
|
}
|
|
|
|
WriteJson(new
|
|
{
|
|
command = "probe",
|
|
architecture = Environment.Is64BitProcess ? "x64" : "x86",
|
|
connectRequestIssued = false,
|
|
comActivationAttempted = false,
|
|
runtimeRegistration = new
|
|
{
|
|
ready = runtimeRegistration.IsReady,
|
|
issues = runtimeRegistration.Issues.ToString(),
|
|
engineClassReady = runtimeRegistration.IsEngineClassReady,
|
|
eventHandlerClassReady = runtimeRegistration.IsEventHandlerClassReady,
|
|
message = runtimeRegistration.Message
|
|
},
|
|
tornado = new
|
|
{
|
|
inspectionAvailable = processInspectionAvailable,
|
|
processCount = tornadoProcessCount,
|
|
programWindowDetected
|
|
},
|
|
safety = programWindowDetected
|
|
? "PROGRAM or ambiguous Tornado window detected; no COM activation was attempted."
|
|
: "Read-only probe complete; no COM activation was attempted."
|
|
});
|
|
|
|
return runtimeRegistration.IsReady && Environment.Is64BitProcess
|
|
? 0
|
|
: 2;
|
|
}
|
|
|
|
static async Task<int> DryRunAsync()
|
|
{
|
|
var options = new PlayoutOptions { Mode = PlayoutMode.DryRun };
|
|
await using var engine = PlayoutEngineFactory.Create(options);
|
|
var cue = new PlayoutCue("DRY_RUN_ONLY.t2s", "DRY_RUN_ONLY");
|
|
|
|
var results = new[]
|
|
{
|
|
await engine.ConnectAsync(),
|
|
await engine.PrepareAsync(cue),
|
|
await engine.TakeInAsync(),
|
|
await engine.NextAsync(cue with { SceneName = "DRY_RUN_NEXT" }),
|
|
await engine.TakeOutAsync(PlayoutTakeOutScope.All),
|
|
await engine.DisconnectAsync()
|
|
};
|
|
|
|
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
|
|
})
|
|
});
|
|
|
|
return results.All(result => result.IsSuccess) ? 0 : 3;
|
|
}
|
|
|
|
static int TestPlan(SmokeCommandInvocation invocation)
|
|
{
|
|
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;
|
|
}
|