Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial revision

svn path=/trunk/monocov/; revision=15853
  • Loading branch information...
commit cb203a9979f943df538a31ff27d6393dec873547 0 parents
@vargaz vargaz authored
33 ChangeLog
@@ -0,0 +1,33 @@
+2003-07-02 Zoltan Varga <vargaz@freemail.hu>
+
+ * all: Initial import into CVS.
+
+2003-07-01 Zoltan Varga <vargaz@freemail.hu>
+
+ * HtmlExporter.cs: Implemented HTML exporter.
+
+ * style.xsl: Small modifications needed by the HTML export method.
+
+2003-06-07 Zoltan Varga <vargaz@freemail.hu>
+
+ * New version with many enchantments.
+
+2003-01-06 Zoltan Varga <vargaz@freemail.hu>
+
+ * gui/qt/SourceView.cs: fixed viewing of source files which contain
+ tags recognized by QTextEdit.
+
+2003-01-05 Zoltan Varga <vargaz@freemail.hu>
+
+ * coverage.c: Handle nested types correctly.
+
+2002-12-30 Zoltan Varga <vargaz@freemail.hu>
+
+ * coverage.c README: Added filters to the back end. They can be used by
+ passing the '--coverage-args filterfile=<NAME>' argument on the command
+ line.
+
+2002-12-28 Zoltan Varga <vargaz@freemail.hu>
+
+ * first public version.
+
54 ClassCoverageItem.cs
@@ -0,0 +1,54 @@
+
+using System;
+using System.Collections;
+
+namespace MonoCov {
+
+public class ClassCoverageItem : CoverageItem {
+
+ /// the namespace to which this class belongs
+ public string name_space;
+
+ /// the scoped name of this class
+ public string name;
+
+ public SourceFileCoverageData sourceFile;
+
+ // The type object representing this class
+ public Type type;
+
+ // Contains MethodBase -> MethodCoverageData mappings
+ public Hashtable methodsByMethod;
+
+ public ClassCoverageItem (CoverageItem parent) : base (parent) {
+ methodsByMethod = new Hashtable ();
+ }
+
+ public ArrayList Methods {
+ get {
+ if (children == null)
+ return new ArrayList (0);
+ else
+ return children;
+ }
+ }
+
+ public string FullName {
+ get {
+ if ((name_space == "") || (name_space == "<GLOBAL>"))
+ return name;
+ else
+ return name_space + "." + name;
+ }
+ }
+
+ public string Namespace {
+ get {
+ if (name_space == "<GLOBAL>")
+ return "";
+ else
+ return name_space;
+ }
+ }
+}
+}
111 CoverageItem.cs
@@ -0,0 +1,111 @@
+
+using System;
+using System.Collections;
+
+namespace MonoCov {
+
+public abstract class CoverageItem {
+
+ public int hit;
+ public int missed;
+ public double coveragePercent;
+
+ public bool filtered;
+
+ public CoverageItem parent;
+
+ public ArrayList children;
+
+ public CoverageItem () {
+ hit = 0;
+ missed = 0;
+ coveragePercent = 0.0;
+ }
+
+ public CoverageItem (CoverageItem parent) : this () {
+ if (parent != null)
+ parent.AddChildren (this);
+ }
+
+ public void AddChildren (CoverageItem item) {
+ if (children == null)
+ children = new ArrayList ();
+ children.Add (item);
+ item.parent = this;
+ }
+
+ public virtual bool IsLeaf {
+ get {
+ return false;
+ }
+ }
+
+ public int ChildCount {
+ get {
+ if (children == null)
+ return 0;
+ else
+ return children.Count;
+ }
+ }
+
+ public void setCoverage (int hit, int missed) {
+ this.hit = hit;
+ this.missed = missed;
+ if (hit + missed == 0)
+ coveragePercent = 100.0;
+ else
+ coveragePercent = (double)hit / (hit + missed);
+ }
+
+ public void computeCoveragePercent () {
+ if (hit + missed == 0)
+ coveragePercent = 100.0;
+ else
+ coveragePercent = (double)hit / (hit + missed);
+ }
+
+ public void computeCoverage () {
+ computeCoverage (false);
+ }
+
+ public void computeCoverage (bool recurse) {
+ if (IsLeaf)
+ return;
+
+ hit = 0;
+ missed = 0;
+
+ if (children != null) {
+ foreach (CoverageItem item in children) {
+ if (!item.filtered) {
+ if (recurse)
+ item.computeCoverage (recurse);
+ hit += item.hit;
+ missed += item.missed;
+ }
+ }
+ }
+
+ computeCoveragePercent ();
+ }
+
+ public void recomputeCoverage () {
+ computeCoverage ();
+
+ if (parent != null)
+ parent.recomputeCoverage ();
+ }
+
+ public void FilterItem (bool isFiltered) {
+ if (filtered != isFiltered) {
+ filtered = isFiltered;
+ recomputeCoverage ();
+ }
+ }
+
+ public override string ToString () {
+ return "" + GetType () + "(hit=" + hit + ", missed=" + missed + ")";
+ }
+}
+}
481 CoverageModel.cs
@@ -0,0 +1,481 @@
+
+using System;
+using System.Collections;
+using System.Xml;
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+using Mono.CSharp.Debugger;
+
+namespace MonoCov {
+
+public class CoverageModel : CoverageItem {
+
+ private Hashtable namespaces;
+ private Hashtable classes;
+ private Hashtable sources;
+
+ private Hashtable loadedAssemblies;
+ private Hashtable symbolFiles;
+
+ /**
+ * List of filters, which are strings
+ */
+ private ArrayList filters;
+
+ public CoverageModel () {
+ namespaces = new Hashtable ();
+ classes = new Hashtable ();
+ sources = new Hashtable ();
+ filters = new ArrayList ();
+ }
+
+ public Hashtable Classes {
+ get {
+ return classes;
+ }
+ }
+
+ public Hashtable Namespaces {
+ get {
+ return namespaces;
+ }
+ }
+
+ public void AddFilter (String pattern) {
+ filters.Add (pattern);
+ }
+
+ private bool IsFiltered (string name) {
+
+ name = name.Replace ("mscorlib", "corlib");
+
+ // Check positive filters first
+ bool hasPositive = false;
+ bool found = false;
+ foreach (String pattern in filters) {
+ if (pattern [0] == '+') {
+ string p = pattern.Substring (1);
+ if (name.IndexOf (p) != -1) {
+ // Console.WriteLine ("FILTERED: " + regex + " -> " + name);
+ found = true;
+ }
+ hasPositive = true;
+ }
+ }
+ if (hasPositive && !found)
+ return true;
+
+ foreach (String pattern in filters) {
+ if (pattern [0] == '-') {
+ string p = pattern.Substring (1);
+ if (name.IndexOf (p) != -1) {
+ // Console.WriteLine ("FILTERED: " + regex + " -> " + name);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void LoadAssemblies (XmlDocument dom) {
+ foreach (XmlNode n in dom.GetElementsByTagName ("assembly")) {
+ string assemblyName = n.Attributes ["name"].Value;
+ string guid = n.Attributes ["guid"].Value;
+
+ Assembly assembly = Assembly.Load (assemblyName);
+
+ if (assembly.GetLoadedModules ()[0].Mono_GetGuid () !=
+ new Guid (guid)) {
+ Console.WriteLine ("WARNING: Loaded version of assembly " + assembly + " is different from the version used to collect coverage data.");
+ }
+
+ MonoSymbolFile symbolFile;
+
+ loadedAssemblies [assemblyName] = assembly;
+
+ Console.Write ("Reading symbols for " + assembly + " ...");
+ symbolFile = MonoSymbolFile.ReadSymbolFile (assembly);
+ if (symbolFile == null)
+ Console.WriteLine (" (No symbols found)");
+ else {
+ symbolFiles [assembly] = symbolFile;
+ Console.WriteLine (" (" + symbolFile.SourceCount + " files, " + symbolFile.MethodCount + " methods)");
+ }
+ }
+ }
+
+ private void LoadFilters (XmlDocument dom) {
+ foreach (XmlNode n in dom.GetElementsByTagName ("filter")) {
+ AddFilter (n.Attributes ["pattern"].Value);
+ }
+ }
+
+ public void ReadFromFile (string fileName) {
+ namespaces = new Hashtable ();
+ classes = new Hashtable ();
+
+ long begin = DateTime.Now.Ticks / 10000;
+ long msec = DateTime.Now.Ticks / 10000;
+ long msec2;
+
+ loadedAssemblies = new Hashtable ();
+ symbolFiles = new Hashtable ();
+
+ XmlDocument dom = new XmlDocument ();
+ Console.Write ("Loading " + fileName + "...");
+ dom.Load (new XmlTextReader (new FileStream (fileName, FileMode.Open)));
+ Console.WriteLine (" Done.");
+
+ msec2 = DateTime.Now.Ticks / 10000;
+ Console.WriteLine ("XML Reading: " + (msec2 - msec) + " msec");
+ msec = msec2;
+
+ LoadAssemblies (dom);
+
+ LoadFilters (dom);
+
+ msec2 = DateTime.Now.Ticks / 10000;
+ Console.WriteLine ("Load assemblies: " + (msec2 - msec) + " msec");
+ msec = msec2;
+
+ foreach (XmlNode n in dom.GetElementsByTagName ("method")) {
+ string assemblyName = n.Attributes ["assembly"].Value;
+ string className = n.Attributes ["class"].Value;
+ string methodName = n.Attributes ["name"].Value;
+ string token = n.Attributes ["token"].Value;
+ string cov_info = n.FirstChild.Value;
+
+ Assembly assembly = (Assembly)loadedAssemblies [assemblyName];
+ MonoSymbolFile symbolFile = (MonoSymbolFile)symbolFiles [assembly];
+
+ if (symbolFile == null)
+ continue;
+
+ Type t = assembly.GetType (className);
+ if (t == null) {
+ Console.WriteLine ("ERROR: Unable to resolve type " + className + " in " + assembly);
+ return;
+ }
+
+ ClassCoverageItem klass = ProcessClass (t);
+
+ MethodEntry entry = symbolFile.GetMethodByToken (Int32.Parse (token));
+ MethodBase monoMethod = assembly.MonoDebugger_GetMethod (Int32.Parse (token));
+
+ ProcessMethod (monoMethod, entry, klass, methodName, cov_info);
+ }
+
+ msec2 = DateTime.Now.Ticks / 10000;
+ Console.WriteLine ("Process methods: " + (msec2 - msec) + " msec");
+ msec = msec2;
+
+ // Add info for klasses for which we have no coverage
+
+ foreach (Assembly assembly in loadedAssemblies.Values) {
+ foreach (Type t in assembly.GetTypes ()) {
+ ProcessClass (t);
+ }
+ }
+
+ // Add info for methods for which we have no coverage
+
+ foreach (ClassCoverageItem klass in classes.Values) {
+ foreach (MethodInfo mb in klass.type.GetMethods (BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Static|BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
+ MonoSymbolFile symbolFile = (MonoSymbolFile)symbolFiles [klass.type.Assembly];
+ if (symbolFile == null)
+ continue;
+
+ if (! klass.methodsByMethod.ContainsKey (mb)) {
+ MethodEntry entry = symbolFile.GetMethod (mb);
+ ProcessMethod (mb, entry, klass, mb.Name, null);
+ }
+ }
+ }
+
+ msec2 = DateTime.Now.Ticks / 10000;
+ Console.WriteLine ("Additional classes: " + (msec2 - msec) + " msec");
+ msec = msec2;
+
+ // Compute coverage for all items
+
+ computeCoverage (true);
+
+ msec2 = DateTime.Now.Ticks / 10000;
+ Console.WriteLine ("Compute coverage: " + (msec2 - msec) + " msec");
+ msec = msec2;
+
+ Console.WriteLine ("All: " + (msec2 - begin) + " msec");
+
+ // Free memory
+ symbolFiles = null;
+ }
+
+ //
+ // Computes the coverage of METHOD
+ //
+
+ private char[] digits = "0123456789".ToCharArray ();
+ private char[] ws = "\t\n ".ToCharArray ();
+
+ private int parsePositiveInteger (string s, int pos) {
+ int n = 0;
+
+ while (s [pos] >= '0' && s [pos] <= '9'){
+ n = n * 10 + (s [pos] - '0');
+ pos ++;
+ }
+ return n;
+ }
+
+ private void computeMethodCoverage (MethodCoverageItem method,
+ LineNumberEntry[] lines,
+ string cov_info) {
+
+ ClassCoverageItem klass = method.Class;
+ SourceFileCoverageData source = klass.sourceFile;
+
+ source.AddMethod (method);
+
+ int nlines = method.endLine - method.startLine + 1;
+
+ int[] coverage = new int [nlines];
+
+ if (cov_info == null) {
+ for (int i = 0; i < nlines; ++i)
+ coverage [i] = 0;
+ }
+ else {
+ for (int i = 0; i < nlines; ++i)
+ coverage [i] = -1;
+
+ // Hand crafted parsing code since this is performance critical
+ int pos = 0;
+ int prev_offset = 0;
+ while (pos < cov_info.Length) {
+ int pos2 = cov_info.IndexOfAny (digits, pos);
+ if (pos2 == -1)
+ break;
+ pos = cov_info.IndexOfAny (ws, pos2);
+ if (pos == -1)
+ break;
+
+ int offset = parsePositiveInteger (cov_info, pos2);
+
+ pos2 = cov_info.IndexOfAny (digits, pos);
+ if (pos2 == -1)
+ break;
+ pos = cov_info.IndexOfAny (ws, pos2);
+
+ int count = parsePositiveInteger (cov_info, pos2);
+
+ offset += prev_offset;
+ prev_offset = offset;
+
+ int line1;
+ int line2;
+
+ bool found = GetSourceRangeFor (offset, method, lines, ref line1, ref line2);
+
+ /*
+ if (found && (entry.Name.IndexOf ("Find") != -1)) {
+ Console.WriteLine ("OFFSET: " + offset + " " + line1 + ":" + line2);
+ }
+ */
+
+ if (found) {
+ for (int i = line1; i < line2 + 1; ++i)
+ if ((i >= method.startLine) && (i <= method.endLine))
+ if (coverage [i - method.startLine] < count)
+ coverage [i - method.startLine] = count;
+ }
+ }
+ }
+
+ int hit = 0;
+ int missed = 0;
+
+ for (int i = 0; i < nlines; ++i) {
+ int count = coverage [i];
+ if (count > 0)
+ hit ++;
+ else if (count == 0)
+ missed ++;
+ }
+
+ method.setCoverage (hit, missed);
+
+ method.lineCoverage = coverage;
+ }
+
+ //
+ // Return a range of source lines which have something to do with OFFSET.
+ //
+ private bool GetSourceRangeFor (int offset, MethodCoverageItem method,
+ LineNumberEntry[] lines,
+ ref int startLine, ref int endLine) {
+
+ /**
+ * The line number info generated by mcs is pretty strange sometimes... :)
+ */
+
+ /**
+ * First, we split the range of IL offsets into consecutive blocks and
+ * identify the block which contains OFFSET. Then we identify the range
+ * of source lines which are mapped to this range by the line number
+ * entries.
+ */
+
+ int beginOffset;
+ int endOffset;
+ int i;
+
+ for (i = 0; i < lines.Length; ++i) {
+ if (offset >= lines [i].Offset)
+ if (i == lines.Length - 1) {
+ beginOffset = lines [i].Offset;
+ endOffset = lines [i].Offset;
+ break;
+ }
+ else if (offset < lines [i + 1].Offset) {
+ beginOffset = lines [i].Offset;
+ endOffset = lines [i + 1].Offset - 1;
+ break;
+ }
+ }
+
+ /*
+ if (method.Name.IndexOf ("Find") != -1) {
+ Console.WriteLine ("OFFSET: " + offset + " " + beginOffset + " " + endOffset);
+ }
+ */
+
+ if (i == lines.Length) {
+ if (offset <= lines [0].Offset) {
+ return false;
+ }
+ else {
+ for (i = 0; i < lines.Length; ++i)
+ Console.WriteLine (lines [i]);
+ throw new Exception ("Unable to determine source range for offset " + offset + " in " + method.name);
+ }
+ }
+
+ /* Find start line */
+ for (i = 0; i < lines.Length; ++i)
+ if (lines [i].Offset == beginOffset) {
+ startLine = lines [i].Row;
+ break;
+ }
+
+ // g_assert (i < num_line_numbers);
+
+ /* Find end line */
+ if (lines.Length == 1)
+ endLine = lines [0].Row;
+ else {
+ for (i = 0; i < lines.Length; ++i)
+ if (i == lines.Length - 1) {
+ endLine = lines [i].Row;
+ break;
+ }
+ else if (lines [i + 1].Offset > endOffset) {
+ endLine = lines [i + 1].Row - 1;
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ private ClassCoverageItem ProcessClass (Type t) {
+ string className = t.FullName;
+ int nsindex = className.LastIndexOf (".");
+ string namespace2;
+ string scopedName;
+ if (nsindex == -1) {
+ namespace2 = "<GLOBAL>";
+ scopedName = className;
+ } else if (nsindex == 0) {
+ namespace2 = "<GLOBAL>";
+ scopedName = className.Substring (1);
+ }
+ else {
+ namespace2 = className.Substring (0, nsindex);
+ scopedName = className.Substring (nsindex + 1);
+ }
+
+ // Create namespaces
+ NamespaceCoverageItem ns = (NamespaceCoverageItem)namespaces [namespace2];
+ if (ns == null) {
+ string nsPrefix = "";
+ foreach (String nsPart in namespace2.Split ('.')) {
+ if (nsPrefix == "")
+ nsPrefix = nsPart;
+ else
+ nsPrefix = nsPrefix + "." + nsPart;
+ NamespaceCoverageItem ns2 = (NamespaceCoverageItem)namespaces [nsPrefix];
+ if (ns2 == null) {
+ if (ns == null)
+ ns2 = new NamespaceCoverageItem (this, nsPrefix);
+ else
+ ns2 = new NamespaceCoverageItem (ns, nsPrefix);
+ namespaces [nsPrefix] = ns2;
+ }
+ ns = ns2;
+ }
+ }
+
+ ClassCoverageItem klass = (ClassCoverageItem)classes [className];
+ if (klass == null) {
+ klass = new ClassCoverageItem (ns);
+ klass.name_space = namespace2;
+ klass.name = scopedName;
+ klass.type = t;
+ klass.parent = ns;
+
+ klass.filtered = IsFiltered ("[" + t.Assembly + "]" + className);
+ classes [className] = klass;
+ }
+
+ return klass;
+ }
+
+ private void ProcessMethod (MethodBase monoMethod, MethodEntry entry, ClassCoverageItem klass, string methodName, string cov_info) {
+ if (entry == null)
+ // Compiler generated, abstract method etc.
+ return;
+
+ LineNumberEntry[] lines = entry.LineNumbers;
+
+ if (lines.Length == 0)
+ return;
+
+ int start_line = lines [0].Row;
+ int end_line = lines [lines.Length - 1].Row;
+
+ MethodCoverageItem method
+ = new MethodCoverageItem (klass, methodName);
+
+ method.startLine = start_line;
+ method.endLine = end_line;
+ method.filtered = IsFiltered ("[" + monoMethod.DeclaringType.Assembly + "]" + monoMethod.DeclaringType + "::" + monoMethod.Name);
+ klass.methodsByMethod [monoMethod] = method;
+
+
+ if (klass.sourceFile == null) {
+ string sourceFile = entry.SourceFile.FileName;
+
+ SourceFileCoverageData source = (SourceFileCoverageData)sources [sourceFile];
+ if (source == null) {
+ source = new SourceFileCoverageData (sourceFile);
+ sources [sourceFile] = source;
+ }
+ klass.sourceFile = source;
+ }
+
+ computeMethodCoverage (method, lines, cov_info);
+ }
+}
+}
114 HtmlExporter.cs
@@ -0,0 +1,114 @@
+
+using System;
+using System.Collections;
+using System.Text;
+using System.IO;
+using System.Xml;
+using System.Xml.Xsl;
+using System.Xml.XPath;
+
+namespace MonoCov {
+
+public class HtmlExporter {
+
+ public class ProgressEventArgs {
+ public CoverageItem item;
+
+ public string fileName;
+
+ public int pos;
+
+ public int itemCount;
+
+ public ProgressEventArgs (CoverageItem item, string fileName,
+ int pos, int itemCount) {
+ this.item = item;
+ this.fileName = fileName;
+ this.pos = pos;
+ this.itemCount = itemCount;
+ }
+ }
+
+ public delegate void ProgressEventHandler (Object sender,
+ ProgressEventArgs e);
+
+
+ public string DestinationDir;
+
+ public string StyleSheet;
+
+ public event ProgressEventHandler Progress;
+
+ private XmlTextWriter writer;
+
+ private CoverageModel model;
+
+ private static string DefaultStyleSheet = "style.xsl";
+
+ private int itemCount;
+
+ private int itemsProcessed;
+
+ private XslTransform transform;
+
+ //
+ // Algorithm: export the data as XML, then transform it into HTML
+ // using a stylesheet
+ //
+
+ public void Export (CoverageModel model) {
+
+ this.model = model;
+
+ if (model.hit + model.missed == 0)
+ return;
+
+ // Why doesn't NET has a Path.GetTempDirectoryName () method ?
+ int index = 0;
+ string tempDir;
+ // Of course this is not safe but it doesn't matter
+ while (true) {
+ tempDir = Path.Combine (Path.Combine (Path.GetTempPath (), "monocov"), "" + index);
+ if (! Directory.Exists (tempDir))
+ break;
+ index ++;
+ }
+ Directory.CreateDirectory (tempDir);
+
+ XmlExporter exporter = new XmlExporter ();
+ exporter.StyleSheet = StyleSheet;
+ exporter.DestinationDir = tempDir;
+ exporter.Progress += new XmlExporter.ProgressEventHandler (progressListener);
+ // Count items
+ itemCount = 1 + model.Classes.Count + model.Namespaces.Count;
+ itemsProcessed = 0;
+
+ exporter.Export (model);
+
+ Directory.Delete (tempDir, true);
+ }
+
+ private void progressListener (object sender, XmlExporter.ProgressEventArgs e) {
+ if (e.fileName != null) {
+ string name = Path.GetFileName (e.fileName);
+
+ if (transform == null) {
+ transform = new XslTransform();
+ transform.Load(Path.Combine (Path.GetDirectoryName (e.fileName), "style.xsl"));
+ }
+
+ try {
+ XsltArgumentList args = new XsltArgumentList ();
+ args.AddParam ("link-suffix", "", ".html");
+ transform.Transform (new XPathDocument (new StreamReader (e.fileName)), args, new StreamWriter (Path.Combine (DestinationDir, name.Replace (".xml", ".html"))));
+ }
+ catch (Exception ex) {
+ Console.WriteLine ("Error: Unable to transform '" + e.fileName + "': " + ex);
+ }
+ }
+
+ if (Progress != null)
+ Progress (this, new ProgressEventArgs (e.item, e.fileName, e.pos, e.itemCount));
+ }
+}
+}
11 LICENSE
@@ -0,0 +1,11 @@
+Copyright (c) 2002 Zoltan Varga
+
+This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source distribution.
30 MANIFEST
@@ -0,0 +1,30 @@
+MANIFEST
+README
+LICENSE
+ChangeLog
+Makefile
+CoverageItem.cs
+NamespaceCoverageItem.cs
+ClassCoverageItem.cs
+MethodCoverageItem.cs
+CoverageModel.cs
+SourceFileCoverageData.cs
+XmlExporter.cs
+HtmlExporter.cs
+style.xsl
+trans.gif
+gui/qt/MonoCov.cs
+gui/qt/CoverageView.cs
+gui/qt/SourceWindow.cs
+gui/qt/FilterDialog.cs
+gui/qt/filterdialog.ui
+gui/gtk/MonoCov.cs
+gui/gtk/CoverageView.cs
+gui/gtk/monocov.glade
+test.cs
+symbols.cs
+nunit-console.cs
+coverage.c
+Qt-monocov.dll
+qtsharp.diff
+corlib-tests.cov
72 Makefile
@@ -0,0 +1,72 @@
+
+PROJECTNAME = monocov
+GUI = qt
+MONO_ROOT = $(HOME)/mono-cvs/mono
+
+all: monocov.exe libmono-profiler-monocov.so symbols.exe nunit-console.exe
+
+ifeq ($(GUI), gtk)
+GUI_SRCS = \
+ gui/gtk/MonoCov.cs \
+ gui/gtk/CoverageView.cs
+GUI_LIBS = -r gtk-sharp.dll -r gdk-sharp.dll -r glib-sharp.dll -r glade-sharp.dll -r gnome-sharp.dll -r System.Drawing -resource:gui/gtk/monocov.glade,monocov.glade
+else
+GUI_SRCS = \
+ gui/qt/MonoCov.cs \
+ gui/qt/CoverageView.cs \
+ gui/qt/SourceWindow.cs \
+ gui/qt/FilterDialog.cs
+GUI_LIBS = -r Qt
+endif
+
+SRCS = \
+ CoverageItem.cs \
+ NamespaceCoverageItem.cs \
+ ClassCoverageItem.cs \
+ MethodCoverageItem.cs \
+ CoverageModel.cs \
+ SourceFileCoverageData.cs \
+ XmlExporter.cs \
+ HtmlExporter.cs \
+ $(GUI_SRCS)
+
+monocov.exe: $(SRCS) style.xsl
+ mcs -g /target:exe /out:$@ -r Mono.CSharp.Debugger -r Mono.GetOptions $(GUI_LIBS) $(SRCS) -resource:style.xsl,style.xsl -resource:trans.gif,trans.gif
+
+symbols.exe: symbols.cs
+ mcs -g /target:exe /out:$@ -r Mono.CSharp.Debugger symbols.cs
+
+nunit-console.exe: nunit-console.cs
+ mcs -r NUnit.Framework -r Mono.GetOptions nunit-console.cs
+
+libmono-profiler-monocov.so: coverage.c
+ $(CC) -g -I$(MONO_ROOT) `pkg-config --cflags glib-2.0` --shared -o $@ $^
+
+test:
+ mcs -g test.cs
+ ./mono --profile=monocov:outfile=res.cov test.exe
+
+cortests:
+ MONO_PATH=$(HOME)/mono-cvs/mcs/class/corlib/Test ./mono --profile=monocov:outfile=corlib-tests.cov,+[corlib] nunit-console.exe corlib_test.dll
+
+export-cortests:
+ ./monocov.exe --export-xml=export corlib-tests.cov
+ tar cvzf corlib-tests.tar.gz export
+
+html-cortests:
+ ./monocov.exe --export-html=html-export corlib-tests.cov
+ tar cvzf html-tests.tar.gz html-export
+
+# MONO_PATH=$(HOME)/mono-cvs/mcs/class/corlib/Test ./mono --profile=monocov:outfile=corlib-tests.cov,-MonoTests,-NUnit,-[System nunit-console.exe -x Security corlib_test.dll
+
+hash-test:
+ ./mono --profile=monocov:+Hashtable hash-table.exe
+
+test-colorizer.exe: test-colorizer.cs SyntaxHighlighter.cs
+ mcs -g /out:$@ $^
+
+clean:
+ rm -f monocov.exe symbols.exe nunit-console.exe
+
+distrib:
+ tar -cvhzf $(PROJECTNAME).tar.gz `cat MANIFEST` && DIRNAME=$(PROJECTNAME)-`date +%d-%b-%y` && rm -rf $$DIRNAME && mkdir $$DIRNAME && mv $(PROJECTNAME).tar.gz $$DIRNAME && cd $$DIRNAME && tar -xzf $(PROJECTNAME).tar.gz && rm $(PROJECTNAME).tar.gz && cd - && tar -cvzf $$DIRNAME.tar.gz $$DIRNAME && rm -rf $$DIRNAME
41 MethodCoverageItem.cs
@@ -0,0 +1,41 @@
+
+using System;
+using System.Collections;
+using Mono.CSharp.Debugger;
+
+namespace MonoCov {
+
+public class MethodCoverageItem : CoverageItem {
+
+ public string name;
+ public int startLine;
+ public int endLine;
+
+ public int[] lineCoverage;
+
+ public MethodCoverageItem (ClassCoverageItem parent, String name) : base (parent) {
+ this.name = name;
+ }
+
+ public ClassCoverageItem Class {
+ get {
+ return (ClassCoverageItem)parent;
+ }
+ }
+
+ public override bool IsLeaf {
+ get {
+ return true;
+ }
+ }
+
+ public string Name {
+ get {
+ return name;
+ }
+ set {
+ name = value;
+ }
+ }
+}
+}
21 NamespaceCoverageItem.cs
@@ -0,0 +1,21 @@
+
+using System;
+using System.Collections;
+
+namespace MonoCov {
+
+public class NamespaceCoverageItem : CoverageItem {
+
+ public string ns;
+
+ public NamespaceCoverageItem (CoverageItem parent, string ns) : base (parent) {
+ this.ns = ns;
+ }
+
+ public NamespaceCoverageItem ParentNamespace {
+ get {
+ return (NamespaceCoverageItem)parent;
+ }
+ }
+}
+}
158 README
@@ -0,0 +1,158 @@
+
+1. INTRODUCTION
+---------------
+
+ MonoCov is a line coverage analysis program for mono. It can be used to
+display coverage data collected while running a .NET program. There are two
+types of GUI interfaces, one implemented using Qt#, while the other is
+implemented using Gtk#. The Qt# version is more advanced.
+
+1.5 REQUIREMENTS
+----------------
+
+The runtime parts are tested with the then current Mono CVS. The Qt# GUI
+requires a patched version of the Qt# library which is included in the
+distribution. This file is named Qt-monocov.dll, and the original Qt.dll should
+be replace with this file. The upcoming Qt# 0.7.1 release fixes most of the
+problems, so this won't be neccessary in the future.
+
+2. USAGE
+--------
+
+2.1 COVERAGE DATA COLLECTION
+----------------------------
+
+To produce coverage info for an .NET program, compile it with the -g
+switch to generate debug information. After this, run the program as follows:
+
+$ ./mono --profile:monocov prog.exe
+
+This will produce a coverage data file called prog.exe.cov. You can run the
+analyser program as follows:
+
+$ ./mono monocov.exe prog.exe.cov
+
+This will display the class hierarchy of the program with the corresponding
+coverage information.
+
+It is also possible to filter the list of classes which need coverage data
+generated. Filters are string which are applied agains the fully qualified
+names of classes, e.g. [assemblyname]classname. You can specify filters
+directly on the command line:
+
+$ ./mono --profile:monocov:-Security,-[System.Xml] prog.exe
+
+There are two types of filters: include filters, whose name begins with '+',
+and exclude filters, whose name begins with '-'. Include filters are checked
+before exclude filters.
+
+For example:
+
+$ ./mono --profile:monocov:+[corlib],-Hashtable prog.exe
+
+This will collect coverage info for all classes in corlib, except the ones
+whose name contains 'Hashtable'.
+
+2.2 ANALYSIS
+------------
+
+ The collected coverage data can be browsed using the monocov.exe program.
+This program will read the data file produced by the profiler module, and
+display its contents in a hierarchical fashion.
+ It is also possible to export the contents of a data file into XML, which
+can be viewed in an XSL capable browser like mozilla.
+To export the data as XML, run monocov like this:
+ monocov.exe --export-xml=<DEST DIR> <DATA FILE NAME>
+
+The generated XML files use a default stylesheet which is a bit ugly. It would
+be good if somebody could contribute a better one :)
+
+2.5 KNOWN BUGS
+--------------
+
+Due to some memory management problems in Qt#, the GUI will most likely crash
+on exit. It will also crash if the user tries to load a second data file.
+
+3. UTILITY PROGRAMS
+-------------------
+
+There are two utility programs included with MonoCov:
+
+- symbols.exe: this program can be used to dump the line number information
+ contained in an mcs generated assembly.
+
+- nunit-console.exe: this is a rewritten version of the original nunit console
+ program. It has the following features:
+ - easier to invoke: no stupid /assembly and /fixture arguments
+ - ability to run test fixtures whose name matches a given pattern, like
+ all the System.IO tests.
+ - ability to exclude tests whose name matches a given pattern.
+ - display of more detailed progress information.
+
+4. TODO
+-------------
+
+- Add ability to run the program from inside the analyzer
+- Add filters (globals/per program)
+- Add HTML export option
+- Handle nested classes more intelligently
+
+5. LICENSE
+----------
+
+zlib/libpng.
+
+6. CONTACT
+----------
+
+Zoltan Varga (vargaz@freemail.hu)
+
+RANDOM NOTES:
+-------------
+
+- There is a bug in QString:~QString (): it should be
+ if (this != Null)
+ qt_del_QString (rawObject);
+
+- Mono finalizes QApplication before QMainWindow, leading to
+ invalid memory read errors.
+
+- Mono frees the the string[] array passed to qt_new_QMainWindow, leading to
+ invalid read errors.
+
+- DecimalTest2:.ctor () causes the register allocator to allocate more than
+ 2^16 registers, overflowing MonoInst->dreg.
+
+- Methods with strange debugging info:
+ - System.Collections.Hashtable..cctor()
+ - SortedList+SynchedSortedList::this [key]
+ - SortedList+SynchedSortedList::Clear ()
+ - System.Text.RegularExpressions.Capture
+
+- If trans.gif is missing from the export directory:
+ - when viewing HTML, it doesn't matter
+ - when viewing XML, it matters
+
+- How can a bar be created without using an image ?
+
+TODO:
+- Add private paths used during data collection to the paths used to search for
+ assemblies.
+- add 'include' and 'exclude' to filters
+- Implement merging of coverage results
+- put namespaces above classes in the hierarchy
+- handle missing source files
+- QDesigner
+- Scintilla
+- html output (with XSLT)
+- use Xml serialization in SyntaxHighlighter
+- speed up test suite generation in nunit (or in mono)
+- namespaces & filtering ???
+- fix StackTrace tests
+- add ability to exclude some appdomains (like the nunit main appdomain)
+ so the tests will run faster.
+- avoid instrumentation for instruction without side effects (like ldc.i4)
+- why does the RSA tests take so much time -> because of entropy generation
+- check in cryptography patches
+- optimize reflection classes by caching the results of get_type_info etc.
+- put monocov into mono CVS.
59 SourceFileCoverageData.cs
@@ -0,0 +1,59 @@
+
+
+using System;
+using System.Collections;
+
+namespace MonoCov {
+
+public class SourceFileCoverageData {
+
+ /// the name of the source file
+ public string sourceFile;
+
+ /// contains line_number->count mappings
+ /// count > 0 -> hit
+ /// count == 0 -> missed
+ /// count == -1 -> comment/declaration/whitespace/no info
+ public int[] coverage;
+
+ // The list of methods which are part of this source file
+ public ArrayList methods;
+
+ public SourceFileCoverageData (string sourceFile) {
+ this.sourceFile = sourceFile;
+ }
+
+ public void AddMethod (MethodCoverageItem method) {
+ if (methods == null)
+ methods = new ArrayList ();
+ methods.Add (method);
+ }
+
+ public int[] Coverage {
+ get {
+ if (coverage != null)
+ return coverage;
+
+ if (methods == null)
+ return new int [0];
+
+ int endLine = 0;
+ foreach (MethodCoverageItem method in methods) {
+ if (method.endLine > endLine)
+ endLine = method.endLine;
+ }
+ coverage = new int [endLine + 1];
+ for (int i = 0; i < coverage.Length; ++i)
+ coverage [i] = -1;
+
+ foreach (MethodCoverageItem method in methods) {
+ if (method.lineCoverage != null) {
+ for (int i = 0; i < method.lineCoverage.Length; ++i)
+ coverage [method.startLine + i] = method.lineCoverage [i];
+ }
+ }
+ return coverage;
+ }
+ }
+}
+}
283 XmlExporter.cs
@@ -0,0 +1,283 @@
+
+using System;
+using System.Collections;
+using System.Text;
+using System.IO;
+using System.Xml;
+
+namespace MonoCov {
+
+public class XmlExporter {
+
+ public class ProgressEventArgs {
+ public CoverageItem item;
+
+ public string fileName;
+
+ public int pos;
+
+ public int itemCount;
+
+ public ProgressEventArgs (CoverageItem item, string fileName,
+ int pos, int itemCount) {
+ this.item = item;
+ this.fileName = fileName;
+ this.pos = pos;
+ this.itemCount = itemCount;
+ }
+ }
+
+ public delegate void ProgressEventHandler (Object sender,
+ ProgressEventArgs e);
+
+
+ public string DestinationDir;
+
+ public string StyleSheet;
+
+ public event ProgressEventHandler Progress;
+
+ private XmlTextWriter writer;
+
+ private CoverageModel model;
+
+ private static string DefaultStyleSheet = "style.xsl";
+
+ private int itemCount;
+
+ private int itemsProcessed;
+
+ public void Export (CoverageModel model) {
+
+ this.model = model;
+
+ if (model.hit + model.missed == 0)
+ return;
+
+ if (StyleSheet == null) {
+ // Use default stylesheet
+ using (StreamReader sr = new StreamReader (typeof (XmlExporter).Assembly.GetManifestResourceStream ("style.xsl"))) {
+ using (StreamWriter sw = new StreamWriter (Path.Combine (DestinationDir, "style.xsl"))) {
+ string line;
+ while ((line = sr.ReadLine ()) != null)
+ sw.WriteLine (line);
+ }
+ }
+ using (Stream s = typeof (XmlExporter).Assembly.GetManifestResourceStream ("trans.gif")) {
+ using (FileStream fs = new FileStream (Path.Combine (DestinationDir, "trans.gif"), FileMode.Create)) {
+ byte[] buf = new byte[1024];
+ int len = s.Read (buf, 0, buf.Length);
+ fs.Write (buf, 0, len);
+ }
+ }
+
+ StyleSheet = DefaultStyleSheet;
+ }
+
+ // Count items
+ itemCount = 1 + model.Classes.Count + model.Namespaces.Count;
+ itemsProcessed = 0;
+
+ WriteProject ();
+ WriteNamespaces ();
+ WriteClasses ();
+ }
+
+ private void WriteStyleSheet () {
+ // The standard says text/xml, while IE6 only understands text/xsl
+ writer.WriteProcessingInstruction ("xml-stylesheet", "href=\"" + StyleSheet + "\" type=\"text/xsl\"");
+ }
+
+ private void WriteProject () {
+ string fileName = Path.Combine (DestinationDir, "project.xml");
+
+ // If I use Encoding.UTF8 here, the file will start with strange
+ // characters
+ writer = new XmlTextWriter (fileName, Encoding.ASCII);
+ writer.Formatting = Formatting.Indented;
+ writer.WriteStartDocument ();
+ WriteStyleSheet ();
+
+ WriteItem (model, typeof (ClassCoverageItem), 999);
+
+ writer.WriteEndDocument ();
+ writer.WriteRaw ("\n");
+ writer.Close ();
+
+ itemsProcessed ++;
+ if (Progress != null)
+ Progress (this, new ProgressEventArgs (model, fileName, itemsProcessed, itemCount));
+ }
+
+ private void WriteItem (CoverageItem item, Type stopLevel, int level) {
+ if (item.filtered)
+ return;
+
+ if (item.hit + item.missed == 0)
+ // Filtered
+ return;
+
+ if (level == 0)
+ return;
+
+ if (item.GetType () == stopLevel)
+ return;
+
+ if (item is CoverageModel) {
+ writer.WriteStartElement ("project");
+ writer.WriteAttributeString ("name", "Project");
+ }
+ else
+ if (item is NamespaceCoverageItem) {
+ NamespaceCoverageItem ns = (NamespaceCoverageItem)item;
+ writer.WriteStartElement ("namespace");
+
+ if (ns.ns == "<GLOBAL>")
+ writer.WriteAttributeString ("name", "GLOBAL");
+ else
+ writer.WriteAttributeString ("name", ns.ns);
+ }
+ else
+ if (item is ClassCoverageItem) {
+ ClassCoverageItem klass = (ClassCoverageItem)item;
+ writer.WriteStartElement ("class");
+ writer.WriteAttributeString ("name", klass.name);
+ writer.WriteAttributeString ("fullname", klass.FullName);
+ }
+
+ WriteCoverage (item);
+
+ if (item.ChildCount > 0)
+ foreach (CoverageItem child in item.children)
+ WriteItem (child, stopLevel, level - 1);
+ writer.WriteEndElement ();
+ }
+
+ private void WriteNamespaces () {
+ foreach (NamespaceCoverageItem ns in model.Namespaces.Values) {
+ bool filtered = false;
+
+ string fileSuffix = ns.ns;
+ if (ns.ns == "<GLOBAL>")
+ fileSuffix = "GLOBAL";
+
+ string fileName =
+ Path.Combine (DestinationDir, String.Format ("namespace-{0}.xml", fileSuffix));
+
+ if (ns.hit + ns.missed == 0)
+ // Filtered
+ filtered = true;
+
+ if (!filtered) {
+ writer = new XmlTextWriter (fileName, Encoding.ASCII);
+ writer.Formatting = Formatting.Indented;
+ writer.WriteStartDocument ();
+ WriteStyleSheet ();
+
+ WriteItem (ns, typeof (MethodCoverageItem), 2);
+
+ writer.WriteEndDocument ();
+ writer.WriteRaw ("\n");
+ writer.Close ();
+ }
+ else
+ fileName = null;
+
+ itemsProcessed ++;
+ if (Progress != null)
+ Progress (this, new ProgressEventArgs (ns, fileName, itemsProcessed, itemCount));
+ }
+ }
+
+ private void WriteClasses () {
+ foreach (ClassCoverageItem item in model.Classes.Values) {
+ bool filtered = false;
+
+ string fileName = Path.Combine (DestinationDir, String.Format ("class-{0}.xml", item.FullName));
+
+ if (item.filtered)
+ filtered = true;
+
+ if (item.hit + item.missed == 0)
+ // Filtered
+ filtered = true;
+
+ if (!filtered) {
+ writer = new XmlTextWriter (fileName, Encoding.ASCII);
+ writer.Formatting = Formatting.Indented;
+ writer.WriteStartDocument ();
+ WriteStyleSheet ();
+
+ WriteClass (item);
+
+ writer.WriteEndDocument ();
+ writer.WriteRaw ("\n");
+ writer.Close ();
+ }
+ else
+ fileName = null;
+
+ itemsProcessed ++;
+ if (Progress != null)
+ Progress (this, new ProgressEventArgs (item, fileName, itemsProcessed, itemCount));
+ }
+ }
+
+ private void WriteClass (ClassCoverageItem item) {
+ if (item.filtered)
+ return;
+
+ writer.WriteStartElement ("class");
+ writer.WriteAttributeString ("name", item.name);
+ writer.WriteAttributeString ("fullname", item.FullName);
+ writer.WriteAttributeString ("namespace", item.Namespace);
+
+ WriteCoverage (item);
+
+ writer.WriteStartElement ("source");
+
+ if (item.sourceFile != null) {
+ writer.WriteAttributeString ("sourceFile", item.sourceFile.sourceFile);
+
+ StreamReader infile = new StreamReader (item.sourceFile.sourceFile, Encoding.ASCII);
+ int[] coverage = item.sourceFile.Coverage;
+ int pos = 1;
+ while (infile.Peek () > -1) {
+ int count;
+ if ((coverage != null) && (pos < coverage.Length))
+ count = coverage [pos];
+ else
+ count = -1;
+ writer.WriteStartElement ("l");
+ writer.WriteAttributeString ("line", "" + pos);
+ writer.WriteAttributeString ("count", "" + count);
+ string line = infile.ReadLine ();
+ writer.WriteString (line);
+ writer.WriteEndElement ();
+
+ pos ++;
+ }
+ }
+
+ writer.WriteEndElement ();
+ }
+
+ private void WriteCoverage (CoverageItem item) {
+
+ double coverage;
+ if (item.hit + item.missed == 0)
+ coverage = 1.0;
+ else
+ coverage = (double)item.hit / (item.hit + item.missed);
+
+ string coveragePercent
+ = String.Format ("{0:###0}", coverage * 100);
+
+ writer.WriteStartElement ("coverage");
+ writer.WriteAttributeString ("hit", item.hit.ToString ());
+ writer.WriteAttributeString ("missed", item.missed.ToString ());
+ writer.WriteAttributeString ("coverage", coveragePercent);
+ writer.WriteEndElement ();
+ }
+}
+}
320 coverage.c
@@ -0,0 +1,320 @@
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "mono/metadata/tabledefs.h"
+#include "mono/metadata/class.h"
+#include "mono/metadata/mono-debug.h"
+#include "mono/metadata/debug-helpers.h"
+#include "mono/metadata/profiler.h"
+
+struct _MonoProfiler {
+ /* Contains the methods for which we have coverage data */
+ GHashTable *methods;
+
+ /* A list of classes for which we are collecting coverage data */
+ GHashTable *classes;
+
+ /* A list of assemblies for which we are collecting coverage data */
+ GHashTable *assemblies;
+
+ char *outfile_name;
+
+ GPtrArray *filters;
+
+ GPtrArray *filters_as_str;
+
+ GHashTable *filtered_classes;
+
+ FILE *outfile;
+};
+
+static void
+add_filter (MonoProfiler *prof, const char *filter);
+
+static void
+assembly_load (MonoProfiler *prof, MonoAssembly *assembly, int result);
+
+static void
+coverage_shutdown (MonoProfiler *prof);
+
+static gboolean
+collect_coverage_for (MonoProfiler *prof, MonoMethod *method);
+
+void
+mono_profiler_startup (char *arg)
+{
+ gchar **ptr;
+ char *filterfile_name = NULL;
+ gchar **args;
+
+ /* Why does the runtime passes the module name to us ? */
+ arg = strstr (arg, ":") + 1;
+ args = g_strsplit (arg ? arg : "", ",", -1);
+
+ MonoProfiler *prof = g_new0 (MonoProfiler, 1);
+
+ prof->methods = g_hash_table_new (NULL, NULL);
+ prof->classes = g_hash_table_new (NULL, NULL);
+ prof->assemblies = g_hash_table_new (NULL, NULL);
+
+ for (ptr = args; ptr && *ptr; ptr++) {
+ const char *arg = *ptr;
+ gchar *message;
+
+ if (strncmp (arg, "filterfile=", 11) == 0)
+ filterfile_name = g_strdup (arg + 11);
+ else
+ if (strncmp (arg, "outfile=", 8) == 0)
+ prof->outfile_name = g_strdup (arg + 8);
+ else
+ if (strncmp (arg, "-", 1) == 0) {
+ add_filter (prof, arg);
+ }
+ else if (strncmp (arg, "+", 1) == 0) {
+ add_filter (prof, arg);
+ }
+ else {
+ message = g_strdup_printf ("Unknown argument '%s'.", arg);
+ fprintf (stderr, "monocov | Error while processing arguments: %s\n", message);
+ g_free (message);
+ }
+ }
+
+ g_strfreev (args);
+
+ if (filterfile_name) {
+ FILE *filterfile;
+
+ filterfile = fopen (filterfile_name, "r");
+ if (!filterfile) {
+ fprintf (stderr, "coverage.c: Unable to open filter file '%s'.\n", filterfile_name);
+ exit (1);
+ }
+
+ while (!feof (filterfile)) {
+ char buf [2048];
+
+ fgets (buf, 2048, filterfile);
+ buf [sizeof (buf) - 1] = '\0';
+
+ if ((buf [0] == '#') || (buf [0] == '\0'))
+ continue;
+
+ if (buf [strlen (buf) - 1] == '\n')
+ buf [strlen (buf) - 1] = '\0';
+
+ add_filter (prof, buf);
+ }
+ fclose (filterfile);
+ }
+
+ mono_profiler_install (prof, coverage_shutdown);
+ mono_profiler_set_events (MONO_PROFILE_INS_COVERAGE | MONO_PROFILE_ASSEMBLY_EVENTS);
+ mono_profiler_install_coverage_filter (collect_coverage_for);
+ mono_profiler_install_assembly (NULL, assembly_load, NULL, NULL);
+}
+
+static void
+assembly_load (MonoProfiler *prof, MonoAssembly *assembly, int result)
+{
+ /* Unfortunately, this doesn't get called... */
+}
+
+static gboolean
+collect_coverage_for (MonoProfiler *prof, MonoMethod *method)
+{
+ int i;
+ char *classname;
+ char *fqn;
+ MonoMethodHeader *header;
+ gboolean has_positive, found;
+
+ if ((method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) ||
+ (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
+ return FALSE;
+
+ if (method->wrapper_type != MONO_WRAPPER_NONE)
+ return FALSE;
+
+ /* Hacky way of determining the executing assembly */
+ if (! prof->outfile_name && (strcmp (method->name, "Main") == 0))
+ prof->outfile_name = g_strdup_printf ("%s.cov", method->klass->image->assembly->image->name);
+
+ /* Check filters */
+ if (prof->filters) {
+ /* Check already filtered classes first */
+ if (g_hash_table_lookup (prof->filtered_classes, method->klass))
+ return FALSE;
+
+ classname = mono_type_get_name (&method->klass->byval_arg);
+
+ fqn = g_strdup_printf ("[%s]%s", method->klass->image->assembly_name, classname);
+
+ // Check positive filters first
+ has_positive = FALSE;
+ found = FALSE;
+ for (i = 0; i < prof->filters->len; ++i) {
+ char *filter = g_ptr_array_index (prof->filters_as_str, i);
+ if (filter [0] == '+') {
+ filter = &filter [1];
+ if (strstr (fqn, filter) != NULL)
+ found = TRUE;
+ has_positive = TRUE;
+ }
+ }
+ if (has_positive && !found)
+ return FALSE;
+
+ for (i = 0; i < prof->filters->len; ++i) {
+ // Is substring search suffices ???
+// GPatternSpec *spec = g_ptr_array_index (filters, i);
+// if (g_pattern_match_string (spec, classname)) {
+ char *filter = g_ptr_array_index (prof->filters_as_str, i);
+ if (filter [0] == '+')
+ continue;
+
+ // Skip '-'
+ filter = &filter [1];
+ if (strstr (fqn, filter) != NULL) {
+ g_hash_table_insert (prof->filtered_classes, method->klass, method->klass);
+ return FALSE;
+ }
+ }
+ g_free (fqn);
+ g_free (classname);
+ }
+
+ header = ((MonoMethodNormal *)method)->header;
+
+ if (header->code_size > 20000) {
+ exit (1);
+ g_warning ("Unable to instrument method %s:%s since it is too complex.", method->klass->name, method->name);
+ return FALSE;
+ }
+
+ g_hash_table_insert (prof->methods, method, method);
+
+ g_hash_table_insert (prof->classes, method->klass, method->klass);
+
+ g_hash_table_insert (prof->assemblies, method->klass->image->assembly, method->klass->image->assembly);
+
+ return TRUE;
+}
+
+static void
+add_filter (MonoProfiler *prof, const char *filter)
+{
+ GPatternSpec *spec;
+
+ if (prof->filters == NULL) {
+ prof->filters = g_ptr_array_new ();
+ prof->filters_as_str = g_ptr_array_new ();
+ prof->filtered_classes = g_hash_table_new (NULL, NULL);
+ }
+
+ g_ptr_array_add (prof->filters, spec);
+ g_ptr_array_add (prof->filters_as_str, g_strdup (filter));
+}
+
+static void
+output_filters (MonoProfiler *prof, FILE *outfile)
+{
+ int i;
+
+ if (prof->filters) {
+ for (i = 0; i < prof->filters_as_str->len; ++i) {
+ char *str = g_ptr_array_index (prof->filters_as_str, i);
+
+ fprintf (outfile, "\t<filter pattern=\"%s\"/>\n",
+ g_markup_escape_text (str, strlen (str)));
+ }
+ }
+}
+
+static void
+output_assembly (MonoAssembly *assembly, MonoAssembly *assembly2, FILE *outfile)
+{
+ fprintf (outfile, "\t<assembly name=\"%s\" guid=\"%s\"/>\n", assembly->image->assembly_name, assembly->image->guid);
+}
+
+static int count;
+static int prev_offset;
+
+static void
+output_entry (MonoProfiler *prof, const MonoProfileCoverageEntry *entry)
+{
+ count ++;
+ if ((count % 8) == 0)
+ fprintf (prof->outfile, "\n\t\t");
+
+ fprintf (prof->outfile, "%d %d\t", entry->iloffset - prev_offset, entry->counter);
+ prev_offset = entry->iloffset;
+}
+
+static void
+output_method (MonoMethod *method, gpointer dummy, MonoProfiler *prof)
+{
+ MonoMethodHeader *header;
+ int i;
+ char *classname;
+ char *tmpsig;
+ FILE *outfile;
+
+ outfile = prof->outfile;
+ header = ((MonoMethodNormal *)method)->header;
+
+ tmpsig = mono_signature_get_desc (method->signature, TRUE);
+ tmpsig = g_markup_escape_text (tmpsig, strlen (tmpsig));
+
+ classname = mono_type_get_name (&method->klass->byval_arg);
+
+ fprintf (outfile, "\t<method assembly=\"%s\" class=\"%s\" name=\"%s (%s)\" token=\"%d\">\n",
+ method->klass->image->assembly_name,
+ classname, method->name,
+ tmpsig, method->token);
+
+ g_free (tmpsig);
+ fprintf (outfile, "\t\t");
+ count = 0;
+ prev_offset = 0;
+
+ mono_profiler_coverage_get (prof, method, output_entry);
+
+ fprintf (outfile, "\n");
+ fprintf (outfile, "\t</method>\n");
+}
+
+static void
+coverage_shutdown (MonoProfiler *prof)
+{
+ FILE *outfile;
+
+ if (!prof->outfile_name)
+ prof->outfile_name = g_strdup ("/dev/stdout");
+
+ printf ("Dumping coverage data to %s ...\n", prof->outfile_name);
+
+ outfile = fopen (prof->outfile_name, "w");
+ if (!outfile) {
+ fprintf (stderr, "coverage: unable to create result file %s: %s.\n",
+ prof->outfile_name, strerror (errno));
+ return;
+ }
+ prof->outfile = outfile;
+
+ fprintf (outfile, "<?xml version=\"1.0\"?>\n");
+ fprintf (outfile, "<coverage>\n");
+
+ output_filters (prof, outfile);
+
+ g_hash_table_foreach (prof->assemblies, (GHFunc)output_assembly, outfile);
+
+ g_hash_table_foreach (prof->methods, (GHFunc)output_method, prof);
+
+ fprintf (outfile, "</coverage>\n");
+ fclose (outfile);
+
+ printf ("Done.\n");
+}
223 gui/gtk/CoverageView.cs
@@ -0,0 +1,223 @@
+
+
+using Gtk;
+using GLib;
+using Gdk;
+using Gnome;
+using Glade;
+using GtkSharp;
+
+using System;
+using System.Collections;
+
+namespace MonoCov.Gui.Gtk {
+
+public class CoverageView {
+
+ public class TreeItem {
+
+ public string title;
+
+ public TreeStore store;
+
+ public TreeItem parent;
+
+ public TreeIter iter;
+
+ public CoverageItem model;
+
+ public TreeItem (TreeStore store, TreeItem parent, CoverageItem model,
+ string title) {
+ this.store = store;
+ this.parent = parent;
+ this.model = model;
+
+ if (parent == null)
+ iter = store.AppendValues (title);
+ else
+ iter = store.AppendValues (parent.Iter, title);
+ FillColumns ();
+ }
+
+ public TreeIter Iter {
+ get {
+ return iter;
+ }
+
+ set {
+ this.iter = value;
+ }
+ }
+
+ public void FillColumns () {
+ double coverage;
+
+ if (model.hit + model.missed == 0)
+ coverage = 1.0;
+ else
+ coverage = (double)model.hit / (model.hit + model.missed);
+
+ store.SetValue (iter, 1, "" + model.hit);
+ store.SetValue (iter, 2, "" + model.missed);
+
+ string coveragePercent
+ = String.Format ("{0:###0}", coverage * 100);
+
+ store.SetValue (iter, 3, coveragePercent + "%");
+ }
+ }
+
+ public static string[] DEFAULT_FILTERS = {
+ "-PrivateImplementationDetails"
+ };
+
+ private TreeView tree;
+ private Hashtable namespaces;
+ private Hashtable classes;
+
+ public CoverageView (string fileName) {
+ TreeStore store = new TreeStore (typeof (string), typeof (string),
+ typeof (string), typeof (string));
+
+
+ tree = new TreeView (store);
+
+ CellRendererText renderer = new CellRendererText ();
+ // LAME: Why is this property a float instead of a double ?
+ renderer.Xalign = 0.5f;
+
+ tree.AppendColumn ("Classes", new CellRendererText (), "text", 0);
+ tree.AppendColumn ("Lines Hit", renderer, "text", 1);
+ tree.AppendColumn ("Lines Missed", renderer, "text", 2);
+ tree.AppendColumn ("Coverage", renderer, "text", 3);
+
+ tree.GetColumn (0).Resizable = true;
+ tree.GetColumn (1).Alignment = 0.5f;
+ tree.GetColumn (1).Resizable = true;
+ tree.GetColumn (2).Alignment = 0.5f;
+ tree.GetColumn (2).Resizable = true;
+ tree.GetColumn (3).Alignment = 0.5f;
+ tree.GetColumn (3).Resizable = true;
+
+ tree.HeadersVisible = true;
+
+ CoverageModel model = new CoverageModel ();
+ foreach (string filter in DEFAULT_FILTERS) {
+ model.AddFilter (filter);
+ }
+ model.ReadFromFile (fileName);
+
+ TreeItem root = new TreeItem (store, null, model, "PROJECT");
+
+ Hashtable classes2 = model.Classes;
+
+ namespaces = new Hashtable ();
+ foreach (string name in classes2.Keys) {
+ ClassCoverageItem klass = (ClassCoverageItem)classes2 [name];
+
+ if (klass.filtered)
+ continue;
+
+ string namespace2 = klass.name_space;
+ TreeItem nsItem = (TreeItem)namespaces [namespace2];
+ if (nsItem == null) {
+ nsItem = new TreeItem (store, root, (CoverageItem)model.Namespaces [namespace2], namespace2);
+ // nsItem.SetPixmap (0, namespaceOpenPixmap);
+ namespaces [namespace2] = nsItem;
+ }
+
+ if (nsItem.model.filtered)
+ continue;
+
+ TreeItem classItem = new TreeItem (store, nsItem, klass, klass.name);
+ // classItem.SetPixmap (0, classPixmap);
+
+ // We should create the method nodes only when the class item
+ // is opened
+
+ foreach (MethodCoverageItem method in klass.Methods) {
+ if (method.filtered)
+ continue;
+
+ string title = method.Name;
+ if (title.Length > 64)
+ title = title.Substring (0, 63) + "...)";
+
+ TreeItem methodItem = new TreeItem (store, classItem, method, title);
+ }
+ }
+
+ tree.ExpandRow (store.GetPath (root.Iter), false);
+
+ foreach (string ns in namespaces.Keys)
+ tree.ExpandRow (store.GetPath (((TreeItem)namespaces [ns]).Iter), false);
+
+ tree.ButtonPressEvent += new ButtonPressEventHandler (OnDoubleClick);
+ tree.Selection.Mode = SelectionMode.Single;
+
+ // LAME: Why doesn't widgets visible by default ???
+ tree.Show ();
+ }
+
+ private void OnDoubleClick (object o, ButtonPressEventArgs args) {
+ if (args.Event.type == Gdk.EventType.TwoButtonPress) {
+ // LAME: This should be the default... :(
+ TreeModel model;
+ TreeIter iter = new TreeIter ();
+ if (tree.Selection.GetSelected (out model, ref iter))
+ if (tree.RowExpand (model.GetPath (iter))) {
+ // LAME: This seems to collapse the entire tree...
+ tree.CollapseRow (model.GetPath (iter));
+ }
+ else {
+ tree.ExpandRow (model.GetPath (iter), false);
+ }
+ }
+ }
+
+ /*
+ public void SlotDoubleClick (QListViewItem item) {
+ if (item is MethodItem) {
+ MethodItem method = (MethodItem)item;
+
+ ClassItem parent = method.parent;
+
+ if (parent.sourceBrowser != null) {
+ parent.sourceBrowser.ShowNormal ();
+ parent.sourceBrowser.Raise ();
+ }
+ else {
+ parent.sourceBrowser = new SourceWindow (this, parent);
+ parent.sourceBrowser.Show ();
+ }
+
+ parent.sourceBrowser.CenterOnMethod (method);
+ }
+ }
+ */
+
+ public Widget Widget {
+ get {
+ return tree;
+ }
+ }
+
+ private void FillCoverageColumns (TreeItem item, int hit, int missed) {
+ double coverage;
+
+ if (hit + missed == 0)
+ coverage = 100.0;
+ else
+ coverage = (double)hit / (hit + missed);
+
+ item.store.SetValue (item.iter, 1, "" + hit);
+ item.store.SetValue (item.iter, 2, "" + missed);
+
+ string coveragePercent
+ = String.Format ("{0:##0}%", coverage * 100);
+
+ item.store.SetValue (item.iter, 3, coveragePercent);
+ }
+
+}
+}
151 gui/gtk/MonoCov.cs
@@ -0,0 +1,151 @@
+
+//
+// TODO:
+// - Why there is no implementation for Diagnostics.SymbolStore for mono???
+// - the line number information generated for some methods is funky, like
+// System.Text.Encoding::get_ASCII
+// - the line number information does not contain columns, making it
+// impossible to determine coverage for code like this:
+// if (something) { something }
+// - mono does not respect precision specifiers eg.
+// String.Format ("{0:f2}", 3.141592377) returns 3.14159....
+//
+
+//
+// GTK# impressions:
+// - there are no convinience functions, like new FooWidget (parent)
+// - the generated sources are hard to read because the API functions are
+// mixed with internal functions, export directives etc.
+// - the app is slow to start up, slower than Qt#. Afterwards, it is faster.
+// - the open file dialog requires 30 lines in Gtk#, and 2 lines in Qt#
+// - no way to set a file name filter in FileSelection(Dialog)
+// - Why can't I inherit from Widget as in Qt ???
+//
+
+using Gtk;
+using Gnome;
+using Glade;
+using GtkSharp;
+
+using System;
+using System.IO;
+using System.Drawing;
+
+namespace MonoCov.Gui.Gtk {
+
+public class MonoCov {
+
+ private static string CAPTION = "MonoCov 0.1";
+
+ private FileSelection openDialog;
+
+ private CoverageView coverageView;
+
+ Glade.XML xml;
+
+ Window main;
+
+ public MonoCov () {
+ xml = new Glade.XML (typeof (MonoCov).Assembly, "monocov.glade",
+ null, null);
+
+ main = (Window)xml ["main"];
+ main.Title = CAPTION;
+
+ xml.Autoconnect (this);
+ }
+
+ public static int Main (String[] args) {
+
+ Application.Init ();
+
+ MonoCov main = new MonoCov ();
+
+ if (args.Length > 0)
+ main.OpenFile (args[0]);
+
+ Application.Run ();
+
+ return 0;
+ }
+
+ public void OnQuit (object o, EventArgs args) {
+ Application.Quit ();
+ }
+
+ public void OnAbout (object o, EventArgs args) {
+ MessageDialog dialog =
+ new MessageDialog (main, DialogFlags.Modal, MessageType.Info,
+ ButtonsType.Ok,
+ "A Coverage Analysis program for MONO.\n" +
+ "By Zoltan Varga (vargaz@freemail.hu)\n" +
+ "Powered by\n" +
+ "MONO (http://www.go-mono.com)\n" +
+ "and Gtk# (http://gtk-sharp.sourceforge.net)");
+ dialog.Run ();
+ dialog.Destroy ();
+ }
+
+ private void OpenFile (string fileName) {
+ // if (coverageView != null)
+ // coverageView.Close (true);
+
+ coverageView = new CoverageView (fileName);
+
+ // TODO: How to tell Qt to set a good size automatically ???
+ // coverageView.SetMinimumSize (coverageView.SizeHint ());
+ // this.SetCentralWidget (coverageView);
+
+ main.Title = (CAPTION + " - " + new FileInfo (fileName).Name);
+
+ // Console.WriteLine ("A: " + (Container)xml ["bonobodock1"]);
+ ((Container)xml ["scrolledwindow1"]).Add (coverageView.Widget);
+
+ main.ShowAll ();
+ }
+
+ public void delete_cb (object o, DeleteEventArgs args) {
+ SignalArgs sa = (SignalArgs) args;
+ Application.Quit ();
+ sa.RetVal = true;
+ }
+
+ public void file_sel_delete_event (object o, DeleteEventArgs args) {
+ if (openDialog != null)
+ openDialog.Destroy ();
+ }
+
+ public void file_sel_ok_event (object o, EventArgs args) {
+ string fileName = openDialog.Filename;
+ openDialog.Destroy ();
+ OpenFile (fileName);
+ }
+
+ public void file_sel_cancel_event (object o, EventArgs args) {
+ openDialog.Destroy ();
+ openDialog = null;
+ }
+
+ public void OnOpen (object o, EventArgs args) {
+ FileSelection dialog =
+ new FileSelection ("Choose a file");
+
+ // TODO: How to set a filter ???
+
+ dialog.HideFileopButtons ();
+
+ dialog.Filename = "*.cs";
+
+ dialog.DeleteEvent += new DeleteEventHandler (file_sel_delete_event);
+
+ dialog.OkButton.Clicked +=new EventHandler (file_sel_ok_event);
+
+ dialog.CancelButton.Clicked +=new EventHandler (file_sel_cancel_event);
+
+ openDialog = dialog;
+
+ dialog.Modal = true;
+ dialog.ShowAll ();
+ }
+}
+}
121 gui/gtk/monocov.glade
@@ -0,0 +1,121 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="main">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">window1</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">600</property>
+ <property name="default_height">400</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <signal name="delete_event" handler="OnQuit" last_modification_time="Thu, 29 May 2003 13:53:18 GMT"/>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menuitem4_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="open1">
+ <property name="visible">True</property>
+ <property name="label">gtk-open</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="OnOpen" last_modification_time="Thu, 29 May 2003 13:54:37 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="quit1">
+ <property name="visible">True</property>
+ <property name="label">gtk-quit</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="OnQuit" last_modification_time="Thu, 29 May 2003 13:54:37 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menuitem7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menuitem7_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="about1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_About</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="OnAbout" last_modification_time="Thu, 29 May 2003 13:54:37 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
350 gui/qt/CoverageView.cs
@@ -0,0 +1,350 @@
+
+using Qt;
+using System;
+using System.Collections;
+using System.Text.RegularExpressions;
+
+namespace MonoCov.Gui.Qt {
+
+public class TreeItem : QListViewItem {
+ public CoverageItem model;
+
+ public TreeItem (QListViewItem parent, String name, CoverageItem model) :
+ base (parent, name) {
+ this.model = model;
+
+ FillColumns ();
+ }
+
+ public TreeItem (QListView parent, String name, CoverageItem model) :
+ base (parent, name) {
+ this.model = model;
+
+ FillColumns ();
+ }
+
+ public void FillColumns () {
+ double coverage;
+
+ if (model.hit + model.missed == 0)
+ coverage = 1.0;
+ else
+ coverage = (double)model.hit / (model.hit + model.missed);
+
+ SetText (1, "" + model.hit);
+ SetText (2, "" + model.missed);
+
+ string coveragePercent
+ = String.Format ("{0:###0}", coverage * 100);
+
+ SetText (3, coveragePercent + "%");
+ }
+}
+
+public class NamespaceItem : TreeItem {
+ public NamespaceItem (QListViewItem parent, String name, CoverageItem model) :
+ base (parent, name, model) {
+ }
+}
+
+public class ClassItem : TreeItem {
+ public ClassItem (QListViewItem parent, String name, CoverageItem model) :
+ base (parent, name, model) {
+ }
+
+ public ClassCoverageItem Model {
+ get {
+ return (ClassCoverageItem)model;
+ }
+ }
+}
+
+public class MethodItem : TreeItem {
+ public MethodItem (QListViewItem parent, String name, CoverageItem model) :
+ base (parent, name, model) {
+ }
+
+ public MethodCoverageItem Model {
+ get {
+ return (MethodCoverageItem)model;
+ }
+ }
+}
+
+public class CoverageView : QListView {
+
+ public static string[] DEFAULT_FILTERS = {
+ "-PrivateImplementationDetails"
+ };
+
+ // pixmaps stolen from some Qt example program...
+ static string[] namespace_open_xpm = new string[] {
+ "16 16 11 1",
+ "# c #000000",
+ "g c #c0c0c0",
+ "e c #303030",
+ "a c #ffa858",
+ "b c #808080",
+ "d c #a0a0a4",
+ "f c #585858",
+ "c c #ffdca8",
+ "h c #dcdcdc",
+ "i c #ffffff",
+ ". c None",
+ "....###.........",
+ "....#ab##.......",
+ "....#acab####...",
+ "###.#acccccca#..",
+ "#ddefaaaccccca#.",
+ "#bdddbaaaacccab#",
+ ".eddddbbaaaacab#",
+ ".#bddggdbbaaaab#",
+ "..edgdggggbbaab#",
+ "..#bgggghghdaab#",
+ "...ebhggghicfab#",
+ "....#edhhiiidab#",
+ "......#egiiicfb#",
+ "........#egiibb#",
+ "..........#egib#",
+ "............#ee#"};
+
+ static string[] namespace_closed_xpm = new string[] {
+ "16 16 9 1",
+ "g c #808080",
+ "b c #c0c000",
+ "e c #c0c0c0",
+ "# c #000000",
+ "c c #ffff00",
+ ". c None",
+ "a c #585858",
+ "f c #a0a0a4",
+ "d c #ffffff",
+ "..###...........",
+ ".#abc##.........",
+ ".#daabc#####....",
+ ".#ddeaabbccc#...",
+ ".#dedeeabbbba...",
+ ".#edeeeeaaaab#..",
+ ".#deeeeeeefe#ba.",
+ ".#eeeeeeefef#ba.",
+ ".#eeeeeefeff#ba.",
+ ".#eeeeefefff#ba.",
+ ".##geefeffff#ba.",
+ "...##gefffff#ba.",
+ ".....##fffff#ba.",
+ ".......##fff#b##",
+ ".........##f#b##",
+ "...........####."};
+
+ static string[] class_xpm = new string[] {
+ "16 16 7 1",
+ "# c #000000",
+ "b c #ffffff",
+ "e c #000000",
+ "d c #404000",
+ "c c #c0c000",
+ "a c #ffffc0",
+ ". c None",
+ "................",
+ ".........#......",
+ "......#.#a##....",
+ ".....#b#bbba##..",
+ "....#b#bbbabbb#.",
+ "...#b#bba##bb#..",
+ "..#b#abb#bb##...",
+ ".#a#aab#bbbab##.",
+ "#a#aaa#bcbbbbbb#",
+ "#ccdc#bcbbcbbb#.",
+ ".##c#bcbbcabb#..",
+ "...#acbacbbbe...",
+ "..#aaaacaba#....",
+ "...##aaaaa#.....",
+ ".....##aa#......",
+ ".......##......."};
+
+ private QListView table;
+ private Hashtable namespaces;
+ private Hashtable sourceViews;
+ private CoverageModel model;
+
+ public CoverageView (QWidget parent, String fileName) : base (parent) {
+ SetRootIsDecorated (true);
+ AddColumn ("Classes");
+ AddColumn ("Lines Hit");
+ AddColumn ("Lines Missed");
+ AddColumn ("Coverage");
+
+ sourceViews = new Hashtable ();
+
+ // TODO: Why the cast ?
+ SetColumnAlignment (1, (int)Qt.AlignmentFlags.AlignCenter);
+ SetColumnAlignment (2, (int)Qt.AlignmentFlags.AlignCenter);
+ SetColumnAlignment (3, (int)Qt.AlignmentFlags.AlignCenter);
+
+ QObject.Connect (this, SIGNAL ("doubleClicked(QListViewItem)"),
+ this, SLOT ("OnDoubleClick(QListViewItem)"));
+
+ QObject.Connect (this, SIGNAL ("expanded(QListViewItem)"),
+ this, SLOT ("OnExpanded(QListViewItem)"));
+
+ // TODO: This is not supported by current Qt#
+ try {
+ QObject.Connect (this, SIGNAL ("contextMenuRequested(QListViewItem,QPoint,int)"),
+ this, SLOT ("OnContextMenu(QListViewItem, QPoint, Int32)"));
+ }
+ catch (Exception) {
+ }
+
+ QPixmap namespaceOpenPixmap = new QPixmap (namespace_open_xpm);
+ QPixmap namespaceClosedPixmap = new QPixmap (namespace_closed_xpm);
+ QPixmap classPixmap = new QPixmap (class_xpm);
+
+ model = new CoverageModel ();
+ foreach (string filter in DEFAULT_FILTERS) {
+ model.AddFilter (filter);
+ }
+ model.ReadFromFile (fileName);
+
+ QListViewItem rootItem = new TreeItem (this, "PROJECT", model);
+ rootItem.SetOpen (true);
+
+ Hashtable classes2 = model.Classes;
+
+ namespaces = new Hashtable ();
+
+ foreach (string name in classes2.Keys) {
+ ClassCoverageItem klass = (ClassCoverageItem)classes2 [name];
+
+ if (klass.filtered)
+ continue;
+
+ string namespace2 = klass.name_space;
+ NamespaceItem nsItem = (NamespaceItem)namespaces [namespace2];
+ if (nsItem == null) {
+ // Create namespaces
+ String nsPrefix = "";
+ QListViewItem parentItem = rootItem;
+ foreach (String nsPart in namespace2.Split ('.')) {
+ if (nsPrefix == "")
+ nsPrefix = nsPart;
+ else
+ nsPrefix = nsPrefix + "." + nsPart;
+
+ NamespaceCoverageItem nsModel = (NamespaceCoverageItem)model.Namespaces [nsPrefix];
+ if (nsModel.filtered)
+ break;
+
+ nsItem = (NamespaceItem)namespaces [nsPrefix];
+ if (nsItem == null) {
+ nsItem = new NamespaceItem (parentItem, nsPrefix,
+ nsModel);
+ nsItem.SetOpen (true);
+ nsItem.SetPixmap (0, namespaceOpenPixmap);
+ namespaces [nsPrefix] = nsItem;
+ }
+ parentItem = nsItem;
+ }
+ }
+
+ if (nsItem != null) {
+ ClassItem classItem = new ClassItem (nsItem, klass.name, klass);
+ classItem.SetPixmap (0, classPixmap);
+ if (klass.ChildCount > 0)
+ classItem.SetExpandable (true);
+ }
+ }
+ }
+
+ private SourceWindow ShowSourceFor (ClassItem item) {
+ SourceWindow sourceView = (SourceWindow)sourceViews [item.Model.sourceFile];
+ if (sourceView != null) {
+ sourceView.ShowNormal ();
+ sourceView.Raise ();
+ }
+ else {
+ sourceView = new SourceWindow (this, item);
+ sourceViews [item.Model.sourceFile] = sourceView;
+ sourceView.Show ();
+ }
+ return sourceView;
+ }
+
+ public void exportAsXml (string destDir) {
+ XmlExporter exporter = new XmlExporter ();
+ exporter.DestinationDir = destDir;
+ exporter.Export (model);
+ Environment.Exit (1);
+ }
+
+ private void OnDoubleClick (QListViewItem item) {
+ if (item is MethodItem) {
+ MethodItem method = (MethodItem)item;
+
+ SourceWindow sourceView = ShowSourceFor ((ClassItem)method.Parent ());
+
+ sourceView.CenterOnMethod (method);
+ }
+ }
+
+ private void OnViewSource () {
+ ShowSourceFor ((ClassItem)popupMenuItem);
+ }
+
+ private void OnExpanded (QListViewItem item) {
+ if (item is ClassItem) {
+ // Create children on-demand
+ ClassItem classItem = (ClassItem)item;
+ ClassCoverageItem klass = classItem.Model;
+
+ if (item.ChildCount () == 0) {
+ foreach (MethodCoverageItem method in klass.Methods) {
+ string title = method.name;
+ if (title.Length > 64)
+ title = title.Substring (0, 63) + "...)";
+
+ MethodItem methodItem = new MethodItem (classItem, title, method);
+ }
+ }
+ }
+ }
+
+ private QListViewItem popupMenuItem;
+
+ private void OnContextMenu (QListViewItem item, QPoint point, int col) {
+ popupMenuItem = item;
+
+ if (item is ClassItem) {
+ ClassItem klass = (ClassItem)item;
+
+ QPopupMenu menu = new QPopupMenu (this);
+
+ menu.InsertItem ("Exclude from coverage", this, SLOT ("OnExclude()"));
+
+ if (klass.Model.sourceFile != null)
+ menu.InsertItem ("View Source", this, SLOT ("OnViewSource()"));
+
+ menu.Exec (point);
+ }
+ else if (item is NamespaceItem) {
+ QPopupMenu menu = new QPopupMenu (this);
+
+ menu.InsertItem ("Exclude from coverage", this, SLOT ("OnExclude()"));
+ menu.Exec (point);
+ }
+ }
+
+ private void OnExclude () {
+ TreeItem item = (TreeItem)popupMenuItem;
+ if (item != null) {
+ item.model.FilterItem (true);
+
+ QListViewItem parent = item.Parent ();
+ parent.TakeItem (item);
+ while (parent is TreeItem) {
+ ((TreeItem)parent).FillColumns ();
+ parent = parent.Parent ();
+ }
+ }
+ }
+}
+}
38 gui/qt/FilterDialog.cs
@@ -0,0 +1,38 @@
+
+using Qt;
+using System;
+using System.IO;
+using System.Text;
+using System.Collections;
+
+namespace MonoCov.Gui.Qt {
+
+public class FilterDialog {
+
+ QDialog dialog;
+
+ ArrayList filters = new ArrayList ();
+ ArrayList enabled = new ArrayList ();
+
+ public FilterDialog (QDialog dialog) {
+ this.dialog = dialog;
+
+ filters.Add ("NOT YET");
+ enabled.Add (true);
+
+ // TODO: Add indexer
+ QListView list = (QListView)dialog.Child ("list").QtCast ();
+ list.SetResizeMode (QListView.ResizeMode.LastColumn);
+
+ for (int i = 0; i < filters.Count; ++i) {
+ string pattern = (string)filters [i];
+ QCheckListItem item = new QCheckListItem (list, pattern, QCheckListItem.Type.CheckBox);
+ item.SetOn ((bool)enabled [i]);
+ }
+ }
+
+ public void Show () {
+ dialog.Show ();
+ }
+}
+}
234 gui/qt/MonoCov.cs
@@ -0,0 +1,234 @@
+
+//
+// TODO:
+// - the line number information generated for some methods is funky, like
+// System.Text.Encoding::get_ASCII
+// - the line number information for System.CurrentTimeZone:GetDaylightChanges
+// contains a 0->196 mapping !!!
+// - the line number information for life.cs does not contain anything for line
+// 38.
+// - when a method signature consists of multiple lines, the begin_line for
+// the methods seems to be the last line of the signature, instead of the
+// first.
+// - the line number information does not contain columns, making it
+// impossible to determine coverage for code like this:
+// if (something) { something }
+// - why doesn't some line numbers do not appear in stack traces ????
+
+// QT# problems:
+// - open a second .cov file + exit -> crash in qt_del_QListViewItem
+// - open a second .cov file + click on class name -> strange things happen
+// - unable to override QListViewItem::SetOpen() because it is not virtual
+// - themes does not work (i.e. new QPlatinumStyle () crashes)
+//
+
+using Qt;
+using System;
+using System.Reflection;
+using System.IO;
+using System.Text;
+using Mono.GetOptions;
+
+[assembly: AssemblyTitle("monocov")]
+[assembly: AssemblyDescription("A Coverage Analysis program for .NET")]
+[assembly: AssemblyCopyright("Copyright (C) 2003 Zoltan Varga")]
+[assembly: Mono.Author("Zoltan Varga (vargaz@freemail.hu)")]
+[assembly: AssemblyVersion("0.1")]
+[assembly: Mono.UsageComplement("[<datafile>]")]
+[assembly: Mono.About("")]
+
+namespace MonoCov.Gui.Qt {
+