diff options
Diffstat (limited to 'linker')
26 files changed, 587 insertions, 12 deletions
diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/DefineAttribute.cs b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/DefineAttribute.cs new file mode 100644 index 000000000..14e95fd4d --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/DefineAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Metadata { + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] + public class DefineAttribute : BaseMetadataAttribute { + public DefineAttribute (string name) + { + if (string.IsNullOrEmpty (name)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (name)); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupCompileAfterAttribute.cs b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupCompileAfterAttribute.cs new file mode 100644 index 000000000..fa580a2f8 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupCompileAfterAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Metadata { + /// <summary> + /// Use to compile an assembly after compiling the main test case executabe + /// </summary> + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] + public class SetupCompileAfterAttribute : BaseMetadataAttribute { + public SetupCompileAfterAttribute (string outputName, string[] sourceFiles, string[] references = null, string[] defines = null) + { + if (sourceFiles == null) + throw new ArgumentNullException (nameof (sourceFiles)); + + if (string.IsNullOrEmpty (outputName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (outputName)); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupCompileBeforeAttribute.cs b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupCompileBeforeAttribute.cs new file mode 100644 index 000000000..d9a3bd658 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupCompileBeforeAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Metadata { + /// <summary> + /// Use to compile an assembly before compiling the main test case executabe + /// </summary> + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] + public class SetupCompileBeforeAttribute : BaseMetadataAttribute { + public SetupCompileBeforeAttribute (string outputName, string[] sourceFiles, string[] references = null, string[] defines = null, bool addAsReference = true) + { + if (sourceFiles == null) + throw new ArgumentNullException (nameof (sourceFiles)); + + if (string.IsNullOrEmpty (outputName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (outputName)); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj index fb1b6e3e1..20c4a9479 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj @@ -51,7 +51,10 @@ <Compile Include="Assertions\RemovedTypeInAssemblyAttribute.cs" /> <Compile Include="Assertions\SkipPeVerifyAttribute.cs" /> <Compile Include="Metadata\BaseMetadataAttribute.cs" /> + <Compile Include="Metadata\SetupCompileAfterAttribute.cs" /> + <Compile Include="Metadata\SetupCompileBeforeAttribute.cs" /> <Compile Include="Metadata\CoreLinkAttribute.cs" /> + <Compile Include="Metadata\DefineAttribute.cs" /> <Compile Include="Metadata\IncludeBlacklistStepAttribute.cs" /> <Compile Include="Metadata\Il8nAttribute.cs" /> <Compile Include="Metadata\KeepTypeForwarderOnlyAssembliesAttribute.cs" /> diff --git a/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj b/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj index be75f1d64..9601bca40 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj +++ b/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj @@ -60,6 +60,14 @@ <Compile Include="LinkXml\UnusedAssemblyWithNoDefinedPreserveHasAllTypesPreserved.cs" /> <Compile Include="LinkXml\UnusedEventPreservedByLinkXmlIsKept.cs" /> <Compile Include="LinkXml\UnusedTypeWithNoDefinedPreserveHasAllMembersPreserved.cs" /> + <Compile Include="TestFramework\CanCompileILAssembly.cs" /> + <Compile Include="TestFramework\VerifyDefineAttributeBehavior.cs" /> + <None Include="TypeForwarding\Dependencies\ForwarderLibrary.cs" /> + <Compile Include="TypeForwarding\Dependencies\ImplementationLibrary.cs" /> + <Compile Include="TypeForwarding\Dependencies\LibraryUsingForwarder.cs" /> + <Compile Include="TypeForwarding\Dependencies\ReferenceImplementationLibrary.cs" /> + <Compile Include="TypeForwarding\TypeForwarderOnlyAssembliesRemoved.cs" /> + <Compile Include="TypeForwarding\TypeForwarderOnlyAssembliesKept.cs" /> <Compile Include="VirtualMethods\ClassUsedFromConcreteTypeHasInterfaceMethodRemoved.cs" /> <Compile Include="VirtualMethods\ClassUsedFromInterfaceHasInterfaceMethodKept.cs" /> <Compile Include="VirtualMethods\StructUsedFromConcreteTypeHasInterfaceMethodRemoved.cs" /> @@ -154,6 +162,9 @@ <Name>Mono.Linker.Tests.Cases.Expectations</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <None Include="TestFramework\Dependencies\ILAssemblySample.il" /> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/CanCompileILAssembly.cs b/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/CanCompileILAssembly.cs new file mode 100644 index 000000000..5ad63788e --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/CanCompileILAssembly.cs @@ -0,0 +1,15 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using Mono.Linker.Tests.Cases.TestFramework.Dependencies; + +namespace Mono.Linker.Tests.Cases.TestFramework { + [SetupCompileBefore ("ILAssembly.dll", new[] { "Dependencies/ILAssemblySample.il" })] + [KeptMemberInAssembly ("ILAssembly.dll", typeof (ILAssemblySample), "GiveMeAValue()")] + public class CanCompileILAssembly { + static void Main () + { + Console.WriteLine (new ILAssemblySample ().GiveMeAValue ()); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/Dependencies/ILAssemblySample.il b/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/Dependencies/ILAssemblySample.il new file mode 100644 index 000000000..0060542bd --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/Dependencies/ILAssemblySample.il @@ -0,0 +1,42 @@ +.assembly extern mscorlib +{ +} + +.assembly ILAssembly +{ + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} + +.module ILAssembly.dll + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit Mono.Linker.Tests.Cases.TestFramework.Dependencies.ILAssemblySample + extends [mscorlib]System.Object +{ + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method ILAssemblySample::.ctor + + .method public hidebysig instance string + GiveMeAValue() cil managed + { + .maxstack 1 + .locals init (string V_0) + IL_0000: nop + IL_0001: ldstr "Bar" + IL_0006: stloc.0 + IL_0007: br IL_000c + + IL_000c: ldloc.0 + IL_000d: ret + } // end of method ILAssemblySample::GiveMeAValue + +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/VerifyDefineAttributeBehavior.cs b/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/VerifyDefineAttributeBehavior.cs new file mode 100644 index 000000000..abe143042 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TestFramework/VerifyDefineAttributeBehavior.cs @@ -0,0 +1,26 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.TestFramework { + + /// <summary> + /// The purpose of this test is to verify that the testing framework's define attribute is working correctly + /// </summary> + [Define("SOME_DEFINE")] + public class VerifyDefineAttributeBehavior { + static void Main () + { +#if SOME_DEFINE + MethodThatIsUsedIfDefineIsWorkingProperly (); +#endif + } + + + [Kept] + static void MethodThatIsUsedIfDefineIsWorkingProperly () + { + Console.WriteLine ("Foo"); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs new file mode 100644 index 000000000..bb1521525 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ForwarderLibrary.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: System.Runtime.CompilerServices.TypeForwardedTo (typeof (Mono.Linker.Tests.Cases.TypeForwarding.Dependencies.ImplementationLibrary))] diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs new file mode 100644 index 000000000..f26aaa3b5 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ImplementationLibrary.cs @@ -0,0 +1,12 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies { + [NotATestCase] + public class ImplementationLibrary { + public string GetSomeValue () + { + return "Hello"; + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/LibraryUsingForwarder.cs b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/LibraryUsingForwarder.cs new file mode 100644 index 000000000..97c728e7d --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/LibraryUsingForwarder.cs @@ -0,0 +1,12 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies { + [NotATestCase] + public class LibraryUsingForwarder { + public string GetValueFromOtherAssembly () + { + return new ImplementationLibrary ().GetSomeValue (); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs new file mode 100644 index 000000000..00bb1e30c --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/Dependencies/ReferenceImplementationLibrary.cs @@ -0,0 +1,17 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.TypeForwarding.Dependencies { + [NotATestCase] + public class ReferenceImplementationLibrary { + } + +#if INCLUDE_REFERENCE_IMPL + public class ImplementationLibrary { + public string GetSomeValue () + { + return null; + } + } +#endif +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/TypeForwarderOnlyAssembliesKept.cs b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/TypeForwarderOnlyAssembliesKept.cs new file mode 100644 index 000000000..e986def6a --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/TypeForwarderOnlyAssembliesKept.cs @@ -0,0 +1,28 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using Mono.Linker.Tests.Cases.TypeForwarding.Dependencies; + +namespace Mono.Linker.Tests.Cases.TypeForwarding { + [KeepTypeForwarderOnlyAssemblies ("true")] + [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + // Add another assembly in that uses the forwarder just to make things a little more complex + [SetupCompileBefore ("Library.dll", new[] { "Dependencies/LibraryUsingForwarder.cs" }, references: new[] { "Forwarder.dll" })] + + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [KeptAssembly ("Library.dll")] + [KeptAssembly ("Implementation.dll")] + [KeptAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Library.dll", typeof (LibraryUsingForwarder), "GetValueFromOtherAssembly()")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + public class TypeForwarderOnlyAssembliesKept { + static void Main () + { + Console.WriteLine (new ImplementationLibrary ().GetSomeValue ()); + Console.WriteLine (new LibraryUsingForwarder ().GetValueFromOtherAssembly ()); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/TypeForwarderOnlyAssembliesRemoved.cs b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/TypeForwarderOnlyAssembliesRemoved.cs new file mode 100644 index 000000000..78c7fc571 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/TypeForwarding/TypeForwarderOnlyAssembliesRemoved.cs @@ -0,0 +1,29 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using Mono.Linker.Tests.Cases.TypeForwarding.Dependencies; + +namespace Mono.Linker.Tests.Cases.TypeForwarding +{ + [KeepTypeForwarderOnlyAssemblies ("false")] + [SetupCompileBefore ("Forwarder.dll", new[] { "Dependencies/ReferenceImplementationLibrary.cs" }, defines: new[] { "INCLUDE_REFERENCE_IMPL" })] + // Add another assembly in that uses the forwarder just to make things a little more complex + [SetupCompileBefore ("Library.dll", new[] { "Dependencies/LibraryUsingForwarder.cs" }, references: new[] { "Forwarder.dll" })] + + // After compiling the test case we then replace the reference impl with implementation + type forwarder + [SetupCompileAfter ("Implementation.dll", new[] { "Dependencies/ImplementationLibrary.cs" })] + [SetupCompileAfter ("Forwarder.dll", new[] { "Dependencies/ForwarderLibrary.cs" }, references: new[] { "Implementation.dll" })] + + [KeptAssembly ("Library.dll")] + [KeptAssembly ("Implementation.dll")] + [RemovedAssembly ("Forwarder.dll")] + [KeptMemberInAssembly ("Implementation.dll", typeof (ImplementationLibrary), "GetSomeValue()")] + [KeptMemberInAssembly ("Library.dll", typeof (LibraryUsingForwarder), "GetValueFromOtherAssembly()")] + class TypeForwarderOnlyAssembliesRemoved { + static void Main() + { + Console.WriteLine (new ImplementationLibrary ().GetSomeValue ()); + Console.WriteLine (new LibraryUsingForwarder ().GetValueFromOtherAssembly ()); + } + } +} diff --git a/linker/Tests/Mono.Linker.Tests.csproj b/linker/Tests/Mono.Linker.Tests.csproj index bb3565063..7c69b2d6f 100644 --- a/linker/Tests/Mono.Linker.Tests.csproj +++ b/linker/Tests/Mono.Linker.Tests.csproj @@ -72,6 +72,9 @@ <None Include="packages.config" /> </ItemGroup> <ItemGroup> + <Compile Include="TestCasesRunner\CompilerOptions.cs" /> + <Compile Include="TestCasesRunner\ILCompiler.cs" /> + <Compile Include="TestCasesRunner\SetupCompileInfo.cs" /> <Compile Include="TestCasesRunner\PeVerifier.cs" /> <Compile Include="TestCases\TestSuites.cs" /> <Compile Include="TestCases\TestDatabase.cs" /> diff --git a/linker/Tests/TestCases/TestDatabase.cs b/linker/Tests/TestCases/TestDatabase.cs index 2575b8d46..e7dd072c9 100644 --- a/linker/Tests/TestCases/TestDatabase.cs +++ b/linker/Tests/TestCases/TestDatabase.cs @@ -54,6 +54,16 @@ namespace Mono.Linker.Tests.TestCases return NUnitCasesByPrefix("References."); } + public static IEnumerable<TestCaseData> TypeForwardingTests () + { + return NUnitCasesByPrefix ("TypeForwarding."); + } + + public static IEnumerable<TestCaseData> TestFrameworkTests () + { + return NUnitCasesByPrefix ("TestFramework."); + } + public static IEnumerable<TestCaseData> OtherTests() { var allGroupedTestNames = new HashSet<string>( @@ -67,6 +77,8 @@ namespace Mono.Linker.Tests.TestCases .Concat(StaticsTests()) .Concat(InteropTests()) .Concat(ReferencesTests ()) + .Concat(TypeForwardingTests ()) + .Concat(TestFrameworkTests ()) .Select(c => ((TestCase)c.Arguments[0]).ReconstructedFullTypeName)); return AllCases().Where(c => !allGroupedTestNames.Contains(c.ReconstructedFullTypeName)).Select(c => CreateNUnitTestCase(c, c.DisplayName)); diff --git a/linker/Tests/TestCases/TestSuites.cs b/linker/Tests/TestCases/TestSuites.cs index ca5a0eb7e..e5be82d62 100644 --- a/linker/Tests/TestCases/TestSuites.cs +++ b/linker/Tests/TestCases/TestSuites.cs @@ -60,6 +60,18 @@ namespace Mono.Linker.Tests.TestCases Run(testCase); } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.TypeForwardingTests))] + public void TypeForwardingTests (TestCase testCase) + { + Run (testCase); + } + + [TestCaseSource(typeof (TestDatabase), nameof (TestDatabase.TestFrameworkTests))] + public void TestFrameworkTests (TestCase testCase) + { + Run (testCase); + } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.OtherTests))] public void OtherTests (TestCase testCase) { diff --git a/linker/Tests/TestCasesRunner/CompilerOptions.cs b/linker/Tests/TestCasesRunner/CompilerOptions.cs new file mode 100644 index 000000000..5929c8b8b --- /dev/null +++ b/linker/Tests/TestCasesRunner/CompilerOptions.cs @@ -0,0 +1,11 @@ +using System; +using Mono.Linker.Tests.Extensions; + +namespace Mono.Linker.Tests.TestCasesRunner { + public class CompilerOptions { + public NPath OutputPath; + public NPath[] SourceFiles; + public string[] Defines; + public NPath[] References; + } +} diff --git a/linker/Tests/TestCasesRunner/ILCompiler.cs b/linker/Tests/TestCasesRunner/ILCompiler.cs new file mode 100644 index 000000000..517bcb273 --- /dev/null +++ b/linker/Tests/TestCasesRunner/ILCompiler.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Mono.Linker.Tests.Extensions; +using NUnit.Framework; + +namespace Mono.Linker.Tests.TestCasesRunner { + public class ILCompiler { + private readonly string _ilasmExecutable; + + public ILCompiler () + { + _ilasmExecutable = Environment.OSVersion.Platform == PlatformID.Win32NT ? LocateIlasmOnWindows ().ToString () : "ilasm"; + } + + public ILCompiler (string ilasmExecutable) + { + _ilasmExecutable = ilasmExecutable; + } + + public NPath Compile (CompilerOptions options) + { + var capturedOutput = new List<string> (); + var process = new Process (); + SetupProcess (process, options); + process.StartInfo.RedirectStandardOutput = true; + process.OutputDataReceived += (sender, args) => capturedOutput.Add (args.Data); + process.Start (); + process.BeginOutputReadLine (); + process.WaitForExit (); + + if (process.ExitCode != 0) + { + Assert.Fail($"Failed to compile IL assembly : {options.OutputPath}\n{capturedOutput.Aggregate ((buff, s) => buff + Environment.NewLine + s)}"); + } + + return options.OutputPath; + } + + protected virtual void SetupProcess (Process process, CompilerOptions options) + { + process.StartInfo.FileName = _ilasmExecutable; + process.StartInfo.Arguments = BuildArguments (options); + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + } + + private string BuildArguments (CompilerOptions options) + { + var args = new StringBuilder(); + args.Append(options.OutputPath.ExtensionWithDot == ".dll" ? "/dll" : "/exe"); + args.Append($" /out:{options.OutputPath.InQuotes ()}"); + args.Append($" {options.SourceFiles.Aggregate (string.Empty, (buff, file) => $"{buff} {file.InQuotes ()}")}"); + return args.ToString (); + } + + public static NPath LocateIlasmOnWindows () + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + throw new InvalidOperationException ("This method should only be called on windows"); + + var possiblePath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory ().ToNPath ().Combine ("ilasm.exe"); + if (possiblePath.FileExists ()) + return possiblePath; + + throw new InvalidOperationException ("Could not locate a ilasm.exe executable"); + } + } +} diff --git a/linker/Tests/TestCasesRunner/ObjectFactory.cs b/linker/Tests/TestCasesRunner/ObjectFactory.cs index d756df6f9..0c96ae142 100644 --- a/linker/Tests/TestCasesRunner/ObjectFactory.cs +++ b/linker/Tests/TestCasesRunner/ObjectFactory.cs @@ -8,9 +8,9 @@ namespace Mono.Linker.Tests.TestCasesRunner { return new TestCaseSandbox (testCase); } - public virtual TestCaseCompiler CreateCompiler () + public virtual TestCaseCompiler CreateCompiler (TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider) { - return new TestCaseCompiler (); + return new TestCaseCompiler (sandbox, metadataProvider); } public virtual LinkerDriver CreateLinker () diff --git a/linker/Tests/TestCasesRunner/SetupCompileInfo.cs b/linker/Tests/TestCasesRunner/SetupCompileInfo.cs new file mode 100644 index 000000000..e50a4bd1c --- /dev/null +++ b/linker/Tests/TestCasesRunner/SetupCompileInfo.cs @@ -0,0 +1,12 @@ +using System; +using Mono.Linker.Tests.Extensions; + +namespace Mono.Linker.Tests.TestCasesRunner { + public class SetupCompileInfo { + public string OutputName; + public NPath[] SourceFiles; + public string[] Defines; + public string[] References; + public bool AddAsReference; + } +} diff --git a/linker/Tests/TestCasesRunner/TestCaseCollector.cs b/linker/Tests/TestCasesRunner/TestCaseCollector.cs index 9545e0061..ed3683541 100644 --- a/linker/Tests/TestCasesRunner/TestCaseCollector.cs +++ b/linker/Tests/TestCasesRunner/TestCaseCollector.cs @@ -59,6 +59,14 @@ namespace Mono.Linker.Tests.TestCasesRunner { continue; foreach (var file in subDir.Files ("*.cs", true)) { + + // Magic : Anything in a directory named Dependnecies is assumed to be a dependency to a test case + // and never a test itself + // This makes life a little easier when writing these supporting files as it removes some contraints you would previously have + // had to follow such as ensuring a class exists that matches the file name and putting [NotATestCase] on that class + if (file.Parent.FileName == "Dependencies") + continue; + yield return file; } } diff --git a/linker/Tests/TestCasesRunner/TestCaseCompiler.cs b/linker/Tests/TestCasesRunner/TestCaseCompiler.cs index cbebd5e06..4c403ccdb 100644 --- a/linker/Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/linker/Tests/TestCasesRunner/TestCaseCompiler.cs @@ -1,17 +1,156 @@ using System; using System.CodeDom.Compiler; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using Mono.Linker.Tests.Extensions; namespace Mono.Linker.Tests.TestCasesRunner { public class TestCaseCompiler { - public virtual NPath CompileTestIn (NPath outputDirectory, string outputName, IEnumerable<string> sourceFiles, IEnumerable<string> references, IEnumerable<string> defines) + protected readonly TestCaseMetadaProvider _metadataProvider; + protected readonly TestCaseSandbox _sandbox; + protected readonly ILCompiler _ilCompiler; + + public TestCaseCompiler (TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider) + : this(sandbox, metadataProvider, new ILCompiler ()) + { + } + + public TestCaseCompiler (TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider, ILCompiler ilCompiler) + { + _ilCompiler = ilCompiler; + _sandbox = sandbox; + _metadataProvider = metadataProvider; + } + + public NPath CompileTestIn (NPath outputDirectory, string outputName, IEnumerable<string> sourceFiles, IEnumerable<string> references, IEnumerable<string> defines) + { + var originalReferences = references.Select (r => r.ToNPath ()).ToArray (); + var originalDefines = defines?.ToArray () ?? new string [0]; + + Prepare (outputDirectory); + + var compiledReferences = CompileBeforeTestCaseAssemblies (outputDirectory, originalReferences, originalDefines).ToArray (); + var allTestCaseReferences = originalReferences.Concat (compiledReferences).ToArray (); + + var options = CreateOptionsForTestCase ( + outputDirectory.Combine (outputName), + sourceFiles.Select (s => s.ToNPath ()).ToArray (), + allTestCaseReferences, + originalDefines); + var testAssembly = CompileAssembly (options); + + + // The compile after step is used by tests to mess around with the input to the linker. Generally speaking, it doesn't seem like we would ever want to mess with the + // expectations assemblies because this would undermine our ability to inspect them for expected results during ResultChecking. The UnityLinker UnresolvedHandling tests depend on this + // behavior of skipping the after test compile + if (outputDirectory != _sandbox.ExpectationsDirectory) + CompileAfterTestCaseAssemblies (outputDirectory, originalReferences, originalDefines); + + return testAssembly; + } + + protected virtual void Prepare (NPath outputDirectory) { - var compilerOptions = CreateCompilerOptions (outputDirectory, outputName, references, defines); + } + + protected virtual CompilerOptions CreateOptionsForTestCase (NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines) + { + return new CompilerOptions + { + OutputPath = outputPath, + SourceFiles = sourceFiles, + References = references, + Defines = defines.Concat (_metadataProvider.GetDefines ()).ToArray () + }; + } + + protected virtual CompilerOptions CreateOptionsForSupportingAssembly (SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines) + { + var allDefines = defines.Concat (setupCompileInfo.Defines ?? new string [0]).ToArray (); + var allReferences = references.Concat (setupCompileInfo.References?.Select (p => MakeSupportingAssemblyReferencePathAbsolute (outputDirectory, p)) ?? new NPath [0]).ToArray (); + return new CompilerOptions + { + OutputPath = outputDirectory.Combine (setupCompileInfo.OutputName), + SourceFiles = sourceFiles, + References = allReferences, + Defines = allDefines + }; + } + + private IEnumerable<NPath> CompileBeforeTestCaseAssemblies (NPath outputDirectory, NPath[] references, string[] defines) + { + foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesBefore ()) + { + var options = CreateOptionsForSupportingAssembly (setupCompileInfo, outputDirectory, CollectSetupBeforeSourcesFiles (setupCompileInfo), references, defines); + var output = CompileAssembly (options); + if (setupCompileInfo.AddAsReference) + yield return output; + } + } + + private void CompileAfterTestCaseAssemblies (NPath outputDirectory, NPath[] references, string[] defines) + { + foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesAfter ()) + { + var options = CreateOptionsForSupportingAssembly (setupCompileInfo, outputDirectory, CollectSetupAfterSourcesFiles (setupCompileInfo), references, defines); + CompileAssembly (options); + } + } + + private NPath[] CollectSetupBeforeSourcesFiles (SetupCompileInfo info) + { + return CollectSourceFilesFrom (_sandbox.BeforeReferenceSourceDirectoryFor (info.OutputName)); + } + + private NPath[] CollectSetupAfterSourcesFiles (SetupCompileInfo info) + { + return CollectSourceFilesFrom (_sandbox.AfterReferenceSourceDirectoryFor (info.OutputName)); + } + + private static NPath[] CollectSourceFilesFrom (NPath directory) + { + var sourceFiles = directory.Files ("*.cs").ToArray (); + if (sourceFiles.Length > 0) + return sourceFiles; + + sourceFiles = directory.Files ("*.il").ToArray (); + if (sourceFiles.Length > 0) + return sourceFiles; + + throw new FileNotFoundException ($"Didn't find any sources files in {directory}"); + } + + protected static NPath MakeSupportingAssemblyReferencePathAbsolute (NPath outputDirectory, string referenceFileName) + { + // Not a good idea to use a full path in a test, but maybe someone is trying to quickly test something locally + if (Path.IsPathRooted (referenceFileName)) + return referenceFileName.ToNPath (); + + var possiblePath = outputDirectory.Combine (referenceFileName); + if (possiblePath.FileExists ()) + return possiblePath; + + return referenceFileName.ToNPath(); + } + + protected NPath CompileAssembly (CompilerOptions options) + { + if (options.SourceFiles.Any (path => path.ExtensionWithDot == ".cs")) + return CompileCSharpAssembly (options); + + if (options.SourceFiles.Any (path => path.ExtensionWithDot == ".il")) + return CompileIlAssembly (options); + + throw new NotSupportedException ($"Unable to compile sources files with extension `{options.SourceFiles.First ().ExtensionWithDot}`"); + } + + protected virtual NPath CompileCSharpAssembly (CompilerOptions options) + { + var compilerOptions = CreateCodeDomCompilerOptions (options); var provider = CodeDomProvider.CreateProvider ("C#"); - var result = provider.CompileAssemblyFromFile (compilerOptions, sourceFiles.ToArray ()); + var result = provider.CompileAssemblyFromFile (compilerOptions, options.SourceFiles.Select (p => p.ToString ()).ToArray ()); if (!result.Errors.HasErrors) return compilerOptions.OutputAssembly.ToNPath (); @@ -21,19 +160,22 @@ namespace Mono.Linker.Tests.TestCasesRunner { throw new Exception ("Compilation errors: " + errors); } - protected virtual CompilerParameters CreateCompilerOptions (NPath outputDirectory, string outputName, IEnumerable<string> references, IEnumerable<string> defines) + protected NPath CompileIlAssembly (CompilerOptions options) { - var outputPath = outputDirectory.Combine (outputName); + return _ilCompiler.Compile (options); + } + private CompilerParameters CreateCodeDomCompilerOptions (CompilerOptions options) + { var compilerParameters = new CompilerParameters { - OutputAssembly = outputPath.ToString (), - GenerateExecutable = outputName.EndsWith(".exe") + OutputAssembly = options.OutputPath.ToString (), + GenerateExecutable = options.OutputPath.FileName.EndsWith (".exe") }; - compilerParameters.CompilerOptions = defines?.Aggregate (string.Empty, (buff, arg) => $"{buff} /define:{arg}"); + compilerParameters.CompilerOptions = options.Defines?.Aggregate (string.Empty, (buff, arg) => $"{buff} /define:{arg}"); - compilerParameters.ReferencedAssemblies.AddRange (references.ToArray ()); + compilerParameters.ReferencedAssemblies.AddRange (options.References.Select (r => r.ToString ()).ToArray ()); return compilerParameters; } diff --git a/linker/Tests/TestCasesRunner/TestCaseMetadaProvider.cs b/linker/Tests/TestCasesRunner/TestCaseMetadaProvider.cs index 8055cfccb..46b981793 100644 --- a/linker/Tests/TestCasesRunner/TestCaseMetadaProvider.cs +++ b/linker/Tests/TestCasesRunner/TestCaseMetadaProvider.cs @@ -73,6 +73,27 @@ namespace Mono.Linker.Tests.TestCasesRunner { } } + public virtual IEnumerable<SetupCompileInfo> GetSetupCompileAssembliesBefore () + { + return _testCaseTypeDefinition.CustomAttributes + .Where (attr => attr.AttributeType.Name == nameof (SetupCompileBeforeAttribute)) + .Select (CreateSetupCompileAssemblyInfo); + } + + public virtual IEnumerable<SetupCompileInfo> GetSetupCompileAssembliesAfter () + { + return _testCaseTypeDefinition.CustomAttributes + .Where (attr => attr.AttributeType.Name == nameof (SetupCompileAfterAttribute)) + .Select (CreateSetupCompileAssemblyInfo); + } + + public virtual IEnumerable<string> GetDefines () + { + return _testCaseTypeDefinition.CustomAttributes + .Where (attr => attr.AttributeType.Name == nameof (DefineAttribute)) + .Select (attr => (string) attr.ConstructorArguments.First ().Value); + } + T GetOptionAttributeValue<T> (string attributeName, T defaultValue) { var attribute = _testCaseTypeDefinition.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == attributeName); @@ -81,5 +102,18 @@ namespace Mono.Linker.Tests.TestCasesRunner { return defaultValue; } + + private SetupCompileInfo CreateSetupCompileAssemblyInfo (CustomAttribute attribute) + { + var ctorArguments = attribute.ConstructorArguments; + return new SetupCompileInfo + { + OutputName = (string) ctorArguments [0].Value, + SourceFiles = ((CustomAttributeArgument []) ctorArguments [1].Value).Select (arg => _testCase.SourceFile.Parent.Combine (arg.Value.ToString ())).ToArray (), + References = ((CustomAttributeArgument []) ctorArguments [2].Value)?.Select (arg => arg.Value.ToString ()).ToArray (), + Defines = ((CustomAttributeArgument []) ctorArguments [3].Value)?.Select (arg => arg.Value.ToString ()).ToArray (), + AddAsReference = ctorArguments.Count >= 5 ? (bool) ctorArguments [4].Value : true + }; + } } }
\ No newline at end of file diff --git a/linker/Tests/TestCasesRunner/TestCaseSandbox.cs b/linker/Tests/TestCasesRunner/TestCaseSandbox.cs index 03e990ff0..17f66dd0c 100644 --- a/linker/Tests/TestCasesRunner/TestCaseSandbox.cs +++ b/linker/Tests/TestCasesRunner/TestCaseSandbox.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Extensions; using Mono.Linker.Tests.TestCases; @@ -63,6 +64,18 @@ namespace Mono.Linker.Tests.TestCasesRunner { foreach (var dep in metadataProvider.AdditionalFilesToSandbox ()) { dep.FileMustExist ().Copy (_directory); } + + foreach (var compileRefInfo in metadataProvider.GetSetupCompileAssembliesBefore ()) + { + var destination = BeforeReferenceSourceDirectoryFor (compileRefInfo.OutputName).EnsureDirectoryExists (); + compileRefInfo.SourceFiles.Copy (destination); + } + + foreach (var compileRefInfo in metadataProvider.GetSetupCompileAssembliesAfter ()) + { + var destination = AfterReferenceSourceDirectoryFor (compileRefInfo.OutputName).EnsureDirectoryExists (); + compileRefInfo.SourceFiles.Copy (destination); + } } private static NPath GetExpectationsAssemblyPath () @@ -75,5 +88,15 @@ namespace Mono.Linker.Tests.TestCasesRunner { source.Copy (InputDirectory); source.Copy (ExpectationsDirectory); } + + public NPath BeforeReferenceSourceDirectoryFor (string outputName) + { + return _directory.Combine ($"ref_source_before_{Path.GetFileNameWithoutExtension (outputName)}"); + } + + public NPath AfterReferenceSourceDirectoryFor (string outputName) + { + return _directory.Combine ($"ref_source_after_{Path.GetFileNameWithoutExtension (outputName)}"); + } } }
\ No newline at end of file diff --git a/linker/Tests/TestCasesRunner/TestRunner.cs b/linker/Tests/TestCasesRunner/TestRunner.cs index 848ee27b6..9693eeaf7 100644 --- a/linker/Tests/TestCasesRunner/TestRunner.cs +++ b/linker/Tests/TestCasesRunner/TestRunner.cs @@ -37,7 +37,7 @@ namespace Mono.Linker.Tests.TestCasesRunner { private ManagedCompilationResult Compile (TestCaseSandbox sandbox, TestCaseMetadaProvider metadataProvider) { - var compiler = _factory.CreateCompiler (); + var compiler = _factory.CreateCompiler (sandbox, metadataProvider); var sourceFiles = sandbox.SourceFiles.Select(s => s.ToString()).ToArray(); var references = metadataProvider.GetReferencedAssemblies(sandbox.InputDirectory); |