Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion tests/ClientTest/ClientTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<UpgradeTestDir>..\UpgradeTest\bin\$(Configuration)\net10.0</UpgradeTestDir>
</PropertyGroup>
<ItemGroup>
<UpgradeTestFiles Include="$(UpgradeTestDir)\**\*" Exclude="$(UpgradeTestDir)\**\*.pdb;$(UpgradeTestDir)\**\*.json" />
<UpgradeTestFiles Include="$(UpgradeTestDir)\**\*" Exclude="$(UpgradeTestDir)\**\*.pdb" />
</ItemGroup>
<Copy SourceFiles="@(UpgradeTestFiles)"
DestinationFolder="$(OutputPath)"
Expand Down
150 changes: 47 additions & 103 deletions tests/ClientTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,7 @@

try
{
await RunOssClientAsync();
/*var isOssMode = args.Length > 0 && args[0] == "--oss";

if (isOssMode)
{
await RunOssClientAsync();
}
else
{
await RunStandardClientAsync();
}*/
await RunUpdateTestAsync();
}
catch (Exception ex)
{
Expand All @@ -27,67 +17,58 @@
Environment.Exit(1);
}

// NOTE: In the success path where a MainApp update is applied and the Upgrade
// process is launched, the Client process exits inside the bootstrap —
// WindowsStrategy.StartAppAsync() calls GracefulExit.CurrentProcessAsync().
// The code below is only reached when no update is needed, or when only
// Upgrade packages were applied (no MainApp IPC/launch).
Comment thread
Copilot marked this conversation as resolved.
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();

// ═══════════════════════════════════════════════════════════════════
// OSS Client mode — version JSON download → version compare → launch upgrade
// Core update test — runs the full non-silent immediate update flow.
//
// The update mode (chain vs. cross-version) is determined entirely by
// GeneralUpdate.Core's DownloadPlanBuilder, which inspects the server
// response and prefers CVP when its FromVersion matches the local
// client version. The test itself is mode-agnostic — configure the
// GeneralSpacestation server's data to exercise either path.
// ═══════════════════════════════════════════════════════════════════
static async Task RunOssClientAsync()
static async Task RunUpdateTestAsync()
{
Console.WriteLine("=== GeneralUpdate OSS Client Test ===");
Console.WriteLine("=== GeneralUpdate Client Test ===");
Console.WriteLine($"Started at {DateTime.Now}");
Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}");

// Only secrets are supplied in code. Identity fields (MainAppName,
// ClientVersion, UpdateAppName, UpdatePath) are read from
// generalupdate.manifest.json by OssStrategy via
// AppMetadataDiscoverer.Discover() — same as the standard flow.
var updateUrl = "http://localhost:5000/packages/versions.json";
var appSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6";
var updateUrl = "http://localhost:7391/Upgrade/Verification";
var reportUrl = "http://localhost:7391/Upgrade/Report";
var appSecretKey =
Environment.GetEnvironmentVariable("APP_SECRET_KEY")
?? "dfeb5833-975e-4afb-88f1-6278ee9aeff6";

Console.WriteLine($"UpdateUrl: {updateUrl}");
Console.WriteLine();

await new GeneralUpdateBootstrap()
.SetSource(updateUrl, appSecretKey)
.SetOption(Option.AppType, AppType.OssClient)
.Hooks<ClientTestHooks>()
.AddListenerMultiDownloadStatistics(OnDownloadStatistics)
.AddListenerMultiDownloadCompleted(OnDownloadCompleted)
.AddListenerMultiAllDownloadCompleted(OnAllDownloadCompleted)
.AddListenerMultiDownloadError(OnDownloadError)
.AddListenerException(OnException)
.AddListenerUpdateInfo(OnUpdateInfo)
.LaunchAsync();

Console.WriteLine("OSS Client test completed.");
}

// ═══════════════════════════════════════════════════════════════════
// Standard Client mode — silent poll with IPC handoff to Upgrade
// ═══════════════════════════════════════════════════════════════════
static async Task RunStandardClientAsync()
{
Console.WriteLine("=== GeneralUpdate Client Test (Silent Mode) ===");
Console.WriteLine($"Started at {DateTime.Now}");
Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}");

// Secrets come from code — never from files.
var updateUrl = "http://localhost:5000/Upgrade/Verification";
var reportUrl = "http://localhost:5000/Upgrade/Report";
var appSecretKey = Environment.GetEnvironmentVariable("APP_SECRET_KEY") ?? "dfeb5833-975e-4afb-88f1-6278ee9aeff6";

