xamarin-macios/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs

303 строки
14 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Build.Utilities;
using NUnit.Framework;
using Xamarin.iOS.Tasks;
using Xamarin.Tests;
using Xamarin.Utils;
namespace Xamarin.MacDev.Tasks {
[TestFixture]
public class MergeAppBundleTaskTest : TestBase {
[OneTimeSetUp]
public void SetUp ()
{
var env = new Dictionary<string, string> {
{ "MSBUILD_EXE_PATH", null }, // Comes from VSMac (when running tests from inside the IDE), and it confuses 'dotnet build', so remove it.
{ "MSBuildSDKsPath", null }, // Comes from MSBuild, and confuses 'dotnet build'
};
RunMake (Path.Combine (Configuration.RootPath, "tests", "test-libraries"), environment: env);
RunMake (Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly"), environment: env);
}
static void RunMake (string directory, Dictionary<string, string> environment = null)
{
var arguments = new List<string> {
"-C",
directory,
"V=1",
};
var rv = ExecutionHelper.Execute ("make",
arguments,
output: out var output,
working_directory: null,
timeout: TimeSpan.FromSeconds (120),
environment_variables: environment);
if (rv != 0) {
var failure = $"'make {StringUtils.FormatArguments (StringUtils.QuoteForProcess (arguments))}' exited with exit code {rv}:";
var indented = "\t" + string.Join ("\n\t", output.ToString ().Split ('\n'));
Console.WriteLine (failure);
Console.WriteLine (indented);
// Only show the last 10 lines in the assert message, because otherwise the html reports can end up quite big.
var shortIndented = indented.Split ('\n').Reverse ().Take (10).Reverse ();
Assert.Fail (failure + "\n" + string.Join ("\n", shortIndented));
}
}
MergeAppBundles CreateTask (string outputBundle, params string[] inputBundles)
{
var inputItems = new List<TaskItem> ();
for (var i = 0; i < inputBundles.Length; i++) {
var item = new TaskItem (inputBundles [i]);
item.SetMetadata ("SpecificSubdirectory", $"SubDir{i + 1}");
inputItems.Add (item);
}
var task = CreateTask<MergeAppBundles> ();
task.InputAppBundles = inputItems.ToArray ();
task.OutputAppBundle = outputBundle;
return task;
}
// Create two app bundles, one with fileA, and one with fileB, in the root directory
string[] CreateAppBundles (string fileA, string fileB, string fileName = null)
{
var appBundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var appBundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
Directory.CreateDirectory (appBundleA);
Directory.CreateDirectory (appBundleB);
File.Copy (fileA, Path.Combine (appBundleA, fileName ?? Path.GetFileName (fileA)));
File.Copy (fileB, Path.Combine (appBundleB, fileName ?? Path.GetFileName (fileB)));
return new string [] { appBundleA, appBundleB };
}
string CreateAppBundle (string directory, params string[] files)
{
var appBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
Directory.CreateDirectory (appBundle);
foreach (var file in files) {
var inputPath = Path.Combine (directory, file);
var outputPath = Path.Combine (appBundle, file);
Directory.CreateDirectory (Path.GetDirectoryName (outputPath));
File.Copy (inputPath, outputPath, true);
}
return appBundle;
}
[Test]
public void TestLipoExecutable ()
{
var fileA = Path.Combine (Configuration.RootPath, "tests", "test-libraries", ".libs", "macos", "libtest.arm64.dylib");
var fileB = Path.Combine (Configuration.RootPath, "tests", "test-libraries", ".libs", "macos", "libtest.x86_64.dylib");
var bundles = CreateAppBundles (fileA, fileB, "libtest.dylib");
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundles);
Assert.IsTrue (task.Execute (), "Task execution");
// The bundle should only contain a single file.
Assert.AreEqual (1, Directory.GetFileSystemEntries (outputBundle).Length, "Files in bundle");
// The resulting dylib should contain 2 architectures.
var fatLibrary = Path.Combine (outputBundle, "libtest.dylib");
Assert.That (fatLibrary, Does.Exist, "Existence");
var machO = MachO.Read (fatLibrary).ToArray ();
Assert.AreEqual (2, machO.Length, "Architecture Count");
}
[Test]
public void TestPEAssembly ()
{
var complexAssemblyPath = Path.Combine (Configuration.RootPath, "tests", "common", "TestProjects", "ComplexAssembly", "bin", "Debug", Configuration.DotNetTfm);
var complexFiles = new string [] {
"ComplexAssembly.dll",
"ComplexAssembly.pdb",
"ComplexAssembly.dll.config",
"de/ComplexAssembly.resources.dll",
"en-AU/ComplexAssembly.resources.dll",
"es/ComplexAssembly.resources.dll",
};
var appA = CreateAppBundle (complexAssemblyPath, complexFiles);
var appB = CreateAppBundle (complexAssemblyPath, complexFiles);
var bundles = new string [] { appA, appB };
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundles);
Assert.IsTrue (task.Execute (), "Task execution");
// The bundle should have all the files
Assert.AreEqual (complexFiles.Length, Directory.GetFileSystemEntries (outputBundle).Length, "Files in bundle");
// with the same structure
foreach (var file in complexFiles)
Assert.That (Path.Combine (outputBundle, file), Does.Exist, $"File existence");
}
[Test]
public void TestDifferentOtherFiles ()
{
var tmpDir = Cache.CreateTemporaryDirectory ();
var fileA = Path.Combine (tmpDir, "A.txt");
var fileB = Path.Combine (tmpDir, "B.txt");
File.WriteAllText (fileA, "A");
File.WriteAllText (fileB, "B");
var bundles = CreateAppBundles (fileA, fileB, "Something.txt");
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundles);
Assert.IsFalse (task.Execute (), "Task execution");
Assert.AreEqual (3, Engine.Logger.ErrorEvents.Count, "Errors:\n\t" + string.Join ("\n\t", Engine.Logger.ErrorEvents.Select ((v) => v.Message).ToArray ()));
Assert.AreEqual ("Unable to merge the file 'Something.txt', it's different between the input app bundles.", Engine.Logger.ErrorEvents [0].Message, "Error message");
Assert.That (Engine.Logger.ErrorEvents [1].Message, Does.Match ("App bundle file #1: .*/MergeMe.app/Something.txt"), "Error message 2");
Assert.That (Engine.Logger.ErrorEvents [2].Message, Does.Match ("App bundle file #2: .*/MergeMe.app/Something.txt"), "Error message 3");
}
[Test]
public void TestSymlinks ()
{
var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var fileA = Path.Combine (bundleA, "A.txt");
var fileB = Path.Combine (bundleB, "A.txt");
Directory.CreateDirectory (Path.GetDirectoryName (fileA));
File.WriteAllText (fileA, "A");
Directory.CreateDirectory (Path.GetDirectoryName (fileB));
File.WriteAllText (fileB, "A");
var linkA = Path.Combine (bundleA, "B.txt");
var linkB = Path.Combine (bundleB, "B.txt");
Assert.IsTrue (PathUtils.Symlink ("A.txt", linkA), "Link A");
Assert.IsTrue (PathUtils.Symlink ("A.txt", linkB), "Link B");
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundleA, bundleB);
Assert.IsTrue (task.Execute (), "Task execution");
Assert.IsTrue (PathUtils.IsSymlink (Path.Combine (outputBundle, "B.txt")), "IsSymlink");
}
[Test]
public void TestSymlinksWithDifferentTargets ()
{
var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var fileA = Path.Combine (bundleA, "A.txt");
var fileB = Path.Combine (bundleB, "A.txt");
var fileAC = Path.Combine (bundleA, "C.txt");
var fileBC = Path.Combine (bundleB, "C.txt");
Directory.CreateDirectory (Path.GetDirectoryName (fileA));
File.WriteAllText (fileA, "A");
File.WriteAllText (fileAC, "C");
Directory.CreateDirectory (Path.GetDirectoryName (fileB));
File.WriteAllText (fileB, "A");
File.WriteAllText (fileBC, "C");
// There's a symlink in both apps, but they have different targets.
var linkA = Path.Combine (bundleA, "B.txt");
var linkB = Path.Combine (bundleB, "B.txt");
Assert.IsTrue (PathUtils.Symlink ("A.txt", linkA), "Link A");
Assert.IsTrue (PathUtils.Symlink ("C.txt", linkB), "Link B");
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundleA, bundleB);
Assert.IsFalse (task.Execute (), "Task execution");
Assert.AreEqual (3, Engine.Logger.ErrorEvents.Count, "Errors:\n\t" + string.Join ("\n\t", Engine.Logger.ErrorEvents.Select ((v) => v.Message).ToArray ()));
Assert.AreEqual ("Can't merge the symlink 'B.txt', it has different targets.", Engine.Logger.ErrorEvents [0].Message, "Error message");
Assert.That (Engine.Logger.ErrorEvents [1].Message, Does.Match ("App bundle file #1: .*/MergeMe.app/B.txt"), "Error message 2");
Assert.That (Engine.Logger.ErrorEvents [2].Message, Does.Match ("App bundle file #2: .*/MergeMe.app/B.txt"), "Error message 3");
}
[Test]
public void TestDirectories ()
{
var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var onlyA = "A";
var onlyB = "B";
var bothAB = "AB";
var nestedOnlyA = "AA/AA";
var nestedOnlyB = "BB/BB";
var nestedBothAB = "ABAB/ABAB";
var nestedSharedOnlyA = "ABS/A";
var nestedSharedOnlyB = "ABS/B";
Directory.CreateDirectory (Path.Combine (bundleA, onlyA));
Directory.CreateDirectory (Path.Combine (bundleA, bothAB));
Directory.CreateDirectory (Path.Combine (bundleA, nestedOnlyA));
Directory.CreateDirectory (Path.Combine (bundleA, nestedBothAB));
Directory.CreateDirectory (Path.Combine (bundleA, nestedSharedOnlyA));
Directory.CreateDirectory (Path.Combine (bundleB, onlyB));
Directory.CreateDirectory (Path.Combine (bundleB, nestedOnlyB));
Directory.CreateDirectory (Path.Combine (bundleB, bothAB));
Directory.CreateDirectory (Path.Combine (bundleB, nestedBothAB));
Directory.CreateDirectory (Path.Combine (bundleB, nestedSharedOnlyB));
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundleA, bundleB);
Assert.IsTrue (task.Execute (), "Task execution");
Assert.That (Path.Combine (outputBundle, onlyA), Does.Exist, "onlyA");
Assert.That (Path.Combine (outputBundle, onlyB), Does.Exist, "onlyB");
Assert.That (Path.Combine (outputBundle, bothAB), Does.Exist, "bothAB");
Assert.That (Path.Combine (outputBundle, nestedOnlyA), Does.Exist, "nestedOnlyA");
Assert.That (Path.Combine (outputBundle, nestedOnlyB), Does.Exist, "nestedOnlyB");
Assert.That (Path.Combine (outputBundle, nestedBothAB), Does.Exist, "nestedBothAB");
Assert.That (Path.Combine (outputBundle, nestedSharedOnlyA), Does.Exist, "nestedSharedOnlyA");
Assert.That (Path.Combine (outputBundle, nestedSharedOnlyB), Does.Exist, "nestedSharedOnlyB");
// Verify that there aren't any other directories
Assert.AreEqual (7, Directory.GetFileSystemEntries (outputBundle).Length, "Directories in bundle");
}
[Test]
public void TestSingleInput ()
{
var fileA = Path.Combine (Configuration.RootPath, "tests", "test-libraries", ".libs", "macos", "libtest.arm64.dylib");
var bundle = CreateAppBundle (Path.GetDirectoryName (fileA), Path.GetFileName (fileA));
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundle);
Assert.IsTrue (task.Execute (), "Task execution");
// The bundle should only contain a single file.
Assert.AreEqual (1, Directory.GetFileSystemEntries (outputBundle).Length, "Files in bundle");
// The resulting dylib should contain 1 architecture.
var nonFatBinary = Path.Combine (outputBundle, "libtest.arm64.dylib");
Assert.That (nonFatBinary, Does.Exist, "Existence");
var machO = MachO.Read (nonFatBinary).ToArray ();
Assert.AreEqual (1, machO.Length, "Architecture Count");
// and the file size should be the same as the input
Assert.That (new FileInfo (fileA).Length, Is.EqualTo (new FileInfo (nonFatBinary).Length), "File length");
}
[Test]
public void TestDirectoriesAsSymlinks ()
{
var bundleA = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var bundleB = Path.Combine (Cache.CreateTemporaryDirectory (), "MergeMe.app");
var fileA = Path.Combine (bundleA, "A", "A.txt");
var fileB = Path.Combine (bundleB, "A.txt");
Directory.CreateDirectory (Path.GetDirectoryName (fileA));
File.WriteAllText (fileA, "A");
Directory.CreateDirectory (Path.GetDirectoryName (fileB));
File.WriteAllText (fileB, "A");
var linkA = Path.Combine (bundleA, "B");
var linkB = Path.Combine (bundleB, "B");
Assert.IsTrue (PathUtils.Symlink ("A", linkA), "Link A");
Assert.IsTrue (PathUtils.Symlink ("A", linkB), "Link B");
var outputBundle = Path.Combine (Cache.CreateTemporaryDirectory (), "Merged.app");
var task = CreateTask (outputBundle, bundleA, bundleB);
Assert.IsTrue (task.Execute (), "Task execution");
Assert.IsTrue (PathUtils.IsSymlink (Path.Combine (outputBundle, "B")), "IsSymlink");
Assert.IsFalse (PathUtils.IsSymlink (Path.Combine (outputBundle, "A", "A.txt")), "IsSymlink");
Assert.IsFalse (PathUtils.IsSymlink (Path.Combine (outputBundle, "B", "A.txt")), "IsSymlink");
}
}
}