//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: mktests.cs // // Examine assembly metadata to compile a manifest of test code in a Singularity // distribution. // using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Xml; using Bartok.MSIL; public class mktests { const string ATTRIBUTE_PREFIX = "Microsoft.Singularity.UnitTest."; private static void Usage() { Console.WriteLine("Usage:\n" + " mktests /out: [assemblies]\n"); } public static int Main(string[] args) { if (args.Length == 0) { Usage(); return 0; } DateTime timeBegin = DateTime.Now; ArrayList infiles = new ArrayList(); string outfile = null; for (int i = 0; i < args.Length; i++) { string arg = args[i]; if (arg.StartsWith("-") || arg.StartsWith("/")) { if (arg.StartsWith("-out:") || arg.StartsWith("/out:")) { outfile = arg.Substring(arg.IndexOf(":") + 1); } else { Console.WriteLine("Malformed argument: \"" + arg + "\""); Usage(); return 1; } } else { // This is just an assembly name infiles.Add(arg); } } if (outfile == null) { Console.WriteLine("You must specify the output file."); Usage(); return 1; } if (infiles.Count == 0) { Console.WriteLine("You must specify at least one input file."); Usage(); return 1; } XmlTextWriter oo = new XmlTextWriter(outfile, null); oo.Formatting = Formatting.Indented; oo.Indentation = 4; try { oo.WriteStartDocument(); if (infiles.Count == 1) { oo.WriteComment("Export of tests from: " + infiles[0]); } else { oo.WriteComment("Export of tests from:"); foreach (string f in infiles) { oo.WriteComment(" " + f); } } oo.WriteStartElement("Profile"); oo.WriteAttributeString("Name", outfile); ProcessAssemblies(oo, infiles); oo.WriteEndElement(); oo.WriteEndDocument(); } finally { oo.Close(); } TimeSpan elapsed = DateTime.Now - timeBegin; Console.WriteLine("mktests: {0} seconds elapsed.", elapsed.TotalSeconds); return 0; } private static void ProcessAssemblies(XmlTextWriter oo, ArrayList infiles) { MetaDataResolver resolver = new MetaDataResolver(infiles, new ArrayList(), new DateTime(), false, false); MetaDataResolver.ResolveCustomAttributes(new MetaDataResolver[] { resolver }); foreach (MetaData md in resolver.MetaDataList) { // Assume that if we are processing an assembly, it will contain tests string name = md.Name; string pkg; string assembly = Tail(name, '\\', out pkg); // extract filename string module = assembly.Substring(0, assembly.IndexOf('.')); // trim extension oo.WriteStartElement("Module"); oo.WriteAttributeString("Name", module); ProcessAssembly(oo, md); oo.WriteEndElement(); } } private static void ProcessAssembly(XmlTextWriter oo, MetaData md) { IDictionary tests = new SortedDictionary(); IDictionary suites = new SortedDictionary(); // Look for the annotation that tells us that this assembly is a stand-alone // test app. MetaDataAssembly mda = (MetaDataAssembly) md.Assemblies[0]; foreach (MetaDataCustomAttribute attrib in md.CustomAttributes) { MetaDataObject parent = attrib.Parent; //Console.WriteLine("Found: {0} in {1} {2}", attrib.Name, parent.FullName, parent.FullNameWithContext); if (!attrib.Name.StartsWith(ATTRIBUTE_PREFIX)) { continue; } string attribName = attrib.Name.Substring(ATTRIBUTE_PREFIX.Length); switch (attribName) { case "TestAppAttribute": break; case "TestClassAttribute": suites.Add(attrib.Parent.FullName, attrib); break; case "TestMethodAttribute": MetaDataObject m = attrib.Parent; tests.Add(attrib.Parent.FullName, attrib); break; } } string prevSuite = ""; foreach (KeyValuePair kvp in tests) { string k = kvp.Key; string className; string testname = Tail(k, '.', out className); if (!suites.ContainsKey(className)) { Console.WriteLine("TestMethod declared outside of a TestClass: {0}", k); continue; } if (!prevSuite.Equals(className)) { if (! prevSuite.Equals("")) { oo.WriteEndElement(); } oo.WriteStartElement("Suite"); string ignored; oo.WriteAttributeString("Name", Tail(className, '.', out ignored)); prevSuite = className; } oo.WriteStartElement("Test"); oo.WriteAttributeString("Name", testname); #if EXTRACT_TIMEOUT object timeout = psItem.Fields["Test Timeout"].Value; if (timeout != null) { oo.WriteAttributeString("Timeout", timeout.ToString()); } object knownFailure = psItem.Fields["Test Known Failure"].Value; if (knownFailure != null) { oo.WriteAttributeString("KnownFailure", knownFailure.ToString()); } #endif oo.WriteEndElement(); } if (! prevSuite.Equals("")) { oo.WriteEndElement(); } } // Split a string at the last occcurence of a character, returning both // the before and after. If the character is not present, then the tail // is empty and before is the entire string. private static string Tail(string it, char pattern, out string before) { int i = it.LastIndexOf(pattern); if (i < 0) { before = it; return ""; } else { before = it.Substring(0, i); return it.Substring(i + 1); } } private static void Assert(bool cond, string trueText) { if (!cond) { throw new Exception("Expected: " + trueText); } } }