feat(launcher): complete Campaign LA11 pre-gate support
This commit is contained in:
parent
f881e5b467
commit
134edabed2
19 changed files with 433 additions and 55 deletions
|
|
@ -160,18 +160,23 @@ static async Task<int> BootstrapProbeAsync(string[] arguments)
|
|||
|
||||
static int CanonicalProbe(string[] arguments)
|
||||
{
|
||||
if (arguments.Length != 1)
|
||||
if (arguments.Length < 1)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
string suffix = arguments.Length == 1
|
||||
? string.Empty
|
||||
: Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, arguments[1..]);
|
||||
File.WriteAllText(
|
||||
Path.GetFullPath(arguments[0]),
|
||||
Environment.ProcessId.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||
+ "|"
|
||||
+ Path.GetFullPath(
|
||||
Environment.ProcessPath
|
||||
?? throw new InvalidOperationException("Process path is unavailable.")));
|
||||
?? throw new InvalidOperationException("Process path is unavailable."))
|
||||
+ suffix);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -279,25 +279,43 @@ public sealed class LauncherSelfUpdateManagerTests : IDisposable
|
|||
public async Task BootstrapConfirmationAndOrdinaryStartupDoNotUseShellParsing()
|
||||
{
|
||||
using var harness = new Harness(_root);
|
||||
string[] publicArguments =
|
||||
[
|
||||
"--config-dir", Path.Combine(_root, "config with spaces"),
|
||||
"--data-dir", Path.Combine(_root, "data & literal"),
|
||||
"--cache-dir", Path.Combine(_root, "cache"),
|
||||
"--update-manifest-uri", "http://127.0.0.1:43119/manifest.json",
|
||||
];
|
||||
SelfUpdateStartupResult ordinary = await LauncherSelfUpdateBootstrap.HandleAsync(
|
||||
["--literal", "argument with spaces & metacharacters"],
|
||||
publicArguments,
|
||||
harness.Manager,
|
||||
harness.Target,
|
||||
harness.LauncherPath);
|
||||
Assert.False(ordinary.ShouldExit);
|
||||
Assert.Equal(["--literal", "argument with spaces & metacharacters"],
|
||||
ordinary.RemainingArguments);
|
||||
Assert.Equal(publicArguments, ordinary.RemainingArguments);
|
||||
|
||||
SelfUpdateStartupResult deferred = await LauncherSelfUpdateBootstrap.HandleAsync(
|
||||
[LauncherSelfUpdateBootstrap.DeferredArgument, .. publicArguments],
|
||||
harness.Manager,
|
||||
harness.Target,
|
||||
harness.LauncherPath);
|
||||
Assert.False(deferred.ShouldExit);
|
||||
Assert.Equal(publicArguments, deferred.RemainingArguments);
|
||||
|
||||
_ = await harness.StageAsync();
|
||||
SelfUpdatePlan applied = await harness.Manager.ApplyPendingAsync(harness.Target);
|
||||
SelfUpdateStartupResult confirmation = await LauncherSelfUpdateBootstrap.HandleAsync(
|
||||
[LauncherSelfUpdateBootstrap.ConfirmArgument, applied.TransactionId],
|
||||
[
|
||||
LauncherSelfUpdateBootstrap.ConfirmArgument,
|
||||
applied.TransactionId,
|
||||
.. publicArguments,
|
||||
],
|
||||
harness.Manager,
|
||||
harness.Target,
|
||||
harness.LauncherPath);
|
||||
|
||||
Assert.False(confirmation.ShouldExit);
|
||||
Assert.Empty(confirmation.RemainingArguments);
|
||||
Assert.Equal(publicArguments, confirmation.RemainingArguments);
|
||||
Assert.False(File.Exists(harness.Manager.PendingPlanPath));
|
||||
Assert.False(harness.Manager.IsConfirmed(applied.TransactionId));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ public sealed class LauncherSelfUpdateProcessTests : IDisposable
|
|||
string ready = Path.Combine(_root, "crash.ready");
|
||||
string launched = Path.Combine(_root, "replacement.ready");
|
||||
string helperPidPath = Path.Combine(_root, "helper.pid");
|
||||
string[] processLocalSuffix =
|
||||
[
|
||||
"--config-dir", Path.Combine(_root, "isolated config"),
|
||||
"--data-dir", Path.Combine(_root, "isolated data"),
|
||||
"--cache-dir", Path.Combine(_root, "isolated cache"),
|
||||
"--update-manifest-uri", "http://127.0.0.1:43119/manifest.json",
|
||||
];
|
||||
Directory.CreateDirectory(_root);
|
||||
string rid = LauncherRuntimeIdentity.DetectRid();
|
||||
PreparedLauncher prepared = PrepareLauncherClosure(target, rid);
|
||||
|
|
@ -76,7 +83,7 @@ public sealed class LauncherSelfUpdateProcessTests : IDisposable
|
|||
};
|
||||
using Process canonical = StartProcess(
|
||||
prepared.CanonicalPath,
|
||||
["canonical-probe", launched],
|
||||
["canonical-probe", launched, .. processLocalSuffix],
|
||||
environment);
|
||||
await canonical.WaitForExitAsync().WaitAsync(TimeSpan.FromSeconds(20));
|
||||
Assert.Equal(0, canonical.ExitCode);
|
||||
|
|
@ -100,7 +107,12 @@ public sealed class LauncherSelfUpdateProcessTests : IDisposable
|
|||
SearchOption.TopDirectoryOnly));
|
||||
Assert.Null(await manager.LoadPendingAsync());
|
||||
|
||||
int replacementPid = ParsePid(await File.ReadAllTextAsync(launched));
|
||||
string launchMarker = await File.ReadAllTextAsync(launched);
|
||||
Assert.EndsWith(
|
||||
Environment.NewLine + string.Join(Environment.NewLine, processLocalSuffix),
|
||||
launchMarker,
|
||||
StringComparison.Ordinal);
|
||||
int replacementPid = ParsePid(launchMarker);
|
||||
int helperPid = int.Parse(
|
||||
await File.ReadAllTextAsync(helperPidPath),
|
||||
System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,26 @@ public sealed class LauncherVersionTests
|
|||
|
||||
public sealed class ReleaseManifestClientTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("https://updates.example.test/manifest.json")]
|
||||
[InlineData("http://127.0.0.1:43119/manifest.json")]
|
||||
[InlineData("http://localhost:43119/manifest.json")]
|
||||
public void LocalUpdateFeedOverrideAcceptsOnlySecureOrLoopbackFeeds(string value)
|
||||
{
|
||||
using ReleaseManifestClient source =
|
||||
ReleaseManifestClient.CreateLocalUpdateFeedOverride(new Uri(value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://updates.example.test/manifest.json")]
|
||||
[InlineData("file:///tmp/manifest.json")]
|
||||
[InlineData("https://user:secret@updates.example.test/manifest.json")]
|
||||
[InlineData("https://updates.example.test/manifest.json?token=secret")]
|
||||
[InlineData("https://updates.example.test/manifest.json#fragment")]
|
||||
public void LocalUpdateFeedOverrideRejectsRemoteHttpAndCredentialLikeUris(string value) =>
|
||||
Assert.Throws<LauncherUpdateException>(() =>
|
||||
ReleaseManifestClient.CreateLocalUpdateFeedOverride(new Uri(value)));
|
||||
|
||||
[Fact]
|
||||
public async Task FetchesStrictManifestFromLoopbackAndPinsProductionFeed()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -146,6 +146,78 @@ public sealed class LauncherStartupOptionsTests
|
|||
Assert.Equal(helperOptions.UpdateManifestUri, confirmationOptions.UpdateManifestUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeferredSelfUpdateRestartRetainsIsolationWithoutResolvingDefaults()
|
||||
{
|
||||
string root = Path.GetFullPath(
|
||||
Path.Combine(Path.GetTempPath(), "acdream-la11-deferred"));
|
||||
string[] suffix =
|
||||
[
|
||||
"--config-dir", Path.Combine(root, "config"),
|
||||
"--data-dir", Path.Combine(root, "data"),
|
||||
"--cache-dir", Path.Combine(root, "cache"),
|
||||
"--update-manifest-uri", "http://127.0.0.1:43119/manifest.json",
|
||||
];
|
||||
|
||||
LauncherStartupOptions options = LauncherStartupOptions.Parse(
|
||||
["--acdream-self-update-deferred-v1", .. suffix],
|
||||
() => throw new InvalidOperationException(
|
||||
"canonical path resolver was touched"));
|
||||
|
||||
Assert.Equal(LauncherStartupMode.SelfUpdateDeferred, options.Mode);
|
||||
Assert.Equal(suffix, options.PublicArguments);
|
||||
Assert.Equal(Path.Combine(root, "config"), options.Paths.ConfigDirectory);
|
||||
Assert.Equal(Path.Combine(root, "data"), options.Paths.DataDirectory);
|
||||
Assert.Equal(Path.Combine(root, "cache"), options.Paths.CacheDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AvaloniaCompositionRetainsTheExactParsedOptionsAndPathSet()
|
||||
{
|
||||
string root = Path.GetFullPath(
|
||||
Path.Combine(Path.GetTempPath(), "acdream-la11-app-composition"));
|
||||
LauncherStartupOptions options = LauncherStartupOptions.Parse(
|
||||
[
|
||||
"--config-dir", Path.Combine(root, "config"),
|
||||
"--data-dir", Path.Combine(root, "data"),
|
||||
"--cache-dir", Path.Combine(root, "cache"),
|
||||
"--update-manifest-uri", "https://updates.example.test/manifest.json",
|
||||
],
|
||||
() => throw new InvalidOperationException(
|
||||
"canonical path resolver was touched"));
|
||||
|
||||
var app = new App(options);
|
||||
|
||||
Assert.Same(options, app.StartupOptions);
|
||||
Assert.Same(options.Paths, app.StartupOptions.Paths);
|
||||
Assert.Equal(
|
||||
new Uri("https://updates.example.test/manifest.json"),
|
||||
app.StartupOptions.UpdateManifestUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompositionRejectsAnyBootstrapArgumentDrift()
|
||||
{
|
||||
var paths = new ApplicationPathSet("config", "data", "cache", null);
|
||||
LauncherStartupOptions options = LauncherStartupOptions.Parse(
|
||||
["--update-manifest-uri", "https://updates.example.test/manifest.json"],
|
||||
() => paths);
|
||||
|
||||
Program.RequireUnchangedPublicArguments(
|
||||
options,
|
||||
new SelfUpdateStartupResult(
|
||||
false,
|
||||
0,
|
||||
options.PublicArguments.ToArray()));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
Program.RequireUnchangedPublicArguments(
|
||||
options,
|
||||
new SelfUpdateStartupResult(
|
||||
false,
|
||||
0,
|
||||
["--update-manifest-uri", "https://other.example.test/manifest.json"])));
|
||||
}
|
||||
|
||||
public static TheoryData<string[]> InvalidArguments()
|
||||
{
|
||||
string absolute = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "acdream-la11"));
|
||||
|
|
@ -171,6 +243,10 @@ public sealed class LauncherStartupOptionsTests
|
|||
data.Add(["--update-manifest-uri", "file:///tmp/manifest.json"]);
|
||||
data.Add(
|
||||
["--update-manifest-uri", "https://user:secret@example.test/manifest.json"]);
|
||||
data.Add(
|
||||
["--update-manifest-uri", "https://example.test/manifest.json?token=secret"]);
|
||||
data.Add(
|
||||
["--update-manifest-uri", "https://example.test/manifest.json#fragment"]);
|
||||
data.Add(["--update-manifest-uri", "not-a-uri"]);
|
||||
data.Add(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -62,4 +62,44 @@ public sealed class LauncherUpdateCompositionTests : IDisposable
|
|||
() => composition.Updater.CheckAsync());
|
||||
Assert.Contains(exception.Message, updateError.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://updates.example.test/manifest.json")]
|
||||
[InlineData("http://127.0.0.1:43119/manifest.json")]
|
||||
public void ProcessLocalManifestOverrideReachesOnlyUpdateComposition(string value)
|
||||
{
|
||||
Directory.CreateDirectory(_root);
|
||||
var paths = new ApplicationPathSet(
|
||||
Path.Combine(_root, "config"),
|
||||
Path.Combine(_root, "data"),
|
||||
Path.Combine(_root, "cache"),
|
||||
null);
|
||||
var manifestUri = new Uri(value);
|
||||
|
||||
using LauncherUpdateComposition composition = LauncherUpdateComposition.Create(
|
||||
paths,
|
||||
LauncherRuntimeIdentity.DetectRid(),
|
||||
LauncherVersion.Parse("1.0.0"),
|
||||
_root,
|
||||
() => false,
|
||||
initialize: (_, _) => new ClientVersionResolution(
|
||||
ClientVersionState.Missing,
|
||||
"No client version is installed.",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null),
|
||||
updateManifestUri: manifestUri);
|
||||
|
||||
Assert.Same(manifestUri, composition.UpdateManifestUri);
|
||||
Assert.Equal(
|
||||
Path.Combine(paths.DataDirectory, "app"),
|
||||
composition.Versions.AppDirectory);
|
||||
Assert.False(File.Exists(
|
||||
Path.Combine(paths.ConfigDirectory, "launcher-profiles.json")));
|
||||
Assert.Empty(Directory.EnumerateFiles(
|
||||
_root,
|
||||
"*",
|
||||
SearchOption.AllDirectories));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue