File: UnpackPackTests.cs
Web Access
Project: src\src\Microsoft.DotNet.MacOsPkg.Tests\Microsoft.DotNet.MacOsPkg.Tests.csproj (Microsoft.DotNet.MacOsPkg.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.DotNet.MacOsPkg.Tests
{
    public class UnpackPackTests
    {
        private readonly ITestOutputHelper output;
        private static readonly string simplePkg = GetResourceFilePath("Simple.pkg");
        private static readonly string withAppPkg = GetResourceFilePath("WithApp.pkg");
        private static readonly string simpleInstallerPkg = GetResourceFilePath("SimpleInstaller.pkg");
        private static readonly string withAppInstallerPkg = GetResourceFilePath("WithAppInstaller.pkg");
 
        private static readonly string pkgToolPath = Path.Combine(
            Path.GetDirectoryName(typeof(UnpackPackTests).Assembly.Location)!,
            "tools",
            "macospkg",
            "Microsoft.Dotnet.MacOsPkg.dll");
 
        const UnixFileMode nonExecutableFileMode = UnixFileMode.OtherRead | 
                                                   UnixFileMode.GroupRead |
                                                   UnixFileMode.UserWrite |
                                                   UnixFileMode.UserRead;
        const UnixFileMode executableFileMode = UnixFileMode.OtherExecute |
                                                UnixFileMode.OtherRead |
                                                UnixFileMode.GroupExecute |
                                                UnixFileMode.GroupRead |
                                                UnixFileMode.UserExecute |
                                                UnixFileMode.UserWrite |
                                                UnixFileMode.UserRead;
        private static readonly (string file, UnixFileMode mode)[] simplePkgFiles =
        [
            ("Bom", nonExecutableFileMode ),
            ("PackageInfo", nonExecutableFileMode),
            (Path.Combine("Payload", "Sample.txt"), nonExecutableFileMode),
        ];
 
        private static readonly (string file, UnixFileMode mode)[] withAppPkgFiles =
        [
            ("Bom", nonExecutableFileMode),
            ("PackageInfo", nonExecutableFileMode),
            (Path.Combine("Payload", "test.app"), nonExecutableFileMode),
        ];
 
        private static readonly (string file, UnixFileMode mode)[] appFiles =
        [
            (Path.Combine("Contents", "Info.plist"), nonExecutableFileMode),
            (Path.Combine("Contents", "MacOS", "main"), executableFileMode),
            (Path.Combine("Contents", "Resources", "libexample.dylib"), executableFileMode)
        ];
 
        private static readonly (string file, UnixFileMode mode)[] simpleInstallerFiles =
        [
            ("Distribution", nonExecutableFileMode),
            ("Simple.pkg", nonExecutableFileMode),
        ];
 
        private static readonly (string file, UnixFileMode mode)[] withAppInstallerFiles =
        [
            ("Distribution", nonExecutableFileMode),
            ("WithApp.pkg", nonExecutableFileMode),
        ];
 
        public UnpackPackTests(ITestOutputHelper output) => this.output = output;
 
        [MacOSOnlyFact]
        public void UnpackPackSimplePkg()
        {
            string unpackPath = Path.GetTempFileName();
            string packPath = GetTempPkgPath();
 
            ExecuteWithCleanup(() =>
            {
                Unpack(simplePkg, unpackPath, simplePkgFiles);
                Pack(unpackPath, packPath, simplePkgFiles);
            }, [ unpackPath, packPath ]);
        }
 
        [MacOSOnlyFact]
        public void UnpackPackWithAppPkg()
        {
            string unpackPath = Path.GetTempFileName();
            string packPath = GetTempPkgPath();
 
            ExecuteWithCleanup(() =>
            {
                Unpack(withAppPkg, unpackPath, withAppPkgFiles);
                Pack(unpackPath, packPath, withAppPkgFiles);
            }, [ unpackPath, packPath ]);
        }
 
        [MacOSOnlyFact]
        public void UnpackPackAppBundle()
        {
            string unpackPkgPath = Path.GetTempFileName();
            string unpackAppPath = Path.GetTempFileName();
            string packAppPath = GetTempAppPath();
 
            ExecuteWithCleanup(() =>
            {
                Unpack(withAppPkg, unpackPkgPath, withAppPkgFiles);
                Unpack(Path.Combine(unpackPkgPath, "Payload", "test.app"), unpackAppPath, appFiles);
                Pack(unpackAppPath, packAppPath, appFiles);
            }, [ unpackPkgPath, unpackAppPath ]);
        }
 
        [MacOSOnlyFact]
        public void UnpackPackSimpleInstallerPkg()
        {
            string unpackPath = Path.GetTempFileName();
            string packPath = GetTempPkgPath();
 
            ExecuteWithCleanup(() =>
            {
                Unpack(simpleInstallerPkg, unpackPath, simpleInstallerFiles);
                Pack(unpackPath, packPath, simpleInstallerFiles);
            }, [ unpackPath, packPath ]);
        }
 
        [MacOSOnlyFact]
        public void UnpackPackSimplePkgInSimpleInstallerPkg()
        {
            string unpackInstallerPath = Path.GetTempFileName();
            string unpackComponentPath = Path.GetTempFileName();
            string packInstallerPath = GetTempPkgPath();
 
            string componentPkgPath = Path.Combine(unpackInstallerPath, "Simple.pkg");
 
            ExecuteWithCleanup(() =>
            {
                Unpack(simpleInstallerPkg, unpackInstallerPath, simpleInstallerFiles);
                Unpack(componentPkgPath, unpackComponentPath, simplePkgFiles);
                Pack(unpackComponentPath, componentPkgPath, simplePkgFiles);
                Pack(unpackInstallerPath, packInstallerPath, simpleInstallerFiles);
            }, [ unpackInstallerPath, unpackComponentPath, packInstallerPath ]);
        }
 
        [MacOSOnlyFact]
        public void UnpackPackAppBundleAndWithAppPkgInWithAppInstallerPkg()
        {
            string unpackInstallerPath = Path.GetTempFileName();
            string unpackComponentPath = Path.GetTempFileName();
            string unpackAppPath = Path.GetTempFileName();
            string packInstallerPath = GetTempPkgPath();
            
            string componentPkgPath = Path.Combine(unpackInstallerPath, "WithApp.pkg");
            string appPath = Path.Combine(unpackComponentPath, "Payload", "test.app");
 
            ExecuteWithCleanup(() =>
                {
                    Unpack(withAppInstallerPkg, unpackInstallerPath, withAppInstallerFiles);
                    Unpack(componentPkgPath, unpackComponentPath, withAppPkgFiles);
                    Unpack(appPath, unpackAppPath, appFiles);
                    Pack(unpackAppPath, appPath, appFiles);
                    Pack(unpackComponentPath, componentPkgPath, withAppPkgFiles);
                    Pack(unpackInstallerPath, packInstallerPath, withAppInstallerFiles);
                }, [ unpackInstallerPath, unpackComponentPath, unpackAppPath, packInstallerPath ]);
        }
 
        private static void ExecuteWithCleanup(Action action, List<string> cleanupPaths)
        {
            try
            {
                action();
            }
            finally
            {
                foreach (string path in cleanupPaths)
                {
                    if (Directory.Exists(path))
                    {
                        Directory.Delete(path, true);
                    }
                    else if (File.Exists(path))
                    {
                        File.Delete(path);
                    }
                }
            }
        }
 
        private void Unpack(string srcPath, string dstPath, (string, UnixFileMode)[] expectedFiles)
        {
            RunPkgProcess(srcPath, dstPath, "unpack").Should().BeTrue();
 
            Directory.Exists(dstPath).Should().BeTrue();
            
            CompareContent(dstPath, expectedFiles);
        }
 
        private void Pack(string srcPath, string dstPath, (string, UnixFileMode)[] expectedFiles)
        {
            RunPkgProcess(srcPath, dstPath, "pack").Should().BeTrue();
 
            File.Exists(dstPath).Should().BeTrue();
 
            // Unpack the packed pkg and verify the content
            string unpackPath = Path.GetTempFileName();
            ExecuteWithCleanup(() =>
            {
                Unpack(dstPath, unpackPath, expectedFiles);
            }, [ unpackPath ]);
        }
 
        private bool RunPkgProcess(string inputPath, string outputPath, string action)
        {
            var process = Process.Start(new ProcessStartInfo()
            {
                FileName = "dotnet",
                Arguments = $@"exec ""{pkgToolPath}"" ""{inputPath}"" ""{outputPath}"" {action}",
                UseShellExecute = false,
                RedirectStandardError = true,
            });
 
            process!.WaitForExit(60000); // 60 seconds
            bool success = process.ExitCode == 0;
            if (!success)
            {
                output.WriteLine($"Error: {process.StandardError.ReadToEnd()}");
            }
            return success;
        }
 
        private static string GetResourceFilePath(string resourceName)
        {
            return Path.Combine(
                Path.GetDirectoryName(typeof(UnpackPackTests).Assembly.Location)!,
                "Resources",
                resourceName);
        }
 
        private static string GetTempPkgPath() => $"{Path.GetTempFileName()}.pkg";
 
        private static string GetTempAppPath() => $"{Path.GetTempFileName()}.app";
 
#pragma warning disable CA1416
        private static void CompareContent(string basePath, (string file, UnixFileMode mode)[] expectedFiles)
        {
            (string, UnixFileMode)[] actualFiles = Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories)
                .Select(f => (f.Substring(basePath.Length + 1), File.GetUnixFileMode(f)))
                .ToArray();
            actualFiles.Should().BeEquivalentTo(expectedFiles);
        }
    }
}