/// <summary> /// Create a TreeGridNode for a coverage item. /// </summary> /// <param name="parent">Parent of the tree node.</param> /// <param name="item">Coverage item.</param> /// <returns>TreeGridNode for the coverage item.</returns> private TreeGridNode CreateCoverageNode(TreeGridNode parent, CoverageItem item) { TreeGridNode node = new TreeGridNode { Tag = item }; ((parent != null) ? parent.Nodes : _grdCoverage.Nodes).Add(node); node.SetValues(item.Name, item.UncoveredBlocks, item.UncoveredBlocksPercentage, item.CoveredBlocks, item.CoveredBlocksPercentage, null); switch (item.ItemType) { case CoverageItemType.Assembly: node.ImageIndex = 0; node.Expand(); break; case CoverageItemType.Namespace: node.ImageIndex = 1; node.Expand(); break; case CoverageItemType.Type: node.ImageIndex = 2; node.Expand(); break; case CoverageItemType.Method: node.ImageIndex = 3; break; } return(node); }
/// <summary> /// View the coverage data for a specific item. /// </summary> /// <param name="sender">DataGrid cell.</param> /// <param name="e">Event arguments.</param> private void OnCoverageCellDoubleClick(object sender, DataGridViewCellEventArgs e) { // Get the node for the clicked row TreeGridNode node = _grdCoverage.GetNodeForRow(e.RowIndex); if (node != null) { // Get the associated coverage item CoverageItem item = node.Tag as CoverageItem; if (item != null) { // Walk down the children until we have a file while (string.IsNullOrEmpty(item.File) && item.Children.Count > 0) { item = item.Children[0]; } // Change to the file if we can if (!string.IsNullOrEmpty(item.File)) { ViewCoverageItem(item); } } } }
/// <summary> /// View the coverage data for an item. /// </summary> /// <param name="item">Coverage item to view.</param> private void ViewCoverageItem(CoverageItem item) { if (SelectedItem == item) { return; } SelectedItem = item; // Only change the source tab if the file is different if (string.Compare(SelectedFile, item.File, StringComparison.OrdinalIgnoreCase) != 0) { SelectedFile = item.File; _lblFile.Text = Path.GetFileName(item.File); _txtSource.Text = File.ReadAllText(item.File); _cmbIRMethods.Items.Clear(); // Highlight the file List <CoverageItem> intervals; if (Highlighting.TryGetValue(item.File, out intervals)) { foreach (CoverageItem interval in intervals) { Color highlight = GetHighlightColor(interval.Covered); for (int line = interval.StartLine; line <= interval.EndLine; line++) { int start = (line == interval.StartLine) ? interval.StartColumn : 0; int end = (line == interval.EndLine) ? interval.EndColumn : -1; HighlightLine(_txtSource, line, start, end, highlight); } } } // Fill the LIR drop down CoverageItem typeItem = item.Parent; foreach (CoverageItem methodItem in typeItem.Children) { _cmbIRMethods.Items.Add(methodItem); } } // Scroll to the desired line in the source tab (though we move 5 // lines up in the file since the method lines start at the first // instruction instead of the declaration) _txtSource.Select(_txtSource.GetFirstCharIndexFromLine(Math.Max(0, item.StartLine - 5)), 0); _txtSource.ScrollToCaret(); // Select the item in the LIR drop down _cmbIRMethods.SelectedItem = SelectedItem; }
/// <summary> /// Load code coverage data. /// </summary> /// <param name="path">Path to the coverage data.</param> /// <param name="newPathPrefix">New path prefix for files.</param> public void LoadCoverageData(string path, string newPathPrefix) { // Clear any existing values _grdCoverage.Nodes.Clear(); SelectedFile = null; _lblFile.Text = null; _txtSource.Text = null; // Load the coverage data CoverageDataPath = path; CoverageData = XDocument.Load(path); IEnumerable <CoverageItem> data = CoverageItem.Parse(CoverageData, newPathPrefix); Highlighting = new Dictionary <string, List <CoverageItem> >(); // Populate the grid foreach (CoverageItem assemblyItem in data) { TreeGridNode assemblyNode = CreateCoverageNode(null, assemblyItem); foreach (CoverageItem namespaceItem in assemblyItem.Children) { TreeGridNode namespaceNode = CreateCoverageNode(assemblyNode, namespaceItem); foreach (CoverageItem typeItem in namespaceItem.Children) { TreeGridNode typeNode = CreateCoverageNode(namespaceNode, typeItem); foreach (CoverageItem methodItem in typeItem.Children) { CreateCoverageNode(typeNode, methodItem); foreach (CoverageItem blockItem in methodItem.Children) { List <CoverageItem> intervals = null; if (!Highlighting.TryGetValue(blockItem.File, out intervals)) { intervals = new List <CoverageItem>(); Highlighting[blockItem.File] = intervals; } intervals.Add(blockItem); } } typeNode.Collapse(); } } } // Sort the highlights (see HighlightComparer for more details) foreach (List <CoverageItem> list in Highlighting.Values) { list.Sort(CoverageItem.HighlightComparer); } }
/// <summary> /// Compare the highlight ordering of two coverage items. /// </summary> /// <param name="first">First coverage item.</param> /// <param name="second">Second coverage item.</param> /// <returns> /// 0 if the items are equal, -1 if first comes before second, or 1 if /// second comes before first. /// </returns> public static int HighlightComparer(CoverageItem first, CoverageItem second) { // Basic blocks will only overlap by nesting one inside the other. // Therefore we want to sort with the largest containing blocks at // the front of the list (so they will be "painted" first) if (first == null) { // If one or both are null return((second == null) ? 0 : 1); } else if (first == second) { // If they're the same object return(0); } else if (first.StartLine <= second.StartLine && first.EndLine >= second.EndLine) { // If the first contains the second return(-1); } else if (second.StartLine <= first.StartLine && second.EndLine >= first.EndLine) { // If the second contains the first return(1); } else if (first.StartLine != second.StartLine) { // Otherwise sort them be file position return((first.StartLine <= second.StartLine) ? -1 : 1); } else if (first.StartColumn != second.StartColumn) { // Or sort them by line position return((first.StartColumn <= second.StartColumn) ? -1 : 1); } else if (first.Covered != second.Covered) { // Or if they're the same character, make the uncovered one go // last so people can see it easier return(first.Covered ? -1 : 1); } else { return(0); } }
/// <summary> /// Reload the LIR when the selected index is changed. /// </summary> /// <param name="sender">Combo box.</param> /// <param name="e">Event arguments.</param> private void OnIRMethodsSelectedIndexChanged(object sender, EventArgs e) { _txtIR.Text = ""; // Get the selected item CoverageItem methodItem = _cmbIRMethods.SelectedItem as CoverageItem; if (methodItem == null) { return; } // Get the IR instructions string fullTypeName = methodItem.Parent.Parent.Name + '.' + methodItem.Parent.Name; XElement[] instructions = CoverageData .Descendants("method") .Where(m => string.Compare(m.Attribute("name").Value, methodItem.Name, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(m.Attribute("class").Value, fullTypeName, StringComparison.OrdinalIgnoreCase) == 0) .Descendants("instruction") .ToArray(); // Build up the coverage display StringBuilder source = new StringBuilder(); foreach (XElement instruction in instructions) { source.AppendLine(instruction.Value); } _txtIR.Text = source.ToString(); // Highlight the coverage Dictionary <string, bool> coverage = methodItem.Children.ToDictionary(i => i.Name, i => i.Covered); for (int i = 0; i < instructions.Length; i++) { bool covered; string block = instructions[i].Attribute("block").Value; if (coverage.TryGetValue(block, out covered)) { HighlightLine(_txtIR, i, 0, -1, GetHighlightColor(covered)); } } }
/// <summary> /// Compare the names of two coverage items for sorting. /// </summary> /// <param name="first">First coverage item.</param> /// <param name="second">Second coverage item.</param> /// <returns> /// 0 if the names are equal, -1 if first comes before second, or 1 if /// second comes before first. /// </returns> public static int SortComparer(CoverageItem first, CoverageItem second) { return(string.CompareOrdinal(first.Name, second.Name)); }
/// <summary> /// Parse a coverage data file into coverage items. /// </summary> /// <param name="document">XDocument containing the file.</param> /// <param name="newPathPrefix">New path prefix for files.</param> /// <returns>Top level coverage items.</returns> public static IEnumerable <CoverageItem> Parse(XDocument document, string newPathPrefix) { // Obtain the value of old prefix used in coverage.xml string oldpathprefix = document.Root.Attribute("pathprefix").Value; // Covert the XML into CoverageItems List <CoverageItem> assemblies = new List <CoverageItem>(); foreach (XElement assemblyElement in document.Root.Elements("assembly")) { // Get coverate details for an assembly CoverageItem assemblyItem = new CoverageItem { ItemType = CoverageItemType.Assembly, Name = assemblyElement.Attribute("name").Value }; assemblies.Add(assemblyItem); foreach (XElement methodElement in assemblyElement.Elements("method")) { // Split the type name into namespace/type string fullName = methodElement.Attribute("class").Value; int partition = fullName.LastIndexOf('.'); string namespaceName = (partition < 0) ? "" : fullName.Substring(0, partition); string typeName = fullName.Substring(partition + 1, fullName.Length - partition - 1); // Get coverage details for the namespace CoverageItem namespaceItem = null; foreach (CoverageItem ns in assemblyItem.Children) { if (string.Compare(ns.Name, namespaceName, StringComparison.OrdinalIgnoreCase) == 0) { namespaceItem = ns; break; } } if (namespaceItem == null) { namespaceItem = new CoverageItem { ItemType = CoverageItemType.Namespace, Name = namespaceName, Parent = assemblyItem }; assemblyItem.Children.Add(namespaceItem); } // Get coverage details for the type CoverageItem typeItem = null; foreach (CoverageItem t in namespaceItem.Children) { if (string.Compare(t.Name, typeName, StringComparison.OrdinalIgnoreCase) == 0) { typeItem = t; break; } } if (typeItem == null) { typeItem = new CoverageItem { ItemType = CoverageItemType.Type, Name = typeName, Parent = namespaceItem }; namespaceItem.Children.Add(typeItem); } // Replace the old file path prefix in method with new prefix if not null string tempmethodstr = methodElement.Attribute("file").Value; tempmethodstr = ChangeAbsolutePath(tempmethodstr, oldpathprefix, newPathPrefix); // Get coverage details for the method CoverageItem methodItem = new CoverageItem { ItemType = CoverageItemType.Method, Name = methodElement.Attribute("name").Value, File = tempmethodstr, StartLine = int.Parse(methodElement.Attribute("line").Value, CultureInfo.InvariantCulture), Parent = typeItem }; typeItem.Children.Add(methodItem); // Get coverage details for the method's blocks foreach (XElement blockElement in methodElement.Descendants("block")) { // Replace the old file path prefix in block with new prefix if not null string tempblockstr = blockElement.Attribute("file").Value; tempblockstr = ChangeAbsolutePath(tempblockstr, oldpathprefix, newPathPrefix); CoverageItem blockItem = new CoverageItem { ItemType = CoverageItemType.Block, Name = blockElement.Attribute("id").Value, File = tempblockstr, CoveredBlocks = string.Compare(blockElement.Attribute("visited").Value, "1", StringComparison.OrdinalIgnoreCase) == 0 ? 1 : 0, TotalBlocks = 1, StartLine = int.Parse(blockElement.Attribute("startLine").Value, CultureInfo.InvariantCulture), StartColumn = int.Parse(blockElement.Attribute("startColumn").Value, CultureInfo.InvariantCulture), EndLine = int.Parse(blockElement.Attribute("lastLine").Value, CultureInfo.InvariantCulture), EndColumn = int.Parse(blockElement.Attribute("lastColumn").Value, CultureInfo.InvariantCulture), Parent = methodItem }; methodItem.Children.Add(blockItem); // Update the coverage statistics assemblyItem.TotalBlocks++; namespaceItem.TotalBlocks++; typeItem.TotalBlocks++; methodItem.TotalBlocks++; if (blockItem.Covered) { assemblyItem.CoveredBlocks++; namespaceItem.CoveredBlocks++; typeItem.CoveredBlocks++; methodItem.CoveredBlocks++; } } } // Sort all of the lists when an assembly is finished List <CoverageItem> namespaces = assemblyItem.Children as List <CoverageItem>; namespaces.Sort(SortComparer); foreach (CoverageItem namespaceItem in assemblyItem.Children) { List <CoverageItem> types = namespaceItem.Children as List <CoverageItem>; types.Sort(SortComparer); foreach (CoverageItem typeItem in namespaceItem.Children) { List <CoverageItem> methods = typeItem.Children as List <CoverageItem>; methods.Sort(SortComparer); } } } assemblies.Sort(SortComparer); return(assemblies); }
/// <summary> /// View the coverage data for an item. /// </summary> /// <param name="item">Coverage item to view.</param> private void ViewCoverageItem(CoverageItem item) { if (SelectedItem == item) { return; } SelectedItem = item; // Only change the source tab if the file is different if (string.Compare(SelectedFile, item.File, StringComparison.OrdinalIgnoreCase) != 0) { SelectedFile = item.File; _lblFile.Text = Path.GetFileName(item.File); _txtSource.Text = File.ReadAllText(item.File); _cmbIRMethods.Items.Clear(); // Highlight the file List<CoverageItem> intervals; if (Highlighting.TryGetValue(item.File, out intervals)) { foreach (CoverageItem interval in intervals) { Color highlight = GetHighlightColor(interval.Covered); for (int line = interval.StartLine; line <= interval.EndLine; line++) { int start = (line == interval.StartLine) ? interval.StartColumn : 0; int end = (line == interval.EndLine) ? interval.EndColumn : -1; HighlightLine(_txtSource, line, start, end, highlight); } } } // Fill the LIR drop down CoverageItem typeItem = item.Parent; foreach (CoverageItem methodItem in typeItem.Children) { _cmbIRMethods.Items.Add(methodItem); } } // Scroll to the desired line in the source tab (though we move 5 // lines up in the file since the method lines start at the first // instruction instead of the declaration) _txtSource.Select(_txtSource.GetFirstCharIndexFromLine(Math.Max(0, item.StartLine - 5)), 0); _txtSource.ScrollToCaret(); // Select the item in the LIR drop down _cmbIRMethods.SelectedItem = SelectedItem; }
/// <summary> /// Create a TreeGridNode for a coverage item. /// </summary> /// <param name="parent">Parent of the tree node.</param> /// <param name="item">Coverage item.</param> /// <returns>TreeGridNode for the coverage item.</returns> private TreeGridNode CreateCoverageNode(TreeGridNode parent, CoverageItem item) { TreeGridNode node = new TreeGridNode { Tag = item }; ((parent != null) ? parent.Nodes : _grdCoverage.Nodes).Add(node); node.SetValues(item.Name, item.UncoveredBlocks, item.UncoveredBlocksPercentage, item.CoveredBlocks, item.CoveredBlocksPercentage, null); switch (item.ItemType) { case CoverageItemType.Assembly: node.ImageIndex = 0; node.Expand(); break; case CoverageItemType.Namespace: node.ImageIndex = 1; node.Expand(); break; case CoverageItemType.Type: node.ImageIndex = 2; node.Expand(); break; case CoverageItemType.Method: node.ImageIndex = 3; break; } return node; }
/// <summary> /// Compare the highlight ordering of two coverage items. /// </summary> /// <param name="first">First coverage item.</param> /// <param name="second">Second coverage item.</param> /// <returns> /// 0 if the items are equal, -1 if first comes before second, or 1 if /// second comes before first. /// </returns> public static int HighlightComparer(CoverageItem first, CoverageItem second) { // Basic blocks will only overlap by nesting one inside the other. // Therefore we want to sort with the largest containing blocks at // the front of the list (so they will be "painted" first) if (first == null) { // If one or both are null return (second == null) ? 0 : 1; } else if (first == second) { // If they're the same object return 0; } else if (first.StartLine <= second.StartLine && first.EndLine >= second.EndLine) { // If the first contains the second return -1; } else if (second.StartLine <= first.StartLine && second.EndLine >= first.EndLine) { // If the second contains the first return 1; } else if (first.StartLine != second.StartLine) { // Otherwise sort them be file position return (first.StartLine <= second.StartLine) ? -1 : 1; } else if (first.StartColumn != second.StartColumn) { // Or sort them by line position return (first.StartColumn <= second.StartColumn) ? -1 : 1; } else if (first.Covered != second.Covered) { // Or if they're the same character, make the uncovered one go // last so people can see it easier return first.Covered ? -1 : 1; } else { return 0; } }
/// <summary> /// Compare the names of two coverage items for sorting. /// </summary> /// <param name="first">First coverage item.</param> /// <param name="second">Second coverage item.</param> /// <returns> /// 0 if the names are equal, -1 if first comes before second, or 1 if /// second comes before first. /// </returns> public static int SortComparer(CoverageItem first, CoverageItem second) { return string.CompareOrdinal(first.Name, second.Name); }
/// <summary> /// Parse a coverage data file into coverage items. /// </summary> /// <param name="document">XDocument containing the file.</param> /// <param name="newPathPrefix">New path prefix for files.</param> /// <returns>Top level coverage items.</returns> public static IEnumerable<CoverageItem> Parse(XDocument document, string newPathPrefix) { // Obtain the value of old prefix used in coverage.xml string oldpathprefix = document.Root.Attribute("pathprefix").Value; // Covert the XML into CoverageItems List<CoverageItem> assemblies = new List<CoverageItem>(); foreach (XElement assemblyElement in document.Root.Elements("assembly")) { // Get coverate details for an assembly CoverageItem assemblyItem = new CoverageItem { ItemType = CoverageItemType.Assembly, Name = assemblyElement.Attribute("name").Value }; assemblies.Add(assemblyItem); foreach (XElement methodElement in assemblyElement.Elements("method")) { // Split the type name into namespace/type string fullName = methodElement.Attribute("class").Value; int partition = fullName.LastIndexOf('.'); string namespaceName = (partition < 0) ? "" : fullName.Substring(0, partition); string typeName = fullName.Substring(partition + 1, fullName.Length - partition - 1); // Get coverage details for the namespace CoverageItem namespaceItem = null; foreach (CoverageItem ns in assemblyItem.Children) { if (string.Compare(ns.Name, namespaceName, StringComparison.OrdinalIgnoreCase) == 0) { namespaceItem = ns; break; } } if (namespaceItem == null) { namespaceItem = new CoverageItem { ItemType = CoverageItemType.Namespace, Name = namespaceName, Parent = assemblyItem }; assemblyItem.Children.Add(namespaceItem); } // Get coverage details for the type CoverageItem typeItem = null; foreach (CoverageItem t in namespaceItem.Children) { if (string.Compare(t.Name, typeName, StringComparison.OrdinalIgnoreCase) == 0) { typeItem = t; break; } } if (typeItem == null) { typeItem = new CoverageItem { ItemType = CoverageItemType.Type, Name = typeName, Parent = namespaceItem }; namespaceItem.Children.Add(typeItem); } // Replace the old file path prefix in method with new prefix if not null string tempmethodstr = methodElement.Attribute("file").Value; tempmethodstr = ChangeAbsolutePath(tempmethodstr, oldpathprefix, newPathPrefix); // Get coverage details for the method CoverageItem methodItem = new CoverageItem { ItemType = CoverageItemType.Method, Name = methodElement.Attribute("name").Value, File = tempmethodstr, StartLine = int.Parse(methodElement.Attribute("line").Value, CultureInfo.InvariantCulture), Parent = typeItem }; typeItem.Children.Add(methodItem); // Get coverage details for the method's blocks foreach (XElement blockElement in methodElement.Descendants("block")) { // Replace the old file path prefix in block with new prefix if not null string tempblockstr = blockElement.Attribute("file").Value; tempblockstr = ChangeAbsolutePath(tempblockstr, oldpathprefix, newPathPrefix); CoverageItem blockItem = new CoverageItem { ItemType = CoverageItemType.Block, Name = blockElement.Attribute("id").Value, File = tempblockstr, CoveredBlocks = string.Compare(blockElement.Attribute("visited").Value, "1", StringComparison.OrdinalIgnoreCase) == 0 ? 1 : 0, TotalBlocks = 1, StartLine = int.Parse(blockElement.Attribute("startLine").Value, CultureInfo.InvariantCulture), StartColumn = int.Parse(blockElement.Attribute("startColumn").Value, CultureInfo.InvariantCulture), EndLine = int.Parse(blockElement.Attribute("lastLine").Value, CultureInfo.InvariantCulture), EndColumn = int.Parse(blockElement.Attribute("lastColumn").Value, CultureInfo.InvariantCulture), Parent = methodItem }; methodItem.Children.Add(blockItem); // Update the coverage statistics assemblyItem.TotalBlocks++; namespaceItem.TotalBlocks++; typeItem.TotalBlocks++; methodItem.TotalBlocks++; if (blockItem.Covered) { assemblyItem.CoveredBlocks++; namespaceItem.CoveredBlocks++; typeItem.CoveredBlocks++; methodItem.CoveredBlocks++; } } } // Sort all of the lists when an assembly is finished List<CoverageItem> namespaces = assemblyItem.Children as List<CoverageItem>; namespaces.Sort(SortComparer); foreach (CoverageItem namespaceItem in assemblyItem.Children) { List<CoverageItem> types = namespaceItem.Children as List<CoverageItem>; types.Sort(SortComparer); foreach (CoverageItem typeItem in namespaceItem.Children) { List<CoverageItem> methods = typeItem.Children as List<CoverageItem>; methods.Sort(SortComparer); } } } assemblies.Sort(SortComparer); return assemblies; }