Console.WriteLine($"UpdateUrl: {updateUrl}");
Console.WriteLine($"Silent mode: ENABLED (poll every 1 minute)");
Console.WriteLine("NOTE: Configure the GeneralSpacestation server with");
Console.WriteLine(" the desired test data before running.");
Console.WriteLine(" - Chain data: TbPackets (IsCrossVersion=false)");
Console.WriteLine(" - Cross-version: additionally TbVersionArchives +");
Console.WriteLine(" TbPacket (IsCrossVersion=true,");
Console.WriteLine(" FromVersion=currentVersion)");
Console.WriteLine();

// Silent mode: polls server in background, prepares update, launches Upgrade on exit.
var bootstrap = await new GeneralUpdateBootstrap()
// Non-silent immediate update flow:
// 1. Version validation against server (HttpDownloadSource.ListAsync)
// 2. Event dispatch (UpdateInfoEventArgs — shows available versions)
// 3. Pre-check, hooks, backup
// 4. Download all packages via DefaultDownloadOrchestrator
// 5. Scenario dispatch:
// - UpgradeOnly: apply upgrade packages in-place, client continues
// - MainOnly: send MainApp versions via IPC → launch Upgrade process → exit
// - Both: apply upgrade packages → IPC → launch Upgrade process → exit
// - None: no-op
await new GeneralUpdateBootstrap()
.SetSource(updateUrl, appSecretKey, reportUrl)
.SetOption(Option.AppType, AppType.Client)
.SetOption(Option.Silent, true)
.SetOption(Option.SilentPollIntervalMinutes, 1)
.Hooks<ClientTestHooks>()
.AddListenerMultiDownloadStatistics(OnDownloadStatistics)
.AddListenerMultiDownloadCompleted(OnDownloadCompleted)
Expand All @@ -97,54 +78,11 @@ static async Task RunStandardClientAsync()
.AddListenerUpdateInfo(OnUpdateInfo)
.LaunchAsync();

var orchestrator = bootstrap.SilentOrchestrator;

Console.WriteLine();
Console.WriteLine("╔════════════════════════════════════════════╗");
Console.WriteLine("║ Silent poll running in background. ║");
Console.WriteLine("║ Press Ctrl+C or Enter to exit. ║");
Console.WriteLine("║ On exit, Upgrade process will be launched ║");
Console.WriteLine("║ if an update has been prepared. ║");
Console.WriteLine("╚════════════════════════════════════════════╝");
Console.WriteLine();

// Keep the process alive so the background poll loop can work.
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
Console.WriteLine();
Console.WriteLine("[Shutdown] Ctrl+C pressed. Exiting...");
e.Cancel = true;
cts.Cancel();
};

try
{
await Task.Delay(Timeout.Infinite, cts.Token);
}
catch (OperationCanceledException)
{
// Expected on Ctrl+C — graceful shutdown
}

Console.WriteLine("[Shutdown] Launching upgrade process...");
if (orchestrator != null && orchestrator.HasPreparedUpdate)
{
var launched = orchestrator.TryLaunchUpgrade();
Console.WriteLine(launched
? "[Shutdown] Upgrade process launched successfully."
: "[Shutdown] No update prepared or upgrade already launched.");
}
else
{
Console.WriteLine("[Shutdown] No orchestrator or no update prepared.");
}

Console.WriteLine("[Shutdown] Client test exiting gracefully.");
Console.WriteLine("Update test completed.");
}

// ═══════════════════════════════════════════════════════════════════
// Event handlers (shared across both modes)
// Event handlers
// ═══════════════════════════════════════════════════════════════════

static void OnDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e)
Expand Down Expand Up @@ -183,7 +121,13 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e)
if (e.Info?.Body is { Count: > 0 })
{
foreach (var vi in e.Info.Body)
Console.WriteLine($" - {vi.Version} ({vi.Name}) [{vi.Size} bytes] {(vi.IsForcibly == true ? "(forced)" : "")}");
{
var mode = vi.IsCrossVersion == true ? "CVP" : "Chain";
Console.WriteLine($" - [{mode}] {vi.Version} ({vi.Name}) [{vi.Size} bytes] " +
$"AppType={(vi.AppType == 1 ? "Client" : "Upgrade")} " +
$"{(vi.IsForcibly == true ? "(forced)" : "")}" +
$"{(!string.IsNullOrEmpty(vi.FromVersion) ? $" from={vi.FromVersion}" : "")}");
Comment on lines +125 to +135
}
}
else
{
Expand All @@ -192,7 +136,7 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e)
}

// ═══════════════════════════════════════════════════════════════════
// Hooks (shared across both modes)
// Hooks
// ═══════════════════════════════════════════════════════════════════

sealed class ClientTestHooks : IUpdateHooks
Expand Down
77 changes: 20 additions & 57 deletions tests/UpgradeTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,16 @@

try
{
await RunOssUpgradeAsync();
/*var isOssMode = args.Length > 0 && args[0] == "--oss";

if (isOssMode)
{
await RunOssUpgradeAsync();
}
else
{
await RunStandardUpgradeAsync();
}*/
await RunStandardUpgradeAsync();
}
catch (Exception ex)
{
Console.WriteLine($"FATAL: {ex}");
Environment.Exit(1);
}

