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 PositiveTestCase : TestCase
156 public class VerificationData : MarshalByRefObject
158 public class MethodData : MarshalByRefObject
160 public MethodData (MethodBase mi, int il_size)
162 this.Type = mi.DeclaringType.ToString ();
163 this.MethodName = mi.ToString ();
164 this.ILSize = il_size;
167 public MethodData (string type_name, string method_name, int il_size)
169 this.Type = type_name;
170 this.MethodName = method_name;
171 this.ILSize = il_size;
175 public string MethodName;
181 public bool IsNewSet;
183 public VerificationData (string test_file)
185 this.test_file = test_file;
190 public static VerificationData FromFile (string name, XmlReader r)
192 VerificationData tc = new VerificationData (name);
193 ArrayList methods = new ArrayList ();
195 while (r.ReadToNextSibling ("type")) {
196 string type_name = r ["name"];
198 while (r.ReadToNextSibling ("method")) {
199 string m_name = r ["name"];
201 r.ReadToDescendant ("size");
202 int il_size = r.ReadElementContentAsInt ();
203 methods.Add (new MethodData (type_name, m_name, il_size));
209 tc.methods = methods;
213 public void WriteCodeInfoTo (XmlWriter w)
215 w.WriteStartElement ("test");
216 w.WriteAttributeString ("name", test_file);
219 foreach (MethodData data in methods) {
223 if (type != data.Type) {
225 w.WriteEndElement ();
228 w.WriteStartElement ("type");
229 w.WriteAttributeString ("name", type);
232 w.WriteStartElement ("method");
233 w.WriteAttributeString ("name", data.MethodName);
234 w.WriteStartElement ("size");
235 w.WriteValue (data.ILSize);
236 w.WriteEndElement ();
237 w.WriteEndElement ();
241 w.WriteEndElement ();
243 w.WriteEndElement ();
246 public MethodData FindMethodData (string method_name, string declaring_type)
251 foreach (MethodData md in methods) {
252 if (md.MethodName == method_name && md.Type == declaring_type)
259 public void AddNewMethod (MethodBase mb, int il_size)
262 methods = new ArrayList ();
264 MethodData md = new MethodData (mb, il_size);
270 VerificationData verif_data;
272 public PositiveTestCase (string filename, string [] options, string [] deps)
273 : base (filename, options, deps)
277 public void CreateNewTest ()
279 verif_data = new VerificationData (FileName);
280 verif_data.IsNewSet = true;
283 public VerificationData VerificationProvider {
293 class Checker: MarshalByRefObject, IDisposable
295 protected ITester tester;
296 protected int success;
298 protected int ignored;
299 protected int syntax_errors;
301 StreamWriter log_file;
302 protected string[] extra_compiler_options;
303 // protected string[] compiler_options;
304 // protected string[] dependencies;
306 protected ArrayList tests = new ArrayList ();
307 protected Hashtable test_hash = new Hashtable ();
308 protected ArrayList regression = new ArrayList ();
309 protected ArrayList know_issues = new ArrayList ();
310 protected ArrayList ignore_list = new ArrayList ();
311 protected ArrayList no_error_list = new ArrayList ();
313 protected bool verbose;
314 protected bool safe_execution;
316 int total_known_issues;
318 protected Checker (ITester tester)
320 this.tester = tester;
323 public string IssueFile {
325 this.issue_file = value;
326 ReadWrongErrors (issue_file);
330 public string LogFile {
332 this.log_file = new StreamWriter (value, false);
336 public bool Verbose {
342 public bool SafeExecution {
344 safe_execution = value;
348 public string[] ExtraCompilerOptions {
350 extra_compiler_options = value;
354 protected virtual bool GetExtraOptions (string file, out string[] compiler_options,
355 out string[] dependencies)
358 compiler_options = null;
361 using (StreamReader sr = new StreamReader (file)) {
363 while (row++ < 3 && (line = sr.ReadLine()) != null) {
364 if (!AnalyzeTestFile (file, ref row, line, ref compiler_options,
375 protected virtual bool AnalyzeTestFile (string file, ref int row, string line,
376 ref string[] compiler_options,
377 ref string[] dependencies)
379 const string options = "// Compiler options:";
380 const string depends = "// Dependencies:";
383 compiler_options = null;
387 int index = line.IndexOf (options);
389 compiler_options = line.Substring (index + options.Length).Trim().Split (' ');
390 for (int i = 0; i < compiler_options.Length; i++)
391 compiler_options[i] = compiler_options[i].TrimStart ();
393 index = line.IndexOf (depends);
395 dependencies = line.Substring (index + depends.Length).Trim().Split (' ');
396 for (int i = 0; i < dependencies.Length; i++)
397 dependencies[i] = dependencies[i].TrimStart ();
403 public bool Do (string filename)
405 if (test_hash.Contains (filename))
409 Log (filename + "...\t");
411 if (ignore_list.Contains (filename)) {
413 LogFileLine (filename, "NOT TESTED");
417 string[] compiler_options, dependencies;
418 if (!GetExtraOptions (filename, out compiler_options, out dependencies)) {
419 LogFileLine (filename, "ERROR");
423 if (extra_compiler_options != null) {
424 if (compiler_options == null)
425 compiler_options = extra_compiler_options;
427 string[] new_options = new string [compiler_options.Length + extra_compiler_options.Length];
428 extra_compiler_options.CopyTo (new_options, 0);
429 compiler_options.CopyTo (new_options, extra_compiler_options.Length);
430 compiler_options = new_options;
434 TestCase test = CreateTestCase (filename, compiler_options, dependencies);
435 test_hash.Add (filename, test);
438 if (dependencies != null) {
439 foreach (string dependency in dependencies) {
440 if (!Do (dependency)) {
441 LogFileLine (filename, "DEPENDENCY FAILED");
452 protected virtual bool Check (TestCase test)
456 if (test.CompilerOptions != null) {
457 test_args = new string [2 + test.CompilerOptions.Length];
458 test.CompilerOptions.CopyTo (test_args, 0);
460 test_args = new string [2];
462 test_args [test_args.Length - 2] = test.FileName;
463 test_args [test_args.Length - 1] = "-debug";
465 return tester.Invoke (test_args);
468 protected virtual TestCase CreateTestCase (string filename, string [] options, string [] deps)
470 return new TestCase (filename, options, deps);
473 void ReadWrongErrors (string file)
475 const string ignored = "IGNORE";
476 const string no_error = "NO ERROR";
478 using (StreamReader sr = new StreamReader (file)) {
480 while ((line = sr.ReadLine()) != null) {
481 if (line.StartsWith ("#"))
484 ArrayList active_cont = know_issues;
486 if (line.IndexOf (ignored) > 0)
487 active_cont = ignore_list;
488 else if (line.IndexOf (no_error) > 0)
489 active_cont = no_error_list;
491 string file_name = line.Split (' ')[0];
492 if (file_name.Length == 0)
495 active_cont.Add (file_name);
498 total_known_issues = know_issues.Count;
501 protected virtual void PrintSummary ()
503 LogLine ("Done" + Environment.NewLine);
506 rate = (float) (success) / (float)total;
507 LogLine ("{0} test cases passed ({1:0.##%})", success, rate);
509 if (syntax_errors > 0)
510 LogLine ("{0} test(s) ignored because of wrong syntax !", syntax_errors);
513 LogLine ("{0} test(s) ignored", ignored);
515 if (total_known_issues - know_issues.Count > 0)
516 LogLine ("{0} known issue(s)", total_known_issues - know_issues.Count);
518 know_issues.AddRange (no_error_list);
519 if (know_issues.Count > 0) {
521 LogLine (issue_file + " contains {0} already fixed issues. Please remove", know_issues.Count);
522 foreach (string s in know_issues)
525 if (regression.Count > 0) {
527 LogLine ("The latest changes caused regression in {0} file(s)", regression.Count);
528 foreach (string s in regression)
533 public int ResultCode
536 return regression.Count == 0 ? 0 : 1;
540 protected void Log (string msg, params object [] rest)
542 Console.Write (msg, rest);
543 if (log_file != null)
544 log_file.Write (msg, rest);
547 protected void LogLine (string msg)
549 Console.WriteLine (msg);
550 if (log_file != null)
551 log_file.WriteLine (msg);
554 protected void LogLine (string msg, params object [] rest)
556 Console.WriteLine (msg, rest);
557 if (log_file != null)
558 log_file.WriteLine (msg, rest);
561 public void LogFileLine (string file, string msg, params object [] args)
564 string.Format (msg, args) :
565 file + "...\t" + string.Format (msg, args);
567 Console.WriteLine (s);
568 if (log_file != null)
569 log_file.WriteLine (s);
572 #region IDisposable Members
574 public void Dispose()
576 if (log_file != null)
582 public virtual void Initialize ()
586 public virtual void CleanUp ()
592 class PositiveChecker: Checker
594 readonly string files_folder;
595 readonly static object[] default_args = new object[1] { new string[] {} };
598 bool update_verif_file;
599 Hashtable verif_data;
604 readonly string mono;
606 public enum TestResult {
616 public PositiveChecker (ITester tester, string verif_file):
619 files_folder = Directory.GetCurrentDirectory ();
620 this.verif_file = verif_file;
623 pi = new ProcessStartInfo ();
624 pi.CreateNoWindow = true;
625 pi.WindowStyle = ProcessWindowStyle.Hidden;
626 pi.RedirectStandardOutput = true;
627 pi.RedirectStandardError = true;
628 pi.UseShellExecute = false;
630 mono = Environment.GetEnvironmentVariable ("MONO_RUNTIME");
637 public bool UpdateVerificationDataFile {
639 update_verif_file = value;
642 return update_verif_file;
646 protected override bool GetExtraOptions(string file, out string[] compiler_options,
647 out string[] dependencies) {
648 if (!base.GetExtraOptions (file, out compiler_options, out dependencies))
652 if (compiler_options == null)
655 foreach (string one_opt in compiler_options) {
656 if (one_opt.StartsWith ("-doc:")) {
657 doc_output = one_opt.Split (':', '/')[1];
663 class DomainTester : MarshalByRefObject
665 public bool CheckILSize (PositiveTestCase test, PositiveChecker checker, string file)
667 Assembly assembly = Assembly.LoadFile (file);
670 Type[] types = assembly.GetTypes ();
671 foreach (Type t in types) {
674 if (!t.IsClass && !t.IsValueType)
677 if (test.VerificationProvider == null) {
678 if (!checker.UpdateVerificationDataFile)
679 checker.LogFileLine (test.FileName, "Missing IL verification data");
680 test.CreateNewTest ();
683 foreach (MemberInfo m in t.GetMembers (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
684 MethodBase mi = m as MethodBase;
688 if ((mi.Attributes & (MethodAttributes.PinvokeImpl)) != 0)
691 success &= CompareIL (mi, test, checker);
698 bool CompareIL (MethodBase mi, PositiveTestCase test, PositiveChecker checker)
700 string m_name = mi.ToString ();
701 string decl_type = mi.DeclaringType.ToString ();
702 PositiveTestCase.VerificationData data_provider = test.VerificationProvider;
704 PositiveTestCase.VerificationData.MethodData md = data_provider.FindMethodData (m_name, decl_type);
706 data_provider.AddNewMethod (mi, GetILSize (mi));
707 if (!data_provider.IsNewSet) {
708 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError, decl_type + ": " + m_name + " (new method?)");
716 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError, decl_type + ": " + m_name + " has a duplicate");
722 int il_size = GetILSize (mi);
723 if (md.ILSize == il_size)
726 if (md.ILSize > il_size) {
727 checker.LogFileLine (test.FileName, "{0} (code size reduction {1} -> {2})", decl_type + ": " + m_name, md.ILSize, il_size);
732 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError,
733 string.Format ("{0} (code size {1} -> {2})", decl_type + ": " + m_name, md.ILSize, il_size));
740 static int GetILSize (MethodBase mi)
742 MethodBody body = mi.GetMethodBody ();
744 return body.GetILAsByteArray ().Length;
749 bool ExecuteFile (MethodInfo entry_point, string filename)
751 TextWriter stdout = Console.Out;
752 TextWriter stderr = Console.Error;
753 Console.SetOut (TextWriter.Null);
754 Console.SetError (TextWriter.Null);
755 ParameterInfo[] pi = entry_point.GetParameters ();
756 object[] args = pi.Length == 0 ? null : default_args;
758 object result = null;
761 result = entry_point.Invoke (null, args);
763 Console.SetOut (stdout);
764 Console.SetError (stderr);
766 } catch (Exception e) {
767 throw new ApplicationException (e.ToString ());
770 if (result is int && (int) result != 0)
771 throw new ApplicationException ("Wrong return code: " + result.ToString ());
776 public bool Test (string file)
778 Assembly assembly = Assembly.LoadFile (file);
779 return ExecuteFile (assembly.EntryPoint, file);
783 protected override bool Check(TestCase test)
785 string filename = test.FileName;
787 if (!base.Check (test)) {
788 HandleFailure (filename, TestResult.CompileError, tester.Output);
792 catch (Exception e) {
793 if (e.InnerException != null)
794 e = e.InnerException;
796 HandleFailure (filename, TestResult.CompileError, e.ToString ());
801 if (filename.EndsWith ("-lib.cs") || filename.EndsWith ("-mod.cs")) {
803 LogFileLine (filename, "OK");
808 string file = Path.Combine (files_folder, Path.GetFileNameWithoutExtension (filename) + ".exe");
810 // Enable .dll only tests (no execution required)
811 if (!File.Exists(file)) {
812 HandleFailure (filename, TestResult.Success, null);
816 AppDomain domain = null;
818 if (safe_execution) {
819 // Create a new AppDomain, with the current directory as the base.
820 AppDomainSetup setupInfo = new AppDomainSetup ();
821 setupInfo.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
822 setupInfo.LoaderOptimization = LoaderOptimization.SingleDomain;
823 domain = AppDomain.CreateDomain (Path.GetFileNameWithoutExtension (file), null, setupInfo);
831 tester = (DomainTester) domain.CreateInstanceAndUnwrap (typeof (PositiveChecker).Assembly.FullName, typeof (DomainTester).FullName);
834 tester = new DomainTester ();
836 if (!tester.Test (file))
839 } catch (ApplicationException e) {
840 HandleFailure (filename, TestResult.ExecError, e.Message);
842 } catch (Exception e) {
843 HandleFailure (filename, TestResult.LoadError, e.ToString ());
847 if (doc_output != null) {
848 string ref_file = filename.Replace (".cs", "-ref.xml");
851 new XmlComparer ("doc").Compare (ref_file, doc_output);
853 } catch (Exception e) {
854 HandleFailure (filename, TestResult.XmlError, e.Message);
858 if (verif_file != null) {
859 PositiveTestCase pt = (PositiveTestCase) test;
860 pt.VerificationProvider = (PositiveTestCase.VerificationData) verif_data[filename];
862 if (!tester.CheckILSize (pt, this, file))
866 if (filename.StartsWith ("test-debug", StringComparison.OrdinalIgnoreCase)) {
867 MonoSymbolFile mdb_file = MonoSymbolFile.ReadSymbolFile (file + ".mdb");
868 var mdb_xml_file = mdb_file.FileName + ".xml";
869 ConvertSymbolFileToXml (mdb_file, mdb_xml_file);
871 var ref_file = filename.Replace(".cs", "-ref.xml");
873 new XmlComparer ("symbols").Compare (ref_file, mdb_xml_file);
874 } catch (Exception e) {
875 HandleFailure (filename, TestResult.DebugError, e.Message);
883 AppDomain.Unload (domain);
886 HandleFailure (filename, TestResult.Success, null);
890 static void ConvertSymbolFileToXml (MonoSymbolFile symbolFile, string xmlFile)
892 using (XmlTextWriter writer = new XmlTextWriter (xmlFile, Encoding.UTF8)) {
893 writer.Formatting = Formatting.Indented;
895 writer.WriteStartDocument ();
897 writer.WriteStartElement ("symbols");
899 writer.WriteStartElement ("files");
900 foreach (var file in symbolFile.Sources) {
901 writer.WriteStartElement ("file");
902 writer.WriteAttributeString ("id", file.Index.ToString ());
903 writer.WriteAttributeString ("name", Path.GetFileName (file.FileName));
904 writer.WriteEndElement ();
906 writer.WriteEndElement ();
908 writer.WriteStartElement ("methods");
909 foreach (var method in symbolFile.Methods) {
910 writer.WriteStartElement ("method");
911 writer.WriteAttributeString ("token", IntToHex (method.Token));
913 var il_entries = method.GetLineNumberTable ();
914 writer.WriteStartElement ("sequencepoints");
915 foreach (var entry in il_entries.LineNumbers) {
916 writer.WriteStartElement ("entry");
917 writer.WriteAttributeString ("il", IntToHex (entry.Offset));
918 writer.WriteAttributeString ("row", entry.Row.ToString ());
919 writer.WriteAttributeString ("file_ref", entry.File.ToString ());
920 writer.WriteAttributeString ("hidden", BoolToString (entry.IsHidden));
921 writer.WriteEndElement ();
923 writer.WriteEndElement ();
925 writer.WriteStartElement ("locals");
926 foreach (var local in method.GetLocals ()) {
927 writer.WriteStartElement ("entry");
928 writer.WriteAttributeString ("name", local.Name);
929 writer.WriteAttributeString ("il_index", local.Index.ToString ());
930 writer.WriteAttributeString ("scope_ref", local.BlockIndex.ToString ());
931 writer.WriteEndElement ();
933 writer.WriteEndElement ();
935 writer.WriteStartElement ("scopes");
936 foreach (var scope in method.GetCodeBlocks ()) {
937 writer.WriteStartElement ("entry");
938 writer.WriteAttributeString ("index", scope.Index.ToString ());
939 writer.WriteAttributeString ("start", IntToHex (scope.StartOffset));
940 writer.WriteAttributeString ("end", IntToHex (scope.EndOffset));
941 writer.WriteEndElement ();
943 writer.WriteEndElement ();
945 writer.WriteEndElement ();
947 writer.WriteEndElement ();
949 writer.WriteEndElement ();
950 writer.WriteEndDocument ();
954 static string IntToHex (int value)
956 return "0x" + value.ToString ("x", CultureInfo.InvariantCulture);
959 static string BoolToString (bool value)
961 return value ? "true" : "false";
964 protected override TestCase CreateTestCase (string filename, string [] options, string [] deps)
966 return new PositiveTestCase (filename, options, deps);
969 public void HandleFailure (string file, TestResult status, string extra)
972 case TestResult.Success:
974 if (know_issues.Contains (file)) {
975 LogFileLine (file, "FIXED ISSUE");
979 LogFileLine (file, "OK");
982 case TestResult.CompileError:
983 if (know_issues.Contains (file)) {
984 LogFileLine (file, "KNOWN ISSUE (Compilation error)");
985 know_issues.Remove (file);
988 LogFileLine (file, "REGRESSION (SUCCESS -> COMPILATION ERROR)");
991 case TestResult.ExecError:
992 if (know_issues.Contains (file)) {
993 LogFileLine (file, "KNOWN ISSUE (Execution error)");
994 know_issues.Remove (file);
997 LogFileLine (file, "REGRESSION (SUCCESS -> EXECUTION ERROR)");
1000 case TestResult.XmlError:
1001 if (know_issues.Contains (file)) {
1002 LogFileLine (file, "KNOWN ISSUE (Xml comparision error)");
1003 know_issues.Remove (file);
1006 LogFileLine (file, "REGRESSION (SUCCESS -> DOCUMENTATION ERROR)");
1009 case TestResult.LoadError:
1010 LogFileLine (file, "REGRESSION (SUCCESS -> LOAD ERROR)");
1013 case TestResult.ILError:
1014 if (!update_verif_file) {
1015 LogFileLine (file, "IL REGRESSION: " + extra);
1020 case TestResult.DebugError:
1021 LogFileLine (file, "REGRESSION (SUCCESS -> SYMBOL FILE ERROR)");
1026 LogLine ("{0}", extra);
1028 if (!regression.Contains (file))
1029 regression.Add (file);
1032 public override void Initialize ()
1034 if (verif_file != null) {
1035 LoadVerificationData (verif_file);
1041 public override void CleanUp ()
1045 if (update_verif_file) {
1046 UpdateVerificationData (verif_file);
1050 void LoadVerificationData (string file)
1052 LogLine ("Loading verification data from `{0}' ...", file);
1054 using (XmlReader r = XmlReader.Create (file)) {
1055 r.ReadStartElement ("tests");
1056 verif_data = new Hashtable ();
1059 if (r.Name != "test")
1062 string name = r.GetAttribute ("name");
1063 PositiveTestCase.VerificationData tc = PositiveTestCase.VerificationData.FromFile (name, r);
1064 verif_data.Add (name, tc);
1069 void UpdateVerificationData (string file)
1071 LogLine ("Updating verification data `{0}' ...", file);
1073 XmlWriterSettings s = new XmlWriterSettings ();
1075 using (XmlWriter w = XmlWriter.Create (new StreamWriter (file, false, Encoding.UTF8), s)) {
1076 w.WriteStartDocument ();
1077 w.WriteComment ("This file contains expected IL and metadata produced by compiler for each test");
1078 w.WriteStartElement ("tests");
1079 foreach (PositiveTestCase tc in tests) {
1080 if (tc.VerificationProvider != null)
1081 tc.VerificationProvider.WriteCodeInfoTo (w);
1083 w.WriteEndElement ();
1088 class NegativeChecker: Checker
1090 string expected_message;
1091 string error_message;
1093 bool check_error_line;
1095 IDictionary wrong_warning;
1097 protected enum CompilerError {
1106 public NegativeChecker (ITester tester, bool check_msg):
1109 this.check_msg = check_msg;
1110 wrong_warning = new Hashtable ();
1113 protected override bool AnalyzeTestFile (string file, ref int row, string line,
1114 ref string[] compiler_options,
1115 ref string[] dependencies)
1118 expected_message = null;
1120 int index = line.IndexOf (':');
1121 if (index == -1 || index > 15) {
1122 LogFileLine (file, "IGNORING: Wrong test file syntax (missing error mesage text)");
1124 base.AnalyzeTestFile (file, ref row, line, ref compiler_options,
1129 expected_message = line.Substring (index + 1).Trim ();
1133 string filtered = line.Replace(" ", "");
1135 // Some error tests require to have different error text for different runtimes.
1136 if (filtered.StartsWith ("//GMCS")) {
1138 return AnalyzeTestFile(file, ref row, line, ref compiler_options, ref dependencies);
1141 check_error_line = !filtered.StartsWith ("//Line:0");
1143 if (!filtered.StartsWith ("//Line:")) {
1144 LogFileLine (file, "IGNORING: Wrong test syntax (following line after an error messsage must have `// Line: xx' syntax");
1150 if (!base.AnalyzeTestFile (file, ref row, line, ref compiler_options, ref dependencies))
1154 if (compiler_options != null) {
1155 foreach (string s in compiler_options) {
1156 if (s.StartsWith ("-warnaserror") || s.StartsWith ("/warnaserror"))
1164 protected override bool Check (TestCase test)
1166 string filename = test.FileName;
1169 while (Char.IsLetter (filename, start_char))
1172 int end_char = filename.IndexOfAny (new char [] { '-', '.' } );
1173 string expected = filename.Substring (start_char, end_char - start_char);
1176 if (base.Check (test)) {
1177 HandleFailure (filename, CompilerError.Missing);
1181 catch (Exception e) {
1182 HandleFailure (filename, CompilerError.Missing);
1183 if (e.InnerException != null)
1184 e = e.InnerException;
1186 Log (e.ToString ());
1190 int err_id = int.Parse (expected, System.Globalization.CultureInfo.InvariantCulture);
1191 if (tester.IsWarning (err_id)) {
1193 wrong_warning [err_id] = true;
1196 wrong_warning [err_id] = false;
1199 CompilerError result_code = GetCompilerError (expected, tester.Output);
1200 if (HandleFailure (filename, result_code)) {
1205 if (result_code == CompilerError.Wrong)
1206 LogLine (tester.Output);
1211 CompilerError GetCompilerError (string expected, string buffer)
1213 const string error_prefix = "CS";
1214 const string ignored_error = "error CS5001";
1215 string tested_text = "error " + error_prefix + expected;
1216 StringReader sr = new StringReader (buffer);
1217 string line = sr.ReadLine ();
1218 ArrayList ld = new ArrayList ();
1219 CompilerError result = CompilerError.Missing;
1220 while (line != null) {
1221 if (ld.Contains (line) && result == CompilerError.Expected) {
1222 if (line.IndexOf ("Location of the symbol related to previous") == -1)
1223 return CompilerError.Duplicate;
1227 if (result != CompilerError.Expected) {
1228 if (line.IndexOf (tested_text) != -1) {
1230 int first = line.IndexOf (':');
1231 int second = line.IndexOf (':', first + 1);
1232 if (line.IndexOf ("Warning as Error: ", first, StringComparison.Ordinal) > 0) {
1233 if (check_error_line) {
1234 second = line.IndexOf (':', second + 1);
1236 } else if (second == -1 || !check_error_line) {
1240 string msg = line.Substring (second + 1).TrimEnd ('.').Trim ();
1241 if (msg != expected_message && msg != expected_message.Replace ('`', '\'')) {
1242 error_message = msg;
1243 return CompilerError.WrongMessage;
1246 if (check_error_line && line.IndexOf (".cs(") == -1)
1247 return CompilerError.MissingLocation;
1249 result = CompilerError.Expected;
1250 } else if (line.IndexOf (error_prefix) != -1 &&
1251 line.IndexOf (ignored_error) == -1)
1252 result = CompilerError.Wrong;
1255 line = sr.ReadLine ();
1261 bool HandleFailure (string file, CompilerError status)
1264 case CompilerError.Expected:
1265 if (know_issues.Contains (file) || no_error_list.Contains (file)) {
1266 LogFileLine (file, "FIXED ISSUE");
1271 LogFileLine (file, "OK");
1274 case CompilerError.Wrong:
1275 if (know_issues.Contains (file)) {
1276 LogFileLine (file, "KNOWN ISSUE (Wrong error reported)");
1277 know_issues.Remove (file);
1280 if (no_error_list.Contains (file)) {
1281 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR CODE)");
1282 no_error_list.Remove (file);
1285 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR CODE)");
1289 case CompilerError.WrongMessage:
1290 if (know_issues.Contains (file)) {
1291 LogFileLine (file, "KNOWN ISSUE (Wrong error message reported)");
1292 know_issues.Remove (file);
1295 if (no_error_list.Contains (file)) {
1296 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR MESSAGE)");
1297 no_error_list.Remove (file);
1300 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR MESSAGE)");
1301 LogLine ("Exp: {0}", expected_message);
1302 LogLine ("Was: {0}", error_message);
1306 case CompilerError.Missing:
1307 if (no_error_list.Contains (file)) {
1308 LogFileLine (file, "KNOWN ISSUE (No error reported)");
1309 no_error_list.Remove (file);
1313 if (know_issues.Contains (file)) {
1314 LogFileLine (file, "REGRESSION (WRONG ERROR -> NO ERROR)");
1315 know_issues.Remove (file);
1318 LogFileLine (file, "REGRESSION (CORRECT ERROR -> NO ERROR)");
1323 case CompilerError.MissingLocation:
1324 if (know_issues.Contains (file)) {
1325 LogFileLine (file, "KNOWN ISSUE (Missing error location)");
1326 know_issues.Remove (file);
1329 if (no_error_list.Contains (file)) {
1330 LogFileLine (file, "REGRESSION (NO ERROR -> MISSING ERROR LOCATION)");
1331 no_error_list.Remove (file);
1334 LogFileLine (file, "REGRESSION (CORRECT ERROR -> MISSING ERROR LOCATION)");
1338 case CompilerError.Duplicate:
1339 // Will become an error soon
1340 LogFileLine (file, "WARNING: EXACTLY SAME ERROR HAS BEEN ISSUED MULTIPLE TIMES");
1344 regression.Add (file);
1348 protected override void PrintSummary()
1350 base.PrintSummary ();
1352 if (wrong_warning.Count > 0) {
1354 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)");
1356 foreach (DictionaryEntry de in wrong_warning)
1357 LogLine ("CS{0:0000} : {1}", de.Key, (bool)de.Value ? "incorrect warning definition" : "missing warning definition");
1365 static int Main(string[] args)
1369 if (GetOption ("help", args, false, out temp)) {
1375 if (!GetOption ("compiler", args, true, out compiler)) {
1382 Console.WriteLine ("Loading " + compiler + " ...");
1383 tester = new ReflectionTester (Assembly.LoadFile (compiler));
1389 Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
1390 if (!File.Exists (compiler)) {
1391 Console.Error.WriteLine ("ERROR: Tested compiler was not found");
1394 tester = new ProcessTester (compiler);
1399 if (!GetOption ("mode", args, true, out mode)) {
1408 checker = new NegativeChecker (tester, true);
1413 GetOption ("il", args, false, out iltest);
1414 checker = new PositiveChecker (tester, iltest);
1417 if (iltest != null && GetOption ("update-il", args, false, out temp)) {
1418 ((PositiveChecker) checker).UpdateVerificationDataFile = true;
1423 Console.Error.WriteLine ("Invalid -mode argument");
1428 if (GetOption ("issues", args, true, out temp))
1429 checker.IssueFile = temp;
1430 if (GetOption ("log", args, true, out temp))
1431 checker.LogFile = temp;
1432 if (GetOption ("verbose", args, false, out temp))
1433 checker.Verbose = true;
1434 if (GetOption ("safe-execution", args, false, out temp))
1435 checker.SafeExecution = true;
1436 if (GetOption ("compiler-options", args, true, out temp)) {
1437 string[] extra = temp.Split (' ');
1438 checker.ExtraCompilerOptions = extra;
1441 string test_pattern;
1442 if (!GetOption ("files", args, true, out test_pattern)) {
1447 var files = new List<string> ();
1448 switch (test_pattern) {
1450 files.AddRange (Directory.GetFiles (".", positive ? "test*.cs" : "cs*.cs"));
1453 files.AddRange (Directory.GetFiles (".", positive ? "gtest*.cs" : "gcs*.cs"));
1456 files.AddRange (Directory.GetFiles (".", positive ? "dtest*.cs" : "dcs*.cs"));
1459 files.AddRange (Directory.GetFiles (".", test_pattern));
1463 if (files.Count == 0) {
1464 Console.Error.WriteLine ("No files matching `{0}' found", test_pattern);
1468 checker.Initialize ();
1470 files.Sort ((a, b) => {
1471 if (a.EndsWith ("-lib.cs", StringComparison.Ordinal)) {
1472 if (!b.EndsWith ("-lib.cs", StringComparison.Ordinal))
1474 } else if (b.EndsWith ("-lib.cs", StringComparison.Ordinal)) {
1475 if (!a.EndsWith ("-lib.cs", StringComparison.Ordinal))
1479 return a.CompareTo (b);
1482 foreach (string s in files) {
1483 string filename = Path.GetFileName (s);
1484 if (Char.IsUpper (filename, 0)) { // Windows hack
1488 if (filename.EndsWith ("-p2.cs"))
1491 checker.Do (filename);
1498 return checker.ResultCode;
1501 static bool GetOption (string opt, string[] args, bool req_arg, out string value)
1504 foreach (string a in args) {
1505 if (a.StartsWith (opt)) {
1506 int sep = a.IndexOf (':');
1508 value = a.Substring (sep + 1);
1512 Console.Error.WriteLine ("Missing argument in option " + opt);
1525 static void Usage ()
1528 "Mono compiler tester, (C) 2009 Novell, Inc.\n" +
1529 "compiler-tester -mode:[pos|neg] -compiler:FILE -files:file-list [options]\n" +
1531 " -compiler:FILE The file which will be used to compiler tests\n" +
1532 " -compiler-options:OPTIONS Add global compiler options\n" +
1533 " -il:IL-FILE XML file with expected IL details for each test\n" +
1534 " -issues:FILE The list of expected failures\n" +
1535 " -log:FILE Writes any output also to the file\n" +
1536 " -help Lists all options\n" +
1537 " -mode:[pos|neg] Specifies compiler test mode\n" +
1538 " -safe-execution Runs compiled executables in separate app-domain\n" +
1539 " -update-il Updates IL-FILE to match compiler output\n" +
1540 " -verbose Prints more details during testing\n"