// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Mono.Linker; using Xunit; namespace ILLink.Tasks.Tests { // These tests ensure that the task options correctly flow from // the task -> response file -> parsed arguments -> options on LinkContext public class TaskArgumentTests { public static IEnumerable AssemblyPathsCases => new List { new object [] { new ITaskItem [] { new TaskItem ("Assembly.dll", new Dictionary { { "trimmode", "copy" } }) } }, new object [] { new ITaskItem [] { new TaskItem ("Assembly.dll", new Dictionary { { "TrimMode", "Copy" } }) } }, new object [] { new ITaskItem [] { new TaskItem ("path with/spaces/Assembly.dll") } }, new object [] { new ITaskItem [] { // same path new TaskItem ("path/to/Assembly1.dll"), new TaskItem ("path/to/Assembly2.dll") } }, new object [] { new ITaskItem [] { // same assembly new TaskItem ("path/to/Assembly.dll"), new TaskItem ("path/to/Assembly.dll") } }, new object [] { new ITaskItem [] { // same assembly name, different paths new TaskItem ("path1/Assembly.dll"), new TaskItem ("path2/Assembly.dll") } } }; [Theory] [MemberData (nameof (AssemblyPathsCases))] public void TestAssemblyPaths (ITaskItem[] assemblyPaths) { var task = new MockTask () { AssemblyPaths = assemblyPaths }; using (var driver = task.CreateDriver ()) { var context = driver.Context; var expectedReferences = assemblyPaths.Select (p => p.ItemSpec) .GroupBy (p => Path.GetFileNameWithoutExtension (p)) .Select (g => g.First ()); var actualReferences = driver.GetReferenceAssemblies (); Assert.Equal (expectedReferences.OrderBy (a => a), actualReferences.OrderBy (a => a)); foreach (var item in assemblyPaths) { var assemblyPath = item.ItemSpec; var trimMode = item.GetMetadata ("TrimMode"); if (String.IsNullOrEmpty (trimMode)) continue; AssemblyAction expectedAction = (AssemblyAction) Enum.Parse (typeof (AssemblyAction), trimMode, ignoreCase: true); AssemblyAction actualAction = context.Actions[Path.GetFileNameWithoutExtension (assemblyPath)]; Assert.Equal (expectedAction, actualAction); } } } [Fact] public void TestAssemblyPathsWithInvalidAction () { var task = new MockTask () { AssemblyPaths = new ITaskItem[] { new TaskItem ("Assembly.dll", new Dictionary { { "TrimMode", "invalid" } }) } }; using (var driver = task.CreateDriver ()) { Assert.Equal (1031, driver.Logger.Messages[0].Code); } } // the InlineData string [] parameters are wrapped in object [] as described in https://github.com/xunit/xunit/issues/2060 [Theory] [InlineData (new object[] { new string[] { "path/to/Assembly.dll" } })] [InlineData (new object[] { new string[] { "path with/spaces/Assembly.dll" } })] [InlineData (new object[] { new string[] { "path/to/Assembly With Spaces.dll" } })] public void TestReferenceAssemblyPaths (string[] referenceAssemblyPaths) { var task = new MockTask () { ReferenceAssemblyPaths = referenceAssemblyPaths.Select (p => new TaskItem (p)).ToArray () }; using (var driver = task.CreateDriver ()) { var expectedReferences = referenceAssemblyPaths; var actualReferences = driver.GetReferenceAssemblies (); Assert.Equal (expectedReferences.OrderBy (a => a), actualReferences.OrderBy (a => a)); foreach (var reference in expectedReferences) { var referenceName = Path.GetFileNameWithoutExtension (reference); var actualAction = driver.Context.Actions[referenceName]; Assert.Equal (AssemblyAction.Skip, actualAction); } } } [Theory] [InlineData (new object[] { new string[] { "illink.dll" } })] [InlineData (new object[] { new string[] { "illink" } })] public void TestRootEntryPointAssemblyNames (string[] rootAssemblyNames) { var task = new MockTask () { RootAssemblyNames = rootAssemblyNames.Select (a => new TaskItem (a)).ToArray () }; using (var driver = task.CreateDriver ()) { var expectedRoots = rootAssemblyNames; var actualRoots = driver.GetRootAssemblies (); Assert.Equal (rootAssemblyNames.OrderBy (r => r), actualRoots.OrderBy (r => r)); } } [Theory] [InlineData ("path/to/directory")] [InlineData ("path with/spaces")] public void TestOutputDirectory (string outputDirectory) { var task = new MockTask () { OutputDirectory = new TaskItem (outputDirectory) }; using (var driver = task.CreateDriver ()) { var actualOutputDirectory = driver.Context.OutputDirectory; Assert.Equal (outputDirectory, actualOutputDirectory); } } [Theory] [InlineData (new object[] { new string[] { "combined_output.xml" } })] public void TestRootDescriptorFiles (string[] rootDescriptorFiles) { var task = new MockTask () { RootDescriptorFiles = rootDescriptorFiles.Select (f => new TaskItem (f)).ToArray () }; using (var driver = task.CreateDriver ()) { var actualDescriptors = driver.GetRootDescriptors (); Assert.Equal (rootDescriptorFiles.OrderBy (f => f), actualDescriptors.OrderBy (f => f)); } } public static IEnumerable OptimizationsCases () { foreach (var optimization in MockTask.OptimizationNames) { yield return new object[] { optimization, true }; yield return new object[] { optimization, false }; } } [Theory] [MemberData (nameof (OptimizationsCases))] public void TestGlobalOptimizations (string optimization, bool enabled) { var task = new MockTask (); task.SetOptimization (optimization, enabled); // get the corresponding CodeOptimizations value using (var driver = task.CreateDriver ()) { Assert.True (driver.GetOptimizationName (optimization, out CodeOptimizations codeOptimizations)); var actualValue = driver.Context.Optimizations.IsEnabled (codeOptimizations, assemblyName: null); Assert.Equal (enabled, actualValue); } } public static IEnumerable PerAssemblyOptimizationsCases () { // test that we can individually enable/disable each optimization foreach (var optimization in MockTask.OptimizationNames) { yield return new object[] { new ITaskItem [] { new TaskItem ("path/to/Assembly.dll", new Dictionary { { optimization, "True" } }) } }; yield return new object[] { new ITaskItem [] { new TaskItem ("path/to/Assembly.dll", new Dictionary { { optimization, "False" } }) } }; } // complex case with multiple optimizations, assemblies yield return new object[] { new ITaskItem [] { new TaskItem ("path/to/Assembly1.dll", new Dictionary { { "Sealer", "True" }, { "BeforeFieldInit", "False" } }), new TaskItem ("path/to/Assembly2.dll", new Dictionary { { "Sealer", "False" }, { "BeforeFieldInit", "True" } }) } }; } [Theory] [MemberData (nameof (PerAssemblyOptimizationsCases))] public void TestPerAssemblyOptimizations (ITaskItem[] assemblyPaths) { var task = new MockTask () { AssemblyPaths = assemblyPaths }; using (var driver = task.CreateDriver ()) { foreach (var item in assemblyPaths) { var assemblyName = Path.GetFileNameWithoutExtension (item.ItemSpec); foreach (var optimization in MockTask.OptimizationNames) { Assert.True (driver.GetOptimizationName (optimization, out CodeOptimizations codeOptimizations)); var optimizationValue = item.GetMetadata (optimization); if (String.IsNullOrEmpty (optimizationValue)) continue; var enabled = Boolean.Parse (optimizationValue); var actualValue = driver.Context.Optimizations.IsEnabled (codeOptimizations, assemblyName: assemblyName); Assert.Equal (enabled, actualValue); } } } } public static IEnumerable SingleWarnCases => new List { new object[] { true, new ITaskItem [] { new TaskItem ("AssemblyTrue.dll", new Dictionary { { "TrimmerSingleWarn", "true" } } ), new TaskItem ("AssemblyFalse.dll", new Dictionary { { "TrimmerSingleWarn", "false" } } ) }, }, new object [] { false, new ITaskItem [] { new TaskItem ("AssemblyTrue.dll", new Dictionary { { "TrimmerSingleWarn", "true" } } ), new TaskItem ("AssemblyFalse.dll", new Dictionary { { "TrimmerSingleWarn", "false" } } ) } } }; [Theory] [MemberData (nameof (SingleWarnCases))] public void TestSingleWarn (bool singleWarn, ITaskItem[] assemblyPaths) { var task = new MockTask () { AssemblyPaths = assemblyPaths, SingleWarn = singleWarn }; using (var driver = task.CreateDriver ()) { Assert.Equal (singleWarn, driver.Context.GeneralSingleWarn); var expectedSingleWarn = assemblyPaths.ToDictionary ( p => Path.GetFileNameWithoutExtension (p.ItemSpec), p => bool.Parse (p.GetMetadata ("TrimmerSingleWarn")) ); Assert.Equal (expectedSingleWarn, driver.Context.SingleWarn); } } [Fact] public void TestInvalidPerAssemblyOptimizations () { var task = new MockTask () { AssemblyPaths = new ITaskItem[] { new TaskItem ("path/to/Assembly.dll", new Dictionary { { "Sealer", "invalid" } }) } }; Assert.Throws (() => task.CreateDriver ()); } [Fact] public void TestOptimizationsDefaults () { var task = new MockTask (); using (var driver = task.CreateDriver ()) { var expectedOptimizations = driver.GetDefaultOptimizations (); var actualOptimizations = driver.Context.Optimizations.Global; Assert.Equal (expectedOptimizations, actualOptimizations); } } [Fact] public void CheckGlobalOptimizationsMatchPerAssemblyOptimizations () { var task = new MockTask (); var optimizationMetadataNames = MockTask.OptimizationNames; var optimizationPropertyNames = MockTask.GetOptimizationPropertyNames (); Assert.Equal (optimizationMetadataNames.OrderBy (o => o), optimizationPropertyNames.OrderBy (o => o)); } [Theory] [InlineData ("IL2001;IL2002;IL2003;IL2004", 4)] [InlineData ("IL2001 IL2002 IL2003 IL2004", 4)] [InlineData ("IL2001,IL2002,IL2003,IL2004", 4)] [InlineData ("IL2001,IL2002;IL2003 IL2004", 4)] [InlineData ("IL2001,IL2002,IL8000,IL1003", 4)] [InlineData ("IL20000,IL02000", 2)] [InlineData (" IL2001\n IL2002;\n \tIL2003", 3)] public void TestValidNoWarn (string noWarn, int validNoWarns) { var task = new MockTask () { NoWarn = noWarn }; using (var driver = task.CreateDriver ()) { var actualUsedNoWarns = driver.Context.NoWarn; Assert.Equal (validNoWarns, actualUsedNoWarns.Count); } } [Theory] [InlineData ("0", WarnVersion.ILLink0)] [InlineData ("5", WarnVersion.ILLink5)] [InlineData ("6", (WarnVersion) 6)] [InlineData ("9999", WarnVersion.Latest)] public void TestWarn (string warnArg, WarnVersion expectedVersion) { var task = new MockTask () { Warn = warnArg }; using (var driver = task.CreateDriver ()) { Assert.Equal (expectedVersion, driver.Context.WarnVersion); } } #nullable enable [Theory] [InlineData (true, null, null, new int[] { }, new int[] { })] [InlineData (false, "IL1001,IL2000,IL2054,IL2022", null, new int[] { 1001, 2000, 2054, 2022 }, new int[] { })] [InlineData (false, "IL2023,IL6000;IL5042 IL2040", "IL4000,IL4001;IL4002 IL4003", new int[] { 2023, 2040, 5042, 6000 }, new int[] { 4000, 4001, 4002, 4003 })] [InlineData (false, "IL3000;IL3000;ABCD", "IL2005 IL3005 IL2005", new int[] { 3000 }, new int[] { 2005, 3005 })] [InlineData (true, null, "IL2067", new int[] { }, new int[] { 2067 })] [InlineData (true, "IL2001", "IL2001", new int[] { }, new int[] { 2001 })] [InlineData (false, "IL1001;\n\t IL1002\n\t IL1003", null, new int[] { 1001, 1002, 1003 }, new int[] { })] public void TestWarningsAsErrors (bool treatWarningsAsErrors, string? warningsAsErrors, string? warningsNotAsErrors, int[] warnAsError, int[] warnNotAsError) { var task = new MockTask () { TreatWarningsAsErrors = treatWarningsAsErrors, WarningsAsErrors = warningsAsErrors, WarningsNotAsErrors = warningsNotAsErrors }; using (var driver = task.CreateDriver ()) { var actualWarnAsError = driver.Context.WarnAsError; var actualGeneralWarnAsError = driver.Context.GeneralWarnAsError; Assert.Equal (warnAsError.Count () + warnNotAsError.Count (), actualWarnAsError.Count); Assert.Equal (treatWarningsAsErrors, actualGeneralWarnAsError); if (warnAsError.Length > 0) { foreach (var warningCode in warnAsError) Assert.True (actualWarnAsError.ContainsKey (warningCode) && actualWarnAsError[warningCode] == true); } if (warnNotAsError.Length > 0) { foreach (var warningCode in warnNotAsError) Assert.True (actualWarnAsError.ContainsKey (warningCode) && actualWarnAsError[warningCode] == false); } } } #nullable disable public static IEnumerable CustomDataCases => new List { new object [] { new ITaskItem [] { new TaskItem ("DataName", new Dictionary { { "Value", "DataValue" } }) }, }, new object [] { new ITaskItem [] { new TaskItem ("DataName", new Dictionary { { "Value", "DataValue" } }), new TaskItem ("DataName", new Dictionary { { "Value", "DataValue2" } }) }, }, new object [] { new ITaskItem [] { new TaskItem ("DataName1", new Dictionary { { "Value", "DataValue1" } }), new TaskItem ("DataName2", new Dictionary { { "Value", "DataValue2" } }) }, }, new object [] { new ITaskItem [] { new TaskItem ("DataName", new Dictionary { { "Value", "data value with spaces" } }) }, }, }; [Theory] [MemberData (nameof (CustomDataCases))] public void TestCustomData (ITaskItem[] customData) { var task = new MockTask () { CustomData = customData }; using (var driver = task.CreateDriver ()) { var expectedCustomData = customData.Select (c => new { Key = c.ItemSpec, Value = c.GetMetadata ("Value") }) .GroupBy (c => c.Key) .Select (c => c.Last ()) .ToDictionary (c => c.Key, c => c.Value); var actualCustomData = driver.GetCustomData (); Assert.Equal (expectedCustomData, actualCustomData); } } public static IEnumerable FeatureSettingsCases => new List { new object [] { new ITaskItem [] { new TaskItem ("FeatureName", new Dictionary { { "Value", "true" } }) }, }, new object [] { new ITaskItem [] { new TaskItem ("FeatureName", new Dictionary { { "Value", "true" } }), new TaskItem ("FeatureName", new Dictionary { { "Value", "false" } }) }, }, new object [] { new ITaskItem [] { new TaskItem ("FeatureName1", new Dictionary { { "value", "true" } }), new TaskItem ("FeatureName2", new Dictionary { { "value", "false" } }), }, }, }; [Theory] [MemberData (nameof (FeatureSettingsCases))] public void TestFeatureSettings (ITaskItem[] featureSettings) { var task = new MockTask () { FeatureSettings = featureSettings }; using (var driver = task.CreateDriver ()) { var expectedSettings = featureSettings.Select (f => new { Feature = f.ItemSpec, Value = f.GetMetadata ("Value") }) .GroupBy (f => f.Feature) .Select (f => f.Last ()) .ToDictionary (f => f.Feature, f => bool.Parse (f.Value)); var actualSettings = driver.Context.FeatureSettings; Assert.Equal (expectedSettings, actualSettings); } } [Fact] public void TestInvalidFeatureSettings () { var task = new MockTask () { FeatureSettings = new ITaskItem[] { new TaskItem ("FeatureName") } }; Assert.Throws (() => task.CreateDriver ()); } [Fact] public void TestExtraArgs () { var task = new MockTask () { TrimMode = "copy", ExtraArgs = "--trim-mode copyused" }; using (var driver = task.CreateDriver ()) { Assert.Equal (AssemblyAction.Link, driver.Context.DefaultAction); // Check that ExtraArgs can override TrimMode Assert.Equal (AssemblyAction.CopyUsed, driver.Context.TrimAction); } } [Theory] [InlineData (true)] [InlineData (false)] public void TestDumpDependencies (bool dumpDependencies) { var task = new MockTask () { DumpDependencies = dumpDependencies }; using (var driver = task.CreateDriver ()) { Assert.Equal (dumpDependencies, driver.GetDependencyRecorders ()?.Single () == MockXmlDependencyRecorder.Singleton); } } [Theory] [InlineData (true)] [InlineData (false)] public void TestRemoveSymbols (bool removeSymbols) { var task = new MockTask () { RemoveSymbols = removeSymbols }; using (var driver = task.CreateDriver ()) { Assert.NotEqual (removeSymbols, driver.Context.LinkSymbols); } } [Fact] public void TestRemoveSymbolsDefault () { var task = new MockTask (); using (var driver = task.CreateDriver ()) { Assert.False (driver.Context.LinkSymbols); } } [Fact] public void TestKeepCustomMetadata () { var task = new MockTask () { KeepMetadata = new ITaskItem[] { new TaskItem ("parametername") } }; using (var driver = task.CreateDriver ()) { Assert.Equal (MetadataTrimming.None, driver.Context.MetadataTrimming); } } [Theory] [InlineData ("copy")] [InlineData ("link")] [InlineData ("copyused")] public void TestGlobalTrimMode (string trimMode) { var task = new MockTask () { TrimMode = trimMode }; using (var driver = task.CreateDriver ()) { var expectedAction = (AssemblyAction) Enum.Parse (typeof (AssemblyAction), trimMode, ignoreCase: true); Assert.Equal (expectedAction, driver.Context.TrimAction); Assert.Equal (AssemblyAction.Link, driver.Context.DefaultAction); } } [Theory] [InlineData ("copy")] [InlineData ("link")] [InlineData ("copyused")] public void TestDefaultAction (string defaultAction) { var task = new MockTask () { DefaultAction = defaultAction }; using (var driver = task.CreateDriver ()) { var expectedAction = (AssemblyAction) Enum.Parse (typeof (AssemblyAction), defaultAction, ignoreCase: true); Assert.Equal (expectedAction, driver.Context.DefaultAction); Assert.Equal (AssemblyAction.Link, driver.Context.TrimAction); } } [Fact] public void TestInvalidDefaultAction () { var task = new MockTask () { TrimMode = "invalid" }; using (var driver = task.CreateDriver ()) { Assert.Equal (1031, driver.Logger.Messages[0].Code); } } public static IEnumerable CustomStepsCases => new List { new object [] { new ITaskItem [] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" } }) }, }, new object [] { new ITaskItem [] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" }, { "BeforeStep", "MarkStep" } }) }, }, new object [] { new ITaskItem [] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "type", "ILLink.Tasks.Tests.MockCustomStep" }, { "beforebtep", "MarkStep" } }) }, }, new object [] { new ITaskItem [] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" }, { "AfterStep", "MarkStep" } }) }, }, new object [] { new ITaskItem [] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" }, { "BeforeStep", "MarkStep" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" }, { "AfterStep", "MarkStep" } }) }, } }; [Theory] [MemberData (nameof (CustomStepsCases))] public void TestCustomSteps (ITaskItem[] customSteps) { var task = new MockTask () { CustomSteps = customSteps }; using (var driver = task.CreateDriver ()) { foreach (var customStep in customSteps) { var stepType = customStep.GetMetadata ("Type"); var stepName = stepType.Substring (stepType.LastIndexOf (Type.Delimiter) + 1); var beforeStepName = customStep.GetMetadata ("BeforeStep"); var afterStepName = customStep.GetMetadata ("AfterStep"); Assert.True (String.IsNullOrEmpty (beforeStepName) || String.IsNullOrEmpty (afterStepName)); var actualStepNames = driver.Context.Pipeline.GetSteps ().Select (s => s.GetType ().Name); if (!String.IsNullOrEmpty (beforeStepName)) { Assert.Contains (beforeStepName, actualStepNames); Assert.Equal (stepName, actualStepNames.TakeWhile (s => s != beforeStepName).Last ()); } else if (!String.IsNullOrEmpty (afterStepName)) { Assert.Contains (afterStepName, actualStepNames); Assert.Equal (stepName, actualStepNames.SkipWhile (s => s != afterStepName).ElementAt (1)); } else { Assert.Equal (stepName, actualStepNames.Last ()); } } } } [Fact] public void TestCustomStepsWithBeforeAndAfterSteps () { var customSteps = new ITaskItem[] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" }, { "BeforeStep", "MarkStep" }, { "AfterStep", "MarkStep" } }) }; var task = new MockTask () { CustomSteps = customSteps }; Assert.Throws (() => task.CreateDriver ()); } [Fact] public void TestCustomStepOrdering () { var customSteps = new ITaskItem[] { new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep" }, { "BeforeStep", "MarkStep" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep2" }, { "BeforeStep", "MarkStep" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep3" }, { "AfterStep", "MarkStep" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep4" }, { "AfterStep", "MarkStep" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep5" }, }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockCustomStep6" }, }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockMarkHandler" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockMarkHandler2" }, { "BeforeStep", "MockMarkHandler" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockMarkHandler3" }, { "AfterStep", "MockMarkHandler2" } }), new TaskItem (Assembly.GetExecutingAssembly ().Location, new Dictionary { { "Type", "ILLink.Tasks.Tests.MockMarkHandler4" } }), }; var task = new MockTask () { CustomSteps = customSteps }; using (var driver = task.CreateDriver ()) { var actualSteps = driver.Context.Pipeline.GetSteps ().Select (s => s.GetType ().Name).ToList (); Assert.Equal (new List { "MockCustomStep", "MockCustomStep2", "MarkStep", "MockCustomStep4", "MockCustomStep3", }, actualSteps.Skip (actualSteps.IndexOf ("MarkStep") - 2).Take (5).ToList ()); Assert.Equal (new List { "MockCustomStep5", "MockCustomStep6" }, actualSteps.TakeLast (2).ToList ()); var actualMarkHandlers = driver.Context.Pipeline.MarkHandlers.Select (h => h.GetType ().Name).ToList (); Assert.Equal (new List { "MockMarkHandler2", "MockMarkHandler3", "MockMarkHandler", "MockMarkHandler4" }, actualMarkHandlers.TakeLast (4).ToList ()); } } [Fact] public void TestCustomStepsMissingType () { var customSteps = new ITaskItem[] { new TaskItem (Assembly.GetExecutingAssembly ().Location) }; var task = new MockTask () { CustomSteps = customSteps }; Assert.Throws (() => task.CreateDriver ()); } [Fact] public void TestErrorHandling () { var task = new MockTask () { RootAssemblyNames = new ITaskItem[0] }; task.BuildEngine = new MockBuildEngine (); Assert.False (task.Execute ()); Assert.Contains (task.Messages, message => message.Line.Contains ("No input files were specified")); } } }