diff --git a/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs b/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs index 600bf4c..e8f1d2c 100644 --- a/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs +++ b/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs @@ -1,41 +1,41 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System.Collections.Generic; +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System.Collections.Generic; using BoostTestAdapter.Boost.Results.LogEntryTypes; -namespace BoostTestAdapter.Boost.Results -{ - /// - /// Standard Output as emitted by Boost Test executables - /// - public class BoostStandardOutput : BoostConsoleOutputBase - { - #region Constructors - - /// - /// Constructor accepting a path to the external file - /// - /// The destination result collection. Possibly used for result aggregation. - public BoostStandardOutput(IDictionary target) - : base(target) - { - } - - #endregion Constructors - - #region BoostConsoleOutputBase - - protected override LogEntry CreateLogEntry(string message) - { - return new LogEntryStandardOutputMessage() - { - Detail = message - }; - } - - #endregion BoostConsoleOutputBase - } +namespace BoostTestAdapter.Boost.Results +{ + /// + /// Standard Output as emitted by Boost Test executables + /// + public class BoostStandardOutput : BoostConsoleOutputBase + { + #region Constructors + + /// + /// Constructor accepting a path to the external file + /// + /// The destination result collection. Possibly used for result aggregation. + public BoostStandardOutput(IDictionary target) + : base(target) + { + } + + #endregion Constructors + + #region BoostConsoleOutputBase + + protected override LogEntry CreateLogEntry(string message) + { + return new LogEntryStandardOutputMessage() + { + Detail = message + }; + } + + #endregion BoostConsoleOutputBase + } } \ No newline at end of file diff --git a/BoostTestAdapter/Boost/Test/TestFrameworkDOTDeserialiser.cs b/BoostTestAdapter/Boost/Test/TestFrameworkDOTDeserialiser.cs index 94eff63..ec8a92e 100644 --- a/BoostTestAdapter/Boost/Test/TestFrameworkDOTDeserialiser.cs +++ b/BoostTestAdapter/Boost/Test/TestFrameworkDOTDeserialiser.cs @@ -1,545 +1,506 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System; -using System.IO; -using System.Linq; -using System.Globalization; -using System.Collections.Generic; - -using Antlr.DOT; -using Antlr4.Runtime.Tree; -using Antlr4.Runtime.Misc; - -using BoostTestAdapter.Utility; - -namespace BoostTestAdapter.Boost.Test -{ - /// - /// A Test Framework deserialiser which generates a - /// Test Framework instance from a Boost Test DOT representation. - /// - public class TestFrameworkDOTDeserialiser - { - /// - /// Constructor - /// - /// The test module source file path - public TestFrameworkDOTDeserialiser(string source) - { - this.Source = source; - } - - /// - /// The test module source file path - /// - public string Source { get; private set; } - - /// - /// Parses the stream containing a Boost Test DOT representation of a Test Framework - /// - /// The stream consisting of a DOT representation - /// The deserialised Test Framework - public TestFramework Deserialise(Stream stream) - { - return Deserialise(stream, null); - } - - /// - /// Parses the stream containing a Boost Test DOT representation of a Test Framework. Notifies the - /// provided visitor (during parsing) of any identified test units. - /// - /// The stream consisting of a DOT representation - /// The visitor which will be notified during parsing - /// The deserialised Test Framework - /// - /// The visitor will not necessarily be notified in a top-down fashion. To ensure top-down - /// visitation, wait for the result and visit the master test suite. - /// - public TestFramework Deserialise(Stream stream, ITestVisitor visitor) - { - BoostTestFrameworkVisitor dotVisitor = new BoostTestFrameworkVisitor(this, visitor); - return DOT.Parse(stream, dotVisitor); - } - - /// - /// Implementation of DOTBaseVisitor which creates/populates a - /// TestFramework instance from a DOT abstract syntax tree. - /// - private class BoostTestFrameworkVisitor : DOTBaseVisitor - { - /// - /// Constructor - /// - /// The parent TestFrameworkDOTDeserialiser instance - public BoostTestFrameworkVisitor(TestFrameworkDOTDeserialiser parent, ITestVisitor visitor) - { - this.Parent = parent; - this.Visitor = visitor; - - this.Framework = null; - this.Context = null; - } - - /// - /// The parent TestFrameworkDOTDeserialiser instance - /// - public TestFrameworkDOTDeserialiser Parent { get; private set; } - - /// - /// An ITestVisitor which is to be notified once a test unit has been fully defined - /// - public ITestVisitor Visitor { get; private set; } - - /// - /// The generated Test Framework - /// - public TestFramework Framework { get; private set; } - - /// - /// Context class used to aggregate information during parsing - /// - private class DOTContext - { - /// - /// Default Constructor - /// - public DOTContext() - { - this.TestUnits = new Stack(); - } - - /// - /// The current parent test suite which will host child test cases - /// - public TestSuite ParentSuite { get; set; } - - /// - /// The master test suite - /// - public TestSuite MasterTestSuite { get; set; } - - /// - /// In-progress test unit information which is currently being parsed - /// - public Stack TestUnits { get; private set; } - } - - /// - /// The current deserialisation context - /// - private DOTContext Context { get; set; } - - #region DOTBaseVisitor - - public override TestFramework VisitGraph([NotNull] DOTParser.GraphContext context) - { - this.Context = new DOTContext(); - this.Framework = null; - - // Visit children - this.Framework = base.VisitGraph(context); - - this.Framework = new TestFramework(this.Parent.Source, this.Context.MasterTestSuite); - this.Context = null; - - return this.Framework; - } - - public override TestFramework VisitSubgraph([NotNull] DOTParser.SubgraphContext context) - { - TestUnitInfo info = this.Context.TestUnits.Peek(); - - TestSuite suite = CreateTestSuite(info, this.Context.ParentSuite); - this.Context.ParentSuite = suite; - - if (this.Context.MasterTestSuite == null) - { - this.Context.MasterTestSuite = suite; - } - - // Visit Children - this.Framework = base.VisitSubgraph(context); - - // Register any child test cases - while (info != this.Context.TestUnits.Peek()) - { - TestCase test = CreateTestCase(this.Context.TestUnits.Pop(), this.Context.ParentSuite); - Visit(test); - } - - // NOTE Suite is visited after children since it is at this point that we - // can guarantee that the suite is fully formed - Visit(suite); - - this.Context.TestUnits.Pop(); - this.Context.ParentSuite = (TestSuite)suite.Parent; - - return this.Framework; - } - - public override TestFramework VisitNode_stmt([NotNull] DOTParser.Node_stmtContext context) - { - TestUnitInfo info = new TestUnitInfo(context.node_id().GetText()); - - foreach (var attribute in GetKeyValuePairs(context.attr_list())) - { - switch (attribute.Key) - { - case "color": - { - // 'green' implies that the test is explicitly enabled by default - // 'yellow' implies that it is enabled, but *may* be disabled - info.DefaultEnabled = (attribute.Value == "green"); - break; - } - - case "label": - { - // Parse BOOST Test specific content - info.Parse(attribute.Value.Trim('"')); - break; - } - } - }; - - this.Context.TestUnits.Push(info); - - return base.VisitNode_stmt(context); - } - - public override TestFramework VisitEdge_stmt([NotNull] DOTParser.Edge_stmtContext context) - { - TestUnitInfo info = this.Context.TestUnits.Peek(); - - if (info != null) - { - var lhs = context.node_id(); - var rhs = lhs; - - var edgeRhs = context.edgeRHS(); - - // NOTE Boost Test DOT output only define one edge per edge statement - if (edgeRhs.edgeop().Length == 1) - { - var edgeop = edgeRhs.edgeop()[0]; - // Ensure that a directed edge '->' token is used - if (edgeop.GetToken(DOTLexer.T__7, 0) != null) - { - rhs = edgeRhs.node_id()[0]; - } - } - - if ((lhs != rhs) && (rhs != null)) - { - // Identify whether this edge is a constraining edge (i.e. an actual graph edge) or a non-constraining edge - bool constraint = !GetKeyValuePairs(context.attr_list()).Any((attribute) => (attribute.Key == "constraint") && (attribute.Value == "false")); - - // This implies a test dependency - if ((lhs.GetText() == info.id) && !constraint) - { - info.Dependencies.Add(rhs.GetText()); - } - // This implies a test unit relationship - else if (rhs.GetText() == info.id) - { - info.Parents.Add(lhs.GetText()); - } - } - } - - return base.VisitEdge_stmt(context); - } - - #endregion DOTBaseVisitor - - /// - /// Iterates over all key-value pair attributes contained within the provided attr_list - /// - /// The attr_list to iterate - /// An enumeration of all key-value pairs present in the provided attr_list - private static IEnumerable> GetKeyValuePairs(DOTParser.Attr_listContext attr_list) - { - // NOTE Refer to DOT grammar; an 'attr_list' is composed of an 'a_list', which is composed of multiple 'id's - - if (attr_list != null) - { - var a_lists = attr_list.a_list(); - if (a_lists != null) - { - for (int i = 0; i < a_lists.Length; ++i) - { - var a_list = a_lists[i]; - - int id = 0; - int idCount = a_list.id().Length; - - // Try to identify the id() list as a list of 'id' = 'id' tuples - - while (id < idCount) - { - var lhs = a_list.id()[id]; - - // Reference: Antlr4 C# Runtime [ParserRuleContext.GetTokens(int)] - var sibling = a_list.GetChild(a_list.children.IndexOf(lhs) + 1) as ITerminalNode; - - // Identify if the a_list contains a key/value pair separated by the '=' token - if ((sibling != null) && (sibling.Symbol.Type == DOTLexer.T__3)) - { - yield return new KeyValuePair(lhs.GetText(), a_list.id()[id + 1].GetText()); - id += 2; - } - // Else provide the identifier a single item - else - { - yield return new KeyValuePair(lhs.GetText(), null); - ++id; - } - } - } - } - } - } - - /// - /// Creates a TestSuite instance from the provided TestUnitInfo structure - /// - /// The currently accumulated test unit information - /// The parent test suite of this test unit - /// A TestSuite based on the provided information - private static TestSuite CreateTestSuite(TestUnitInfo info, TestSuite parent) - { - return PopulateTestUnit(info, new TestSuite(info.Name, parent)); - } - - /// - /// Creates a TestCase instance from the provided TestUnitInfo structure - /// - /// The currently accumulated test unit information - /// The parent test suite of this test unit - /// A TestCase based on the provided information - private static TestCase CreateTestCase(TestUnitInfo info, TestSuite parent) - { - return PopulateTestUnit(info, new TestCase(info.Name, parent)); - } - - /// - /// Populates the provided TestUnit with the test unit information - /// - /// A TestUnit derived type - /// The currently accumulated test unit information - /// The test unit instance which is to be populated - /// unit - private static T PopulateTestUnit(TestUnitInfo info, T unit) where T : TestUnit - { - if ((!string.IsNullOrEmpty(info.id)) && (info.id.Length > 2)) - { - // Remove the 'tu' prefix from the test unit string ID - - int id = 0; - if (int.TryParse(info.id.Substring(2), NumberStyles.Integer, CultureInfo.InvariantCulture, out id)) - { - unit.Id = id; - } - } - - unit.Source = info.SourceInfo; - unit.Labels = info.Labels; - - unit.DefaultEnabled = info.DefaultEnabled; - - // Default Enabled - // Timeout - // Expected Failures - // Dependencies - - return unit; - } - - /// - /// Allows the registered ITestVisitor to visit the completed TestUnit definition - /// - /// The test unit to visit - void Visit(TestUnit unit) - { - if (this.Visitor != null) - { - unit.Apply(this.Visitor); - } - } - - /// - /// Aggregation of test unit information contained within a Boost Test DOT serialisation - /// - private class TestUnitInfo - { - /// - /// Constructor - /// - /// Test Unit ID (e.g. tu1) - public TestUnitInfo(string id) - { - this.id = id; - - this.Name = string.Empty; - this.SourceInfo = null; - this.Timeout = 0; - this.ExpectedFailures = 0; - this.Labels = Enumerable.Empty(); - - this.Parents = new List(); - this.Dependencies = new List(); - - this.DefaultEnabled = true; - } - - /// - /// Test Unit ID (e.g. tu1) - /// - public string id { get; private set; } - - /// - /// Test Unit Name - /// - public string Name { get; set; } - - /// - /// Source information - /// - public SourceFileInfo SourceInfo { get; set; } - - /// - /// Test timeout - /// - public uint Timeout { get; set; } - - /// - /// Test expected failure count - /// - public uint ExpectedFailures { get; set; } - - /// - /// Test labels - /// - public IEnumerable Labels { get; set; } - - /// - /// Test unit children - /// - public List Parents { get; set; } - - /// - /// Test unit dependencies - /// - public List Dependencies { get; set; } - - /// - /// Flag which is raised when the test is explicitly set to true - /// - public bool DefaultEnabled { get; set; } - - /// - /// Parses test unit information from the provided string - /// - /// The string to parse - /// Test unit information contained within value - /// - public TestUnitInfo Parse(string value) - { - TestUnitInfo info = new TestUnitInfo(this.id); - info.DefaultEnabled = this.DefaultEnabled; - string[] properties = value.Split('|'); - - if (properties.Length > 0) - { - info.Name = properties[0]; - } - if (properties.Length > 1) - { - info.SourceInfo = SourceFileInfo.Parse(properties[1]); - } - if (properties.Length > 2) - { - foreach (var attribute in ParseKeyValuePairs(properties.Skip(2))) - { - ParseNamedAttribute(info, attribute); - } - } - - // Replace the contents of 'this' with that of 'info' - Set(info); - - return this; - } - - /// - /// Sets the current instance properties to the one provided - /// - /// - private void Set(TestUnitInfo value) - { - this.id = value.id; - this.Name = value.Name; - this.SourceInfo = value.SourceInfo; - this.DefaultEnabled = value.DefaultEnabled; - this.Timeout = value.Timeout; - this.ExpectedFailures = value.ExpectedFailures; - this.Labels = value.Labels; - this.Parents = value.Parents; - this.Dependencies = value.Dependencies; - } - - /// - /// Parses a Boost Test DOT named attribute label - /// - /// The test unit information structure which to populate - /// The attribute to parse - private static void ParseNamedAttribute(TestUnitInfo info, KeyValuePair attribute) - { - switch (attribute.Key) - { - case "timeout": - { - info.Timeout = uint.Parse(attribute.Value, CultureInfo.InvariantCulture); - break; - } - case "expected failures": - { - info.ExpectedFailures = uint.Parse(attribute.Value, CultureInfo.InvariantCulture); - break; - } - case "labels": - { - var labels = attribute.Value.Split(new[] { " @" }, StringSplitOptions.RemoveEmptyEntries); - if (labels.Length > 0) - { - info.Labels = labels; - } - break; - } - } - } - - /// - /// Parses the Boost Test DOT label attributes for any key-value pairs - /// - /// The values/attributes to parse - /// An enumeration of successfully parsed key-value pairs - private static IEnumerable> ParseKeyValuePairs(IEnumerable values) - { - foreach (string value in values) - { - // NOTE 'timeout' and 'expected failures' use '=' as a separator, 'labels' use ':' - string[] keyValue = value.Split(new[] { '=', ':' }, 2, StringSplitOptions.RemoveEmptyEntries); - if (keyValue.Length == 2) - { - yield return new KeyValuePair(keyValue[0], keyValue[1]); - } - } - } - } - } - } +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System; +using System.IO; +using System.Linq; +using System.Globalization; +using System.Collections.Generic; + +using Antlr.DOT; +using Antlr4.Runtime.Tree; +using Antlr4.Runtime.Misc; + +using BoostTestAdapter.Utility; + +namespace BoostTestAdapter.Boost.Test +{ + /// + /// A Test Framework deserialiser which generates a + /// Test Framework instance from a Boost Test DOT representation. + /// + public class TestFrameworkDOTDeserialiser + { + /// + /// Constructor + /// + /// The test module source file path + public TestFrameworkDOTDeserialiser(string source) + { + this.Source = source; + } + + /// + /// The test module source file path + /// + public string Source { get; private set; } + + /// + /// Parses the stream containing a Boost Test DOT representation of a Test Framework + /// + /// The stream consisting of a DOT representation + /// The deserialised Test Framework + public TestFramework Deserialise(Stream stream) + { + BoostTestFrameworkVisitor dotVisitor = new BoostTestFrameworkVisitor(this); + return DOT.Parse(stream, dotVisitor); + } + + /// + /// Implementation of DOTBaseVisitor which creates/populates a + /// TestFramework instance from a DOT abstract syntax tree. + /// + private class BoostTestFrameworkVisitor : DOTBaseVisitor + { + /// + /// Constructor + /// + /// The parent TestFrameworkDOTDeserialiser instance + public BoostTestFrameworkVisitor(TestFrameworkDOTDeserialiser parent) + { + this.Parent = parent; + + this.Framework = null; + this.Context = null; + } + + /// + /// The parent TestFrameworkDOTDeserialiser instance + /// + public TestFrameworkDOTDeserialiser Parent { get; private set; } + + /// + /// The generated Test Framework + /// + public TestFramework Framework { get; private set; } + + /// + /// Context class used to aggregate information during parsing + /// + private class DOTContext + { + /// + /// Default Constructor + /// + public DOTContext() + { + this.TestUnits = new Stack(); + } + + /// + /// The current parent test suite which will host child test cases + /// + public TestSuite ParentSuite { get; set; } + + /// + /// The master test suite + /// + public TestSuite MasterTestSuite { get; set; } + + /// + /// In-progress test unit information which is currently being parsed + /// + public Stack TestUnits { get; private set; } + } + + /// + /// The current deserialisation context + /// + private DOTContext Context { get; set; } + + #region DOTBaseVisitor + + public override TestFramework VisitGraph([NotNull] DOTParser.GraphContext context) + { + this.Context = new DOTContext(); + this.Framework = null; + + // Visit children + this.Framework = base.VisitGraph(context); + + this.Framework = new TestFramework(this.Parent.Source, this.Context.MasterTestSuite); + this.Context = null; + + return this.Framework; + } + + public override TestFramework VisitSubgraph([NotNull] DOTParser.SubgraphContext context) + { + TestUnitInfo info = this.Context.TestUnits.Peek(); + + TestSuite suite = CreateTestSuite(info, this.Context.ParentSuite); + this.Context.ParentSuite = suite; + + if (this.Context.MasterTestSuite == null) + { + this.Context.MasterTestSuite = suite; + } + + // Visit Children + this.Framework = base.VisitSubgraph(context); + + // Register any child test cases + while (info != this.Context.TestUnits.Peek()) + { + CreateTestCase(this.Context.TestUnits.Pop(), this.Context.ParentSuite); + } + + this.Context.TestUnits.Pop(); + this.Context.ParentSuite = (TestSuite)suite.Parent; + + return this.Framework; + } + + public override TestFramework VisitNode_stmt([NotNull] DOTParser.Node_stmtContext context) + { + TestUnitInfo info = new TestUnitInfo(context.node_id().GetText()); + + foreach (var attribute in GetKeyValuePairs(context.attr_list())) + { + switch (attribute.Key) + { + case "color": + { + // 'green' implies that the test is explicitly enabled by default + // 'yellow' implies that it is enabled, but *may* be disabled + info.DefaultEnabled = (attribute.Value == "green"); + break; + } + + case "label": + { + // Parse BOOST Test specific content + info.Parse(attribute.Value.Trim('"')); + break; + } + } + }; + + this.Context.TestUnits.Push(info); + + return base.VisitNode_stmt(context); + } + + public override TestFramework VisitEdge_stmt([NotNull] DOTParser.Edge_stmtContext context) + { + TestUnitInfo info = this.Context.TestUnits.Peek(); + + if (info != null) + { + var lhs = context.node_id(); + var rhs = lhs; + + var edgeRhs = context.edgeRHS(); + + // NOTE Boost Test DOT output only define one edge per edge statement + if (edgeRhs.edgeop().Length == 1) + { + var edgeop = edgeRhs.edgeop()[0]; + // Ensure that a directed edge '->' token is used + if (edgeop.GetToken(DOTLexer.T__7, 0) != null) + { + rhs = edgeRhs.node_id()[0]; + } + } + + if ((lhs != rhs) && (rhs != null)) + { + // Identify whether this edge is a constraining edge (i.e. an actual graph edge) or a non-constraining edge + bool constraint = !GetKeyValuePairs(context.attr_list()).Any((attribute) => (attribute.Key == "constraint") && (attribute.Value == "false")); + + // This implies a test dependency + if ((lhs.GetText() == info.id) && !constraint) + { + info.Dependencies.Add(rhs.GetText()); + } + // This implies a test unit relationship + else if (rhs.GetText() == info.id) + { + info.Parents.Add(lhs.GetText()); + } + } + } + + return base.VisitEdge_stmt(context); + } + + #endregion DOTBaseVisitor + + /// + /// Iterates over all key-value pair attributes contained within the provided attr_list + /// + /// The attr_list to iterate + /// An enumeration of all key-value pairs present in the provided attr_list + private static IEnumerable> GetKeyValuePairs(DOTParser.Attr_listContext attr_list) + { + // NOTE Refer to DOT grammar; an 'attr_list' is composed of an 'a_list', which is composed of multiple 'id's + + if (attr_list != null) + { + var a_lists = attr_list.a_list(); + if (a_lists != null) + { + for (int i = 0; i < a_lists.Length; ++i) + { + var a_list = a_lists[i]; + + int id = 0; + int idCount = a_list.id().Length; + + // Try to identify the id() list as a list of 'id' = 'id' tuples + + while (id < idCount) + { + var lhs = a_list.id()[id]; + + // Reference: Antlr4 C# Runtime [ParserRuleContext.GetTokens(int)] + var sibling = a_list.GetChild(a_list.children.IndexOf(lhs) + 1) as ITerminalNode; + + // Identify if the a_list contains a key/value pair separated by the '=' token + if ((sibling != null) && (sibling.Symbol.Type == DOTLexer.T__3)) + { + yield return new KeyValuePair(lhs.GetText(), a_list.id()[id + 1].GetText()); + id += 2; + } + // Else provide the identifier a single item + else + { + yield return new KeyValuePair(lhs.GetText(), null); + ++id; + } + } + } + } + } + } + + /// + /// Creates a TestSuite instance from the provided TestUnitInfo structure + /// + /// The currently accumulated test unit information + /// The parent test suite of this test unit + /// A TestSuite based on the provided information + private static TestSuite CreateTestSuite(TestUnitInfo info, TestSuite parent) + { + return PopulateTestUnit(info, new TestSuite(info.Name, parent)); + } + + /// + /// Creates a TestCase instance from the provided TestUnitInfo structure + /// + /// The currently accumulated test unit information + /// The parent test suite of this test unit + /// A TestCase based on the provided information + private static TestCase CreateTestCase(TestUnitInfo info, TestSuite parent) + { + return PopulateTestUnit(info, new TestCase(info.Name, parent)); + } + + /// + /// Populates the provided TestUnit with the test unit information + /// + /// A TestUnit derived type + /// The currently accumulated test unit information + /// The test unit instance which is to be populated + /// unit + private static T PopulateTestUnit(TestUnitInfo info, T unit) where T : TestUnit + { + if ((!string.IsNullOrEmpty(info.id)) && (info.id.Length > 2)) + { + // Remove the 'tu' prefix from the test unit string ID + + int id = 0; + if (int.TryParse(info.id.Substring(2), NumberStyles.Integer, CultureInfo.InvariantCulture, out id)) + { + unit.Id = id; + } + } + + unit.Source = info.SourceInfo; + unit.Labels = info.Labels; + + unit.DefaultEnabled = info.DefaultEnabled; + + // Default Enabled + // Timeout + // Expected Failures + // Dependencies + + return unit; + } + + /// + /// Aggregation of test unit information contained within a Boost Test DOT serialisation + /// + private class TestUnitInfo + { + /// + /// Constructor + /// + /// Test Unit ID (e.g. tu1) + public TestUnitInfo(string id) + { + this.id = id; + + this.Name = string.Empty; + this.SourceInfo = null; + this.Timeout = 0; + this.ExpectedFailures = 0; + this.Labels = Enumerable.Empty(); + + this.Parents = new List(); + this.Dependencies = new List(); + + this.DefaultEnabled = true; + } + + /// + /// Test Unit ID (e.g. tu1) + /// + public string id { get; private set; } + + /// + /// Test Unit Name + /// + public string Name { get; set; } + + /// + /// Source information + /// + public SourceFileInfo SourceInfo { get; set; } + + /// + /// Test timeout + /// + public uint Timeout { get; set; } + + /// + /// Test expected failure count + /// + public uint ExpectedFailures { get; set; } + + /// + /// Test labels + /// + public IEnumerable Labels { get; set; } + + /// + /// Test unit children + /// + public List Parents { get; set; } + + /// + /// Test unit dependencies + /// + public List Dependencies { get; set; } + + /// + /// Flag which is raised when the test is explicitly set to true + /// + public bool DefaultEnabled { get; set; } + + /// + /// Parses test unit information from the provided string + /// + /// The string to parse + /// Test unit information contained within value + /// + public TestUnitInfo Parse(string value) + { + TestUnitInfo info = new TestUnitInfo(this.id); + info.DefaultEnabled = this.DefaultEnabled; + string[] properties = value.Split('|'); + + if (properties.Length > 0) + { + info.Name = properties[0]; + } + if (properties.Length > 1) + { + info.SourceInfo = SourceFileInfo.Parse(properties[1]); + } + if (properties.Length > 2) + { + foreach (var attribute in ParseKeyValuePairs(properties.Skip(2))) + { + ParseNamedAttribute(info, attribute); + } + } + + // Replace the contents of 'this' with that of 'info' + Set(info); + + return this; + } + + /// + /// Sets the current instance properties to the one provided + /// + /// + private void Set(TestUnitInfo value) + { + this.id = value.id; + this.Name = value.Name; + this.SourceInfo = value.SourceInfo; + this.DefaultEnabled = value.DefaultEnabled; + this.Timeout = value.Timeout; + this.ExpectedFailures = value.ExpectedFailures; + this.Labels = value.Labels; + this.Parents = value.Parents; + this.Dependencies = value.Dependencies; + } + + /// + /// Parses a Boost Test DOT named attribute label + /// + /// The test unit information structure which to populate + /// The attribute to parse + private static void ParseNamedAttribute(TestUnitInfo info, KeyValuePair attribute) + { + switch (attribute.Key) + { + case "timeout": + { + info.Timeout = uint.Parse(attribute.Value, CultureInfo.InvariantCulture); + break; + } + case "expected failures": + { + info.ExpectedFailures = uint.Parse(attribute.Value, CultureInfo.InvariantCulture); + break; + } + case "labels": + { + var labels = attribute.Value.Split(new[] { " @" }, StringSplitOptions.RemoveEmptyEntries); + if (labels.Length > 0) + { + info.Labels = labels; + } + break; + } + } + } + + /// + /// Parses the Boost Test DOT label attributes for any key-value pairs + /// + /// The values/attributes to parse + /// An enumeration of successfully parsed key-value pairs + private static IEnumerable> ParseKeyValuePairs(IEnumerable values) + { + foreach (string value in values) + { + // NOTE 'timeout' and 'expected failures' use '=' as a separator, 'labels' use ':' + string[] keyValue = value.Split(new[] { '=', ':' }, 2, StringSplitOptions.RemoveEmptyEntries); + if (keyValue.Length == 2) + { + yield return new KeyValuePair(keyValue[0], keyValue[1]); + } + } + } + } + } + } } \ No newline at end of file diff --git a/BoostTestAdapter/Boost/Test/TestUnit.cs b/BoostTestAdapter/Boost/Test/TestUnit.cs index b09927f..6194773 100644 --- a/BoostTestAdapter/Boost/Test/TestUnit.cs +++ b/BoostTestAdapter/Boost/Test/TestUnit.cs @@ -1,192 +1,189 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml; -using BoostTestAdapter.Utility; - -namespace BoostTestAdapter.Boost.Test -{ - /// - /// Base class for Boost Test test components. Follows the composite design pattern. - /// - public abstract class TestUnit : ITestVisitable - { - #region Constructors - - /// - /// Constructor - /// - /// Test Unit (local) name. - /// Parent/Owner Test Unit of this instance. - protected TestUnit(string name, TestUnit parent) - { - this.Id = null; - this.Name = name; - this.Parent = parent; - this.Labels = Enumerable.Empty(); - - this.DefaultEnabled = true; - - if (parent != null) - { - parent.AddChild(this); - } - } - - #endregion Constructors - - #region Properties - - /// - /// Test Unit Id. Optional. - /// - public int? Id { get; set; } - - /// - /// Test Unit (local) Name. - /// - public string Name { get; private set; } - - /// - /// Parent/Owner Test Unit of this instance. - /// - public TestUnit Parent { get; private set; } - - /// - /// Child Test Units of this instance. - /// - public virtual IEnumerable Children - { - get - { - return Enumerable.Empty(); - } - } - - /// - /// Optional source file information related to this test unit. - /// - public SourceFileInfo Source { get; set; } - - /// - /// Identifies any label associations with this test unit - /// - public IEnumerable Labels { get; set; } - - - - /// - /// Cached version of the fully qualified name builder - /// - private QualifiedNameBuilder _fullyQualifiedName = null; - - /// - /// Internal property which provides a cached QualifiedNameBuilder to represent the fully qualified name - /// - private QualifiedNameBuilder FullyQualifiedNameBuilder - { - get - { - if (this._fullyQualifiedName == null) - { - _fullyQualifiedName = (Parent == null) ? new QualifiedNameBuilder() : Parent.FullyQualifiedNameBuilder.Clone(); - _fullyQualifiedName.Push(this); - } - - return _fullyQualifiedName; - } - } - - /// - /// Identifies the fully qualified name of this TestUnit - /// - public string FullyQualifiedName - { - get - { - return FullyQualifiedNameBuilder.ToString(); - } - } - - /// - /// Identifies whether the test is explicitly disabled by setting this value to false - /// - - public bool DefaultEnabled { get; set; } - - #endregion Properties - - /// - /// Adds a child to this TestUnit - /// - /// The unit to add as a child - public virtual void AddChild(TestUnit unit) - { - throw new InvalidOperationException(); - } - - #region ITestVisitable - - public abstract void Apply(ITestVisitor visitor); - - #endregion ITestVisitable - - #region Utility - - /// - /// Given a fully qualified name of a test case, generates the respective test unit hierarchy. - /// - /// The fully qualified name of the test case - /// The test case hierarcy represented by the provided fully qualified name - public static TestCase FromFullyQualifiedName(string fullyQualifiedName) - { - return FromFullyQualifiedName(QualifiedNameBuilder.FromString(fullyQualifiedName)); - } - - /// - /// Given a fully qualified name of a test case, generates the respective test unit hierarchy. - /// - /// The fully qualified name of the test case - /// The test case hierarchy represented by the provided fully qualified name - /// The parameter 'fullyQualifiedName' will be modified and emptied in due process - private static TestCase FromFullyQualifiedName(QualifiedNameBuilder fullyQualifiedName) - { - // Reverse the fully qualified name stack i.e. Master Test Suite should be first element and Test Case should be last element - Stack hierarchy = new Stack(); - while (fullyQualifiedName.Peek() != null) - { - hierarchy.Push(fullyQualifiedName.Peek()); - fullyQualifiedName.Pop(); - } - - TestSuite parent = null; - - // Treat each entry (except for the last) as a test suite - while (hierarchy.Count > 1) - { - parent = new TestSuite(hierarchy.Peek(), parent); - hierarchy.Pop(); - } - - // Treat the last entry as a test case - return (hierarchy.Count == 1) ? new TestCase(hierarchy.Peek(), parent) : null; - } - - #endregion Utility - - #region object overrides - - public override string ToString() - { - return this.FullyQualifiedName; - } - - #endregion object overrides - } +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using BoostTestAdapter.Utility; + +namespace BoostTestAdapter.Boost.Test +{ + /// + /// Base class for Boost Test test components. Follows the composite design pattern. + /// + public abstract class TestUnit : ITestVisitable + { + #region Constructors + + /// + /// Constructor + /// + /// Test Unit (local) name. + /// Parent/Owner Test Unit of this instance. + protected TestUnit(string name, TestUnit parent) + { + this.Id = null; + this.Name = name; + this.Parent = parent; + this.Labels = Enumerable.Empty(); + + this.DefaultEnabled = true; + + if (parent != null) + { + parent.AddChild(this); + } + } + + #endregion Constructors + + #region Properties + + /// + /// Test Unit Id. Optional. + /// + public int? Id { get; set; } + + /// + /// Test Unit (local) Name. + /// + public string Name { get; private set; } + + /// + /// Parent/Owner Test Unit of this instance. + /// + public TestUnit Parent { get; private set; } + + /// + /// Child Test Units of this instance. + /// + public virtual IEnumerable Children + { + get + { + return Enumerable.Empty(); + } + } + + /// + /// Optional source file information related to this test unit. + /// + public SourceFileInfo Source { get; set; } + + /// + /// Identifies any label associations with this test unit + /// + public IEnumerable Labels { get; set; } + + /// + /// Cached version of the fully qualified name builder + /// + private QualifiedNameBuilder _fullyQualifiedName = null; + + /// + /// Internal property which provides a cached QualifiedNameBuilder to represent the fully qualified name + /// + private QualifiedNameBuilder FullyQualifiedNameBuilder + { + get + { + if (this._fullyQualifiedName == null) + { + _fullyQualifiedName = (Parent == null) ? new QualifiedNameBuilder() : Parent.FullyQualifiedNameBuilder.Clone(); + _fullyQualifiedName.Push(this); + } + + return _fullyQualifiedName; + } + } + + /// + /// Identifies the fully qualified name of this TestUnit + /// + public string FullyQualifiedName + { + get + { + return FullyQualifiedNameBuilder.ToString(); + } + } + + /// + /// Identifies whether the test is explicitly disabled by setting this value to false + /// + public bool DefaultEnabled { get; set; } + + #endregion Properties + + /// + /// Adds a child to this TestUnit + /// + /// The unit to add as a child + public virtual void AddChild(TestUnit unit) + { + throw new InvalidOperationException(); + } + + #region ITestVisitable + + public abstract void Apply(ITestVisitor visitor); + + #endregion ITestVisitable + + #region Utility + + /// + /// Given a fully qualified name of a test case, generates the respective test unit hierarchy. + /// + /// The fully qualified name of the test case + /// The test case hierarcy represented by the provided fully qualified name + public static TestCase FromFullyQualifiedName(string fullyQualifiedName) + { + return FromFullyQualifiedName(QualifiedNameBuilder.FromString(fullyQualifiedName)); + } + + /// + /// Given a fully qualified name of a test case, generates the respective test unit hierarchy. + /// + /// The fully qualified name of the test case + /// The test case hierarchy represented by the provided fully qualified name + /// The parameter 'fullyQualifiedName' will be modified and emptied in due process + private static TestCase FromFullyQualifiedName(QualifiedNameBuilder fullyQualifiedName) + { + // Reverse the fully qualified name stack i.e. Master Test Suite should be first element and Test Case should be last element + Stack hierarchy = new Stack(); + while (fullyQualifiedName.Peek() != null) + { + hierarchy.Push(fullyQualifiedName.Peek()); + fullyQualifiedName.Pop(); + } + + TestSuite parent = null; + + // Treat each entry (except for the last) as a test suite + while (hierarchy.Count > 1) + { + parent = new TestSuite(hierarchy.Peek(), parent); + hierarchy.Pop(); + } + + // Treat the last entry as a test case + return (hierarchy.Count == 1) ? new TestCase(hierarchy.Peek(), parent) : null; + } + + #endregion Utility + + #region object overrides + + public override string ToString() + { + return this.FullyQualifiedName; + } + + #endregion object overrides + } } \ No newline at end of file diff --git a/BoostTestAdapter/BoostTestAdapter.csproj b/BoostTestAdapter/BoostTestAdapter.csproj index f2ac959..c003063 100644 --- a/BoostTestAdapter/BoostTestAdapter.csproj +++ b/BoostTestAdapter/BoostTestAdapter.csproj @@ -125,6 +125,7 @@ + @@ -152,7 +153,7 @@ - + diff --git a/BoostTestAdapter/BoostTestDiscoverer.cs b/BoostTestAdapter/BoostTestDiscoverer.cs index 52c71c9..e34e56a 100644 --- a/BoostTestAdapter/BoostTestDiscoverer.cs +++ b/BoostTestAdapter/BoostTestDiscoverer.cs @@ -1,129 +1,129 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System; -using System.Collections.Generic; -using System.Linq; -using BoostTestAdapter.Settings; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -using BoostTestAdapter.Utility; - -namespace BoostTestAdapter -{ - [FileExtension(DllExtension)] - [FileExtension(ExeExtension)] - [DefaultExecutorUri(BoostTestExecutor.ExecutorUriString)] - public class BoostTestDiscoverer : ITestDiscoverer - { - #region Constants - - public const string DllExtension = ".dll"; - public const string ExeExtension = ".exe"; - - #endregion Constants - - #region Constructors - - /// - /// Default constructor. The default IBoostTestDiscovererFactory implementation is provided. - /// - public BoostTestDiscoverer() - :this(new BoostTestDiscovererFactory()) - { - } - - /// - /// Constructor. - /// - /// A custom IBoostTestDiscovererFactory implementation. - public BoostTestDiscoverer(IBoostTestDiscovererFactory boostTestDiscovererFactory) - { - _boostTestDiscovererFactory = boostTestDiscovererFactory; - } - - #endregion - - - #region Members - - private readonly IBoostTestDiscovererFactory _boostTestDiscovererFactory; - - #endregion - - - #region ITestDiscoverer - - /// - /// Method called by Visual Studio (discovered via reflection) for test enumeration - /// - /// path, target name and target extensions to discover - /// discovery context settings - /// - /// Unit test framework Sink - /// Entry point of the discovery procedure - public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) - { -#if DEBUG && LAUNCH_DEBUGGER - System.Diagnostics.Debugger.Launch(); -#endif - - if (sources == null) - return; - - Logger.Initialize(logger); - - DiscoverTests(sources, discoveryContext, discoverySink); - - Logger.Shutdown(); - } - - #endregion ITestDiscoverer - - /// - /// Method called by BoostTestExecutor for test enumeration - /// - /// path, target name and target extensions to discover - /// discovery context settings - /// Unit test framework Sink - /// This method assumes that the Logger singleton is maintained by the caller. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, ITestCaseDiscoverySink discoverySink) - { - if (sources == null) - return; - - BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(discoveryContext); - - try - { - // Filter out any sources which are not of interest - if (!TestSourceFilter.IsNullOrEmpty(settings.Filters)) - { - sources = sources.Where(settings.Filters.ShouldInclude); - } - - var results = _boostTestDiscovererFactory.GetDiscoverers(sources.ToList(), settings); - if (results == null) - return; - - // Test discovery - foreach (var discoverer in results) - { - if (discoverer.Sources.Count > 0) - { - Logger.Info("Discovering ({0}): -> [{1}]", discoverer.Discoverer.GetType().Name, string.Join(", ", discoverer.Sources)); - discoverer.Discoverer.DiscoverTests(discoverer.Sources, discoveryContext, discoverySink); - } - } - } - catch (Exception ex) - { - Logger.Exception(ex, "Exception caught while discovering tests: {0} ({1})", ex.Message, ex.HResult); - } - } - } -} +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using BoostTestAdapter.Settings; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using BoostTestAdapter.Utility; + +namespace BoostTestAdapter +{ + [FileExtension(DllExtension)] + [FileExtension(ExeExtension)] + [DefaultExecutorUri(BoostTestExecutor.ExecutorUriString)] + public class BoostTestDiscoverer : ITestDiscoverer + { + #region Constants + + public const string DllExtension = ".dll"; + public const string ExeExtension = ".exe"; + + #endregion Constants + + #region Constructors + + /// + /// Default constructor. The default IBoostTestDiscovererFactory implementation is provided. + /// + public BoostTestDiscoverer() + :this(new BoostTestDiscovererFactory()) + { + } + + /// + /// Constructor. + /// + /// A custom IBoostTestDiscovererFactory implementation. + public BoostTestDiscoverer(IBoostTestDiscovererFactory boostTestDiscovererFactory) + { + _boostTestDiscovererFactory = boostTestDiscovererFactory; + } + + #endregion + + + #region Members + + private readonly IBoostTestDiscovererFactory _boostTestDiscovererFactory; + + #endregion + + + #region ITestDiscoverer + + /// + /// Method called by Visual Studio (discovered via reflection) for test enumeration + /// + /// path, target name and target extensions to discover + /// discovery context settings + /// + /// Unit test framework Sink + /// Entry point of the discovery procedure + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { +#if DEBUG && LAUNCH_DEBUGGER + System.Diagnostics.Debugger.Launch(); +#endif + + if (sources == null) + return; + + Logger.Initialize(logger); + + DiscoverTests(sources, discoveryContext, discoverySink); + + Logger.Shutdown(); + } + + #endregion ITestDiscoverer + + /// + /// Method called by BoostTestExecutor for test enumeration + /// + /// path, target name and target extensions to discover + /// discovery context settings + /// Unit test framework Sink + /// This method assumes that the Logger singleton is maintained by the caller. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, ITestCaseDiscoverySink discoverySink) + { + if (sources == null) + return; + + BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(discoveryContext); + + try + { + // Filter out any sources which are not of interest + if (!TestSourceFilter.IsNullOrEmpty(settings.Filters)) + { + sources = sources.Where(settings.Filters.ShouldInclude); + } + + var results = _boostTestDiscovererFactory.GetDiscoverers(sources.ToList(), settings); + if (results == null) + return; + + // Test discovery + foreach (var discoverer in results) + { + if (discoverer.Sources.Count > 0) + { + Logger.Info("Discovering ({0}): -> [{1}]", discoverer.Discoverer.GetType().Name, string.Join(", ", discoverer.Sources)); + discoverer.Discoverer.DiscoverTests(discoverer.Sources, discoveryContext, discoverySink); + } + } + } + catch (Exception ex) + { + Logger.Exception(ex, "Exception caught while discovering tests: {0} ({1})", ex.Message, ex.HResult); + } + } + } +} diff --git a/BoostTestAdapter/Discoverers/ListContentDiscoverer.cs b/BoostTestAdapter/Discoverers/ListContentDiscoverer.cs index 35c60fb..ed26cc4 100644 --- a/BoostTestAdapter/Discoverers/ListContentDiscoverer.cs +++ b/BoostTestAdapter/Discoverers/ListContentDiscoverer.cs @@ -88,7 +88,7 @@ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discove if (vs != null) { Logger.Debug("Connected to Visual Studio {0} instance", vs.Version); - } + } args.SetWorkingEnvironment(source, settings, vs); } @@ -122,12 +122,11 @@ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discove using (FileStream stream = File.OpenRead(args.StandardErrorFile)) { TestFrameworkDOTDeserialiser deserialiser = new TestFrameworkDOTDeserialiser(source); - - // Pass in a visitor to avoid a 2-pass loop in order to notify test cases to VS - // - // NOTE Due to deserialisation, make sure that only test cases are visited. Test - // suites may be visited after their child test cases are visited. - deserialiser.Deserialise(stream, new VSDiscoveryVisitorTestsOnly(source, discoverySink)); + TestFramework framework = deserialiser.Deserialise(stream); + if ((framework != null) && (framework.MasterTestSuite != null)) + { + framework.MasterTestSuite.Apply(new VSDiscoveryVisitor(source, discoverySink)); + } } } } @@ -139,27 +138,5 @@ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discove } #endregion IBoostTestDiscoverer - - /// - /// A specification of VSDiscoveryVisitor which limits visitation to tests only. - /// Allows for optimal visitation during DOT deserialisation. - /// - private class VSDiscoveryVisitorTestsOnly : VSDiscoveryVisitor - { - public VSDiscoveryVisitorTestsOnly(string source, ITestCaseDiscoverySink sink) - : base(source, sink) - { - } - - protected override bool ShouldVisit(TestSuite suite) - { - return false; - } - - protected override bool ShouldVisit(TestCase test) - { - return true; - } - } } } diff --git a/BoostTestAdapter/Discoverers/VSDiscoveryVisitor.cs b/BoostTestAdapter/Discoverers/VSDiscoveryVisitor.cs index b04e60b..4f7891a 100644 --- a/BoostTestAdapter/Discoverers/VSDiscoveryVisitor.cs +++ b/BoostTestAdapter/Discoverers/VSDiscoveryVisitor.cs @@ -1,185 +1,185 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System.Diagnostics; - -using BoostTestAdapter.Boost.Test; - -using BoostTestAdapter.Utility; -using BoostTestAdapter.Utility.VisualStudio; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - -using TestCase = BoostTestAdapter.Boost.Test.TestCase; -using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; -using System.IO; - -namespace BoostTestAdapter.Discoverers -{ - /// - /// ITestVisitor implementation. Visits TestCases and registers them - /// with the supplied ITestCaseDiscoverySink. - /// - public class VSDiscoveryVisitor : ITestVisitor - { - /// - /// Constructor - /// - /// The source test module which contains the discovered tests - /// The ITestCaseDiscoverySink which will have tests registered with - public VSDiscoveryVisitor(string source, ITestCaseDiscoverySink sink) - { - this.Source = source; - this.DiscoverySink = sink; - this.OutputLog = true; - } - - /// - /// The test module source file path - /// - public string Source { get; private set; } - - /// - /// Whether the module should output to the logger regarding relative paths - /// - public bool OutputLog { get; private set; } - - /// - /// The Visual Studio DiscoverySink which is used to notify test discovery - /// - public ITestCaseDiscoverySink DiscoverySink { get; private set; } - - #region ITestVisitor - - public void Visit(TestSuite testSuite) - { - Code.Require(testSuite, "testSuite"); - - if (ShouldVisit(testSuite)) - { - foreach (TestUnit child in testSuite.Children) - { - child.Apply(this); - } - } - } - - public void Visit(TestCase testCase) - { - Code.Require(testCase, "testCase"); - - if (ShouldVisit(testCase)) - { - VSTestCase test = GenerateTestCase(testCase); - - // Send to discovery sink - if (null != this.DiscoverySink) - { - Logger.Info("Found test: {0}", test.FullyQualifiedName); - this.DiscoverySink.SendTestCase(test); - } - } - } - - #endregion ITestVisitor - - /// - /// Generates a Visual Studio equivalent test case structure. - /// - /// The Boost.Test.TestCase to convert. - /// An equivalent Visual Studio TestCase structure to the one provided. - - private VSTestCase GenerateTestCase(TestCase testCase) - { - VSTestCase test = new VSTestCase( - testCase.FullyQualifiedName, - BoostTestExecutor.ExecutorUri, - this.Source - ); - - test.DisplayName = testCase.Name; - - if (testCase.Source != null) - { - // NOTE As of Boost 1.61, this warning might be triggered when BOOST_DATA_TEST_CASEs are used due to irregular DOT output - if (!Path.IsPathRooted(testCase.Source.File) && this.OutputLog) - { - Logger.Info("Relative Paths are being used. Please note that test navigation from the Test Explorer window will not be available. To enable such functionality, the Use Full Paths setting under C++ -> Advanced in the project's Property Page must be set to Yes (/FC)."); - this.OutputLog = false; - } - - test.CodeFilePath = testCase.Source.File; - test.LineNumber = testCase.Source.LineNumber; - } - - // Register the test suite as a trait - test.Traits.Add(new Trait(VSTestModel.TestSuiteTrait, GetParentFullyQualifiedName(testCase))); - - // Register enabled and disabled as traits - test.Traits.Add(new Trait(VSTestModel.StatusTrait, (testCase.DefaultEnabled ? VSTestModel.TestEnabled : VSTestModel.TestDisabled))); - - TestUnit unit = testCase; - while (unit != null) - { - foreach (string label in unit.Labels) - { - // Register each and every label as an individual trait - test.Traits.Add(new Trait(label, string.Empty)); - } - - // Test cases inherit the labels of parent test units - // Reference: http://www.boost.org/doc/libs/1_60_0/libs/test/doc/html/boost_test/tests_organization/tests_grouping.html - unit = unit.Parent; - } - - return test; - } - - /// - /// States whether the provided test suite should be visited - /// - /// The test suite under consideration - /// true if the provided TestSuite should be visited; false otherwise - protected virtual bool ShouldVisit(TestSuite suite) - { - return true; - } - - /// - /// States whether the provided test case should be visited - /// - /// The test case under consideration - /// true if the provided TestCase should be visited; false otherwise - protected virtual bool ShouldVisit(TestCase test) - { - return true; - } - - /// - /// Provides the fully qualified name of the parent TestUnit of the provided TestCase - /// - /// The TestCase whose parent TestUnit is to be queried - /// The fully qualified name of the parent TestUnit - private static string GetParentFullyQualifiedName(TestCase test) - { - Code.Require(test, "test"); - - TestUnit parent = test.Parent; - - // A test case must have a parent, at the very least, the master test suite should be the parent of a test case - Debug.Assert(parent != null); - - // Since the master test suite name is not included in the fully qualified name, identify - // this edge case and explicitly return the master test suite name in such cases. - if (parent.Parent == null) - { - return string.IsNullOrEmpty(parent.Name) ? QualifiedNameBuilder.DefaultMasterTestSuiteName : parent.Name; - } - - return parent.FullyQualifiedName; - } - } -} +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System.IO; +using System.Diagnostics; + +using BoostTestAdapter.Boost.Test; + +using BoostTestAdapter.Utility; +using BoostTestAdapter.Utility.VisualStudio; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +using TestCase = BoostTestAdapter.Boost.Test.TestCase; +using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; + +namespace BoostTestAdapter.Discoverers +{ + /// + /// ITestVisitor implementation. Visits TestCases and registers them + /// with the supplied ITestCaseDiscoverySink. + /// + public class VSDiscoveryVisitor : ITestVisitor + { + /// + /// Constructor + /// + /// The source test module which contains the discovered tests + /// The ITestCaseDiscoverySink which will have tests registered with + public VSDiscoveryVisitor(string source, ITestCaseDiscoverySink sink) + { + Code.Require(sink, "sink"); + + this.Source = source; + this.DiscoverySink = sink; + this.OutputLog = true; + } + + /// + /// The test module source file path + /// + public string Source { get; private set; } + + /// + /// Whether the module should output to the logger regarding relative paths + /// + private bool OutputLog { get; set; } + + /// + /// The Visual Studio DiscoverySink which is used to notify test discovery + /// + public ITestCaseDiscoverySink DiscoverySink { get; private set; } + + #region ITestVisitor + + public void Visit(TestSuite testSuite) + { + Code.Require(testSuite, "testSuite"); + + if (BoostDataTestCaseVerifier.IsBoostDataTestCase(testSuite)) + { + foreach (TestUnit child in testSuite.Children) + { + // NOTE Since we have asserted that the suite is a BOOST_DATA_TEST_CASE, + // all child instances are to be of type TestCase + + var displayName = testSuite.Name + '/' + child.Name; + Visit((TestCase)child, displayName); + } + } + else + { + foreach (TestUnit child in testSuite.Children) + { + child.Apply(this); + } + } + } + + public void Visit(TestCase testCase) + { + Code.Require(testCase, "testCase"); + + Visit(testCase, testCase.Name); + } + + #endregion ITestVisitor + + /// + /// Visits the provided TestCase + /// + /// The TestCase which is to be visited + /// The test case display name to use (overrides the test case name) + private void Visit(TestCase testCase, string displayName) + { + Code.Require(testCase, "testCase"); + + VSTestCase test = GenerateTestCase(testCase); + test.DisplayName = string.IsNullOrEmpty(displayName) ? test.DisplayName : displayName; + + // Send to discovery sink + Logger.Info("Found test: {0}", test.FullyQualifiedName); + this.DiscoverySink.SendTestCase(test); + } + + /// + /// Generates a Visual Studio equivalent test case structure. + /// + /// The Boost.Test.TestCase to convert. + /// An equivalent Visual Studio TestCase structure to the one provided. + + private VSTestCase GenerateTestCase(TestCase testCase) + { + VSTestCase test = new VSTestCase( + testCase.FullyQualifiedName, + BoostTestExecutor.ExecutorUri, + this.Source + ); + + test.DisplayName = testCase.Name; + + if (testCase.Source != null) + { + // NOTE As of Boost 1.61, this warning might be triggered when BOOST_DATA_TEST_CASEs are used due to irregular DOT output + if (!Path.IsPathRooted(testCase.Source.File) && this.OutputLog) + { + Logger.Info("Relative Paths are being used. Please note that test navigation from the Test Explorer window will not be available. To enable such functionality, the Use Full Paths setting under C++ -> Advanced in the project's Property Page must be set to Yes (/FC)."); + this.OutputLog = false; + } + + test.CodeFilePath = testCase.Source.File; + test.LineNumber = testCase.Source.LineNumber; + } + + // Register the test suite as a trait + test.Traits.Add(new Trait(VSTestModel.TestSuiteTrait, GetParentFullyQualifiedName(testCase))); + + // Register enabled and disabled as traits + test.Traits.Add(new Trait(VSTestModel.StatusTrait, (testCase.DefaultEnabled ? VSTestModel.TestEnabled : VSTestModel.TestDisabled))); + + TestUnit unit = testCase; + while (unit != null) + { + foreach (string label in unit.Labels) + { + // Register each and every label as an individual trait + test.Traits.Add(new Trait(label, string.Empty)); + } + + // Test cases inherit the labels of parent test units + // Reference: http://www.boost.org/doc/libs/1_60_0/libs/test/doc/html/boost_test/tests_organization/tests_grouping.html + unit = unit.Parent; + } + + return test; + } + + /// + /// Provides the fully qualified name of the parent TestUnit of the provided TestCase + /// + /// The TestCase whose parent TestUnit is to be queried + /// The fully qualified name of the parent TestUnit + private static string GetParentFullyQualifiedName(TestCase test) + { + Code.Require(test, "test"); + + TestUnit parent = test.Parent; + + // A test case must have a parent, at the very least, the master test suite should be the parent of a test case + Debug.Assert(parent != null); + + // Since the master test suite name is not included in the fully qualified name, identify + // this edge case and explicitly return the master test suite name in such cases. + if (parent.Parent == null) + { + return string.IsNullOrEmpty(parent.Name) ? QualifiedNameBuilder.DefaultMasterTestSuiteName : parent.Name; + } + + return parent.FullyQualifiedName; + } + } +} diff --git a/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs b/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs index 4e6f0cf..50976cd 100644 --- a/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs +++ b/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs @@ -210,6 +210,7 @@ public bool UseBoost162Workaround /// /// Determines a delay represented in milliseconds which will be forced after the execution of each test batch. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PostTest")] [DefaultValue(0)] public int PostTestDelay { get; set; } diff --git a/BoostTestAdapter/TestBatch/TestSuiteTestBatchStrategy.cs b/BoostTestAdapter/TestBatch/TestSuiteTestBatchStrategy.cs index de15ee0..ca0f1be 100644 --- a/BoostTestAdapter/TestBatch/TestSuiteTestBatchStrategy.cs +++ b/BoostTestAdapter/TestBatch/TestSuiteTestBatchStrategy.cs @@ -1,64 +1,63 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System.Collections.Generic; -using System.Linq; -using BoostTestAdapter.Boost.Runner; -using BoostTestAdapter.Settings; -using BoostTestAdapter.Utility; -using BoostTestAdapter.Utility.VisualStudio; - -using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; - -namespace BoostTestAdapter.TestBatch -{ - /// - /// An ITestBatchingStrategy which allocates a test run for test suites. All tests - /// contained within the same test suite are executed in one test run. - /// - public class TestSuiteTestBatchStrategy : TestBatchStrategy - { - public TestSuiteTestBatchStrategy(IBoostTestRunnerFactory testRunnerFactory, BoostTestAdapterSettings settings, CommandLineArgsBuilder argsBuilder) : - base(testRunnerFactory, settings, argsBuilder) - { - } - - #region TestBatchStrategy - - public override IEnumerable BatchTests(IEnumerable tests) - { - BoostTestRunnerSettings adaptedSettings = this.Settings.TestRunnerSettings.Clone(); - adaptedSettings.Timeout = -1; - - // Group by source - IEnumerable> sources = tests.GroupBy(test => test.Source); - foreach (IGrouping source in sources) - { - IBoostTestRunner runner = GetTestRunner(source.Key); - if (runner == null) - { - continue; - } - - // Group by test suite - var suiteGroups = source.GroupBy(test => test.Traits.First(trait => (trait.Name == VSTestModel.TestSuiteTrait)).Value); - foreach (var suiteGroup in suiteGroups) - { - BoostTestRunnerCommandLineArgs args = BuildCommandLineArgs(source.Key); - foreach (VSTestCase test in suiteGroup) - { - // List all tests by display name - // but ensure that the first test is fully qualified so that remaining tests are taken relative to this test suite - args.Tests.Add((args.Tests.Count == 0) ? test.FullyQualifiedName : test.DisplayName); - } - - yield return new TestRun(runner, suiteGroup, args, adaptedSettings); - } - } - } - - #endregion TestBatchStrategy - } +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System.Collections.Generic; +using System.Linq; +using BoostTestAdapter.Boost.Runner; +using BoostTestAdapter.Settings; +using BoostTestAdapter.Utility; +using BoostTestAdapter.Utility.VisualStudio; + +using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; + +namespace BoostTestAdapter.TestBatch +{ + /// + /// An ITestBatchingStrategy which allocates a test run for test suites. All tests + /// contained within the same test suite are executed in one test run. + /// + public class TestSuiteTestBatchStrategy : TestBatchStrategy + { + public TestSuiteTestBatchStrategy(IBoostTestRunnerFactory testRunnerFactory, BoostTestAdapterSettings settings, CommandLineArgsBuilder argsBuilder) : + base(testRunnerFactory, settings, argsBuilder) + { + } + + #region TestBatchStrategy + + public override IEnumerable BatchTests(IEnumerable tests) + { + BoostTestRunnerSettings adaptedSettings = this.Settings.TestRunnerSettings.Clone(); + adaptedSettings.Timeout = -1; + + // Group by source + IEnumerable> sources = tests.GroupBy(test => test.Source); + foreach (IGrouping source in sources) + { + IBoostTestRunner runner = GetTestRunner(source.Key); + if (runner == null) + { + continue; + } + + // Group by test suite + var suiteGroups = source.GroupBy(test => test.Traits.First(trait => (trait.Name == VSTestModel.TestSuiteTrait)).Value); + foreach (var suiteGroup in suiteGroups) + { + BoostTestRunnerCommandLineArgs args = BuildCommandLineArgs(source.Key); + foreach (VSTestCase test in suiteGroup) + { + // List all tests by name but ensure that the first test is fully qualified so that remaining tests are taken relative to this test suite + args.Tests.Add((args.Tests.Count == 0) ? test.FullyQualifiedName : QualifiedNameBuilder.FromString(test.FullyQualifiedName).Peek()); + } + + yield return new TestRun(runner, suiteGroup, args, adaptedSettings); + } + } + } + + #endregion TestBatchStrategy + } } \ No newline at end of file diff --git a/BoostTestAdapter/Utility/BoostDataTestCaseVerifier.cs b/BoostTestAdapter/Utility/BoostDataTestCaseVerifier.cs new file mode 100644 index 0000000..a63ba7d --- /dev/null +++ b/BoostTestAdapter/Utility/BoostDataTestCaseVerifier.cs @@ -0,0 +1,95 @@ +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System.Text.RegularExpressions; + +using BoostTestAdapter.Boost.Test; + +namespace BoostTestAdapter.Utility +{ + /// + /// Utility class which determines if a test suite is, in fact, a BOOST_DATA_TEST_CASE + /// + public class BoostDataTestCaseVerifier : ITestVisitor + { + /// + /// The test case name pattern used for BOOST_DATA_TEST_CASE instances + /// + private static readonly Regex _dataTestCaseNamePattern = new Regex(@"^_\d+$"); + + /// + /// Constructor + /// + /// The test suite which is to be determined if it is a BOOST_DATA_TEST_CASE + private BoostDataTestCaseVerifier(TestSuite testSuite) + { + this.RootTestSuite = testSuite; + this.DataTestCase = true; + } + + /// + /// The root test suite which is to be determined if it is a BOOST_DATA_TEST_CASE + /// + public TestSuite RootTestSuite { get; set; } + + /// + /// Flag identifying if RootTestSuite is a BOOST_DATA_TEST_CASE + /// + public bool DataTestCase { get; set; } + + #region ITestVisitor + + public void Visit(TestSuite testSuite) + { + Code.Require(testSuite, "testSuite"); + + // A BOOST_DATA_TEST_CASE should only have child test cases + // Any child test suite implies that the suite is not a BOOST_DATA_TEST_CASE + DataTestCase = ((testSuite == RootTestSuite) && (testSuite.Source != null)); + + if (DataTestCase) + { + foreach (var child in testSuite.Children) + { + child.Apply(this); + + if (!DataTestCase) + { + break; + } + } + } + } + + public void Visit(TestCase testCase) + { + Code.Require(testCase, "testCase"); + + // A BOOST_DATA_TEST_CASE test has the following properties: + // - It's name is of the format '_[number]' (where [number] increments per test case) + // - It has the same line number as all other data test case instance + // - It has the same source file reference as all other data test case instances + DataTestCase = ( + (testCase.Source.LineNumber == RootTestSuite.Source.LineNumber) && + (testCase.Source.File == RootTestSuite.Source.File) && + _dataTestCaseNamePattern.IsMatch(testCase.Name) + ); + } + + #endregion + + /// + /// Determines if the provided test suite is, in fact, a BOOST_DATA_TEST_CASE + /// + /// The test suite which is to be determined if it is a BOOST_DATA_TEST_CASE + /// true if the provided test suite is a BOOST_DATA_TEST_CASE; false otherwise + public static bool IsBoostDataTestCase(TestSuite testSuite) + { + var verifier = new BoostDataTestCaseVerifier(testSuite); + testSuite.Apply(verifier); + return verifier.DataTestCase; + } + } +} diff --git a/BoostTestAdapter/Utility/QualifiedNameBuilder.cs b/BoostTestAdapter/Utility/QualifiedNameBuilder.cs index dd8b06d..05f2647 100644 --- a/BoostTestAdapter/Utility/QualifiedNameBuilder.cs +++ b/BoostTestAdapter/Utility/QualifiedNameBuilder.cs @@ -1,224 +1,219 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System; -using System.Collections.Generic; -using System.Linq; -using BoostTestAdapter.Boost.Test; - -namespace BoostTestAdapter.Utility -{ - /// - /// Builds qualified names for Boost Test Test Units. - /// - public class QualifiedNameBuilder : ICloneable - { - #region Constants - - private const string Separator = "/"; - - #endregion Constants - - #region Constructors - - /// - /// Default constructor. - /// - public QualifiedNameBuilder() - : this(new List()) - { - } - - /// - /// Internal constructor. - /// - /// The initial fully qualified path - private QualifiedNameBuilder(List path) - { - this.Path = path; - } - - /// - /// Constructor. Initializes this qualified name based on the provided TestUnit. - /// - /// The TestUnit from which this qualified name is to be initialized. - public QualifiedNameBuilder(TestUnit root) : - this() - { - Initialize(root); - } - - #endregion Constructors - - #region Helper Methods - - /// - /// Helper function which aids in the implementation of QualifiedNameBuilder(TestUnit) constructor - /// - /// The test unit which is to be listed - private void Initialize(TestUnit root) - { - if (root == null) - { - return; - } - - Initialize(root.Parent); - - this.Push(root); - } - - #endregion Helper Methods - - #region Properties - - /// - /// The Master Test Suite local name. - /// - public string MasterTestSuite - { - get { return this.Path.FirstOrDefault(); } - } - - /// - /// Stack which contains the entries for this qualified name. - /// - private List Path { get; set; } - - /// - /// The depth of this fully qualified name. - /// A depth of 0 implies an empty object. - /// - public int Level { get { return this.Path.Count; } } - - #endregion Properties - - #region Constant Properties - - /// - /// Identifies the standard depth level at which the MasterTestSuite is located. - /// - public static uint MasterTestSuiteLevel { get { return 1; } } - - /// - /// Identifies the default MasterTestSuite test suite name. - /// - public static string DefaultMasterTestSuiteName { get { return "Master Test Suite"; } } - - #endregion Constant Properties - - /// - /// Pushes the test unit on this structure. - /// - /// The test unit to push - /// this - public QualifiedNameBuilder Push(TestUnit unit) - { - Utility.Code.Require(unit, "unit"); - - return this.Push(unit.Name); - } - - /// - /// Pushes the (local) name of a test unit on this structure. - /// - /// The test unit (local) name to push - /// this - public QualifiedNameBuilder Push(string name) - { - this.Path.Add(name); - - return this; - } - - /// - /// Peeks at the last (local) name pushed on this builder. - /// - /// The last (local) name pushed on this builder. - public string Peek() - { - return (this.Path.Count > 0) ? this.Path[this.Path.Count - 1] : null; - } - - /// - /// Pops the last test unit from this instance. - /// - /// this - public QualifiedNameBuilder Pop() - { - if (this.Path.Count > 0) - { - this.Path.RemoveAt(this.Path.Count - 1); - } - - return this; - } - - #region object overrides - - /// - /// Provides a string representation of this fully qualified name as expected by Boost Test standards. - /// - /// A string representation of this fully qualified name as expected by Boost Test standards. - public override string ToString() - { - // Skip the Master Test Suite. Master Test Suite is omitted in qualified name. - return string.Join(Separator, this.Path.Skip(1)); - } - - #endregion object overrides - - /// - /// Factory method which creates a QualifiedNameBuilder - /// from an already existing qualified name string. - /// - /// The qualified name - /// A QualifiedNameBuilder from the provided string. - public static QualifiedNameBuilder FromString(string name) - { - // Assume Master Test Suite name - return FromString(DefaultMasterTestSuiteName, name); - } - - /// - /// Factory method which creates a QualifiedNameBuilder - /// from an already existing qualified name string. - /// - /// The local name of the master test suite - /// The qualified name - /// A QualifiedNameBuilder from the provided string. - public static QualifiedNameBuilder FromString(string masterSuite, string name) - { - Utility.Code.Require(masterSuite, "masterSuite"); - Utility.Code.Require(name, "name"); - - QualifiedNameBuilder builder = new QualifiedNameBuilder(); - - builder.Push(masterSuite); - - foreach (string part in name.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries)) - { - builder.Push(part); - } - - return builder; - } - - #region ICloneable - - object ICloneable.Clone() - { - return this.Clone(); - } - - public QualifiedNameBuilder Clone() - { - // NOTE Make an explicit copy of the path - return new QualifiedNameBuilder(new List(this.Path)); - } - - #endregion ICloneable - } +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using BoostTestAdapter.Boost.Test; + +namespace BoostTestAdapter.Utility +{ + /// + /// Builds qualified names for Boost Test Test Units. + /// + public class QualifiedNameBuilder : ICloneable + { + #region Constants + + private const string Separator = "/"; + + #endregion Constants + + #region Constructors + + /// + /// Default constructor. + /// + public QualifiedNameBuilder() + : this(new List()) + { + } + + /// + /// Internal constructor. + /// + /// The initial fully qualified path + private QualifiedNameBuilder(List path) + { + this.Path = path; + } + + /// + /// Constructor. Initializes this qualified name based on the provided TestUnit. + /// + /// The TestUnit from which this qualified name is to be initialized. + public QualifiedNameBuilder(TestUnit root) : + this() + { + Initialize(root); + } + + #endregion Constructors + + #region Helper Methods + + /// + /// Helper function which aids in the implementation of QualifiedNameBuilder(TestUnit) constructor + /// + /// The test unit which is to be listed + private void Initialize(TestUnit root) + { + if (root == null) + { + return; + } + + Initialize(root.Parent); + + this.Push(root); + } + + #endregion Helper Methods + + #region Properties + + /// + /// The Master Test Suite local name. + /// + public string MasterTestSuite + { + get { return this.Path.FirstOrDefault(); } + } + + /// + /// Stack which contains the entries for this qualified name. + /// + private List Path { get; set; } + + /// + /// The depth of this fully qualified name. + /// A depth of 0 implies an empty object. + /// + public int Level { get { return this.Path.Count; } } + + #endregion Properties + + #region Constant Properties + + /// + /// Identifies the default MasterTestSuite test suite name. + /// + public static string DefaultMasterTestSuiteName { get { return "Master Test Suite"; } } + + #endregion Constant Properties + + /// + /// Pushes the test unit on this structure. + /// + /// The test unit to push + /// this + public QualifiedNameBuilder Push(TestUnit unit) + { + Utility.Code.Require(unit, "unit"); + + return this.Push(unit.Name); + } + + /// + /// Pushes the (local) name of a test unit on this structure. + /// + /// The test unit (local) name to push + /// this + public QualifiedNameBuilder Push(string name) + { + this.Path.Add(name); + + return this; + } + + /// + /// Peeks at the last (local) name pushed on this builder. + /// + /// The last (local) name pushed on this builder. + public string Peek() + { + return (this.Path.Count > 0) ? this.Path[this.Path.Count - 1] : null; + } + + /// + /// Pops the last test unit from this instance. + /// + /// this + public QualifiedNameBuilder Pop() + { + if (this.Path.Count > 0) + { + this.Path.RemoveAt(this.Path.Count - 1); + } + + return this; + } + + #region object overrides + + /// + /// Provides a string representation of this fully qualified name as expected by Boost Test standards. + /// + /// A string representation of this fully qualified name as expected by Boost Test standards. + public override string ToString() + { + // Skip the Master Test Suite. Master Test Suite is omitted in qualified name. + return string.Join(Separator, this.Path.Skip(1)); + } + + #endregion object overrides + + /// + /// Factory method which creates a QualifiedNameBuilder + /// from an already existing qualified name string. + /// + /// The qualified name + /// A QualifiedNameBuilder from the provided string. + public static QualifiedNameBuilder FromString(string name) + { + // Assume Master Test Suite name + return FromString(DefaultMasterTestSuiteName, name); + } + + /// + /// Factory method which creates a QualifiedNameBuilder + /// from an already existing qualified name string. + /// + /// The local name of the master test suite + /// The qualified name + /// A QualifiedNameBuilder from the provided string. + public static QualifiedNameBuilder FromString(string masterSuite, string name) + { + Utility.Code.Require(masterSuite, "masterSuite"); + Utility.Code.Require(name, "name"); + + QualifiedNameBuilder builder = new QualifiedNameBuilder(); + + builder.Push(masterSuite); + + foreach (string part in name.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries)) + { + builder.Push(part); + } + + return builder; + } + + #region ICloneable + + object ICloneable.Clone() + { + return this.Clone(); + } + + public QualifiedNameBuilder Clone() + { + // NOTE Make an explicit copy of the path + return new QualifiedNameBuilder(new List(this.Path)); + } + + #endregion ICloneable + } } \ No newline at end of file diff --git a/BoostTestAdapter/Utility/VisualStudio/DefaultDiscoverySink.cs b/BoostTestAdapter/Utility/VisualStudio/DefaultTestCaseDiscoverySink.cs similarity index 96% rename from BoostTestAdapter/Utility/VisualStudio/DefaultDiscoverySink.cs rename to BoostTestAdapter/Utility/VisualStudio/DefaultTestCaseDiscoverySink.cs index 8410142..f0562e1 100644 --- a/BoostTestAdapter/Utility/VisualStudio/DefaultDiscoverySink.cs +++ b/BoostTestAdapter/Utility/VisualStudio/DefaultTestCaseDiscoverySink.cs @@ -1,66 +1,66 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using System.Collections.Generic; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; - -namespace BoostTestAdapter.Utility.VisualStudio -{ - /// - /// An ITestCaseDiscoverySink implementation. Aggregates all tests - /// within an internal collection which is publicly accessible. - /// - public class DefaultTestCaseDiscoverySink : ITestCaseDiscoverySink - { - private ICollection _tests = new HashSet(new TestCaseComparer()); - - /// - /// The collection of discovered TestCases - /// - public IEnumerable Tests - { - get - { - return _tests; - } - } - - #region ITestCaseDiscoverySink - - public void SendTestCase(TestCase discoveredTest) - { - this._tests.Add(discoveredTest); - } - - #endregion ITestCaseDiscoverySink - } - - /// - /// TestCase equality comparer which defines equality based on the TestCase's - /// Fully Qualified Name. - /// - public class TestCaseComparer : IEqualityComparer - { - #region IEqualityComparer - - public bool Equals(TestCase x, TestCase y) - { - Utility.Code.Require(x, "x"); - Utility.Code.Require(y, "y"); - - return (x.FullyQualifiedName == y.FullyQualifiedName) && (x.Source == y.Source); - } - - public int GetHashCode(TestCase obj) - { - Utility.Code.Require(obj, "obj"); - - return obj.FullyQualifiedName.GetHashCode() ^ obj.Source.GetHashCode(); - } - - #endregion IEqualityComparer - } +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System.Collections.Generic; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +namespace BoostTestAdapter.Utility.VisualStudio +{ + /// + /// An ITestCaseDiscoverySink implementation. Aggregates all tests + /// within an internal collection which is publicly accessible. + /// + public class DefaultTestCaseDiscoverySink : ITestCaseDiscoverySink + { + private ICollection _tests = new HashSet(new TestCaseComparer()); + + /// + /// The collection of discovered TestCases + /// + public IEnumerable Tests + { + get + { + return _tests; + } + } + + #region ITestCaseDiscoverySink + + public void SendTestCase(TestCase discoveredTest) + { + this._tests.Add(discoveredTest); + } + + #endregion ITestCaseDiscoverySink + } + + /// + /// TestCase equality comparer which defines equality based on the TestCase's + /// Fully Qualified Name. + /// + public class TestCaseComparer : IEqualityComparer + { + #region IEqualityComparer + + public bool Equals(TestCase x, TestCase y) + { + Utility.Code.Require(x, "x"); + Utility.Code.Require(y, "y"); + + return (x.FullyQualifiedName == y.FullyQualifiedName) && (x.Source == y.Source); + } + + public int GetHashCode(TestCase obj) + { + Utility.Code.Require(obj, "obj"); + + return obj.FullyQualifiedName.GetHashCode() ^ obj.Source.GetHashCode(); + } + + #endregion IEqualityComparer + } } \ No newline at end of file diff --git a/BoostTestAdapterNunit/BoostDataTestCaseVerifierTest.cs b/BoostTestAdapterNunit/BoostDataTestCaseVerifierTest.cs new file mode 100644 index 0000000..96cd91f --- /dev/null +++ b/BoostTestAdapterNunit/BoostDataTestCaseVerifierTest.cs @@ -0,0 +1,139 @@ +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using NUnit.Framework; + +using BoostTestAdapter.Boost.Test; +using BoostTestAdapter.Utility; + +using BoostTestAdapterNunit.Utility; + +namespace BoostTestAdapterNunit +{ + [TestFixture] + internal class BoostDataTestCaseVerifierTest + { + private const string _sourceFile = @"c:\test.cpp"; + + /// + /// Creates a 'default' TestFrameworkBuilder instance for this TestFixture + /// + /// A 'default' TestFrameworkBuilder instance + private static TestFrameworkBuilder CreateFrameworkBuilder() + { + return new TestFrameworkBuilder("test.exe", "MyTest", 1); + } + + /// + /// Locates the test suite with the provided fully qualified name + /// + /// The framework from which to locate the test suite + /// The test suite's fully qualified name + /// The test suite which was located or null if it was not found + private static TestSuite LocateSuite(TestFramework framework, string fullyQualifiedName) + { + var suite = BoostTestLocator.Locate(framework, fullyQualifiedName); + + Assert.That(suite, Is.Not.Null); + Assert.That(suite, Is.TypeOf()); + + return (TestSuite) suite; + } + + /// + /// Verifies if the test suite with the specified fully qualified name is a BOOST_DATA_TEST_CASE + /// + /// The framework from which to locate the test suite + /// The test suite's fully qualified name + /// true if the test suite is a BOOST_DATA_TEST_CASE; false otherwise + private static bool IsBoostDataTestCase(TestFramework framework, string fullyQualifiedName) + { + var suite = LocateSuite(framework, fullyQualifiedName); + return (suite != null) && (BoostDataTestCaseVerifier.IsBoostDataTestCase(suite)); + } + + /// + /// Assert that: A valid BOOST_DATA_TEST_CASE can be identified as such + /// + [Test] + public void ValidBoostDataTestCase() + { + TestFramework framework = CreateFrameworkBuilder(). + TestSuite("DataTestCase", 2, new SourceFileInfo(_sourceFile, 10)). + TestCase("_0", 65536, new SourceFileInfo(_sourceFile, 10)). + TestCase("_1", 65537, new SourceFileInfo(_sourceFile, 10)). + TestCase("_2", 65538, new SourceFileInfo(_sourceFile, 10)). + TestCase("_3", 65539, new SourceFileInfo(_sourceFile, 10)). + EndSuite(). + Build(); + + bool result = IsBoostDataTestCase(framework, "DataTestCase"); + Assert.That(result, Is.True); + } + + /// + /// Assert that: A test suite containing tests with a pattern similar to BOOST_DATA_TEST_CASE is not identified + /// as a data test case instance if the line numbers differ from the parent test suite instance + /// + [Test] + public void FakeBoostDataTestCase() + { + TestFramework framework = CreateFrameworkBuilder(). + TestSuite("BoostUnitTest", 2, new SourceFileInfo(_sourceFile, 10)). + TestCase("_0", 65536, new SourceFileInfo(_sourceFile, 13)). + TestCase("_1", 65537, new SourceFileInfo(_sourceFile, 23)). + TestCase("_2", 65538, new SourceFileInfo(_sourceFile, 33)). + TestCase("_3", 65539, new SourceFileInfo(_sourceFile, 43)). + EndSuite(). + Build(); + + bool result = IsBoostDataTestCase(framework, "BoostUnitTest"); + Assert.That(result, Is.False); + } + + /// + /// Assert that: A regular test suite containing test suites and test cases is not identified as a + /// BOOST_DATA_TEST_CASE + /// + [Test] + public void RegularTestSuite() + { + TestFramework framework = CreateFrameworkBuilder(). + TestSuite("Suite1", 2, new SourceFileInfo(_sourceFile, 10)). + TestSuite("Suite2", 3, new SourceFileInfo(_sourceFile, 15)). + TestCase("Test1", 65536, new SourceFileInfo(_sourceFile, 23)). + EndSuite(). + EndSuite(). + Build(); + + { + bool result = IsBoostDataTestCase(framework, "Suite1"); + Assert.That(result, Is.False); + } + + { + bool result = IsBoostDataTestCase(framework, "Suite1/Suite2"); + Assert.That(result, Is.False); + } + } + + /// + /// Assert that: A regular test suite containing solely test cases is not identified as a + /// BOOST_DATA_TEST_CASE + /// + [Test] + public void RegularTestCase() + { + TestFramework framework = CreateFrameworkBuilder(). + TestSuite("Suite1", 2, new SourceFileInfo(_sourceFile, 10)). + TestCase("Test1", 65536, new SourceFileInfo(_sourceFile, 23)). + EndSuite(). + Build(); + + bool result = IsBoostDataTestCase(framework, "Suite1"); + Assert.That(result, Is.False); + } + } +} diff --git a/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj b/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj index 0f13538..16808bb 100644 --- a/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj +++ b/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj @@ -1,182 +1,186 @@ - - - - - Debug - AnyCPU - {FE58A67C-D313-46FD-B8F3-F80383EE5FD1} - Library - Properties - BoostTestAdapterNunit - BoostTestAdapterNunit - v4.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - $(MSBuildProgramFiles32) - - $(ProgramFiles%28x86%29) + + + + + Debug + AnyCPU + {FE58A67C-D313-46FD-B8F3-F80383EE5FD1} + Library + Properties + BoostTestAdapterNunit + BoostTestAdapterNunit + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(MSBuildProgramFiles32) + + $(ProgramFiles%28x86%29) - $(ProgramFiles) (x86) - - $(ProgramFiles) - - - - ..\Antlr.DOT\lib\Antlr4.Runtime.dll - - - ..\packages\FakeItEasy.1.13.1\lib\net40\FakeItEasy.dll - - - False - $(ProgramFiles32)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - - - ..\packages\NUnit.2.6.4\lib\nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {488ae1ce-107d-4f5b-9f1f-ed7905d5ce75} - Antlr.DOT - - - {bc4b3bed-9241-4dd6-8070-a9b66dfc08c1} - BoostTestAdapter - - - {62347cc7-c839-413d-a7ce-598409f6f15b} - VisualStudioAdapter - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + NB this trick (Adding a literal " (x86)" to the 64 bit Program Files path) may or may not work on all versions/locales of Windows --> + $(ProgramFiles) (x86) + + $(ProgramFiles) + + + + ..\Antlr.DOT\lib\Antlr4.Runtime.dll + + + ..\packages\FakeItEasy.1.13.1\lib\net40\FakeItEasy.dll + + + False + $(ProgramFiles32)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {488ae1ce-107d-4f5b-9f1f-ed7905d5ce75} + Antlr.DOT + + + {bc4b3bed-9241-4dd6-8070-a9b66dfc08c1} + BoostTestAdapter + + + {62347cc7-c839-413d-a7ce-598409f6f15b} + VisualStudioAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> \ No newline at end of file diff --git a/BoostTestAdapterNunit/BoostTestExecutorTest.cs b/BoostTestAdapterNunit/BoostTestExecutorTest.cs index 237630b..6d5ec92 100644 --- a/BoostTestAdapterNunit/BoostTestExecutorTest.cs +++ b/BoostTestAdapterNunit/BoostTestExecutorTest.cs @@ -466,7 +466,11 @@ private VSTestCase CreateTestCase(string fullyQualifiedName, string source) { VSTestCase test = new VSTestCase(fullyQualifiedName, BoostTestExecutor.ExecutorUri, source); - test.Traits.Add(VSTestModel.TestSuiteTrait, QualifiedNameBuilder.FromString(fullyQualifiedName).Pop().ToString()); + var fullyQualifiedNameBuilder = QualifiedNameBuilder.FromString(fullyQualifiedName); + + test.DisplayName = fullyQualifiedNameBuilder.Peek(); + + test.Traits.Add(VSTestModel.TestSuiteTrait, fullyQualifiedNameBuilder.Pop().ToString()); test.Traits.Add(VSTestModel.StatusTrait, VSTestModel.TestEnabled); return test; @@ -972,7 +976,7 @@ public void TestTestSuiteBatchedRuns() List>> expectedBatches = new List>> { - new KeyValuePair>(DefaultSource, new List {"A/Test1", "A/Test2"}), + new KeyValuePair>(DefaultSource, new List {"A/Test1", "Test2"}), new KeyValuePair>(DefaultSource, new List {"B/Test1"}), new KeyValuePair>(otherSource, new List {"A/Test1"}) }; diff --git a/BoostTestAdapterNunit/BoostTestTest.cs b/BoostTestAdapterNunit/BoostTestTest.cs index cecf636..389287e 100644 --- a/BoostTestAdapterNunit/BoostTestTest.cs +++ b/BoostTestAdapterNunit/BoostTestTest.cs @@ -4,14 +4,11 @@ // http://www.boost.org/LICENSE_1_0.txt) using System; -using System.IO; -using System.Xml; -using System.Xml.Serialization; using BoostTestAdapter.Boost.Test; using BoostTestAdapter.Utility; -using BoostTestAdapterNunit.Utility; using NUnit.Framework; - +using BoostTestAdapterNunit.Utility; + namespace BoostTestAdapterNunit { [TestFixture] @@ -25,57 +22,7 @@ class BoostTestTest private const string Source = @"C:\tests.dll"; #endregion Test Data - - #region Helper Classes - /// - /// ITestVisitor implementation which looks up test units based on their qualified name. - /// - private class TestUnitLookup : ITestVisitor - { - public TestUnitLookup(string fullyQualifiedName) - { - this.FullyQualifiedName = fullyQualifiedName; - } - - public string FullyQualifiedName { get; private set; } - public TestUnit Unit { get; private set; } - - public void Visit(TestCase testCase) - { - Check(testCase); - } - - public void Visit(TestSuite testSuite) - { - if (!Check(testSuite)) - { - foreach (TestUnit child in testSuite.Children) - { - child.Apply(this); - if (this.Unit != null) - { - break; - } - } - } - } - - private bool Check(TestUnit unit) - { - bool match = (unit.FullyQualifiedName == this.FullyQualifiedName); - - if (match) - { - this.Unit = unit; - } - - return match; - } - } - - #endregion Helper Classes - #region Helper Methods /// @@ -86,9 +33,7 @@ private bool Check(TestUnit unit) /// The test unit with the requested fully qualified name or null if it cannot be found private TestUnit Lookup(TestUnit root, string fullyQualifiedName) { - TestUnitLookup lookup = new TestUnitLookup(fullyQualifiedName); - root.Apply(lookup); - return lookup.Unit; + return BoostTestLocator.Locate(root, fullyQualifiedName); } /// diff --git a/BoostTestAdapterNunit/DOTDeserialisationTest.cs b/BoostTestAdapterNunit/DOTDeserialisationTest.cs index 86adc6f..b429ae6 100644 --- a/BoostTestAdapterNunit/DOTDeserialisationTest.cs +++ b/BoostTestAdapterNunit/DOTDeserialisationTest.cs @@ -1,160 +1,177 @@ -// (C) Copyright ETAS 2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -using BoostTestAdapter.Utility; -using BoostTestAdapter.Boost.Test; - -using BoostTestAdapterNunit.Utility; - -using NUnit.Framework; - -namespace BoostTestAdapterNunit -{ - [TestFixture] - public class DOTDeserialisationTest - { - #region Test Data - - /// - /// Default source identifier for test purposes - /// - private static string Source - { - get - { - return "source.exe"; - } - } - - #endregion Test Data - - #region Helper Methods - - /// - /// Compares a test framework deserialised from the provided embedded resource against the expected one - /// - /// Path to an embedded resource which is to be treated as input for TestFramework deserialisation - /// The TestFramework which the deserialised version should be compared against - private void Compare(string resource, TestFramework expected) - { - using (var stream = TestHelper.LoadEmbeddedResource(resource)) - { - TestFrameworkDOTDeserialiser parser = new TestFrameworkDOTDeserialiser(Source); - TestFramework framework = parser.Deserialise(stream); - - FrameworkEqualityVisitor.IsEqualTo(framework, expected, false); - } - } - - #endregion Helper Methods - - /// - /// Deserialisation of (relatively large) Boost Test DOT content containing most features - /// - /// Test aims: - /// - Ensure that a TestFramework can be deserialised correctly from DOT content - /// - [Test] - public void DeserialiseDOT() - { - TestFramework expected = new TestFrameworkBuilder(Source, "sample", 1, false). - TestSuite("suite_1", 2, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 6), false). - TestSuite("suite_2", 3, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 8), false). - TestCase("test_3", 65536, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 14), null, false). - TestCase("test_4", 65537, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 44), null, false). - TestCase("test_5", 65538, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 62), null, false). - EndSuite(). - TestSuite("suite_6", 4, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 19), false). - TestCase("test_7", 65539, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 24), null, false). - TestCase("test_8", 65540, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 40), null, false). - TestCase("test_9", 65541, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 90), null, false). - TestCase("test_10", 65542, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 105), null, false). - TestCase("test_11", 65543, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 121), null, false). - TestCase("test_12", 65544, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 134), null, false). - TestCase("test_13", 65545, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 310), null, false). - EndSuite(). - TestSuite("suite_14", 5, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 20), false). - TestCase("test_15", 65546, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 27), null, false). - TestCase("test_16", 65547, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 49), null, false). - TestCase("test_17", 65548, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 59), null, false). - TestCase("test_18", 65549, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 88), null, false). - EndSuite(). - TestSuite("suite_19", 6, new SourceFileInfo(@"d:\sample.boostunittest\file_4.cpp", 7), false). - TestCase("test_20", 65550, new SourceFileInfo(@"d:\sample.boostunittest\file_4.cpp", 12), null, false). - EndSuite(). - TestSuite("suite_21", 7, new SourceFileInfo(@"d:\sample.boostunittest\file_5.cpp", 8), false). - TestCase("test_22", 65551, new SourceFileInfo(@"d:\sample.boostunittest\file_5.cpp", 14), null, false). - EndSuite(). - TestSuite("suite_23", 8, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 13), false). - TestCase("test_24", 65552, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 18), null, false). - TestCase("test_25", 65553, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 28), null, false). - TestCase("test_26", 65554, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 38), null, false). - TestCase("test_27", 65555, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 48), null, false). - TestCase("test_28", 65556, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 59), null, false). - TestCase("test_29", 65557, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 70), null, false). - EndSuite(). - TestSuite("suite_30", 9, new SourceFileInfo(@"d:\sample.boostunittest\file_7.cpp", 8), false). - TestCase("test_31", 65558, new SourceFileInfo(@"d:\sample.boostunittest\file_7.cpp", 14), new[] { "hello", "world", "labels" }, false). - EndSuite(). - EndSuite(). - Build(); - - Compare("BoostTestAdapterNunit.Resources.ListContentDOT.sample.list.content.gv", expected); - } - - /// - /// Deserialisation of Boost Test DOT content consisting of the following scenario: - /// - /// - Master Test Suite - /// -- Test Case - /// -- Test Suite - /// --- Test Case - /// -- Test Case - /// - /// Test aims: - /// - Ensure that a TestFramework can be deserialised correctly from DOT content - /// - [Test] - public void TestSuiteWithChildTestsAndSuites() - { - TestFramework expected = new TestFrameworkBuilder(Source, "MyTest", 1, false). - TestCase("Test", 65536, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 30),null, false). - TestSuite("Suite", 2, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 35), false). - TestCase("Test", 65537, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 37), null, false). - EndSuite(). - TestCase("TestB", 65538, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 44), null, false). - Build(); - - Compare("BoostTestAdapterNunit.Resources.ListContentDOT.sample.3.list.content.gv", expected); - } - - - /// - /// Deserialisation of Boost Test DOT content consisting of test cases which are - /// explicitly enabled or disabled using boost decorators - /// - /// Test aims: - /// - Ensure that the decorators are correctly read and grouping is done correctly - /// - [Test] - public void TestsWithDecorators() - { - TestFramework expected = new TestFrameworkBuilder(Source, "Sample", 1). - TestSuite("Suite1", 2, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 11)). - TestCase("MyBoost_Test1", 65536, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 12)). - TestCase("MyBoost_Test2", 65537, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 16), null, false). - TestCase("MyBoost_Test3", 65538, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 20)). - TestCase("MyBoost_Test4", 65539, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 24), null, false). - EndSuite(). - TestCase("MyBoost_Test5", 65540, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 30)). - TestCase("MyBoost_Test6", 65541, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 34), null, false). - TestCase("MyBoost_Test7", 65542, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 38)). - TestCase("MyBoost_Test8", 65543, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 42), null, false). - Build(); - - Compare("BoostTestAdapterNunit.Resources.ListContentDOT.test_list_content.gv", expected); - } - } +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using BoostTestAdapter.Utility; +using BoostTestAdapter.Boost.Test; + +using BoostTestAdapterNunit.Utility; + +using NUnit.Framework; + +namespace BoostTestAdapterNunit +{ + [TestFixture] + public class DOTDeserialisationTest + { + #region Test Data + + /// + /// Default source identifier for test purposes + /// + private static string Source + { + get + { + return "source.exe"; + } + } + + #endregion Test Data + + #region Helper Methods + + /// + /// Compares a test framework deserialised from the provided embedded resource against the expected one + /// + /// Path to an embedded resource which is to be treated as input for TestFramework deserialisation + /// The TestFramework which the deserialised version should be compared against + private void Compare(string resource, TestFramework expected) + { + using (var stream = TestHelper.LoadEmbeddedResource(resource)) + { + TestFrameworkDOTDeserialiser parser = new TestFrameworkDOTDeserialiser(Source); + TestFramework framework = parser.Deserialise(stream); + + FrameworkEqualityVisitor.IsEqualTo(framework, expected, false); + } + } + + #endregion Helper Methods + + /// + /// Deserialisation of (relatively large) Boost Test DOT content containing most features + /// + /// Test aims: + /// - Ensure that a TestFramework can be deserialised correctly from DOT content + /// + [Test] + public void DeserialiseDOT() + { + TestFramework expected = new TestFrameworkBuilder(Source, "sample", 1, false). + TestSuite("suite_1", 2, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 6), false). + TestSuite("suite_2", 3, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 8), false). + TestCase("test_3", 65536, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 14), null, false). + TestCase("test_4", 65537, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 44), null, false). + TestCase("test_5", 65538, new SourceFileInfo(@"d:\sample.boostunittest\file_1.cpp", 62), null, false). + EndSuite(). + TestSuite("suite_6", 4, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 19), false). + TestCase("test_7", 65539, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 24), null, false). + TestCase("test_8", 65540, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 40), null, false). + TestCase("test_9", 65541, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 90), null, false). + TestCase("test_10", 65542, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 105), null, false). + TestCase("test_11", 65543, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 121), null, false). + TestCase("test_12", 65544, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 134), null, false). + TestCase("test_13", 65545, new SourceFileInfo(@"d:\sample.boostunittest\file_2.cpp", 310), null, false). + EndSuite(). + TestSuite("suite_14", 5, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 20), false). + TestCase("test_15", 65546, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 27), null, false). + TestCase("test_16", 65547, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 49), null, false). + TestCase("test_17", 65548, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 59), null, false). + TestCase("test_18", 65549, new SourceFileInfo(@"d:\sample.boostunittest\file_3.cpp", 88), null, false). + EndSuite(). + TestSuite("suite_19", 6, new SourceFileInfo(@"d:\sample.boostunittest\file_4.cpp", 7), false). + TestCase("test_20", 65550, new SourceFileInfo(@"d:\sample.boostunittest\file_4.cpp", 12), null, false). + EndSuite(). + TestSuite("suite_21", 7, new SourceFileInfo(@"d:\sample.boostunittest\file_5.cpp", 8), false). + TestCase("test_22", 65551, new SourceFileInfo(@"d:\sample.boostunittest\file_5.cpp", 14), null, false). + EndSuite(). + TestSuite("suite_23", 8, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 13), false). + TestCase("test_24", 65552, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 18), null, false). + TestCase("test_25", 65553, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 28), null, false). + TestCase("test_26", 65554, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 38), null, false). + TestCase("test_27", 65555, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 48), null, false). + TestCase("test_28", 65556, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 59), null, false). + TestCase("test_29", 65557, new SourceFileInfo(@"d:\sample.boostunittest\file_6.cpp", 70), null, false). + EndSuite(). + TestSuite("suite_30", 9, new SourceFileInfo(@"d:\sample.boostunittest\file_7.cpp", 8), false). + TestCase("test_31", 65558, new SourceFileInfo(@"d:\sample.boostunittest\file_7.cpp", 14), new[] { "hello", "world", "labels" }, false). + EndSuite(). + EndSuite(). + Build(); + + Compare("BoostTestAdapterNunit.Resources.ListContentDOT.sample.list.content.gv", expected); + } + + /// + /// Deserialisation of Boost Test DOT content consisting of the following scenario: + /// + /// - Master Test Suite + /// -- Test Case + /// -- Test Suite + /// --- Test Case + /// -- Test Case + /// + /// Test aims: + /// - Ensure that a TestFramework can be deserialised correctly from DOT content + /// + [Test] + public void TestSuiteWithChildTestsAndSuites() + { + TestFramework expected = new TestFrameworkBuilder(Source, "MyTest", 1, false). + TestCase("Test", 65536, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 30),null, false). + TestSuite("Suite", 2, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 35), false). + TestCase("Test", 65537, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 37), null, false). + EndSuite(). + TestCase("TestB", 65538, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 44), null, false). + Build(); + + Compare("BoostTestAdapterNunit.Resources.ListContentDOT.sample.3.list.content.gv", expected); + } + + /// + /// Deserialisation of Boost Test DOT content consisting of test cases which are + /// explicitly enabled or disabled using boost decorators + /// + /// Test aims: + /// - Ensure that the decorators are correctly read and grouping is done correctly + /// + [Test] + public void TestsWithDecorators() + { + TestFramework expected = new TestFrameworkBuilder(Source, "Sample", 1). + TestSuite("Suite1", 2, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 11)). + TestCase("MyBoost_Test1", 65536, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 12)). + TestCase("MyBoost_Test2", 65537, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 16), null, false). + TestCase("MyBoost_Test3", 65538, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 20)). + TestCase("MyBoost_Test4", 65539, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 24), null, false). + EndSuite(). + TestCase("MyBoost_Test5", 65540, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 30)). + TestCase("MyBoost_Test6", 65541, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 34), null, false). + TestCase("MyBoost_Test7", 65542, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 38)). + TestCase("MyBoost_Test8", 65543, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 42), null, false). + Build(); + + Compare("BoostTestAdapterNunit.Resources.ListContentDOT.test_list_content.gv", expected); + } + + /// + /// Assert that: It is possible to deserialize --list_content=DOT output consisting of a BOOST_DATA_TEST_CASE + /// + [Test] + public void DeserializeBoostDataTestCase() + { + TestFramework expected = new TestFrameworkBuilder(Source, "DataTestCaseExample", 1). + TestSuite("BoostUnitTest", 2, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 10)). + TestCase("_0", 65536, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 10)). + TestCase("_1", 65537, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 10)). + TestCase("_2", 65538, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 10)). + TestCase("_3", 65539, new SourceFileInfo(@"c:\boostunittest\boostunittestsample.cpp", 10)). + EndSuite(). + Build(); + + Compare("BoostTestAdapterNunit.Resources.ListContentDOT.boost_data_test_case.gv", expected); + } + } } \ No newline at end of file diff --git a/BoostTestAdapterNunit/Resources/ListContentDOT/boost_data_test_case.gv b/BoostTestAdapterNunit/Resources/ListContentDOT/boost_data_test_case.gv new file mode 100644 index 0000000..1a8a681 --- /dev/null +++ b/BoostTestAdapterNunit/Resources/ListContentDOT/boost_data_test_case.gv @@ -0,0 +1,17 @@ +digraph G {rankdir=LR; +tu1[shape=ellipse,peripheries=2,fontname=Helvetica,color=green,label="DataTestCaseExample"]; +{ +tu2[shape=Mrecord,fontname=Helvetica,color=green,label="BoostUnitTest|c:\boostunittest\boostunittestsample.cpp(10)"]; +tu1 -> tu2; +{ +tu65536[shape=Mrecord,fontname=Helvetica,color=green,label="_0|c:\boostunittest\boostunittestsample.cpp(10)"]; +tu2 -> tu65536; +tu65537[shape=Mrecord,fontname=Helvetica,color=green,label="_1|c:\boostunittest\boostunittestsample.cpp(10)"]; +tu2 -> tu65537; +tu65538[shape=Mrecord,fontname=Helvetica,color=green,label="_2|c:\boostunittest\boostunittestsample.cpp(10)"]; +tu2 -> tu65538; +tu65539[shape=Mrecord,fontname=Helvetica,color=green,label="_3|c:\boostunittest\boostunittestsample.cpp(10)"]; +tu2 -> tu65539; +} +} +} diff --git a/BoostTestAdapterNunit/Utility/BoostTestLocator.cs b/BoostTestAdapterNunit/Utility/BoostTestLocator.cs new file mode 100644 index 0000000..dcc0a9b --- /dev/null +++ b/BoostTestAdapterNunit/Utility/BoostTestLocator.cs @@ -0,0 +1,105 @@ +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using BoostTestAdapter.Boost.Test; + +namespace BoostTestAdapterNunit.Utility +{ + /// + /// ITestVisitor implementation which looks up test units based on their qualified name. + /// + public class BoostTestLocator : ITestVisitor + { + /// + /// Constructor + /// + /// The test unit's fully qualified name to locate + private BoostTestLocator(string fullyQualifiedName) + { + this.FullyQualifiedName = fullyQualifiedName; + } + + /// + /// The fully qualified name of the test unit to locate + /// + public string FullyQualifiedName { get; private set; } + + /// + /// The resultant test unit + /// + public TestUnit Unit { get; private set; } + + #region ITestVisitor + + public void Visit(TestCase testCase) + { + Check(testCase); + } + + public void Visit(TestSuite testSuite) + { + if (!Check(testSuite)) + { + foreach (TestUnit child in testSuite.Children) + { + child.Apply(this); + + if (this.Unit != null) + { + break; + } + } + } + } + + #endregion + + /// + /// Checks if the provided test unit is the one which was requested for location + /// + /// The test unit to test + /// true if the provided test unit was the one requested for location; false otherwise + private bool Check(TestUnit unit) + { + bool match = (unit.FullyQualifiedName == this.FullyQualifiedName); + + if (match) + { + this.Unit = unit; + } + + return match; + } + + /// + /// Locates the test unit with the specified fully qualified name from the provided test framework + /// + /// The framework from which to locate the test unit + /// The test unit's fully qualified name to locate + /// The located test unit or null if it was not found + public static TestUnit Locate(TestFramework root, string fullyQualifiedName) + { + return Locate(root?.MasterTestSuite, fullyQualifiedName); + } + + /// + /// Locates the test unit with the specified fully qualified name from the provided root + /// + /// The root test unit from which to locate the test unit + /// The test unit's fully qualified name to locate + /// The located test unit or null if it was not found + public static TestUnit Locate(TestUnit root, string fullyQualifiedName) + { + if (root == null) + { + return null; + } + + var locator = new BoostTestLocator(fullyQualifiedName); + root.Apply(locator); + return locator.Unit; + } + } +} diff --git a/BoostTestAdapterNunit/VSDiscoveryVisitorTest.cs b/BoostTestAdapterNunit/VSDiscoveryVisitorTest.cs new file mode 100644 index 0000000..4b4ae0f --- /dev/null +++ b/BoostTestAdapterNunit/VSDiscoveryVisitorTest.cs @@ -0,0 +1,137 @@ +// (C) Copyright ETAS 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +using System; +using NUnit.Framework; + +using System.Linq; +using System.Collections.Generic; + +using BoostTestAdapter; +using BoostTestAdapter.Boost.Test; +using BoostTestAdapter.Discoverers; +using BoostTestAdapter.Utility; +using BoostTestAdapter.Utility.VisualStudio; + +using BoostTestCase = BoostTestAdapter.Boost.Test.TestCase; +using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; + +using BoostTestAdapterNunit.Utility; + +namespace BoostTestAdapterNunit +{ + [TestFixture] + internal class VSDiscoveryVisitorTest + { + /// + /// Default test source file + /// + private const string _sourceFile = @"c:\test.cpp"; + + /// + /// Creates a 'default' TestFrameworkBuilder instance for this TestFixture + /// + /// A 'default' TestFrameworkBuilder instance + private static TestFrameworkBuilder CreateFrameworkBuilder() + { + return new TestFrameworkBuilder("test.exe", "MyTest", 1); + } + + /// + /// Discovers all tests cases enumerated within the provided framework + /// + /// The framework to discover tests from + /// An enumeration of all the discovered tests + private static IEnumerable Discover(TestFramework framework) + { + var sink = new DefaultTestCaseDiscoverySink(); + var visitor = new VSDiscoveryVisitor(framework.Source, sink); + framework.MasterTestSuite.Apply(visitor); + return sink.Tests; + } + + /// + /// Locates the test case with the provided fully qualified name from the provided framework + /// + /// The framework to locate the test case from + /// The test case fully qualified name to locate + /// The located TestCase instance or null if it is not found + private static BoostTestCase LocateTestCase(TestFramework framework, string fullyQualifiedName) + { + var unit = BoostTestLocator.Locate(framework, fullyQualifiedName); + return unit as BoostTestCase; + } + + /// + /// Asserts 'common' test case details between the test case encoded in the framework and the respective Visual Studio test case + /// + /// The Visual Studio TestCase instance to verify + /// The framework to locate the test case from + /// The test case fully qualified name to locate + private static void AssertCommonTestCaseDetails(VSTestCase test, TestFramework framework, string fullyQualifiedName) + { + Assert.That(framework, Is.Not.Null); + + var expected = LocateTestCase(framework, fullyQualifiedName); + + Assert.That(expected, Is.Not.Null); + + AssertCommonTestCaseDetails(test, expected, framework.Source); + } + + /// + /// Asserts 'common' test case details between the Boost test case and the respective Visual Studio test case + /// + /// The Visual Studio TestCase instance to verify + /// The Boost TestCase to which test should match to + /// The source from which the Boost Test case was discovered from + private static void AssertCommonTestCaseDetails(VSTestCase test, BoostTestCase expected, string source) + { + Assert.That(expected, Is.Not.Null); + Assert.That(test, Is.Not.Null); + + Assert.That(test.Source, Is.EqualTo(source)); + Assert.That(test.ExecutorUri, Is.EqualTo(new Uri(BoostTestExecutor.ExecutorUriString))); + Assert.That(test.FullyQualifiedName, Is.EqualTo(expected.FullyQualifiedName)); + + if (expected.Source != null) + { + Assert.That(test.CodeFilePath, Is.EqualTo(expected.Source.File)); + Assert.That(test.LineNumber, Is.EqualTo(expected.Source.LineNumber)); + } + } + + /// + /// Assert that: Test display names for BOOST_DATA_TEST_CASE instances are different + /// than the names determined by the Boost.Test framework + /// + [Test] + public void BoostDataTestCase() + { + TestFramework framework = CreateFrameworkBuilder(). + TestSuite("DataTestCase", 2, new SourceFileInfo(_sourceFile, 10)). + TestCase("_0", 65536, new SourceFileInfo(_sourceFile, 10)). + TestCase("_1", 65537, new SourceFileInfo(_sourceFile, 10)). + TestCase("_2", 65538, new SourceFileInfo(_sourceFile, 10)). + TestCase("_3", 65539, new SourceFileInfo(_sourceFile, 10)). + EndSuite(). + Build(); + + var tests = Discover(framework).OrderBy(test => test.FullyQualifiedName).ToList(); + + Assert.That(tests.Count, Is.EqualTo(4)); + + AssertCommonTestCaseDetails(tests[0], framework, "DataTestCase/_0"); + AssertCommonTestCaseDetails(tests[1], framework, "DataTestCase/_1"); + AssertCommonTestCaseDetails(tests[2], framework, "DataTestCase/_2"); + AssertCommonTestCaseDetails(tests[3], framework, "DataTestCase/_3"); + + Assert.That(tests[0].DisplayName, Is.Not.EqualTo(LocateTestCase(framework, "DataTestCase/_0").Name)); + Assert.That(tests[1].DisplayName, Is.Not.EqualTo(LocateTestCase(framework, "DataTestCase/_1").Name)); + Assert.That(tests[2].DisplayName, Is.Not.EqualTo(LocateTestCase(framework, "DataTestCase/_2").Name)); + Assert.That(tests[3].DisplayName, Is.Not.EqualTo(LocateTestCase(framework, "DataTestCase/_3").Name)); + } + } +}