1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.IO; 8 using System.Reflection; 9 using System.Reflection.Metadata; 10 using System.Reflection.Metadata.Ecma335; 11 using System.Text; 12 using Internal.IL; 13 using Internal.TypeSystem; 14 using Internal.TypeSystem.Ecma; 15 using Newtonsoft.Json; 16 using Xunit; 17 using Xunit.Abstractions; 18 19 namespace ILVerify.Tests 20 { 21 /// <summary> 22 /// Parses the methods in the test assemblies. 23 /// It loads all assemblies from the test folder defined in <code>TestDataLoader.TESTASSEMBLYPATH</code> 24 /// This class feeds the xunit Theories 25 /// </summary> 26 class TestDataLoader 27 { 28 /// <summary> 29 /// The folder with the binaries which are compiled from the test driver IL Code 30 /// Currently the test .il code is built manually, but the plan is to have a ProjectReference and automatically build the .il files. 31 /// See: https://github.com/dotnet/corert/pull/3725#discussion_r118820770 32 /// </summary> 33 public static string TESTASSEMBLYPATH = @"..\..\..\ILTests\"; 34 35 private const string SPECIALTEST_PREFIX = "special."; 36 37 /// <summary> 38 /// Returns all methods that contain valid IL code based on the following naming convention: 39 /// [FriendlyName]_Valid 40 /// The method must contain 1 '_'. The part before the '_' is a friendly name describing what the method does. 41 /// The word after the '_' has to be 'Valid' (Case sensitive) 42 /// E.g.: 'SimpleAdd_Valid' 43 /// </summary> GetMethodsWithValidIL()44 public static TheoryData<TestCase> GetMethodsWithValidIL() 45 { 46 var methodSelector = new Func<string[], MethodDefinitionHandle, TestCase>((mparams, methodHandle) => 47 { 48 if (mparams.Length == 2 && mparams[1] == "Valid") 49 { 50 return new ValidILTestCase { MetadataToken = MetadataTokens.GetToken(methodHandle) }; 51 } 52 return null; 53 }); 54 return GetTestMethodsFromDll(methodSelector); 55 } 56 57 /// <summary> 58 /// Returns all methods that contain valid IL code based on the following naming convention: 59 /// [FriendlyName]_Invalid_[ExpectedVerifierError1].[ExpectedVerifierError2]....[ExpectedVerifierErrorN] 60 /// The method name must contain 2 '_' characters. 61 /// 1. part: a friendly name 62 /// 2. part: must be the word 'Invalid' (Case sensitive) 63 /// 3. part: the expected VerifierErrors as string separated by '.'. 64 /// E.g.: SimpleAdd_Invalid_ExpectedNumericType 65 /// </summary> GetMethodsWithInvalidIL()66 public static TheoryData<TestCase> GetMethodsWithInvalidIL() 67 { 68 var methodSelector = new Func<string[], MethodDefinitionHandle, TestCase>((mparams, methodHandle) => 69 { 70 if (mparams.Length == 3 && mparams[1] == "Invalid") 71 { 72 var expectedErrors = mparams[2].Split('.'); 73 var verificationErros = new List<VerifierError>(); 74 75 foreach (var item in expectedErrors) 76 { 77 if (Enum.TryParse(item, out VerifierError expectedError)) 78 { 79 verificationErros.Add(expectedError); 80 } 81 } 82 83 var newItem = new InvalidILTestCase { MetadataToken = MetadataTokens.GetToken(methodHandle) }; 84 85 if (expectedErrors.Length > 0) 86 { 87 newItem.ExpectedVerifierErrors = verificationErros; 88 } 89 90 return newItem; 91 } 92 return null; 93 }); 94 return GetTestMethodsFromDll(methodSelector); 95 } 96 GetTestMethodsFromDll(Func<string[], MethodDefinitionHandle, TestCase> methodSelector)97 private static TheoryData<TestCase> GetTestMethodsFromDll(Func<string[], MethodDefinitionHandle, TestCase> methodSelector) 98 { 99 var retVal = new Xunit.TheoryData<TestCase>(); 100 101 foreach (var testDllName in GetAllTestDlls()) 102 { 103 var testModule = GetModuleForTestAssembly(testDllName); 104 105 foreach (var methodHandle in testModule.MetadataReader.MethodDefinitions) 106 { 107 var method = (EcmaMethod)testModule.GetMethod(methodHandle); 108 var methodName = method.Name; 109 110 if (!String.IsNullOrEmpty(methodName) && methodName.Contains("_")) 111 { 112 var mparams = methodName.Split('_'); 113 var specialMethodHandle = HandleSpecialTests(mparams, method); 114 var newItem = methodSelector(mparams, specialMethodHandle); 115 116 if (newItem != null) 117 { 118 newItem.TestName = mparams[0]; 119 newItem.MethodName = methodName; 120 newItem.ModuleName = testDllName; 121 122 retVal.Add(newItem); 123 } 124 } 125 } 126 } 127 return retVal; 128 } 129 HandleSpecialTests(string[] methodParams, EcmaMethod method)130 private static MethodDefinitionHandle HandleSpecialTests(string[] methodParams, EcmaMethod method) 131 { 132 if (!methodParams[0].StartsWith(SPECIALTEST_PREFIX)) 133 return method.Handle; 134 135 // Cut off special prefix 136 var specialParams = methodParams[0].Substring(SPECIALTEST_PREFIX.Length); 137 138 // Get friendly name / special name 139 int delimiter = specialParams.IndexOf('.'); 140 if (delimiter < 0) 141 return method.Handle; 142 143 var friendlyName = specialParams.Substring(0, delimiter); 144 var specialName = specialParams.Substring(delimiter + 1); 145 146 // Substitute method parameters with friendly name 147 methodParams[0] = friendlyName; 148 149 var specialMethodHandle = (EcmaMethod)method.OwningType.GetMethod(specialName, method.Signature); 150 return specialMethodHandle == null ? method.Handle : specialMethodHandle.Handle; 151 } 152 GetAllTestDlls()153 private static IEnumerable<string> GetAllTestDlls() 154 { 155 foreach (var item in System.IO.Directory.GetFiles(TESTASSEMBLYPATH)) 156 { 157 if (item.ToLower().EndsWith(".dll")) 158 { 159 yield return System.IO.Path.GetFileName(item); 160 } 161 } 162 } 163 GetModuleForTestAssembly(string assemblyName)164 public static EcmaModule GetModuleForTestAssembly(string assemblyName) 165 { 166 var typeSystemContext = new SimpleTypeSystemContext(); 167 var coreAssembly = typeof(Object).Assembly; 168 var systemRuntime = Assembly.Load("System.Runtime"); 169 170 typeSystemContext.InputFilePaths = new Dictionary<string, string> 171 { 172 { coreAssembly.GetName().Name, coreAssembly.Location }, 173 { systemRuntime.GetName().Name, systemRuntime.Location } 174 }; 175 176 typeSystemContext.ReferenceFilePaths = new Dictionary<string, string>(); 177 foreach (var fileName in GetAllTestDlls()) 178 typeSystemContext.ReferenceFilePaths.Add(Path.GetFileNameWithoutExtension(fileName), TESTASSEMBLYPATH + fileName); 179 180 typeSystemContext.SetSystemModule(typeSystemContext.GetModuleForSimpleName(coreAssembly.GetName().Name)); 181 return typeSystemContext.GetModuleFromPath(TESTASSEMBLYPATH + assemblyName); 182 } 183 } 184 185 abstract class TestCase : IXunitSerializable 186 { 187 public string TestName { get; set; } 188 public string MethodName { get; set; } 189 public int MetadataToken { get; set; } 190 public string ModuleName { get; set; } 191 Deserialize(IXunitSerializationInfo info)192 public virtual void Deserialize(IXunitSerializationInfo info) 193 { 194 TestName = info.GetValue<string>(nameof(TestName)); 195 MethodName = info.GetValue<string>(nameof(MethodName)); 196 MetadataToken = info.GetValue<int>(nameof(MetadataToken)); 197 ModuleName = info.GetValue<string>(nameof(ModuleName)); 198 } 199 Serialize(IXunitSerializationInfo info)200 public virtual void Serialize(IXunitSerializationInfo info) 201 { 202 info.AddValue(nameof(TestName), TestName); 203 info.AddValue(nameof(MethodName), MethodName); 204 info.AddValue(nameof(MetadataToken), MetadataToken); 205 info.AddValue(nameof(ModuleName), ModuleName); 206 } 207 ToString()208 public override string ToString() 209 { 210 return $"[{Path.GetFileNameWithoutExtension(ModuleName)}] {TestName}"; 211 } 212 } 213 214 /// <summary> 215 /// Describes a test case with a method that contains valid IL 216 /// </summary> 217 class ValidILTestCase : TestCase { } 218 219 /// <summary> 220 /// Describes a test case with a method that contains invalid IL with the expected VerifierErrors 221 /// </summary> 222 class InvalidILTestCase : TestCase 223 { 224 public List<VerifierError> ExpectedVerifierErrors { get; set; } 225 Serialize(IXunitSerializationInfo info)226 public override void Serialize(IXunitSerializationInfo info) 227 { 228 base.Serialize(info); 229 var serializedExpectedErrors = JsonConvert.SerializeObject(ExpectedVerifierErrors); 230 info.AddValue(nameof(ExpectedVerifierErrors), serializedExpectedErrors); 231 } 232 Deserialize(IXunitSerializationInfo info)233 public override void Deserialize(IXunitSerializationInfo info) 234 { 235 base.Deserialize(info); 236 var serializedExpectedErrors = info.GetValue<string>(nameof(ExpectedVerifierErrors)); 237 ExpectedVerifierErrors = JsonConvert.DeserializeObject<List<VerifierError>>(serializedExpectedErrors); 238 } 239 ToString()240 public override string ToString() 241 { 242 return base.ToString() + GetErrorsString(ExpectedVerifierErrors); 243 } 244 GetErrorsString(List<VerifierError> errors)245 private static string GetErrorsString(List<VerifierError> errors) 246 { 247 if (errors == null || errors.Count <= 0) 248 return String.Empty; 249 250 var errorsString = new StringBuilder(" ("); 251 252 for (int i = 0; i < errors.Count - 1; ++i) 253 errorsString.Append(errors[i]).Append(", "); 254 255 errorsString.Append(errors[errors.Count - 1]); 256 errorsString.Append(")"); 257 258 return errorsString.ToString(); 259 } 260 } 261 } 262