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