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");
28 Console.Error.WriteLine ("null, huh?");
30 ep = t.GetMethod ("InvokeCompiler",
31 BindingFlags.Static | BindingFlags.Public);
33 throw new MissingMethodException ("static InvokeCompiler");
34 method_arg = new object [2];
36 PropertyInfo pi = t.GetProperty ("AllWarningNumbers");
37 all_warnings = (int[])pi.GetValue (null, null);
38 Array.Sort (all_warnings);
41 public string Output {
43 return output.GetStringBuilder ().ToString ();
47 public bool Invoke(string[] args)
49 output = new StringWriter ();
50 method_arg [0] = args;
51 method_arg [1] = output;
52 return (bool)ep.Invoke (null, method_arg);
55 public bool IsWarning (int warningNumber)
57 return Array.BinarySearch (all_warnings, warningNumber) >= 0;
62 class ProcessTester: ITester
67 public ProcessTester (string p_path)
69 pi = new ProcessStartInfo ();
71 pi.CreateNoWindow = true;
72 pi.WindowStyle = ProcessWindowStyle.Hidden;
73 pi.RedirectStandardOutput = true;
74 pi.RedirectStandardError = true;
75 pi.UseShellExecute = false;
78 public string Output {
84 public bool Invoke(string[] args)
86 StringBuilder sb = new StringBuilder ("/nologo ");
87 foreach (string s in args) {
91 pi.Arguments = sb.ToString ();
92 Process p = Process.Start (pi);
93 output = p.StandardError.ReadToEnd ();
94 if (output.Length == 0)
95 output = p.StandardOutput.ReadToEnd ();
97 return p.ExitCode == 0;
100 public bool IsWarning (int warningNumber)
102 throw new NotImplementedException ();
109 public readonly string FileName;
110 public readonly string[] CompilerOptions;
111 public readonly string[] Dependencies;
113 public TestCase (string filename, string[] options, string[] deps)
115 this.FileName = filename;
116 this.CompilerOptions = options;
117 this.Dependencies = deps;
121 class Checker: IDisposable
123 protected ITester tester;
124 protected int success;
126 protected int ignored;
127 protected int syntax_errors;
129 StreamWriter log_file;
130 // protected string[] compiler_options;
131 // protected string[] dependencies;
133 protected ArrayList tests = new ArrayList ();
134 protected Hashtable test_hash = new Hashtable ();
135 protected ArrayList regression = new ArrayList ();
136 protected ArrayList know_issues = new ArrayList ();
137 protected ArrayList ignore_list = new ArrayList ();
138 protected ArrayList no_error_list = new ArrayList ();
140 protected bool verbose; // = true;
142 int total_known_issues;
144 protected Checker (ITester tester, string log_file, string issue_file)
146 this.tester = tester;
147 this.issue_file = issue_file;
148 ReadWrongErrors (issue_file);
149 this.log_file = new StreamWriter (log_file, false);
152 protected virtual bool GetExtraOptions (string file, out string[] compiler_options,
153 out string[] dependencies)
156 compiler_options = null;
159 using (StreamReader sr = new StreamReader (file)) {
161 while (row++ < 3 && (line = sr.ReadLine()) != null) {
162 if (!AnalyzeTestFile (file, ref row, line, ref compiler_options,
173 protected virtual bool AnalyzeTestFile (string file, ref int row, string line,
174 ref string[] compiler_options,
175 ref string[] dependencies)
177 const string options = "// Compiler options:";
178 const string depends = "// Dependencies:";
181 compiler_options = null;
185 int index = line.IndexOf (options);
187 compiler_options = line.Substring (index + options.Length).Trim().Split (' ');
188 for (int i = 0; i < compiler_options.Length; i++)
189 compiler_options[i] = compiler_options[i].TrimStart ();
191 index = line.IndexOf (depends);
193 dependencies = line.Substring (index + depends.Length).Trim().Split (' ');
194 for (int i = 0; i < dependencies.Length; i++)
195 dependencies[i] = dependencies[i].TrimStart ();
201 public bool Do (string filename)
203 if (test_hash.Contains (filename))
206 if (ignore_list.Contains (filename)) {
208 LogFileLine (filename, "NOT TESTED");
212 string[] compiler_options, dependencies;
213 if (!GetExtraOptions (filename, out compiler_options, out dependencies)) {
214 LogFileLine (filename, "ERROR");
218 TestCase test = new TestCase (filename, compiler_options, dependencies);
219 test_hash.Add (filename, test);
222 if (dependencies != null) {
223 foreach (string dependency in dependencies) {
224 if (!Do (dependency)) {
225 LogFileLine (filename, "DEPENDENCY FAILED");
236 protected virtual bool Check (TestCase test)
240 if (test.CompilerOptions != null) {
241 test_args = new string [1 + test.CompilerOptions.Length];
242 test.CompilerOptions.CopyTo (test_args, 0);
244 test_args = new string [1];
246 test_args [test_args.Length - 1] = test.FileName;
248 return tester.Invoke (test_args);
252 void ReadWrongErrors (string file)
254 const string ignored = "IGNORE";
255 const string no_error = "NO ERROR";
257 using (StreamReader sr = new StreamReader (file)) {
259 while ((line = sr.ReadLine()) != null) {
260 if (line.StartsWith ("#"))
263 ArrayList active_cont = know_issues;
265 if (line.IndexOf (ignored) > 0)
266 active_cont = ignore_list;
267 else if (line.IndexOf (no_error) > 0)
268 active_cont = no_error_list;
270 string file_name = line.Split (' ')[0];
271 if (file_name.Length == 0)
274 active_cont.Add (file_name);
277 total_known_issues = know_issues.Count;
280 public virtual void PrintSummary ()
282 LogLine ("Done" + Environment.NewLine);
283 LogLine ("{0} test cases passed ({1:0.##%})", success, (float) (success) / (float)total);
285 if (syntax_errors > 0)
286 LogLine ("{0} test(s) ignored because of wrong syntax !", syntax_errors);
289 LogLine ("{0} test(s) ignored", ignored);
291 if (total_known_issues - know_issues.Count > 0)
292 LogLine ("{0} known issue(s)", total_known_issues - know_issues.Count);
294 know_issues.AddRange (no_error_list);
295 if (know_issues.Count > 0) {
297 LogLine (issue_file + " contains {0} already fixed issues. Please remove", know_issues.Count);
298 foreach (string s in know_issues)
301 if (regression.Count > 0) {
303 LogLine ("The latest changes caused regression in {0} file(s)", regression.Count);
304 foreach (string s in regression)
309 public int ResultCode
312 return regression.Count == 0 ? 0 : 1;
316 protected void Log (string msg, params object [] rest)
318 Console.Write (msg, rest);
319 log_file.Write (msg, rest);
322 protected void LogLine (string msg, params object [] rest)
324 Console.WriteLine (msg, rest);
325 log_file.WriteLine (msg, rest);
328 protected void LogFileLine (string file, string msg, params object [] args)
330 string s = file + "...\t" + string.Format (msg, args);
331 Console.WriteLine (s);
332 log_file.WriteLine (s);
335 #region IDisposable Members
337 public void Dispose()
345 class PositiveChecker: Checker
347 readonly string files_folder;
348 readonly static object[] default_args = new object[1] { new string[] {} };
354 readonly string mono;
356 protected enum TestResult {
364 public PositiveChecker (ITester tester, string log_file, string issue_file):
365 base (tester, log_file, issue_file)
367 files_folder = Directory.GetCurrentDirectory ();
370 pi = new ProcessStartInfo ();
371 pi.CreateNoWindow = true;
372 pi.WindowStyle = ProcessWindowStyle.Hidden;
373 pi.RedirectStandardOutput = true;
374 pi.RedirectStandardError = true;
375 pi.UseShellExecute = false;
377 mono = Environment.GetEnvironmentVariable ("MONO_RUNTIME");
384 protected override bool GetExtraOptions(string file, out string[] compiler_options,
385 out string[] dependencies) {
386 if (!base.GetExtraOptions (file, out compiler_options, out dependencies))
390 if (compiler_options == null)
393 foreach (string one_opt in compiler_options) {
394 if (one_opt.StartsWith ("-doc:")) {
395 doc_output = one_opt.Split (':', '/')[1];
401 protected override bool Check(TestCase test)
403 string filename = test.FileName;
405 if (!base.Check (test)) {
406 HandleFailure (filename, TestResult.CompileError, tester.Output);
410 catch (Exception e) {
411 if (e.InnerException != null)
412 e = e.InnerException;
414 HandleFailure (filename, TestResult.CompileError, e.ToString ());
419 if (filename.EndsWith ("-lib.cs") || filename.EndsWith ("-mod.cs")) {
421 LogFileLine (filename, "OK");
426 MethodInfo mi = null;
427 string file = Path.Combine (files_folder, Path.GetFileNameWithoutExtension (filename) + ".exe");
429 // Enable .dll only tests (no execution required)
430 if (!File.Exists(file)) {
431 HandleFailure (filename, TestResult.Success, null);
436 mi = Assembly.LoadFile (file).EntryPoint;
438 catch (FileNotFoundException) {
439 if (File.Exists (file)) {
440 Console.WriteLine ("APPDOMAIN LIMIT REACHED");
443 catch (Exception e) {
444 HandleFailure (filename, TestResult.LoadError, e.ToString ());
448 if (!ExecuteFile (mi, file, filename))
451 if (doc_output != null) {
452 string ref_file = filename.Replace (".cs", "-ref.xml");
455 XmlComparer.Compare (ref_file, doc_output);
458 catch (Exception e) {
459 HandleFailure (filename, TestResult.XmlError, e.Message);
464 HandleFailure (filename, TestResult.Success, null);
469 int ExecFile (string exe_name, string filename)
472 pi.FileName = exe_name;
474 pi.Arguments = exe_name;
476 Process p = Process.Start (pi);
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) {
502 int exit_code = ExecFile (exe_name, filename);
503 if (exit_code == 0) {
504 LogLine ("(appdomain method failed, external executable succeeded)");
505 LogLine (e.ToString ());
509 HandleFailure (filename, TestResult.ExecError, e.ToString ());
513 if (result is int && (int)result != 0) {
514 HandleFailure (filename, TestResult.ExecError, "Wrong return code: " + result.ToString ());
520 void HandleFailure (string file, TestResult status, string extra)
523 case TestResult.Success:
525 if (know_issues.Contains (file)) {
526 LogFileLine (file, "FIXED ISSUE");
530 LogFileLine (file, "OK");
533 case TestResult.CompileError:
534 if (know_issues.Contains (file)) {
535 LogFileLine (file, "KNOWN ISSUE (Compilation error)");
536 know_issues.Remove (file);
539 LogFileLine (file, "REGRESSION (SUCCESS -> COMPILATION ERROR)");
542 case TestResult.ExecError:
543 if (know_issues.Contains (file)) {
544 LogFileLine (file, "KNOWN ISSUE (Execution error)");
545 know_issues.Remove (file);
548 LogFileLine (file, "REGRESSION (SUCCESS -> EXECUTION ERROR)");
551 case TestResult.XmlError:
552 if (know_issues.Contains (file)) {
553 LogFileLine (file, "KNOWN ISSUE (Xml comparision error)");
554 know_issues.Remove (file);
557 LogFileLine (file, "REGRESSION (SUCCESS -> DOCUMENTATION ERROR)");
560 case TestResult.LoadError:
561 LogFileLine (file, "REGRESSION (SUCCESS -> LOAD ERROR)");
566 LogLine ("{0}", extra);
568 regression.Add (file);
572 class NegativeChecker: Checker
574 string expected_message;
575 string error_message;
577 bool check_error_line;
579 IDictionary wrong_warning;
581 protected enum CompilerError {
590 public NegativeChecker (ITester tester, string log_file, string issue_file, bool check_msg):
591 base (tester, log_file, issue_file)
593 this.check_msg = check_msg;
594 wrong_warning = new Hashtable ();
597 protected override bool AnalyzeTestFile (string file, ref int row, string line,
598 ref string[] compiler_options,
599 ref string[] dependencies)
602 expected_message = null;
604 int index = line.IndexOf (':');
605 if (index == -1 || index > 15) {
606 LogFileLine (file, "IGNORING: Wrong test file syntax (missing error mesage text)");
608 base.AnalyzeTestFile (file, ref row, line, ref compiler_options,
613 expected_message = line.Substring (index + 1).Trim ();
617 string filtered = line.Replace(" ", "");
619 // Some error tests require to have different error text for different runtimes.
620 if (filtered.StartsWith ("//GMCS")) {
625 return AnalyzeTestFile(file, ref row, line, ref compiler_options, ref dependencies);
629 check_error_line = !filtered.StartsWith ("//Line:0");
631 if (!filtered.StartsWith ("//Line:")) {
632 LogFileLine (file, "IGNORING: Wrong test syntax (following line after an error messsage must have `// Line: xx' syntax");
638 if (!base.AnalyzeTestFile (file, ref row, line, ref compiler_options, ref dependencies))
642 if (compiler_options != null) {
643 foreach (string s in compiler_options) {
644 if (s.EndsWith ("warnaserror"))
652 protected override bool Check (TestCase test)
654 string filename = test.FileName;
657 while (Char.IsLetter (filename, start_char))
660 int end_char = filename.IndexOfAny (new char [] { '-', '.' } );
661 string expected = filename.Substring (start_char, end_char - start_char);
664 if (base.Check (test)) {
665 HandleFailure (filename, CompilerError.Missing);
669 catch (Exception e) {
670 HandleFailure (filename, CompilerError.Missing);
671 if (e.InnerException != null)
672 e = e.InnerException;
678 int err_id = int.Parse (expected, System.Globalization.CultureInfo.InvariantCulture);
679 if (tester.IsWarning (err_id)) {
681 wrong_warning [err_id] = true;
684 wrong_warning [err_id] = false;
687 CompilerError result_code = GetCompilerError (expected, tester.Output);
688 if (HandleFailure (filename, result_code)) {
693 if (result_code == CompilerError.Wrong)
694 LogLine (tester.Output);
699 CompilerError GetCompilerError (string expected, string buffer)
701 const string error_prefix = "CS";
702 const string ignored_error = "error CS5001";
703 string tested_text = "error " + error_prefix + expected;
704 StringReader sr = new StringReader (buffer);
705 string line = sr.ReadLine ();
706 ArrayList ld = new ArrayList ();
707 CompilerError result = CompilerError.Missing;
708 while (line != null) {
709 if (ld.Contains (line)) {
710 if (line.IndexOf ("Location of the symbol related to previous") == -1)
711 return CompilerError.Duplicate;
715 if (result != CompilerError.Expected) {
716 if (line.IndexOf (tested_text) != -1) {
718 int first = line.IndexOf (':');
719 int second = line.IndexOf (':', first + 1);
720 if (second == -1 || !check_error_line)
723 string msg = line.Substring (second + 1).TrimEnd ('.').Trim ();
724 if (msg != expected_message && msg != expected_message.Replace ('`', '\'')) {
726 return CompilerError.WrongMessage;
729 if (check_error_line && line.IndexOf (".cs(") == -1)
730 return CompilerError.MissingLocation;
732 result = CompilerError.Expected;
733 } else if (line.IndexOf (error_prefix) != -1 &&
734 line.IndexOf (ignored_error) == -1)
735 result = CompilerError.Wrong;
738 line = sr.ReadLine ();
744 bool HandleFailure (string file, CompilerError status)
747 case CompilerError.Expected:
748 if (know_issues.Contains (file) || no_error_list.Contains (file)) {
749 LogFileLine (file, "FIXED ISSUE");
754 LogFileLine (file, "OK");
757 case CompilerError.Wrong:
758 if (know_issues.Contains (file)) {
759 LogFileLine (file, "KNOWN ISSUE (Wrong error reported)");
760 know_issues.Remove (file);
763 if (no_error_list.Contains (file)) {
764 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR CODE)");
765 no_error_list.Remove (file);
768 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR CODE)");
772 case CompilerError.WrongMessage:
773 if (know_issues.Contains (file)) {
774 LogFileLine (file, "KNOWN ISSUE (Wrong error message reported)");
775 know_issues.Remove (file);
778 if (no_error_list.Contains (file)) {
779 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR MESSAGE)");
780 no_error_list.Remove (file);
783 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR MESSAGE)");
784 LogLine ("Exp: {0}", expected_message);
785 LogLine ("Was: {0}", error_message);
789 case CompilerError.Missing:
790 if (no_error_list.Contains (file)) {
791 LogFileLine (file, "KNOWN ISSUE (No error reported)");
792 no_error_list.Remove (file);
796 if (know_issues.Contains (file)) {
797 LogFileLine (file, "REGRESSION (WRONG ERROR -> NO ERROR)");
798 know_issues.Remove (file);
801 LogFileLine (file, "REGRESSION (CORRECT ERROR -> NO ERROR)");
806 case CompilerError.MissingLocation:
807 if (know_issues.Contains (file)) {
808 LogFileLine (file, "KNOWN ISSUE (Missing error location)");
809 know_issues.Remove (file);
812 if (no_error_list.Contains (file)) {
813 LogFileLine (file, "REGRESSION (NO ERROR -> MISSING ERROR LOCATION)");
814 no_error_list.Remove (file);
817 LogFileLine (file, "REGRESSION (CORRECT ERROR -> MISSING ERROR LOCATION)");
821 case CompilerError.Duplicate:
822 // Will become an error soon
823 LogFileLine (file, "WARNING: EXACTLY SAME ERROR HAS BEEN ISSUED MULTIPLE TIMES");
827 regression.Add (file);
831 public override void PrintSummary()
833 base.PrintSummary ();
835 if (wrong_warning.Count > 0) {
837 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)");
839 foreach (DictionaryEntry de in wrong_warning)
840 LogLine ("CS{0:0000} : {1}", de.Key, (bool)de.Value ? "incorrect warning definition" : "missing warning definition");
848 static int Main(string[] args) {
849 if (args.Length != 5) {
850 Console.Error.WriteLine ("Usage: TestRunner [negative|positive] test-pattern compiler know-issues log-file");
854 string mode = args[0].ToLower ();
856 string test_pattern = args [1] == "0" ? "*cs*.cs" : "*test-*.cs"; //args [1];
858 string test_pattern = args [1] == "0" ? "cs*.cs" : "test-*.cs"; //args [1];
860 string mcs = args [2];
861 string issue_file = args [3];
862 string log_fname = args [4];
864 string[] files = Directory.GetFiles (".", test_pattern);
868 Console.WriteLine ("Testing: " + mcs);
869 tester = new ReflectionTester (Assembly.LoadFile (mcs));
875 Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
876 if (!File.Exists (mcs)) {
877 Console.Error.WriteLine ("ERROR: Tested compiler was not found");
880 tester = new ProcessTester (mcs);
887 checker = new NegativeChecker (tester, log_fname, issue_file, true);
890 checker = new PositiveChecker (tester, log_fname, issue_file);
893 Console.Error.WriteLine ("You must specify testing mode (positive or negative)");
897 foreach (string s in files) {
898 string filename = Path.GetFileName (s);
899 if (Char.IsUpper (filename, 0)) { // Windows hack
903 if (filename.EndsWith ("-p2.cs"))
906 checker.Do (filename);
909 checker.PrintSummary ();
913 return checker.ResultCode;