[netcore] Use codedom for code emission in the xunit generator. Add support for Theor...
[mono-project.git] / netcore / gen-xunit-runner / Program.cs
blob4215f17aa348b86d11d1813b0749e37548fc4c6b
1 //
2 // NOTES:
3 // - If xunit can't laod a trait discoverer assembly, it silently ignores the error.
4 // - the RemoteTestExecutor code used by corefx only seems to work if
5 // the app is executed from the binary dir using dotnet ./<dllname>.
6 // If ran using dotnet run, it seems to invoke itself instead of
7 // RemoteExecutorConsoleApp.exe.
8 // If ran using dotnet bin/.../<dllname>, it fails with:
9 // No executable found matching command "dotnet-<dir>/RemoteExecutorConsoleApp.exe"
12 using System;
13 using System.Runtime.Loader;
14 using System.Linq;
15 using System.Threading;
16 using System.IO;
17 using System.Reflection;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.CodeDom;
21 using System.CodeDom.Compiler;
22 using Microsoft.CSharp;
23 using Xunit;
24 using Xunit.Sdk;
25 using Xunit.Abstractions;
26 using Xunit.ConsoleClient;
28 class MsgSink : IMessageSink {
30 public bool OnMessage(IMessageSinkMessage message) {
31 Console.WriteLine (((Xunit.Sdk.DiagnosticMessage)message).Message);
32 return true;
35 public bool OnMessageWithTypes(IMessageSinkMessage message, HashSet<string> messageTypes) {
36 Console.WriteLine ("m2");
37 return true;
41 class Program
43 static string GetTypeName (Type t) {
44 if (t.IsNested)
45 return t.DeclaringType.FullName + "." + t.Name;
46 else
47 return t.FullName;
50 static void ComputeTraits (Assembly assembly, XunitTestCase tc) {
51 // Traits are not set because of some assembly loading problems i.e. Assembly.Load (new AssemblyName ("Microsoft.DotNet.XUnitExtensions")) fails
52 // So load them manually
53 foreach (ReflectionAttributeInfo attr in ((ReflectionMethodInfo)tc.Method).GetCustomAttributes (typeof (ITraitAttribute))) {
54 var discovererAttr = attr.GetCustomAttributes (typeof (TraitDiscovererAttribute)).FirstOrDefault();
55 if (discovererAttr != null) {
56 var discoverer_args = discovererAttr.GetConstructorArguments().Cast<string>().ToList();
57 var disc_assembly = Assembly.LoadFrom (Path.Combine (Path.GetDirectoryName (assembly.Location), discoverer_args [1]) + ".dll");
58 var disc_type = disc_assembly.GetType (discoverer_args [0]);
59 var disc_obj = (ITraitDiscoverer)Activator.CreateInstance (disc_type);
60 foreach (var trait in disc_obj.GetTraits (attr)) {
61 if (!tc.Traits.ContainsKey (trait.Key))
62 tc.Traits [trait.Key] = new List<string> ();
63 tc.Traits [trait.Key].Add (trait.Value);
69 class CaseData {
70 public object[] Values;
73 static CodeExpression EncodeValue (object val)
75 if (val is int || val is long || val is uint || val is ulong || val is byte || val is sbyte || val is short || val is ushort || val is bool || val is string || val is char || val is float || val is double)
76 return new CodePrimitiveExpression (val);
77 else if (val is Type)
78 return new CodeTypeOfExpression ((Type)val);
79 else if (val is Enum) {
80 TypeCode typeCode = Convert.GetTypeCode (val);
81 object o;
82 if (typeCode == TypeCode.UInt64)
83 o = UInt64.Parse (((Enum)val).ToString ("D"));
84 else
85 o = Int64.Parse (((Enum)val).ToString ("D"));
86 return new CodeCastExpression (new CodeTypeReference (val.GetType ()), new CodePrimitiveExpression (o));
87 } else if (val is null) {
88 return new CodePrimitiveExpression (null);
89 } else if (val is Array) {
90 var arr = (IEnumerable)val;
91 var arr_expr = new CodeArrayCreateExpression (new CodeTypeReference (val.GetType ()), new CodeExpression [] {});
92 foreach (var v in arr)
93 arr_expr.Initializers.Add (EncodeValue (v));
94 return arr_expr;
95 } else {
96 throw new Exception ("Unable to emit inline data: " + val.GetType () + " " + val);
100 static int Main (string[] args)
102 if (args.Length < 3) {
103 Console.WriteLine ("Usage: <outfile> <corefx dir> <test assembly filename> <xunit console options>");
104 return 1;
107 var outfile_name = args [0];
108 var sdkdir = args [1] + "/artifacts/bin/runtime/netcoreapp-OSX-Debug-x64";
109 args = args.Skip (2).ToArray ();
111 // Despite a lot of effort, couldn't get dotnet to load these assemblies from the sdk dir, so copy them to our binary dir
112 File.Copy ($"{sdkdir}/Microsoft.DotNet.PlatformAbstractions.dll", AppContext.BaseDirectory, true);
113 File.Copy ($"{sdkdir}/CoreFx.Private.TestUtilities.dll", AppContext.BaseDirectory, true);
114 File.Copy ($"{sdkdir}/Microsoft.DotNet.XUnitExtensions.dll", AppContext.BaseDirectory, true);
116 var cmdline = CommandLine.Parse (args);
118 // Ditto
119 File.Copy (cmdline.Project.Assemblies.First ().AssemblyFilename, AppContext.BaseDirectory, true);
121 var assembly = Assembly.LoadFrom (Path.Combine (AppContext.BaseDirectory, Path.GetFileName (cmdline.Project.Assemblies.First ().AssemblyFilename)));
123 var msg_sink = new MsgSink ();
124 var xunit2 = new Xunit2Discoverer (AppDomainSupport.Denied, new NullSourceInformationProvider (), new ReflectionAssemblyInfo (assembly), null, null, msg_sink);
125 var sink = new TestDiscoverySink ();
126 var config = new TestAssemblyConfiguration () { DiagnosticMessages = true, InternalDiagnosticMessages = true, PreEnumerateTheories = false };
127 xunit2.Find (false, sink, TestFrameworkOptions.ForDiscovery (config));
128 sink.Finished.WaitOne ();
130 foreach (XunitTestCase tc in sink.TestCases)
131 ComputeTraits (assembly, tc);
133 // Compute testcase data
134 var tc_data = new Dictionary<XunitTestCase, List<CaseData>> ();
135 foreach (XunitTestCase tc in sink.TestCases) {
136 var m = ((ReflectionMethodInfo)tc.Method).MethodInfo;
137 var t = m.ReflectedType;
139 var cases = new List<CaseData> ();
141 if (m.GetParameters ().Length > 0) {
142 foreach (var cattr in m.GetCustomAttributes (true))
143 if (cattr is InlineDataAttribute) {
144 var data = ((InlineDataAttribute)cattr).GetData (null).First ();
145 if (data == null)
146 data = new object [m.GetParameters ().Length];
147 if (data.Length != m.GetParameters ().Length)
148 throw new Exception ();
150 bool unhandled = false;
151 foreach (var val in data) {
152 if (val is float || val is double)
153 unhandled = true;
154 if (val is Type) {
155 var type = val as Type;
156 if (!type.IsVisible)
157 unhandled = true;
159 if (val is Enum) {
160 if (!val.GetType ().IsPublic)
161 unhandled = true;
164 if (!unhandled)
165 cases.Add (new CaseData () { Values = data });
167 } else {
168 cases.Add (new CaseData ());
170 tc_data [tc] = cases;
173 #if FALSE
174 //w.WriteLine ($"\t\ttypeof({typename}).GetMethod (\"{m.Name}\", BindingFlags.Static|BindingFlags.NonPublic).Invoke(null, null);");
175 //w.WriteLine ($"\t\tusing (var o = new {typename} ()) {{");
176 //if (cmdline.StopOnFail)
177 //w.WriteLine ("\t\tif (nfailed > 0) return 1;");
178 //w.WriteLine ("\t\tConsole.WriteLine (\"RUN: \" + nrun + \", FAILED: \" + nfailed);");
179 #endif
181 var cu = new CodeCompileUnit ();
182 var ns = new CodeNamespace ("");
183 cu.Namespaces.Add (ns);
184 ns.Imports.Add (new CodeNamespaceImport ("System"));
185 ns.Imports.Add (new CodeNamespaceImport ("System.Reflection"));
186 var code_class = new CodeTypeDeclaration ("RunTests");
187 ns.Types.Add (code_class);
189 var code_main = new CodeEntryPointMethod ();
190 code_main.ReturnType = new CodeTypeReference ("System.Int32");
191 code_class.Members.Add (code_main);
193 var statements = code_main.Statements;
194 statements.Add (new CodeVariableDeclarationStatement (typeof (int), "nrun", new CodePrimitiveExpression (0)));
195 statements.Add (new CodeVariableDeclarationStatement (typeof (int), "nfailed", new CodePrimitiveExpression (0)));
197 var filters = cmdline.Project.Filters;
198 foreach (XunitTestCase tc in sink.TestCases) {
199 var m = ((ReflectionMethodInfo)tc.Method).MethodInfo;
200 //Console.WriteLine ("" + m.ReflectedType + " " + m + " " + (tc.TestMethodArguments == null));
201 var t = m.ReflectedType;
202 if (t.IsGenericType)
203 continue;
204 if (!filters.Filter (tc))
205 continue;
207 var cases = tc_data [tc];
209 int caseindex = 0;
210 foreach (var test in cases) {
211 string typename = GetTypeName (t);
212 string msg;
213 if (cases.Count > 1)
214 msg = $"{typename}:{m.Name}[{caseindex}]...";
215 else
216 msg = $"{typename}:{m.Name}...";
217 caseindex ++;
218 statements.Add (new CodeMethodInvokeExpression (new CodeTypeReferenceExpression ("Console"), "WriteLine", new CodeExpression [] { new CodePrimitiveExpression (msg) }));
219 statements.Add (new CodeAssignStatement (new CodeVariableReferenceExpression ("nrun"), new CodeBinaryOperatorExpression (new CodeVariableReferenceExpression ("nrun"), CodeBinaryOperatorType.Add, new CodePrimitiveExpression (1))));
220 var try1 = new CodeTryCatchFinallyStatement();
221 statements.Add (try1);
222 if (!m.IsStatic) {
223 // FIXME: Disposable
224 try1.TryStatements.Add (new CodeVariableDeclarationStatement ("var", "o", new CodeObjectCreateExpression (t, new CodeExpression[] {})));
226 if (!m.IsPublic) {
227 // FIXME:
228 } else {
229 CodeMethodInvokeExpression call;
231 if (m.IsStatic)
232 call = new CodeMethodInvokeExpression (new CodeTypeReferenceExpression (t), m.Name, new CodeExpression [] {});
233 else
234 call = new CodeMethodInvokeExpression (new CodeVariableReferenceExpression ("o"), m.Name, new CodeExpression [] {});
236 if (test.Values != null) {
237 foreach (var val in test.Values)
238 call.Parameters.Add (EncodeValue (val));
240 try1.TryStatements.Add (call);
242 var catch1 = new CodeCatchClause ("ex", new CodeTypeReference ("System.Exception"));
243 catch1.Statements.Add (new CodeAssignStatement (new CodeVariableReferenceExpression ("nfailed"), new CodeBinaryOperatorExpression (new CodeVariableReferenceExpression ("nfailed"), CodeBinaryOperatorType.Add, new CodePrimitiveExpression (1))));
244 catch1.Statements.Add (new CodeMethodInvokeExpression (new CodeTypeReferenceExpression ("Console"), "WriteLine", new CodeExpression [] {
245 new CodeBinaryOperatorExpression (
246 new CodePrimitiveExpression ("FAILED: "),
247 CodeBinaryOperatorType.Add,
248 new CodeVariableReferenceExpression ("ex"))
249 }));
250 try1.CatchClauses.Add (catch1);
254 //w.WriteLine ("\t\tConsole.WriteLine (\"RUN: \" + nrun + \", FAILED: \" + nfailed);");
255 statements.Add (new CodeMethodReturnStatement (new CodePrimitiveExpression (0)));
257 var provider = new CSharpCodeProvider ();
258 using (var w2 = File.CreateText (outfile_name)) {
259 provider.GenerateCodeFromCompileUnit (cu, w2, new CodeGeneratorOptions ());
261 return 0;