// ═══════════════════════════════════════════════════════════════════
// OSS Upgrade mode — read versions.json → download packages → decompress → launch main app
// ═══════════════════════════════════════════════════════════════════
static async Task RunOssUpgradeAsync()
{
Console.WriteLine("=== GeneralUpdate OSS Upgrade Test ===");
Console.WriteLine($"Started at {DateTime.Now}");
Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}");

// OssUpgrade runs from a subdirectory (e.g. update/), but the manifest
// and versions.json are in the parent directory (same as OssClient).
// InstallPath resolves to the parent so OssStrategy reads the correct
// manifest and versions.json.
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
var installPath = Path.GetFullPath(Path.Combine(baseDir, ".."));

Console.WriteLine($"BaseDir={baseDir}");
Console.WriteLine($"InstallPath={installPath} (parent, where manifest + versions.json live)");
Console.WriteLine();

// Only secrets and InstallPath are supplied in code. Identity fields
// (MainAppName, ClientVersion, UpdateAppName) are read from
// generalupdate.manifest.json by OssStrategy via
// AppMetadataDiscoverer.Discover() — same as the standard OSS client flow.
await new GeneralUpdateBootstrap()
.SetSource(
"http://localhost:5000/packages/versions.json",
"dfeb5833-975e-4afb-88f1-6278ee9aeff6",
installPath: installPath)
.SetOption(Option.AppType, AppType.OssUpgrade)
.Hooks<UpgradeTestHooks>()
.AddListenerMultiDownloadStatistics(OnDownloadStatistics)
.AddListenerMultiDownloadCompleted(OnDownloadCompleted)
.AddListenerMultiAllDownloadCompleted(OnAllDownloadCompleted)
.AddListenerMultiDownloadError(OnDownloadError)
.AddListenerException(OnException)
.LaunchAsync();

Console.WriteLine("OSS Upgrade test completed.");
}
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();

// ═══════════════════════════════════════════════════════════════════
// Standard Upgrade mode — read IPC config → apply patches → launch main app
Expand All @@ -75,8 +27,15 @@ static async Task RunStandardUpgradeAsync()
Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}");

// Config comes from the encrypted IPC file written by the Client process.
// The Client's generalupdate.manifest.json + SetSource flows through IPC,
// so the Upgrade never needs to load a manifest directly.
// The generalupdate.manifest.json lives in the Client's directory; the Upgrade
// process receives all necessary identity fields and update versions through
// the ProcessContract via EncryptedFileProcessContractProvider.Receive().
//
// When no IPC file exists (first run, no update pending), the bootstrap
// treats it as a no-op and returns gracefully — no error, no crash.
Console.WriteLine("Reading IPC process contract (written by Client process)...");
Console.WriteLine("IPC file: %TEMP%/GeneralUpdate/ipc/process_info.enc");
Console.WriteLine();

await new GeneralUpdateBootstrap()
.SetOption(Option.AppType, AppType.Upgrade)
Expand All @@ -88,11 +47,15 @@ static async Task RunStandardUpgradeAsync()
.AddListenerException(OnException)
.LaunchAsync();

// NOTE: After the update pipeline completes and the main app is launched,
// WindowsStrategy.StartAppAsync() calls GracefulExit.CurrentProcessAsync(),
// which terminates the Upgrade process. This line is only reached when no
// updates were found (no IPC data) or when update application fails.
Comment thread
Copilot marked this conversation as resolved.
Console.WriteLine("Upgrade test completed.");
}

// ═══════════════════════════════════════════════════════════════════
// Event handlers (shared across both modes)
// Event handlers
// ═══════════════════════════════════════════════════════════════════

static void OnDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e)
Expand Down Expand Up @@ -126,7 +89,7 @@ static void OnException(object sender, ExceptionEventArgs e)
}

// ═══════════════════════════════════════════════════════════════════
// Hooks (shared across both modes)
// Hooks
// ═══════════════════════════════════════════════════════════════════

sealed class UpgradeTestHooks : IUpdateHooks
Expand All @@ -146,8 +109,8 @@ public async Task OnDownloadCompletedAsync(DownloadContext ctx)
public async Task OnAfterUpdateAsync(HookContext ctx)
{
Console.WriteLine($"[Hook] OnAfterUpdate: {ctx.CurrentVersion} -> {ctx.TargetVersion}");
// Manifest ClientVersion is updated by OssStrategy itself via
// ManifestInfo.TryUpdateVersion() after decompression.
// Manifest ClientVersion is updated by UpdateStrategy itself via
// ManifestInfo.TryUpdateVersion() after successful apply.
await Task.CompletedTask;
}

Expand Down
Loading