5 // Marek Safar (marek.safar@gmail.com)
7 // Copyright (C) 2008, 2009 Novell, Inc (http://www.novell.com)
8 // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Diagnostics;
33 using System.Reflection;
35 using System.Collections;
37 using System.Collections.Generic;
38 using Mono.CompilerServices.SymbolWriter;
39 using System.Globalization;
41 namespace TestRunner {
45 string Output { get; }
46 bool Invoke (string[] args);
47 bool IsWarning (int warningNumber);
50 class ReflectionTester: ITester {
56 public ReflectionTester (Assembly a)
58 Type t = a.GetType ("Mono.CSharp.CompilerCallableEntryPoint");
61 Console.Error.WriteLine ("null, huh?");
63 ep = t.GetMethod ("InvokeCompiler",
64 BindingFlags.Static | BindingFlags.Public);
66 throw new MissingMethodException ("static InvokeCompiler");
67 method_arg = new object [2];
69 PropertyInfo pi = t.GetProperty ("AllWarningNumbers");
70 all_warnings = (int[])pi.GetValue (null, null);
71 Array.Sort (all_warnings);
74 public string Output {
76 return output.GetStringBuilder ().ToString ();
80 public bool Invoke(string[] args)
82 output = new StringWriter ();
83 method_arg [0] = args;
84 method_arg [1] = output;
85 return (bool)ep.Invoke (null, method_arg);
88 public bool IsWarning (int warningNumber)
90 return Array.BinarySearch (all_warnings, warningNumber) >= 0;
95 class ProcessTester: ITester
100 public ProcessTester (string p_path)
102 pi = new ProcessStartInfo ();
103 pi.FileName = p_path;
104 pi.CreateNoWindow = true;
105 pi.WindowStyle = ProcessWindowStyle.Hidden;
106 pi.RedirectStandardOutput = true;
107 pi.RedirectStandardError = true;
108 pi.UseShellExecute = false;
111 public string Output {
117 public bool Invoke(string[] args)
119 StringBuilder sb = new StringBuilder ("/nologo ");
120 foreach (string s in args) {
124 pi.Arguments = sb.ToString ();
125 Process p = Process.Start (pi);
126 output = p.StandardError.ReadToEnd ();
127 if (output.Length == 0)
128 output = p.StandardOutput.ReadToEnd ();
130 return p.ExitCode == 0;
133 public bool IsWarning (int warningNumber)
135 throw new NotImplementedException ();
140 class TestCase : MarshalByRefObject
142 public readonly string FileName;
143 public readonly string[] CompilerOptions;
144 public readonly string[] Dependencies;
146 public TestCase (string filename, string[] options, string[] deps)
148 this.FileName = filename;
149 this.CompilerOptions = options;
150 this.Dependencies = deps;
154 class NUnitChecker : PositiveChecker
159 string referenceFile;
160 string executedMethod;
163 public TestCaseEntry (string name, string referenceFile, MethodInfo executedMethod)
165 this.name = name.Replace ('-', '_');
166 this.referenceFile = referenceFile;
167 this.executedMethod = ConvertMethodInfoToText (executedMethod, out has_return);
178 public string ReferenceFile {
180 return referenceFile;
184 static string ConvertMethodInfoToText (MethodInfo mi, out bool hasReturn)
186 hasReturn = mi.ReturnType != typeof (void);
187 string declaring = mi.DeclaringType.FullName.Replace ('+', '.');
188 var param = mi.GetParameters ();
189 if (param.Length == 0)
190 return declaring + "." + mi.Name + " ()";
192 return declaring + "." + mi.Name + " (new string[0])";
195 public string GetTestFixture ()
197 var call = name + "::" + executedMethod;
201 return string.Format ("Assert.AreEqual (0, {0})", call);
205 List<TestCaseEntry> entries = new List<TestCaseEntry> ();
207 public NUnitChecker (ITester tester)
208 : base (tester, null)
212 public override void CleanUp ()
216 StringBuilder aliases = new StringBuilder ();
217 var src_dir = Path.Combine ("projects", "MonoTouch");
218 string src_file = Path.Combine (src_dir, "tests.cs");
220 using (var file = new StreamWriter (src_file, false)) {
221 foreach (var e in entries) {
222 file.WriteLine ("extern alias {0};", e.Name);
223 aliases.AppendFormat (" <Reference Include=\"{0}\">", Path.GetFileNameWithoutExtension (e.ReferenceFile));
224 aliases.Append (Environment.NewLine);
225 aliases.AppendFormat (" <Aliases>{0}</Aliases>", e.Name);
226 aliases.Append (Environment.NewLine);
227 aliases.AppendFormat (" <HintPath>..\\..\\{0}</HintPath>", Path.GetFileName (e.ReferenceFile));
228 aliases.Append (Environment.NewLine);
229 aliases.AppendLine (" </Reference>");
233 file.WriteLine ("using NUnit.Framework;");
235 file.WriteLine ("[TestFixture]");
236 file.WriteLine ("public class Tests {");
238 foreach (var e in entries) {
239 file.WriteLine ("\t[Test]");
240 file.WriteLine ("\tpublic void TestFile_{0} ()", e.Name);
241 file.WriteLine ("\t{");
242 file.WriteLine ("\t\t{0};", e.GetTestFixture ());
243 file.WriteLine ("\t}");
247 file.WriteLine ("}");
250 var input = File.ReadAllText (Path.Combine (src_dir, "MonoTouch.csproj.template"));
251 input = input.Replace ("@GENERATED_REFERENCES", aliases.ToString ());
252 input = input.Replace ("@TEST_SOURCEFILE", Path.GetFileName (src_file));
254 File.WriteAllText (Path.Combine (src_dir, "MonoTouch.csproj"), input);
258 protected override bool ExecuteTestFile (TestCase test, string binaryFileName)
260 Assembly assembly = Assembly.LoadFile (binaryFileName);
261 var ep = assembly.EntryPoint;
263 HandleFailure (test.FileName, TestResult.LoadError, "Entry method is private");
267 if (ep.DeclaringType.IsNestedPrivate || ep.DeclaringType.IsNestedFamily) {
268 HandleFailure (test.FileName, TestResult.LoadError, "Entry method in hidden nested type");
272 entries.Add (new TestCaseEntry (Path.GetFileNameWithoutExtension (test.FileName), binaryFileName, ep));
273 HandleFailure (test.FileName, TestResult.Success, null);
278 class PositiveTestCase : TestCase
280 public class VerificationData : MarshalByRefObject
282 public class MethodData : MarshalByRefObject
284 public MethodData (MethodBase mi, int il_size)
286 this.Type = mi.DeclaringType.ToString ();
287 this.MethodName = mi.ToString ();
288 this.MethodAttributes = (int) mi.Attributes;
289 this.ILSize = il_size;
292 public MethodData (string type_name, string method_name, int method_attributes, int il_size)
294 this.Type = type_name;
295 this.MethodName = method_name;
296 this.MethodAttributes = method_attributes;
297 this.ILSize = il_size;
301 public string MethodName;
304 public int MethodAttributes;
308 public bool IsNewSet;
310 public VerificationData (string test_file)
312 this.test_file = test_file;
317 public static VerificationData FromFile (string name, XmlReader r)
319 VerificationData tc = new VerificationData (name);
320 ArrayList methods = new ArrayList ();
322 while (r.ReadToNextSibling ("type")) {
323 string type_name = r ["name"];
325 while (r.ReadToNextSibling ("method")) {
326 string m_name = r ["name"];
327 int method_attrs = int.Parse (r["attrs"]);
329 r.ReadToDescendant ("size");
330 int il_size = r.ReadElementContentAsInt ();
331 methods.Add (new MethodData (type_name, m_name, method_attrs, il_size));
337 tc.methods = methods;
341 public void WriteCodeInfoTo (XmlWriter w)
343 w.WriteStartElement ("test");
344 w.WriteAttributeString ("name", test_file);
347 foreach (MethodData data in methods) {
351 if (type != data.Type) {
353 w.WriteEndElement ();
356 w.WriteStartElement ("type");
357 w.WriteAttributeString ("name", type);
360 w.WriteStartElement ("method");
361 w.WriteAttributeString ("name", data.MethodName);
362 int v = data.MethodAttributes;
363 w.WriteAttributeString ("attrs", v.ToString ());
364 w.WriteStartElement ("size");
365 w.WriteValue (data.ILSize);
366 w.WriteEndElement ();
367 w.WriteEndElement ();
371 w.WriteEndElement ();
373 w.WriteEndElement ();
376 public MethodData FindMethodData (string method_name, string declaring_type)
381 foreach (MethodData md in methods) {
382 if (md.MethodName == method_name && md.Type == declaring_type)
389 public void AddNewMethod (MethodBase mb, int il_size)
392 methods = new ArrayList ();
394 MethodData md = new MethodData (mb, il_size);
400 VerificationData verif_data;
402 public PositiveTestCase (string filename, string [] options, string [] deps)
403 : base (filename, options, deps)
407 public void CreateNewTest ()
409 verif_data = new VerificationData (FileName);
410 verif_data.IsNewSet = true;
413 public VerificationData VerificationProvider {
423 class Checker: MarshalByRefObject, IDisposable
425 protected ITester tester;
426 protected int success;
428 protected int ignored;
429 protected int syntax_errors;
431 StreamWriter log_file;
432 protected string[] extra_compiler_options;
433 // protected string[] compiler_options;
434 // protected string[] dependencies;
436 protected ArrayList tests = new ArrayList ();
437 protected Hashtable test_hash = new Hashtable ();
438 protected ArrayList regression = new ArrayList ();
439 protected ArrayList know_issues = new ArrayList ();
440 protected ArrayList ignore_list = new ArrayList ();
441 protected ArrayList no_error_list = new ArrayList ();
442 ArrayList skip = new ArrayList ();
444 protected bool verbose;
445 protected bool safe_execution;
447 int total_known_issues;
449 protected Checker (ITester tester)
451 this.tester = tester;
454 public string IssueFile {
456 this.issue_file = value;
457 ReadWrongErrors (issue_file);
461 public string LogFile {
463 this.log_file = new StreamWriter (value, false);
467 public bool Verbose {
473 public bool SafeExecution {
475 safe_execution = value;
479 public string[] ExtraCompilerOptions {
481 extra_compiler_options = value;
485 protected virtual bool GetExtraOptions (string file, out string[] compiler_options,
486 out string[] dependencies)
489 compiler_options = null;
492 using (StreamReader sr = new StreamReader (file)) {
494 while (row++ < 3 && (line = sr.ReadLine()) != null) {
495 if (!AnalyzeTestFile (file, ref row, line, ref compiler_options,
506 protected virtual bool AnalyzeTestFile (string file, ref int row, string line,
507 ref string[] compiler_options,
508 ref string[] dependencies)
510 const string options = "// Compiler options:";
511 const string depends = "// Dependencies:";
514 compiler_options = null;
518 int index = line.IndexOf (options);
520 compiler_options = line.Substring (index + options.Length).Trim().Split (' ');
521 for (int i = 0; i < compiler_options.Length; i++)
522 compiler_options[i] = compiler_options[i].TrimStart ();
524 index = line.IndexOf (depends);
526 dependencies = line.Substring (index + depends.Length).Trim().Split (' ');
527 for (int i = 0; i < dependencies.Length; i++)
528 dependencies[i] = dependencies[i].TrimStart ();
534 public bool Do (string filename)
536 if (test_hash.Contains (filename))
540 Log (filename + "...\t");
542 if (skip.Contains (filename)) {
546 if (ignore_list.Contains (filename)) {
548 LogFileLine (filename, "NOT TESTED");
552 string[] compiler_options, dependencies;
553 if (!GetExtraOptions (filename, out compiler_options, out dependencies)) {
554 LogFileLine (filename, "ERROR");
558 if (extra_compiler_options != null) {
559 if (compiler_options == null)
560 compiler_options = extra_compiler_options;
562 string[] new_options = new string [compiler_options.Length + extra_compiler_options.Length];
563 extra_compiler_options.CopyTo (new_options, 0);
564 compiler_options.CopyTo (new_options, extra_compiler_options.Length);
565 compiler_options = new_options;
569 TestCase test = CreateTestCase (filename, compiler_options, dependencies);
570 test_hash.Add (filename, test);
573 if (dependencies != null) {
574 foreach (string dependency in dependencies) {
575 if (!Do (dependency)) {
576 LogFileLine (filename, "DEPENDENCY FAILED");
587 protected virtual bool Check (TestCase test)
591 if (test.CompilerOptions != null) {
592 test_args = new string[1 + test.CompilerOptions.Length];
593 test.CompilerOptions.CopyTo (test_args, 0);
595 test_args = new string[1];
597 test_args[test_args.Length - 1] = test_args[0];
598 test_args[0] = test.FileName;
600 return tester.Invoke (test_args);
603 protected virtual TestCase CreateTestCase (string filename, string [] options, string [] deps)
605 return new TestCase (filename, options, deps);
608 void ReadWrongErrors (string file)
610 const string ignored = "IGNORE";
611 const string no_error = "NO ERROR";
612 const string skip_tag = "SKIP";
614 using (StreamReader sr = new StreamReader (file)) {
616 while ((line = sr.ReadLine()) != null) {
617 if (line.StartsWith ("#"))
620 ArrayList active_cont = know_issues;
622 if (line.IndexOf (ignored) > 0)
623 active_cont = ignore_list;
624 else if (line.IndexOf (no_error) > 0)
625 active_cont = no_error_list;
626 else if (line.Contains (skip_tag))
629 string file_name = line.Split (' ')[0];
630 if (file_name.Length == 0)
633 active_cont.Add (file_name);
636 total_known_issues = know_issues.Count;
639 protected virtual void PrintSummary ()
641 LogLine ("Done" + Environment.NewLine);
644 rate = (float) (success) / (float)total;
645 LogLine ("{0} test cases passed ({1:0.##%})", success, rate);
647 if (syntax_errors > 0)
648 LogLine ("{0} test(s) ignored because of wrong syntax !", syntax_errors);
651 LogLine ("{0} test(s) ignored", ignored);
653 if (total_known_issues - know_issues.Count > 0)
654 LogLine ("{0} known issue(s)", total_known_issues - know_issues.Count);
656 know_issues.AddRange (no_error_list);
657 if (know_issues.Count > 0) {
659 LogLine (issue_file + " contains {0} already fixed issues. Please remove", know_issues.Count);
660 foreach (string s in know_issues)
663 if (regression.Count > 0) {
665 LogLine ("The latest changes caused regression in {0} file(s)", regression.Count);
666 foreach (string s in regression)
671 public int ResultCode
674 return regression.Count == 0 ? 0 : 1;
678 protected void Log (string msg, params object [] rest)
680 Console.Write (msg, rest);
681 if (log_file != null)
682 log_file.Write (msg, rest);
685 protected void LogLine (string msg)
687 Console.WriteLine (msg);
688 if (log_file != null)
689 log_file.WriteLine (msg);
692 protected void LogLine (string msg, params object [] rest)
694 Console.WriteLine (msg, rest);
695 if (log_file != null)
696 log_file.WriteLine (msg, rest);
699 public void LogFileLine (string file, string msg)
701 string s = verbose ? msg : file + "...\t" + msg;
703 Console.WriteLine (s);
704 if (log_file != null)
705 log_file.WriteLine (s);
708 #region IDisposable Members
710 public void Dispose()
712 if (log_file != null)
718 public virtual void Initialize ()
722 public virtual void CleanUp ()
728 class PositiveChecker: Checker
730 readonly string files_folder;
731 readonly static object[] default_args = new object[1] { new string[] {} };
734 bool update_verif_file;
735 Hashtable verif_data;
740 readonly string mono;
742 public enum TestResult {
750 MethodAttributesError
753 public PositiveChecker (ITester tester, string verif_file):
756 files_folder = Directory.GetCurrentDirectory ();
757 this.verif_file = verif_file;
760 pi = new ProcessStartInfo ();
761 pi.CreateNoWindow = true;
762 pi.WindowStyle = ProcessWindowStyle.Hidden;
763 pi.RedirectStandardOutput = true;
764 pi.RedirectStandardError = true;
765 pi.UseShellExecute = false;
767 mono = Environment.GetEnvironmentVariable ("MONO_RUNTIME");
774 public bool UpdateVerificationDataFile {
776 update_verif_file = value;
779 return update_verif_file;
783 protected override bool GetExtraOptions(string file, out string[] compiler_options,
784 out string[] dependencies) {
785 if (!base.GetExtraOptions (file, out compiler_options, out dependencies))
789 if (compiler_options == null)
792 foreach (string one_opt in compiler_options) {
793 if (one_opt.StartsWith ("-doc:")) {
794 doc_output = one_opt.Split (':', '/')[1];
800 class DomainTester : MarshalByRefObject
802 public bool CheckILSize (PositiveTestCase test, PositiveChecker checker, string file)
804 Assembly assembly = Assembly.LoadFile (file);
807 Type[] types = assembly.GetTypes ();
808 foreach (Type t in types) {
811 if (!t.IsClass && !t.IsValueType)
814 if (test.VerificationProvider == null) {
815 if (!checker.UpdateVerificationDataFile)
816 checker.LogFileLine (test.FileName, "Missing IL verification data");
817 test.CreateNewTest ();
820 foreach (MemberInfo m in t.GetMembers (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
821 MethodBase mi = m as MethodBase;
825 if ((mi.Attributes & (MethodAttributes.PinvokeImpl)) != 0)
828 success &= CompareIL (mi, test, checker);
835 bool CompareIL (MethodBase mi, PositiveTestCase test, PositiveChecker checker)
837 string m_name = mi.ToString ();
838 string decl_type = mi.DeclaringType.ToString ();
839 PositiveTestCase.VerificationData data_provider = test.VerificationProvider;
841 PositiveTestCase.VerificationData.MethodData md = data_provider.FindMethodData (m_name, decl_type);
843 data_provider.AddNewMethod (mi, GetILSize (mi));
844 if (!data_provider.IsNewSet) {
845 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError, decl_type + ": " + m_name + " (new method?)");
853 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError, decl_type + ": " + m_name + " has a duplicate");
859 if (md.MethodAttributes != (int) mi.Attributes) {
860 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.MethodAttributesError,
861 string.Format ("{0} ({1} -> {2})", decl_type + ": " + m_name, (MethodAttributes) md.MethodAttributes, mi.Attributes));
864 md.MethodAttributes = (int) mi.Attributes;
866 int il_size = GetILSize (mi);
867 if (md.ILSize == il_size)
870 if (md.ILSize > il_size) {
871 checker.LogFileLine (test.FileName, string.Format ("{0} (code size reduction {1} -> {2})", decl_type + ": " + m_name, md.ILSize, il_size));
876 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError,
877 string.Format ("{0} (code size {1} -> {2})", decl_type + ": " + m_name, md.ILSize, il_size));
884 static int GetILSize (MethodBase mi)
886 MethodBody body = mi.GetMethodBody ();
888 return body.GetILAsByteArray ().Length;
893 bool ExecuteFile (MethodInfo entry_point, string filename)
895 TextWriter stdout = Console.Out;
896 TextWriter stderr = Console.Error;
897 Console.SetOut (TextWriter.Null);
898 Console.SetError (TextWriter.Null);
899 ParameterInfo[] pi = entry_point.GetParameters ();
900 object[] args = pi.Length == 0 ? null : default_args;
902 object result = null;
905 result = entry_point.Invoke (null, args);
907 Console.SetOut (stdout);
908 Console.SetError (stderr);
910 } catch (Exception e) {
911 throw new ApplicationException (e.ToString ());
914 if (result is int && (int) result != 0)
915 throw new ApplicationException ("Wrong return code: " + result.ToString ());
920 public bool Test (string file)
922 Assembly assembly = Assembly.LoadFile (file);
923 return ExecuteFile (assembly.EntryPoint, file);
927 protected override bool Check(TestCase test)
929 string filename = test.FileName;
931 if (!base.Check (test)) {
932 HandleFailure (filename, TestResult.CompileError, tester.Output);
936 catch (Exception e) {
937 if (e.InnerException != null)
938 e = e.InnerException;
940 HandleFailure (filename, TestResult.CompileError, e.ToString ());
945 if (filename.EndsWith ("-lib.cs") || filename.EndsWith ("-mod.cs")) {
947 LogFileLine (filename, "OK");
952 string file = Path.Combine (files_folder, Path.GetFileNameWithoutExtension (filename) + ".exe");
954 // Enable .dll only tests (no execution required)
955 if (!File.Exists(file)) {
956 HandleFailure (filename, TestResult.Success, null);
960 return ExecuteTestFile (test, file);
963 protected virtual bool ExecuteTestFile (TestCase test, string binaryFileName)
965 string filename = test.FileName;
967 AppDomain domain = null;
969 if (safe_execution) {
970 // Create a new AppDomain, with the current directory as the base.
971 AppDomainSetup setupInfo = new AppDomainSetup ();
972 setupInfo.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
973 setupInfo.LoaderOptimization = LoaderOptimization.SingleDomain;
974 domain = AppDomain.CreateDomain (Path.GetFileNameWithoutExtension (binaryFileName), null, setupInfo);
982 tester = (DomainTester) domain.CreateInstanceAndUnwrap (typeof (PositiveChecker).Assembly.FullName, typeof (DomainTester).FullName);
985 tester = new DomainTester ();
987 if (!tester.Test (binaryFileName))
990 } catch (ApplicationException e) {
991 HandleFailure (filename, TestResult.ExecError, e.Message);
993 } catch (Exception e) {
994 HandleFailure (filename, TestResult.LoadError, e.ToString ());
998 if (doc_output != null) {
999 string ref_file = filename.Replace (".cs", "-ref.xml");
1002 new XmlComparer ("doc").Compare (ref_file, doc_output);
1004 } catch (Exception e) {
1005 HandleFailure (filename, TestResult.XmlError, e.Message);
1009 if (verif_file != null) {
1010 PositiveTestCase pt = (PositiveTestCase) test;
1011 pt.VerificationProvider = (PositiveTestCase.VerificationData) verif_data[filename];
1013 if (!tester.CheckILSize (pt, this, binaryFileName))
1017 if (filename.StartsWith ("test-debug", StringComparison.OrdinalIgnoreCase)) {
1018 var mdb_file_name = binaryFileName + ".mdb";
1019 MonoSymbolFile mdb_file = MonoSymbolFile.ReadSymbolFile (mdb_file_name);
1020 var mdb_xml_file = mdb_file_name + ".xml";
1021 ConvertSymbolFileToXml (mdb_file, mdb_xml_file);
1023 var ref_file = filename.Replace(".cs", "-ref.xml");
1025 new XmlComparer ("symbols").Compare (ref_file, mdb_xml_file);
1026 } catch (Exception e) {
1027 HandleFailure (filename, TestResult.DebugError, e.Message);
1035 AppDomain.Unload (domain);
1038 HandleFailure (filename, TestResult.Success, null);
1042 static void ConvertSymbolFileToXml (MonoSymbolFile symbolFile, string xmlFile)
1044 using (XmlTextWriter writer = new XmlTextWriter (xmlFile, Encoding.UTF8)) {
1045 writer.Formatting = Formatting.Indented;
1047 writer.WriteStartDocument ();
1049 writer.WriteStartElement ("symbols");
1051 writer.WriteStartElement ("files");
1052 foreach (var file in symbolFile.Sources) {
1053 writer.WriteStartElement ("file");
1054 writer.WriteAttributeString ("id", file.Index.ToString ());
1055 writer.WriteAttributeString ("name", Path.GetFileName (file.FileName));
1056 var checksum = file.Checksum;
1057 if (checksum != null)
1058 writer.WriteAttributeString ("checksum", ChecksumToString (checksum));
1060 writer.WriteEndElement ();
1062 writer.WriteEndElement ();
1064 writer.WriteStartElement ("methods");
1065 foreach (var method in symbolFile.Methods) {
1066 writer.WriteStartElement ("method");
1067 writer.WriteAttributeString ("token", IntToHex (method.Token));
1069 var il_entries = method.GetLineNumberTable ();
1070 writer.WriteStartElement ("sequencepoints");
1071 foreach (var entry in il_entries.LineNumbers) {
1072 writer.WriteStartElement ("entry");
1073 writer.WriteAttributeString ("il", IntToHex (entry.Offset));
1074 writer.WriteAttributeString ("row", entry.Row.ToString ());
1075 writer.WriteAttributeString ("col", entry.Column.ToString ());
1076 writer.WriteAttributeString ("file_ref", entry.File.ToString ());
1077 writer.WriteAttributeString ("hidden", BoolToString (entry.IsHidden));
1078 writer.WriteEndElement ();
1080 writer.WriteEndElement ();
1082 writer.WriteStartElement ("locals");
1083 foreach (var local in method.GetLocals ()) {
1084 writer.WriteStartElement ("entry");
1085 writer.WriteAttributeString ("name", local.Name);
1086 writer.WriteAttributeString ("il_index", local.Index.ToString ());
1087 writer.WriteAttributeString ("scope_ref", local.BlockIndex.ToString ());
1088 writer.WriteEndElement ();
1090 writer.WriteEndElement ();
1092 writer.WriteStartElement ("scopes");
1093 foreach (var scope in method.GetCodeBlocks ()) {
1094 writer.WriteStartElement ("entry");
1095 writer.WriteAttributeString ("index", scope.Index.ToString ());
1096 writer.WriteAttributeString ("start", IntToHex (scope.StartOffset));
1097 writer.WriteAttributeString ("end", IntToHex (scope.EndOffset));
1098 writer.WriteEndElement ();
1100 writer.WriteEndElement ();
1102 writer.WriteEndElement ();
1104 writer.WriteEndElement ();
1106 writer.WriteEndElement ();
1107 writer.WriteEndDocument ();
1111 static string ChecksumToString (byte[] checksum)
1113 var sb = new StringBuilder (checksum.Length * 2);
1114 for (int i = 0; i < checksum.Length; i++) {
1115 sb.Append ("0123456789abcdef"[checksum[i] >> 4]);
1116 sb.Append ("0123456789abcdef"[checksum[i] & 0x0F]);
1119 return sb.ToString ();
1122 static string IntToHex (int value)
1124 return "0x" + value.ToString ("x", CultureInfo.InvariantCulture);
1127 static string BoolToString (bool value)
1129 return value ? "true" : "false";
1132 protected override TestCase CreateTestCase (string filename, string [] options, string [] deps)
1134 return new PositiveTestCase (filename, options, deps);
1137 public void HandleFailure (string file, TestResult status, string extra)
1140 case TestResult.Success:
1142 if (know_issues.Contains (file)) {
1143 LogFileLine (file, "FIXED ISSUE");
1147 LogFileLine (file, "OK");
1150 case TestResult.CompileError:
1151 if (know_issues.Contains (file)) {
1152 LogFileLine (file, "KNOWN ISSUE (Compilation error)");
1153 know_issues.Remove (file);
1156 LogFileLine (file, "REGRESSION (SUCCESS -> COMPILATION ERROR)");
1159 case TestResult.ExecError:
1160 if (know_issues.Contains (file)) {
1161 LogFileLine (file, "KNOWN ISSUE (Execution error)");
1162 know_issues.Remove (file);
1165 LogFileLine (file, "REGRESSION (SUCCESS -> EXECUTION ERROR)");
1168 case TestResult.XmlError:
1169 if (know_issues.Contains (file)) {
1170 LogFileLine (file, "KNOWN ISSUE (Xml comparision error)");
1171 know_issues.Remove (file);
1174 LogFileLine (file, "REGRESSION (SUCCESS -> DOCUMENTATION ERROR)");
1177 case TestResult.LoadError:
1179 extra = ": " + extra;
1181 LogFileLine (file, "REGRESSION (SUCCESS -> LOAD ERROR)" + extra);
1185 case TestResult.MethodAttributesError:
1186 case TestResult.ILError:
1187 if (!update_verif_file) {
1188 LogFileLine (file, "IL REGRESSION: " + extra);
1193 case TestResult.DebugError:
1194 LogFileLine (file, "REGRESSION (SUCCESS -> SYMBOL FILE ERROR)");
1199 LogLine ("{0}", extra);
1201 if (!regression.Contains (file))
1202 regression.Add (file);
1205 public override void Initialize ()
1207 if (verif_file != null) {
1208 LoadVerificationData (verif_file);
1214 public override void CleanUp ()
1218 if (update_verif_file) {
1219 UpdateVerificationData (verif_file);
1223 void LoadVerificationData (string file)
1225 verif_data = new Hashtable ();
1227 if (!File.Exists (file)) {
1228 LogLine ("Writing verification data to `{0}' ...", file);
1232 LogLine ("Loading verification data from `{0}' ...", file);
1234 using (XmlReader r = XmlReader.Create (file)) {
1235 r.ReadStartElement ("tests");
1238 if (r.Name != "test")
1241 string name = r.GetAttribute ("name");
1242 PositiveTestCase.VerificationData tc = PositiveTestCase.VerificationData.FromFile (name, r);
1243 verif_data.Add (name, tc);
1248 void UpdateVerificationData (string file)
1250 LogLine ("Updating verification data `{0}' ...", file);
1252 XmlWriterSettings s = new XmlWriterSettings ();
1254 using (XmlWriter w = XmlWriter.Create (new StreamWriter (file, false, Encoding.UTF8), s)) {
1255 w.WriteStartDocument ();
1256 w.WriteComment ("This file contains expected IL and metadata produced by compiler for each test");
1257 w.WriteStartElement ("tests");
1258 foreach (PositiveTestCase tc in tests) {
1259 if (tc.VerificationProvider != null)
1260 tc.VerificationProvider.WriteCodeInfoTo (w);
1262 w.WriteEndElement ();
1267 class NegativeChecker: Checker
1269 string expected_message;
1270 string error_message;
1272 bool check_error_line;
1274 IDictionary wrong_warning;
1276 protected enum CompilerError {
1285 public NegativeChecker (ITester tester, bool check_msg):
1288 this.check_msg = check_msg;
1289 wrong_warning = new Hashtable ();
1292 protected override bool AnalyzeTestFile (string file, ref int row, string line,
1293 ref string[] compiler_options,
1294 ref string[] dependencies)
1297 expected_message = null;
1299 int index = line.IndexOf (':');
1300 if (index == -1 || index > 15) {
1301 LogFileLine (file, "IGNORING: Wrong test file syntax (missing error mesage text)");
1303 base.AnalyzeTestFile (file, ref row, line, ref compiler_options,
1308 expected_message = line.Substring (index + 1).Trim ();
1312 string filtered = line.Replace(" ", "");
1314 // Some error tests require to have different error text for different runtimes.
1315 if (filtered.StartsWith ("//GMCS")) {
1317 return AnalyzeTestFile(file, ref row, line, ref compiler_options, ref dependencies);
1320 check_error_line = !filtered.StartsWith ("//Line:0");
1322 if (!filtered.StartsWith ("//Line:")) {
1323 LogFileLine (file, "IGNORING: Wrong test syntax (following line after an error messsage must have `// Line: xx' syntax");
1329 if (!base.AnalyzeTestFile (file, ref row, line, ref compiler_options, ref dependencies))
1333 if (compiler_options != null) {
1334 foreach (string s in compiler_options) {
1335 if (s.StartsWith ("-warnaserror") || s.StartsWith ("/warnaserror"))
1343 protected override bool Check (TestCase test)
1345 string filename = test.FileName;
1348 while (Char.IsLetter (filename, start_char))
1351 int end_char = filename.IndexOfAny (new char [] { '-', '.' } );
1352 string expected = filename.Substring (start_char, end_char - start_char);
1355 if (base.Check (test)) {
1356 HandleFailure (filename, CompilerError.Missing);
1360 catch (Exception e) {
1361 HandleFailure (filename, CompilerError.Missing);
1362 if (e.InnerException != null)
1363 e = e.InnerException;
1365 Log (e.ToString ());
1369 int err_id = int.Parse (expected, System.Globalization.CultureInfo.InvariantCulture);
1370 if (tester.IsWarning (err_id)) {
1372 wrong_warning [err_id] = true;
1375 wrong_warning [err_id] = false;
1378 CompilerError result_code = GetCompilerError (expected, tester.Output);
1379 if (HandleFailure (filename, result_code)) {
1384 if (result_code == CompilerError.Wrong)
1385 LogLine (tester.Output);
1390 CompilerError GetCompilerError (string expected, string buffer)
1392 const string error_prefix = "CS";
1393 const string ignored_error = "error CS5001";
1394 string tested_text = "error " + error_prefix + expected;
1395 StringReader sr = new StringReader (buffer);
1396 string line = sr.ReadLine ();
1397 ArrayList ld = new ArrayList ();
1398 CompilerError result = CompilerError.Missing;
1399 while (line != null) {
1400 if (ld.Contains (line) && result == CompilerError.Expected) {
1401 if (line.IndexOf ("Location of the symbol related to previous") == -1)
1402 return CompilerError.Duplicate;
1406 if (result != CompilerError.Expected) {
1407 if (line.IndexOf (tested_text) != -1) {
1409 int first = line.IndexOf (':');
1410 int second = line.IndexOf (':', first + 1);
1411 if (line.IndexOf ("Warning as Error: ", first, StringComparison.Ordinal) > 0) {
1412 if (check_error_line) {
1413 second = line.IndexOf (':', second + 1);
1415 } else if (second == -1 || !check_error_line) {
1419 string msg = line.Substring (second + 1).TrimEnd ('.').Trim ();
1420 if (msg != expected_message && !TryToMatchErrorMessage (msg, expected_message)) {
1421 error_message = msg;
1422 return CompilerError.WrongMessage;
1425 if (check_error_line && line.IndexOf (".cs(") == -1)
1426 return CompilerError.MissingLocation;
1428 result = CompilerError.Expected;
1429 } else if (line.IndexOf (error_prefix) != -1 &&
1430 line.IndexOf (ignored_error) == -1)
1431 result = CompilerError.Wrong;
1434 line = sr.ReadLine ();
1440 static bool TryToMatchErrorMessage (string actual, string expected)
1442 actual = actual.Replace ("\\", "/");
1443 var path_mask_start = expected.IndexOf ("*PATH*");
1444 if (path_mask_start > 0 && actual.Length > path_mask_start) {
1445 var path_mask_continue = expected.Substring (path_mask_start + 6);
1446 var expected_continue = actual.IndexOf (path_mask_continue, path_mask_start);
1447 if (expected_continue > 0) {
1448 var path = actual.Substring (path_mask_start, expected_continue - path_mask_start);
1449 if (actual == expected.Replace ("*PATH*", path))
1452 throw new ApplicationException (expected.Replace ("*PATH*", path));
1459 bool HandleFailure (string file, CompilerError status)
1462 case CompilerError.Expected:
1463 if (know_issues.Contains (file) || no_error_list.Contains (file)) {
1464 LogFileLine (file, "FIXED ISSUE");
1469 LogFileLine (file, "OK");
1472 case CompilerError.Wrong:
1473 if (know_issues.Contains (file)) {
1474 LogFileLine (file, "KNOWN ISSUE (Wrong error reported)");
1475 know_issues.Remove (file);
1478 if (no_error_list.Contains (file)) {
1479 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR CODE)");
1480 no_error_list.Remove (file);
1483 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR CODE)");
1487 case CompilerError.WrongMessage:
1488 if (know_issues.Contains (file)) {
1489 LogFileLine (file, "KNOWN ISSUE (Wrong error message reported)");
1490 know_issues.Remove (file);
1493 if (no_error_list.Contains (file)) {
1494 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR MESSAGE)");
1495 no_error_list.Remove (file);
1498 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR MESSAGE)");
1499 LogLine ("Exp: {0}", expected_message);
1500 LogLine ("Was: {0}", error_message);
1504 case CompilerError.Missing:
1505 if (no_error_list.Contains (file)) {
1506 LogFileLine (file, "KNOWN ISSUE (No error reported)");
1507 no_error_list.Remove (file);
1511 if (know_issues.Contains (file)) {
1512 LogFileLine (file, "REGRESSION (WRONG ERROR -> NO ERROR)");
1513 know_issues.Remove (file);
1516 LogFileLine (file, "REGRESSION (CORRECT ERROR -> NO ERROR)");
1521 case CompilerError.MissingLocation:
1522 if (know_issues.Contains (file)) {
1523 LogFileLine (file, "KNOWN ISSUE (Missing error location)");
1524 know_issues.Remove (file);
1527 if (no_error_list.Contains (file)) {
1528 LogFileLine (file, "REGRESSION (NO ERROR -> MISSING ERROR LOCATION)");
1529 no_error_list.Remove (file);
1532 LogFileLine (file, "REGRESSION (CORRECT ERROR -> MISSING ERROR LOCATION)");
1536 case CompilerError.Duplicate:
1537 // Will become an error soon
1538 LogFileLine (file, "WARNING: EXACTLY SAME ERROR HAS BEEN ISSUED MULTIPLE TIMES");
1542 regression.Add (file);
1546 protected override void PrintSummary()
1548 base.PrintSummary ();
1550 if (wrong_warning.Count > 0) {
1552 LogLine ("List of incorectly defined warnings (they should be either defined in the compiler as a warning or a test-case has redundant `warnaserror' option)");
1554 foreach (DictionaryEntry de in wrong_warning)
1555 LogLine ("CS{0:0000} : {1}", de.Key, (bool)de.Value ? "incorrect warning definition" : "missing warning definition");
1563 static int Main(string[] args)
1567 if (GetOption ("help", args, false, out temp)) {
1573 if (!GetOption ("compiler", args, true, out compiler)) {
1580 Console.WriteLine ("Loading " + compiler + " ...");
1581 tester = new ReflectionTester (Assembly.LoadFile (compiler));
1582 } catch (Exception) {
1583 Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
1584 if (!File.Exists (compiler)) {
1585 Console.Error.WriteLine ("ERROR: Tested compiler was not found");
1588 tester = new ProcessTester (compiler);
1592 if (!GetOption ("mode", args, true, out mode)) {
1601 checker = new NegativeChecker (tester, true);
1606 GetOption ("il", args, false, out iltest);
1607 checker = new PositiveChecker (tester, iltest);
1610 if (iltest != null && GetOption ("update-il", args, false, out temp)) {
1611 ((PositiveChecker) checker).UpdateVerificationDataFile = true;
1617 checker = new NUnitChecker (tester);
1620 Console.Error.WriteLine ("Invalid -mode argument");
1625 if (GetOption ("issues", args, true, out temp))
1626 checker.IssueFile = temp;
1627 if (GetOption ("log", args, true, out temp))
1628 checker.LogFile = temp;
1629 if (GetOption ("verbose", args, false, out temp))
1630 checker.Verbose = true;
1631 if (GetOption ("safe-execution", args, false, out temp))
1632 checker.SafeExecution = true;
1633 if (GetOption ("compiler-options", args, true, out temp)) {
1634 string[] extra = temp.Split (' ');
1635 checker.ExtraCompilerOptions = extra;
1638 string test_pattern;
1639 if (!GetOption ("files", args, true, out test_pattern)) {
1644 var files = new List<string> ();
1645 switch (test_pattern) {
1647 files.AddRange (Directory.GetFiles (".", positive ? "test*.cs" : "cs*.cs"));
1650 files.AddRange (Directory.GetFiles (".", positive ? "gtest*.cs" : "gcs*.cs"));
1653 files.AddRange (Directory.GetFiles (".", positive ? "dtest*.cs" : "dcs*.cs"));
1656 files.AddRange (Directory.GetFiles (".", test_pattern));
1660 if (files.Count == 0) {
1661 Console.Error.WriteLine ("No files matching `{0}' found", test_pattern);
1665 checker.Initialize ();
1667 files.Sort ((a, b) => {
1668 if (a.EndsWith ("-lib.cs", StringComparison.Ordinal)) {
1669 if (!b.EndsWith ("-lib.cs", StringComparison.Ordinal))
1671 } else if (b.EndsWith ("-lib.cs", StringComparison.Ordinal)) {
1672 if (!a.EndsWith ("-lib.cs", StringComparison.Ordinal))
1676 return a.CompareTo (b);
1679 foreach (string s in files) {
1680 string filename = Path.GetFileName (s);
1681 if (Char.IsUpper (filename, 0)) { // Windows hack
1685 if (filename.EndsWith ("-p2.cs"))
1688 checker.Do (filename);
1695 return checker.ResultCode;
1698 static bool GetOption (string opt, string[] args, bool req_arg, out string value)
1701 foreach (string a in args) {
1702 if (a.StartsWith (opt)) {
1703 int sep = a.IndexOf (':');
1705 value = a.Substring (sep + 1);
1709 Console.Error.WriteLine ("Missing argument in option " + opt);
1722 static void Usage ()
1725 "Mono compiler tester, (C) 2009 Novell, Inc.\n" +
1726 "compiler-tester -mode:[pos|neg] -compiler:FILE -files:file-list [options]\n" +
1728 " -compiler:FILE The file which will be used to compiler tests\n" +
1729 " -compiler-options:OPTIONS Add global compiler options\n" +
1730 " -il:IL-FILE XML file with expected IL details for each test\n" +
1731 " -issues:FILE The list of expected failures\n" +
1732 " -log:FILE Writes any output also to the file\n" +
1733 " -help Lists all options\n" +
1734 " -mode:[pos|neg] Specifies compiler test mode\n" +
1735 " -safe-execution Runs compiled executables in separate app-domain\n" +
1736 " -update-il Updates IL-FILE to match compiler output\n" +
1737 " -verbose Prints more details during testing\n"