Merge pull request #495 from nicolas-raoul/fix-for-issue2907-with-no-formatting-changes
[mono.git] / mcs / tools / compiler-tester / compiler-tester.cs
index cc66e8b1e5ca01971b53cc74b8e8939b1fbe60d7..eed75991911e208a6810cd055e53245e301c5651 100644 (file)
@@ -3,10 +3,9 @@
 //
 // Author:
 //   Marek Safar (marek.safar@gmail.com)
-//
-
 //
 // Copyright (C) 2008, 2009 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -36,6 +35,8 @@ using System.Text;
 using System.Collections;
 using System.Xml;
 using System.Collections.Generic;
+using Mono.CompilerServices.SymbolWriter;
+using System.Globalization;
 
 namespace TestRunner {
 
@@ -150,6 +151,130 @@ namespace TestRunner {
                }
        }
 
+       class NUnitChecker : PositiveChecker
+       {
+               class TestCaseEntry
+               {
+                       string name;
+                       string referenceFile;
+                       string executedMethod;
+                       bool has_return;
+
+                       public TestCaseEntry (string name, string referenceFile, MethodInfo executedMethod)
+                       {
+                               this.name = name.Replace ('-', '_');
+                               this.referenceFile = referenceFile;
+                               this.executedMethod = ConvertMethodInfoToText (executedMethod, out has_return);
+                       }
+
+                       public string Name
+                       {
+                               get
+                               {
+                                       return name;
+                               }
+                       }
+
+                       public string ReferenceFile {
+                               get {
+                                       return referenceFile;
+                               }
+                       }
+
+                       static string ConvertMethodInfoToText (MethodInfo mi, out bool hasReturn)
+                       {
+                               hasReturn = mi.ReturnType != typeof (void);
+                               string declaring = mi.DeclaringType.FullName.Replace ('+', '.');
+                               var param = mi.GetParameters ();
+                               if (param.Length == 0)
+                                       return declaring + "." + mi.Name + " ()";
+
+                               return declaring + "." + mi.Name + " (new string[0])";
+                       }
+
+                       public string GetTestFixture ()
+                       {
+                               var call = name + "::" + executedMethod;
+                               if (!has_return)
+                                       return call;
+
+                               return string.Format ("Assert.AreEqual (0, {0})", call); 
+                       }
+               }
+
+               List<TestCaseEntry> entries = new List<TestCaseEntry> ();
+
+               public NUnitChecker (ITester tester)
+                       : base (tester, null)
+               {
+               }
+
+               public override void CleanUp ()
+               {
+                       base.CleanUp ();
+
+                       StringBuilder aliases = new StringBuilder ();
+                       var src_dir = Path.Combine ("projects", "MonoTouch");
+                       string src_file = Path.Combine (src_dir, "tests.cs");
+
+                       using (var file = new StreamWriter (src_file, false)) {
+                               foreach (var e in entries) {
+                                       file.WriteLine ("extern alias {0};", e.Name);
+                                       aliases.AppendFormat ("    <Reference Include=\"{0}\">", Path.GetFileNameWithoutExtension (e.ReferenceFile));
+                                       aliases.Append (Environment.NewLine);
+                                       aliases.AppendFormat ("      <Aliases>{0}</Aliases>", e.Name);
+                                       aliases.Append (Environment.NewLine);
+                                       aliases.AppendFormat ("      <HintPath>..\\..\\{0}</HintPath>", Path.GetFileName (e.ReferenceFile));
+                                       aliases.Append (Environment.NewLine);
+                                       aliases.AppendLine ("    </Reference>");
+                               }
+
+                               file.WriteLine ();
+                               file.WriteLine ("using NUnit.Framework;");
+                               file.WriteLine ();
+                               file.WriteLine ("[TestFixture]");
+                               file.WriteLine ("public class Tests {");
+
+                               foreach (var e in entries) {
+                                       file.WriteLine ("\t[Test]");
+                                       file.WriteLine ("\tpublic void TestFile_{0} ()", e.Name);
+                                       file.WriteLine ("\t{");
+                                       file.WriteLine ("\t\t{0};", e.GetTestFixture ());
+                                       file.WriteLine ("\t}");
+                                       file.WriteLine ();
+                               }
+
+                               file.WriteLine ("}");
+                       }
+
+                       var input = File.ReadAllText (Path.Combine (src_dir, "MonoTouch.csproj.template"));
+                       input = input.Replace ("@GENERATED_REFERENCES", aliases.ToString ());
+                       input = input.Replace ("@TEST_SOURCEFILE", Path.GetFileName (src_file));
+
+                       File.WriteAllText (Path.Combine (src_dir, "MonoTouch.csproj"), input);
+                       return;
+               }
+
+               protected override bool ExecuteTestFile (TestCase test, string binaryFileName)
+               {
+                       Assembly assembly = Assembly.LoadFile (binaryFileName);
+                       var ep = assembly.EntryPoint;
+                       if (!ep.IsPublic) {
+                               HandleFailure (test.FileName, TestResult.LoadError, "Entry method is private");
+                               return false;
+                       }
+
+                       if (ep.DeclaringType.IsNestedPrivate || ep.DeclaringType.IsNestedFamily) {
+                               HandleFailure (test.FileName, TestResult.LoadError, "Entry method in hidden nested type");
+                               return false;
+                       }
+
+                       entries.Add (new TestCaseEntry (Path.GetFileNameWithoutExtension (test.FileName), binaryFileName, ep));
+                       HandleFailure (test.FileName, TestResult.Success, null);
+                       return true;
+               }
+       }
+
        class PositiveTestCase : TestCase
        {
                public class VerificationData : MarshalByRefObject
@@ -160,13 +285,15 @@ namespace TestRunner {
                                {
                                        this.Type = mi.DeclaringType.ToString ();
                                        this.MethodName = mi.ToString ();
+                                       this.MethodAttributes = (int) mi.Attributes;
                                        this.ILSize = il_size;
                                }
 
-                               public MethodData (string type_name, string method_name, int il_size)
+                               public MethodData (string type_name, string method_name, int method_attributes, int il_size)
                                {
                                        this.Type = type_name;
                                        this.MethodName = method_name;
+                                       this.MethodAttributes = method_attributes;
                                        this.ILSize = il_size;
                                }
 
@@ -174,6 +301,7 @@ namespace TestRunner {
                                public string MethodName;
                                public int ILSize;
                                public bool Checked;
+                               public int MethodAttributes;
                        }
 
                        ArrayList methods;
@@ -181,12 +309,9 @@ namespace TestRunner {
 
                        public VerificationData (string test_file)
                        {
-#if NET_2_0
                                this.test_file = test_file;
-#endif                         
                        }
 
-#if NET_2_0
                        string test_file;
 
                        public static VerificationData FromFile (string name, XmlReader r)
@@ -199,10 +324,11 @@ namespace TestRunner {
                                        r.Read ();
                                        while (r.ReadToNextSibling ("method")) {
                                                string m_name = r ["name"];
+                                               int method_attrs = int.Parse (r["attrs"]);
 
                                                r.ReadToDescendant ("size");
                                                int il_size = r.ReadElementContentAsInt ();
-                                               methods.Add (new MethodData (type_name, m_name, il_size));
+                                               methods.Add (new MethodData (type_name, m_name, method_attrs, il_size));
                                                r.Read ();
                                        }
                                        r.Read ();
@@ -233,6 +359,8 @@ namespace TestRunner {
 
                                        w.WriteStartElement ("method");
                                        w.WriteAttributeString ("name", data.MethodName);
+                                       int v = data.MethodAttributes;
+                                       w.WriteAttributeString ("attrs", v.ToString ());
                                        w.WriteStartElement ("size");
                                        w.WriteValue (data.ILSize);
                                        w.WriteEndElement ();
@@ -244,7 +372,6 @@ namespace TestRunner {
 
                                w.WriteEndElement ();
                        }
-#endif
 
                        public MethodData FindMethodData (string method_name, string declaring_type)
                        {
@@ -312,6 +439,7 @@ namespace TestRunner {
                protected ArrayList know_issues = new ArrayList ();
                protected ArrayList ignore_list = new ArrayList ();
                protected ArrayList no_error_list = new ArrayList ();
+               ArrayList skip = new ArrayList ();
                
                protected bool verbose;
                protected bool safe_execution;
@@ -411,6 +539,10 @@ namespace TestRunner {
                        if (verbose)
                                Log (filename + "...\t");
 
+                       if (skip.Contains (filename)) {
+                               return false;
+                       }
+
                        if (ignore_list.Contains (filename)) {
                                ++ignored;
                                LogFileLine (filename, "NOT TESTED");
@@ -457,13 +589,14 @@ namespace TestRunner {
                        string[] test_args;
 
                        if (test.CompilerOptions != null) {
-                               test_args = new string [2 + test.CompilerOptions.Length];
+                               test_args = new string[2 + test.CompilerOptions.Length];
                                test.CompilerOptions.CopyTo (test_args, 0);
                        } else {
-                               test_args = new string [2];
+                               test_args = new string[2];
                        }
-                       test_args [test_args.Length - 2] = test.FileName;
-                       test_args [test_args.Length - 1] = "-debug";
+                       test_args[test_args.Length - 2] = test_args[0];
+                       test_args[test_args.Length - 1] = "-debug";
+                       test_args[0] = test.FileName;
 
                        return tester.Invoke (test_args);
                }
@@ -477,6 +610,7 @@ namespace TestRunner {
                {
                        const string ignored = "IGNORE";
                        const string no_error = "NO ERROR";
+                       const string skip_tag = "SKIP";
 
                        using (StreamReader sr = new StreamReader (file)) {
                                string line;
@@ -490,6 +624,8 @@ namespace TestRunner {
                                                active_cont = ignore_list;
                                        else if (line.IndexOf (no_error) > 0)
                                                active_cont = no_error_list;
+                                       else if (line.Contains (skip_tag))
+                                               active_cont = skip;
 
                                        string file_name = line.Split (' ')[0];
                                        if (file_name.Length == 0)
@@ -612,7 +748,9 @@ namespace TestRunner {
                        LoadError,
                        XmlError,
                        Success,
-                       ILError
+                       ILError,
+                       DebugError,
+                       MethodAttributesError
                }
 
                public PositiveChecker (ITester tester, string verif_file):
@@ -721,18 +859,25 @@ namespace TestRunner {
 
                                md.Checked = true;
 
+                               if (md.MethodAttributes != (int) mi.Attributes) {
+                                       checker.HandleFailure (test.FileName, PositiveChecker.TestResult.MethodAttributesError,
+                                               string.Format ("{0} ({1} -> {2})", decl_type + ": " + m_name, md.MethodAttributes, mi.Attributes));
+                               }
+
+                               md.MethodAttributes = (int) mi.Attributes;
+
                                int il_size = GetILSize (mi);
                                if (md.ILSize == il_size)
                                        return true;
 
                                if (md.ILSize > il_size) {
-                                       checker.LogFileLine (test.FileName, "{0} (code size reduction {1} -> {2})", m_name, md.ILSize, il_size);
+                                       checker.LogFileLine (test.FileName, "{0} (code size reduction {1} -> {2})", decl_type + ": " + m_name, md.ILSize, il_size);
                                        md.ILSize = il_size;
                                        return true;
                                }
 
                                checker.HandleFailure (test.FileName, PositiveChecker.TestResult.ILError,
-                                       string.Format ("{0} (code size {1} -> {2})", m_name, md.ILSize, il_size));
+                                       string.Format ("{0} (code size {1} -> {2})", decl_type + ": " + m_name, md.ILSize, il_size));
 
                                md.ILSize = il_size;
 
@@ -741,11 +886,10 @@ namespace TestRunner {
 
                        static int GetILSize (MethodBase mi)
                        {
-#if NET_2_0
                                MethodBody body = mi.GetMethodBody ();
                                if (body != null)
                                        return body.GetILAsByteArray ().Length;
-#endif
+
                                return 0;
                        }
 
@@ -816,6 +960,13 @@ namespace TestRunner {
                                return true;
                        }
 
+                       return ExecuteTestFile (test, file);
+               }
+
+               protected virtual bool ExecuteTestFile (TestCase test, string binaryFileName)
+               {
+                       string filename = test.FileName;
+
                        AppDomain domain = null;
 #if !NET_2_1
                        if (safe_execution) {
@@ -823,7 +974,7 @@ namespace TestRunner {
                                AppDomainSetup setupInfo = new AppDomainSetup ();
                                setupInfo.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
                                setupInfo.LoaderOptimization = LoaderOptimization.SingleDomain;
-                               domain = AppDomain.CreateDomain (Path.GetFileNameWithoutExtension (file), null, setupInfo);
+                               domain = AppDomain.CreateDomain (Path.GetFileNameWithoutExtension (binaryFileName), null, setupInfo);
                        }
 #endif
                        try {
@@ -836,7 +987,7 @@ namespace TestRunner {
 #endif
                                                tester = new DomainTester ();
 
-                                       if (!tester.Test (file))
+                                       if (!tester.Test (binaryFileName))
                                                return false;
 
                                } catch (ApplicationException e) {
@@ -851,7 +1002,7 @@ namespace TestRunner {
                                        string ref_file = filename.Replace (".cs", "-ref.xml");
                                        try {
 #if !NET_2_1
-                                               XmlComparer.Compare (ref_file, doc_output);
+                                               new XmlComparer ("doc").Compare (ref_file, doc_output);
 #endif
                                        } catch (Exception e) {
                                                HandleFailure (filename, TestResult.XmlError, e.Message);
@@ -862,9 +1013,25 @@ namespace TestRunner {
                                                PositiveTestCase pt = (PositiveTestCase) test;
                                                pt.VerificationProvider = (PositiveTestCase.VerificationData) verif_data[filename];
 
-                                               if (!tester.CheckILSize (pt, this, file))
+                                               if (!tester.CheckILSize (pt, this, binaryFileName))
                                                        return false;
                                        }
+
+                                       if (filename.StartsWith ("test-debug", StringComparison.OrdinalIgnoreCase)) {
+                                               var mdb_file_name = binaryFileName + ".mdb";
+                                               MonoSymbolFile mdb_file = MonoSymbolFile.ReadSymbolFile (mdb_file_name);
+                                               var mdb_xml_file = mdb_file_name + ".xml";
+                                               ConvertSymbolFileToXml (mdb_file, mdb_xml_file);
+
+                                               var ref_file = filename.Replace(".cs", "-ref.xml");
+                                               try {
+                                                       new XmlComparer ("symbols").Compare (ref_file, mdb_xml_file);
+                                               } catch (Exception e) {
+                                                       HandleFailure (filename, TestResult.DebugError, e.Message);
+                                                       return false;
+                                               }
+                                       }
+
                                }
                        } finally {
                                if (domain != null)
@@ -875,6 +1042,96 @@ namespace TestRunner {
                        return true;
                }
 
+               static void ConvertSymbolFileToXml (MonoSymbolFile symbolFile, string xmlFile)
+               {
+                       using (XmlTextWriter writer = new XmlTextWriter (xmlFile, Encoding.UTF8)) {
+                               writer.Formatting = Formatting.Indented;
+
+                               writer.WriteStartDocument ();
+
+                               writer.WriteStartElement ("symbols");
+
+                               writer.WriteStartElement ("files");
+                               foreach (var file in symbolFile.Sources) {
+                                       writer.WriteStartElement ("file");
+                                       writer.WriteAttributeString ("id", file.Index.ToString ());
+                                       writer.WriteAttributeString ("name", Path.GetFileName (file.FileName));
+                                       var checksum = file.Checksum;
+                                       if (checksum != null)
+                                               writer.WriteAttributeString ("checksum", ChecksumToString (checksum));
+
+                                       writer.WriteEndElement ();
+                               }
+                               writer.WriteEndElement ();
+
+                               writer.WriteStartElement ("methods");
+                               foreach (var method in symbolFile.Methods) {
+                                       writer.WriteStartElement ("method");
+                                       writer.WriteAttributeString ("token", IntToHex (method.Token));
+
+                                       var il_entries = method.GetLineNumberTable ();
+                                       writer.WriteStartElement ("sequencepoints");
+                                       foreach (var entry in il_entries.LineNumbers) {
+                                               writer.WriteStartElement ("entry");
+                                               writer.WriteAttributeString ("il", IntToHex (entry.Offset));
+                                               writer.WriteAttributeString ("row", entry.Row.ToString ());
+                                               writer.WriteAttributeString ("col", entry.Column.ToString ());
+                                               writer.WriteAttributeString ("file_ref", entry.File.ToString ());
+                                               writer.WriteAttributeString ("hidden", BoolToString (entry.IsHidden));
+                                               writer.WriteEndElement ();
+                                       }
+                                       writer.WriteEndElement ();
+
+                                       writer.WriteStartElement ("locals");
+                                       foreach (var local in method.GetLocals ()) {
+                                               writer.WriteStartElement ("entry");
+                                               writer.WriteAttributeString ("name", local.Name);
+                                               writer.WriteAttributeString ("il_index", local.Index.ToString ());
+                                               writer.WriteAttributeString ("scope_ref", local.BlockIndex.ToString ());
+                                               writer.WriteEndElement ();
+                                       }
+                                       writer.WriteEndElement ();
+
+                                       writer.WriteStartElement ("scopes");
+                                       foreach (var scope in method.GetCodeBlocks ()) {
+                                               writer.WriteStartElement ("entry");
+                                               writer.WriteAttributeString ("index", scope.Index.ToString ());
+                                               writer.WriteAttributeString ("start", IntToHex (scope.StartOffset));
+                                               writer.WriteAttributeString ("end", IntToHex (scope.EndOffset));
+                                               writer.WriteEndElement ();
+                                       }
+                                       writer.WriteEndElement ();
+
+                                       writer.WriteEndElement ();
+                               }
+                               writer.WriteEndElement ();
+
+                               writer.WriteEndElement ();
+                               writer.WriteEndDocument ();
+                       }
+               }
+
+               static string ChecksumToString (byte[] checksum)
+               {
+                       var sb = new StringBuilder (checksum.Length * 2);
+                       for (int i = 0; i < checksum.Length; i++) {
+                               sb.Append ("0123456789abcdef"[checksum[i] >> 4]);
+                               sb.Append ("0123456789abcdef"[checksum[i] & 0x0F]);
+                       }
+
+                       return sb.ToString ();
+               }
+
+               static string IntToHex (int value)
+               {
+                       return "0x" + value.ToString ("x", CultureInfo.InvariantCulture);
+               }
+
+               static string BoolToString (bool value)
+               {
+                       return value ? "true" : "false";
+               }
+
                protected override TestCase CreateTestCase (string filename, string [] options, string [] deps)
                {
                        return new PositiveTestCase (filename, options, deps);
@@ -921,15 +1178,24 @@ namespace TestRunner {
                                        break;
 
                                case TestResult.LoadError:
-                                       LogFileLine (file, "REGRESSION (SUCCESS -> LOAD ERROR)");
+                                       if (extra != null)
+                                               extra = ": " + extra;
+
+                                       LogFileLine (file, "REGRESSION (SUCCESS -> LOAD ERROR)" + extra);
+                                       extra = null;
                                        break;
 
+                               case TestResult.MethodAttributesError:
                                case TestResult.ILError:
                                        if (!update_verif_file) {
                                                LogFileLine (file, "IL REGRESSION: " + extra);
                                        }
                                        extra = null;
                                        break;
+
+                               case TestResult.DebugError:
+                                       LogFileLine (file, "REGRESSION (SUCCESS -> SYMBOL FILE ERROR)");
+                                       break;
                        }
 
                        if (extra != null)
@@ -942,11 +1208,7 @@ namespace TestRunner {
                public override void Initialize ()
                {
                        if (verif_file != null) {
-#if NET_2_0
                                LoadVerificationData (verif_file);
-#else
-                               throw new NotSupportedException ();
-#endif
                        }
 
                        base.Initialize ();
@@ -957,22 +1219,23 @@ namespace TestRunner {
                        base.CleanUp ();
 
                        if (update_verif_file) {
-#if NET_2_0
                                UpdateVerificationData (verif_file);
-#else
-                               throw new NotSupportedException ();
-#endif
                        }
                }
 
-#if NET_2_0
                void LoadVerificationData (string file)
                {
+                       verif_data = new Hashtable ();
+
+                       if (!File.Exists (file)) {
+                               LogLine ("Writing verification data to `{0}' ...", file);
+                               return;
+                       }
+
                        LogLine ("Loading verification data from `{0}' ...", file);
 
                        using (XmlReader r = XmlReader.Create (file)) {
                                r.ReadStartElement ("tests");
-                               verif_data = new Hashtable ();
 
                                while (r.Read ()) {
                                        if (r.Name != "test")
@@ -1002,7 +1265,6 @@ namespace TestRunner {
                                w.WriteEndElement ();
                        }
                }
-#endif
        }
 
        class NegativeChecker: Checker
@@ -1055,11 +1317,7 @@ namespace TestRunner {
                                // Some error tests require to have different error text for different runtimes.
                                if (filtered.StartsWith ("//GMCS")) {
                                        row = 1;
-#if !NET_2_0
-                                       return true;
-#else
                                        return AnalyzeTestFile(file, ref row, line, ref compiler_options, ref dependencies);
-#endif
                                }
 
                                check_error_line = !filtered.StartsWith ("//Line:0");
@@ -1305,18 +1563,13 @@ namespace TestRunner {
                        try {
                                Console.WriteLine ("Loading " + compiler + " ...");
                                tester = new ReflectionTester (Assembly.LoadFile (compiler));
-                       }
-                       catch (Exception) {
-#if NET_2_1
-                               throw;
-#else
+                       } catch (Exception) {
                                Console.Error.WriteLine ("Switching to command line mode (compiler entry point was not found)");
                                if (!File.Exists (compiler)) {
                                        Console.Error.WriteLine ("ERROR: Tested compiler was not found");
                                        return 1;
                                }
                                tester = new ProcessTester (compiler);
-#endif
                        }
 
                        string mode;
@@ -1342,6 +1595,10 @@ namespace TestRunner {
                                                ((PositiveChecker) checker).UpdateVerificationDataFile = true;
                                        }
 
+                                       break;
+                               case "nunit":
+                                       positive = true;
+                                       checker = new NUnitChecker (tester);
                                        break;
                                default:
                                        Console.Error.WriteLine ("Invalid -mode argument");