3 using System.Diagnostics;
4 using System.Reflection;
6 using System.Collections;
12 string Output { get; }
13 bool Invoke (string[] args);
14 bool IsWarning (int warningNumber);
17 class ReflectionTester: ITester {
23 public ReflectionTester (Assembly a)
25 Type t = a.GetType ("Mono.CSharp.CompilerCallableEntryPoint");
26 ep = t.GetMethod ("InvokeCompiler",
27 BindingFlags.Static | BindingFlags.Public);
29 throw new MissingMethodException ("static InvokeCompiler");
30 method_arg = new object [2];
32 PropertyInfo pi = t.GetProperty ("AllWarningNumbers");
33 all_warnings = (int[])pi.GetValue (null, null);
34 Array.Sort (all_warnings);
37 public string Output {
39 return output.GetStringBuilder ().ToString ();
43 public bool Invoke(string[] args)
45 output = new StringWriter ();
46 method_arg [0] = args;
47 method_arg [1] = output;
48 return (bool)ep.Invoke (null, method_arg);
51 public bool IsWarning (int warningNumber)
53 return Array.BinarySearch (all_warnings, warningNumber) >= 0;
57 class ProcessTester: ITester
62 public ProcessTester (string p_path)
64 pi = new ProcessStartInfo ();
66 pi.CreateNoWindow = true;
67 pi.WindowStyle = ProcessWindowStyle.Hidden;
68 pi.RedirectStandardOutput = true;
69 pi.RedirectStandardError = true;
70 pi.UseShellExecute = false;
73 public string Output {
79 public bool Invoke(string[] args)
81 StringBuilder sb = new StringBuilder ("/nologo ");
82 foreach (string s in args) {
86 pi.Arguments = sb.ToString ();
87 Process p = Process.Start (pi);
88 output = p.StandardError.ReadToEnd ();
89 if (output.Length == 0)
90 output = p.StandardOutput.ReadToEnd ();
92 return p.ExitCode == 0;
95 public bool IsWarning (int warningNumber)
97 throw new NotImplementedException ();
103 public readonly string FileName;
104 public readonly string[] CompilerOptions;
105 public readonly string[] Dependencies;
107 public TestCase (string filename, string[] options, string[] deps)
109 this.FileName = filename;
110 this.CompilerOptions = options;
111 this.Dependencies = deps;
115 class Checker: IDisposable
117 protected ITester tester;
118 protected int success;
120 protected int ignored;
122 StreamWriter log_file;
123 // protected string[] compiler_options;
124 // protected string[] dependencies;
126 protected ArrayList tests = new ArrayList ();
127 protected Hashtable test_hash = new Hashtable ();
128 protected ArrayList regression = new ArrayList ();
129 protected ArrayList know_issues = new ArrayList ();
130 protected ArrayList ignore_list = new ArrayList ();
131 protected ArrayList no_error_list = new ArrayList ();
133 int total_known_issues;
135 protected Checker (ITester tester, string log_file, string issue_file)
137 this.tester = tester;
138 this.issue_file = issue_file;
139 ReadWrongErrors (issue_file);
140 this.log_file = new StreamWriter (log_file, false);
143 protected virtual bool GetExtraOptions (string file, out string[] compiler_options,
144 out string[] dependencies)
147 compiler_options = null;
150 using (StreamReader sr = new StreamReader (file)) {
152 while (row++ < 3 && (line = sr.ReadLine()) != null) {
153 if (!AnalyzeTestFile (ref row, line, ref compiler_options,
164 protected virtual bool AnalyzeTestFile (ref int row, string line,
165 ref string[] compiler_options,
166 ref string[] dependencies)
168 const string options = "// Compiler options:";
169 const string depends = "// Dependencies:";
172 compiler_options = null;
176 int index = line.IndexOf (options);
178 compiler_options = line.Substring (index + options.Length).Trim().Split (' ');
179 for (int i = 0; i < compiler_options.Length; i++)
180 compiler_options[i] = compiler_options[i].TrimStart ();
182 index = line.IndexOf (depends);
184 dependencies = line.Substring (index + depends.Length).Trim().Split (' ');
185 for (int i = 0; i < dependencies.Length; i++)
186 dependencies[i] = dependencies[i].TrimStart ();
192 public bool Do (string filename)
194 if (test_hash.Contains (filename))
197 if (ignore_list.Contains (filename)) {
199 LogLine (filename + "...\tNOT TESTED");
203 string[] compiler_options, dependencies;
204 if (!GetExtraOptions (filename, out compiler_options, out dependencies)) {
205 LogLine (filename + "...\tERROR");
209 TestCase test = new TestCase (filename, compiler_options, dependencies);
210 test_hash.Add (filename, test);
213 if (dependencies != null) {
214 foreach (string dependency in dependencies) {
215 if (!Do (dependency)) {
216 LogLine (filename + "...\tDEPENDENCY FAILED");
230 protected virtual bool Check (TestCase test)
234 if (test.CompilerOptions != null) {
235 test_args = new string [1 + test.CompilerOptions.Length];
236 test.CompilerOptions.CopyTo (test_args, 0);
238 test_args = new string [1];
240 test_args [test_args.Length - 1] = test.FileName;
242 return tester.Invoke (test_args);
246 void ReadWrongErrors (string file)
248 const string ignored = "IGNORE";
249 const string no_error = "NO ERROR";
251 using (StreamReader sr = new StreamReader (file)) {
253 while ((line = sr.ReadLine()) != null) {
254 if (line.StartsWith ("#"))
257 ArrayList active_cont = know_issues;
259 if (line.IndexOf (ignored) > 0)
260 active_cont = ignore_list;
261 else if (line.IndexOf (no_error) > 0)
262 active_cont = no_error_list;
264 string file_name = line.Split (' ')[0];
265 if (file_name.Length == 0)
268 active_cont.Add (file_name);
271 total_known_issues = know_issues.Count;
274 public virtual void PrintSummary ()
276 LogLine ("Done" + Environment.NewLine);
277 LogLine ("{0} test cases passed ({1:.##%})", success, (float) (success) / (float)total);
280 LogLine ("{0} test cases ignored", ignored);
282 if (total_known_issues - know_issues.Count > 0)
283 LogLine ("{0} known issues", total_known_issues - know_issues.Count);
285 know_issues.AddRange (no_error_list);
286 if (know_issues.Count > 0) {
288 LogLine (issue_file + " contains {0} already fixed issues. Please remove", know_issues.Count);
289 foreach (string s in know_issues)
292 if (regression.Count > 0) {
294 LogLine ("The latest changes caused regression in {0} file(s)", regression.Count);
295 foreach (string s in regression)
300 public int ResultCode
303 return regression.Count == 0 ? 0 : 1;
307 protected void Log (string msg, params object [] rest)
309 Console.Write (msg, rest);
310 log_file.Write (msg, rest);
313 protected void LogLine (string msg, params object [] rest)
315 Console.WriteLine (msg, rest);
316 log_file.WriteLine (msg, rest);
319 #region IDisposable Members
321 public void Dispose()
329 class PositiveChecker: Checker {
330 readonly string files_folder;
331 readonly static object[] default_args = new object[1] { new string[] {} };
334 // When we cannot load assembly to domain we are automaticaly switching to process calling
335 bool appdomain_limit_reached;
338 readonly string mono;
340 // This is really pain
341 // This number is highly experimental on my box I am not able to load more than 1000 files to domain
342 // Every files is there twice and we need some space for tests which reference assemblies
343 const int MAX_TESTS_IN_DOMAIN = 420;
346 protected enum TestResult {
354 public PositiveChecker (ITester tester, string log_file, string issue_file):
355 base (tester, log_file, issue_file)
357 files_folder = Directory.GetCurrentDirectory ();
359 pi = new ProcessStartInfo ();
360 pi.CreateNoWindow = true;
361 pi.WindowStyle = ProcessWindowStyle.Hidden;
362 pi.RedirectStandardOutput = true;
363 pi.RedirectStandardError = true;
364 pi.UseShellExecute = false;
366 mono = Environment.GetEnvironmentVariable ("MONO_RUNTIME");
372 protected override bool GetExtraOptions(string file, out string[] compiler_options,
373 out string[] dependencies) {
374 if (!base.GetExtraOptions (file, out compiler_options, out dependencies))
378 if (compiler_options == null)
381 foreach (string one_opt in compiler_options) {
382 if (one_opt.StartsWith ("-doc:")) {
383 doc_output = one_opt.Split (':')[1];
389 protected override bool Check(TestCase test)
391 string filename = test.FileName;
393 if (!base.Check (test)) {
394 HandleFailure (filename, TestResult.CompileError, tester.Output);
398 catch (Exception e) {
399 HandleFailure (filename, TestResult.CompileError, e.ToString ());
404 if (filename.EndsWith ("-lib.cs") || filename.EndsWith ("-mod.cs")) {
410 MethodInfo mi = null;
411 string file = Path.Combine (files_folder, Path.GetFileNameWithoutExtension (filename) + ".exe");
413 // Enable .dll only tests (no execution required)
414 if (!File.Exists(file)) {
415 HandleFailure (filename, TestResult.Success, null);
419 if (!appdomain_limit_reached) {
421 mi = Assembly.LoadFile (file).EntryPoint;
422 if (test_counter++ > MAX_TESTS_IN_DOMAIN)
423 appdomain_limit_reached = true;
425 catch (FileNotFoundException) {
426 if (File.Exists (file)) {
427 Console.WriteLine ("APPDOMAIN LIMIT REACHED");
428 appdomain_limit_reached = true;
431 catch (Exception e) {
432 HandleFailure (filename, TestResult.LoadError, e.ToString ());
437 if (appdomain_limit_reached) {
438 if (!ExecuteFile (file, filename))
441 if (!ExecuteFile (mi, file, filename))
445 if (doc_output != null) {
446 string ref_file = filename.Replace (".cs", "-ref.xml");
448 XmlComparer.Compare (ref_file, doc_output);
450 catch (Exception e) {
451 HandleFailure (filename, TestResult.XmlError, e.Message);
456 HandleFailure (filename, TestResult.Success, null);
460 int ExecFile (string exe_name, string filename)
463 pi.FileName = exe_name;
465 pi.Arguments = exe_name;
467 Process p = Process.Start (pi);
472 bool ExecuteFile (string exe_name, string filename)
474 int exit_code = ExecFile (exe_name, filename);
478 HandleFailure (filename, TestResult.ExecError, "Wrong return code: " + exit_code);
482 bool ExecuteFile (MethodInfo entry_point, string exe_name, string filename)
484 TextWriter stdout = Console.Out;
485 TextWriter stderr = Console.Error;
486 Console.SetOut (TextWriter.Null);
487 Console.SetError (TextWriter.Null);
488 ParameterInfo[] pi = entry_point.GetParameters ();
489 object[] args = pi.Length == 0 ? null : default_args;
491 object result = null;
494 result = entry_point.Invoke (null, args);
496 Console.SetOut (stdout);
497 Console.SetError (stderr);
500 catch (Exception e) {
501 int exit_code = ExecFile (exe_name, filename);
502 if (exit_code == 0) {
503 LogLine ("(appdomain method failed, external executable succeeded)");
504 LogLine (e.ToString ());
507 HandleFailure (filename, TestResult.ExecError, e.ToString ());
511 if (result is int && (int)result != 0) {
512 HandleFailure (filename, TestResult.ExecError, "Wrong return code: " + result.ToString ());
518 void HandleFailure (string file, TestResult status, string extra)
521 case TestResult.Success:
523 if (know_issues.Contains (file)) {
524 LogLine ("FIXED ISSUE");
530 case TestResult.CompileError:
531 if (know_issues.Contains (file)) {
532 LogLine ("KNOWN ISSUE (Compilation error)");
533 know_issues.Remove (file);
536 LogLine ("REGRESSION (SUCCESS -> COMPILATION ERROR)");
539 case TestResult.ExecError:
540 if (know_issues.Contains (file)) {
541 LogLine ("KNOWN ISSUE (Execution error)");
542 know_issues.Remove (file);
545 LogLine ("REGRESSION (SUCCESS -> EXECUTION ERROR)");
548 case TestResult.XmlError:
549 if (know_issues.Contains (file)) {
550 LogLine ("KNOWN ISSUE (Xml comparision error)");
551 know_issues.Remove (file);
554 LogLine ("REGRESSION (SUCCESS -> DOCUMENTATION ERROR)");
557 case TestResult.LoadError:
558 LogLine ("REGRESSION (SUCCESS -> LOAD ERROR)");
563 LogLine ("{0}", extra);
565 regression.Add (file);
569 class NegativeChecker: Checker
571 string expected_message;
572 string error_message;
574 bool check_error_line;
576 IDictionary wrong_warning;
578 protected enum CompilerError {
587 public NegativeChecker (ITester tester, string log_file, string issue_file, bool check_msg):
588 base (tester, log_file, issue_file)
590 this.check_msg = check_msg;
591 wrong_warning = new Hashtable ();
594 protected override bool AnalyzeTestFile (ref int row, string line,
595 ref string[] compiler_options,
596 ref string[] dependencies)
599 expected_message = null;
601 int index = line.IndexOf (':');
602 if (index == -1 || index > 15) {
603 LogLine ("IGNORING: Wrong test file syntax (missing error mesage text)");
605 base.AnalyzeTestFile (ref row, line, ref compiler_options,
610 expected_message = line.Substring (index + 1).Trim ();
614 string filtered = line.Replace(" ", "");
616 // Some error tests require to have different error text for different runtimes.
617 if (filtered.StartsWith ("//GMCS")) {
622 return AnalyzeTestFile(ref row, line, ref compiler_options, ref dependencies);
626 check_error_line = !filtered.StartsWith ("//Line:0");
628 if (!filtered.StartsWith ("//Line:")) {
629 LogLine ("IGNORING: Wrong test syntax (following line after an error messsage must have `// Line: xx' syntax");
635 if (!base.AnalyzeTestFile (ref row, line, ref compiler_options, ref dependencies))
639 if (compiler_options != null) {
640 foreach (string s in compiler_options) {
641 if (s.EndsWith ("warnaserror"))
649 protected override bool Check (TestCase test)
651 string filename = test.FileName;
654 while (Char.IsLetter (filename, start_char))
657 int end_char = filename.IndexOfAny (new char [] { '-', '.' } );
658 string expected = filename.Substring (start_char, end_char - start_char);
661 if (base.Check (test)) {
662 HandleFailure (filename, CompilerError.Missing);
666 catch (Exception e) {
667 HandleFailure (filename, CompilerError.Missing);
672 int err_id = int.Parse (expected, System.Globalization.CultureInfo.InvariantCulture);
673 if (tester.IsWarning (err_id)) {
675 wrong_warning [err_id] = true;
678 wrong_warning [err_id] = false;
681 CompilerError result_code = GetCompilerError (expected, tester.Output);
682 if (HandleFailure (filename, result_code)) {
687 if (result_code == CompilerError.Wrong)
688 LogLine (tester.Output);
693 CompilerError GetCompilerError (string expected, string buffer)
695 const string error_prefix = "CS";
696 const string ignored_error = "error CS5001";
697 string tested_text = "error " + error_prefix + expected;
698 StringReader sr = new StringReader (buffer);
699 string line = sr.ReadLine ();
700 ArrayList ld = new ArrayList ();
701 CompilerError result = CompilerError.Missing;
702 while (line != null) {
703 if (ld.Contains (line)) {
704 if (line.IndexOf ("Location of the symbol related to previous") == -1)
705 return CompilerError.Duplicate;
709 if (result != CompilerError.Expected) {
710 if (line.IndexOf (tested_text) != -1) {
712 int first = line.IndexOf (':');
713 int second = line.IndexOf (':', first + 1);
714 if (second == -1 || !check_error_line)
717 string msg = line.Substring (second + 1).TrimEnd ('.').Trim ();
718 if (msg != expected_message && msg != expected_message.Replace ('`', '\'')) {
720 return CompilerError.WrongMessage;
723 if (check_error_line && line.IndexOf (".cs(") == -1)
724 return CompilerError.MissingLocation;
726 result = CompilerError.Expected;
727 } else if (line.IndexOf (error_prefix) != -1 &&
728 line.IndexOf (ignored_error) == -1)
729 result = CompilerError.Wrong;
732 line = sr.ReadLine ();
738 bool HandleFailure (string file, CompilerError status)
741 case CompilerError.Expected:
742 if (know_issues.Contains (file) || no_error_list.Contains (file)) {
743 LogLine ("FIXED ISSUE");
749 case CompilerError.Wrong:
750 if (know_issues.Contains (file)) {
751 LogLine ("KNOWN ISSUE (Wrong error reported)");
752 know_issues.Remove (file);
755 if (no_error_list.Contains (file)) {
756 LogLine ("REGRESSION (NO ERROR -> WRONG ERROR CODE)");
757 no_error_list.Remove (file);
760 LogLine ("REGRESSION (CORRECT ERROR -> WRONG ERROR CODE)");
764 case CompilerError.WrongMessage:
765 if (know_issues.Contains (file)) {
766 LogLine ("KNOWN ISSUE (Wrong error message reported)");
767 know_issues.Remove (file);
770 if (no_error_list.Contains (file)) {
771 LogLine ("REGRESSION (NO ERROR -> WRONG ERROR MESSAGE)");
772 no_error_list.Remove (file);
775 LogLine ("REGRESSION (CORRECT ERROR -> WRONG ERROR MESSAGE)");
776 LogLine ("Exp: {0}", expected_message);
777 LogLine ("Was: {0}", error_message);
781 case CompilerError.Missing:
782 if (no_error_list.Contains (file)) {
783 LogLine ("KNOWN ISSUE (No error reported)");
784 no_error_list.Remove (file);
788 if (know_issues.Contains (file)) {
789 LogLine ("REGRESSION (WRONG ERROR -> NO ERROR)");
790 know_issues.Remove (file);
793 LogLine ("REGRESSION (CORRECT ERROR -> NO ERROR)");
798 case CompilerError.MissingLocation:
799 if (know_issues.Contains (file)) {
800 LogLine ("KNOWN ISSUE (Missing error location)");
801 know_issues.Remove (file);
804 if (no_error_list.Contains (file)) {
805 LogLine ("REGRESSION (NO ERROR -> MISSING ERROR LOCATION)");
806 no_error_list.Remove (file);
809 LogLine ("REGRESSION (CORRECT ERROR -> MISSING ERROR LOCATION)");
813 case CompilerError.Duplicate:
814 // Will become an error soon
815 LogLine("WARNING: EXACTLY SAME ERROR HAS BEEN ISSUED MULTIPLE TIMES");
819 regression.Add (file);
823 public override void PrintSummary()
825 base.PrintSummary ();
827 if (wrong_warning.Count > 0) {
829 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)");
831 foreach (DictionaryEntry de in wrong_warning)
832 LogLine ("CS{0:0000} : {1}", de.Key, (bool)de.Value ? "incorrect warning definition" : "missing warning definition");
840 static int Main(string[] args) {
841 if (args.Length != 5) {
842 Console.Error.WriteLine ("Usage: TestRunner [negative|positive] test-pattern compiler know-issues log-file");
846 string mode = args[0].ToLower ();
848 string test_pattern = args [1] == "0" ? "*cs*.cs" : "*test-*.cs"; //args [1];
850 string test_pattern = args [1] == "0" ? "cs*.cs" : "test-*.cs"; //args [1];
852 string mcs = args [2];
853 string issue_file = args [3];
854 string log_fname = args [4];
856 string[] files = Directory.GetFiles (".", test_pattern);
860 Console.WriteLine ("Loading: " + mcs);
861 tester = new ReflectionTester (Assembly.LoadFile (mcs));
864 Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
865 if (!File.Exists (mcs)) {
866 Console.Error.WriteLine ("ERROR: Tested compiler was not found");
869 tester = new ProcessTester (mcs);
875 checker = new NegativeChecker (tester, log_fname, issue_file, true);
878 checker = new PositiveChecker (tester, log_fname, issue_file);
881 Console.Error.WriteLine ("You must specify testing mode (positive or negative)");
885 foreach (string s in files) {
886 string filename = Path.GetFileName (s);
887 if (Char.IsUpper (filename, 0)) { // Windows hack
891 if (filename.EndsWith ("-p2.cs"))
894 checker.Do (filename);
897 checker.PrintSummary ();
901 return checker.ResultCode;