XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEToolBox

【WPF】XFE工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text.Json;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using XFEToolBox.Core.Tools;

namespace SpaceEngineersPluginManager.Validation;

internal static partial class Program
{
    private static readonly string Workspace = Assembly.GetExecutingAssembly().GetCustomAttributes<AssemblyMetadataAttribute>().Single(x => x.Key == "ToolWorkspace").Value!;
    private static readonly string Artifacts = Path.Combine(AppContext.BaseDirectory, "test-artifacts");
    private static readonly string Fixtures = Path.Combine(Path.GetTempPath(), "XfeSeValidation", Guid.NewGuid().ToString("N")[..10]);
    private static readonly List<string> Results = [];
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true };
    private static int exitCode;

    [STAThread]
    private static int Main(string[] args)
    {
        if (args.FirstOrDefault() == "--process-fixture") return RunProcessFixture(args[1]);
        Directory.CreateDirectory(Fixtures);
        var app = new Application { ShutdownMode = ShutdownMode.OnExplicitShutdown };
        app.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/XFEToolBox.WpfCore;component/Resources/Style/ToolThemeResources.xaml") });
        app.Startup += async (_, _) =>
        {
            try
            {
                await TestAsync();
                if (!args.Contains("--skip-host")) await ValidateHostAsync(Workspace, ReadManifest(Workspace), "canonical source");
                int packageIndex = Array.IndexOf(args, "--package");
                if (packageIndex >= 0)
                {
                    if (packageIndex + 1 == args.Length) throw new ArgumentException("--package requires an .xfetool path");
                    await ValidatePackageAsync(Path.GetFullPath(args[packageIndex + 1]));
                }
                if (args.Contains("--register")) await RegisterAsync();
                Console.WriteLine($"ALL {Results.Count} CHECKS PASSED. Artifacts: {Artifacts}");
            }
            catch (Exception e) { Console.Error.WriteLine(e); exitCode = 1; }
            finally
            {
                File.WriteAllText(Path.Combine(Artifacts, "results.json"), JsonSerializer.Serialize(new { time = DateTimeOffset.Now, workspace = Workspace, fixtures = Fixtures, passed = exitCode == 0, checks = Results }, JsonOptions));
                app.Shutdown();
            }
        };
        app.Run();
        return exitCode;
    }

    private static void Check(bool value, string name)
    {
        if (!value) throw new InvalidOperationException("FAIL: " + name);
        Results.Add(name);
        Console.WriteLine("PASS: " + name);
    }

    private static void Throws<T>(Action action, string name) where T : Exception
    {
        try { action(); }
        catch (T) { Check(true, name); return; }
        throw new InvalidOperationException("FAIL: " + name + " did not throw " + typeof(T).Name);
    }

    private static async Task ThrowsAsync<T>(Func<Task> action, string name) where T : Exception
    {
        try { await action(); }
        catch (T) { Check(true, name); return; }
        throw new InvalidOperationException("FAIL: " + name + " did not throw " + typeof(T).Name);
    }

    private static ToolPackageManifest ReadManifest(string workspace) => JsonSerializer.Deserialize<ToolPackageManifest>(File.ReadAllText(Path.Combine(workspace, "manifest.json")), JsonOptions)!;
    private static Type HostService => typeof(XFEToolBox.Client.Models.LauncherItem).Assembly.GetType("XFEToolBox.Client.Utilities.ToolProjectRunService", true)!;

    private static async Task ValidateHostAsync(string workspace, ToolPackageManifest manifest, string description)
    {
        var build = (Task)HostService.GetMethod("BuildAsync", BindingFlags.Public | BindingFlags.Static)!.Invoke(null, [workspace, manifest, CancellationToken.None, null])!;
        await build.WaitAsync(TimeSpan.FromMinutes(3));
        var result = build.GetType().GetProperty("Result")!.GetValue(build)!;
        Check((bool)result.GetType().GetProperty("Success")!.GetValue(result)!, "production runtime BuildAsync (" + description + "): " + result.GetType().GetProperty("Message")!.GetValue(result));
    }

    private static async Task ValidatePackageAsync(string package)
    {
        var sourceManifest = ReadManifest(Workspace);
        string extracted = Path.Combine(Artifacts, "package-" + Guid.NewGuid().ToString("N"));
        var extract = (Task<ToolPackageManifest>)HostService.GetMethod("ExtractAndValidatePackageAsync", BindingFlags.NonPublic | BindingFlags.Static)!
            .Invoke(null, [package, extracted, sourceManifest.Id, sourceManifest.Version, CancellationToken.None])!;
        var manifest = await extract;
        var files = Directory.GetFiles(extracted, "*", SearchOption.AllDirectories);
        bool equal = files.Length == Directory.GetFiles(Workspace, "*", SearchOption.AllDirectories).Length;
        foreach (string file in files)
        {
            string original = Path.Combine(Workspace, Path.GetRelativePath(extracted, file));
            equal &= File.Exists(original) && File.ReadAllBytes(original).SequenceEqual(File.ReadAllBytes(file));
        }
        Check(equal, "production package extraction matches every canonical source file byte-for-byte");
        await ValidateHostAsync(extracted, manifest, "extracted package");
        Console.WriteLine("PACKAGE SHA256: " + Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(package))));
    }

    private static async Task RegisterAsync()
    {
        var projects = typeof(XFEToolBox.Client.Models.LauncherItem).Assembly.GetType("XFEToolBox.Client.Utilities.ToolProjectWorkspaceService", true)!;
        await (Task)projects.GetMethod("RememberProjectAsync", BindingFlags.Public | BindingFlags.Static)!.Invoke(null, [Workspace])!;
        Console.WriteLine("Registered project: " + Workspace);
    }

    private static void SaveView(FrameworkElement view, int width, int height, string name)
    {
        view.Measure(new Size(width, height)); view.Arrange(new Rect(0, 0, width, height)); view.UpdateLayout();
        var bitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); bitmap.Render(view);
        var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmap));
        using var output = File.Create(Path.Combine(Artifacts, name)); encoder.Save(output);
    }

    private sealed class BindingTrace : TraceListener
    {
        public List<string> Errors { get; } = [];
        public override void Write(string? message) { if (!string.IsNullOrWhiteSpace(message)) Errors.Add(message); }
        public override void WriteLine(string? message) => Write(message);
    }

    private static async Task TestAsync()
    {
        Check(Directory.Exists(Workspace), "canonical tool workspace exists");
        TestProfiles();
        await TestRuntimeAsync();
        await TestLoaderAsync();
        await TestViewAsync();
    }
}