3 using System.Diagnostics;
4 using System.Reflection;
6 using System.Collections;
12 string Output { get; }
13 bool Invoke (string[] args);
16 class ReflectionTester: ITester {
21 public ReflectionTester (Assembly a)
23 ep = a.GetType ("Mono.CSharp.CompilerCallableEntryPoint").GetMethod ("InvokeCompiler",
24 BindingFlags.Static | BindingFlags.Public);
26 throw new MissingMethodException ("static InvokeCompiler");
27 method_arg = new object [2];
30 public string Output {
32 return output.GetStringBuilder ().ToString ();
36 public bool Invoke(string[] args)
38 output = new StringWriter ();
39 method_arg [0] = args;
40 method_arg [1] = output;
41 return (bool)ep.Invoke (null, method_arg);
45 class ProcessTester: ITester
50 public ProcessTester (string p_path)
52 pi = new ProcessStartInfo ();
54 pi.CreateNoWindow = true;
55 pi.WindowStyle = ProcessWindowStyle.Hidden;
56 pi.RedirectStandardOutput = true;
57 pi.RedirectStandardError = true;
58 pi.UseShellExecute = false;
61 public string Output {
67 public bool Invoke(string[] args)
69 StringBuilder sb = new StringBuilder ("/nologo ");
70 foreach (string s in args) {
74 pi.Arguments = sb.ToString ();
75 Process p = Process.Start (pi);
76 output = p.StandardError.ReadToEnd ();
77 if (output.Length == 0)
78 output = p.StandardOutput.ReadToEnd ();
80 return p.ExitCode == 0;
84 class Checker: IDisposable
86 protected ITester tester;
87 protected int success;
89 protected int ignored;
91 StreamWriter log_file;
92 protected string[] compiler_options;
94 protected ArrayList regression = new ArrayList ();
95 protected ArrayList know_issues = new ArrayList ();
96 protected ArrayList ignore_list = new ArrayList ();
97 protected ArrayList no_error_list = new ArrayList ();
99 protected Checker (ITester tester, string log_file, string issue_file)
101 this.tester = tester;
102 this.issue_file = issue_file;
103 ReadWrongErrors (issue_file);
104 this.log_file = new StreamWriter (log_file, false);
107 protected virtual bool GetExtraOptions (string file)
110 using (StreamReader sr = new StreamReader (file)) {
112 while (row++ < 3 && (line = sr.ReadLine()) != null) {
113 if (!AnalyzeTestFile (row, line))
120 protected virtual bool AnalyzeTestFile (int row, string line)
122 const string options = "// Compiler options:";
125 compiler_options = null;
127 int index = line.IndexOf (options);
129 compiler_options = line.Substring (index + options.Length).Trim().Split (' ');
130 for (int i = 0; i < compiler_options.Length; i++)
131 compiler_options[i] = compiler_options[i].TrimStart ();
136 public void Do (string filename)
141 if (ignore_list.Contains (filename)) {
143 LogLine ("NOT TESTED");
147 if (!GetExtraOptions (filename)) {
155 protected virtual bool Check (string filename)
159 if (compiler_options != null) {
160 test_args = new string [1 + compiler_options.Length];
161 compiler_options.CopyTo (test_args, 0);
163 test_args = new string [1];
165 test_args [test_args.Length - 1] = filename;
167 return tester.Invoke (test_args);
171 void ReadWrongErrors (string file)
173 const string ignored = "IGNORE";
174 const string no_error = "NO ERROR";
176 using (StreamReader sr = new StreamReader (file)) {
178 while ((line = sr.ReadLine()) != null) {
179 if (line.StartsWith ("#"))
182 ArrayList active_cont = know_issues;
184 if (line.IndexOf (ignored) > 0)
185 active_cont = ignore_list;
186 else if (line.IndexOf (no_error) > 0)
187 active_cont = no_error_list;
189 string file_name = line.Split (' ')[0];
190 if (file_name.Length == 0)
193 active_cont.Add (file_name);
198 public void PrintSummary ()
200 LogLine ("Done" + Environment.NewLine);
201 LogLine ("{0} test cases passed ({1:.##%})", success, (float) (success) / (float)total);
204 LogLine ("{0} test cases ignored", ignored);
206 know_issues.AddRange (no_error_list);
207 if (know_issues.Count > 0) {
209 LogLine (issue_file + " contains {0} already fixed issues. Please remove", know_issues.Count);
210 foreach (string s in know_issues)
213 if (regression.Count > 0) {
215 LogLine ("The latest changes caused regression in {0} file(s)", regression.Count);
216 foreach (string s in regression)
221 public int ResultCode
224 return regression.Count == 0 ? 0 : 1;
228 protected void Log (string msg, params object [] rest)
230 Console.Write (msg, rest);
231 log_file.Write (msg, rest);
234 protected void LogLine (string msg, params object [] rest)
236 Console.WriteLine (msg, rest);
237 log_file.WriteLine (msg, rest);
240 #region IDisposable Members
242 public void Dispose()
250 class PositiveChecker: Checker {
251 readonly string files_folder;
252 readonly static object[] default_args = new object[1] { new string[] {} };
255 // When we cannot load assembly to domain we are automaticaly switching to process calling
256 bool appdomain_limit_reached;
259 readonly string mono;
261 // This is really pain
262 // This number is highly experimental on my box I am not able to load more than 1000 files to domain
263 // Every files is there twice and we need some space for tests which reference assemblies
264 const int MAX_TESTS_IN_DOMAIN = 420;
267 protected enum TestResult {
275 public PositiveChecker (ITester tester, string log_file, string issue_file):
276 base (tester, log_file, issue_file)
278 files_folder = Directory.GetCurrentDirectory ();
280 pi = new ProcessStartInfo ();
281 pi.CreateNoWindow = true;
282 pi.WindowStyle = ProcessWindowStyle.Hidden;
283 pi.RedirectStandardOutput = true;
284 pi.RedirectStandardError = true;
285 pi.UseShellExecute = false;
287 mono = Environment.GetEnvironmentVariable ("MONO_RUNTIME");
293 protected override bool GetExtraOptions(string file) {
294 if (!base.GetExtraOptions (file))
298 if (compiler_options == null)
301 foreach (string one_opt in compiler_options) {
302 if (one_opt.StartsWith ("-doc:")) {
303 doc_output = one_opt.Split (':')[1];
309 protected override bool Check(string filename) {
311 if (!base.Check (filename)) {
312 HandleFailure (filename, TestResult.CompileError, tester.Output);
316 catch (Exception e) {
317 HandleFailure (filename, TestResult.CompileError, e.ToString ());
322 if (filename.EndsWith ("-lib.cs") || filename.EndsWith ("-mod.cs")) {
328 MethodInfo mi = null;
329 string file = Path.Combine (files_folder, Path.GetFileNameWithoutExtension (filename) + ".exe");
330 if (!appdomain_limit_reached) {
332 mi = Assembly.LoadFile (file).EntryPoint;
333 if (test_counter++ > MAX_TESTS_IN_DOMAIN)
334 appdomain_limit_reached = true;
336 catch (FileNotFoundException) {
337 if (File.Exists (file)) {
338 Console.WriteLine ("APPDOMAIN LIMIT REACHED");
339 appdomain_limit_reached = true;
342 catch (Exception e) {
343 HandleFailure (filename, TestResult.LoadError, e.ToString ());
348 if (appdomain_limit_reached) {
349 if (!ExecuteFile (file, filename))
352 if (!ExecuteFile (mi, filename))
356 if (doc_output != null) {
357 string ref_file = filename.Replace (".cs", "-ref.xml");
359 XmlComparer.Compare (ref_file, doc_output);
361 catch (Exception e) {
362 HandleFailure (filename, TestResult.XmlError, e.Message);
367 HandleFailure (filename, TestResult.Success, null);
371 bool ExecuteFile (string exe_name, string filename)
374 pi.FileName = exe_name;
376 pi.Arguments = exe_name;
378 Process p = Process.Start (pi);
381 // TODO: How can I recognize return type void ?
385 HandleFailure (filename, TestResult.ExecError, "Wrong return code: " + p.ExitCode.ToString ());
389 bool ExecuteFile (MethodInfo entry_point, string filename)
391 TextWriter standart_ouput = Console.Out;
392 TextWriter standart_error = Console.Error;
393 Console.SetOut (TextWriter.Null);
394 Console.SetError (TextWriter.Null);
395 ParameterInfo[] pi = entry_point.GetParameters ();
396 object[] args = pi.Length == 0 ? null : default_args;
398 object result = null;
400 result = entry_point.Invoke (null, args);
401 Console.SetOut (standart_ouput);
402 Console.SetError (standart_error);
404 catch (Exception e) {
405 Console.SetOut (standart_ouput);
406 Console.SetError (standart_error);
407 HandleFailure (filename, TestResult.ExecError, e.ToString ());
411 if (result is int && (int)result != 0) {
412 HandleFailure (filename, TestResult.ExecError, "Wrong return code: " + result.ToString ());
418 void HandleFailure (string file, TestResult status, string extra)
421 case TestResult.Success:
423 if (know_issues.Contains (file)) {
424 LogLine ("FIXED ISSUE");
430 case TestResult.CompileError:
431 if (know_issues.Contains (file)) {
432 LogLine ("KNOWN ISSUE (Compilation error)");
433 know_issues.Remove (file);
436 LogLine ("REGRESSION (SUCCESS -> COMPILATION ERROR)");
439 case TestResult.ExecError:
440 if (know_issues.Contains (file)) {
441 LogLine ("KNOWN ISSUE (Execution error)");
442 know_issues.Remove (file);
445 LogLine ("REGRESSION (SUCCESS -> EXECUTION ERROR)");
448 case TestResult.XmlError:
449 if (know_issues.Contains (file)) {
450 LogLine ("KNOWN ISSUE (Xml comparision error)");
451 know_issues.Remove (file);
454 LogLine ("REGRESSION (SUCCESS -> DOCUMENTATION ERROR)");
457 case TestResult.LoadError:
458 LogLine ("REGRESSION (SUCCESS -> LOAD ERROR)");
465 regression.Add (file);
469 class NegativeChecker: Checker
471 string expected_message;
472 string error_message;
474 protected enum CompilerError {
481 public NegativeChecker (ITester tester, string log_file, string issue_file):
482 base (tester, log_file, issue_file)
486 protected override bool AnalyzeTestFile(int row, string line)
489 expected_message = null;
491 int index = line.IndexOf (':');
492 if (index == -1 || index > 15) {
493 LogLine ("IGNORING: Wrong test file syntax (missing error mesage text)");
495 base.AnalyzeTestFile (row, line);
499 expected_message = line.Substring (index + 1).Trim ();
502 return base.AnalyzeTestFile (row, line);
506 protected override bool Check (string filename)
509 while (Char.IsLetter (filename, start_char))
512 int end_char = filename.IndexOfAny (new char [] { '-', '.' } );
513 string expected = filename.Substring (start_char, end_char - start_char);
516 if (base.Check (filename)) {
517 HandleFailure (filename, CompilerError.Missing);
521 catch (Exception e) {
522 HandleFailure (filename, CompilerError.Missing);
527 CompilerError result_code = GetCompilerError (expected, tester.Output);
528 if (HandleFailure (filename, result_code)) {
533 if (result_code == CompilerError.Wrong)
534 LogLine (tester.Output);
539 CompilerError GetCompilerError (string expected, string buffer)
541 const string error_prefix = "CS";
542 const string ignored_error = "error CS5001";
543 string tested_text = "error " + error_prefix + expected;
544 StringReader sr = new StringReader (buffer);
545 string line = sr.ReadLine ();
546 bool any_error = false;
547 while (line != null) {
549 if (line.IndexOf (tested_text) != -1) {
550 // string msg = line.Substring (line.IndexOf (':', 22) + 1).TrimEnd ('.').Trim ();
551 // if (msg != expected_message && msg != expected_message.Replace ('`', '\'')) {
552 // error_message = msg;
553 // return CompilerError.WrongMessage;
555 return CompilerError.Expected;
558 if (line.IndexOf (error_prefix) != -1 &&
559 line.IndexOf (ignored_error) == -1)
562 line = sr.ReadLine ();
565 return any_error ? CompilerError.Wrong : CompilerError.Missing;
568 bool HandleFailure (string file, CompilerError status)
571 case CompilerError.Expected:
572 if (know_issues.Contains (file) || no_error_list.Contains (file)) {
573 LogLine ("FIXED ISSUE");
579 case CompilerError.Wrong:
580 if (know_issues.Contains (file)) {
581 LogLine ("KNOWN ISSUE (Wrong error reported)");
582 know_issues.Remove (file);
585 if (no_error_list.Contains (file)) {
586 LogLine ("REGRESSION (NO ERROR -> WRONG ERROR CODE)");
587 no_error_list.Remove (file);
590 LogLine ("REGRESSION (CORRECT ERROR -> WRONG ERROR CODE)");
594 case CompilerError.WrongMessage:
595 if (know_issues.Contains (file)) {
596 LogLine ("KNOWN ISSUE (Wrong error message reported)");
597 know_issues.Remove (file);
600 if (no_error_list.Contains (file)) {
601 LogLine ("REGRESSION (NO ERROR -> WRONG ERROR MESSAGE)");
602 no_error_list.Remove (file);
605 LogLine ("REGRESSION (CORRECT ERROR -> WRONG ERROR MESSAGE)");
606 Console.WriteLine ("E: {0}", expected_message);
607 Console.WriteLine ("W: {0}", error_message);
611 case CompilerError.Missing:
612 if (no_error_list.Contains (file)) {
613 LogLine ("KNOWN ISSUE (No error reported)");
614 no_error_list.Remove (file);
618 if (know_issues.Contains (file)) {
619 LogLine ("REGRESSION (WRONG ERROR -> NO ERROR)");
620 know_issues.Remove (file);
623 LogLine ("REGRESSION (CORRECT ERROR -> NO ERROR)");
629 regression.Add (file);
636 static int Main(string[] args) {
637 if (args.Length != 5) {
638 Console.Error.WriteLine ("Usage: TestRunner [negative|positive] test-pattern compiler know-issues log-file");
642 string mode = args[0].ToLower ();
643 string test_pattern = args [1];
644 string mcs = args [2];
645 string issue_file = args [3];
646 string log_fname = args [4];
648 string[] files = Directory.GetFiles (".", test_pattern);
652 Console.WriteLine ("Loading: " + mcs);
653 tester = new ReflectionTester (Assembly.LoadFile (mcs));
656 Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
657 if (!File.Exists (mcs)) {
658 Console.Error.WriteLine ("ERROR: Tested compiler was not found");
661 tester = new ProcessTester (mcs);
667 checker = new NegativeChecker (tester, log_fname, issue_file);
670 checker = new PositiveChecker (tester, log_fname, issue_file);
673 Console.Error.WriteLine ("You must specify testing mode (positive or negative)");
677 foreach (string s in files) {
678 string filename = Path.GetFileName (s);
679 if (Char.IsUpper (filename, 0)) { // Windows hack
683 if (filename.EndsWith ("-p2.cs"))
686 checker.Do (filename);
689 checker.PrintSummary ();
693 return checker.ResultCode;