using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Data.Sqlite; using YMhut.Box.Core.App; using YMhut.Box.Core.Startup; namespace YMhut.Box.Tests; [TestClass] [DoNotParallelize] public sealed class StartupCheckTests { [TestMethod] public async Task FastPreflightPassesForPureLangInstallLayout() { using var workspace = TestWorkspace.Create("ymhut-startup-fast"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var store = new StartupCheckStore(paths); var service = new InstallIntegrityCheckService(paths, store); var report = await service.RunFastPreflightAsync(); Assert.AreEqual(StartupCheckKind.FastPreflight, report.Kind); Assert.AreEqual(0, report.CriticalIssueCount); Assert.IsTrue(report.Items.Any(item => item.Id == "lang:zh-CN" && item.Status == StartupCheckStatus.Passed)); Assert.IsTrue(report.Items.Any(item => item.Id == "lang:en-US" && item.Status == StartupCheckStatus.Passed)); Assert.IsFalse(Directory.Exists(Path.Combine(appRoot, "Runtime"))); StringAssert.StartsWith(store.DatabasePath, Path.Combine(appRoot, "Logs")); } [TestMethod] public async Task FastPreflightDoesNotBlockDevelopmentRootCultureLayout() { using var workspace = TestWorkspace.Create("ymhut-startup-dev-layout"); var installRoot = CreateDevelopmentInstall(workspace); var appRoot = workspace.CreateDirectory("user"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunFastPreflightAsync(); var resourcesPri = report.Items.FirstOrDefault(item => item.Id == "file:resources.pri"); var zhCn = report.Items.FirstOrDefault(item => item.Id == "lang:zh-CN"); var enUs = report.Items.FirstOrDefault(item => item.Id == "lang:en-US"); Assert.AreEqual(0, report.CriticalIssueCount); Assert.IsNotNull(resourcesPri); Assert.AreEqual(StartupCheckStatus.Missing, resourcesPri.Status); Assert.AreEqual(StartupCheckSeverity.Warning, resourcesPri.Severity); Assert.IsNotNull(zhCn); Assert.AreEqual(StartupCheckStatus.Passed, zhCn.Status); Assert.IsNotNull(enUs); Assert.AreEqual(StartupCheckStatus.Passed, enUs.Status); Assert.IsFalse(report.Items.Any(item => item.Id.StartsWith("lang:root:", StringComparison.OrdinalIgnoreCase))); } [TestMethod] public async Task FastPreflightAcceptsWinUiDevelopmentPriAndRootCultureSatellites() { using var workspace = TestWorkspace.Create("ymhut-startup-dev-pri"); var installRoot = CreateDevelopmentInstall(workspace); var appRoot = workspace.CreateDirectory("user"); workspace.CreateFile(["install", "YMhutBox.pri"], string.Empty); workspace.CreateFile(["install", "fr-FR", "Microsoft.ui.xaml.dll.mui"], string.Empty); workspace.CreateFile(["install", "ja-JP", "Microsoft.ui.xaml.dll.mui"], string.Empty); workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty); workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }"""); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunFastPreflightAsync(); Assert.AreEqual(0, report.VisibleIssueCount); Assert.IsTrue(report.Items.Any(item => item.Id == "manifest" && item.Status == StartupCheckStatus.Skipped)); Assert.IsTrue(report.Items.Any(item => item.Id == "file:resources.pri" && item.Status == StartupCheckStatus.Passed)); Assert.IsTrue(report.Items.Any(item => item.Id == "lang:root-culture-summary" && item.Status == StartupCheckStatus.Skipped)); Assert.IsFalse(report.Items.Any(item => item.Id.StartsWith("lang:root:", StringComparison.OrdinalIgnoreCase))); } [TestMethod] public async Task CurrentHistoryNormalizesLegacyDevelopmentLayoutFalsePositives() { using var workspace = TestWorkspace.Create("ymhut-startup-legacy-false-positive"); var installRoot = CreateDevelopmentInstall(workspace); var appRoot = workspace.CreateDirectory("user"); workspace.CreateFile(["install", "YMhutBox.pri"], string.Empty); workspace.CreateFile(["install", "fr-FR", "Microsoft.ui.xaml.dll.mui"], string.Empty); workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty); workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }"""); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var store = new StartupCheckStore(paths); var manifestIdentity = InstallRootContext.BuildManifestIdentity(installRoot); var oldReport = StartupCheckReport.Create( StartupCheckKind.FastPreflight, new DateTimeOffset(2026, 6, 1, 8, 0, 0, TimeSpan.Zero), installRoot, manifestIdentity, InstallRootContext.BuildInstallIdentity(installRoot, manifestIdentity), $"installRoot={installRoot}; manifest={manifestIdentity}", [ new StartupCheckItem("manifest", "安装清单", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "文件缺失", Path.Combine(installRoot, "config", "install-manifest.ini")), new StartupCheckItem("file:resources.pri", "resources.pri", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "文件缺失", Path.Combine(installRoot, "resources.pri")), new StartupCheckItem("lang:zh-CN", "语言资源 zh-CN", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "语言目录缺失", Path.Combine(installRoot, "lang", "zh-CN")), new StartupCheckItem("lang:root:fr-FR", "根目录语言资源 fr-FR", StartupCheckSeverity.Warning, StartupCheckStatus.Failed, "旧版误判", Path.Combine(installRoot, "fr-FR")) ]); await store.SaveAsync(oldReport); var service = new InstallIntegrityCheckService(paths, store); var latest = await service.GetLatestCurrentReportAsync(StartupCheckKind.FastPreflight); var history = await service.GetCurrentHistoryAsync(10); Assert.IsNotNull(latest); Assert.AreEqual(0, latest.VisibleIssueCount); Assert.AreEqual(StartupCheckStatus.Skipped, latest.Items.First(item => item.Id == "manifest").Status); Assert.AreEqual(StartupCheckStatus.Passed, latest.Items.First(item => item.Id == "file:resources.pri").Status); Assert.AreEqual(StartupCheckStatus.Passed, latest.Items.First(item => item.Id == "lang:zh-CN").Status); Assert.AreEqual(StartupCheckStatus.Skipped, latest.Items.First(item => item.Id == "lang:root:fr-FR").Status); Assert.AreEqual(0, history[0].VisibleIssueCount); } [TestMethod] public async Task MonthlyIntegrityUsesRecentReportUntilManifestIdentityChanges() { using var workspace = TestWorkspace.Create("ymhut-startup-monthly"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); var now = new DateTimeOffset(2026, 6, 14, 9, 0, 0, TimeSpan.Zero); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var store = new StartupCheckStore(paths); var service = new InstallIntegrityCheckService(paths, store, clock: () => now); var first = await service.RunMonthlyIntegrityCheckAsync(); now = now.AddDays(5); var cached = await service.RunMonthlyIntegrityCheckAsync(); Assert.AreEqual(first.Id, cached.Id); File.AppendAllText(Path.Combine(installRoot, InstallManifest.RelativePath.Replace('/', Path.DirectorySeparatorChar)), Environment.NewLine + "; changed"); now = now.AddDays(1); var changed = await service.RunMonthlyIntegrityCheckAsync(); Assert.AreNotEqual(first.Id, changed.Id); } [TestMethod] public async Task MonthlyIntegrityDoesNotReuseReportFromDifferentInstallRoot() { using var workspace = TestWorkspace.Create("ymhut-startup-monthly-root"); var installRoot = CreateHealthyInstall(workspace); var secondRoot = workspace.CreateDirectory("install2"); CopyDirectory(installRoot, secondRoot); var appRoot = workspace.CreateDirectory("user"); var now = new DateTimeOffset(2026, 6, 14, 9, 0, 0, TimeSpan.Zero); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var store = new StartupCheckStore(paths); var service = new InstallIntegrityCheckService(paths, store, clock: () => now); var first = await service.RunMonthlyIntegrityCheckAsync(); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, secondRoot); now = now.AddDays(1); var second = await service.RunMonthlyIntegrityCheckAsync(); Assert.AreNotEqual(first.Id, second.Id); Assert.AreNotEqual(first.InstallIdentity, second.InstallIdentity); } [TestMethod] public async Task IntegrityReportsMissingExternalToolLaunchableAsWarning() { using var workspace = TestWorkspace.Create("ymhut-startup-tool-missing"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); Directory.CreateDirectory(Path.Combine(installRoot, "Tools", "Sample")); File.WriteAllText(Path.Combine(installRoot, "Tools", "Sample", "present.exe"), string.Empty); File.AppendAllText( Path.Combine(installRoot, InstallManifest.RelativePath.Replace('/', Path.DirectorySeparatorChar)), Environment.NewLine + "Tools/Sample/missing.exe"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunManualIntegrityCheckAsync(); var missing = report.Items.FirstOrDefault(item => item.Id == "file:Tools/Sample/missing.exe"); Assert.IsNotNull(missing); Assert.AreEqual(StartupCheckStatus.Missing, missing.Status); Assert.AreEqual(StartupCheckSeverity.Warning, missing.Severity); } [TestMethod] public async Task UserDataRuntimePayloadIsRemovedBeforePreflight() { using var workspace = TestWorkspace.Create("ymhut-startup-userdata"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); var runtimeRoot = Path.Combine(appRoot, "Runtime"); var runtimesRoot = Path.Combine(appRoot, "runtimes"); Directory.CreateDirectory(runtimeRoot); Directory.CreateDirectory(runtimesRoot); File.WriteAllText(Path.Combine(runtimeRoot, "YMhutBox.exe"), string.Empty); File.WriteAllText(Path.Combine(runtimesRoot, "Microsoft.ui.xaml.dll"), string.Empty); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); Assert.IsFalse(Directory.Exists(runtimeRoot)); Assert.IsFalse(Directory.Exists(runtimesRoot)); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunFastPreflightAsync(); var runtime = report.Items.FirstOrDefault(item => item.Id == "userdata:runtime"); Assert.IsNotNull(runtime); Assert.AreEqual(StartupCheckStatus.Passed, runtime.Status); Assert.IsFalse(Directory.Exists(runtimeRoot)); Assert.IsFalse(Directory.Exists(runtimesRoot)); } [TestMethod] public async Task UserDataToolsAndMetadataPayloadsAreRemovedBeforePreflight() { using var workspace = TestWorkspace.Create("ymhut-startup-userdata-tools"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var toolsRoot = Path.Combine(appRoot, "Tools"); var metadataRoot = Path.Combine(appRoot, "Metadata"); Directory.CreateDirectory(toolsRoot); Directory.CreateDirectory(metadataRoot); File.WriteAllText(Path.Combine(toolsRoot, "YMhutBox.exe"), string.Empty); File.WriteAllText(Path.Combine(metadataRoot, "tools.json"), "{}"); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunFastPreflightAsync(); var tools = report.Items.FirstOrDefault(item => item.Id == "userdata:tools"); var metadata = report.Items.FirstOrDefault(item => item.Id == "userdata:metadata"); Assert.IsNotNull(tools); Assert.IsNotNull(metadata); Assert.AreEqual(StartupCheckStatus.Passed, tools.Status); Assert.AreEqual(StartupCheckStatus.Passed, metadata.Status); Assert.IsFalse(Directory.Exists(toolsRoot)); Assert.IsFalse(Directory.Exists(metadataRoot)); Assert.AreEqual(0, report.VisibleIssueCount); } [TestMethod] public async Task CurrentHistoryFiltersOutHistoricalInstallReports() { using var workspace = TestWorkspace.Create("ymhut-startup-current-history"); var installRoot = CreateHealthyInstall(workspace); var secondRoot = workspace.CreateDirectory("install2"); CopyDirectory(installRoot, secondRoot); var appRoot = workspace.CreateDirectory("user"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var store = new StartupCheckStore(paths); var service = new InstallIntegrityCheckService(paths, store); var oldReport = await service.RunManualIntegrityCheckAsync(); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, secondRoot); var currentReport = await service.RunManualIntegrityCheckAsync(); var all = await service.GetHistorySummariesAsync(); var current = await service.GetCurrentHistoryAsync(); Assert.IsTrue(all.Any(summary => summary.Id == oldReport.Id && !summary.IsCurrentInstall)); Assert.IsTrue(current.All(summary => summary.IsCurrentInstall)); Assert.IsTrue(current.Any(summary => summary.Id == currentReport.Id)); Assert.IsFalse(current.Any(summary => summary.Id == oldReport.Id)); } [TestMethod] public async Task RecheckItemReportsCurrentPathPassesAfterFileRestored() { using var workspace = TestWorkspace.Create("ymhut-startup-recheck"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); var executable = Path.Combine(installRoot, "YMhutBox.exe"); File.Delete(executable); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunManualIntegrityCheckAsync(); var missing = report.Items.First(item => item.Id == "file:YMhutBox.exe"); File.WriteAllText(executable, string.Empty); var rechecked = await service.RecheckItemAsync(report.Id, missing.Id); Assert.IsNotNull(rechecked); Assert.AreEqual(StartupCheckStatus.Passed, rechecked.Status); StringAssert.Contains(rechecked.Detail, "old record"); } [TestMethod] public async Task IntegrityDoesNotTreatNormalRootFoldersAsCultureFolders() { using var workspace = TestWorkspace.Create("ymhut-startup-root-folders"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); Directory.CreateDirectory(Path.Combine(installRoot, "Assets")); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths)); var report = await service.RunManualIntegrityCheckAsync(); Assert.IsFalse(report.Items.Any(item => string.Equals(item.Id, "lang:root:Assets", StringComparison.OrdinalIgnoreCase))); Assert.IsFalse(report.Items.Any(item => string.Equals(item.Id, "lang:root:Tools", StringComparison.OrdinalIgnoreCase))); } [TestMethod] public async Task StartupCheckStorePersistsHistoryAndLatestReport() { using var workspace = TestWorkspace.Create("ymhut-startup-store"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var store = new StartupCheckStore(paths); var service = new InstallIntegrityCheckService(paths, store); var report = await service.RunManualIntegrityCheckAsync(); var latest = await store.GetLatestReportAsync(); var history = await store.GetHistoryAsync(); var summaries = await store.GetHistorySummariesAsync(); var loaded = await store.GetReportAsync(report.Id); Assert.IsNotNull(latest); Assert.AreEqual(report.Id, latest.Id); Assert.IsGreaterThanOrEqualTo(history.Count, 1); Assert.IsNotEmpty(history[0].Items); Assert.IsGreaterThanOrEqualTo(summaries.Count, 1); Assert.AreEqual(report.Id, summaries[0].Id); Assert.AreEqual(report.Items.Count, summaries[0].ItemCount); Assert.AreEqual(report.IssueCount, summaries[0].IssueCount); Assert.IsNotNull(loaded); Assert.AreEqual(report.Id, loaded.Id); Assert.IsNotEmpty(loaded.Items); } [TestMethod] public async Task StartupCheckStoreMigratesLegacyDatabaseIntoMainSqlite() { using var workspace = TestWorkspace.Create("ymhut-startup-store-migrate"); var installRoot = CreateHealthyInstall(workspace); var appRoot = workspace.CreateDirectory("user"); using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable); environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot); var paths = AppPaths.ForCurrentUser(appRoot); var legacyPath = Path.Combine(paths.Data, "startup-checks.db"); Directory.CreateDirectory(Path.GetDirectoryName(legacyPath)!); await using (var connection = new SqliteConnection($"Data Source={legacyPath};Pooling=False")) { connection.Open(); await using var command = connection.CreateCommand(); command.CommandText = """ CREATE TABLE startup_check_reports ( id TEXT PRIMARY KEY, kind TEXT NOT NULL, started_at TEXT NOT NULL, completed_at TEXT NOT NULL, install_root TEXT NOT NULL, manifest_identity TEXT NOT NULL, issue_count INTEGER NOT NULL, critical_issue_count INTEGER NOT NULL, warning_issue_count INTEGER NOT NULL ); CREATE TABLE startup_check_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, report_id TEXT NOT NULL, item_id TEXT NOT NULL, name TEXT NOT NULL, severity TEXT NOT NULL, status TEXT NOT NULL, detail TEXT NOT NULL, path TEXT NULL ); INSERT INTO startup_check_reports( id, kind, started_at, completed_at, install_root, manifest_identity, issue_count, critical_issue_count, warning_issue_count) VALUES( '11111111111111111111111111111111', 'FastPreflight', '2026-06-01T00:00:00.0000000+00:00', '2026-06-01T00:00:01.0000000+00:00', 'C:\Legacy', 'legacy', 1, 0, 1); INSERT INTO startup_check_items(report_id, item_id, name, severity, status, detail, path) VALUES( '11111111111111111111111111111111', 'legacy:item', '旧记录', 'Warning', 'Failed', '从旧库导入', NULL); """; await command.ExecuteNonQueryAsync(); } var store = new StartupCheckStore(paths); var latest = await store.GetLatestReportAsync(); Assert.IsNotNull(latest); Assert.AreEqual("legacy", latest.ManifestIdentity); Assert.HasCount(1, latest.Items); Assert.IsFalse(File.Exists(legacyPath)); Assert.AreEqual(Path.Combine(paths.Logs, AppDatabasePaths.MainDatabaseFileName), store.DatabasePath); } [TestMethod] public async Task StartupInitializationPipelineReportsMonotonicWeightedProgress() { var progress = new List(); var pipeline = new StartupInitializationPipeline([ new StartupInitializationStage("first", "First", 1, true, (context, token) => { context.Report(0.5, "Half"); return Task.CompletedTask; }), new StartupInitializationStage("second", "Second", 3, true, (context, token) => { context.Report(0.25, "Quarter"); context.Report(0.75, "Almost"); return Task.CompletedTask; }) ]); await pipeline.RunAsync(new InlineProgress(progress.Add)); Assert.IsNotEmpty(progress); Assert.AreEqual(100, progress[^1].Progress); for (var index = 1; index < progress.Count; index++) { Assert.IsGreaterThanOrEqualTo( progress[index - 1].Progress, progress[index].Progress, $"Progress moved backwards at {index}: {progress[index - 1].Progress} -> {progress[index].Progress}"); } CollectionAssert.AreEqual(new[] { "first", "second" }, pipeline.Stages.Select(stage => stage.Id).ToArray()); } [TestMethod] public void StartupSplashMessageSerializesStableContract() { var message = new StartupSplashMessage( "progress", "正在检查", 42, "fast-preflight", "快速预检", 8, 10, "检查关键文件", "warning", IsRepairStep: true, Theme: "dark", ReducedMotion: true); var json = message.ToJson(); StringAssert.Contains(json, "\"type\": \"progress\""); StringAssert.Contains(json, "\"stageId\": \"fast-preflight\""); StringAssert.Contains(json, "\"isRepairStep\": true"); StringAssert.Contains(json, "\"reducedMotion\": true"); } private static string CreateHealthyInstall(TestWorkspace workspace) { var installRoot = workspace.CreateDirectory("install"); workspace.CreateFile(["install", "YMhutBox.exe"], string.Empty); workspace.CreateFile(["install", "YMhutBox.dll"], string.Empty); workspace.CreateFile(["install", "resources.pri"], string.Empty); workspace.CreateFile(["install", "lang", "zh-CN", "Microsoft.ui.xaml.dll.mui"], string.Empty); workspace.CreateFile(["install", "lang", "en-US", "Microsoft.ui.xaml.dll.mui"], string.Empty); workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty); workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }"""); workspace.CreateFile(["install", "config", "install-manifest.ini"], """ [Release] Version=2.0.6 Build=1 Channel=stable PackageVersion=2.0.6.1 [RequiredFiles] YMhutBox.exe YMhutBox.dll resources.pri [Files] YMhutBox.exe YMhutBox.dll resources.pri lang/zh-CN/Microsoft.ui.xaml.dll.mui lang/en-US/Microsoft.ui.xaml.dll.mui Tools/Sample/tool.exe Metadata/tools.json """); return installRoot; } private static string CreateDevelopmentInstall(TestWorkspace workspace) { var installRoot = workspace.CreateDirectory("install"); workspace.CreateFile(["install", "YMhutBox.exe"], string.Empty); workspace.CreateFile(["install", "YMhutBox.dll"], string.Empty); workspace.CreateFile(["install", "zh-CN", "Microsoft.ui.xaml.dll.mui"], string.Empty); workspace.CreateFile(["install", "en-US", "Microsoft.ui.xaml.dll.mui"], string.Empty); return installRoot; } private static void CopyDirectory(string source, string destination) { Directory.CreateDirectory(destination); foreach (var directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories)) { Directory.CreateDirectory(Path.Combine(destination, Path.GetRelativePath(source, directory))); } foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)) { var target = Path.Combine(destination, Path.GetRelativePath(source, file)); Directory.CreateDirectory(Path.GetDirectoryName(target)!); File.Copy(file, target, overwrite: true); } } private sealed class EnvironmentScope : IDisposable { private readonly Dictionary _snapshot; private EnvironmentScope(IEnumerable names) { _snapshot = names.ToDictionary(name => name, Environment.GetEnvironmentVariable, StringComparer.Ordinal); } public static EnvironmentScope Capture(params string[] names) => new(names); public void Set(string name, string? value) => Environment.SetEnvironmentVariable(name, value); public void Dispose() { foreach (var item in _snapshot) { Environment.SetEnvironmentVariable(item.Key, item.Value); } } } private sealed class InlineProgress(Action report) : IProgress { public void Report(T value) => report(value); } private sealed class TestWorkspace : IDisposable { private TestWorkspace(string root) { Root = root; } public string Root { get; } public static TestWorkspace Create(string prefix) { var root = Path.Combine(Path.GetTempPath(), prefix, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); return new TestWorkspace(root); } public string CreateDirectory(params string[] parts) { var directory = Path.Combine([Root, .. parts]); Directory.CreateDirectory(directory); return directory; } public string CreateFile(string[] parts, string contents) { var path = Path.Combine([Root, .. parts]); Directory.CreateDirectory(Path.GetDirectoryName(path)!); File.WriteAllText(path, contents); return path; } public void Dispose() { if (Directory.Exists(Root)) { Directory.Delete(Root, recursive: true); } } } }