D'oh, forgot to add g_hash_table_steal() to glib.h
[mono-project/dkf.git] / mcs / tools / compiler-tester / compiler-tester.cs
blobcc66e8b1e5ca01971b53cc74b8e8939b1fbe60d7
1 //
2 // compiler-tester.cs
3 //
4 // Author:
5 // Marek Safar (marek.safar@gmail.com)
6 //
8 //
9 // Copyright (C) 2008, 2009 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.IO;
33 using System.Diagnostics;
34 using System.Reflection;
35 using System.Text;
36 using System.Collections;
37 using System.Xml;
38 using System.Collections.Generic;
40 namespace TestRunner {
42 interface ITester
44 string Output { get; }
45 bool Invoke (string[] args);
46 bool IsWarning (int warningNumber);
49 class ReflectionTester: ITester {
50 MethodInfo ep;
51 object[] method_arg;
52 StringWriter output;
53 int[] all_warnings;
55 public ReflectionTester (Assembly a)
57 Type t = a.GetType ("Mono.CSharp.CompilerCallableEntryPoint");
59 if (t == null)
60 Console.Error.WriteLine ("null, huh?");
62 ep = t.GetMethod ("InvokeCompiler",
63 BindingFlags.Static | BindingFlags.Public);
64 if (ep == null)
65 throw new MissingMethodException ("static InvokeCompiler");
66 method_arg = new object [2];
68 PropertyInfo pi = t.GetProperty ("AllWarningNumbers");
69 all_warnings = (int[])pi.GetValue (null, null);
70 Array.Sort (all_warnings);
73 public string Output {
74 get {
75 return output.GetStringBuilder ().ToString ();
79 public bool Invoke(string[] args)
81 output = new StringWriter ();
82 method_arg [0] = args;
83 method_arg [1] = output;
84 return (bool)ep.Invoke (null, method_arg);
87 public bool IsWarning (int warningNumber)
89 return Array.BinarySearch (all_warnings, warningNumber) >= 0;
93 #if !NET_2_1
94 class ProcessTester: ITester
96 ProcessStartInfo pi;
97 string output;
99 public ProcessTester (string p_path)
101 pi = new ProcessStartInfo ();
102 pi.FileName = p_path;
103 pi.CreateNoWindow = true;
104 pi.WindowStyle = ProcessWindowStyle.Hidden;
105 pi.RedirectStandardOutput = true;
106 pi.RedirectStandardError = true;
107 pi.UseShellExecute = false;
110 public string Output {
111 get {
112 return output;
116 public bool Invoke(string[] args)
118 StringBuilder sb = new StringBuilder ("/nologo ");
119 foreach (string s in args) {
120 sb.Append (s);
121 sb.Append (" ");
123 pi.Arguments = sb.ToString ();
124 Process p = Process.Start (pi);
125 output = p.StandardError.ReadToEnd ();
126 if (output.Length == 0)
127 output = p.StandardOutput.ReadToEnd ();
128 p.WaitForExit ();
129 return p.ExitCode == 0;
132 public bool IsWarning (int warningNumber)
134 throw new NotImplementedException ();
137 #endif
139 class TestCase : MarshalByRefObject
141 public readonly string FileName;
142 public readonly string[] CompilerOptions;
143 public readonly string[] Dependencies;
145 public TestCase (string filename, string[] options, string[] deps)
147 this.FileName = filename;
148 this.CompilerOptions = options;
149 this.Dependencies = deps;
153 class PositiveTestCase : TestCase
155 public class VerificationData : MarshalByRefObject
157 public class MethodData : MarshalByRefObject
159 public MethodData (MethodBase mi, int il_size)
161 this.Type = mi.DeclaringType.ToString ();
162 this.MethodName = mi.ToString ();
163 this.ILSize = il_size;
166 public MethodData (string type_name, string method_name, int il_size)
168 this.Type = type_name;
169 this.MethodName = method_name;
170 this.ILSize = il_size;
173 public string Type;
174 public string MethodName;
175 public int ILSize;
176 public bool Checked;
179 ArrayList methods;
180 public bool IsNewSet;
182 public VerificationData (string test_file)
184 #if NET_2_0
185 this.test_file = test_file;
186 #endif
189 #if NET_2_0
190 string test_file;
192 public static VerificationData FromFile (string name, XmlReader r)
194 VerificationData tc = new VerificationData (name);
195 ArrayList methods = new ArrayList ();
196 r.Read ();
197 while (r.ReadToNextSibling ("type")) {
198 string type_name = r ["name"];
199 r.Read ();
200 while (r.ReadToNextSibling ("method")) {
201 string m_name = r ["name"];
203 r.ReadToDescendant ("size");
204 int il_size = r.ReadElementContentAsInt ();
205 methods.Add (new MethodData (type_name, m_name, il_size));
206 r.Read ();
208 r.Read ();
211 tc.methods = methods;
212 return tc;
215 public void WriteCodeInfoTo (XmlWriter w)
217 w.WriteStartElement ("test");
218 w.WriteAttributeString ("name", test_file);
220 string type = null;
221 foreach (MethodData data in methods) {
222 if (!data.Checked)
223 continue;
225 if (type != data.Type) {
226 if (type != null)
227 w.WriteEndElement ();
229 type = data.Type;
230 w.WriteStartElement ("type");
231 w.WriteAttributeString ("name", type);
234 w.WriteStartElement ("method");
235 w.WriteAttributeString ("name", data.MethodName);
236 w.WriteStartElement ("size");
237 w.WriteValue (data.ILSize);
238 w.WriteEndElement ();
239 w.WriteEndElement ();
242 if (type != null)
243 w.WriteEndElement ();
245 w.WriteEndElement ();
247 #endif
249 public MethodData FindMethodData (string method_name, string declaring_type)
251 if (methods == null)
252 return null;
254 foreach (MethodData md in methods) {
255 if (md.MethodName == method_name && md.Type == declaring_type)
256 return md;
259 return null;
262 public void AddNewMethod (MethodBase mb, int il_size)
264 if (methods == null)
265 methods = new ArrayList ();
267 MethodData md = new MethodData (mb, il_size);
268 md.Checked = true;
269 methods.Add (md);
273 VerificationData verif_data;
275 public PositiveTestCase (string filename, string [] options, string [] deps)
276 : base (filename, options, deps)
280 public void CreateNewTest ()
282 verif_data = new VerificationData (FileName);
283 verif_data.IsNewSet = true;
286 public VerificationData VerificationProvider {
287 set {
288 verif_data = value;
290 get {
291 return verif_data;
296 class Checker: MarshalByRefObject, IDisposable
298 protected ITester tester;
299 protected int success;
300 protected int total;
301 protected int ignored;
302 protected int syntax_errors;
303 string issue_file;
304 StreamWriter log_file;
305 protected string[] extra_compiler_options;
306 // protected string[] compiler_options;
307 // protected string[] dependencies;
309 protected ArrayList tests = new ArrayList ();
310 protected Hashtable test_hash = new Hashtable ();
311 protected ArrayList regression = new ArrayList ();
312 protected ArrayList know_issues = new ArrayList ();
313 protected ArrayList ignore_list = new ArrayList ();
314 protected ArrayList no_error_list = new ArrayList ();
316 protected bool verbose;
317 protected bool safe_execution;
319 int total_known_issues;
321 protected Checker (ITester tester)
323 this.tester = tester;
326 public string IssueFile {
327 set {
328 this.issue_file = value;
329 ReadWrongErrors (issue_file);
333 public string LogFile {
334 set {
335 this.log_file = new StreamWriter (value, false);
339 public bool Verbose {
340 set {
341 verbose = value;
345 public bool SafeExecution {
346 set {
347 safe_execution = value;
351 public string[] ExtraCompilerOptions {
352 set {
353 extra_compiler_options = value;
357 protected virtual bool GetExtraOptions (string file, out string[] compiler_options,
358 out string[] dependencies)
360 int row = 0;
361 compiler_options = null;
362 dependencies = null;
363 try {
364 using (StreamReader sr = new StreamReader (file)) {
365 String line;
366 while (row++ < 3 && (line = sr.ReadLine()) != null) {
367 if (!AnalyzeTestFile (file, ref row, line, ref compiler_options,
368 ref dependencies))
369 return false;
372 } catch {
373 return false;
375 return true;
378 protected virtual bool AnalyzeTestFile (string file, ref int row, string line,
379 ref string[] compiler_options,
380 ref string[] dependencies)
382 const string options = "// Compiler options:";
383 const string depends = "// Dependencies:";
385 if (row == 1) {
386 compiler_options = null;
387 dependencies = null;
390 int index = line.IndexOf (options);
391 if (index != -1) {
392 compiler_options = line.Substring (index + options.Length).Trim().Split (' ');
393 for (int i = 0; i < compiler_options.Length; i++)
394 compiler_options[i] = compiler_options[i].TrimStart ();
396 index = line.IndexOf (depends);
397 if (index != -1) {
398 dependencies = line.Substring (index + depends.Length).Trim().Split (' ');
399 for (int i = 0; i < dependencies.Length; i++)
400 dependencies[i] = dependencies[i].TrimStart ();
403 return true;
406 public bool Do (string filename)
408 if (test_hash.Contains (filename))
409 return true;
411 if (verbose)
412 Log (filename + "...\t");
414 if (ignore_list.Contains (filename)) {
415 ++ignored;
416 LogFileLine (filename, "NOT TESTED");
417 return false;
420 string[] compiler_options, dependencies;
421 if (!GetExtraOptions (filename, out compiler_options, out dependencies)) {
422 LogFileLine (filename, "ERROR");
423 return false;
426 if (extra_compiler_options != null) {
427 if (compiler_options == null)
428 compiler_options = extra_compiler_options;
429 else {
430 string[] new_options = new string [compiler_options.Length + extra_compiler_options.Length];
431 extra_compiler_options.CopyTo (new_options, 0);
432 compiler_options.CopyTo (new_options, extra_compiler_options.Length);
433 compiler_options = new_options;
437 TestCase test = CreateTestCase (filename, compiler_options, dependencies);
438 test_hash.Add (filename, test);
440 ++total;
441 if (dependencies != null) {
442 foreach (string dependency in dependencies) {
443 if (!Do (dependency)) {
444 LogFileLine (filename, "DEPENDENCY FAILED");
445 return false;
450 tests.Add (test);
452 return Check (test);
455 protected virtual bool Check (TestCase test)
457 string[] test_args;
459 if (test.CompilerOptions != null) {
460 test_args = new string [2 + test.CompilerOptions.Length];
461 test.CompilerOptions.CopyTo (test_args, 0);
462 } else {
463 test_args = new string [2];
465 test_args [test_args.Length - 2] = test.FileName;
466 test_args [test_args.Length - 1] = "-debug";
468 return tester.Invoke (test_args);
471 protected virtual TestCase CreateTestCase (string filename, string [] options, string [] deps)
473 return new TestCase (filename, options, deps);
476 void ReadWrongErrors (string file)
478 const string ignored = "IGNORE";
479 const string no_error = "NO ERROR";
481 using (StreamReader sr = new StreamReader (file)) {
482 string line;
483 while ((line = sr.ReadLine()) != null) {
484 if (line.StartsWith ("#"))
485 continue;
487 ArrayList active_cont = know_issues;
489 if (line.IndexOf (ignored) > 0)
490 active_cont = ignore_list;
491 else if (line.IndexOf (no_error) > 0)
492 active_cont = no_error_list;
494 string file_name = line.Split (' ')[0];
495 if (file_name.Length == 0)
496 continue;
498 active_cont.Add (file_name);
501 total_known_issues = know_issues.Count;
504 protected virtual void PrintSummary ()
506 LogLine ("Done" + Environment.NewLine);
507 float rate = 0;
508 if (total > 0)
509 rate = (float) (success) / (float)total;
510 LogLine ("{0} test cases passed ({1:0.##%})", success, rate);
512 if (syntax_errors > 0)
513 LogLine ("{0} test(s) ignored because of wrong syntax !", syntax_errors);
515 if (ignored > 0)
516 LogLine ("{0} test(s) ignored", ignored);
518 if (total_known_issues - know_issues.Count > 0)
519 LogLine ("{0} known issue(s)", total_known_issues - know_issues.Count);
521 know_issues.AddRange (no_error_list);
522 if (know_issues.Count > 0) {
523 LogLine ("");
524 LogLine (issue_file + " contains {0} already fixed issues. Please remove", know_issues.Count);
525 foreach (string s in know_issues)
526 LogLine (s);
528 if (regression.Count > 0) {
529 LogLine ("");
530 LogLine ("The latest changes caused regression in {0} file(s)", regression.Count);
531 foreach (string s in regression)
532 LogLine (s);
536 public int ResultCode
538 get {
539 return regression.Count == 0 ? 0 : 1;
543 protected void Log (string msg, params object [] rest)
545 Console.Write (msg, rest);
546 if (log_file != null)
547 log_file.Write (msg, rest);
550 protected void LogLine (string msg)
552 Console.WriteLine (msg);
553 if (log_file != null)
554 log_file.WriteLine (msg);
557 protected void LogLine (string msg, params object [] rest)
559 Console.WriteLine (msg, rest);
560 if (log_file != null)
561 log_file.WriteLine (msg, rest);
564 public void LogFileLine (string file, string msg, params object [] args)
566 string s = verbose ?
567 string.Format (msg, args) :
568 file + "...\t" + string.Format (msg, args);
570 Console.WriteLine (s);
571 if (log_file != null)
572 log_file.WriteLine (s);
575 #region IDisposable Members
577 public void Dispose()
579 if (log_file != null)
580 log_file.Close ();
583 #endregion
585 public virtual void Initialize ()
589 public virtual void CleanUp ()
591 PrintSummary ();
595 class PositiveChecker: Checker
597 readonly string files_folder;
598 readonly static object[] default_args = new object[1] { new string[] {} };
599 string doc_output;
600 string verif_file;
601 bool update_verif_file;
602 Hashtable verif_data;
604 #if !NET_2_1
605 ProcessStartInfo pi;
606 #endif
607 readonly string mono;
609 public enum TestResult {
610 CompileError,
611 ExecError,
612 LoadError,
613 XmlError,
614 Success,
615 ILError
618 public PositiveChecker (ITester tester, string verif_file):
619 base (tester)
621 files_folder = Directory.GetCurrentDirectory ();
622 this.verif_file = verif_file;
624 #if !NET_2_1
625 pi = new ProcessStartInfo ();
626 pi.CreateNoWindow = true;
627 pi.WindowStyle = ProcessWindowStyle.Hidden;
628 pi.RedirectStandardOutput = true;
629 pi.RedirectStandardError = true;
630 pi.UseShellExecute = false;
632 mono = Environment.GetEnvironmentVariable ("MONO_RUNTIME");
633 if (mono != null) {
634 pi.FileName = mono;
636 #endif
639 public bool UpdateVerificationDataFile {
640 set {
641 update_verif_file = value;
643 get {
644 return update_verif_file;
648 protected override bool GetExtraOptions(string file, out string[] compiler_options,
649 out string[] dependencies) {
650 if (!base.GetExtraOptions (file, out compiler_options, out dependencies))
651 return false;
653 doc_output = null;
654 if (compiler_options == null)
655 return true;
657 foreach (string one_opt in compiler_options) {
658 if (one_opt.StartsWith ("-doc:")) {
659 doc_output = one_opt.Split (':', '/')[1];
662 return true;
665 class DomainTester : MarshalByRefObject
667 public bool CheckILSize (PositiveTestCase test, PositiveChecker checker, string file)
669 Assembly assembly = Assembly.LoadFile (file);
671 bool success = true;
672 Type[] types = assembly.GetTypes ();
673 foreach (Type t in types) {
675 // Skip interfaces
676 if (!t.IsClass && !t.IsValueType)
677 continue;
679 if (test.VerificationProvider == null) {
680 if (!checker.UpdateVerificationDataFile)
681 checker.LogFileLine (test.FileName, "Missing IL verification data");
682 test.CreateNewTest ();
685 foreach (MemberInfo m in t.GetMembers (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
686 MethodBase mi = m as MethodBase;
687 if (mi == null)
688 continue;
690 if ((mi.Attributes & (MethodAttributes.PinvokeImpl)) != 0)
691 continue;
693 success &= CompareIL (mi, test, checker);
697 return success;
700 bool CompareIL (MethodBase mi, PositiveTestCase test, PositiveChecker checker)
702 string m_name = mi.ToString ();
703 string decl_type = mi.DeclaringType.ToString ();
704 PositiveTestCase.VerificationData data_provider = test.VerificationProvider;
706 PositiveTestCase.VerificationData.MethodData md = data_provider.FindMethodData (m_name, decl_type);
707 if (md == null) {
708 data_provider.AddNewMethod (mi, GetILSize (mi));
709 if (!data_provider.IsNewSet) {
710 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError, decl_type + ": " + m_name + " (new method?)");
711 return false;
714 return true;
717 if (md.Checked) {
718 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError, decl_type + ": " + m_name + " has a duplicate");
719 return false;
722 md.Checked = true;
724 int il_size = GetILSize (mi);
725 if (md.ILSize == il_size)
726 return true;
728 if (md.ILSize > il_size) {
729 checker.LogFileLine (test.FileName, "{0} (code size reduction {1} -> {2})", m_name, md.ILSize, il_size);
730 md.ILSize = il_size;
731 return true;
734 checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError,
735 string.Format ("{0} (code size {1} -> {2})", m_name, md.ILSize, il_size));
737 md.ILSize = il_size;
739 return false;
742 static int GetILSize (MethodBase mi)
744 #if NET_2_0
745 MethodBody body = mi.GetMethodBody ();
746 if (body != null)
747 return body.GetILAsByteArray ().Length;
748 #endif
749 return 0;
752 bool ExecuteFile (MethodInfo entry_point, string filename)
754 TextWriter stdout = Console.Out;
755 TextWriter stderr = Console.Error;
756 Console.SetOut (TextWriter.Null);
757 Console.SetError (TextWriter.Null);
758 ParameterInfo[] pi = entry_point.GetParameters ();
759 object[] args = pi.Length == 0 ? null : default_args;
761 object result = null;
762 try {
763 try {
764 result = entry_point.Invoke (null, args);
765 } finally {
766 Console.SetOut (stdout);
767 Console.SetError (stderr);
769 } catch (Exception e) {
770 throw new ApplicationException (e.ToString ());
773 if (result is int && (int) result != 0)
774 throw new ApplicationException ("Wrong return code: " + result.ToString ());
776 return true;
779 public bool Test (string file)
781 Assembly assembly = Assembly.LoadFile (file);
782 return ExecuteFile (assembly.EntryPoint, file);
786 protected override bool Check(TestCase test)
788 string filename = test.FileName;
789 try {
790 if (!base.Check (test)) {
791 HandleFailure (filename, TestResult.CompileError, tester.Output);
792 return false;
795 catch (Exception e) {
796 if (e.InnerException != null)
797 e = e.InnerException;
799 HandleFailure (filename, TestResult.CompileError, e.ToString ());
800 return false;
803 // Test setup
804 if (filename.EndsWith ("-lib.cs") || filename.EndsWith ("-mod.cs")) {
805 if (verbose)
806 LogFileLine (filename, "OK");
807 --total;
808 return true;
811 string file = Path.Combine (files_folder, Path.GetFileNameWithoutExtension (filename) + ".exe");
813 // Enable .dll only tests (no execution required)
814 if (!File.Exists(file)) {
815 HandleFailure (filename, TestResult.Success, null);
816 return true;
819 AppDomain domain = null;
820 #if !NET_2_1
821 if (safe_execution) {
822 // Create a new AppDomain, with the current directory as the base.
823 AppDomainSetup setupInfo = new AppDomainSetup ();
824 setupInfo.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
825 setupInfo.LoaderOptimization = LoaderOptimization.SingleDomain;
826 domain = AppDomain.CreateDomain (Path.GetFileNameWithoutExtension (file), null, setupInfo);
828 #endif
829 try {
830 DomainTester tester;
831 try {
832 #if !NET_2_1
833 if (domain != null)
834 tester = (DomainTester) domain.CreateInstanceAndUnwrap (typeof (PositiveChecker).Assembly.FullName, typeof (DomainTester).FullName);
835 else
836 #endif
837 tester = new DomainTester ();
839 if (!tester.Test (file))
840 return false;
842 } catch (ApplicationException e) {
843 HandleFailure (filename, TestResult.ExecError, e.Message);
844 return false;
845 } catch (Exception e) {
846 HandleFailure (filename, TestResult.LoadError, e.ToString ());
847 return false;
850 if (doc_output != null) {
851 string ref_file = filename.Replace (".cs", "-ref.xml");
852 try {
853 #if !NET_2_1
854 XmlComparer.Compare (ref_file, doc_output);
855 #endif
856 } catch (Exception e) {
857 HandleFailure (filename, TestResult.XmlError, e.Message);
858 return false;
860 } else {
861 if (verif_file != null) {
862 PositiveTestCase pt = (PositiveTestCase) test;
863 pt.VerificationProvider = (PositiveTestCase.VerificationData) verif_data[filename];
865 if (!tester.CheckILSize (pt, this, file))
866 return false;
869 } finally {
870 if (domain != null)
871 AppDomain.Unload (domain);
874 HandleFailure (filename, TestResult.Success, null);
875 return true;
878 protected override TestCase CreateTestCase (string filename, string [] options, string [] deps)
880 return new PositiveTestCase (filename, options, deps);
883 public void HandleFailure (string file, TestResult status, string extra)
885 switch (status) {
886 case TestResult.Success:
887 success++;
888 if (know_issues.Contains (file)) {
889 LogFileLine (file, "FIXED ISSUE");
890 return;
892 if (verbose)
893 LogFileLine (file, "OK");
894 return;
896 case TestResult.CompileError:
897 if (know_issues.Contains (file)) {
898 LogFileLine (file, "KNOWN ISSUE (Compilation error)");
899 know_issues.Remove (file);
900 return;
902 LogFileLine (file, "REGRESSION (SUCCESS -> COMPILATION ERROR)");
903 break;
905 case TestResult.ExecError:
906 if (know_issues.Contains (file)) {
907 LogFileLine (file, "KNOWN ISSUE (Execution error)");
908 know_issues.Remove (file);
909 return;
911 LogFileLine (file, "REGRESSION (SUCCESS -> EXECUTION ERROR)");
912 break;
914 case TestResult.XmlError:
915 if (know_issues.Contains (file)) {
916 LogFileLine (file, "KNOWN ISSUE (Xml comparision error)");
917 know_issues.Remove (file);
918 return;
920 LogFileLine (file, "REGRESSION (SUCCESS -> DOCUMENTATION ERROR)");
921 break;
923 case TestResult.LoadError:
924 LogFileLine (file, "REGRESSION (SUCCESS -> LOAD ERROR)");
925 break;
927 case TestResult.ILError:
928 if (!update_verif_file) {
929 LogFileLine (file, "IL REGRESSION: " + extra);
931 extra = null;
932 break;
935 if (extra != null)
936 LogLine ("{0}", extra);
938 if (!regression.Contains (file))
939 regression.Add (file);
942 public override void Initialize ()
944 if (verif_file != null) {
945 #if NET_2_0
946 LoadVerificationData (verif_file);
947 #else
948 throw new NotSupportedException ();
949 #endif
952 base.Initialize ();
955 public override void CleanUp ()
957 base.CleanUp ();
959 if (update_verif_file) {
960 #if NET_2_0
961 UpdateVerificationData (verif_file);
962 #else
963 throw new NotSupportedException ();
964 #endif
968 #if NET_2_0
969 void LoadVerificationData (string file)
971 LogLine ("Loading verification data from `{0}' ...", file);
973 using (XmlReader r = XmlReader.Create (file)) {
974 r.ReadStartElement ("tests");
975 verif_data = new Hashtable ();
977 while (r.Read ()) {
978 if (r.Name != "test")
979 continue;
981 string name = r.GetAttribute ("name");
982 PositiveTestCase.VerificationData tc = PositiveTestCase.VerificationData.FromFile (name, r);
983 verif_data.Add (name, tc);
988 void UpdateVerificationData (string file)
990 LogLine ("Updating verification data `{0}' ...", file);
992 XmlWriterSettings s = new XmlWriterSettings ();
993 s.Indent = true;
994 using (XmlWriter w = XmlWriter.Create (new StreamWriter (file, false, Encoding.UTF8), s)) {
995 w.WriteStartDocument ();
996 w.WriteComment ("This file contains expected IL and metadata produced by compiler for each test");
997 w.WriteStartElement ("tests");
998 foreach (PositiveTestCase tc in tests) {
999 if (tc.VerificationProvider != null)
1000 tc.VerificationProvider.WriteCodeInfoTo (w);
1002 w.WriteEndElement ();
1005 #endif
1008 class NegativeChecker: Checker
1010 string expected_message;
1011 string error_message;
1012 bool check_msg;
1013 bool check_error_line;
1014 bool is_warning;
1015 IDictionary wrong_warning;
1017 protected enum CompilerError {
1018 Expected,
1019 Wrong,
1020 Missing,
1021 WrongMessage,
1022 MissingLocation,
1023 Duplicate
1026 public NegativeChecker (ITester tester, bool check_msg):
1027 base (tester)
1029 this.check_msg = check_msg;
1030 wrong_warning = new Hashtable ();
1033 protected override bool AnalyzeTestFile (string file, ref int row, string line,
1034 ref string[] compiler_options,
1035 ref string[] dependencies)
1037 if (row == 1) {
1038 expected_message = null;
1040 int index = line.IndexOf (':');
1041 if (index == -1 || index > 15) {
1042 LogFileLine (file, "IGNORING: Wrong test file syntax (missing error mesage text)");
1043 ++syntax_errors;
1044 base.AnalyzeTestFile (file, ref row, line, ref compiler_options,
1045 ref dependencies);
1046 return false;
1049 expected_message = line.Substring (index + 1).Trim ();
1052 if (row == 2) {
1053 string filtered = line.Replace(" ", "");
1055 // Some error tests require to have different error text for different runtimes.
1056 if (filtered.StartsWith ("//GMCS")) {
1057 row = 1;
1058 #if !NET_2_0
1059 return true;
1060 #else
1061 return AnalyzeTestFile(file, ref row, line, ref compiler_options, ref dependencies);
1062 #endif
1065 check_error_line = !filtered.StartsWith ("//Line:0");
1067 if (!filtered.StartsWith ("//Line:")) {
1068 LogFileLine (file, "IGNORING: Wrong test syntax (following line after an error messsage must have `// Line: xx' syntax");
1069 ++syntax_errors;
1070 return false;
1074 if (!base.AnalyzeTestFile (file, ref row, line, ref compiler_options, ref dependencies))
1075 return false;
1077 is_warning = false;
1078 if (compiler_options != null) {
1079 foreach (string s in compiler_options) {
1080 if (s.StartsWith ("-warnaserror") || s.StartsWith ("/warnaserror"))
1081 is_warning = true;
1084 return true;
1088 protected override bool Check (TestCase test)
1090 string filename = test.FileName;
1092 int start_char = 0;
1093 while (Char.IsLetter (filename, start_char))
1094 ++start_char;
1096 int end_char = filename.IndexOfAny (new char [] { '-', '.' } );
1097 string expected = filename.Substring (start_char, end_char - start_char);
1099 try {
1100 if (base.Check (test)) {
1101 HandleFailure (filename, CompilerError.Missing);
1102 return false;
1105 catch (Exception e) {
1106 HandleFailure (filename, CompilerError.Missing);
1107 if (e.InnerException != null)
1108 e = e.InnerException;
1110 Log (e.ToString ());
1111 return false;
1114 int err_id = int.Parse (expected, System.Globalization.CultureInfo.InvariantCulture);
1115 if (tester.IsWarning (err_id)) {
1116 if (!is_warning)
1117 wrong_warning [err_id] = true;
1118 } else {
1119 if (is_warning)
1120 wrong_warning [err_id] = false;
1123 CompilerError result_code = GetCompilerError (expected, tester.Output);
1124 if (HandleFailure (filename, result_code)) {
1125 success++;
1126 return true;
1129 if (result_code == CompilerError.Wrong)
1130 LogLine (tester.Output);
1132 return false;
1135 CompilerError GetCompilerError (string expected, string buffer)
1137 const string error_prefix = "CS";
1138 const string ignored_error = "error CS5001";
1139 string tested_text = "error " + error_prefix + expected;
1140 StringReader sr = new StringReader (buffer);
1141 string line = sr.ReadLine ();
1142 ArrayList ld = new ArrayList ();
1143 CompilerError result = CompilerError.Missing;
1144 while (line != null) {
1145 if (ld.Contains (line) && result == CompilerError.Expected) {
1146 if (line.IndexOf ("Location of the symbol related to previous") == -1)
1147 return CompilerError.Duplicate;
1149 ld.Add (line);
1151 if (result != CompilerError.Expected) {
1152 if (line.IndexOf (tested_text) != -1) {
1153 if (check_msg) {
1154 int first = line.IndexOf (':');
1155 int second = line.IndexOf (':', first + 1);
1156 if (line.IndexOf ("Warning as Error: ", first, StringComparison.Ordinal) > 0) {
1157 if (check_error_line) {
1158 second = line.IndexOf (':', second + 1);
1160 } else if (second == -1 || !check_error_line) {
1161 second = first;
1164 string msg = line.Substring (second + 1).TrimEnd ('.').Trim ();
1165 if (msg != expected_message && msg != expected_message.Replace ('`', '\'')) {
1166 error_message = msg;
1167 return CompilerError.WrongMessage;
1170 if (check_error_line && line.IndexOf (".cs(") == -1)
1171 return CompilerError.MissingLocation;
1173 result = CompilerError.Expected;
1174 } else if (line.IndexOf (error_prefix) != -1 &&
1175 line.IndexOf (ignored_error) == -1)
1176 result = CompilerError.Wrong;
1179 line = sr.ReadLine ();
1182 return result;
1185 bool HandleFailure (string file, CompilerError status)
1187 switch (status) {
1188 case CompilerError.Expected:
1189 if (know_issues.Contains (file) || no_error_list.Contains (file)) {
1190 LogFileLine (file, "FIXED ISSUE");
1191 return true;
1194 if (verbose)
1195 LogFileLine (file, "OK");
1196 return true;
1198 case CompilerError.Wrong:
1199 if (know_issues.Contains (file)) {
1200 LogFileLine (file, "KNOWN ISSUE (Wrong error reported)");
1201 know_issues.Remove (file);
1202 return false;
1204 if (no_error_list.Contains (file)) {
1205 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR CODE)");
1206 no_error_list.Remove (file);
1208 else {
1209 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR CODE)");
1211 break;
1213 case CompilerError.WrongMessage:
1214 if (know_issues.Contains (file)) {
1215 LogFileLine (file, "KNOWN ISSUE (Wrong error message reported)");
1216 know_issues.Remove (file);
1217 return false;
1219 if (no_error_list.Contains (file)) {
1220 LogFileLine (file, "REGRESSION (NO ERROR -> WRONG ERROR MESSAGE)");
1221 no_error_list.Remove (file);
1223 else {
1224 LogFileLine (file, "REGRESSION (CORRECT ERROR -> WRONG ERROR MESSAGE)");
1225 LogLine ("Exp: {0}", expected_message);
1226 LogLine ("Was: {0}", error_message);
1228 break;
1230 case CompilerError.Missing:
1231 if (no_error_list.Contains (file)) {
1232 LogFileLine (file, "KNOWN ISSUE (No error reported)");
1233 no_error_list.Remove (file);
1234 return false;
1237 if (know_issues.Contains (file)) {
1238 LogFileLine (file, "REGRESSION (WRONG ERROR -> NO ERROR)");
1239 know_issues.Remove (file);
1241 else {
1242 LogFileLine (file, "REGRESSION (CORRECT ERROR -> NO ERROR)");
1245 break;
1247 case CompilerError.MissingLocation:
1248 if (know_issues.Contains (file)) {
1249 LogFileLine (file, "KNOWN ISSUE (Missing error location)");
1250 know_issues.Remove (file);
1251 return false;
1253 if (no_error_list.Contains (file)) {
1254 LogFileLine (file, "REGRESSION (NO ERROR -> MISSING ERROR LOCATION)");
1255 no_error_list.Remove (file);
1257 else {
1258 LogFileLine (file, "REGRESSION (CORRECT ERROR -> MISSING ERROR LOCATION)");
1260 break;
1262 case CompilerError.Duplicate:
1263 // Will become an error soon
1264 LogFileLine (file, "WARNING: EXACTLY SAME ERROR HAS BEEN ISSUED MULTIPLE TIMES");
1265 return true;
1268 regression.Add (file);
1269 return false;
1272 protected override void PrintSummary()
1274 base.PrintSummary ();
1276 if (wrong_warning.Count > 0) {
1277 LogLine ("");
1278 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)");
1279 LogLine ("");
1280 foreach (DictionaryEntry de in wrong_warning)
1281 LogLine ("CS{0:0000} : {1}", de.Key, (bool)de.Value ? "incorrect warning definition" : "missing warning definition");
1287 class Tester {
1289 static int Main(string[] args)
1291 string temp;
1293 if (GetOption ("help", args, false, out temp)) {
1294 Usage ();
1295 return 0;
1298 string compiler;
1299 if (!GetOption ("compiler", args, true, out compiler)) {
1300 Usage ();
1301 return 1;
1304 ITester tester;
1305 try {
1306 Console.WriteLine ("Loading " + compiler + " ...");
1307 tester = new ReflectionTester (Assembly.LoadFile (compiler));
1309 catch (Exception) {
1310 #if NET_2_1
1311 throw;
1312 #else
1313 Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
1314 if (!File.Exists (compiler)) {
1315 Console.Error.WriteLine ("ERROR: Tested compiler was not found");
1316 return 1;
1318 tester = new ProcessTester (compiler);
1319 #endif
1322 string mode;
1323 if (!GetOption ("mode", args, true, out mode)) {
1324 Usage ();
1325 return 1;
1328 Checker checker;
1329 bool positive;
1330 switch (mode) {
1331 case "neg":
1332 checker = new NegativeChecker (tester, true);
1333 positive = false;
1334 break;
1335 case "pos":
1336 string iltest;
1337 GetOption ("il", args, false, out iltest);
1338 checker = new PositiveChecker (tester, iltest);
1339 positive = true;
1341 if (iltest != null && GetOption ("update-il", args, false, out temp)) {
1342 ((PositiveChecker) checker).UpdateVerificationDataFile = true;
1345 break;
1346 default:
1347 Console.Error.WriteLine ("Invalid -mode argument");
1348 return 1;
1352 if (GetOption ("issues", args, true, out temp))
1353 checker.IssueFile = temp;
1354 if (GetOption ("log", args, true, out temp))
1355 checker.LogFile = temp;
1356 if (GetOption ("verbose", args, false, out temp))
1357 checker.Verbose = true;
1358 if (GetOption ("safe-execution", args, false, out temp))
1359 checker.SafeExecution = true;
1360 if (GetOption ("compiler-options", args, true, out temp)) {
1361 string[] extra = temp.Split (' ');
1362 checker.ExtraCompilerOptions = extra;
1365 string test_pattern;
1366 if (!GetOption ("files", args, true, out test_pattern)) {
1367 Usage ();
1368 return 1;
1371 var files = new List<string> ();
1372 switch (test_pattern) {
1373 case "v1":
1374 files.AddRange (Directory.GetFiles (".", positive ? "test*.cs" : "cs*.cs"));
1375 break;
1376 case "v2":
1377 files.AddRange (Directory.GetFiles (".", positive ? "gtest*.cs" : "gcs*.cs"));
1378 goto case "v1";
1379 case "v4":
1380 files.AddRange (Directory.GetFiles (".", positive ? "dtest*.cs" : "dcs*.cs"));
1381 goto case "v2";
1382 default:
1383 files.AddRange (Directory.GetFiles (".", test_pattern));
1384 break;
1387 if (files.Count == 0) {
1388 Console.Error.WriteLine ("No files matching `{0}' found", test_pattern);
1389 return 2;
1392 checker.Initialize ();
1394 files.Sort ((a, b) => {
1395 if (a.EndsWith ("-lib.cs", StringComparison.Ordinal)) {
1396 if (!b.EndsWith ("-lib.cs", StringComparison.Ordinal))
1397 return -1;
1398 } else if (b.EndsWith ("-lib.cs", StringComparison.Ordinal)) {
1399 if (!a.EndsWith ("-lib.cs", StringComparison.Ordinal))
1400 return 1;
1403 return a.CompareTo (b);
1406 foreach (string s in files) {
1407 string filename = Path.GetFileName (s);
1408 if (Char.IsUpper (filename, 0)) { // Windows hack
1409 continue;
1412 if (filename.EndsWith ("-p2.cs"))
1413 continue;
1415 checker.Do (filename);
1418 checker.CleanUp ();
1420 checker.Dispose ();
1422 return checker.ResultCode;
1425 static bool GetOption (string opt, string[] args, bool req_arg, out string value)
1427 opt = "-" + opt;
1428 foreach (string a in args) {
1429 if (a.StartsWith (opt)) {
1430 int sep = a.IndexOf (':');
1431 if (sep > 0) {
1432 value = a.Substring (sep + 1);
1433 } else {
1434 value = null;
1435 if (req_arg) {
1436 Console.Error.WriteLine ("Missing argument in option " + opt);
1437 return false;
1441 return true;
1445 value = null;
1446 return false;
1449 static void Usage ()
1451 Console.WriteLine (
1452 "Mono compiler tester, (C) 2009 Novell, Inc.\n" +
1453 "compiler-tester -mode:[pos|neg] -compiler:FILE -files:file-list [options]\n" +
1454 " \n" +
1455 " -compiler:FILE The file which will be used to compiler tests\n" +
1456 " -compiler-options:OPTIONS Add global compiler options\n" +
1457 " -il:IL-FILE XML file with expected IL details for each test\n" +
1458 " -issues:FILE The list of expected failures\n" +
1459 " -log:FILE Writes any output also to the file\n" +
1460 " -help Lists all options\n" +
1461 " -mode:[pos|neg] Specifies compiler test mode\n" +
1462 " -safe-execution Runs compiled executables in separate app-domain\n" +
1463 " -update-il Updates IL-FILE to match compiler output\n" +
1464 " -verbose Prints more details during testing\n"