diff --git a/BoostTestAdapter.sln b/BoostTestAdapter.sln
new file mode 100644
index 0000000..1b6ddee
--- /dev/null
+++ b/BoostTestAdapter.sln
@@ -0,0 +1,59 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoostTestAdapter", "BoostTestAdapter\BoostTestAdapter.csproj", "{BC4B3BED-9241-4DD6-8070-A9B66DFC08C1}"
+ ProjectSection(ProjectDependencies) = postProject
+ {EB0051E3-1DDA-418C-ABAF-C1DA5339114C} = {EB0051E3-1DDA-418C-ABAF-C1DA5339114C}
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoostTestPlugin", "BoostTestPlugin\BoostTestPlugin.csproj", "{7918D2BE-590C-4F1B-9A72-9F1CE4640AA9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudioAdapter", "VisualStudioAdapter\VisualStudioAdapter.csproj", "{62347CC7-C839-413D-A7CE-598409F6F15B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudio2012Adapter", "VisualStudio2012Adapter\VisualStudio2012Adapter.csproj", "{30ECC867-CE89-425F-B452-7A8A320F727D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudio2013Adapter", "VisualStudio2013Adapter\VisualStudio2013Adapter.csproj", "{82DF0AEB-582A-4B38-96FC-AAEE773BEAFE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudio2015Adapter", "VisualStudio2015Adapter\VisualStudio2015Adapter.csproj", "{EB0051E3-1DDA-418C-ABAF-C1DA5339114C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoostTestAdapterNunit", "BoostTestAdapterNunit\BoostTestAdapterNunit.csproj", "{FE58A67C-D313-46FD-B8F3-F80383EE5FD1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BC4B3BED-9241-4DD6-8070-A9B66DFC08C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC4B3BED-9241-4DD6-8070-A9B66DFC08C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC4B3BED-9241-4DD6-8070-A9B66DFC08C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC4B3BED-9241-4DD6-8070-A9B66DFC08C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7918D2BE-590C-4F1B-9A72-9F1CE4640AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7918D2BE-590C-4F1B-9A72-9F1CE4640AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7918D2BE-590C-4F1B-9A72-9F1CE4640AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7918D2BE-590C-4F1B-9A72-9F1CE4640AA9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {62347CC7-C839-413D-A7CE-598409F6F15B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {62347CC7-C839-413D-A7CE-598409F6F15B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {62347CC7-C839-413D-A7CE-598409F6F15B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {62347CC7-C839-413D-A7CE-598409F6F15B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30ECC867-CE89-425F-B452-7A8A320F727D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30ECC867-CE89-425F-B452-7A8A320F727D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30ECC867-CE89-425F-B452-7A8A320F727D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30ECC867-CE89-425F-B452-7A8A320F727D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {82DF0AEB-582A-4B38-96FC-AAEE773BEAFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {82DF0AEB-582A-4B38-96FC-AAEE773BEAFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {82DF0AEB-582A-4B38-96FC-AAEE773BEAFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {82DF0AEB-582A-4B38-96FC-AAEE773BEAFE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB0051E3-1DDA-418C-ABAF-C1DA5339114C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB0051E3-1DDA-418C-ABAF-C1DA5339114C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB0051E3-1DDA-418C-ABAF-C1DA5339114C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB0051E3-1DDA-418C-ABAF-C1DA5339114C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE58A67C-D313-46FD-B8F3-F80383EE5FD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE58A67C-D313-46FD-B8F3-F80383EE5FD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE58A67C-D313-46FD-B8F3-F80383EE5FD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE58A67C-D313-46FD-B8F3-F80383EE5FD1}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/BoostTestAdapter/App.config b/BoostTestAdapter/App.config
new file mode 100644
index 0000000..400ca7b
--- /dev/null
+++ b/BoostTestAdapter/App.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/BoostStandardError.cs b/BoostTestAdapter/Boost/Results/BoostStandardError.cs
new file mode 100644
index 0000000..03ed9b9
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/BoostStandardError.cs
@@ -0,0 +1,57 @@
+using System.IO;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Standard Error as emitted by Boost Test executables
+ ///
+ public class BoostStandardError : BoostTestResultOutputBase
+ {
+ ///
+ /// Constructor accepting a path to the external file
+ ///
+ /// The path to an external file. File will be opened on construction.
+ public BoostStandardError(string path)
+ : base(path)
+ {
+ }
+
+ ///
+ /// Constructor accepting a stream to the file contents
+ ///
+ /// The file content stream.
+ public BoostStandardError(Stream stream)
+ : base(stream)
+ {
+ }
+
+ #region BoostTestResultOutputBase
+
+ public override void Parse(TestResultCollection collection)
+ {
+ Utility.Code.Require(collection, "collection");
+
+ string err = null;
+
+ using (StreamReader reader = new StreamReader(this.InputStream))
+ {
+ err = reader.ReadToEnd();
+ }
+
+ if (!string.IsNullOrEmpty(err))
+ {
+ // Attach the stderr output to each TestCase result in the collection
+ // since we cannot distinguish to which TestCase (in case multiple TestCases are registered)
+ // the output is associated with.
+ foreach (TestResult result in collection)
+ {
+ // Consider the whole standard error contents as 1 entry.
+ result.LogEntries.Add(new LogEntryStandardErrorMessage(err));
+ }
+ }
+ }
+
+ #endregion BoostTestResultOutputBase
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs b/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs
new file mode 100644
index 0000000..c6c0171
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/BoostStandardOutput.cs
@@ -0,0 +1,241 @@
+using System.IO;
+using System.Text.RegularExpressions;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Standard Output as emitted by Boost Test executables
+ ///
+ public class BoostStandardOutput : BoostTestResultOutputBase
+ {
+ #region Constructors
+
+ ///
+ /// Constructor accepting a path to the external file
+ ///
+ /// The path to an external file. File will be opened on construction.
+ public BoostStandardOutput(string path)
+ : base(path)
+ {
+ this.FailTestOnMemoryLeak = false;
+ }
+
+ ///
+ /// Constructor accepting a stream to the file contents
+ ///
+ /// The file content stream.
+ public BoostStandardOutput(Stream stream)
+ : base(stream)
+ {
+ this.FailTestOnMemoryLeak = false;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public bool FailTestOnMemoryLeak { get; set; }
+
+ #endregion Properties
+
+ #region IBoostOutputParser
+
+ ///
+ /// Processes the standard output and populates the relevant test result data of the referenced collection
+ ///
+ /// test result collection where the leak information data will be inserted at
+ public override void Parse(TestResultCollection collection)
+ {
+ string strConsoleOutput = StreamToString(this.InputStream);
+
+ //the below regex is intended to only to "detect" if any memory leaks are present. Note that any console output printed by the test generally appears before the memory leaks dump.
+ Regex regexObj = new Regex(@"Detected\smemory\sleaks!\nDumping objects\s->\n(.*)Object dump complete.", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline);
+ Match outputMatch = regexObj.Match(strConsoleOutput);
+
+ //leak has been detected
+ if (outputMatch.Success)
+ {
+ RegisterMemoryLeak(outputMatch.Groups[1].Value, collection);
+ }
+
+ // Extract non-memory leak output
+ string output = strConsoleOutput.Substring(0, ((outputMatch.Success) ? outputMatch.Index : strConsoleOutput.Length));
+ RegisterStandardOutputMessage(output, collection);
+ }
+
+ #endregion IBoostOutputParser
+
+ private void RegisterMemoryLeak(string leakInformation, TestResultCollection collection)
+ {
+ foreach (TestResult result in collection)
+ {
+ if (this.FailTestOnMemoryLeak)
+ {
+ result.Result = TestResultType.Failed;
+ }
+
+ Regex regexLeakInformation = new Regex(@"(?:([\\:\w\rA-z.]*?)([\w\d.]*)\((\d{1,})\)\s:\s)?\{(\d{1,})\}[\w\s\d]*,\s(\d{1,})[\s\w.]*\n(.*?)(?=$|(?:[\\\w.:]*\(\d{1,}\)\s:\s)?\{\d{1,}\d)", RegexOptions.IgnoreCase | RegexOptions.Singleline); //the old one wasRegex regexLeakInformation = new Regex(@"^(.*\\)(.*?)\((\d{1,})\).*?{(\d{1,})}.*?(\d{1,})\sbyte", RegexOptions.IgnoreCase | RegexOptions.Multiline);
+ /*
+
+ The same regex works for when the complete file path along with the line number are reported in the console output such as in the below sample output
+
+ d:\hwa\dev\svn\boostunittestadapterdev\branches\tempbugfixing\sample\boostunittest\boostunittest2\adapterbugs.cpp(58) : {869} normal block at 0x00A88A58, 4 bytes long.
+ Data: < > CD CD CD CD
+ d:\hwa\dev\svn\boostunittestadapterdev\branches\tempbugfixing\sample\boostunittest\boostunittest2\adapterbugs.cpp(55) : {868} normal block at 0x00A88788, 4 bytes long.
+ Data: < > F5 01 00 00
+
+ and also when this information is not reported such as in the below sample output
+
+ {869} normal block at 0x005E8998, 4 bytes long.
+ Data: < > CD CD CD CD
+ {868} normal block at 0x005E8848, 4 bytes long.
+ Data: < > F5 01 00 00
+
+ */
+
+ #region regexLeakInformation
+
+ // (?:([\\:\w\rA-z.]*?)([\w\d.]*)\((\d{1,})\)\s:\s)?\{(\d{1,})\}[\w\s\d]*,\s(\d{1,})[\s\w.]*\n(.*?)(?=$|(?:[\\\w.:]*\(\d{1,}\)\s:\s)?\{\d{1,}\d)
+ //
+ // Options: Case insensitive; Exact spacing; Dot matches line breaks; ^$ don't match at line breaks; Numbered capture
+ //
+ // Match the regular expression below «(?:([\\:\w\rA-z.]*?)([\w\d.]*)\((\d{1,})\)\s:\s)?»
+ // Between zero and one times, as many times as possible, giving back as needed (greedy) «?»
+ // Match the regex below and capture its match into backreference number 1 «([\\:\w\rA-z.]*?)»
+ // Match a single character present in the list below «[\\:\w\rA-z.]*?»
+ // Between zero and unlimited times, as few times as possible, expanding as needed (lazy) «*?»
+ // The backslash character «\\»
+ // The literal character “:” «:»
+ // A “word character” (Unicode; any letter or ideograph, digit, connector punctuation) «\w»
+ // The carriage return character «\r»
+ // A character in the range between “A” and “z” (case insensitive) «A-z»
+ // The literal character “.” «.»
+ // Match the regex below and capture its match into backreference number 2 «([\w\d.]*)»
+ // Match a single character present in the list below «[\w\d.]*»
+ // Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»
+ // A “word character” (Unicode; any letter or ideograph, digit, connector punctuation) «\w»
+ // A “digit” (0–9 in any Unicode script) «\d»
+ // The literal character “.” «.»
+ // Match the character “(” literally «\(»
+ // Match the regex below and capture its match into backreference number 3 «(\d{1,})»
+ // Match a single character that is a “digit” (0–9 in any Unicode script) «\d{1,}»
+ // Between one and unlimited times, as many times as possible, giving back as needed (greedy) «{1,}»
+ // Match the character “)” literally «\)»
+ // Match a single character that is a “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // Match the character “:” literally «:»
+ // Match a single character that is a “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // Match the character “{” literally «\{»
+ // Match the regex below and capture its match into backreference number 4 «(\d{1,})»
+ // Match a single character that is a “digit” (0–9 in any Unicode script) «\d{1,}»
+ // Between one and unlimited times, as many times as possible, giving back as needed (greedy) «{1,}»
+ // Match the character “}” literally «\}»
+ // Match a single character present in the list below «[\w\s\d]*»
+ // Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»
+ // A “word character” (Unicode; any letter or ideograph, digit, connector punctuation) «\w»
+ // A “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // A “digit” (0–9 in any Unicode script) «\d»
+ // Match the character “,” literally «,»
+ // Match a single character that is a “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // Match the regex below and capture its match into backreference number 5 «(\d{1,})»
+ // Match a single character that is a “digit” (0–9 in any Unicode script) «\d{1,}»
+ // Between one and unlimited times, as many times as possible, giving back as needed (greedy) «{1,}»
+ // Match a single character present in the list below «[\s\w.]*»
+ // Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»
+ // A “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // A “word character” (Unicode; any letter or ideograph, digit, connector punctuation) «\w»
+ // The literal character “.” «.»
+ // Match the line feed character «\n»
+ // Match the regex below and capture its match into backreference number 6 «(.*?)»
+ // Match any single character «.*?»
+ // Between zero and unlimited times, as few times as possible, expanding as needed (lazy) «*?»
+ // Assert that the regex below can be matched, starting at this position (positive lookahead) «(?=$|(?:[\\\w.:]*\(\d{1,}\)\s:\s)?\{\d{1,}\d)»
+ // Match this alternative (attempting the next alternative only if this one fails) «$»
+ // Assert position at the end of the string, or before the line break at the end of the string, if any (line feed) «$»
+ // Or match this alternative (the entire group fails if this one fails to match) «(?:[\\\w.:]*\(\d{1,}\)\s:\s)?\{\d{1,}\d»
+ // Match the regular expression below «(?:[\\\w.:]*\(\d{1,}\)\s:\s)?»
+ // Between zero and one times, as many times as possible, giving back as needed (greedy) «?»
+ // Match a single character present in the list below «[\\\w.:]*»
+ // Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»
+ // The backslash character «\\»
+ // A “word character” (Unicode; any letter or ideograph, digit, connector punctuation) «\w»
+ // A single character from the list “.:” «.:»
+ // Match the character “(” literally «\(»
+ // Match a single character that is a “digit” (0–9 in any Unicode script) «\d{1,}»
+ // Between one and unlimited times, as many times as possible, giving back as needed (greedy) «{1,}»
+ // Match the character “)” literally «\)»
+ // Match a single character that is a “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // Match the character “:” literally «:»
+ // Match a single character that is a “whitespace character” (any Unicode separator, tab, line feed, carriage return, vertical tab, form feed, next line) «\s»
+ // Match the character “{” literally «\{»
+ // Match a single character that is a “digit” (0–9 in any Unicode script) «\d{1,}»
+ // Between one and unlimited times, as many times as possible, giving back as needed (greedy) «{1,}»
+ // Match a single character that is a “digit” (0–9 in any Unicode script) «\d»
+
+ #endregion regexLeakInformation
+
+ Match matchLeakInformation = regexLeakInformation.Match(leakInformation);
+ while (matchLeakInformation.Success)
+ {
+ LogEntryMemoryLeak leak = new LogEntryMemoryLeak();
+
+ result.LogEntries.Add(leak);
+
+ //Capturing group 1,2 and 3 will have the 'Success' property false in case the C++ new operator has not been replaced via the macro
+
+ // Temporary variable used to try and parse unsigned integer values;
+ uint value = 0;
+
+ if (matchLeakInformation.Groups[1].Success && matchLeakInformation.Groups[2].Success && matchLeakInformation.Groups[3].Success)
+ {
+ leak.LeakSourceFilePath = matchLeakInformation.Groups[1].Value;
+
+ leak.LeakSourceFileName = matchLeakInformation.Groups[2].Value;
+
+ if (uint.TryParse(matchLeakInformation.Groups[3].Value, out value))
+ {
+ leak.LeakLineNumber = value;
+ }
+
+ leak.LeakSourceFileAndLineNumberReportingActive = true;
+ }
+ else
+ {
+ leak.LeakSourceFileAndLineNumberReportingActive = false;
+ }
+
+ if (uint.TryParse(matchLeakInformation.Groups[4].Value, out value))
+ {
+ leak.LeakMemoryAllocationNumber = value;
+ }
+
+ if (uint.TryParse(matchLeakInformation.Groups[5].Value, out value))
+ {
+ leak.LeakSizeInBytes = value;
+ }
+
+ leak.LeakLeakedDataContents = matchLeakInformation.Groups[6].Value;
+
+ matchLeakInformation = matchLeakInformation.NextMatch();
+ }
+ }
+ }
+
+ private static void RegisterStandardOutputMessage(string output, TestResultCollection collection)
+ {
+ if (!string.IsNullOrEmpty(output))
+ {
+ foreach (TestResult result in collection)
+ {
+ result.LogEntries.Add(new LogEntryStandardOutputMessage(output));
+ }
+ }
+ }
+
+ private static string StreamToString(Stream stream)
+ {
+ StreamReader reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/BoostTestResultOutputBase.cs b/BoostTestAdapter/Boost/Results/BoostTestResultOutputBase.cs
new file mode 100644
index 0000000..018a128
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/BoostTestResultOutputBase.cs
@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Base class for IBoostTestResultOutput implementations
+ /// providing common functionality.
+ ///
+ public abstract class BoostTestResultOutputBase : IBoostTestResultOutput
+ {
+ #region Constructors
+
+ ///
+ /// Constructor for external files.
+ ///
+ /// The path to an external file. File will be opened on construction.
+ protected BoostTestResultOutputBase(string path)
+ {
+ this.CloseStreamOnDispose = true;
+ this.InputStream = File.OpenRead(path);
+ this.IsDisposed = false;
+ }
+
+ ///
+ /// Constructor for streams. Ideal for test purposes.
+ ///
+ /// The stream containing the output.
+ protected BoostTestResultOutputBase(Stream stream)
+ {
+ this.CloseStreamOnDispose = false;
+ this.InputStream = stream;
+
+ this.IsDisposed = false;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Flag stating whether the stream should be closed on dispose.
+ ///
+ protected bool CloseStreamOnDispose { get; set; }
+
+ ///
+ /// The input stream representing the content.
+ ///
+ protected Stream InputStream { get; set; }
+
+ #endregion Properties
+
+ #region IBoostOutputParser
+
+ public abstract void Parse(TestResultCollection collection);
+
+ #endregion IBoostOutputParser
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ Dispose(true);
+
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!IsDisposed)
+ {
+ if (disposing)
+ {
+ if (this.CloseStreamOnDispose)
+ {
+ this.InputStream.Dispose();
+ }
+ }
+ }
+
+ IsDisposed = true;
+ }
+
+ private bool IsDisposed { get; set; }
+
+ #endregion IDisposable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/BoostTestResultXMLOutput.cs b/BoostTestAdapter/Boost/Results/BoostTestResultXMLOutput.cs
new file mode 100644
index 0000000..b0b455a
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/BoostTestResultXMLOutput.cs
@@ -0,0 +1,71 @@
+using System.IO;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results
+{
+
+ ///
+ /// Boost Test Result abstraction needed for the proper handling of XML documents.
+ ///
+ public abstract class BoostTestResultXMLOutput : BoostTestResultOutputBase
+ {
+ protected BoostTestResultXMLOutput(string path)
+ : base(AddXMLEncodingDeclaration(path))
+ {
+ this.CloseStreamOnDispose = true;
+ }
+
+ protected BoostTestResultXMLOutput(Stream stream)
+ : base(stream)
+ {
+
+ }
+
+ ///
+ /// Boost UTF does not add any XML Encoding Declaration in the XML file so in case a file contains German characters,
+ /// upon loading the xml document an exception will be thrown due to un-allowed characters
+ ///
+ /// path of the xml file to be loaded
+ /// Stream object containing the xml file data
+ private static Stream AddXMLEncodingDeclaration(string path)
+ {
+ MemoryStream memoryStream = null;
+ try
+ {
+ memoryStream = new MemoryStream();
+ StreamWriter writer = new StreamWriter(memoryStream);
+ writer.WriteLine("");
+ writer.Flush();
+ FileStream fileStream = null;
+ try
+ {
+ fileStream = File.OpenRead(path);
+ fileStream.CopyTo(memoryStream);
+ }
+ finally
+ {
+ if (fileStream != null)
+ {
+ fileStream.Close();
+ }
+ else
+ {
+ Logger.Error("filestream was found to be null when handling path: " + path);
+ }
+ }
+
+ memoryStream.Position = 0;
+ }
+ catch
+ {
+ if (memoryStream != null)
+ {
+ memoryStream.Close();
+ }
+ throw;
+ }
+
+ return memoryStream;
+ }
+ }
+}
diff --git a/BoostTestAdapter/Boost/Results/BoostXmlLog.cs b/BoostTestAdapter/Boost/Results/BoostXmlLog.cs
new file mode 100644
index 0000000..95277a6
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/BoostXmlLog.cs
@@ -0,0 +1,232 @@
+using System.Globalization;
+using System.IO;
+using System.Xml;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Boost Xml Log
+ ///
+ public class BoostXmlLog : BoostTestResultXMLOutput
+ {
+ #region Constants
+
+ ///
+ /// Xml constants
+ ///
+ private static class Xml
+ {
+ public const string TestLog = "TestLog";
+ public const string TestSuite = "TestSuite";
+ public const string TestCase = "TestCase";
+ public const string Name = "name";
+ public const string TestingTime = "TestingTime";
+ public const string Info = "Info";
+ public const string Message = "Message";
+ public const string Warning = "Warning";
+ public const string Error = "Error";
+ public const string FatalError = "FatalError";
+ public const string Exception = "Exception";
+ public const string LastCheckpoint = "LastCheckpoint";
+ public const string File = "file";
+ public const string Line = "line";
+ }
+
+ #endregion Constants
+
+ #region Constructors
+
+ ///
+ /// Constructor accepting a path to the external file
+ ///
+ /// The path to an external file. File will be opened on construction.
+ public BoostXmlLog(string path)
+ : base(path)
+ {
+ }
+
+ ///
+ /// Constructor accepting a stream to the file contents
+ ///
+ /// The file content stream.
+ public BoostXmlLog(Stream stream)
+ : base(stream)
+ {
+ }
+
+ #endregion Constructors
+
+ #region IBoostOutputParser
+
+ public override void Parse(TestResultCollection collection)
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.Load(this.InputStream);
+
+ if (doc.DocumentElement.Name == Xml.TestLog)
+ {
+ ParseTestUnitsLog(doc.DocumentElement.ChildNodes, new QualifiedNameBuilder(), collection);
+ }
+ }
+
+ #endregion IBoostOutputParser
+
+ ///
+ /// Parses child TestUnit nodes.
+ ///
+ /// The collection of Xml nodes which are valid TestUnit nodes.
+ /// The QualifiedNameBuilder which hosts the current fully qualified path.
+ /// The TestResultCollection which will host the result.
+ private static void ParseTestUnitsLog(XmlNodeList nodes, QualifiedNameBuilder path, TestResultCollection collection)
+ {
+ foreach (XmlNode child in nodes)
+ {
+ if (child.NodeType == XmlNodeType.Element)
+ {
+ if (child.Name == Xml.TestSuite)
+ {
+ ParseTestSuiteLog(child, path, collection);
+ }
+ else if (child.Name == Xml.TestCase)
+ {
+ ParseTestCaseLog(child, path, collection);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Parses a TestSuite log node.
+ ///
+ /// The TestSuite Xml node to parse.
+ /// The QualifiedNameBuilder which hosts the current fully qualified path.
+ /// The TestResultCollection which will host the result.
+ private static void ParseTestSuiteLog(XmlNode node, QualifiedNameBuilder path, TestResultCollection collection)
+ {
+ path.Push(node.Attributes[Xml.Name].Value);
+
+ ParseTestUnitsLog(node.ChildNodes, path, collection);
+
+ path.Pop();
+ }
+
+ ///
+ /// Parses a TestCase log node.
+ ///
+ /// The TestCase Xml node to parse.
+ /// The QualifiedNameBuilder which hosts the current fully qualified path.
+ /// The TestResultCollection which will host the result.
+ private static void ParseTestCaseLog(XmlNode node, QualifiedNameBuilder path, TestResultCollection collection)
+ {
+ // Temporarily push TestCase on TestSuite name builder to acquire the fully qualified name of the TestCase
+ path.Push(node.Attributes[Xml.Name].Value);
+
+ // Acquire result record of this TestCase
+ TestResult result = collection[path.ToString()];
+ if (result == null)
+ {
+ result = new TestResult(collection);
+ collection[path.ToString()] = result;
+ }
+
+ // Reset path to original value
+ path.Pop();
+
+ XmlNode testingTime = node.SelectSingleNode(Xml.TestingTime);
+
+ if (testingTime != null)
+ {
+ // Duration is in seconds i.e. remember to * 10 for milliseconds.
+ result.Duration = uint.Parse(testingTime.InnerText, CultureInfo.InvariantCulture);
+ }
+
+ ParseTestCaseLogEntries(node.ChildNodes, result);
+ }
+
+ ///
+ /// Parses Log Entries from the collection of log nodes.
+ ///
+ /// The collection of Xml nodes which are valid LogEntry nodes.
+ /// The TestResult which will host the parsed LogEntries.
+ private static void ParseTestCaseLogEntries(XmlNodeList nodes, TestResult result)
+ {
+ foreach (XmlNode child in nodes)
+ {
+ if (child.NodeType == XmlNodeType.Element)
+ {
+ LogEntry entry = null;
+
+ switch (child.Name)
+ {
+ case Xml.Info: entry = new LogEntryInfo(child.InnerText); break;
+ case Xml.Message: entry = new LogEntryMessage(child.InnerText); break;
+ case Xml.Warning: entry = new LogEntryWarning(child.InnerText); break;
+ case Xml.Error: entry = new LogEntryError(child.InnerText); break;
+ case Xml.FatalError: entry = new LogEntryFatalError(child.InnerText); break;
+ case Xml.Exception: entry = ParseTestCaseLogException(child); break;
+ }
+
+ if (entry != null)
+ {
+ entry.Source = ParseSourceInfo(child);
+ result.LogEntries.Add(entry);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Parse SourceFileInfo from the provided node.
+ ///
+ /// The Xml node which contains source file information.
+ /// A SourceFileInfo populated from the provided Xml node.
+ private static SourceFileInfo ParseSourceInfo(XmlNode node)
+ {
+ SourceFileInfo info = null;
+
+ XmlAttribute file = node.Attributes[Xml.File];
+ if (file != null)
+ {
+ info = new SourceFileInfo(file.Value);
+ }
+
+ if (info != null)
+ {
+ XmlAttribute line = node.Attributes[Xml.Line];
+ if (line != null)
+ {
+ info.LineNumber = int.Parse(line.Value, CultureInfo.InvariantCulture);
+ }
+ }
+
+ return info;
+ }
+
+ ///
+ /// Parse a LogException from the provided node.
+ ///
+ /// The Xml node which contains exception information.
+ /// A LogEntryException populated from the provided Xml node.
+ private static LogEntryException ParseTestCaseLogException(XmlNode node)
+ {
+ LogEntryException exception = new LogEntryException();
+
+ foreach (XmlNode child in node.ChildNodes)
+ {
+ if (child.NodeType == XmlNodeType.CDATA)
+ {
+ exception.Detail = child.InnerText;
+ }
+ else if ((child.NodeType == XmlNodeType.Element) && (child.Name == Xml.LastCheckpoint))
+ {
+ exception.LastCheckpoint = ParseSourceInfo(child);
+ exception.CheckpointDetail = child.InnerText;
+ }
+ }
+
+ return exception;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/BoostXmlReport.cs b/BoostTestAdapter/Boost/Results/BoostXmlReport.cs
new file mode 100644
index 0000000..0d5e6ce
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/BoostXmlReport.cs
@@ -0,0 +1,153 @@
+using System.Globalization;
+using System.IO;
+using System.Xml.XPath;
+using BoostTestAdapter.Boost.Test;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Boost Xml Report
+ ///
+ public class BoostXmlReport : BoostTestResultXMLOutput
+ {
+ #region Constants
+
+ ///
+ /// Xml constants
+ ///
+ private static class Xml
+ {
+ public const string TestResult = "TestResult";
+ public const string TestSuite = "TestSuite";
+ public const string TestCase = "TestCase";
+ public const string Name = "name";
+ public const string Result = "result";
+ public const string AssertionsPassed = "assertions_passed";
+ public const string AssertionsFailed = "assertions_failed";
+ public const string ExpectedFailures = "expected_failures";
+ }
+
+ #endregion Constants
+
+ #region Constructors
+
+ ///
+ /// Constructor accepting a path to the external file
+ ///
+ /// The path to an external file. File will be opened on construction.
+ public BoostXmlReport(string path)
+ : base(path)
+ {
+ }
+
+ ///
+ /// Constructor accepting a stream to the file contents
+ ///
+ /// The file content stream.
+ public BoostXmlReport(Stream stream)
+ : base(stream)
+ {
+ }
+
+ #endregion Constructors
+
+ #region IBoostOutputParser
+
+ public override void Parse(TestResultCollection collection)
+ {
+ XPathDocument doc = new XPathDocument(this.InputStream);
+ XPathNavigator nav = doc.CreateNavigator();
+
+ // Move to document root node
+ if ((nav.MoveToFirstChild()) && (nav.LocalName == Xml.TestResult))
+ {
+ ParseTestUnitsReport(nav, null, collection);
+ }
+ }
+
+ #endregion IBoostOutputParser
+
+ ///
+ /// Parses child TestUnit nodes.
+ ///
+ /// The parent XPathNavigator which hosts TestUnit nodes.
+ /// The parent TestSuite to which TestUnits are attached to.
+ /// The TestResultCollection which will host the result.
+ private static void ParseTestUnitsReport(XPathNavigator nav, TestSuite parent, TestResultCollection collection)
+ {
+ foreach (XPathNavigator child in nav.SelectChildren(Xml.TestSuite, string.Empty))
+ {
+ ParseTestSuiteReport(child, parent, collection);
+ }
+
+ foreach (XPathNavigator child in nav.SelectChildren(Xml.TestCase, string.Empty))
+ {
+ ParseTestCaseReport(child, parent, collection);
+ }
+ }
+
+ ///
+ /// Parses a TestSuite node.
+ ///
+ /// The XPathNavigator pointing to a TestSuite node.
+ /// The parent TestSuite to which TestUnits are attached to.
+ /// The TestResultCollection which will host the result.
+ private static void ParseTestSuiteReport(XPathNavigator node, TestSuite parent, TestResultCollection collection)
+ {
+ TestSuite testSuite = new TestSuite(node.GetAttribute(Xml.Name, string.Empty), parent);
+ collection[testSuite] = ParseTestResult(node, testSuite, collection);
+
+ ParseTestUnitsReport(node, testSuite, collection);
+ }
+
+ ///
+ /// Parses a TestCase node.
+ ///
+ /// The XPathNavigator pointing to a TestCase node.
+ /// The parent TestSuite to which TestUnits are attached to.
+ /// The TestResultCollection which will host the result.
+ private static void ParseTestCaseReport(XPathNavigator node, TestSuite parent, TestResultCollection collection)
+ {
+ TestCase testCase = new TestCase(node.GetAttribute(Xml.Name, string.Empty), parent);
+ collection[testCase] = ParseTestResult(node, testCase, collection);
+ }
+
+ ///
+ /// Parses a general test result information from the provided node.
+ ///
+ /// The XPathNavigator pointing to a TestUnit node.
+ /// The test unit for which the test results are related to.
+ /// The TestResultCollection which will host the result.
+ private static TestResult ParseTestResult(XPathNavigator node, TestUnit unit, TestResultCollection collection)
+ {
+ TestResult result = new TestResult(collection);
+
+ result.Unit = unit;
+ result.Result = ParseResultType(node.GetAttribute(Xml.Result, string.Empty));
+
+ result.AssertionsPassed = uint.Parse(node.GetAttribute(Xml.AssertionsPassed, string.Empty), CultureInfo.InvariantCulture);
+ result.AssertionsFailed = uint.Parse(node.GetAttribute(Xml.AssertionsFailed, string.Empty), CultureInfo.InvariantCulture);
+ result.ExpectedFailures = uint.Parse(node.GetAttribute(Xml.ExpectedFailures, string.Empty), CultureInfo.InvariantCulture);
+
+ return result;
+ }
+
+ ///
+ /// Parses a Result enumeration from string.
+ ///
+ /// The value to parse.
+ /// The parsed Result enumeration value.
+ private static TestResultType ParseResultType(string value)
+ {
+ switch (value.ToUpperInvariant())
+ {
+ case "PASSED": return TestResultType.Passed;
+ case "FAILED": return TestResultType.Failed;
+ case "ABORTED": return TestResultType.Aborted;
+ case "SKIPPED": return TestResultType.Skipped;
+ }
+
+ return TestResultType.Skipped;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/IBoostTestResultOutput.cs b/BoostTestAdapter/Boost/Results/IBoostTestResultOutput.cs
new file mode 100644
index 0000000..a819bec
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/IBoostTestResultOutput.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Interface for Boost Test result output.
+ ///
+ public interface IBoostTestResultOutput : IDisposable
+ {
+ ///
+ /// Parses the referenced output and updates the referred to
+ /// TestResultCollection with the newly collected information.
+ ///
+ ///
+ /// Implementations should check whether the collection already has an entry
+ /// defined for a particular TestUnit and update the entry or rewrite as necessary.
+ ///
+ /// The TestResultCollection which will host the parsed details.
+ void Parse(TestResultCollection collection);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntry.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntry.cs
new file mode 100644
index 0000000..6b8e787
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntry.cs
@@ -0,0 +1,50 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// Base class for Log entries
+ ///
+ public abstract class LogEntry
+ {
+ #region Constructors
+
+ ///
+ /// Default constructor.
+ ///
+ protected LogEntry()
+ {
+ }
+
+ ///
+ /// Constructor accepting a SourceFileInfo
+ ///
+ /// Source file information related to this log message. May be null.
+ protected LogEntry(SourceFileInfo source) :
+ this()
+ {
+ this.Source = source;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Source file information related to this log message. May be null.
+ ///
+ public SourceFileInfo Source { get; set; }
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Base";
+ }
+
+ ///
+ /// Log detail message.
+ ///
+ public string Detail { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryError.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryError.cs
new file mode 100644
index 0000000..0333a94
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryError.cs
@@ -0,0 +1,43 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// 'Error' log entry
+ ///
+ public class LogEntryError : LogEntry
+ {
+ #region Constructors
+
+ ///
+ /// Constructor accepting a detail message
+ ///
+ /// detail message of type string
+ public LogEntryError(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ ///
+ /// Constructor accepting a detail message and a SourceFileInfo object
+ ///
+ /// detail message of type string
+ /// Source file information related to this log message. May be null.
+ public LogEntryError(string detail, SourceFileInfo source)
+ : base(source)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Error";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryException.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryException.cs
new file mode 100644
index 0000000..388a1e6
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryException.cs
@@ -0,0 +1,58 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// A LogEntry specification detailing an exception message.
+ ///
+ public class LogEntryException : LogEntry
+ {
+ #region Constructors
+
+ public LogEntryException() :
+ this(null)
+ {
+ }
+
+ ///
+ /// Constructor accepting a detail message of type string
+ ///
+ /// Exception detail message
+ public LogEntryException(string detail) :
+ this(detail, null)
+ {
+ }
+
+ ///
+ /// Constructor accepting an exception detail message and a SourceFileInfo object
+ ///
+ /// detail message of type string
+ /// Source file information related to this log message. May be null.
+ public LogEntryException(string detail, SourceFileInfo source)
+ : base(source)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Last Checkpoint source information.
+ ///
+ public SourceFileInfo LastCheckpoint { get; set; }
+
+ ///
+ /// Checkpoint detail message.
+ ///
+ public string CheckpointDetail { get; set; }
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Exception";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryFatalError.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryFatalError.cs
new file mode 100644
index 0000000..2c213dc
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryFatalError.cs
@@ -0,0 +1,43 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// A 'Fatal Error' log entry.
+ ///
+ public class LogEntryFatalError : LogEntry
+ {
+ #region Constructors
+
+ ///
+ /// Constructor accepting a detail message of type string
+ ///
+ /// Exception detail message
+ public LogEntryFatalError(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ ///
+ /// Constructor accepting a detail message and a SourceFileInfo object
+ ///
+ /// detail message of type string
+ /// Source file information related to this log message. May be null.
+ public LogEntryFatalError(string detail, SourceFileInfo source)
+ : base(source)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Fatal Error";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryInfo.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryInfo.cs
new file mode 100644
index 0000000..abd244a
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryInfo.cs
@@ -0,0 +1,43 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// An 'Info' log entry
+ ///
+ public class LogEntryInfo : LogEntry
+ {
+ #region Constructors
+
+ ///
+ /// Constructor accepting a detail message of type string
+ ///
+ /// Exception detail message
+ public LogEntryInfo(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ ///
+ /// Constructor accepting a detail message and a SourceFileInfo object
+ ///
+ /// detail message of type string
+ /// Source file information related to this log message. May be null.
+ public LogEntryInfo(string detail, SourceFileInfo source)
+ : base(source)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Info";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryMemoryLeak.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryMemoryLeak.cs
new file mode 100644
index 0000000..a089d26
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryMemoryLeak.cs
@@ -0,0 +1,93 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// Log entry for an identified memory leak.
+ ///
+ public class LogEntryMemoryLeak : LogEntry
+ {
+ private const string MemoryLeakNotification = "Memory leaks have been been detected. Please refer to the output tab for more details.";
+
+ #region Constructors
+
+ ///
+ /// Default constructor.
+ ///
+ public LogEntryMemoryLeak()
+ {
+ this.Detail = MemoryLeakNotification;
+ }
+
+ ///
+ /// Constructor accepting a SourceFileInfo object
+ ///
+ /// Source file information related to this log message. May be null.
+ public LogEntryMemoryLeak(SourceFileInfo source)
+ : base(source)
+ {
+ }
+
+ public LogEntryMemoryLeak(string leakSourceFilePath, string leakSourceFileName, uint? leakLineNumber, uint? leakSizeInBytes, uint? leakMemoryAllocationNumber, string leakLeakedDataContents)
+ {
+ this.LeakSourceFilePath = leakSourceFilePath;
+ this.LeakSourceFileName = leakSourceFileName;
+ this.LeakLineNumber = leakLineNumber;
+ this.LeakSizeInBytes = leakSizeInBytes;
+ this.LeakMemoryAllocationNumber = leakMemoryAllocationNumber;
+ this.LeakLeakedDataContents = leakLeakedDataContents;
+ this.Detail = MemoryLeakNotification;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Memory leak";
+ }
+
+ ///
+ /// Indicates the source file path where the leak was detected at
+ ///
+ /// If null, the information regarding the leak source file path is not available. This generally is because the macro to replace the C++ operator new has not been utilized in the test project.
+ public string LeakSourceFilePath { get; set; }
+
+ ///
+ /// Indicates the source filename where the leak was detected at
+ ///
+ /// If null, the information regarding the leak source file name is not available. This generally is because the macro to replace the C++ operator new has not been utilized in the test project.
+ public string LeakSourceFileName { get; set; }
+
+ ///
+ /// Line number (respective the source file specified in property LeakSourceFileName) where the leak is detected at
+ ///
+ /// If null, the information regarding the leak line number is not available. This generally is because the macro to replace the C++ operator new has not been utilized in the test project, or the parsing of the line number failed.
+ public uint? LeakLineNumber { get; set; }
+
+ ///
+ /// Number of bytes leaked.
+ ///
+ /// The Boost UTF always reports the leak size in bytes. If null, then it means that the parsing of the respective console output failed.
+ public uint? LeakSizeInBytes { get; set; }
+
+ ///
+ /// Property containing the memory allocation number of the leak/
+ ///
+ /// The Boost UTF always reports the Memory allocation number whenever a memory leak is reported. If null, then it means that the parsing of the respective console output failed.
+ public uint? LeakMemoryAllocationNumber { get; set; }
+
+ ///
+ /// Property containing the leaked data contents as reported by Boost UTF
+ ///
+ public string LeakLeakedDataContents { get; set; }
+
+ ///
+ /// Property serves as an indicator wheather the source file and the line number information for the memory leak were available or not
+ ///
+ public bool LeakSourceFileAndLineNumberReportingActive { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryMessage.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryMessage.cs
new file mode 100644
index 0000000..5ffab19
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryMessage.cs
@@ -0,0 +1,43 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// A 'Message' log entry
+ ///
+ public class LogEntryMessage : LogEntry
+ {
+ #region Constructors
+
+ ///
+ /// Constructor accepting a detail message
+ ///
+ /// detail message of type string
+ public LogEntryMessage(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ ///
+ /// Constructor accepting a detail message and a SourceFileInfo object
+ ///
+ /// detail message of type string
+ /// Source file information related to this log message. May be null.
+ public LogEntryMessage(string detail, SourceFileInfo source)
+ : base(source)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Message";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryStandardErrorMessage.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryStandardErrorMessage.cs
new file mode 100644
index 0000000..6e10566
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryStandardErrorMessage.cs
@@ -0,0 +1,26 @@
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// Log entry for a message emitted to standard error.
+ ///
+ public class LogEntryStandardErrorMessage : LogEntry
+ {
+ #region Constructors
+
+ public LogEntryStandardErrorMessage(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ #region object overrides
+
+ public override string ToString()
+ {
+ return "Standard Error";
+ }
+
+ #endregion object overrides
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryStandardOutputMessage.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryStandardOutputMessage.cs
new file mode 100644
index 0000000..daea3ee
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryStandardOutputMessage.cs
@@ -0,0 +1,26 @@
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// Log entry for a message emitted to standard output.
+ ///
+ public class LogEntryStandardOutputMessage : LogEntry
+ {
+ #region Constructors
+
+ public LogEntryStandardOutputMessage(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ #region object overrides
+
+ public override string ToString()
+ {
+ return "Standard Output";
+ }
+
+ #endregion object overrides
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryWarning.cs b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryWarning.cs
new file mode 100644
index 0000000..1dfacff
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/LogEntryTypes/LogEntryWarning.cs
@@ -0,0 +1,43 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Results.LogEntryTypes
+{
+ ///
+ /// A 'Warning' log entry
+ ///
+ public class LogEntryWarning : LogEntry
+ {
+ #region Constructors
+
+ ///
+ /// Constructor accepting a detail message
+ ///
+ /// detail message of type string
+ public LogEntryWarning(string detail)
+ {
+ this.Detail = detail;
+ }
+
+ ///
+ /// Constructor accepting a detail message and a SourceFileInfo object
+ ///
+ /// detail message of type string
+ /// Source file information related to this log message. May be null.
+ public LogEntryWarning(string detail, SourceFileInfo source)
+ : base(source)
+ {
+ this.Detail = detail;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// returns a string with the description of the class
+ ///
+ /// string having the description of the class
+ public override string ToString()
+ {
+ return "Warning";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/TestResult.cs b/BoostTestAdapter/Boost/Results/TestResult.cs
new file mode 100644
index 0000000..05be8e6
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/TestResult.cs
@@ -0,0 +1,226 @@
+using System.Collections.Generic;
+using System.Linq;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+using BoostTestAdapter.Boost.Test;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Test result enumeration.
+ ///
+ public enum TestResultType
+ {
+ Passed,
+ Skipped,
+ Aborted,
+ Failed
+ }
+
+ ///
+ /// Aggregates Boost Test result information.
+ ///
+ public class TestResult
+ {
+ ///
+ /// Constructor.
+ ///
+ public TestResult() :
+ this(null)
+ {
+ }
+
+ ///
+ /// Constructor.
+ ///
+ /// The parent collection which hosts this TestResult.
+ public TestResult(TestResultCollection collection)
+ {
+ this.Collection = collection;
+ this.LogEntries = new List();
+ }
+
+ ///
+ /// Parent collection which hosts this result.
+ ///
+ public TestResultCollection Collection { get; private set; }
+
+ ///
+ /// Test Unit related to this test result.
+ ///
+ public TestUnit Unit { get; set; }
+
+ ///
+ /// Result type.
+ ///
+ public TestResultType Result { get; set; }
+
+ ///
+ /// Number of assertions passed.
+ ///
+ public uint AssertionsPassed { get; set; }
+
+ ///
+ /// Number of assertions failed.
+ ///
+ public uint AssertionsFailed { get; set; }
+
+ ///
+ /// Number of expected failures.
+ ///
+ public uint ExpectedFailures { get; set; }
+
+ ///
+ /// Number of contained test cases which passed.
+ ///
+ public uint TestCasesPassed
+ {
+ get
+ {
+ return GetCount(TestResultType.Passed);
+ }
+ }
+
+ ///
+ /// Number of contained test cases which failed.
+ ///
+ public uint TestCasesFailed
+ {
+ get
+ {
+ return GetCount(new TestResultType[] { TestResultType.Failed, TestResultType.Aborted });
+ }
+ }
+
+ ///
+ /// Number of contained test cases which were skipped.
+ ///
+ public uint TestCasesSkipped
+ {
+ get
+ {
+ return GetCount(TestResultType.Skipped);
+ }
+ }
+
+ ///
+ /// Number of contained test cases which were aborted.
+ ///
+ public uint TestCasesAborted
+ {
+ get
+ {
+ return GetCount(TestResultType.Aborted);
+ }
+ }
+
+ ///
+ /// Duration of test in microseconds
+ ///
+ public uint Duration { get; set; }
+
+ ///
+ /// Collection of related log entries.
+ ///
+ public ICollection LogEntries { get; private set; }
+
+ #region Utility
+
+ ///
+ /// Returns the number of contained test cases which are of the specified Result type.
+ ///
+ /// The result type to lookup
+ /// The number of contained test cases which are of the specified Result type.
+ private uint GetCount(TestResultType type)
+ {
+ return GetCount(new TestResultType[] { type });
+ }
+
+ ///
+ /// Returns the number of contained test cases which are of the specified Result type.
+ ///
+ /// The result types to lookup
+ /// The number of contained test cases which are of the specified Result type.
+ private uint GetCount(IEnumerable types)
+ {
+ if (this.Collection == null)
+ {
+ if (this.Unit is TestCase)
+ {
+ return (types.Contains(this.Result)) ? 1u : 0u;
+ }
+ }
+ else
+ {
+ TestCaseResultVisitor visitor = new TestCaseResultVisitor(this.Collection, types);
+ this.Unit.Apply(visitor);
+ return visitor.Count;
+ }
+
+ return 0u;
+ }
+
+ ///
+ /// Boost Test Unit visitor implementation. Aggregates the number of
+ /// test cases which are of a specific Result type.
+ ///
+ private class TestCaseResultVisitor : ITestVisitor
+ {
+ #region Constructors
+
+ ///
+ /// Constructor.
+ ///
+ /// The TestResultCollection which hosts all results.
+ /// The types to lookup
+ public TestCaseResultVisitor(TestResultCollection collection, IEnumerable types)
+ {
+ this.Collection = collection;
+ this.ResultTypes = types;
+ this.Count = 0;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public TestResultCollection Collection { get; private set; }
+
+ public IEnumerable ResultTypes { get; private set; }
+
+ ///
+ /// The number of test cases encountered which are of the specified Result type.
+ ///
+ public uint Count { get; private set; }
+
+ #endregion Properties
+
+ #region ITestVisitor
+
+ public void Visit(TestCase testCase)
+ {
+ Utility.Code.Require(testCase, "testCase");
+
+ TestResult result = this.Collection[testCase];
+
+ if ((result != null) && (this.ResultTypes.Contains(result.Result)))
+ {
+ ++this.Count;
+ }
+ }
+
+ public void Visit(TestSuite testSuite)
+ {
+ Utility.Code.Require(testSuite, "testSuite");
+
+ foreach (TestUnit unit in testSuite.Children)
+ {
+ unit.Apply(this);
+ }
+ }
+
+ #endregion ITestVisitor
+ }
+
+ #endregion Utility
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Results/TestResultCollection.cs b/BoostTestAdapter/Boost/Results/TestResultCollection.cs
new file mode 100644
index 0000000..7d56533
--- /dev/null
+++ b/BoostTestAdapter/Boost/Results/TestResultCollection.cs
@@ -0,0 +1,226 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BoostTestAdapter.Boost.Runner;
+using BoostTestAdapter.Boost.Test;
+using BoostTestAdapter.Settings;
+
+namespace BoostTestAdapter.Boost.Results
+{
+ ///
+ /// Collection of Boost Test Results.
+ ///
+ public class TestResultCollection : IEnumerable
+ {
+ #region Constructors
+
+ ///
+ /// Default Constructor.
+ ///
+ public TestResultCollection()
+ {
+ this.Results = new Dictionary();
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ private Dictionary Results { get; set; }
+
+ #endregion Properties
+
+ #region Indexers
+
+ ///
+ /// Returns the result for the requested TestUnit or null if not found.
+ ///
+ /// TestUnit to lookup.
+ /// The results associated with the requested TestUnit or null if not found.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")]
+ public TestResult this[TestUnit unit]
+ {
+ get
+ {
+ Utility.Code.Require(unit, "unit");
+ return this[unit.FullyQualifiedName];
+ }
+
+ set
+ {
+ Utility.Code.Require(unit, "unit");
+ this[unit.FullyQualifiedName] = value;
+ }
+ }
+
+ ///
+ /// Returns the result for the requested TestUnit fully qualified name or null if not found.
+ ///
+ /// The TestUnit's fully qualified name to lookup.
+ /// The results associated with the requested TestUnit fully qualified name or null if not found.
+ public TestResult this[string fullyQualifiedName]
+ {
+ get
+ {
+ TestResult value = null;
+ if (this.Results.TryGetValue(fullyQualifiedName, out value))
+ {
+ return value;
+ }
+
+ return null;
+ }
+
+ set
+ {
+ this.Results[fullyQualifiedName] = value;
+ }
+ }
+
+ #endregion Indexers
+
+ ///
+ /// Parses the Xml report and log file as specified within the provided
+ /// BoostTestRunnerCommandLineArgs instance.
+ ///
+ /// The BoostTestRunnerCommandLineArgs which specify the report and log file.
+ /// The BoostTestAdapterSettings which specify adapter specific settings.
+ public void Parse(BoostTestRunnerCommandLineArgs args, BoostTestAdapterSettings settings)
+ {
+ IEnumerable parsers = Enumerable.Empty();
+
+ try
+ {
+ parsers = new IBoostTestResultOutput[] {
+ GetReportParser(args),
+ GetLogParser(args),
+ GetStandardOutput(args, settings),
+ GetStandardError(args)
+ };
+
+ Parse(parsers);
+ }
+ finally
+ {
+ foreach (IBoostTestResultOutput parser in parsers)
+ {
+ if (parser != null)
+ {
+ parser.Dispose();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Parses the collection of BoostTestResultOutput. Results are stored
+ /// within this instance.
+ ///
+ /// The Boost Test output which will be parsed.
+ public void Parse(IEnumerable output)
+ {
+ Utility.Code.Require(output, "output");
+
+ foreach (IBoostTestResultOutput parser in output)
+ {
+ if (parser != null)
+ {
+ parser.Parse(this);
+ }
+ }
+ }
+
+ #region IBoostTestResultOutput Factory Methods
+
+ ///
+ /// Factory method which provides the report IBoostTestResultOutput based on the provided BoostTestRunnerCommandLineArgs
+ ///
+ /// The command line args which were used to generate the test results
+ /// An IBoostTestResultOutput or null if one cannot be identified from the provided arguments
+ private static IBoostTestResultOutput GetReportParser(BoostTestRunnerCommandLineArgs args)
+ {
+ string report = args.ReportFile;
+ if (!string.IsNullOrEmpty(report))
+ {
+ if (args.ReportFormat == OutputFormat.XML)
+ {
+ return new BoostXmlReport(args.ReportFile);
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Factory method which provides the log IBoostTestResultOutput based on the provided BoostTestRunnerCommandLineArgs
+ ///
+ /// The command line args which were used to generate the test results
+ /// An IBoostTestResultOutput or null if one cannot be identified from the provided arguments
+ private static IBoostTestResultOutput GetLogParser(BoostTestRunnerCommandLineArgs args)
+ {
+ string log = args.LogFile;
+ if (!string.IsNullOrEmpty(log))
+ {
+ if (args.LogFormat == OutputFormat.XML)
+ {
+ return new BoostXmlLog(args.LogFile);
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Factory method which provides the standard output IBoostTestResultOutput based on the provided BoostTestRunnerCommandLineArgs and BoostTestAdapterSettings
+ ///
+ /// The command line args which were used to generate the test results
+ /// The run time settings which were used to generate the test results
+ /// An IBoostTestResultOutput or null if one cannot be identified from the provided arguments
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
+ private static IBoostTestResultOutput GetStandardOutput(BoostTestRunnerCommandLineArgs args, BoostTestAdapterSettings settings)
+ {
+ if ((!string.IsNullOrEmpty(args.StandardOutFile)) && (File.Exists(args.StandardOutFile)))
+ {
+ return new BoostStandardOutput(args.StandardOutFile)
+ {
+ FailTestOnMemoryLeak = settings.FailTestOnMemoryLeak
+ };
+ }
+
+ return null;
+ }
+
+ ///
+ /// Factory method which provides the standard error IBoostTestResultOutput based on the provided BoostTestRunnerCommandLineArgs and BoostTestAdapterSettings
+ ///
+ /// The command line args which were used to generate the test results
+ /// An IBoostTestResultOutput or null if one cannot be identified from the provided arguments
+ private static IBoostTestResultOutput GetStandardError(BoostTestRunnerCommandLineArgs args)
+ {
+ if ((!string.IsNullOrEmpty(args.StandardErrorFile)) && (File.Exists(args.StandardErrorFile)))
+ {
+ return new BoostStandardError(args.StandardErrorFile);
+ }
+
+ return null;
+ }
+
+ #endregion IBoostTestResultOutput Factory Methods
+
+ #region IEnumerable
+
+ public IEnumerator GetEnumerator()
+ {
+ // Only enumerate over TestCase results since for our general use case, those are the most important.
+ return this.Results.Values.Where((result) => { return (result.Unit as TestCase) != null; }).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ #endregion IEnumerable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/BoostTestRunner.cs b/BoostTestAdapter/Boost/Runner/BoostTestRunner.cs
new file mode 100644
index 0000000..9a50799
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/BoostTestRunner.cs
@@ -0,0 +1,14 @@
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// IBoostTestRunner implementation. Executes stand-alone
+ /// (i.e. test runner included within '.exe') Boost Tests.
+ ///
+ public class BoostTestRunner : BoostTestRunnerBase
+ {
+ public BoostTestRunner(string exe) :
+ base(exe)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/BoostTestRunnerBase.cs b/BoostTestAdapter/Boost/Runner/BoostTestRunnerBase.cs
new file mode 100644
index 0000000..2115381
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/BoostTestRunnerBase.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Management;
+using BoostTestAdapter.Utility;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Abstract IBoostTestRunner specification which contains common functionality
+ /// for executing external '.exe' Boost Test runners.
+ ///
+ public abstract class BoostTestRunnerBase : IBoostTestRunner
+ {
+ #region Constructors
+
+ ///
+ /// Constructor.
+ ///
+ /// Path to the '.exe' file.
+ protected BoostTestRunnerBase(string testRunnerExecutable)
+ {
+ this.TestRunnerExecutable = testRunnerExecutable;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Boost Test runner '.exe' file path.
+ ///
+ protected string TestRunnerExecutable { get; private set; }
+
+ #endregion Properties
+
+ #region IBoostTestRunner
+
+ public virtual void Debug(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings, IFrameworkHandle framework)
+ {
+ Utility.Code.Require(settings, "settings");
+
+ using (Process process = Debug(framework, GetStartInfo(args, settings)))
+ {
+ MonitorProcess(process, settings.Timeout);
+ }
+ }
+
+ public virtual void Run(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ Utility.Code.Require(settings,"settings");
+
+ using (Process process = Run(GetStartInfo(args, settings)))
+ {
+ MonitorProcess(process, settings.Timeout);
+ }
+ }
+
+ public virtual string Source
+ {
+ get { return this.TestRunnerExecutable; }
+ }
+
+ #endregion IBoostTestRunner
+
+ ///
+ /// Monitors the provided process for the specified timeout.
+ ///
+ /// The process to monitor.
+ /// The timeout threshold until the process and its children should be killed.
+ /// Thrown in case specified timeout threshold is exceeded.
+ private static void MonitorProcess(Process process, int timeout)
+ {
+ process.WaitForExit(timeout);
+
+ if (!process.HasExited)
+ {
+ KillProcessIncludingChildren(process);
+
+ throw new TimeoutException(timeout);
+ }
+ }
+
+ ///
+ /// Starts a Process in a debug session using the provided command line arguments string.
+ ///
+ /// The IFrameworkHandle which provides debug session handling.
+ /// The process start info which will be used to launch the external test program.
+ /// The newly spawned debug process.
+ private static Process Debug(IFrameworkHandle framework, ProcessStartInfo info)
+ {
+ if (framework == null)
+ {
+ return Run(info);
+ }
+
+ Dictionary environment =
+ info.EnvironmentVariables.Cast().ToDictionary(
+ item => item.Key.ToString(),
+ item => item.Value.ToString()
+ );
+
+ int pid = framework.LaunchProcessWithDebuggerAttached(
+ info.FileName,
+ info.WorkingDirectory,
+ info.Arguments,
+ environment
+ );
+
+ // Get a process on the local computer, using the process id.
+ return Process.GetProcessById(pid);
+ }
+
+ ///
+ /// Starts a Process using the provided command line arguments string.
+ ///
+ /// The process start info which will be used to launch the external test program.
+ /// The newly spawned debug process.
+ private static Process Run(ProcessStartInfo info)
+ {
+ // Wrap the process inside cmd.exe in so that we can redirect stdout,
+ // stderr to file using a similar mechanism as available for Debug runs.
+ return Process.Start(ProcessStartInfoEx.StartThroughCmdShell(info.Clone()));
+ }
+
+ ///
+ /// Builds a ProcessStartInfo instance using the provided command line string.
+ ///
+ /// The command line arguments.
+ /// The Boost Test runner settings currently being applied.
+ /// A ProcessStartInfo instance.
+ protected virtual ProcessStartInfo GetStartInfo(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ Utility.Code.Require(args, "args");
+
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = false,
+ UseShellExecute = false,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ WorkingDirectory = args.WorkingDirectory,
+ FileName = this.TestRunnerExecutable,
+ Arguments = args.ToString(),
+ RedirectStandardError = false,
+ RedirectStandardInput = false
+ };
+
+ return startInfo;
+ }
+
+ ///
+ /// Kills a process identified by its pid and all its children processes
+ ///
+ /// process object
+ ///
+ private static void KillProcessIncludingChildren(Process process)
+ {
+ Logger.Info("Finding processes spawned by process with Id [{0}]", process.Id);
+
+ // Once the children pids are available we start killing the processes.
+ // Enumerate each and every child immediately via the .toList() method.
+ List children = EnumerateChildren(process).ToList();
+
+ // Killing the main process
+ if (KillProcess(process))
+ {
+ Logger.Error("Successfully killed process {0}.", process.Id);
+ }
+ else
+ {
+ Logger.Error("Unable to kill process {0}. Process may still be running.", process.Id);
+ }
+
+ foreach (Process child in children)
+ {
+ // Recurse
+ KillProcessIncludingChildren(child);
+ }
+ }
+
+ ///
+ /// Enumerates all live children of the provided parent Process instance.
+ ///
+ /// The parent process whose live children are to be enumerated
+ /// An enumeration of live/active child processes
+ private static IEnumerable EnumerateChildren(Process process)
+ {
+ ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT ProcessId FROM Win32_Process WHERE ParentProcessId = " + process.Id.ToString());
+ ManagementObjectCollection collection = searcher.Get();
+
+ foreach (ManagementBaseObject item in collection)
+ {
+ int childPid = Convert.ToInt32(item["ProcessId"]);
+
+ Process child = null;
+
+ try
+ {
+ child = Process.GetProcessById(childPid);
+ }
+ catch (ArgumentException /* ex */)
+ {
+ Logger.Error("Child process [{0}] does not exist.", childPid);
+ // Reset child to null so that it is not enumerated
+ child = null;
+ }
+ catch (Exception /* ex */)
+ {
+ child = null;
+ }
+
+ if (child != null)
+ {
+ yield return child;
+ }
+ }
+ }
+
+ ///
+ /// Kill a process immediately
+ ///
+ /// process object
+ /// return true if success or false if it was not successfull
+ private static bool KillProcess(Process process)
+ {
+ return KillProcess(process, 0);
+ }
+
+ ///
+ /// Kill a process
+ ///
+ /// process object
+ /// the timeout in milliseconds to note correct process termination
+ /// return true if success or false if it was not successfull
+ private static bool KillProcess(Process process, int killTimeout)
+ {
+ if (process.HasExited)
+ {
+ return true;
+ }
+
+ try
+ {
+ // If the call to the Kill method is made while the process is already terminating, a Win32Exception is thrown for Access Denied.
+
+ process.Kill();
+ return process.WaitForExit(killTimeout);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e.Message);
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/BoostTestRunnerCommandLineArgs.cs b/BoostTestAdapter/Boost/Runner/BoostTestRunnerCommandLineArgs.cs
new file mode 100644
index 0000000..3af5da3
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/BoostTestRunnerCommandLineArgs.cs
@@ -0,0 +1,444 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Output format options for Boost Test output.
+ /// Reference: http://www.boost.org/doc/libs/1_43_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html
+ ///
+ public enum OutputFormat
+ {
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "HRF")]
+ HRF, // Human Readable Format
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "XML")]
+ XML,
+
+ Default = HRF
+ }
+
+ ///
+ /// Log level enumeration
+ /// Reference: http://www.boost.org/doc/libs/1_43_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html
+ ///
+ public enum LogLevel
+ {
+ All,
+ Success,
+ TestSuite,
+ Message,
+ Warning,
+ Error,
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cpp")]
+ CppException,
+ SystemError,
+ FatalError,
+ Nothing,
+
+ Default = Error
+ }
+
+ ///
+ /// Report level enumeration
+ /// Reference: http://www.boost.org/doc/libs/1_43_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html
+ ///
+ public enum ReportLevel
+ {
+ None,
+ Confirm,
+ Short,
+ Detailed,
+
+ Default = Confirm
+ }
+
+ ///
+ /// Aggregates all possible command line options made available by the Boost Test framework.
+ /// Reference: http://www.boost.org/doc/libs/1_43_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html
+ ///
+ public class BoostTestRunnerCommandLineArgs
+ {
+ #region Constants
+
+ private const string RunTestArg = "--run_test";
+
+ private const string LogFormatArg = "--log_format";
+ private const string LogLevelArg = "--log_level";
+ private const string LogSinkArg = "--log_sink";
+
+ private const string ReportFormatArg = "--report_format";
+ private const string ReportLevelArg = "--report_level";
+ private const string ReportSinkArg = "--report_sink";
+
+ private const string DetectMemoryLeakArg = "--detect_memory_leak";
+
+ private const string TestSeparator = ",";
+
+ private const char ArgSeparator = ' ';
+ private const char ArgValueSeparator = '=';
+
+ private const char RedirectionOperator = '>';
+ private const string ErrRedirectionOperator = "2>";
+
+ #endregion Constants
+
+ #region Members
+
+ private string _reportFile = null;
+ private string _logFile = null;
+ private string _stdOutFile = null;
+ private string _stdErrFile = null;
+
+ #endregion Members
+
+ #region Constructors
+
+ ///
+ /// Default Constructor. Initializes options to their defaults as specified in:
+ /// http://www.boost.org/doc/libs/1_43_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html.
+ ///
+ public BoostTestRunnerCommandLineArgs()
+ {
+ this.Tests = new List();
+
+ this.LogFormat = OutputFormat.Default;
+ this.LogLevel = LogLevel.Default;
+
+ this.ReportFormat = OutputFormat.Default;
+ this.ReportLevel = ReportLevel.Default;
+
+ this.DetectMemoryLeaks = 1;
+ }
+
+ ///
+ /// Copy Constructor. Creates a shallow copy of the provided instance.
+ ///
+ /// The instance to be copied.
+ protected BoostTestRunnerCommandLineArgs(BoostTestRunnerCommandLineArgs args)
+ {
+ Utility.Code.Require(args, "args");
+
+ this.WorkingDirectory = args.WorkingDirectory;
+
+ // Shallow copy
+ this.Tests = args.Tests;
+
+ this.LogFormat = args.LogFormat;
+ this.LogLevel = args.LogLevel;
+ this.LogFile = args.LogFile;
+
+ this.ReportFormat = args.ReportFormat;
+ this.ReportLevel = args.ReportLevel;
+ this.ReportFile = args.ReportFile;
+
+ this.DetectMemoryLeaks = args.DetectMemoryLeaks;
+
+ this.StandardOutFile = args.StandardOutFile;
+ this.StandardErrorFile = args.StandardErrorFile;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Specify the process' working directory
+ ///
+ public string WorkingDirectory { get; set; }
+
+ ///
+ /// List of fully qualified name tests which are to be executed.
+ ///
+ public IList Tests { get; private set; }
+
+ ///
+ /// Output format for log information.
+ ///
+ public OutputFormat LogFormat { get; set; }
+
+ ///
+ /// Log level.
+ ///
+ public LogLevel LogLevel { get; set; }
+
+ ///
+ /// Path (relative to the WorkingDirectory) to the log file which will host Boost Test output.
+ ///
+ public string LogFile
+ {
+ get
+ {
+ return GetPath(this._logFile);
+ }
+
+ set
+ {
+ this._logFile = value;
+ }
+ }
+
+ ///
+ /// Output format for report information.
+ ///
+ public OutputFormat ReportFormat { get; set; }
+
+ ///
+ /// Report level.
+ ///
+ public ReportLevel ReportLevel { get; set; }
+
+ ///
+ /// Path (relative to the WorkingDirectory) to the report file which will host Boost Test report output.
+ ///
+ public string ReportFile
+ {
+ get
+ {
+ return GetPath(this._reportFile);
+ }
+
+ set
+ {
+ this._reportFile = value;
+ }
+ }
+
+ ///
+ /// Set to value greater than 0 to detect memory leaks.
+ /// Refer to: http://www.boost.org/doc/libs/1_43_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html.
+ ///
+ public uint DetectMemoryLeaks { get; set; }
+
+ ///
+ /// Path (relative to the WorkingDirectory) to the report file which will host the standard output content.
+ ///
+ public string StandardOutFile
+ {
+ get
+ {
+ return GetPath(this._stdOutFile);
+ }
+
+ set
+ {
+ this._stdOutFile = value;
+ }
+ }
+
+ ///
+ /// Path (relative to the WorkingDirectory) to the report file which will host the standard error content.
+ ///
+ public string StandardErrorFile
+ {
+ get
+ {
+ return GetPath(this._stdErrFile);
+ }
+
+ set
+ {
+ this._stdErrFile = value;
+ }
+ }
+
+ ///
+ /// Provides a string representation of the command line.
+ ///
+ /// The command line as a string.
+ public override string ToString()
+ {
+ StringBuilder args = new StringBuilder();
+
+ // --run_tests=a,b,c
+ if (this.Tests.Count > 0)
+ {
+ AddArgument(RunTestArg, string.Join(TestSeparator, this.Tests), args);
+ }
+
+ if (this.LogFormat != OutputFormat.Default)
+ {
+ // --log_format=xml
+ AddArgument(LogFormatArg, OutputFormatToString(this.LogFormat), args);
+ }
+
+ if (this.LogLevel != LogLevel.Default)
+ {
+ // --log_level=test_suite
+ AddArgument(LogLevelArg, LogLevelToString(this.LogLevel), args);
+ }
+
+ // --log_sink=log.xml
+ if (!string.IsNullOrEmpty(this._logFile))
+ {
+ AddArgument(LogSinkArg, this._logFile, args);
+ }
+
+ if (this.ReportFormat != OutputFormat.Default)
+ {
+ // --report_format=xml
+ AddArgument(ReportFormatArg, OutputFormatToString(this.ReportFormat), args);
+ }
+
+ if (this.ReportLevel != ReportLevel.Default)
+ {
+ // --report_level=detailed
+ AddArgument(ReportLevelArg, ReportLevelToString(this.ReportLevel), args);
+ }
+
+ // --report_sink=report.xml
+ if (!string.IsNullOrEmpty(this._reportFile))
+ {
+ AddArgument(ReportSinkArg, this._reportFile, args);
+ }
+
+ if (this.DetectMemoryLeaks != 1)
+ {
+ // --detect_memory_leak
+ AddArgument(DetectMemoryLeakArg, this.DetectMemoryLeaks.ToString(CultureInfo.InvariantCulture), args);
+ }
+
+ // > std.out
+ if (!string.IsNullOrEmpty(this._stdOutFile))
+ {
+ args.Append(RedirectionOperator).Append(ArgSeparator).Append(Quote(this._stdOutFile)).Append(ArgSeparator);
+ }
+
+ // 2> std.err
+ if (!string.IsNullOrEmpty(this._stdErrFile))
+ {
+ args.Append(ErrRedirectionOperator).Append(ArgSeparator).Append(Quote(this._stdErrFile));
+ }
+
+ return args.ToString().TrimEnd();
+ }
+
+ ///
+ /// Returns a rooted path for the provided one.
+ ///
+ /// The path to root.
+ /// The rooted path.
+ protected string GetPath(string path)
+ {
+ if (!string.IsNullOrEmpty(path) && !Path.IsPathRooted(path))
+ {
+ return Path.Combine(this.WorkingDirectory, path);
+ }
+
+ return path;
+ }
+
+ ///
+ /// Provides a (valid) string representation of the provided OutputFormat.
+ ///
+ /// The value to serialize to string.
+ /// A (valid) string representation of the provided OutputFormat.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
+ private static string OutputFormatToString(OutputFormat value)
+ {
+ return value.ToString().ToLowerInvariant();
+ }
+
+ ///
+ /// Provides a (valid) string representation of the provided LogLevel.
+ ///
+ /// The value to serialize to string.
+ /// A (valid) string representation of the provided LogLevel.
+ private static string LogLevelToString(LogLevel value)
+ {
+ return LevelToString(value.ToString());
+ }
+
+ ///
+ /// Provides a (valid) string representation of the provided ReportLevel.
+ ///
+ /// The value to serialize to string.
+ /// A (valid) string representation of the provided ReportLevel.
+ private static string ReportLevelToString(ReportLevel value)
+ {
+ return LevelToString(value.ToString());
+ }
+
+ ///
+ /// Provides a (valid) string representation of the provided LogLevel/ReportLevel.
+ /// Changes from pascal case to underscore separated lower case.
+ ///
+ /// The value to serialize to string.
+ /// A (valid) string representation of the provided LogLevel/ReportLevel.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
+ private static string LevelToString(string value)
+ {
+ return Regex.Replace(value, "([A-Z])", "_$1").Substring(1).ToLowerInvariant();
+ }
+
+ ///
+ /// Adds a no-value command-line argument to the provided StringBuilder.
+ ///
+ /// The command line option.
+ /// The StringBuilder which will host the result.
+ /// host
+ protected static StringBuilder AddArgument(string prefix, StringBuilder host)
+ {
+ return AddArgument(prefix, string.Empty, host);
+ }
+
+ ///
+ /// Adds a command-line argument to the provided StringBuilder.
+ ///
+ /// The command line option.
+ /// The command line option's value.
+ /// The StringBuilder which will host the result.
+ /// host
+ protected static StringBuilder AddArgument(string prefix, string value, StringBuilder host)
+ {
+ return AddArgument(prefix, ArgValueSeparator, value, host);
+ }
+
+ ///
+ /// Adds a command-line argument to the provided StringBuilder.
+ ///
+ /// The command line option.
+ /// The separator to use between the prefix and the value.
+ /// The command line option's value.
+ /// The StringBuilder which will host the result.
+ /// host
+ protected static StringBuilder AddArgument(string prefix, char separator, string value, StringBuilder host)
+ {
+ Utility.Code.Require(host, "host");
+
+ if (separator == ' ')
+ {
+ prefix = Quote(prefix);
+ value = Quote(value);
+ }
+ else
+ {
+ host.Append('"');
+ }
+
+ host.Append(prefix);
+
+ if (!string.IsNullOrEmpty(value))
+ {
+ host.Append(separator).Append(value);
+ }
+
+ if (separator != ' ')
+ {
+ host.Append('"');
+ }
+
+ return host.Append(ArgSeparator);
+ }
+
+ ///
+ /// Quotes the provided string within double quotation marks.
+ ///
+ /// The string to quote.
+ /// The quoted value.
+ private static string Quote(string value)
+ {
+ return (string.IsNullOrEmpty(value)) ? value : ('"' + value + '"');
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/BoostTestRunnerFactoryOptions.cs b/BoostTestAdapter/Boost/Runner/BoostTestRunnerFactoryOptions.cs
new file mode 100644
index 0000000..6448434
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/BoostTestRunnerFactoryOptions.cs
@@ -0,0 +1,12 @@
+using BoostTestAdapter.Settings;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Aggregates all options for BoostTestRunnerFactory
+ ///
+ public class BoostTestRunnerFactoryOptions
+ {
+ public ExternalBoostTestRunnerSettings ExternalTestRunnerSettings { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/BoostTestRunnerSettings.cs b/BoostTestAdapter/Boost/Runner/BoostTestRunnerSettings.cs
new file mode 100644
index 0000000..cb76830
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/BoostTestRunnerSettings.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Aggregates common settings for a Boost Test execution.
+ ///
+ public class BoostTestRunnerSettings : ICloneable
+ {
+ ///
+ /// Constructor. Initializes all settings to their default state.
+ ///
+ public BoostTestRunnerSettings()
+ {
+ this.Timeout = -1;
+ }
+
+ ///
+ /// Timeout for unit test execution.
+ ///
+ public int Timeout { get; set; }
+
+ #region IClonable
+
+ public BoostTestRunnerSettings Clone()
+ {
+ return new BoostTestRunnerSettings()
+ {
+ Timeout = this.Timeout
+ };
+ }
+
+ object ICloneable.Clone()
+ {
+ return this.Clone();
+ }
+
+ #endregion IClonable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/BoostTestRunnerTimeoutException.cs b/BoostTestAdapter/Boost/Runner/BoostTestRunnerTimeoutException.cs
new file mode 100644
index 0000000..1e4961c
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/BoostTestRunnerTimeoutException.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// An exception raised in case a timeout threshold is exceeded.
+ ///
+ [Serializable]
+ public class TimeoutException : Exception
+ {
+ #region Constructors
+
+ #region Standard Exception Constructors
+
+ public TimeoutException() :
+ this(-1)
+ {
+ }
+
+ public TimeoutException(string message) :
+ this(-1, message)
+ {
+ }
+
+ public TimeoutException(string message, Exception innerException) :
+ base(message, innerException)
+ {
+ this.Timeout = -1;
+ }
+
+ protected TimeoutException(SerializationInfo info, StreamingContext context) :
+ base(info, context)
+ {
+ this.Timeout = info.GetInt32("Timeout");
+ }
+
+ #endregion Standard Exception Constructors
+
+ ///
+ /// Constructor
+ ///
+ /// The timeout threshold which was exceeded.
+ public TimeoutException(int timeout) :
+ this(timeout, "The Boost Test Runner exceeded the timeout threshold of " + timeout)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The timeout threshold which was exceeded.
+ /// The message for this exception.
+ public TimeoutException(int timeout, string message) :
+ base(message)
+ {
+ this.Timeout = timeout;
+ }
+
+ #endregion Constructors
+
+ ///
+ /// The timeout threshold which was exceeded.
+ ///
+ public int Timeout { get; protected set; }
+
+ #region ISerializable
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("Timeout", this.Timeout);
+ }
+
+ #endregion ISerializable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/DefaultBoostTestRunnerFactory.cs b/BoostTestAdapter/Boost/Runner/DefaultBoostTestRunnerFactory.cs
new file mode 100644
index 0000000..d38d5c7
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/DefaultBoostTestRunnerFactory.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using BoostTestAdapter.Settings;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Default implementation for IBoostTestRunnerFactory.
+ ///
+ public class DefaultBoostTestRunnerFactory : IBoostTestRunnerFactory
+ {
+ #region IBoostTestRunnerFactory
+
+ ///
+ /// Based on the provided file name, returns a suitable IBoostTestRunner
+ /// instance or null if none are available.
+ ///
+ /// The identifier which is to be executed via the IBoostTestRunner.
+ /// test runner settings
+ /// A suitable IBoostTestRunner instance or null if none are available.
+ public IBoostTestRunner GetRunner(string identifier, BoostTestRunnerFactoryOptions options)
+ {
+ IBoostTestRunner runner = null;
+
+ if ((options != null) && (options.ExternalTestRunnerSettings != null))
+ {
+ // Provision an external test runner
+ runner = GetExternalTestRunner(identifier, options.ExternalTestRunnerSettings);
+ }
+
+ // Provision a default internal runner
+ if (runner == null)
+ {
+ runner = GetInternalTestRunner(identifier);
+ }
+
+ return runner;
+ }
+
+ private static IBoostTestRunner GetInternalTestRunner(string source)
+ {
+ switch (Path.GetExtension(source))
+ {
+ case ".exe": return new BoostTestRunner(source);
+ }
+
+ return null;
+ }
+
+ private static IBoostTestRunner GetExternalTestRunner(string source, ExternalBoostTestRunnerSettings settings)
+ {
+ Utility.Code.Require(settings, "settings");
+
+ if (settings.ExtensionType == Path.GetExtension(source))
+ {
+ return new ExternalBoostTestRunner(source, settings);
+ }
+
+ return null;
+ }
+
+ #endregion IBoostTestRunnerFactory
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/ExternalBoostTestRunner.cs b/BoostTestAdapter/Boost/Runner/ExternalBoostTestRunner.cs
new file mode 100644
index 0000000..296860c
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/ExternalBoostTestRunner.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Configurable External Boost Test Runner. Launches a process as specified
+ /// in the provided configuration to execute tests.
+ ///
+ public class ExternalBoostTestRunner : BoostTestRunnerBase
+ {
+ #region Constants
+
+ private const string SourcePlaceholder = "source";
+ private const string TimeoutPlaceholder = "timeout";
+ private const string BoostArgsPlaceholder = "boost-args";
+
+ #endregion Constants
+
+ #region Members
+
+ private string _source = null;
+
+ #endregion Members
+
+ ///
+ /// Constructo
+ ///
+ /// The test source (dll/exe) for which this external test runner will execute
+ /// External test runner configuration
+ public ExternalBoostTestRunner(string source, ExternalBoostTestRunnerSettings settings) :
+ base(GetTestExecutable(settings, source))
+ {
+ this._source = source;
+ this.Settings = settings;
+ }
+
+ public ExternalBoostTestRunnerSettings Settings { get; private set; }
+
+ #region IBoostTestRunner
+
+ public override string Source
+ {
+ get { return this._source; }
+ }
+
+ #endregion IBoostTestRunner
+
+ #region BoostTestRunnerBase
+
+ ///
+ /// Provides a ProcessStartInfo structure containing the necessary information to launch the test process.
+ /// Aggregates the BoostTestRunnerCommandLineArgs structure with the command-line arguments specified at configuration stage.
+ ///
+ /// The Boost Test Framework command line arguments
+ /// The Boost Test Runner settings
+ /// A valid ProcessStartInfo structure to launch the test executable
+ protected override ProcessStartInfo GetStartInfo(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ ProcessStartInfo info = base.GetStartInfo(args, settings);
+
+ CommandEvaluator evaluator = BuildEvaluator(this.Source, args, settings);
+ CommandEvaluationResult result = evaluator.Evaluate(this.Settings.ExecutionCommandLine.Arguments);
+
+ string cmdLineArgs = result.Result;
+ if (!result.MappedVariables.Contains(BoostArgsPlaceholder))
+ {
+ cmdLineArgs = result.Result + (result.Result.EndsWith(" ", StringComparison.Ordinal) ? string.Empty : " ") + args.ToString();
+ }
+
+ info.FileName = this.Settings.ExecutionCommandLine.FileName;
+ info.Arguments = cmdLineArgs;
+
+ return info;
+ }
+
+ #endregion BoostTestRunnerBase
+
+ ///
+ /// Extracts and evaluates the test executable from the external Boost Test runner configuration.
+ ///
+ /// The external Boost Test runner configuration
+ /// The test source module containing the tests to execute
+ /// The evaluated, test executable program string
+ private static string GetTestExecutable(ExternalBoostTestRunnerSettings settings, string source)
+ {
+ Utility.Code.Require(settings, "settings");
+ Utility.Code.Require(settings.ExecutionCommandLine, "settings.ExecutionCommandLine");
+
+ return BuildEvaluator(source).Evaluate(settings.ExecutionCommandLine.FileName).Result;
+ }
+
+ ///
+ /// Provides a preset CommandEvaluator instance for evaluating strings containing the source placeholder.
+ ///
+ /// The source placeholder value
+ /// A CommandEvaluator instance for evaluating strings containing the source placeholder.
+ private static CommandEvaluator BuildEvaluator(string source)
+ {
+ CommandEvaluator evaluator = new CommandEvaluator();
+
+ evaluator.SetVariable(SourcePlaceholder, source);
+
+ return evaluator;
+ }
+
+ ///
+ /// Provides a preset CommandEvaluator instance for evaluating strings containing the source, timeout and boost-args placeholders.
+ ///
+ /// The source placeholder value
+ /// The boost arguments placeholder value
+ /// The test runner settings which contains the timeout placeholder value
+ /// A CommandEvaluator instance for evaluating strings containing the source, timeout and boost-args placeholders.
+ private static CommandEvaluator BuildEvaluator(string source, BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ CommandEvaluator evaluator = BuildEvaluator(source);
+
+ if (settings.Timeout > -1)
+ {
+ evaluator.SetVariable(TimeoutPlaceholder, settings.Timeout.ToString(CultureInfo.InvariantCulture));
+ }
+
+ evaluator.SetVariable(BoostArgsPlaceholder, args.ToString());
+
+ return evaluator;
+ }
+ }
+}
diff --git a/BoostTestAdapter/Boost/Runner/IBoostTestRunner.cs b/BoostTestAdapter/Boost/Runner/IBoostTestRunner.cs
new file mode 100644
index 0000000..dfc82c9
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/IBoostTestRunner.cs
@@ -0,0 +1,32 @@
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// BoostTestRunner interface. Identifies a Boost Test Runner.
+ ///
+ public interface IBoostTestRunner
+ {
+ ///
+ /// Initializes a debug instance of this Boost Test runner.
+ ///
+ /// The Boost Test framework command line options.
+ /// The Boost Test runner settings.
+ /// An IFrameworkHandle which provides debugging capabilities.
+ /// Thrown in case specified timeout threshold is exceeded.
+ void Debug(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings, IFrameworkHandle framework);
+
+ ///
+ /// Executes the Boost Test runner with the provided arguments.
+ ///
+ /// The Boost Test framework command line options.
+ /// The Boost Test runner settings.
+ /// Thrown in case specified timeout threshold is exceeded.
+ void Run(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings);
+
+ ///
+ /// Provides a source Id distinguishing different instances
+ ///
+ string Source { get; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Runner/IBoostTestRunnerFactory.cs b/BoostTestAdapter/Boost/Runner/IBoostTestRunnerFactory.cs
new file mode 100644
index 0000000..3a9e4f2
--- /dev/null
+++ b/BoostTestAdapter/Boost/Runner/IBoostTestRunnerFactory.cs
@@ -0,0 +1,16 @@
+namespace BoostTestAdapter.Boost.Runner
+{
+ ///
+ /// Abstract Factory which provides IBoostTestRunner instances.
+ ///
+ public interface IBoostTestRunnerFactory
+ {
+ ///
+ /// Returns an IBoostTestRunner based on the provided identifier.
+ ///
+ /// A unique identifier able to distinguish different BoostTestRunner types.
+ /// A structure which states particular features of interest in the manufactured product.
+ /// An IBoostTestRunner instance or null if one cannot be provided.
+ IBoostTestRunner GetRunner(string identifier, BoostTestRunnerFactoryOptions options);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/ITestVisitable.cs b/BoostTestAdapter/Boost/Test/ITestVisitable.cs
new file mode 100644
index 0000000..8d8415d
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/ITestVisitable.cs
@@ -0,0 +1,15 @@
+namespace BoostTestAdapter.Boost.Test
+{
+ ///
+ /// Visitor design pattern interface intended for Boost.Test.TestUnit concrete implementations.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Visitable")]
+ public interface ITestVisitable
+ {
+ ///
+ /// Applies the test visitor over this instance.
+ ///
+ /// The visitor which will be visiting this instance.
+ void Apply(ITestVisitor visitor);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/ITestVisitor.cs b/BoostTestAdapter/Boost/Test/ITestVisitor.cs
new file mode 100644
index 0000000..2bb1daa
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/ITestVisitor.cs
@@ -0,0 +1,20 @@
+namespace BoostTestAdapter.Boost.Test
+{
+ ///
+ /// Visitor design pattern interface for Boost.Test.TestUnit visitor implementations.
+ ///
+ public interface ITestVisitor
+ {
+ ///
+ /// Visits the provided TestCase
+ ///
+ /// The TestCase which is to be visited
+ void Visit(TestCase testCase);
+
+ ///
+ /// Visits the provided TestSuite
+ ///
+ /// The TestSuite which is to be visited
+ void Visit(TestSuite testSuite);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/TestCase.cs b/BoostTestAdapter/Boost/Test/TestCase.cs
new file mode 100644
index 0000000..d250bf5
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/TestCase.cs
@@ -0,0 +1,106 @@
+using System.Globalization;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Test
+{
+ [XmlRoot(Xml.TestCase)]
+ public class TestCase : TestUnit, IXmlSerializable
+ {
+ #region Constructors
+
+ ///
+ /// Constructor. Required as per IXmlSerializable requirements.
+ ///
+ public TestCase() :
+ this(null, null)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// Test Unit (local) name.
+ /// Parent/Owner Test Unit of this instance.
+ public TestCase(string name, TestSuite parent)
+ : base(name, parent)
+ {
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Optional source file information related to this test.
+ ///
+ public SourceFileInfo Source { get; set; }
+
+ #endregion Properties
+
+ #region IXmlSerializable
+
+ ///
+ /// Xml Tag/Attribute Constants
+ ///
+ internal static class Xml
+ {
+ public const string TestCase = "TestCase";
+ public const string File = "file";
+ public const string Line = "line";
+ }
+
+ public XmlSchema GetSchema()
+ {
+ return null;
+ }
+
+ public void ReadXml(XmlReader reader)
+ {
+ base.ReadXmlAttributes(reader);
+
+ string file = reader.GetAttribute(Xml.File);
+
+ if (!string.IsNullOrEmpty(file))
+ {
+ this.Source = new SourceFileInfo(file);
+ this.Source.LineNumber = int.Parse(reader.GetAttribute(Xml.Line), CultureInfo.InvariantCulture);
+ }
+
+ reader.MoveToElement();
+ bool empty = reader.IsEmptyElement;
+ reader.ReadStartElement(Xml.TestCase);
+
+ if (!empty)
+ {
+ reader.ReadEndElement();
+ }
+ }
+
+ public void WriteXml(XmlWriter writer)
+ {
+ base.WriteXmlAttributes(writer);
+
+ if (this.Source != null)
+ {
+ writer.WriteAttributeString(Xml.File, this.Source.File);
+ writer.WriteAttributeString(Xml.Line, this.Source.LineNumber.ToString(CultureInfo.InvariantCulture));
+ }
+ }
+
+ #endregion IXmlSerializable
+
+ #region ITestVisitable
+
+ public override void Apply(ITestVisitor visitor)
+ {
+ Utility.Code.Require(visitor, "visitor");
+
+ visitor.Visit(this);
+ }
+
+ #endregion ITestVisitable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/TestFramework.cs b/BoostTestAdapter/Boost/Test/TestFramework.cs
new file mode 100644
index 0000000..ed584a4
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/TestFramework.cs
@@ -0,0 +1,90 @@
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+
+namespace BoostTestAdapter.Boost.Test
+{
+ [XmlRoot(Xml.BoostTestFramework)]
+ public class TestFramework : IXmlSerializable
+ {
+ #region Constructors
+
+ public TestFramework() :
+ this(null, null)
+ {
+ }
+
+ public TestFramework(string source, TestSuite master)
+ {
+ this.Source = source;
+ this.MasterTestSuite = master;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Fully qualified path detailing the source Dll/EXE which contains these tests
+ ///
+ public string Source { get; private set; }
+
+ ///
+ /// Boost Test Master Test Suite
+ ///
+ public TestSuite MasterTestSuite { get; private set; }
+
+ #endregion Properties
+
+ #region IXmlSerializable
+
+ ///
+ /// Xml Tag/Attribute Constants
+ ///
+ private static class Xml
+ {
+ public const string BoostTestFramework = "BoostTestFramework";
+ public const string Source = "source";
+ }
+
+ public XmlSchema GetSchema()
+ {
+ return null;
+ }
+
+ public void ReadXml(XmlReader reader)
+ {
+ Utility.Code.Require(reader, "reader");
+
+ reader.MoveToElement();
+
+ this.Source = reader.GetAttribute(Xml.Source);
+
+ bool empty = reader.IsEmptyElement;
+ reader.ReadStartElement(Xml.BoostTestFramework);
+
+ if (!empty)
+ {
+ XmlSerializer deserialiser = new XmlSerializer(typeof(TestSuite));
+ this.MasterTestSuite = deserialiser.Deserialize(reader) as TestSuite;
+
+ reader.ReadEndElement();
+ }
+ }
+
+ public void WriteXml(XmlWriter writer)
+ {
+ Utility.Code.Require(writer, "writer");
+
+ writer.WriteAttributeString(Xml.Source, this.Source);
+
+ if (this.MasterTestSuite != null)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(TestSuite));
+ serializer.Serialize(writer, this.MasterTestSuite);
+ }
+ }
+
+ #endregion IXmlSerializable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/TestFrameworkBuilder.cs b/BoostTestAdapter/Boost/Test/TestFrameworkBuilder.cs
new file mode 100644
index 0000000..f3250a7
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/TestFrameworkBuilder.cs
@@ -0,0 +1,144 @@
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Test
+{
+ ///
+ /// Allows building TestFrameworks with ease using the Builder pattern.
+ ///
+ public class TestFrameworkBuilder
+ {
+ #region Constructors
+
+ ///
+ /// Constructor
+ ///
+ /// Boost Test EXE/Dll file path
+ /// Name of Master Test Suite
+ public TestFrameworkBuilder(string source, string name) :
+ this(source, name, null)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// Boost Test EXE/Dll file path
+ /// Name of Master Test Suite
+ /// Id of Master Test Suite
+ public TestFrameworkBuilder(string source, string name, int? id)
+ {
+ this.Source = source;
+
+ this.MasterTestSuite = new TestSuite(name, null);
+ this.MasterTestSuite.Id = id;
+
+ this.Parent = this.MasterTestSuite;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Boost Test EXE/Dll file path
+ ///
+ private string Source { get; set; }
+
+ ///
+ /// Master Test Suite
+ ///
+ private TestSuite MasterTestSuite { get; set; }
+
+ ///
+ /// Current TestSuite Parent
+ ///
+ private TestSuite Parent { get; set; }
+
+ #endregion Properties
+
+ ///
+ /// Builds a new TestSuite. Starts a new context in which
+ /// newly created TestUnits will be parented to this TestSuite.
+ ///
+ /// Test Suite Name
+ /// this
+ public TestFrameworkBuilder TestSuite(string name)
+ {
+ return this.TestSuite(name, null);
+ }
+
+ ///
+ /// Builds a new TestSuite. Starts a new context in which
+ /// newly created TestUnits will be parented to this TestSuite.
+ ///
+ /// Test Suite Name
+ /// Test Suite Id
+ /// this
+ public TestFrameworkBuilder TestSuite(string name, int? id)
+ {
+ TestSuite testSuite = new TestSuite(name, this.Parent);
+ testSuite.Id = id;
+
+ this.Parent = testSuite;
+
+ return this;
+ }
+
+ ///
+ /// Builds a new TestCase.
+ ///
+ /// Test Case Name
+ /// this
+ public TestFrameworkBuilder TestCase(string name)
+ {
+ return this.TestCase(name, null, null);
+ }
+
+ ///
+ /// Builds a new TestCase.
+ ///
+ /// Test Case Name
+ /// Test Case Id
+ /// this
+ public TestFrameworkBuilder TestCase(string name, int? id)
+ {
+ return this.TestCase(name, id, null);
+ }
+
+ ///
+ /// Builds a new TestCase.
+ ///
+ /// Test Case Name
+ /// Test Case Id
+ /// Test Case source file debug information
+ /// this
+ public TestFrameworkBuilder TestCase(string name, int? id, SourceFileInfo source)
+ {
+ TestCase testCase = new TestCase(name, this.Parent);
+ testCase.Id = id;
+ testCase.Source = source;
+
+ return this;
+ }
+
+ ///
+ /// Ends the current TestSuite context and moves up one level in the hierarchy.
+ ///
+ /// this
+ public TestFrameworkBuilder EndSuite()
+ {
+ this.Parent = (TestSuite)this.Parent.Parent;
+
+ return this;
+ }
+
+ ///
+ /// Builds the TestFramework.
+ ///
+ /// The TestFramework
+ public TestFramework Build()
+ {
+ return new TestFramework(this.Source, this.MasterTestSuite);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/TestSuite.cs b/BoostTestAdapter/Boost/Test/TestSuite.cs
new file mode 100644
index 0000000..e90953a
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/TestSuite.cs
@@ -0,0 +1,162 @@
+using System.Collections.Generic;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Boost.Test
+{
+ [XmlRoot(Xml.TestSuite)]
+ public class TestSuite : TestUnit, IXmlSerializable
+ {
+ #region Members
+
+ private List _children = null;
+
+ #endregion Members
+
+ #region Constructors
+
+ ///
+ /// Constructor. Required as per IXmlSerializable requirements.
+ ///
+ public TestSuite() :
+ this(null)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// Test Unit (local) name.
+ public TestSuite(string name)
+ : this(name, null)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// Test Unit (local) name.
+ /// Parent/Owner Test Unit of this instance.
+ public TestSuite(string name, TestSuite parent)
+ : base(name, parent)
+ {
+ this._children = new List();
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public override IEnumerable Children
+ {
+ get
+ {
+ return this._children;
+ }
+ }
+
+ #endregion Properties
+
+ public override void AddChild(TestUnit unit)
+ {
+ this._children.Add(unit);
+ }
+
+ #region IXmlSerializable
+
+ ///
+ /// Xml Tag/Attribute Constants
+ ///
+ internal static class Xml
+ {
+ public const string TestSuite = "TestSuite";
+ }
+
+ public XmlSchema GetSchema()
+ {
+ return null;
+ }
+
+ public void ReadXml(XmlReader reader)
+ {
+ base.ReadXmlAttributes(reader);
+
+ reader.MoveToElement();
+ bool empty = reader.IsEmptyElement;
+ reader.ReadStartElement(Xml.TestSuite);
+
+ if (!empty)
+ {
+ reader.ConsumeUntilFirst(XmlReaderHelper.ElementFilter);
+
+ while (reader.NodeType == XmlNodeType.Element)
+ {
+ if (reader.Name == Xml.TestSuite)
+ {
+ new TestSuite(null, this).ReadXml(reader);
+ }
+ else if (reader.Name == TestCase.Xml.TestCase)
+ {
+ new TestCase(null, this).ReadXml(reader);
+ }
+
+ reader.ConsumeUntilFirst(XmlReaderHelper.ElementFilter);
+ }
+
+ reader.ReadEndElement();
+ }
+ }
+
+ public void WriteXml(XmlWriter writer)
+ {
+ base.WriteXmlAttributes(writer);
+
+ foreach (TestUnit child in this.Children)
+ {
+ child.Apply(new BoostTestXmlVisitor(writer));
+ }
+ }
+
+ private class BoostTestXmlVisitor : ITestVisitor
+ {
+ private XmlSerializer SuiteSerializer { get; set; }
+
+ private XmlSerializer CaseSerializer { get; set; }
+
+ private XmlWriter Writer { get; set; }
+
+ public BoostTestXmlVisitor(XmlWriter writer)
+ {
+ this.SuiteSerializer = new XmlSerializer(typeof(TestSuite));
+ this.CaseSerializer = new XmlSerializer(typeof(TestCase));
+
+ this.Writer = writer;
+ }
+
+ public void Visit(TestCase testCase)
+ {
+ this.CaseSerializer.Serialize(this.Writer, testCase);
+ }
+
+ public void Visit(TestSuite testSuite)
+ {
+ this.SuiteSerializer.Serialize(this.Writer, testSuite);
+ }
+ }
+
+ #endregion IXmlSerializable
+
+ #region ITestVisitable
+
+ public override void Apply(ITestVisitor visitor)
+ {
+ Utility.Code.Require(visitor, "visitor");
+
+ visitor.Visit(this);
+ }
+
+ #endregion ITestVisitable
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Boost/Test/TestUnit.cs b/BoostTestAdapter/Boost/Test/TestUnit.cs
new file mode 100644
index 0000000..f5eb1e7
--- /dev/null
+++ b/BoostTestAdapter/Boost/Test/TestUnit.cs
@@ -0,0 +1,147 @@
+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;
+
+ 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();
+ }
+ }
+
+ ///
+ /// Identifies the fully qualified name of this TestUnit
+ ///
+ public string FullyQualifiedName
+ {
+ get
+ {
+ return new QualifiedNameBuilder(this).ToString();
+ }
+ }
+
+ #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 IXmlSerializable Helpers
+
+ ///
+ /// Xml Tag/Attribute Constants
+ ///
+ private static class Xml
+ {
+ public const string Id = "id";
+ public const string Name = "name";
+ }
+
+ ///
+ /// Reads common Xml attributes from a TestUnit Xml node.
+ ///
+ /// XmlReader
+ protected void ReadXmlAttributes(XmlReader reader)
+ {
+ Utility.Code.Require(reader, "reader");
+
+ this.Name = reader.GetAttribute(Xml.Name);
+
+ string id = reader.GetAttribute(Xml.Id);
+ if (!string.IsNullOrEmpty(id))
+ {
+ this.Id = int.Parse(id, CultureInfo.InvariantCulture);
+ }
+ }
+
+ ///
+ /// Writes common Xml attributes from a TestUnit Xml node.
+ ///
+ /// XmlWriter
+ protected void WriteXmlAttributes(XmlWriter writer)
+ {
+ Utility.Code.Require(writer, "writer");
+
+ if (this.Id.HasValue)
+ {
+ writer.WriteAttributeString(Xml.Id, this.Id.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ writer.WriteAttributeString(Xml.Name, this.Name);
+ }
+
+ #endregion IXmlSerializable Helpers
+
+ #region ITestVisitable
+
+ public abstract void Apply(ITestVisitor visitor);
+
+ #endregion ITestVisitable
+
+ #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
new file mode 100644
index 0000000..1b1516d
--- /dev/null
+++ b/BoostTestAdapter/BoostTestAdapter.csproj
@@ -0,0 +1,219 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {BC4B3BED-9241-4DD6-8070-A9B66DFC08C1}
+ Library
+ Properties
+ BoostTestAdapter
+ BoostTestAdapter
+ v4.5
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ TRACE;DEBUG;
+ prompt
+ 4
+ false
+ AnyCPU
+ AllRules.ruleset
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+ false
+
+
+ false
+
+
+
+
+
+
+
+ $(MSBuildProgramFiles32)
+
+ $(ProgramFiles%28x86%29)
+
+ $(ProgramFiles) (x86)
+
+ $(ProgramFiles)
+
+
+
+ ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll
+
+
+ $(ProgramFiles32)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll
+
+
+ ..\packages\NCalc.1.3.8\NCalc.dll
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {30ecc867-ce89-425f-b452-7a8a320f727d}
+ VisualStudio2012Adapter
+
+
+ {82df0aeb-582a-4b38-96fc-aaee773beafe}
+ VisualStudio2013Adapter
+
+
+ {eb0051e3-1dda-418c-abaf-c1da5339114c}
+ VisualStudio2015Adapter
+
+
+ {62347cc7-c839-413d-a7ce-598409f6f15b}
+ VisualStudioAdapter
+
+
+
+
+ {80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2}
+ 8
+ 0
+ 0
+ primary
+ False
+ False
+
+
+ {1A31287A-4D7D-413E-8E32-3B374931BD89}
+ 8
+ 0
+ 0
+ primary
+ False
+ False
+
+
+
+
+
+
+
+
+
+ REM required for the BoostTestPlugin so that the log4net config file is inlcuded as part of the vsix installation
+xcopy /c /i /y $(TargetFileName).config ..\..\..\BoostTestPlugin
+REM required by some unit tests in LoggerTest.cs so as to have a certain degree of confidence that log4net is actually logging to file
+xcopy /c /i /y $(TargetFileName).config ..\..\..\BoostTestAdapterNunit
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapter/BoostTestDiscoverer.cs b/BoostTestAdapter/BoostTestDiscoverer.cs
new file mode 100644
index 0000000..bc899b2
--- /dev/null
+++ b/BoostTestAdapter/BoostTestDiscoverer.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+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 one that is called off by Visual Studio before being able to call method DiscoverTests)
+ ///
+ public BoostTestDiscoverer()
+ :this(new DefaultBoostTestDiscovererFactory())
+ {
+ }
+
+ ///
+ /// Constructor accepting an object of type IBoostTestDiscovererFactory (for mocking)
+ ///
+ ///
+ public BoostTestDiscoverer(IBoostTestDiscovererFactory newTestDiscovererFactory)
+ {
+ this.TestDiscovererFactory = newTestDiscovererFactory;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ private IBoostTestDiscovererFactory TestDiscovererFactory { get; set; }
+
+ #endregion Properties
+
+ #region ITestDiscoverer
+
+ ///
+ /// Method call 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
+
+ BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(discoveryContext);
+
+ BoostTestDiscovererFactoryOptions options = new BoostTestDiscovererFactoryOptions();
+ options.ExternalTestRunnerSettings = settings.ExternalTestRunner;
+
+ try
+ {
+ Logger.Initialize(logger);
+
+ var sourceGroups = sources.GroupBy(Path.GetExtension);
+
+ foreach (IGrouping sourceGroup in sourceGroups)
+ {
+ IBoostTestDiscoverer discoverer = TestDiscovererFactory.GetTestDiscoverer(sourceGroup.Key, options);
+
+ if (discoverer != null)
+ {
+ discoverer.DiscoverTests(sourceGroup, discoveryContext, logger, discoverySink);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Exception caught while discovering tests: {0} ({1})", ex.Message, ex.HResult);
+ Logger.Error(ex.StackTrace);
+ }
+ finally
+ {
+ Logger.Shutdown();
+ }
+ }
+
+ #endregion ITestDiscoverer
+
+ }
+}
diff --git a/BoostTestAdapter/BoostTestDiscovererFactoryOptions.cs b/BoostTestAdapter/BoostTestDiscovererFactoryOptions.cs
new file mode 100644
index 0000000..ca6c737
--- /dev/null
+++ b/BoostTestAdapter/BoostTestDiscovererFactoryOptions.cs
@@ -0,0 +1,12 @@
+using BoostTestAdapter.Settings;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// Options for Boost Test discoverer provisioning
+ ///
+ public class BoostTestDiscovererFactoryOptions
+ {
+ public ExternalBoostTestRunnerSettings ExternalTestRunnerSettings { get; set; }
+ }
+}
diff --git a/BoostTestAdapter/BoostTestDiscovererInternal.cs b/BoostTestAdapter/BoostTestDiscovererInternal.cs
new file mode 100644
index 0000000..11b9cd3
--- /dev/null
+++ b/BoostTestAdapter/BoostTestDiscovererInternal.cs
@@ -0,0 +1,357 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using VisualStudioAdapter;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// Contains method to find boost test cases from list of files
+ ///
+ public class BoostTestDiscovererInternal
+ {
+ #region Constants
+
+ ///
+ /// Constants identifying Boost Test tokens.
+ ///
+ private static class Constants
+ {
+ public const string TypedefListIdentifier = "typedef list";
+ public const string TypedefMplListIdentifier = "typedef mpl::list";
+ public const string TypedefBoostMplListIdentifier = "typedef boost::mpl::list";
+ public const string AutoTestCaseIdentifier = "BOOST_AUTO_TEST_CASE";
+ public const string FixtureTestCaseIdentifier = "BOOST_FIXTURE_TEST_CASE";
+ public const string AutoTestSuiteIdentifier = "BOOST_AUTO_TEST_SUITE";
+ public const string FixtureTestSuiteIdentifier = "BOOST_FIXTURE_TEST_SUITE";
+ public const string AutoTestSuiteEndIdentifier = "BOOST_AUTO_TEST_SUITE_END";
+ public const string TestCaseTemplateIdentifier = "BOOST_AUTO_TEST_CASE_TEMPLATE";
+ }
+
+ #endregion Constants
+
+ ///
+ /// Constructor
+ ///
+ /// The Visual Studio instance provider
+ /// source filters object that will be used to filter inactive code
+ public BoostTestDiscovererInternal(IVisualStudioInstanceProvider provider, ISourceFilter[] newSourceFilters)
+ {
+ this.VSProvider = provider;
+ this._sourceFilters = newSourceFilters;
+ }
+
+ #region Members
+
+ ///
+ /// Collection of source filters which are applied to sources for correct test extraction
+ ///
+ private readonly ISourceFilter[] _sourceFilters;
+
+ #endregion Members
+
+ #region Properties
+
+ ///
+ /// Visual Studio Instance provider
+ ///
+ public IVisualStudioInstanceProvider VSProvider { get; private set; }
+
+ #endregion Properties
+
+ #region Public methods
+
+ ///
+ /// gets (parses) all testcases from cpp files checking for
+ /// BOOST_AUTO_TEST_CASE and BOOST_AUTO_TEST_SUITE parameter
+ ///
+ /// mapping between projectexe and the corresponding .cpp files
+ /// UTF component for collecting testcases
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ public void GetBoostTests(IDictionary solutionInfo, ITestCaseDiscoverySink discoverySink)
+ {
+ if (solutionInfo != null)
+ {
+ foreach (KeyValuePair info in solutionInfo)
+ {
+ string source = info.Key;
+ ProjectInfo projectInfo = info.Value;
+
+ foreach(var sourceFile in projectInfo.CppSourceFiles)
+ {
+ try
+ {
+ using (var sr = new StreamReader(sourceFile))
+ {
+ try
+ {
+ var cppSourceFile = new CppSourceFile()
+ {
+ FileName = sourceFile,
+ SourceCode = sr.ReadToEnd()
+ };
+
+ /*
+ * it is important that the pre-processor defines at project level are not modified
+ * because every source file in the project has to have the same starting point.
+ */
+
+ ApplySourceFilter(cppSourceFile, new Defines(projectInfo.DefinesHandler));
+ //call to cpy ctor
+ DiscoverBoostTests(cppSourceFile, source, discoverySink);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(
+ "Exception raised while discovering tests from \"{0}\" of project \"{1}\", ({2})",
+ sourceFile, projectInfo.ProjectExe, ex.Message);
+ Logger.Error(ex.StackTrace);
+ }
+ }
+ }
+ catch
+ {
+ Logger.Error("Unable to open file \"{0}\" of project \"{1}\".", sourceFile, projectInfo.ProjectExe);
+ }
+ }
+ }
+ }
+ else
+ {
+ Logger.Error("the solutionInfo object was found to be null whilst");
+ }
+ }
+
+ ///
+ /// Discovers Boost Test from the provided C++ source file. Notifies test discovery via the provided discoverySink.
+ ///
+ /// The C++ source file to scan for Boost Tests
+ /// The associated test source EXE
+ /// The discoverySink to which identified tests will be notified to
+ private static void DiscoverBoostTests(CppSourceFile cppSourceFile, string source, ITestCaseDiscoverySink discoverySink)
+ {
+ string[] code = cppSourceFile.SourceCode.TrimEnd(new[] { ' ', '\n', '\r' }).Split('\n');
+
+ SourceFileInfo sourceInfo = new SourceFileInfo(cppSourceFile.FileName, 0);
+
+ QualifiedNameBuilder suite = new QualifiedNameBuilder();
+ // Push the equivalent of the Master Test Suite
+ suite.Push(QualifiedNameBuilder.DefaultMasterTestSuiteName);
+
+ var templateLists = new Dictionary>();
+
+ foreach (string line in code)
+ {
+ ++sourceInfo.LineNumber;
+
+ string[] splitMacro = line.Split(new[] { '<', '>', '(', ',', ')', ';' });
+ string desiredMacro = splitMacro[0].Trim();
+
+ /*
+ * Currently the below is not able to handle BOOST UTF signatures spread over multiple lines.
+ */
+ switch (desiredMacro)
+ {
+ case Constants.TypedefListIdentifier:
+ case Constants.TypedefMplListIdentifier:
+ case Constants.TypedefBoostMplListIdentifier:
+ {
+ var dataTypes = new List();
+ int i;
+
+ for (i = 1; i < splitMacro.Length - 2; ++i)
+ {
+ dataTypes.Add(splitMacro[i].Trim());
+ }
+
+ templateLists.Add(splitMacro[i].Trim(), dataTypes);
+ break;
+ }
+
+ case Constants.TestCaseTemplateIdentifier:
+ {
+ string listName = splitMacro[3].Trim();
+ //third parameter is the corresponding boost::mpl::list name
+
+ if (templateLists.ContainsKey(listName))
+ {
+ foreach (var dataType in templateLists[listName])
+ {
+ string testCaseName = splitMacro[1].Trim();
+ //first parameter is the test case name
+ string testCaseNameWithDataType = testCaseName + "<" + dataType + ">";
+
+ var testCase = CreateTestCase(source, sourceInfo,
+ suite, testCaseNameWithDataType);
+
+ AddTestCase(testCase, discoverySink);
+ }
+ }
+ break;
+ }
+
+ case Constants.FixtureTestSuiteIdentifier:
+ case Constants.AutoTestSuiteIdentifier:
+ {
+ suite.Push(splitMacro[1].Trim());
+ break;
+ }
+
+ case Constants.FixtureTestCaseIdentifier:
+ case Constants.AutoTestCaseIdentifier:
+ {
+ string testCaseName = splitMacro[1].Trim();
+
+ var testCase = CreateTestCase(source, sourceInfo, suite,
+ testCaseName);
+
+ AddTestCase(testCase, discoverySink);
+ break;
+ }
+
+ case Constants.AutoTestSuiteEndIdentifier:
+ {
+ suite.Pop();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Applies the filter actions created in the _sourceFilters object onto the source code
+ ///
+ /// source code related information
+ /// reference to the defines instances handling the pre-processor defines
+ private void ApplySourceFilter(CppSourceFile cppSourceFile, Defines definesHandler)
+ {
+ foreach (var sourceFilter in _sourceFilters)
+ {
+ sourceFilter.Filter(cppSourceFile, definesHandler);
+ }
+ }
+
+ ///
+ /// Prepares the test case data. Maps each source to a project within the currently loaded solution.
+ ///
+ /// List of exe files present in the solution.
+ /// Dictionary mapping each source to a ProjectInfo that will be populated with the project information
+ public IDictionary PrepareTestCaseData(IEnumerable sources)
+ {
+ Dictionary solutionInfo = new Dictionary();
+
+ // Get the currently loaded VisualStudio instance
+ IVisualStudio vs = this.VSProvider.Instance;
+
+ if (vs != null)
+ {
+ // Copy the enumerable to a list so that we can maintain/modify this local copy
+ List sourcesCopy = sources.ToList();
+
+ foreach (IProject project in vs.Solution.Projects)
+ {
+ IProjectConfiguration configuration = project.ActiveConfiguration;
+
+ //Iterating over projects and then the sources for improved performance
+ int index = sourcesCopy.FindIndex(source => string.Equals(source, configuration.PrimaryOutput, StringComparison.Ordinal));
+ if (index != -1)
+ {
+ ProjectInfo projectInfo = new ProjectInfo(sourcesCopy[index]);
+
+ // Maintain (copied) list of sources so that we may exit early if possible
+ sourcesCopy.RemoveAt(index);
+
+ projectInfo.DefinesHandler = configuration.CppCompilerOptions.PreprocessorDefinitions;
+ foreach (string sourceFile in project.SourceFiles)
+ {
+ projectInfo.CppSourceFiles.Add(sourceFile);
+ }
+
+ solutionInfo.Add(projectInfo.ProjectExe, projectInfo);
+
+ if (sourcesCopy.Count == 0)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return solutionInfo;
+ }
+
+ #endregion Public methods
+
+ #region Private helper methods
+
+ ///
+ /// Creates a new TestCase object.
+ ///
+ /// Name of the project executable
+ /// .cpp file path and TestCase line number
+ /// The suite in which testcase is present
+ /// Name of the testcase
+ /// The created TestCase object
+ private static VSTestCase CreateTestCase(string sourceExe, SourceFileInfo sourceInfo, QualifiedNameBuilder suite, string testCaseName)
+ {
+ suite.Push(testCaseName);
+
+ string qualifiedName = suite.ToString();
+
+ suite.Pop();
+
+ var testCase = new VSTestCase(qualifiedName, BoostTestExecutor.ExecutorUri, sourceExe)
+ {
+ CodeFilePath = sourceInfo.File,
+ LineNumber = sourceInfo.LineNumber,
+ DisplayName = testCaseName,
+ };
+
+ GroupViaTraits(suite.ToString(), testCase);
+
+ return testCase;
+ }
+
+ ///
+ /// Sets the Traits property for the testcase object.
+ ///
+ /// Name of the test suite to which the testcase belongs
+ /// [ref] The testcase object
+ private static void GroupViaTraits(string suiteName, VSTestCase testCase)
+ {
+ string traitName = suiteName;
+
+ if (string.IsNullOrEmpty(suiteName))
+ {
+ traitName = QualifiedNameBuilder.DefaultMasterTestSuiteName;
+ }
+
+ testCase.Traits.Add(VSTestModel.TestSuiteTrait, traitName);
+ }
+
+ ///
+ /// Helper methods which adds a test case to an internal list and sends the test to the discovery sink
+ ///
+ /// the test case to be added
+ /// the discovery sink where the test case is sent to
+ private static void AddTestCase(VSTestCase testCase, ITestCaseDiscoverySink discoverySink)
+ {
+ //send to discovery sink
+ if (null != discoverySink)
+ {
+ Logger.Info("Found test: {0}", testCase.FullyQualifiedName);
+ discoverySink.SendTestCase(testCase);
+ }
+ }
+
+ #endregion Private helper methods
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/BoostTestExeDiscoverer.cs b/BoostTestAdapter/BoostTestExeDiscoverer.cs
new file mode 100644
index 0000000..fc707ca
--- /dev/null
+++ b/BoostTestAdapter/BoostTestExeDiscoverer.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapter.Utility.VisualStudio;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// Implementation of ITestDiscoverer for Boost Tests contained within .exe files
+ ///
+ public class BoostTestExeDiscoverer : IBoostTestDiscoverer
+ {
+ #region Constructors
+
+ ///
+ /// Default constructor
+ ///
+ public BoostTestExeDiscoverer()
+ : this(new DefaultVisualStudioInstanceProvider())
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public BoostTestExeDiscoverer(IVisualStudioInstanceProvider provider)
+ {
+ this.VSProvider = provider;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public IVisualStudioInstanceProvider VSProvider { get; private set; }
+
+ #endregion Properties
+
+ #region ITestDiscoverer
+
+ ///
+ /// Find and pass all the testcases to discovery sink.
+ ///
+ /// Test files containing testcases
+ /// discovery context settings
+ ///
+ /// Unit test framework Sink
+ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext,
+ IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
+ {
+
+ BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(discoveryContext);
+ var testDiscovererInternal = new BoostTestDiscovererInternal(this.VSProvider, SourceFilterFactory.Get(settings));
+ IDictionary solutioninfo = null;
+
+ var numberOfAttempts = 100;
+
+ // try several times to overcome "Application is Busy" COMException
+ while (numberOfAttempts > 0)
+ {
+ try
+ {
+ solutioninfo = testDiscovererInternal.PrepareTestCaseData(sources);
+ // set numberOfAttempts = 0, because there is no need to try again,
+ // since obviously no exception was thrown at this point
+ numberOfAttempts = 0;
+ }
+ catch (COMException)
+ {
+ --numberOfAttempts;
+
+ // re-throw after all attempts have failed
+ if (numberOfAttempts == 0)
+ {
+ throw;
+ }
+ }
+ }
+
+ testDiscovererInternal.GetBoostTests(solutioninfo, discoverySink);
+ }
+
+ #endregion ITestDiscoverer
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/BoostTestExecutor.cs b/BoostTestAdapter/BoostTestExecutor.cs
new file mode 100644
index 0000000..0d8ce33
--- /dev/null
+++ b/BoostTestAdapter/BoostTestExecutor.cs
@@ -0,0 +1,707 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using BoostTestAdapter.Boost.Results;
+using BoostTestAdapter.Boost.Runner;
+using BoostTestAdapter.Settings;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+using VSTestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// Implementation of ITestExecutor interface for Boost Tests.
+ ///
+ [ExtensionUri(ExecutorUriString)]
+ public class BoostTestExecutor : ITestExecutor
+ {
+ #region Constants
+
+ public const string ExecutorUriString = "executor://BoostTestExecutor/v1";
+ public static readonly Uri ExecutorUri = new Uri(ExecutorUriString);
+
+ // Error issued by Boost Test when a test cannot be executed.
+ private const string TestNotFound = "Test setup error: no test cases matching filter";
+
+ ///
+ /// Static class aggregating constant file extensions.
+ ///
+ private static class FileExtensions
+ {
+ public const string LogFile = ".test.log.xml";
+ public const string ReportFile = ".test.report.xml";
+ public const string StdOutFile = ".test.stdout.log";
+ public const string StdErrFile = ".test.stderr.log";
+ }
+
+ #endregion Constants
+
+ #region Constructors
+
+ ///
+ /// Default constructor
+ ///
+ public BoostTestExecutor()
+ : this(new DefaultBoostTestDiscovererFactory(), new DefaultBoostTestRunnerFactory())
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The ITestDiscovererFactory which is to be used
+ /// The IBoostTestRunnerFactory which is to be used
+ public BoostTestExecutor(IBoostTestDiscovererFactory discovererFactory, IBoostTestRunnerFactory testRunnerFactory)
+ {
+ this.DiscovererFactory = discovererFactory;
+ this.TestRunnerFactory = testRunnerFactory;
+
+ this._cancelled = false;
+ }
+
+ #endregion Constructors
+
+ #region Member variables
+
+ private volatile bool _cancelled = false;
+
+ #endregion Member variables
+
+ #region Properties
+
+ private IBoostTestDiscovererFactory DiscovererFactory { get; set; }
+
+ private IBoostTestRunnerFactory TestRunnerFactory { get; set; }
+
+ #endregion Properties
+
+ #region Delegates
+
+ private delegate BoostTestRunnerCommandLineArgs CommandLineArgsBuilder(string source, BoostTestAdapterSettings settings);
+
+ #endregion Delegates
+
+ ///
+ /// Factory function which returns an appropriate ITestDiscoverer
+ /// for the provided source or null if not applicable.
+ ///
+ /// The source module which requires test discovery
+ /// An IBoostTestDiscoverer valid for the provided source or null if none are available
+ private IBoostTestDiscoverer GetTestDiscoverer(string source, BoostTestAdapterSettings settings)
+ {
+ Utility.Code.Require(settings, "settings");
+
+ BoostTestDiscovererFactoryOptions options = new BoostTestDiscovererFactoryOptions();
+ options.ExternalTestRunnerSettings = settings.ExternalTestRunner;
+
+ return this.DiscovererFactory.GetTestDiscoverer(source, options);
+ }
+
+ ///
+ /// Factory function which returns an appropriate IBoostTestRunner
+ /// for the provided source or null if not applicable.
+ ///
+ /// The test for which to retrieve the IBoostTestRunner
+ /// An IBoostTestRunner valid for the provided source or null if none are available
+ private IBoostTestRunner GetTestRunner(VSTestCase testCase, BoostTestAdapterSettings settings)
+ {
+ BoostTestRunnerFactoryOptions options = new BoostTestRunnerFactoryOptions();
+ options.ExternalTestRunnerSettings = (settings == null) ? null : settings.ExternalTestRunner;
+
+ IBoostTestRunner runner = this.TestRunnerFactory.GetRunner(testCase.Source, options);
+
+ // Using null instance pattern to avoid null reference exceptions raised with use of Linq GroupBy statements
+ return (runner == null) ? NullTestRunner.Instance : runner;
+ }
+
+ ///
+ /// Initialization routine for running tests
+ ///
+ /// The logger which will be used to emit log messages
+ private void SetUp(IMessageLogger logger)
+ {
+#if DEBUG && LAUNCH_DEBUGGER
+ System.Diagnostics.Debugger.Launch();
+#endif
+
+ this._cancelled = false;
+ Logger.Initialize(logger);
+ }
+
+ ///
+ /// Termination/Cleanup routine for running tests
+ ///
+ private static void TearDown()
+ {
+ Logger.Shutdown();
+ }
+
+ #region ITestExecutor
+
+ ///
+ /// Execute the tests one by one. Run All.
+ ///
+ /// Collection of test modules (exe/dll)
+ /// Solution properties
+ /// Unit test framework handle
+ /// Entry point of the execution procedure whenever the user requests to run all the tests
+ public void RunTests(IEnumerable sources,
+ IRunContext runContext,
+ IFrameworkHandle frameworkHandle)
+ {
+ Utility.Code.Require(sources, "sources");
+ Utility.Code.Require(runContext, "runContext");
+ Utility.Code.Require(frameworkHandle, "frameworkHandle");
+
+ SetUp(frameworkHandle);
+
+ BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(runContext);
+
+ foreach (string source in sources)
+ {
+ if (this._cancelled)
+ {
+ break;
+ }
+
+ IBoostTestDiscoverer discoverer = GetTestDiscoverer(source, settings);
+
+ if (discoverer != null)
+ {
+ try
+ {
+ DefaultTestCaseDiscoverySink sink = new DefaultTestCaseDiscoverySink();
+
+ // NOTE IRunContext implements IDiscoveryContext
+ // NOTE IFrameworkHandle implements IMessageLogger
+
+ // Re-discover tests so that we could make use of the RunTests overload which takes an enumeration of test cases.
+ // This is necessary since we need to run tests one by one in order to have the test adapter remain responsive
+ // and have a list of tests over which we can generate test results for.
+ discoverer.DiscoverTests(new string[] { source }, runContext, frameworkHandle, sink);
+
+ IEnumerable batches = null;
+ if (runContext.IsDataCollectionEnabled)
+ {
+ // Batch tests into grouped runs based by source so that we avoid reloading symbols per test run
+ // NOTE For code-coverage speed is given preference over adapter responsiveness.
+ batches = BatchTestsPerSource(sink.Tests, settings, GetCodeCoverageArguments);
+ }
+ else
+ {
+ batches = BatchTestsIndividually(sink.Tests, settings, GetDefaultArguments);
+ }
+
+ // Delegate to the RunBoostTests overload which takes an enumeration of test batches
+ RunBoostTests(batches, runContext, frameworkHandle);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Exception caught while running tests from {0} ({1})", source, ex.Message);
+ }
+ }
+ else
+ {
+ Logger.Error("No suitable discoverer found for {0}.", source);
+ }
+ }
+
+ TearDown();
+ }
+
+ ///
+ /// Execute the tests one by one. Run Selected
+ ///
+ /// Testcases object
+ /// Solution properties
+ /// Unit test framework handle
+ /// Entry point of the execution procedure whenever the user requests to run one or a specific lists of tests
+ public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ Utility.Code.Require(tests, "tests");
+ Utility.Code.Require(runContext, "runContext");
+ Utility.Code.Require(frameworkHandle, "frameworkHandle");
+
+ SetUp(frameworkHandle);
+
+ BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(runContext);
+
+ IEnumerable batches = null;
+ if (runContext.IsDataCollectionEnabled)
+ {
+ // Batch tests into grouped runs based on test source and test suite so that we minimize symbol reloading
+ //
+ // NOTE Required batching at test suite level since Boost Unit Test Framework command-line arguments only allow
+ // multiple test name specification for tests which reside in the same test suite
+ //
+ // NOTE For code-coverage speed is given preference over adapter responsiveness.
+ batches = BatchTestsPerTestSuite(tests, settings, GetCodeCoverageArguments);
+ }
+ else
+ {
+ batches = BatchTestsIndividually(tests, settings, GetDefaultArguments);
+ }
+
+ RunBoostTests(batches, runContext, frameworkHandle);
+
+ TearDown();
+ }
+
+ ///
+ /// Cancel the execution of tests
+ ///
+ public void Cancel()
+ {
+ _cancelled = true;
+ }
+
+ #endregion ITestExecutor
+
+ #region Test Batching
+
+ ///
+ /// Produces test runs, one per test source
+ ///
+ /// The tests to prepare in batches
+ /// Adapter settings which are currently in use
+ /// A builder which produces an appropriate BoostTestRunnerCommandLineArgs structure for a given test and settings pair
+ /// An enumeration of batched test runs, one per distinct test source
+ private IEnumerable BatchTestsPerSource(IEnumerable tests, BoostTestAdapterSettings settings, CommandLineArgsBuilder argsBuilder)
+ {
+ BoostTestRunnerSettings adaptedSettings = settings.TestRunnerSettings.Clone();
+ adaptedSettings.Timeout = -1;
+
+ return tests.
+ GroupBy((source) => GetTestRunner(source, settings), new BoostTestRunnerComparer()).
+ Where((group) => (group.Key != NullTestRunner.Instance)).
+ // Project IGrouping into TestRun instances
+ Select(group =>
+ {
+ BoostTestRunnerCommandLineArgs args = argsBuilder(group.Key.Source, settings);
+
+ // NOTE the --run_test command-line arg is left empty so that all tests are executed
+
+ return new TestRun(group.Key, group, args, adaptedSettings);
+ });
+ }
+
+ ///
+ /// Produces batched test runs grouped by source and test suite
+ ///
+ /// The tests to prepare in batches
+ /// Adapter settings which are currently in use
+ /// A builder which produces an appropriate BoostTestRunnerCommandLineArgs structure for a given test and settings pair
+ /// An enumeration of groups of tests batched into test runs
+ private IEnumerable BatchTestsPerTestSuite(IEnumerable tests, BoostTestAdapterSettings settings, CommandLineArgsBuilder argsBuilder)
+ {
+ BoostTestRunnerSettings adaptedSettings = settings.TestRunnerSettings.Clone();
+ adaptedSettings.Timeout = -1;
+
+ // Group by test runner
+ IEnumerable> sourceGroups =
+ tests.GroupBy((source) => GetTestRunner(source, settings), new BoostTestRunnerComparer()).
+ Where((group) => (group.Key != NullTestRunner.Instance));
+
+ foreach (IGrouping sourceGroup in sourceGroups)
+ {
+ // Group by test suite
+ IEnumerable> suiteGroups = sourceGroup.GroupBy(test => test.Traits.First(trait => (trait.Name == VSTestModel.TestSuiteTrait)).Value);
+ foreach (IGrouping suiteGroup in suiteGroups)
+ {
+ BoostTestRunnerCommandLineArgs args = argsBuilder(sourceGroup.Key.Source, settings);
+
+ 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(sourceGroup.Key, suiteGroup, args, adaptedSettings);
+ }
+ }
+ }
+
+ ///
+ /// Produces test runs, one per test case provided
+ ///
+ /// The tests to prepare in batches
+ /// Adapter settings which are currently in use
+ /// A builder which produces an appropriate BoostTestRunnerCommandLineArgs structure for a given test and settings pair
+ /// An enumeration of batched test runs, one per test
+ private IEnumerable BatchTestsIndividually(IEnumerable tests, BoostTestAdapterSettings settings, CommandLineArgsBuilder argsBuilder)
+ {
+ return tests.Select(test =>
+ {
+ IBoostTestRunner runner = GetTestRunner(test, settings);
+
+ if (runner == NullTestRunner.Instance)
+ {
+ return null;
+ }
+
+ BoostTestRunnerCommandLineArgs args = argsBuilder(runner.Source, settings);
+ args.Tests.Add(test.FullyQualifiedName);
+
+ return new TestRun(runner, new VSTestCase[] { test }, args, settings.TestRunnerSettings);
+ }).Where((testRun) => (testRun != null));
+ }
+
+ ///
+ /// An equality comparer useful for grouping equivalent BoostTestRunners
+ ///
+ private class BoostTestRunnerComparer : IEqualityComparer
+ {
+ #region IEqualityComparer
+
+ public bool Equals(IBoostTestRunner x, IBoostTestRunner y)
+ {
+ Utility.Code.Require(x, "x");
+ Utility.Code.Require(y, "y");
+
+ return x.Source == y.Source;
+ }
+
+ public int GetHashCode(IBoostTestRunner obj)
+ {
+ Utility.Code.Require(obj, "obj");
+
+ return obj.Source.GetHashCode();
+ }
+
+ #endregion IEqualityComparer
+ }
+
+ #endregion Test Batching
+
+ #region Helper methods
+
+ ///
+ /// Run tests one test at a time and update results back to framework.
+ ///
+ /// List of test batches to run
+ /// Solution properties
+ /// Unit test framework handle
+ private void RunBoostTests(IEnumerable testBatches, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ BoostTestAdapterSettings settings = BoostTestAdapterSettingsProvider.GetSettings(runContext);
+
+ foreach (TestRun batch in testBatches)
+ {
+ if (_cancelled)
+ {
+ break;
+ }
+
+ DateTimeOffset start = new DateTimeOffset(DateTime.Now);
+
+ try
+ {
+ Logger.Info("{0}: -> [{1}]", ((runContext.IsBeingDebugged) ? "Debugging" : "Executing"), string.Join(", ", batch.Tests));
+
+ CleanOutput(batch.Arguments);
+
+ // Execute the tests
+ if (ExecuteTests(batch, runContext, frameworkHandle))
+ {
+ foreach (VSTestResult result in GenerateTestResults(batch, start, settings))
+ {
+ // Identify test result to Visual Studio Test framework
+ frameworkHandle.RecordResult(result);
+ }
+ }
+ }
+ catch (BoostTestAdapter.Boost.Runner.TimeoutException ex)
+ {
+ foreach (VSTestCase testCase in batch.Tests)
+ {
+ VSTestResult testResult = GenerateTimeoutResult(testCase, ex);
+ testResult.StartTime = start;
+
+ frameworkHandle.RecordResult(testResult);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Exception caught while running test batch {0} [{1}] ({2})", batch.Source, string.Join(", ", batch.Tests), ex.Message);
+ }
+ }
+ }
+
+ ///
+ /// Delete output files.
+ ///
+ /// The BoostTestRunnerCommandLineArgs which contains references to output files.
+ private static void CleanOutput(BoostTestRunnerCommandLineArgs args)
+ {
+ DeleteFile(args.LogFile);
+ DeleteFile(args.ReportFile);
+ DeleteFile(args.StandardOutFile);
+ DeleteFile(args.StandardErrorFile);
+ }
+
+ ///
+ /// Checks to see if the file is available and deletes it.
+ ///
+ /// The file to delete.
+ /// true if the file is deleted; false otherwise.
+ private static bool DeleteFile(string file)
+ {
+ if (!string.IsNullOrEmpty(file) && File.Exists(file))
+ {
+ File.Delete(file);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Executes the provided test batch
+ ///
+ /// The test batch which will be executed.
+ /// The RunContext for this TestCase. Determines whether the test should be debugged or not.
+ /// The FrameworkHandle for this test execution instance.
+ ///
+ private static bool ExecuteTests(TestRun run, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ if (run.Runner != null)
+ {
+ if (runContext.IsBeingDebugged)
+ {
+ run.Debug(frameworkHandle);
+ }
+ else
+ {
+ run.Run();
+ }
+ }
+ else
+ {
+ Logger.Error("No suitable executor found for [{0}].", string.Join(", ", run.Tests));
+ }
+
+ return run.Runner != null;
+ }
+
+ ///
+ /// Factory function which returns an appropriate BoostTestRunnerCommandLineArgs structure
+ ///
+ /// The TestCases source
+ /// The Boost Test adapter settings currently in use
+ /// A BoostTestRunnerCommandLineArgs structure for the provided source
+ private BoostTestRunnerCommandLineArgs GetDefaultArguments(string source, BoostTestAdapterSettings settings)
+ {
+ BoostTestRunnerCommandLineArgs args = new BoostTestRunnerCommandLineArgs();
+
+ args.WorkingDirectory = Path.GetDirectoryName(source);
+
+ string filename = Path.GetFileName(source);
+
+ // Specify log and report file information
+ args.LogFormat = OutputFormat.XML;
+ args.LogLevel = settings.LogLevel;
+ args.LogFile = SanitizeFileName(filename + FileExtensions.LogFile);
+
+ args.ReportFormat = OutputFormat.XML;
+ args.ReportLevel = ReportLevel.Detailed;
+ args.ReportFile = SanitizeFileName(filename + FileExtensions.ReportFile);
+
+ args.StandardOutFile = SanitizeFileName(filename + FileExtensions.StdOutFile);
+ args.StandardErrorFile = SanitizeFileName(filename + FileExtensions.StdErrFile);
+
+ return args;
+ }
+
+ ///
+ /// Factory function which returns an appropriate BoostTestRunnerCommandLineArgs structure for code coverage
+ ///
+ /// The TestCases source
+ /// The Boost Test adapter settings currently in use
+ /// A BoostTestRunnerCommandLineArgs structure for the provided source
+ private BoostTestRunnerCommandLineArgs GetCodeCoverageArguments(string source, BoostTestAdapterSettings settings)
+ {
+ BoostTestRunnerCommandLineArgs args = GetDefaultArguments(source, settings);
+
+ // Disable standard error/standard output capture
+ args.StandardOutFile = null;
+ args.StandardErrorFile = null;
+
+ // Disable memory leak detection
+ args.DetectMemoryLeaks = 0;
+
+ return args;
+ }
+
+ ///
+ /// Sanitizes a file name suitable for Boost Test command line argument values
+ ///
+ /// The filename to sanitize.
+ /// The sanitized filename.
+ private static string SanitizeFileName(string file)
+ {
+ return file.Replace(' ', '_');
+ }
+
+ ///
+ /// Generates TestResults based on Boost Test result output.
+ ///
+ /// The tests which have been executed in the prior test run.
+ /// The test execution start time.
+ /// boost test adapter settings
+ /// A Visual Studio TestResult related to the executed test.
+ private static IEnumerable GenerateTestResults(TestRun testRun, DateTimeOffset start, BoostTestAdapterSettings settings)
+ {
+ return GenerateTestResults(testRun, start, DateTimeOffset.Now, settings);
+ }
+
+ ///
+ /// Generates TestResults based on Boost Test result output.
+ ///
+ /// The tests which have been executed in the prior test run.
+ /// The test execution start time.
+ /// The test execution end time.
+ /// boost test adapter settings
+ /// A Visual Studio TestResult related to the executed test.
+ private static IEnumerable GenerateTestResults(TestRun testRun, DateTimeOffset start, DateTimeOffset end, BoostTestAdapterSettings settings)
+ {
+ TestResultCollection results = new TestResultCollection();
+
+ try
+ {
+ results.Parse(testRun.Arguments, settings);
+ }
+ catch (XmlException)
+ {
+ string text = File.ReadAllText(testRun.Arguments.ReportFile);
+
+ if (text.Trim() == TestNotFound)
+ {
+ return testRun.Tests.Select(GenerateNotFoundResult);
+ }
+ else
+ {
+ // Re-throw the exception
+ throw;
+ }
+ }
+
+ return testRun.Tests.
+ Select(test =>
+ {
+ // Locate the test result associated to the current test
+ BoostTestAdapter.Boost.Results.TestResult result = results[test.FullyQualifiedName];
+
+ if (result != null)
+ {
+ // Convert the Boost.Test.Result data structure into an equivalent Visual Studio model
+ VSTestResult vsResult = result.AsVSTestResult(test);
+ vsResult.StartTime = start;
+ vsResult.EndTime = end;
+
+ return vsResult;
+ }
+
+ return null;
+ }).
+ Where(result => (result != null));
+ }
+
+ ///
+ /// Generates a default TestResult for a timeout exception.
+ ///
+ /// The test which failed due to a timeout.
+ /// The exception related to this timeout.
+ /// A timed-out, failed TestResult related to the provided test.
+ private static VSTestResult GenerateTimeoutResult(VSTestCase test, BoostTestAdapter.Boost.Runner.TimeoutException ex)
+ {
+ VSTestResult result = new VSTestResult(test);
+
+ result.ComputerName = Environment.MachineName;
+
+ result.Outcome = TestOutcome.Failed;
+ result.Duration = TimeSpan.FromMilliseconds(ex.Timeout);
+ result.ErrorMessage = "Timeout exceeded. Test ran for more than " + ex.Timeout + " ms.";
+
+ if (!string.IsNullOrEmpty(test.CodeFilePath))
+ {
+ result.ErrorStackTrace = new SourceFileInfo(test.CodeFilePath, test.LineNumber).ToString();
+ }
+
+ return result;
+ }
+
+ ///
+ /// Generates a default TestResult for a 'test not found' exception.
+ ///
+ /// The test which failed due to a timeout.
+ /// A timed-out, failed TestResult related to the provided test.
+ private static VSTestResult GenerateNotFoundResult(VSTestCase test)
+ {
+ VSTestResult result = new VSTestResult(test);
+
+ result.ComputerName = Environment.MachineName;
+
+ result.Outcome = TestOutcome.Skipped;
+ result.ErrorMessage = GetNotFoundErrorMessage(test);
+
+ return result;
+ }
+
+ ///
+ /// Provides a suitable message in case the provided test is not found.
+ ///
+ /// The test which was not found.
+ /// A suitable 'not-found' for the provided test case.
+ private static string GetNotFoundErrorMessage(VSTestCase test)
+ {
+ if (test.FullyQualifiedName.Contains(' '))
+ {
+ return TestNotFound + " (Test name contains spaces)";
+ }
+ else if (test.FullyQualifiedName.Contains(','))
+ {
+ return TestNotFound + " (Test name contains commas)";
+ }
+
+ return TestNotFound;
+ }
+
+ #endregion Helper methods
+
+ #region Helper classes
+
+ private class NullTestRunner : IBoostTestRunner
+ {
+ #region IBoostTestRunner
+
+ public void Debug(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings, IFrameworkHandle framework)
+ {
+ // NO OP
+ }
+
+ public void Run(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ // NO OP
+ }
+
+ public string Source
+ {
+ get { return string.Empty; }
+ }
+
+ #endregion IBoostTestRunner
+
+ public static readonly IBoostTestRunner Instance = new NullTestRunner();
+ }
+
+ #endregion Helper classes
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/DefaultBoostTestDiscovererFactory.cs b/BoostTestAdapter/DefaultBoostTestDiscovererFactory.cs
new file mode 100644
index 0000000..a857922
--- /dev/null
+++ b/BoostTestAdapter/DefaultBoostTestDiscovererFactory.cs
@@ -0,0 +1,61 @@
+using System.IO;
+using BoostTestAdapter.Settings;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// Default implementation for IBoostTestDiscovererFactory.
+ ///
+ public class DefaultBoostTestDiscovererFactory : IBoostTestDiscovererFactory
+ {
+ #region IBoostTestDiscovererFactory
+
+ ///
+ /// Provides a test discoverer based on the extension type of the identifier.
+ ///
+ /// The output path and name of the target name along with its extension
+ /// A structure which states particular features of interest in the manufactured product.
+ /// An IBoostTestDiscoverer instance or null if one cannot be provided.
+ public IBoostTestDiscoverer GetTestDiscoverer(string identifier, BoostTestDiscovererFactoryOptions options)
+ {
+ IBoostTestDiscoverer discoverer = null;
+
+ // Prefer external test discoverers over internal ones
+ if ((options != null) && (options.ExternalTestRunnerSettings != null))
+ {
+ discoverer = GetExternalTestDiscoverer(identifier, options.ExternalTestRunnerSettings);
+ }
+
+ if (discoverer == null)
+ {
+ discoverer = GetInternalTestDiscoverer(identifier);
+ }
+
+ return discoverer;
+ }
+
+ private static IBoostTestDiscoverer GetInternalTestDiscoverer(string source)
+ {
+ switch (Path.GetExtension(source))
+ {
+ case ".exe": return new BoostTestExeDiscoverer();
+ }
+
+ return null;
+ }
+
+ private static IBoostTestDiscoverer GetExternalTestDiscoverer(string source, ExternalBoostTestRunnerSettings settings)
+ {
+ Utility.Code.Require(settings, "settings");
+
+ if (settings.ExtensionType == Path.GetExtension(source))
+ {
+ return new ExternalBoostTestDiscoverer(settings);
+ }
+
+ return null;
+ }
+
+ #endregion IBoostTestDiscovererFactory
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/ExternalBoostTestDiscoverer.cs b/BoostTestAdapter/ExternalBoostTestDiscoverer.cs
new file mode 100644
index 0000000..3b58805
--- /dev/null
+++ b/BoostTestAdapter/ExternalBoostTestDiscoverer.cs
@@ -0,0 +1,298 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Xml.Serialization;
+using BoostTestAdapter.Boost.Test;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using TestCase = BoostTestAdapter.Boost.Test.TestCase;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// A Boost Test Discoverer which discovers tests based on configuration.
+ ///
+ public class ExternalBoostTestDiscoverer : IBoostTestDiscoverer
+ {
+ #region Constants
+
+ private const string ListFileSuffix = ".test.list.xml";
+
+ #endregion Constants
+
+ public ExternalBoostTestDiscoverer(ExternalBoostTestRunnerSettings settings)
+ {
+ this.Settings = settings;
+ }
+
+ public ExternalBoostTestRunnerSettings Settings { get; private set; }
+
+ #region IBoostTestDiscoverer
+
+ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
+ {
+ Utility.Code.Require(sources, "sources");
+ Utility.Code.Require(discoverySink, "discoverySink");
+
+ foreach (string source in sources)
+ {
+ TestFramework framework = DiscoverTestFramework(source);
+
+ if ((framework != null) && (framework.MasterTestSuite != null))
+ {
+ BoostTestCaseDiscoverer frameworkDiscoverer = new BoostTestCaseDiscoverer(source, discoverySink);
+ framework.MasterTestSuite.Apply(frameworkDiscoverer);
+ }
+ }
+ }
+
+ #endregion IBoostTestDiscoverer
+
+ ///
+ /// Based on the establishsed configuration, discovers the tests within the provided test source module.
+ ///
+ /// The test source module
+ /// The test framework describing all tests contained within the test source or null if one cannot be provided.
+ private TestFramework DiscoverTestFramework(string source)
+ {
+ if (this.Settings.DiscoveryMethodType == DiscoveryMethodType.DiscoveryFileMap)
+ {
+ return ParseStaticTestList(source);
+ }
+ else if (this.Settings.DiscoveryMethodType == DiscoveryMethodType.DiscoveryCommandLine)
+ {
+ return ExecuteExternalDiscoveryCommand(source);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Executes the discovery command as specified in the configuration for the requested test source.
+ ///
+ /// The test source module
+ /// The test framework describing all tests contained within the test source or null if one cannot be provided.
+ private TestFramework ExecuteExternalDiscoveryCommand(string source)
+ {
+ // Use a temporary file to host the result of the external discovery process
+ string path = Path.Combine(Path.GetDirectoryName(source), Path.GetFileName(source) + ListFileSuffix);
+
+ // Perform cleanup to avoid inconsistent listing
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+
+ CommandEvaluator evaluator = new CommandEvaluator();
+
+ evaluator.SetVariable("source", source);
+ evaluator.SetVariable("out", path);
+
+ // Evaluate the discovery command
+ CommandLine commandLine = new CommandLine
+ {
+ FileName = evaluator.Evaluate(this.Settings.DiscoveryCommandLine.FileName).Result,
+ Arguments = evaluator.Evaluate(this.Settings.DiscoveryCommandLine.Arguments).Result
+ };
+
+ // Execute the discovery command via an external process
+ if (ExecuteCommand(commandLine))
+ {
+ // Parse the generate TestFramework from the temporary file
+ return ParseTestFramework(path);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Executes the provided command line as an external process.
+ ///
+ /// The process command line
+ /// true if the process terminated successfully (exit code = 0); false otherwise.
+ private static bool ExecuteCommand(CommandLine commandLine)
+ {
+ ProcessStartInfo info = new ProcessStartInfo
+ {
+ CreateNoWindow = false,
+ UseShellExecute = false,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ WorkingDirectory = Path.GetTempPath(),
+ FileName = commandLine.FileName,
+ Arguments = commandLine.Arguments,
+ RedirectStandardError = false,
+ RedirectStandardInput = false
+ };
+
+ Process process = Process.Start(ProcessStartInfoEx.StartThroughCmdShell(info));
+ if (process != null)
+ {
+ process.WaitForExit();
+
+ return (process.ExitCode == 0);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Parses a static file containing the test lising for the requested test source as specified in the configuration.
+ ///
+ /// The test source module
+ /// The test framework describing all tests contained within the test source or null if one cannot be provided.
+ private TestFramework ParseStaticTestList(string source)
+ {
+ string path = null;
+ if (this.Settings.DiscoveryFileMap.TryGetValue(Path.GetFileName(source), out path))
+ {
+ return ParseTestFramework(path);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Deserializes a TestFramework from the Xml file path provided.
+ ///
+ /// A valid path to a TestFramework Xml file.
+ /// The deserialized TestFramework
+ private static TestFramework ParseTestFramework(string path)
+ {
+ using (FileStream stream = File.OpenRead(path))
+ {
+ XmlSerializer deserializer = new XmlSerializer(typeof(TestFramework));
+ return deserializer.Deserialize(stream) as TestFramework;
+ }
+ }
+
+ ///
+ /// ITestVisitor implementation. Visits TestCases and registers them
+ /// with the supplied ITestCaseDiscoverySink.
+ ///
+ private class BoostTestCaseDiscoverer : ITestVisitor
+ {
+ #region Constructors
+
+ ///
+ /// Constructor
+ ///
+ /// The source test module which contains the discovered tests
+ /// The ITestCaseDiscoverySink which will have tests registered with
+ public BoostTestCaseDiscoverer(string source, ITestCaseDiscoverySink sink)
+ {
+ this.Source = source;
+ this.Sink = sink;
+
+ this.TestSuite = new QualifiedNameBuilder();
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public ITestCaseDiscoverySink Sink { get; private set; }
+
+ private QualifiedNameBuilder TestSuite { get; set; }
+
+ public uint Count { get; private set; }
+
+ private TestSuite MasterTestSuite { get; set; }
+
+ public string Source { get; private set; }
+
+ #endregion Properties
+
+ public void Visit(Boost.Test.TestCase testCase)
+ {
+ Utility.Code.Require(testCase, "testCase");
+
+ // Convert from Boost.Test.TestCase to a Visual Studio TestCase object
+ VSTestCase test = GenerateTestCase(testCase);
+
+ if (test != null)
+ {
+ Logger.Info("Found test: {0}", testCase.FullyQualifiedName);
+
+ ++this.Count;
+
+ // Register test case
+ this.Sink.SendTestCase(test);
+ }
+ }
+
+ public void Visit(TestSuite testSuite)
+ {
+ Utility.Code.Require(testSuite, "testSuite");
+
+ this.TestSuite.Push(testSuite);
+
+ // Identify Master Test Suite
+ if ((this.MasterTestSuite == null) && (testSuite.Parent == null))
+ {
+ this.MasterTestSuite = testSuite;
+ }
+
+ foreach (TestUnit child in testSuite.Children)
+ {
+ child.Apply(this);
+ }
+
+ this.TestSuite.Pop();
+ }
+
+ ///
+ /// 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)
+ {
+ // Temporarily push TestCase on TestSuite name builder to acquire the fully qualified name of the TestCase
+ this.TestSuite.Push(testCase);
+
+ VSTestCase test = new VSTestCase(
+ this.TestSuite.ToString(),
+ BoostTestExecutor.ExecutorUri,
+ this.Source
+ );
+
+ // Reset TestSuite QualifiedNameBuilder to original value
+ this.TestSuite.Pop();
+
+ if (testCase.Source != null)
+ {
+ test.CodeFilePath = testCase.Source.File;
+ test.LineNumber = testCase.Source.LineNumber;
+ }
+
+ test.DisplayName = testCase.Name;
+
+ // Register the test suite as a trait
+ test.Traits.Add(new Trait(VSTestModel.TestSuiteTrait, GetCurrentTestSuite()));
+
+ return test;
+ }
+
+ ///
+ /// Provides the fully qualified name of the current TestSuite
+ ///
+ /// The fully qualified name of the current TestSuite
+ private string GetCurrentTestSuite()
+ {
+ // 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 (this.TestSuite.Level == QualifiedNameBuilder.MasterTestSuiteLevel)
+ {
+ return (this.MasterTestSuite == null) ? QualifiedNameBuilder.DefaultMasterTestSuiteName : this.MasterTestSuite.Name;
+ }
+
+ return this.TestSuite.ToString();
+ }
+ }
+ }
+}
diff --git a/BoostTestAdapter/GlobalSuppressions.cs b/BoostTestAdapter/GlobalSuppressions.cs
new file mode 100644
index 0000000..a4154c8
Binary files /dev/null and b/BoostTestAdapter/GlobalSuppressions.cs differ
diff --git a/BoostTestAdapter/IBoostTestDiscoverer.cs b/BoostTestAdapter/IBoostTestDiscoverer.cs
new file mode 100644
index 0000000..38aff16
--- /dev/null
+++ b/BoostTestAdapter/IBoostTestDiscoverer.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace BoostTestAdapter
+{
+ public interface IBoostTestDiscoverer
+ {
+ void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger,
+ ITestCaseDiscoverySink discoverySink);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/IBoostTestDiscovererFactory.cs b/BoostTestAdapter/IBoostTestDiscovererFactory.cs
new file mode 100644
index 0000000..9e00c42
--- /dev/null
+++ b/BoostTestAdapter/IBoostTestDiscovererFactory.cs
@@ -0,0 +1,16 @@
+namespace BoostTestAdapter
+{
+ ///
+ /// Abstract Factory which provides ITestDiscoverer instances.
+ ///
+ public interface IBoostTestDiscovererFactory
+ {
+ ///
+ /// Returns an IBoostTestDiscoverer based on the provided identifier.
+ ///
+ /// A unique identifier able to distinguish different ITestDiscoverer types.
+ /// A structure which states particular features of interest in the manufactured product.
+ /// An IBoostTestDiscoverer instance or null if one cannot be provided.
+ IBoostTestDiscoverer GetTestDiscoverer(string identifier, BoostTestDiscovererFactoryOptions options);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/ProjectInfo.cs b/BoostTestAdapter/ProjectInfo.cs
new file mode 100644
index 0000000..20a9eda
--- /dev/null
+++ b/BoostTestAdapter/ProjectInfo.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter
+{
+ ///
+ /// Aggregates necessary project information for EXE Boost test discovery.
+ ///
+ public class ProjectInfo
+ {
+ ///
+ /// Constructor
+ ///
+ /// The EXE test source path
+ public ProjectInfo(string projectExe)
+ {
+ ProjectExe = projectExe;
+ CppSourceFiles = new List();
+ }
+
+ ///
+ /// Preprocessor definitions in use by the test source
+ ///
+ public Defines DefinesHandler { get; set; }
+
+ ///
+ /// Collection of C++ source files related to the test source
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cpp")]
+ public IList CppSourceFiles { get; private set; }
+
+ ///
+ /// Boost Test EXE source path
+ ///
+ public string ProjectExe { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Properties/AssemblyInfo.cs b/BoostTestAdapter/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..52ef303
--- /dev/null
+++ b/BoostTestAdapter/Properties/AssemblyInfo.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyTitle("BoostTestAdapter")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BoostTestAdapter")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: CLSCompliant(false)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the Id of the typelib if this project is exposed to COM
+
+[assembly: Guid("28c1228a-afc5-4bf9-9cc7-fa3d2e81a8ff")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: InternalsVisibleTo("BoostTestAdaptorNUnit")]
\ No newline at end of file
diff --git a/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs b/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs
new file mode 100644
index 0000000..a0640c6
--- /dev/null
+++ b/BoostTestAdapter/Settings/BoostTestAdapterSettings.cs
@@ -0,0 +1,94 @@
+using System.ComponentModel;
+using System.Xml;
+using System.Xml.Serialization;
+using BoostTestAdapter.Boost.Runner;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace BoostTestAdapter.Settings
+{
+ ///
+ /// Settings relating to the Boost Test Visual Studio Adapter.
+ ///
+ ///
+ /// Distinguish between BoostTestAdapterSettings and BoostTestRunnerSettings.
+ /// BoostTestAdapterSettings aggregate both adapter specific settings and
+ /// BoostTestRunner settings as required.
+ ///
+ [XmlRoot(XmlRootName)]
+ public class BoostTestAdapterSettings : TestRunSettings
+ {
+ public const string XmlRootName = "BoostTest";
+
+ public BoostTestAdapterSettings() :
+ base(XmlRootName)
+ {
+ this.TestRunnerSettings = new BoostTestRunnerSettings();
+
+ // Set default configuration values
+
+ this.FailTestOnMemoryLeak = false;
+
+ this.ConditionalInclusionsFilteringEnabled = true;
+
+ this.LogLevel = LogLevel.TestSuite;
+
+ this.ExternalTestRunner = null;
+ }
+
+ #region Properties
+
+ #region Serialisable Fields
+
+ [DefaultValue(-1)]
+ public int TimeoutMilliseconds
+ {
+ get
+ {
+ return this.TestRunnerSettings.Timeout;
+ }
+
+ set
+ {
+ this.TestRunnerSettings.Timeout = value;
+ }
+ }
+
+ [DefaultValue(false)]
+ public bool FailTestOnMemoryLeak { get; set; }
+
+ [DefaultValue(true)]
+ public bool ConditionalInclusionsFilteringEnabled { get; set; }
+
+ [DefaultValue(LogLevel.TestSuite)]
+ public LogLevel LogLevel { get; set; }
+
+ public ExternalBoostTestRunnerSettings ExternalTestRunner { get; set; }
+
+ #endregion Serialisable Fields
+
+ [XmlIgnore]
+ public BoostTestRunnerSettings TestRunnerSettings { get; private set; }
+
+ #endregion Properties
+
+ #region TestRunSettings
+
+ public override XmlElement ToXml()
+ {
+ XmlDocument doc = new XmlDocument();
+
+ using (XmlWriter writer = doc.CreateNavigator().AppendChild())
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(BoostTestAdapterSettings));
+ serializer.Serialize(writer, this);
+ }
+
+ // Remove any namespace related attributes
+ doc.DocumentElement.RemoveAllAttributes();
+
+ return doc.DocumentElement;
+ }
+
+ #endregion TestRunSettings
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Settings/BoostTestAdapterSettingsProvider.cs b/BoostTestAdapter/Settings/BoostTestAdapterSettingsProvider.cs
new file mode 100644
index 0000000..f664c22
--- /dev/null
+++ b/BoostTestAdapter/Settings/BoostTestAdapterSettingsProvider.cs
@@ -0,0 +1,72 @@
+using System.ComponentModel.Composition;
+using System.Xml;
+using System.Xml.Serialization;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace BoostTestAdapter.Settings
+{
+ ///
+ /// A Visual Studio ISettingsProvider implementation. Provisions BoostTestAdapterSettings.
+ ///
+ [Export(typeof(ISettingsProvider))]
+ [SettingsName(BoostTestAdapterSettings.XmlRootName)]
+ public class BoostTestAdapterSettingsProvider : ISettingsProvider
+ {
+ #region Constructors
+
+ public BoostTestAdapterSettingsProvider()
+ {
+ this.Settings = new BoostTestAdapterSettings();
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Reference to the recently loaded settings. May be null if no settings were specified or the settings failed to load.
+ ///
+ public BoostTestAdapterSettings Settings { get; private set; }
+
+ #endregion Properties
+
+ #region ISettingsProvider
+
+ public void Load(XmlReader reader)
+ {
+ Utility.Code.Require(reader, "reader");
+
+ // NOTE This method gets called if the settings name matches the node name as expected.
+
+ if (reader.Read() && reader.Name.Equals(BoostTestAdapterSettings.XmlRootName))
+ {
+ XmlSerializer deserializer = new XmlSerializer(typeof(BoostTestAdapterSettings));
+ this.Settings = deserializer.Deserialize(reader) as BoostTestAdapterSettings;
+ }
+ }
+
+ #endregion ISettingsProvider
+
+ ///
+ /// Builds a BoostTestAdapterSettings structure based on the information located within the IDiscoveryContext instance.
+ ///
+ /// The discovery context instance
+ /// A BoostTestRunnerSettings instance based on the information identified via the provided IDiscoveryContext instance.
+ public static BoostTestAdapterSettings GetSettings(IDiscoveryContext context)
+ {
+ Utility.Code.Require(context, "context");
+
+ BoostTestAdapterSettings settings = new BoostTestAdapterSettings();
+
+ BoostTestAdapterSettingsProvider provider = (context.RunSettings == null) ? null : context.RunSettings.GetSettings(BoostTestAdapterSettings.XmlRootName) as BoostTestAdapterSettingsProvider;
+ if (provider != null)
+ {
+ settings = provider.Settings;
+ }
+
+ // Return defaults
+ return settings;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Settings/ExternalBoostTestRunnerSettings.cs b/BoostTestAdapter/Settings/ExternalBoostTestRunnerSettings.cs
new file mode 100644
index 0000000..ca235c4
--- /dev/null
+++ b/BoostTestAdapter/Settings/ExternalBoostTestRunnerSettings.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.Settings
+{
+ [Serializable]
+ public enum DiscoveryMethodType
+ {
+ DiscoveryCommandLine,
+ DiscoveryFileMap
+ }
+
+ ///
+ /// Identifies the external test runner configuration block and its configuration options.
+ ///
+ [XmlRoot(Xml.ExternalTestRunner)]
+ public class ExternalBoostTestRunnerSettings : IXmlSerializable
+ {
+ #region Constants
+
+ public const string DefaultExtensionType = ".dll";
+
+ #endregion Constants
+
+ #region Constructors
+
+ public ExternalBoostTestRunnerSettings()
+ {
+ this.ExtensionType = DefaultExtensionType;
+ this.DiscoveryFileMap = new Dictionary();
+
+ this.DiscoveryCommandLine = new CommandLine();
+ this.ExecutionCommandLine = new CommandLine();
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Specifies the file extension for which this test runner applies to
+ ///
+ public string ExtensionType { get; set; }
+
+ ///
+ /// Specifies the discovery method i.e. either via a file map or via an external command
+ ///
+ public DiscoveryMethodType DiscoveryMethodType { get; set; }
+
+ ///
+ /// Maps a source to a test discover Xml file path
+ ///
+ public IDictionary DiscoveryFileMap { get; private set; }
+
+ public CommandLine DiscoveryCommandLine { get; set; }
+
+ public CommandLine ExecutionCommandLine { get; set; }
+
+ #endregion Properties
+
+ #region IXmlSerializable
+
+ private static class Xml
+ {
+ public const string ExternalTestRunner = "ExternalTestRunner";
+ public const string Type = "type";
+
+ public const string DiscoveryCommandLine = "DiscoveryCommandLine";
+
+ public const string DiscoveryFileMap = "DiscoveryFileMap";
+ public const string DiscoveryFileMapEntry = "File";
+ public const string DiscoveryFileMapSource = "source";
+
+ public const string ExecutionCommandLine = "ExecutionCommandLine";
+ }
+
+ public XmlSchema GetSchema()
+ {
+ return null;
+ }
+
+ public void ReadXml(XmlReader reader)
+ {
+ Utility.Code.Require(reader, "reader");
+
+ this.ExtensionType = reader.GetAttribute(Xml.Type);
+ if (string.IsNullOrEmpty(this.ExtensionType))
+ {
+ this.ExtensionType = DefaultExtensionType;
+ }
+
+ reader.ReadStartElement();
+
+ reader.ConsumeUntilFirst(XmlReaderHelper.ElementFilter);
+
+ bool empty = reader.IsEmptyElement;
+ string name = reader.Name;
+
+ this.DiscoveryMethodType = (DiscoveryMethodType)Enum.Parse(typeof(DiscoveryMethodType), name);
+
+ reader.ReadStartElement();
+ if (name == Xml.DiscoveryCommandLine)
+ {
+ empty = false;
+ this.DiscoveryCommandLine = CommandLine.FromString(reader.ReadString());
+ }
+ else if (name == Xml.DiscoveryFileMap)
+ {
+ reader.ConsumeUntilFirst(XmlReaderHelper.ElementFilter);
+ while (reader.NodeType == XmlNodeType.Element)
+ {
+ string key = reader.GetAttribute(Xml.DiscoveryFileMapSource);
+
+ reader.MoveToElement();
+ empty = reader.IsEmptyElement;
+ reader.ReadStartElement();
+
+ this.DiscoveryFileMap[key] = (empty) ? string.Empty : reader.ReadString();
+
+ if (!empty)
+ {
+ reader.ReadEndElement();
+ }
+
+ reader.ConsumeUntilFirst(XmlReaderHelper.ElementFilter);
+ }
+ }
+
+ if (!empty)
+ {
+ reader.ReadEndElement();
+ }
+
+ reader.ConsumeUntilFirst(XmlReaderHelper.ElementFilter);
+ this.ExecutionCommandLine = CommandLine.FromString(reader.ReadElementString(Xml.ExecutionCommandLine));
+
+ reader.ReadEndElement();
+ }
+
+ public void WriteXml(XmlWriter writer)
+ {
+ Utility.Code.Require(writer, "writer");
+
+ writer.WriteAttributeString(Xml.Type, this.ExtensionType);
+
+ if (DiscoveryMethodType == DiscoveryMethodType.DiscoveryCommandLine)
+ {
+ writer.WriteElementString(Xml.DiscoveryCommandLine, this.DiscoveryCommandLine.ToString());
+ }
+ else if (DiscoveryMethodType == DiscoveryMethodType.DiscoveryFileMap)
+ {
+ if (this.DiscoveryFileMap.Count > 0)
+ {
+ writer.WriteStartElement(Xml.DiscoveryFileMap);
+
+ foreach (KeyValuePair entry in this.DiscoveryFileMap)
+ {
+ writer.WriteStartElement(Xml.DiscoveryFileMapEntry);
+
+ writer.WriteAttributeString(Xml.DiscoveryFileMapSource, entry.Key);
+ writer.WriteString(entry.Value);
+
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+ }
+
+ writer.WriteElementString(Xml.ExecutionCommandLine, this.ExecutionCommandLine.ToString());
+ }
+
+ #endregion IXmlSerializable
+
+ }
+}
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/ConditionalInclusionsMachine.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/ConditionalInclusionsMachine.cs
new file mode 100644
index 0000000..49077e0
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/ConditionalInclusionsMachine.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using BoostTestAdapter.Utility;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ internal enum SourceLineType
+ {
+ Other = 1,
+ Ifclause,
+ Elifclause,
+ Elseclause,
+ Endifclause,
+ Defineclause,
+ MultiLineDefineclause,
+ Undefineclause,
+ Ifdefclause,
+ Ifndefclause
+ }
+
+ internal enum ParserState
+ {
+ NormalState = 1,
+ IncludingDueToIf,
+ DiscardingDueToSuccessfullIf,
+ DiscardingDueToFailedIf,
+ DiscardingDueToMultilineDefine
+ }
+
+ ///
+ /// Filters source code that is controlled via the preprocessor directives such as:
+ /// #if, #elif, #else, #endif, #ifdef, #ifndef, #undef, #define, #if defined and if !defined
+ ///
+ public class ConditionalInclusionsMachine
+ {
+ private static readonly Regex LinefeedRegex = new Regex(@"[ \t]*?\r??\n", RegexOptions.Singleline | RegexOptions.Multiline);
+ private static readonly Regex UndefineRegex = new Regex(@"#undef\s{1,}(\w{1,})");
+ private static readonly Regex IfdefRegex = new Regex(@"#if(?:(?:\s{1,}defined)|(?:def))\s{1,}(\w{1,})");
+ private static readonly Regex IfndefRegex = new Regex(@"#if(?:(?:\s{1,}!(?:\s{0,})defined)|(?:ndef))\s{1,}(\w{1,})");
+ private static readonly Regex IfRegex = new Regex(@"#if\s+?(.+)");
+ private static readonly Regex ElifRegex = new Regex(@"#elif\s+?(.+)");
+ private static readonly Regex ElseRegex = new Regex(@"#else");
+ private static readonly Regex EndifRegex = new Regex(@"#endif");
+ private static readonly Regex DefineRegex = new Regex(@"#define\s+(\w{1,})(.*)");
+ private static readonly Regex DefineRegexMacro = new Regex(@"#define\s{1,}(\w{1,})\(");
+ private static readonly Regex DefineRegexMultiline = new Regex(@"#define\s{1,}(\w{1,}).*\\\s{0,}$", RegexOptions.Multiline);
+
+ private readonly IConditionalInclusionsState _normalState;
+ private readonly IConditionalInclusionsState _includingDueToIf;
+ private readonly IConditionalInclusionsState _discardingDueToSuccessfullIf;
+ private readonly IConditionalInclusionsState _discardingDueToFailedIf;
+ private readonly IConditionalInclusionsState _discardingDueToMultilineDefine;
+
+ private IConditionalInclusionsState _conditionalInclusionsMachineState;
+
+ private readonly Stack _parserState;
+
+ ///
+ ///
+ ///
+ internal IEvaluation Evaluator { get; private set; }
+
+ internal Defines DefinesHandler { get; private set; }
+
+ #region Constructors
+
+ ///
+ /// Constructor accepting an expression evaluator object
+ ///
+ public ConditionalInclusionsMachine(IEvaluation evaluator)
+ {
+ this.Evaluator = evaluator;
+ this._parserState = new Stack();
+
+ _normalState = new NormalState(this);
+ _includingDueToIf = new IncludingDueToIf(this);
+ _discardingDueToSuccessfullIf = new DiscardingDueToSuccessfullIf(this);
+ _discardingDueToFailedIf = new DiscardingDueToFailedIf(this);
+ _discardingDueToMultilineDefine = new DiscardingDueToMultilineDefine(this);
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Updates the state machine according to the state indicated by the top most element of the stack
+ ///
+ private void UpdateStateMachine()
+ {
+ switch (_parserState.Peek())
+ {
+ case ParserState.NormalState:
+ _conditionalInclusionsMachineState = _normalState;
+ break;
+
+ case ParserState.IncludingDueToIf:
+ _conditionalInclusionsMachineState = _includingDueToIf;
+ break;
+
+ case ParserState.DiscardingDueToSuccessfullIf:
+ _conditionalInclusionsMachineState = _discardingDueToSuccessfullIf;
+ break;
+
+ case ParserState.DiscardingDueToFailedIf:
+ _conditionalInclusionsMachineState = _discardingDueToFailedIf;
+ break;
+
+ case ParserState.DiscardingDueToMultilineDefine:
+ _conditionalInclusionsMachineState = _discardingDueToMultilineDefine;
+ break;
+
+ default:
+ throw new SourceDiscoveryException("Programming error. Found un-catered machine state (" + _parserState.Peek() + ")");
+ }
+ }
+
+ ///
+ /// Adds a new state to the stack and updates the state machine according to the new state
+ ///
+ /// new state
+ internal void AddState(ParserState newParserState)
+ {
+ _parserState.Push(newParserState);
+ UpdateStateMachine();
+ }
+
+ ///
+ /// Updates the current state of the state machine with a newly defined state.
+ ///
+ /// new state
+ internal void UpdateCurrentState(ParserState newParserState)
+ {
+ _parserState.Pop();
+ _parserState.Push(newParserState);
+ UpdateStateMachine();
+ }
+
+ ///
+ /// Called whenever we are done with the current state and therefore we can continue with
+ /// the previous state before wed branched off in handling another state
+ ///
+ internal void FinishWithCurrentState()
+ {
+ _parserState.Pop();
+ UpdateStateMachine();
+ }
+
+ ///
+ /// Driver method for the management of the state machine
+ ///
+ /// source file information
+ /// pre-processor defines
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "cpp")]
+ public void Apply(CppSourceFile cppSourceFile, Defines definesHandler)
+ {
+ Utility.Code.Require(cppSourceFile, "cppSourceFile");
+
+ DefinesHandler = definesHandler;
+
+ string[] sourceLines = LinefeedRegex.Split(cppSourceFile.SourceCode);
+
+ _parserState.Clear();
+ AddState(ParserState.NormalState); //initial state
+
+ SourceLineType sourceLineType;
+ string expression;
+ string subtitutionText;
+ int lineNumber = 0;
+
+ try
+ {
+ for (; lineNumber < sourceLines.Length; lineNumber++)
+ {
+ Inspect(sourceLines[lineNumber], out sourceLineType, out expression, out subtitutionText);
+
+ _conditionalInclusionsMachineState.Process(ref sourceLines[lineNumber], sourceLineType, expression,
+ subtitutionText);
+ }
+
+ /*
+ * Once the parsing is complete we just check that the parserState is back to Normal State.
+ * If not it is either because we parsed bad code (i.e. the code structure was not consistent to start with)
+ * or we've got a problem (programmatically) with our state engine
+ */
+
+ //sanity check
+ if (_parserState.Peek() != ParserState.NormalState)
+ {
+ Logger.Error("The conditionals filter state machine failed to return to normal state. The source file \"{0}\" will not be filtered for conditionals.", cppSourceFile.FileName);
+
+ //the source code for the specific file is left unfiltered in case the state machine did not return to a normal state.
+ }
+ else
+ {
+ //State machine returned to normal state so we can apply the filtering done by the conditional inclusions machine
+ cppSourceFile.SourceCode = string.Join(Environment.NewLine, sourceLines);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(
+ "The conditionals filter encountered error: {0} whilst processing line number {1} of source file \"{2}\". The source file will not be filtered for conditionals",
+ ex.Message, lineNumber + 1, cppSourceFile.FileName);
+ }
+ }
+
+ ///
+ /// Inspects the source line and sets the sourceLineType (byref) according to detected code syntax
+ /// and sets also the expression variable (byref) to the matched identifier statement/expression where applicable
+ ///
+ /// source line to inspect
+ /// match type
+ /// extracted expression or identifier respective to the source type
+ /// extracted substitution text (applicable only to a define match)
+ private static void Inspect(string sourceLine, out SourceLineType sourceLineType, out string token, out string substitutiontext)
+ {
+ if (ElifRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Elifclause;
+ token = ElifRegex.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else if (ElseRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Elseclause;
+ token = "";
+ substitutiontext = "";
+ }
+ else if (EndifRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Endifclause;
+ token = "";
+ substitutiontext = "";
+ }
+ else if (IfdefRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Ifdefclause;
+ token = IfdefRegex.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else if (IfndefRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Ifndefclause;
+ token = IfndefRegex.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else if (IfRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Ifclause;
+ token = IfRegex.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else if (DefineRegexMultiline.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.MultiLineDefineclause;
+ token = DefineRegexMultiline.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else if (DefineRegexMacro.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Defineclause;
+ token = DefineRegexMacro.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else if (DefineRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Defineclause;
+ token = DefineRegex.Match(sourceLine).Groups[1].Value;
+ substitutiontext = DefineRegex.Match(sourceLine).Groups[2].Value.Trim();
+ }
+ else if (UndefineRegex.IsMatch(sourceLine))
+ {
+ sourceLineType = SourceLineType.Undefineclause;
+ token = UndefineRegex.Match(sourceLine).Groups[1].Value;
+ substitutiontext = "";
+ }
+ else
+ {
+ //any other type of source code that is an unrelated to the conditional inclusions
+ sourceLineType = SourceLineType.Other;
+ token = "";
+ substitutiontext = "";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToFailedIf.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToFailedIf.cs
new file mode 100644
index 0000000..07bec63
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToFailedIf.cs
@@ -0,0 +1,83 @@
+using System.Globalization;
+
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ ///
+ /// Class handling the state when the Conditional Inclusions Machine is discarding code when an if statement evaluated to false
+ ///
+ internal class DiscardingDueToFailedIf : IConditionalInclusionsState
+ {
+ private readonly ConditionalInclusionsMachine _conditionalInclusionsMachine;
+
+ #region Constructor
+
+ ///
+ /// Constructor
+ ///
+ /// reference to the state machine
+ internal DiscardingDueToFailedIf(ConditionalInclusionsMachine conditionalInclusionsMachine)
+ {
+ _conditionalInclusionsMachine = conditionalInclusionsMachine;
+ }
+
+ #endregion Constructor
+
+ ///
+ /// Called so as to make the necessary decision making whilst the state machine is discarding code during a failed if
+ ///
+ /// source line under analysis
+ /// source code type as inspected by the Inspect function in the conditional inclusions machine
+ /// expression to be evaluated in case the source Line type is an elif
+ ///
+ public void Process(ref string sourceLine, SourceLineType sourceLineType, string expression, string subtitutionText)
+ {
+ sourceLine = "";
+ switch (sourceLineType)
+ {
+ case SourceLineType.Ifclause:
+ case SourceLineType.Ifdefclause:
+ case SourceLineType.Ifndefclause:
+ /*
+ * In case we encounter an other if whilst discarding we add an other layer
+ * so as to keep the matching endif clauses.
+ *
+ * The reason why we push DiscardingDueToSuccessfullIf is to suppress any
+ * attempt any elif and else at this level.
+ */
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToSuccessfullIf);
+ break;
+
+ case SourceLineType.Elifclause:
+ switch (_conditionalInclusionsMachine.Evaluator.EvaluateExpression(expression, _conditionalInclusionsMachine.DefinesHandler))
+ {
+ case EvaluationResult.IsFalse:
+ /*
+ * in case the evaluation failed the state remains the same. We just
+ * continue discarding.
+ */
+ break;
+
+ case EvaluationResult.IsTrue:
+ /*
+ * in case the evaluation is successfull we just change the state of
+ * the current level
+ */
+ _conditionalInclusionsMachine.UpdateCurrentState(ParserState.IncludingDueToIf);
+ break;
+
+ case EvaluationResult.UnDetermined:
+ throw new SourceDiscoveryException(string.Format(CultureInfo.InvariantCulture, "Unable to evaluate expression \"{0}\"", expression));
+ }
+ break;
+
+ case SourceLineType.Elseclause:
+ _conditionalInclusionsMachine.UpdateCurrentState(ParserState.IncludingDueToIf);
+ break;
+
+ case SourceLineType.Endifclause:
+ _conditionalInclusionsMachine.FinishWithCurrentState();
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToMultilineDefine.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToMultilineDefine.cs
new file mode 100644
index 0000000..b6331fd
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToMultilineDefine.cs
@@ -0,0 +1,47 @@
+using System.Text.RegularExpressions;
+
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ ///
+ /// Class handling the state when the Conditional Inclusions Machine is discarding code due to a multiline define.
+ ///
+ /// Multiline defines are not supported by the current implementation and so any substitution text is just being discarded
+ internal class DiscardingDueToMultilineDefine : IConditionalInclusionsState
+ {
+ private static readonly Regex defineRegexMultilineContinuation = new Regex(@"\\\s*$");
+ private readonly ConditionalInclusionsMachine _conditionalInclusionsMachine;
+
+ #region Constructor
+
+ ///
+ /// Constructor
+ ///
+ /// reference to the state machine
+ internal DiscardingDueToMultilineDefine(ConditionalInclusionsMachine conditionalInclusionsMachine)
+ {
+ _conditionalInclusionsMachine = conditionalInclusionsMachine;
+ }
+
+ #endregion Constructor
+
+ ///
+ /// Called so as to make the necessary decision making whilst the state machine is discarding code during a multiline define
+ ///
+ /// source line under analysis
+ /// source code type as inspected by the Inspect function in the conditional inclusions machine
+ /// not used
+ /// not used
+ public void Process(ref string sourceLine, SourceLineType sourceLineType, string expression, string subtitutionText)
+ {
+ if (!(defineRegexMultilineContinuation.IsMatch(sourceLine)))
+ {
+ //last line of the multiline define met (which should be discarded as well)
+ _conditionalInclusionsMachine.FinishWithCurrentState();
+ }
+ {
+ //still in the multiline define
+ }
+ sourceLine = "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToSuccessfullIf.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToSuccessfullIf.cs
new file mode 100644
index 0000000..c941187
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/DiscardingDueToSuccessfullIf.cs
@@ -0,0 +1,52 @@
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ ///
+ /// Class handling the state when the Conditional Inclusions Machine is discarding code due to a previously successfull if statement
+ ///
+ internal class DiscardingDueToSuccessfullIf : IConditionalInclusionsState
+ {
+ private readonly ConditionalInclusionsMachine _conditionalInclusionsMachine;
+
+ #region Constructor
+
+ ///
+ /// Constructor
+ ///
+ /// reference to the state machine
+ internal DiscardingDueToSuccessfullIf(ConditionalInclusionsMachine conditionalInclusionsMachine)
+ {
+ _conditionalInclusionsMachine = conditionalInclusionsMachine;
+ }
+
+ #endregion Constructor
+
+ ///
+ /// Called so as to make the necessary decision making whilst the state machine is discarding code due to a previously
+ /// successful if statement (i.e. we are currently in some elif or else statement)
+ ///
+ /// source line under analysis
+ /// source code type as inspected by the Inspect function in the conditional inclusions machine
+ /// not used
+ /// not used
+ public void Process(ref string sourceLine, SourceLineType sourceLineType, string expression, string subtitutionText)
+ {
+ sourceLine = "";
+ switch (sourceLineType)
+ {
+ case SourceLineType.Ifclause:
+ case SourceLineType.Ifndefclause:
+ case SourceLineType.Ifdefclause:
+ /*
+ * In case we encounter an other if whilst discarding we add an other layer
+ * so as to keep the matching endif clauses
+ */
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToSuccessfullIf);
+ break;
+
+ case SourceLineType.Endifclause:
+ _conditionalInclusionsMachine.FinishWithCurrentState();
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/IConditionalInclusionsState.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/IConditionalInclusionsState.cs
new file mode 100644
index 0000000..9922532
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/IConditionalInclusionsState.cs
@@ -0,0 +1,18 @@
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ ///
+ /// Interface for any of the expected states of the conditional inclusions machine.
+ ///
+ /// The different states expected are:
+ /// NormalState,
+ /// IncludingDueToIf,
+ /// DiscardingDueToSuccessfullIf,
+ /// DiscardingDueToFailedIf,
+ /// DiscardingDueToMultilineDefine
+ ///
+ ///
+ internal interface IConditionalInclusionsState
+ {
+ void Process(ref string sourceLine, SourceLineType sourceLineType, string expression, string subtitutionText);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/IncludingDueToIf.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/IncludingDueToIf.cs
new file mode 100644
index 0000000..1183a69
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/IncludingDueToIf.cs
@@ -0,0 +1,119 @@
+using System.Globalization;
+
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ ///
+ /// Class handling the state of the Conditional inclusions machine when source is not being discarded to a positively evaluated conditional
+ ///
+ internal class IncludingDueToIf : IConditionalInclusionsState
+ {
+ private readonly ConditionalInclusionsMachine _conditionalInclusionsMachine;
+
+ #region Constructor
+
+ ///
+ /// Constructor
+ ///
+ /// reference to the state machine
+ internal IncludingDueToIf(ConditionalInclusionsMachine conditionalInclusionsMachine)
+ {
+ _conditionalInclusionsMachine = conditionalInclusionsMachine;
+ }
+
+ #endregion Constructor
+
+ ///
+ /// Called so as to make the necessary decision making when a positively evaluated conditional has been met
+ ///
+ /// source line under analysis that may or may not be filtered
+ /// source code type as inspected by the Inspect function in the conditional inclusions machine
+ /// expression to be evaluated in case the source Line type is a conditional based on an expression
+ /// substitution text in case of a define
+ public void Process(ref string sourceLine, SourceLineType sourceLineType, string expression, string subtitutionText)
+ {
+ switch (sourceLineType)
+ {
+ case SourceLineType.Other:
+ //Encountered source code NOT related to the conditional inclusions.
+ break;
+
+ case SourceLineType.Elifclause:
+ //since we were in successfull if inclusion and now we met an elif we have to discard anything in the elif
+ _conditionalInclusionsMachine.UpdateCurrentState(ParserState.DiscardingDueToSuccessfullIf);
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Elseclause:
+ _conditionalInclusionsMachine.UpdateCurrentState(ParserState.DiscardingDueToSuccessfullIf);
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Endifclause:
+ _conditionalInclusionsMachine.FinishWithCurrentState();
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Ifclause:
+ switch (_conditionalInclusionsMachine.Evaluator.EvaluateExpression(expression, _conditionalInclusionsMachine.DefinesHandler))
+ {
+ case EvaluationResult.IsFalse:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToFailedIf);
+ break;
+
+ case EvaluationResult.IsTrue:
+ _conditionalInclusionsMachine.AddState(ParserState.IncludingDueToIf);
+ break;
+
+ case EvaluationResult.UnDetermined:
+ throw new SourceDiscoveryException(string.Format(CultureInfo.InvariantCulture, "Unable to evaluate expression \"{0}\"", expression));
+ }
+ sourceLine = "";
+ break;
+
+ case SourceLineType.MultiLineDefineclause:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToMultilineDefine);
+ _conditionalInclusionsMachine.DefinesHandler.Define(expression, "");
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Defineclause:
+ _conditionalInclusionsMachine.DefinesHandler.Define(expression, subtitutionText);
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Undefineclause:
+ _conditionalInclusionsMachine.DefinesHandler.UnDefine(expression);
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Ifdefclause:
+ switch (_conditionalInclusionsMachine.DefinesHandler.IsDefined(expression))
+ {
+ case true:
+ _conditionalInclusionsMachine.AddState(ParserState.IncludingDueToIf);
+ break;
+
+ case false:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToFailedIf);
+ break;
+ }
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Ifndefclause:
+ switch (_conditionalInclusionsMachine.DefinesHandler.IsDefined(expression))
+ {
+ case true:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToFailedIf);
+ break;
+
+ case false:
+ _conditionalInclusionsMachine.AddState(ParserState.IncludingDueToIf);
+ break;
+ }
+ sourceLine = "";
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusions/NormalState.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusions/NormalState.cs
new file mode 100644
index 0000000..a078000
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusions/NormalState.cs
@@ -0,0 +1,115 @@
+using System.Globalization;
+
+namespace BoostTestAdapter.SourceFilter.ConditionalInclusions
+{
+ ///
+ /// Class handling the state of the Conditional Inclusions machine when the machine is no under particular state (i.e. not under the influence
+ /// of some type of conditional)
+ ///
+ internal class NormalState : IConditionalInclusionsState
+ {
+ private readonly ConditionalInclusionsMachine _conditionalInclusionsMachine;
+
+ #region Constructor
+
+ ///
+ /// Constructor
+ ///
+ /// reference to the state machine
+ internal NormalState(ConditionalInclusionsMachine conditionalInclusionsMachine)
+ {
+ _conditionalInclusionsMachine = conditionalInclusionsMachine;
+ }
+
+ #endregion Constructor
+
+ ///
+ /// Called so as to make the necessary decision making whilst the state machine is in normal sate
+ ///
+ /// source line under analysis that may or may not be filtered
+ /// source code type as inspected by the Inspect function in the conditional inclusions machine
+ /// expression to be evaluated in case the source Line type is a conditional based on an expression
+ /// substitution text in case of a define
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "endif"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "elif")]
+ public void Process(ref string sourceLine, SourceLineType sourceLineType, string expression, string subtitutionText)
+ {
+ switch (sourceLineType)
+ {
+ case SourceLineType.Ifdefclause:
+ switch (_conditionalInclusionsMachine.DefinesHandler.IsDefined(expression))
+ {
+ case true:
+ _conditionalInclusionsMachine.AddState(ParserState.IncludingDueToIf);
+ break;
+
+ case false:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToFailedIf);
+ break;
+ }
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Ifndefclause:
+ switch (_conditionalInclusionsMachine.DefinesHandler.IsDefined(expression))
+ {
+ case true:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToFailedIf);
+ break;
+
+ case false:
+ _conditionalInclusionsMachine.AddState(ParserState.IncludingDueToIf);
+ break;
+ }
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Ifclause:
+ switch (_conditionalInclusionsMachine.Evaluator.EvaluateExpression(expression, _conditionalInclusionsMachine.DefinesHandler))
+ {
+ case EvaluationResult.IsFalse:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToFailedIf);
+ break;
+
+ case EvaluationResult.IsTrue:
+ _conditionalInclusionsMachine.AddState(ParserState.IncludingDueToIf);
+ break;
+
+ case EvaluationResult.UnDetermined:
+ throw new SourceDiscoveryException(string.Format(CultureInfo.InvariantCulture, "Unable to evaluate expression \"{0}\"", expression));
+ }
+ sourceLine = "";
+ break;
+
+ case SourceLineType.MultiLineDefineclause:
+ _conditionalInclusionsMachine.AddState(ParserState.DiscardingDueToMultilineDefine);
+ _conditionalInclusionsMachine.DefinesHandler.Define(expression, "");
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Defineclause:
+ _conditionalInclusionsMachine.DefinesHandler.Define(expression, subtitutionText);
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Undefineclause:
+ _conditionalInclusionsMachine.DefinesHandler.UnDefine(expression);
+ sourceLine = "";
+ break;
+
+ case SourceLineType.Other:
+ //Encountered source code NOT related to the conditional inclusions.
+ break;
+
+ case SourceLineType.Elifclause:
+ // ReSharper disable once StringLiteralTypo
+ throw new SourceDiscoveryException("unexpected #elif found.");
+ case SourceLineType.Elseclause:
+ throw new SourceDiscoveryException("unexpected #else found.");
+ case SourceLineType.Endifclause:
+ throw new SourceDiscoveryException("unexpected #endif found.");
+ default:
+ throw new SourceDiscoveryException("unexpected conditional found. code " + sourceLineType);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ConditionalInclusionsFilter.cs b/BoostTestAdapter/SourceFilter/ConditionalInclusionsFilter.cs
new file mode 100644
index 0000000..9c4c0ec
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ConditionalInclusionsFilter.cs
@@ -0,0 +1,45 @@
+using BoostTestAdapter.SourceFilter.ConditionalInclusions;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// An ISourceFilter implementation which filters out source
+ /// code based on preprocessor conditionals.
+ ///
+ public class ConditionalInclusionsFilter : ISourceFilter
+ {
+ #region Members
+
+ private readonly ConditionalInclusionsMachine _conditionalInclusionsMachine;
+
+ #endregion Members
+
+ #region Constructor
+
+ ///
+ /// Constructor accepting an expression evaluator
+ ///
+ /// expression evaluator
+ public ConditionalInclusionsFilter(IEvaluation evaluator)
+ {
+ _conditionalInclusionsMachine = new ConditionalInclusionsMachine(evaluator);
+ }
+
+ #endregion Constructor
+
+ #region ISourceFilter
+
+ ///
+ /// Applies the filter action onto the source code
+ ///
+ /// source file information
+ /// pre-processor defines
+ public void Filter(CppSourceFile cppSourceFile, Defines definesHandler)
+ {
+ _conditionalInclusionsMachine.Apply(cppSourceFile, definesHandler);
+ }
+
+ #endregion ISourceFilter
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/CppSourceFile.cs b/BoostTestAdapter/SourceFilter/CppSourceFile.cs
new file mode 100644
index 0000000..65f874a
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/CppSourceFile.cs
@@ -0,0 +1,19 @@
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// Aggregates a C++ source file path and its content.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cpp")]
+ public class CppSourceFile
+ {
+ ///
+ /// Source code content of the C++ source file.
+ ///
+ public string SourceCode { get; set; }
+
+ ///
+ /// C++ source file path.
+ ///
+ public string FileName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ExpressionEvaluation.cs b/BoostTestAdapter/SourceFilter/ExpressionEvaluation.cs
new file mode 100644
index 0000000..4052492
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ExpressionEvaluation.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Globalization;
+using NCalc;
+using NCalc.Domain;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// Evaluates an expression using NCalc
+ ///
+ ///
+ public class ExpressionEvaluation : IEvaluation
+ {
+ #region Members
+
+ private Defines _definesHandler;
+
+ #endregion Members
+
+ #region IEvaluation
+
+ ///
+ /// Evaluates an expression
+ ///
+ /// expression to be evaluated
+ /// reference to the defines handler
+ ///
+ public EvaluationResult EvaluateExpression(string expression, Defines definesHandler)
+ {
+ this._definesHandler = definesHandler;
+
+ Expression e = new Expression(expression, EvaluateOptions.NoCache);
+ e.EvaluateParameter += EvaluateParam;
+ e.EvaluateFunction += EvaluateFunction;
+
+ EvaluationResult evaluationResult = EvaluationResult.UnDetermined;
+
+ try
+ {
+ object result = e.Evaluate();
+
+ evaluationResult = Convert.ToBoolean(result, CultureInfo.InvariantCulture) ? EvaluationResult.IsTrue : EvaluationResult.IsFalse;
+ }
+ catch
+ {
+ evaluationResult = EvaluationResult.UnDetermined;
+ }
+
+ return evaluationResult;
+ }
+
+ #endregion IEvaluation
+
+ ///
+ /// Parameter evaluator called off by function EvaluateExpression so as to elimiate the need of trying to cast the expression parameters to a more defined type.
+ /// Additionally it adds the possibility that an expression parameter itself can be of complex type.
+ ///
+ /// name of the parameter
+ /// ParameterArgs object where to store the evaluation result
+ private void EvaluateParam(string name, ParameterArgs args)
+ {
+ if (this._definesHandler.SubstitutionTokens.ContainsKey(name))
+ {
+ object substituionText;
+ this._definesHandler.SubstitutionTokens.TryGetValue(name, out substituionText);
+ Expression parameterExpression = new Expression((string)substituionText);
+ parameterExpression.EvaluateParameter += EvaluateParam;
+
+ try
+ {
+ args.Result = parameterExpression.Evaluate();
+ }
+ catch (EvaluationException)
+ {
+ //in case an exception occurred we do not set args and hence the evaluation will be undefined
+ }
+ }
+ //in case the key is not found we do not set args and hence the evaluation will be undefined
+ }
+
+ ///
+ /// NCalc custom function evaluator
+ ///
+ /// name of the function to be evaluated
+ /// FunctionArgs object from where the function arguments will be read and the function evaluation result will be stored
+ private void EvaluateFunction(string name, FunctionArgs args)
+ {
+ if (name == "defined")
+ {
+ //it is here assumed that defined always takes one argument and is of type identifier
+ Identifier identifier = (Identifier)args.Parameters[0].ParsedExpression;
+ args.Result = _definesHandler.IsDefined(identifier.Name);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/IEvaluation.cs b/BoostTestAdapter/SourceFilter/IEvaluation.cs
new file mode 100644
index 0000000..a83c6f1
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/IEvaluation.cs
@@ -0,0 +1,30 @@
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// Identifies the state of an evaluation result
+ ///
+ public enum EvaluationResult
+ {
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Un")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "UnDetermined")]
+ UnDetermined = -1,
+ IsFalse = 0,
+ IsTrue
+ }
+
+ ///
+ /// IEvaluation implementations evaluate boolean expressions.
+ ///
+ public interface IEvaluation
+ {
+ ///
+ /// Given an expression encoded as a string, parses and evaluates the encoded boolean expression.
+ ///
+ /// The string expression to evaluate
+ /// A collection of identifiers (named constants/variables) which may be referenced during evaluation
+ /// The result of the boolean expression evaluation. May return Undetermined if the statement cannot be parsed or evaluated.
+ EvaluationResult EvaluateExpression(string expression, Defines definesHandler);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/ISourceFilter.cs b/BoostTestAdapter/SourceFilter/ISourceFilter.cs
new file mode 100644
index 0000000..b8b4909
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/ISourceFilter.cs
@@ -0,0 +1,18 @@
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// An ISourceFilter filters out redundant information from source code.
+ ///
+ public interface ISourceFilter
+ {
+ ///
+ /// Filters the provided C++ source file's source code.
+ ///
+ /// The C++ source file to filter
+ /// C++ preprocessor definitions
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "cpp")]
+ void Filter(CppSourceFile cppSourceFile, Defines definesHandler);
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/MultilineCommentFilter.cs b/BoostTestAdapter/SourceFilter/MultilineCommentFilter.cs
new file mode 100644
index 0000000..1cc27b7
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/MultilineCommentFilter.cs
@@ -0,0 +1,75 @@
+using System.Text.RegularExpressions;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// Filters any multiline comments from the source code
+ ///
+ public class MultilineCommentFilter : ISourceFilter
+ {
+ //regex used to extract the line breaks off a multiLine commented section
+ private static readonly Regex lineBreakRegex = new Regex(@"(\r\n?|\n)", RegexOptions.Singleline | RegexOptions.Multiline);
+
+ // (\r\n?|\n)
+ //
+ // Options: Case insensitive; Exact spacing; Dot matches line breaks; ^$ don't match at line breaks; Numbered capture
+ //
+ // Match the regex below and capture its match into backreference number 1 «(\r\n?|\n)»
+ // Match this alternative (attempting the next alternative only if this one fails) «\r\n?»
+ // Match the carriage return character «\r»
+ // Match the line feed character «\n?»
+ // Between zero and one times, as many times as possible, giving back as needed (greedy) «?»
+ // Or match this alternative (the entire group fails if this one fails to match) «\n»
+ // Match the line feed character «\n»
+
+ private static readonly Regex multiLineCommentRegex = new Regex(@"(/\*(?:.+?)\*/)", RegexOptions.Singleline | RegexOptions.Multiline);
+ // (/\*(?:.+?)\*/)
+ //
+ // Options: Case insensitive; Exact spacing; Dot matches line breaks; ^$ don't match at line breaks; Numbered capture
+ //
+ // Match the regex below and capture its match into backreference number 1 «(/\*(?:.+?)\*/)»
+ // Match the character “/” literally «/»
+ // Match the character “*” literally «\*»
+ // Match the regular expression below «(?:.+?)»
+ // Match any single character «.+?»
+ // Between one and unlimited times, as few times as possible, expanding as needed (lazy) «+?»
+ // Match the character “*” literally «\*»
+ // Match the character “/” literally «/»
+
+ #region ISourceFilter
+
+ ///
+ /// Filters any multiline comments from the source code.
+ ///
+ /// CppSourceFile object containing the source file information
+ /// not used for this filter
+ public void Filter(CppSourceFile cppSourceFile, Defines definesHandler)
+ {
+ Utility.Code.Require(cppSourceFile, "cppSourceFile");
+ cppSourceFile.SourceCode = multiLineCommentRegex.Replace(cppSourceFile.SourceCode, ComputeMultiLineCommentReplacement);
+ }
+
+ #endregion ISourceFilter
+
+ ///
+ /// Provides the replacement string for a multiline commented section. The replacement string will just contain line breaks.
+ ///
+ /// comment section for which a replacement string needs to be provided
+ /// replacement string containing only the line breaks off the section to be replace. It is important to note that line breaks of
+ /// unix types or dos types present in the source are preserved
+ private static string ComputeMultiLineCommentReplacement(Match multiLineCommentMatch)
+ {
+ string replacementString = "";
+
+ Match matchLineBreak = lineBreakRegex.Match(multiLineCommentMatch.Groups[0].ToString());
+ while (matchLineBreak.Success)
+ {
+ replacementString = replacementString + matchLineBreak.Groups[0]; //line break types are preserved
+ matchLineBreak = matchLineBreak.NextMatch();
+ }
+
+ return replacementString;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/QuotedStringsFilter.cs b/BoostTestAdapter/SourceFilter/QuotedStringsFilter.cs
new file mode 100644
index 0000000..8ee89d1
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/QuotedStringsFilter.cs
@@ -0,0 +1,102 @@
+using System.Text.RegularExpressions;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// Filters any quoted strings from the sourcecode. This filtering process is required so as to simplyfy the subsequent filtering operations
+ ///
+ public class QuotedStringsFilter : ISourceFilter
+ {
+ private static readonly Regex quotedStringsRegex = new Regex(@"(?""", RegexOptions.Singleline);
+ /*
+ * Options: Case sensitive; Exact spacing; Dot matches line breaks; ^$ don't match at line breaks; Numbered capture
+ *
+ * Match the character “L” literally (case sensitive) «L?»
+ * Between zero and one times, as many times as possible, giving back as needed (greedy) «?»
+ * Match the character string “R"” literally (case sensitive) «R"»
+ * Match the regex below and capture its match into backreference number 1 «(.*)»
+ * Match any single character «.*»
+ * Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»
+ * Match the character “(” literally «\(»
+ * Match the regular expression below «(?:.*?)»
+ * Match any single character «.*?»
+ * Between zero and unlimited times, as few times as possible, expanding as needed (lazy) «*?»
+ * Match the character “)” literally «\)»
+ * Match the same text that was most recently matched by capturing group number 1 (case sensitive; fail if the group did not participate in the match so far) «\k<1>»
+ * Match the character “"” literally «"»
+ */
+
+ private static readonly Regex lineBreakRegex = new Regex(@"(\r\n?|\n)", RegexOptions.Singleline | RegexOptions.Multiline);
+ // (\r\n?|\n)
+ //
+ // Options: Case insensitive; Exact spacing; Dot matches line breaks; ^$ don't match at line breaks; Numbered capture
+ //
+ // Match the regex below and capture its match into backreference number 1 «(\r\n?|\n)»
+ // Match this alternative (attempting the next alternative only if this one fails) «\r\n?»
+ // Match the carriage return character «\r»
+ // Match the line feed character «\n?»
+ // Between zero and one times, as many times as possible, giving back as needed (greedy) «?»
+ // Or match this alternative (the entire group fails if this one fails to match) «\n»
+ // Match the line feed character «\n»
+
+ #region ISourceFilter
+
+ ///
+ /// Applies the quoted strings filter action on the supplied sourceCode string
+ ///
+ /// CppSourceFile object containing the source file information
+ /// not used for this filter
+ public void Filter(CppSourceFile cppSourceFile, Defines definesHandler)
+ {
+ Utility.Code.Require(cppSourceFile, "cppSourceFile");
+
+ /*
+ * It is important not to the change order of the filters.
+ */
+ cppSourceFile.SourceCode = stringLiteralsRegex.Replace(cppSourceFile.SourceCode, new MatchEvaluator(ComputeReplacement));
+
+ cppSourceFile.SourceCode = quotedStringsRegex.Replace(cppSourceFile.SourceCode, "");
+ }
+
+ #endregion ISourceFilter
+
+ ///
+ /// The string literal syntax can span over multiple lines so when generating the replacement string we need to make sure that the number of
+ /// line breaks and their types are preserved
+ ///
+ /// section of the source code to be replaced
+ /// the replacement string
+ private string ComputeReplacement(Match m)
+ {
+ string replacementString = "";
+
+ Match matchLineBreak = lineBreakRegex.Match(m.Value);
+ while (matchLineBreak.Success)
+ {
+ replacementString = replacementString + matchLineBreak.Groups[0]; //line break types are preserved
+ matchLineBreak = matchLineBreak.NextMatch();
+ }
+
+ return replacementString;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/SingleLineCommentFilter.cs b/BoostTestAdapter/SourceFilter/SingleLineCommentFilter.cs
new file mode 100644
index 0000000..7bfa3d7
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/SingleLineCommentFilter.cs
@@ -0,0 +1,29 @@
+using System.Text.RegularExpressions;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// ISourceFilter implementation. Filters single line comments.
+ ///
+ public class SingleLineCommentFilter : ISourceFilter
+ {
+ private static readonly Regex singleLineCommentRegex = new Regex(@"(?://(?:.*))", RegexOptions.IgnoreCase | RegexOptions.Multiline);
+
+ #region ISourceFilter
+
+ ///
+ /// Filters any single line comments from the source code
+ ///
+ /// CppSourceFile object containing the source file information
+ /// not used for this filter
+ public void Filter(CppSourceFile cppSourceFile, Defines definesHandler)
+ {
+ Utility.Code.Require(cppSourceFile, "cppSourceFile");
+
+ cppSourceFile.SourceCode = singleLineCommentRegex.Replace(cppSourceFile.SourceCode, "");
+ }
+
+ #endregion ISourceFilter
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/SourceFilter/SourceDiscoveryException.cs b/BoostTestAdapter/SourceFilter/SourceDiscoveryException.cs
new file mode 100644
index 0000000..0bc2d93
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/SourceDiscoveryException.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ [Serializable]
+ public class SourceDiscoveryException : Exception
+ {
+ #region Standard Exception Constructors
+
+ public SourceDiscoveryException()
+ {
+ }
+
+ public SourceDiscoveryException(string message) :
+ base(message)
+ {
+ }
+
+ public SourceDiscoveryException(string message, Exception innerException) :
+ base(message, innerException)
+ {
+ }
+
+ protected SourceDiscoveryException(SerializationInfo info, StreamingContext context) :
+ base(info, context)
+ {
+ }
+
+ #endregion Standard Exception Constructors
+ }
+}
diff --git a/BoostTestAdapter/SourceFilter/SourceFilterFactory.cs b/BoostTestAdapter/SourceFilter/SourceFilterFactory.cs
new file mode 100644
index 0000000..5f839ea
--- /dev/null
+++ b/BoostTestAdapter/SourceFilter/SourceFilterFactory.cs
@@ -0,0 +1,46 @@
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+
+namespace BoostTestAdapter.SourceFilter
+{
+ ///
+ /// Factory class responsible to create the right set of filters to apply onto a sourcefile
+ ///
+ public static class SourceFilterFactory
+ {
+ ///
+ /// Provides a collection of ISourceFilters based on the provided BoostTestAdapterSettings.
+ ///
+ /// The BoostTestAdapterSettings
+ /// An collection of ISourceFilters based on the provided settings.
+ public static ISourceFilter[] Get(BoostTestAdapterSettings settings)
+ {
+ Utility.Code.Require(settings, "settings");
+
+ if (settings.ConditionalInclusionsFilteringEnabled)
+ {
+ return new ISourceFilter[]
+ {
+ new QuotedStringsFilter(),
+ new MultilineCommentFilter(),
+ new SingleLineCommentFilter(),
+ new ConditionalInclusionsFilter(
+ new ExpressionEvaluation()
+ )
+ };
+ }
+ else
+ {
+ Logger.Warn("Conditional inclusions filtering is disabled.");
+
+ return new ISourceFilter[]
+ {
+ new QuotedStringsFilter(),
+ new MultilineCommentFilter(),
+ new SingleLineCommentFilter(),
+ //conditional inclusions filter omitted
+ };
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/Code.cs b/BoostTestAdapter/Utility/Code.cs
new file mode 100644
index 0000000..84e2c47
--- /dev/null
+++ b/BoostTestAdapter/Utility/Code.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Utility class to reduce boilerplate code
+ /// and to comply with code analysis.
+ ///
+ internal static class Code
+ {
+ ///
+ /// Asserts that an object is not null.
+ ///
+ /// The argument to test
+ /// The name of arg
+ /// Thrown if arg is null
+ public static void Require(object arg, string argName)
+ {
+ if (arg == null) throw new ArgumentNullException(argName);
+ }
+ }
+}
diff --git a/BoostTestAdapter/Utility/CommandEvaluator.cs b/BoostTestAdapter/Utility/CommandEvaluator.cs
new file mode 100644
index 0000000..c52f093
--- /dev/null
+++ b/BoostTestAdapter/Utility/CommandEvaluator.cs
@@ -0,0 +1,117 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Result structure for command evaluation.
+ ///
+ public class CommandEvaluationResult
+ {
+ public CommandEvaluationResult(string result, IEnumerable variables)
+ {
+ this.Result = result;
+ this.MappedVariables = variables;
+ }
+
+ ///
+ /// The resulting string after command evaluation.
+ ///
+ public string Result { get; private set; }
+
+ ///
+ /// A listing of all variables which were successfully mapped during command evaluation.
+ ///
+ public IEnumerable MappedVariables { get; private set; }
+ }
+
+ ///
+ /// Allows for string substution following a format similar to: A {variable} to be substituted.
+ ///
+ public class CommandEvaluator
+ {
+ #region Members
+
+ private IDictionary _variables = null;
+
+ #endregion Members
+
+ #region Constructors
+
+ public CommandEvaluator()
+ {
+ this._variables = new Dictionary();
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Specifies a value for the given variable placeholder.
+ ///
+ /// The variable placeholder label.
+ /// The respective variable value.
+ public void SetVariable(string variable, string value)
+ {
+ this[variable] = value;
+ }
+
+ ///
+ /// Removes the mapped variable.
+ ///
+ /// The variable placeholder label to remove.
+ public void ResetVariable(string variable)
+ {
+ _variables.Remove(variable);
+ }
+
+ public string this[string variable]
+ {
+ get
+ {
+ string value = null;
+ return (_variables.TryGetValue(variable, out value)) ? value : null;
+ }
+
+ set
+ {
+ _variables[variable] = value;
+ }
+ }
+
+ ///
+ /// Evaluates the given string for any and all variable placeholders.
+ ///
+ /// The string to evaluate containing variable placeholders
+ /// A CommandEvaluationResult detailing the result of the evaluation process
+ public CommandEvaluationResult Evaluate(string input)
+ {
+ string evaluation = input;
+
+ ISet mappedVariables = new HashSet();
+
+ foreach (KeyValuePair entry in this._variables)
+ {
+ KeyValuePair entryRef = entry;
+
+ Regex variable = GetVariablePlaceholderRegex(entry.Key);
+ evaluation = variable.Replace(evaluation, (match) =>
+ {
+ mappedVariables.Add(entryRef.Key);
+ return (entryRef.Value ?? "null");
+ });
+ }
+
+ return new CommandEvaluationResult(evaluation, mappedVariables);
+ }
+
+ ///
+ /// Provides a Regex which can identify the provided variable placeholder
+ ///
+ /// The variable placeholder to capture
+ /// A Regex which can identify the provided variable placeholder
+ private static Regex GetVariablePlaceholderRegex(string variable)
+ {
+ return new Regex('{' + Regex.Escape(variable) + '}');
+ }
+ }
+}
diff --git a/BoostTestAdapter/Utility/CommandLine.cs b/BoostTestAdapter/Utility/CommandLine.cs
new file mode 100644
index 0000000..2bb5302
--- /dev/null
+++ b/BoostTestAdapter/Utility/CommandLine.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// 2-tuple defining the FileName and Arguments of a command line string
+ ///
+ public class CommandLine
+ {
+ public CommandLine() :
+ this(string.Empty, string.Empty)
+ {
+ }
+
+ public CommandLine(string fileName, string arguments)
+ {
+ FileName = fileName;
+ Arguments = arguments;
+ }
+
+ public string FileName { get; set; }
+ public string Arguments { get; set; }
+
+ public override string ToString()
+ {
+ return FileName + ' ' + Arguments;
+ }
+
+ public static CommandLine FromString(string cmdLine)
+ {
+ cmdLine = (cmdLine == null) ? string.Empty : cmdLine;
+ int index = cmdLine.IndexOf(' ');
+
+ return new CommandLine
+ {
+ FileName = cmdLine.Substring(0, Math.Max(0, index)),
+ Arguments = cmdLine.Substring(index + 1)
+ };
+ }
+ }
+}
diff --git a/BoostTestAdapter/Utility/Logger.cs b/BoostTestAdapter/Utility/Logger.cs
new file mode 100644
index 0000000..94646b7
--- /dev/null
+++ b/BoostTestAdapter/Utility/Logger.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using log4net;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Logger static class that provides the utility to print messages to the Tests output window.
+ ///
+ public static class Logger
+ {
+ private static IMessageLogger _loggerInstance = null;
+
+ private static readonly ILog log4netLogger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+
+ ///
+ /// Accepts a handle to the logger instance so that subsequently, textual messages can be sent to it.
+ ///
+ /// Reference to the logger.
+ public static void Initialize(IMessageLogger logger)
+ {
+ _loggerInstance = logger; //VS sink handle
+
+ ConfigureLog4Net();
+
+ Info("Logger initialized. Logging to {0}", log4net.GlobalContext.Properties["LogFilePath"]);
+ }
+
+ ///
+ /// Configures the Log4Net module
+ ///
+ private static void ConfigureLog4Net()
+ {
+ string pathOfExecutingAssembly = GetPathOfExecutingAssembly();
+ string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
+
+ string logFilePath = Path.Combine(pathOfExecutingAssembly, (assemblyName + ".dll.log"));
+ string configFilePath = Path.Combine(pathOfExecutingAssembly, (assemblyName + ".dll.config"));
+
+ log4net.GlobalContext.Properties["pid"] = Process.GetCurrentProcess().Id;
+ log4net.GlobalContext.Properties["LogFilePath"] = logFilePath;
+ log4net.Config.XmlConfigurator.Configure(new FileInfo(configFilePath));
+ }
+
+ ///
+ /// Method that accepts a message along with the severity level so as to be printed onto the tests output tab
+ ///
+ /// level parameter used to indicate the severity of the message
+ /// text message that needs to be printed
+ /// In case the Logger is not properly initialized then any messages are simply discared without raising any exceptions
+ public static void SendMessage(TestMessageLevel testMessageLevel, string message)
+ {
+ if (_loggerInstance != null)
+ {
+ _loggerInstance.SendMessage(testMessageLevel, message);
+ }
+
+ switch (testMessageLevel)
+ {
+ case TestMessageLevel.Informational:
+ log4netLogger.Info(message);
+ break;
+
+ case TestMessageLevel.Warning:
+ log4netLogger.Warn(message);
+ break;
+
+ case TestMessageLevel.Error:
+ log4netLogger.Error(message);
+ break;
+ }
+ }
+
+ ///
+ /// Logs the provided message at the requested severity level. Uses a format, args pair to construct the log message.
+ ///
+ /// level parameter used to indicate the severity of the message
+ /// Format string
+ /// Arguments for the format string
+ public static void SendMessage(TestMessageLevel testMessageLevel, string format, params object[] args)
+ {
+ SendMessage(testMessageLevel, string.Format(CultureInfo.InvariantCulture, format, args));
+ }
+
+ ///
+ /// Logs the provided message at the 'Informational' severity level. Uses a format, args pair to construct the log message.
+ ///
+ /// Format string
+ /// Arguments for the format string
+ public static void Info(string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Informational, format, args);
+ }
+
+ ///
+ /// Logs the provided message at the 'Warning' severity level. Uses a format, args pair to construct the log message.
+ ///
+ /// Format string
+ /// Arguments for the format string
+ public static void Warn(string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Warning, format, args);
+ }
+
+ ///
+ /// Logs the provided message at the 'Error' severity level. Uses a format, args pair to construct the log message.
+ ///
+ /// Format string
+ /// Arguments for the format string
+ public static void Error(string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Error, format, args);
+ }
+
+ ///
+ /// Disposes the underlying log module
+ ///
+ public static void Shutdown()
+ {
+ if (log4netLogger != null)
+ {
+ log4netLogger.Logger.Repository.Shutdown();
+ }
+ }
+
+ ///
+ /// Gets the path of the executing assembly
+ ///
+ /// returns the path of the executing assembly
+ private static string GetPathOfExecutingAssembly()
+ {
+ Uri codeBase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
+ return Path.GetDirectoryName(codeBase.LocalPath);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/NativeMethods.cs b/BoostTestAdapter/Utility/NativeMethods.cs
new file mode 100644
index 0000000..b6f77f3
--- /dev/null
+++ b/BoostTestAdapter/Utility/NativeMethods.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+
+namespace BoostTestAdapter.Utility
+{
+ public static class NativeMethods
+ {
+ private const int S_OK = 0;
+
+ [DllImport("ole32.dll")]
+ private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
+
+ [DllImport("ole32.dll")]
+ private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
+
+ ///
+ /// Get a snapshot of the running object table (ROT).
+ ///
+ ///
+ /// A hashtable mapping the name of the object
+ /// in the ROT to the corresponding object
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "GetObject"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "GetRunningObjectTable"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "IRunningObjectTable")]
+ public static IDictionary RunningObjectTable
+ {
+ get
+ {
+ var result = new Dictionary();
+
+ var numFetched = new IntPtr();
+ IRunningObjectTable runningObjectTable;
+ IEnumMoniker monikerEnumerator;
+ var monikers = new IMoniker[1];
+
+ var runningObjectTableReturnCode = GetRunningObjectTable(0, out runningObjectTable); //This function can return the standard return values E_UNEXPECTED and S_OK.
+
+ if (runningObjectTableReturnCode != S_OK)
+ {
+ throw new ROTException("GetRunningObjectTable returned with code:" + runningObjectTableReturnCode);
+ }
+
+ runningObjectTable.EnumRunning(out monikerEnumerator);
+ monikerEnumerator.Reset();
+
+ while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
+ {
+ IBindCtx ctx;
+
+ var createBindCtxReturnCode = CreateBindCtx(0, out ctx); //This function can return the standard return values E_OUTOFMEMORY and S_OK
+
+ if (createBindCtxReturnCode != S_OK)
+ {
+ throw new ROTException("GetRunningObjectTable returned with code:" + createBindCtxReturnCode);
+ }
+
+ string runningObjectName;
+ monikers[0].GetDisplayName(ctx, null, out runningObjectName);
+
+ object runningObjectVal;
+ //usage is described at https://msdn.microsoft.com/en-us/library/windows/desktop/ms683841(v=vs.85).aspx
+ var getObjectReturnValue = runningObjectTable.GetObject(monikers[0], out runningObjectVal); //This function call can return the standard return values S_FALSE and S_OK
+
+ if (getObjectReturnValue != S_OK)
+ {
+ throw new ROTException("IRunningObjectTable::GetObject returned with code:" + getObjectReturnValue);
+ }
+
+ result[runningObjectName] = runningObjectVal;
+ }
+
+ return result;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/ProcessStartInfoEx.cs b/BoostTestAdapter/Utility/ProcessStartInfoEx.cs
new file mode 100644
index 0000000..6a00b2c
--- /dev/null
+++ b/BoostTestAdapter/Utility/ProcessStartInfoEx.cs
@@ -0,0 +1,64 @@
+using System.Collections;
+using System.Diagnostics;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Utility class containing utility methods for ProcessStartInfo
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
+ public static class ProcessStartInfoEx
+ {
+ public static ProcessStartInfo Clone(this ProcessStartInfo info)
+ {
+ Utility.Code.Require(info, "info");
+
+ ProcessStartInfo clone = new ProcessStartInfo
+ {
+ FileName = info.FileName,
+ Arguments = info.Arguments,
+ CreateNoWindow = info.CreateNoWindow,
+ WindowStyle = info.WindowStyle,
+ WorkingDirectory = info.WorkingDirectory,
+ Domain = info.Domain,
+ LoadUserProfile = info.LoadUserProfile,
+ UserName = info.UserName,
+ Password = info.Password,
+ ErrorDialog = info.ErrorDialog,
+ ErrorDialogParentHandle = info.ErrorDialogParentHandle,
+ Verb = info.Verb,
+ UseShellExecute = info.UseShellExecute,
+ RedirectStandardError = info.RedirectStandardError,
+ RedirectStandardInput = info.RedirectStandardInput,
+ RedirectStandardOutput = info.RedirectStandardOutput,
+ StandardErrorEncoding = info.StandardErrorEncoding,
+ StandardOutputEncoding = info.StandardOutputEncoding
+ };
+
+ foreach (DictionaryEntry entry in info.EnvironmentVariables)
+ {
+ string variable = entry.Key.ToString();
+
+ if (!clone.EnvironmentVariables.ContainsKey(variable))
+ {
+ clone.EnvironmentVariables.Add(variable, entry.Value.ToString());
+ }
+ }
+
+ return clone;
+ }
+
+ public static ProcessStartInfo StartThroughCmdShell(ProcessStartInfo info)
+ {
+ Utility.Code.Require(info, "info");
+
+ string fileName = info.FileName;
+ string arguments = info.Arguments;
+
+ info.FileName = "cmd.exe";
+ info.Arguments = "/S /C \"\"" + fileName + "\" " + arguments + '"';
+
+ return info;
+ }
+ }
+}
diff --git a/BoostTestAdapter/Utility/QualifiedNameBuilder.cs b/BoostTestAdapter/Utility/QualifiedNameBuilder.cs
new file mode 100644
index 0000000..9165b90
--- /dev/null
+++ b/BoostTestAdapter/Utility/QualifiedNameBuilder.cs
@@ -0,0 +1,195 @@
+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
+ {
+ #region Constants
+
+ private const string Separator = "/";
+
+ #endregion Constants
+
+ #region Constructors
+
+ ///
+ /// Default constructor.
+ ///
+ public QualifiedNameBuilder()
+ {
+ this.Path = new List();
+ }
+
+ ///
+ /// 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/ROTException.cs b/BoostTestAdapter/Utility/ROTException.cs
new file mode 100644
index 0000000..1260802
--- /dev/null
+++ b/BoostTestAdapter/Utility/ROTException.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Exception class used to raised exceptions when dealing with the Running Object Table (ROT)
+ ///
+ [Serializable]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ROT")]
+ public class ROTException : Exception
+ {
+ #region Standard Exception Constructors
+
+ public ROTException()
+ {
+
+ }
+ public ROTException(string message) :
+ base(message)
+ {
+ }
+
+ public ROTException(string message, Exception innerException) :
+ base(message, innerException)
+ {
+ }
+
+ protected ROTException(SerializationInfo info, StreamingContext context) :
+ base(info, context)
+ {
+ }
+
+ #endregion Standard Exception Constructors
+ }
+}
diff --git a/BoostTestAdapter/Utility/SourceFileInfo.cs b/BoostTestAdapter/Utility/SourceFileInfo.cs
new file mode 100644
index 0000000..b419e55
--- /dev/null
+++ b/BoostTestAdapter/Utility/SourceFileInfo.cs
@@ -0,0 +1,63 @@
+using System.IO;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Identifies a source file and a respective line of interest.
+ ///
+ public class SourceFileInfo
+ {
+ #region Constructors
+
+ ///
+ /// Constructor
+ ///
+ /// The source file path.
+ public SourceFileInfo(string file) :
+ this(file, -1)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The source file path.
+ /// The associated line number of interest or -1 if not available.
+ public SourceFileInfo(string file, int lineNumber)
+ {
+ this.File = file;
+ this.LineNumber = lineNumber;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Source file path.
+ ///
+ public string File { get; private set; }
+
+ ///
+ /// Line number within File. Defaults to -1 meaning no line number information is available.
+ ///
+ public int LineNumber { get; set; }
+
+ #endregion Properties
+
+ #region object overrides
+
+ public override string ToString()
+ {
+ string file = Path.GetFileName(this.File);
+ if ((string.IsNullOrEmpty(file)) && (this.LineNumber > -1))
+ {
+ file = "unknown location";
+ }
+
+ return file + ((this.LineNumber > -1) ? (" line " + this.LineNumber) : string.Empty);
+ }
+
+ #endregion object overrides
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/TestRun.cs b/BoostTestAdapter/Utility/TestRun.cs
new file mode 100644
index 0000000..4b8d065
--- /dev/null
+++ b/BoostTestAdapter/Utility/TestRun.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using BoostTestAdapter.Boost.Runner;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Abstraction for a Boost Test execution run.
+ /// Aggregates the necessary data structures in one convenient location.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
+ public class TestRun
+ {
+ ///
+ /// Constructor
+ ///
+ /// The IBoostTestRunner which will be used to run the tests
+ /// The Visual Studio test cases which will be executed
+ /// The command-line arguments for the IBoostTestRunner representing the Visual Studio test cases
+ /// Additional settings required for correct configuration of the test runner
+ public TestRun(IBoostTestRunner runner, IEnumerable tests, BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ this.Runner = runner;
+ this.Tests = tests;
+ this.Arguments = args;
+ this.Settings = settings;
+ }
+
+ public IBoostTestRunner Runner { get; private set; }
+
+ public string Source
+ {
+ get { return this.Runner.Source; }
+ }
+
+ public IEnumerable Tests { get; private set; }
+
+ public BoostTestRunnerCommandLineArgs Arguments { get; private set; }
+ public BoostTestRunnerSettings Settings { get; private set; }
+
+ ///
+ /// Executes the contained IBoostTestRunner with the contained arguments and settings
+ ///
+ public void Run()
+ {
+ this.Runner.Run(this.Arguments, this.Settings);
+ }
+
+ ///
+ /// Executes the contained IBoostTestRunner in debug mode with the contained arguments and settings
+ ///
+ /// The Visual Studio framework handle which allows for program attaching
+ public void Debug(IFrameworkHandle frameworkHandle)
+ {
+ this.Runner.Debug(this.Arguments, this.Settings, frameworkHandle);
+ }
+ }
+}
diff --git a/BoostTestAdapter/Utility/VisualStudio/DefaultDiscoverySink.cs b/BoostTestAdapter/Utility/VisualStudio/DefaultDiscoverySink.cs
new file mode 100644
index 0000000..31fc922
--- /dev/null
+++ b/BoostTestAdapter/Utility/VisualStudio/DefaultDiscoverySink.cs
@@ -0,0 +1,61 @@
+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.
+ ///
+ private 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;
+ }
+
+ public int GetHashCode(TestCase obj)
+ {
+ Utility.Code.Require(obj, "obj");
+
+ return obj.FullyQualifiedName.GetHashCode();
+ }
+
+ #endregion IEqualityComparer
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/VisualStudio/DefaultVisualStudioInstanceProvider.cs b/BoostTestAdapter/Utility/VisualStudio/DefaultVisualStudioInstanceProvider.cs
new file mode 100644
index 0000000..a8378c4
--- /dev/null
+++ b/BoostTestAdapter/Utility/VisualStudio/DefaultVisualStudioInstanceProvider.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Management;
+using System.Text.RegularExpressions;
+using EnvDTE80;
+
+namespace BoostTestAdapter.Utility.VisualStudio
+{
+ ///
+ /// Default implementation of an IVisualStudioInstanceProvider. Provides an IVisualStudio instance based on currently running Visual Studio IDE instances.
+ ///
+ public class DefaultVisualStudioInstanceProvider : IVisualStudioInstanceProvider
+ {
+ ///
+ /// Default prefix for VisualStudio DTE monickers
+ ///
+ private const string VisualStudioDTEPrefix = "!VisualStudio.DTE.";
+
+ ///
+ /// Regex to extract Visual Studio version Id
+ ///
+ private static readonly Regex VisualStudioDTEVersionRegex = new Regex("^" + Regex.Escape(VisualStudioDTEPrefix) + @"(\d+)", RegexOptions.IgnoreCase);
+
+ #region IVisualStudioInstanceProvider
+
+ public VisualStudioAdapter.IVisualStudio Instance
+ {
+ get
+ {
+ string processId = Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture);
+ string parentProcessId = GetParentProcessId(processId);
+
+ DTEInstance dte = GetSolutionObject(parentProcessId);
+
+ if (dte.DTE != null)
+ {
+ switch (dte.Version)
+ {
+ case "11":
+ return new VisualStudio2012Adapter.VisualStudio(dte.DTE);
+ case "12":
+ return new VisualStudio2013Adapter.VisualStudio(dte.DTE);
+ case "14":
+ return new VisualStudio2015Adapter.VisualStudio(dte.DTE);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ #endregion IVisualStudioInstanceProvider
+
+ ///
+ /// Get a table of the currently running instances of the Visual Studio .NET IDE.
+ ///
+ ///
+ /// Only return instances
+ /// that have opened a solution
+ ///
+ ///
+ /// The DTE object corresponding to the name of the IDE
+ /// in the running object table
+ ///
+ private static DTEInstance GetSolutionObject(string processId)
+ {
+ foreach (KeyValuePair entry in NativeMethods.RunningObjectTable)
+ {
+ var candidateName = entry.Key;
+
+ if (candidateName.StartsWith(VisualStudioDTEPrefix, StringComparison.OrdinalIgnoreCase) &&
+ candidateName.EndsWith(processId, StringComparison.OrdinalIgnoreCase))
+ {
+ return new DTEInstance
+ {
+ DTE = entry.Value as DTE2,
+ Version = GetVersion(candidateName)
+ };
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the process id of the parent process.
+ ///
+ /// The process id of the child process.
+ ///
+ private static string GetParentProcessId(string processId)
+ {
+ string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + processId;
+ uint parentId;
+
+ using (ManagementObjectSearcher search = new ManagementObjectSearcher("root\\CIMV2", query))
+ {
+ ManagementObjectCollection.ManagementObjectEnumerator results = search.Get().GetEnumerator();
+ results.MoveNext();
+ ManagementBaseObject queryObj = results.Current;
+ parentId = (uint)queryObj["ParentProcessId"];
+ }
+
+ return parentId.ToString(CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Given a Visual Studio DTE monicker, extracts the version Id from the name
+ ///
+ /// The Visual Studio COM object display name
+ /// The Visual Studio COM Object version or an empty string if the version cannot be located
+ private static string GetVersion(string candidateName)
+ {
+ Match match = VisualStudioDTEVersionRegex.Match(candidateName);
+ if (match.Success)
+ {
+ return match.Groups[1].Value;
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// Minor class used to aggregate version information
+ /// and the respective Visual Studio DTE2 instance.
+ ///
+ private class DTEInstance
+ {
+ public string Version { get; set; }
+
+ public DTE2 DTE { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/VisualStudio/IVisualStudioInstanceProvider.cs b/BoostTestAdapter/Utility/VisualStudio/IVisualStudioInstanceProvider.cs
new file mode 100644
index 0000000..1f89792
--- /dev/null
+++ b/BoostTestAdapter/Utility/VisualStudio/IVisualStudioInstanceProvider.cs
@@ -0,0 +1,16 @@
+using VisualStudioAdapter;
+
+namespace BoostTestAdapter.Utility.VisualStudio
+{
+ ///
+ /// Abstract factory which provides IVisualStudio instances.
+ ///
+ public interface IVisualStudioInstanceProvider
+ {
+ ///
+ /// Provides an IVisualStudio instance.
+ ///
+ /// An IVisualStudio instance or null if provisioning is not possible.
+ IVisualStudio Instance { get; }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/VisualStudio/VSTestModel.cs b/BoostTestAdapter/Utility/VisualStudio/VSTestModel.cs
new file mode 100644
index 0000000..045a881
--- /dev/null
+++ b/BoostTestAdapter/Utility/VisualStudio/VSTestModel.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using BoostTestAdapter.Boost.Results;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+using BoostTestAdapter.Boost.Test;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+using VSTestOutcome = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestOutcome;
+using VSTestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult;
+
+namespace BoostTestAdapter.Utility.VisualStudio
+{
+ ///
+ /// Static class hosting utility methods related to the
+ /// Visual Studio Test object model.
+ ///
+ public static class VSTestModel
+ {
+ ///
+ /// TestSuite trait name
+ ///
+ public static string TestSuiteTrait
+ {
+ get
+ {
+ return "TestSuite";
+ }
+ }
+
+ ///
+ /// Converts a Boost.Test.Result.TestResult model into an equivalent
+ /// Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult model.
+ ///
+ /// The Boost.Test.Result.TestResult model to convert.
+ /// The Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase model which is related to the result.
+ /// The Boost.Test.Result.TestResult model converted into its Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult counterpart.
+ public static VSTestResult AsVSTestResult(this BoostTestAdapter.Boost.Results.TestResult result, VSTestCase test)
+ {
+ Utility.Code.Require(result, "result");
+ Utility.Code.Require(test, "test");
+
+ VSTestResult vsResult = new VSTestResult(test);
+
+ vsResult.ComputerName = Environment.MachineName;
+
+ vsResult.Outcome = GetTestOutcome(result.Result);
+
+ // Boost.Test.Result.TestResult.Duration is in microseconds
+ vsResult.Duration = TimeSpan.FromMilliseconds(result.Duration / 1000);
+
+ if (result.LogEntries.Count > 0)
+ {
+ foreach (TestResultMessage message in GetTestMessages(result))
+ {
+ vsResult.Messages.Add(message);
+ }
+
+ // Test using the TestOutcome type since elements from the
+ // Boost Result type may be collapsed into a particular value
+ if (vsResult.Outcome == VSTestOutcome.Failed)
+ {
+ LogEntry error = GetLastError(result);
+
+ if (error != null)
+ {
+ vsResult.ErrorMessage = GetErrorMessage(result);
+ vsResult.ErrorStackTrace = ((error.Source == null) ? null : error.Source.ToString());
+ }
+ }
+ }
+
+ return vsResult;
+ }
+
+ ///
+ /// Converts a Boost.Test.Result.Result enumeration into an equivalent
+ /// Microsoft.VisualStudio.TestPlatform.ObjectModel.TestOutcome.
+ ///
+ /// The Boost.Test.Result.Result value to convert.
+ /// The Boost.Test.Result.Result enumeration converted into Microsoft.VisualStudio.TestPlatform.ObjectModel.TestOutcome.
+ private static VSTestOutcome GetTestOutcome(TestResultType result)
+ {
+ switch (result)
+ {
+ case TestResultType.Passed: return VSTestOutcome.Passed;
+ case TestResultType.Skipped: return VSTestOutcome.Skipped;
+
+ case TestResultType.Failed:
+ case TestResultType.Aborted:
+ default: return VSTestOutcome.Failed;
+ }
+ }
+
+ ///
+ /// Converts the log entries stored within the provided test result into equivalent
+ /// Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResultMessages
+ ///
+ /// The Boost.Test.Result.TestResult whose LogEntries are to be converted.
+ /// An enumeration of TestResultMessage equivalent to the Boost log entries stored within the provided TestResult.
+ private static IEnumerable GetTestMessages(BoostTestAdapter.Boost.Results.TestResult result)
+ {
+ foreach (LogEntry entry in result.LogEntries)
+ {
+ string category = null;
+
+ if (
+ (entry is LogEntryInfo) ||
+ (entry is LogEntryMessage) ||
+ (entry is LogEntryStandardOutputMessage)
+ )
+ {
+ category = TestResultMessage.StandardOutCategory;
+ }
+ else if (
+ (entry is LogEntryWarning) ||
+ (entry is LogEntryError) ||
+ (entry is LogEntryFatalError) ||
+ (entry is LogEntryMemoryLeak) ||
+ (entry is LogEntryException) ||
+ (entry is LogEntryStandardErrorMessage)
+ )
+ {
+ category = TestResultMessage.StandardErrorCategory;
+ }
+ else
+ {
+ // Skip unknown message types
+ continue;
+ }
+
+ yield return new TestResultMessage(category, GetTestResultMessageText(result.Unit, entry));
+ }
+ }
+
+ ///
+ /// Given a log entry and its respective test unit, retuns a string
+ /// formatted similar to the compiler_log_formatter.ipp in the Boost Test framework.
+ ///
+ /// The test unit related to this log entry
+ /// The log entry
+ /// A string message using a similar format as specified within compiler_log_formatter.ipp in the Boost Test framework
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
+ private static string GetTestResultMessageText(TestUnit unit, LogEntry entry)
+ {
+ if ((entry is LogEntryStandardOutputMessage) || (entry is LogEntryStandardErrorMessage))
+ {
+ return entry.Detail.TrimEnd() + Environment.NewLine;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ if (entry.Source != null)
+ {
+ AppendSourceInfo(entry.Source, sb);
+ }
+
+ sb.Append(entry.ToString().ToLowerInvariant()).
+ Append(" in \"").
+ Append(unit.Name).
+ Append("\"");
+
+ LogEntryMemoryLeak memoryLeak = entry as LogEntryMemoryLeak;
+ if (memoryLeak == null)
+ {
+ sb.Append(": ").Append(entry.Detail.TrimEnd());
+ }
+
+ LogEntryException exception = entry as LogEntryException;
+ if (exception != null)
+ {
+ if (exception.LastCheckpoint != null)
+ {
+ sb.Append(Environment.NewLine);
+ AppendSourceInfo(exception.LastCheckpoint, sb);
+ sb.Append("last checkpoint: ").Append(exception.CheckpointDetail);
+ }
+ }
+
+ if (memoryLeak != null)
+ {
+ if ((memoryLeak.LeakSourceFilePath != null) && (memoryLeak.LeakSourceFileName != null))
+ {
+ sb.Append("source file path leak detected at :").
+ Append(memoryLeak.LeakSourceFilePath).
+ Append(memoryLeak.LeakSourceFileName);
+ }
+
+ if (memoryLeak.LeakLineNumber != null)
+ {
+ sb.Append(", ").
+ Append("Line number: ").
+ Append(memoryLeak.LeakLineNumber);
+ }
+
+ sb.Append(", ").
+ Append("Memory allocation number: ").
+ Append(memoryLeak.LeakMemoryAllocationNumber);
+
+ sb.Append(", ").
+ Append("Leak size: ").
+ Append(memoryLeak.LeakSizeInBytes).
+ Append(" byte");
+
+ if (memoryLeak.LeakSizeInBytes > 0)
+ {
+ sb.Append('s');
+ }
+
+ sb.Append(Environment.NewLine).
+ Append(memoryLeak.LeakLeakedDataContents);
+ }
+
+ // Append NewLine so that log entries are listed one per line
+ return sb.Append(Environment.NewLine).ToString();
+ }
+
+ ///
+ /// Compresses a message so that it is suitable for the UI.
+ ///
+ /// The erroneous LogEntry whose message is to be displayed.
+ /// A compressed message suitable for UI.
+ private static string GetErrorMessage(BoostTestAdapter.Boost.Results.TestResult result)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ foreach (LogEntry error in GetErrors(result))
+ {
+ sb.Append(error.Detail).Append(Environment.NewLine);
+ }
+
+ // Remove redundant NewLine at the end
+ sb.Remove((sb.Length - Environment.NewLine.Length), Environment.NewLine.Length);
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Appends the SourceInfo instance information to the provided StringBuilder.
+ ///
+ /// The SourceInfo instance to stringify.
+ /// The StringBuilder which will host the result.
+ /// sb
+ private static StringBuilder AppendSourceInfo(SourceFileInfo info, StringBuilder sb)
+ {
+ sb.Append((string.IsNullOrEmpty(info.File) ? "unknown location" : info.File));
+ if (info.LineNumber > -1)
+ {
+ sb.Append('(').Append(info.LineNumber).Append(')');
+ }
+
+ sb.Append(": ");
+
+ return sb;
+ }
+
+ ///
+ /// Given a TestResult returns the last error type log entry.
+ ///
+ /// The TestResult which hosts the necessary log entries
+ /// The last error type log entry or null if none are available.
+ private static LogEntry GetLastError(BoostTestAdapter.Boost.Results.TestResult result)
+ {
+ // Select the last error issued within a Boost Test report
+ return GetErrors(result).LastOrDefault();
+ }
+
+ ///
+ /// Enumerates all log entries which are deemed to be an error (i.e. Warning, Error, Fatal Error and Exception).
+ ///
+ /// The TestResult which hosts the log entries.
+ /// An enumeration of error flagging log entries.
+ private static IEnumerable GetErrors(BoostTestAdapter.Boost.Results.TestResult result)
+ {
+ IEnumerable errors = result.LogEntries.Where((e) =>
+ (e is LogEntryWarning) ||
+ (e is LogEntryError) ||
+ (e is LogEntryFatalError) ||
+ (e is LogEntryException)
+ );
+
+ // Only provide a single memory leak error if the test succeeded successfully (i.e. all asserts passed)
+ return (errors.Any() ? errors : result.LogEntries.Where((e) => (e is LogEntryMemoryLeak)).Take(1));
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/Utility/XmlReaderHelper.cs b/BoostTestAdapter/Utility/XmlReaderHelper.cs
new file mode 100644
index 0000000..c0b6f71
--- /dev/null
+++ b/BoostTestAdapter/Utility/XmlReaderHelper.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Xml;
+
+namespace BoostTestAdapter.Utility
+{
+ ///
+ /// Helper functionality related to System.Xml.XmlReader
+ ///
+ internal static class XmlReaderHelper
+ {
+ ///
+ /// Default filter for Xml Elements
+ ///
+ public static readonly XmlNodeType[] ElementFilter = new XmlNodeType[] { XmlNodeType.Element, XmlNodeType.EndElement };
+
+ ///
+ /// Consumes nodes from the reader until the first ocurance of the XmlNodeType identified within types.
+ ///
+ /// The reader from which to consume Xml nodes
+ /// The XmlNodeType types of interest which will halt consumption
+ public static void ConsumeUntilFirst(this XmlReader reader, XmlNodeType[] types)
+ {
+ while (Array.IndexOf(types, reader.NodeType) < 0)
+ {
+ reader.Read();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapter/packages.config b/BoostTestAdapter/packages.config
new file mode 100644
index 0000000..2fe6773
--- /dev/null
+++ b/BoostTestAdapter/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestAdapter.dll.config b/BoostTestAdapterNunit/BoostTestAdapter.dll.config
new file mode 100644
index 0000000..400ca7b
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestAdapter.dll.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj b/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj
new file mode 100644
index 0000000..236c9f6
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestAdapterNunit.csproj
@@ -0,0 +1,179 @@
+
+
+
+
+ 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)
+
+
+
+ False
+
+
+ ..\packages\FakeItEasy.1.13.1\lib\net40\FakeItEasy.dll
+
+
+ ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll
+
+
+ $(ProgramFiles32)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll
+
+
+ False
+
+
+ ..\packages\NUnit.2.6.4\lib\nunit.framework.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {bc4b3bed-9241-4dd6-8070-a9b66dfc08c1}
+ BoostTestAdapter
+
+
+ {30ecc867-ce89-425f-b452-7a8a320f727d}
+ VisualStudio2012Adapter
+
+
+ {62347cc7-c839-413d-a7ce-598409f6f15b}
+ VisualStudioAdapter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestAdaptorNunitTestRunner.nunit b/BoostTestAdapterNunit/BoostTestAdaptorNunitTestRunner.nunit
new file mode 100644
index 0000000..e715531
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestAdaptorNunitTestRunner.nunit
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestDiscovererTest.cs b/BoostTestAdapterNunit/BoostTestDiscovererTest.cs
new file mode 100644
index 0000000..633d403
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestDiscovererTest.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using BoostTestAdapter;
+using BoostTestAdapter.Utility.VisualStudio;
+using BoostTestAdapterNunit.Fakes;
+using FakeItEasy;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ internal class BoostTestDiscovererTest
+ {
+ ///
+ /// The scope of this test is to check that if the Discoverer is given multiple project,
+ /// method DiscoverTests splits appropiately the sources of type exe and of type dll in exe sources and dll sources
+ /// and dispatches the discovery accordingly.
+ ///
+ [Test]
+ public void CorrectBoostTestDiscovererDispatching()
+ {
+ var bootTestDiscovererFactory = A.Fake();
+ var boostDllTestDiscoverer = A.Fake();
+ var boostExeTestDiscoverer = A.Fake();
+ var defaultTestContext = new DefaultTestContext();
+ var consoleMessageLogger = new ConsoleMessageLogger();
+ var defaultTestCaseDiscoverySink = new DefaultTestCaseDiscoverySink();
+
+ ITestDiscoverer boostTestDiscoverer = new BoostTestDiscoverer(bootTestDiscovererFactory);
+
+ var projects = new string[]
+ {
+ "project1" + BoostTestDiscoverer.DllExtension,
+ "project2" + BoostTestDiscoverer.ExeExtension,
+ "project3" + BoostTestDiscoverer.ExeExtension,
+ "project4" + BoostTestDiscoverer.DllExtension,
+ "project5" + BoostTestDiscoverer.DllExtension,
+ };
+
+ var dllProjectsExpected = new string[]
+ {
+ "project1" + BoostTestDiscoverer.DllExtension,
+ "project4" + BoostTestDiscoverer.DllExtension,
+ "project5" + BoostTestDiscoverer.DllExtension,
+ };
+
+ var exeProjectsExpected = new string[]
+ {
+ "project2" + BoostTestDiscoverer.ExeExtension,
+ "project3" + BoostTestDiscoverer.ExeExtension,
+ };
+
+ IEnumerable dllProjectsActual = null;
+ IEnumerable exeProjectsActual = null;
+
+ A.CallTo(() => bootTestDiscovererFactory.GetTestDiscoverer(BoostTestDiscoverer.DllExtension, A.Ignored))
+ .Returns(boostDllTestDiscoverer);
+ A.CallTo(() => bootTestDiscovererFactory.GetTestDiscoverer(BoostTestDiscoverer.ExeExtension, A.Ignored))
+ .Returns(boostExeTestDiscoverer);
+
+ A.CallTo(
+ () =>
+ boostDllTestDiscoverer.DiscoverTests(A>.Ignored, defaultTestContext,
+ consoleMessageLogger, defaultTestCaseDiscoverySink))
+ .Invokes(call => dllProjectsActual = call.GetArgument>(0));
+
+ A.CallTo(
+ () =>
+ boostExeTestDiscoverer.DiscoverTests(A>.Ignored, defaultTestContext,
+ consoleMessageLogger, defaultTestCaseDiscoverySink))
+ .Invokes(call => exeProjectsActual = call.GetArgument>(0));
+
+ boostTestDiscoverer.DiscoverTests(projects, defaultTestContext, consoleMessageLogger, defaultTestCaseDiscoverySink);
+
+ Assert.AreEqual(dllProjectsExpected, dllProjectsActual);
+ Assert.AreEqual(exeProjectsExpected, exeProjectsActual);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestExeDiscovererTest.cs b/BoostTestAdapterNunit/BoostTestExeDiscovererTest.cs
new file mode 100644
index 0000000..53a3bd2
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestExeDiscovererTest.cs
@@ -0,0 +1,412 @@
+using System.Collections.Generic;
+using System.Linq;
+using BoostTestAdapter;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using BoostTestAdapterNunit.Fakes;
+using BoostTestAdapterNunit.Utility;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using NUnit.Framework;
+using VisualStudioAdapter;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ internal class BoostTestExeDiscovererTest
+ {
+ #region Test Data
+
+ private const string Source = "test.boostd.exe";
+
+ private const string BoostUnitTestSample = "BoostUnitTestSample.cpp";
+ private const string BoostFixtureTestSuite = "BoostFixtureTestSuite.cpp";
+ private const string BoostFixtureTestCase = "BoostFixtureTestCase.cpp";
+ private const string BoostUnitTestSampleRequiringUseOfFilters = "BoostUnitTestSampleRequiringUseOfFilters.cpp";
+
+ #endregion Test Data
+
+ #region Helper Methods
+
+ ///
+ /// Applies the discovery procedure over the dummy solution
+ ///
+ /// A dummy solution from which to discover tests from
+ /// An enumeration of discovered test cases
+ private IEnumerable Discover(DummySolution solution)
+ {
+ return Discover(solution.Provider, new string[] { solution.Source });
+ }
+
+ ///
+ /// Applies the discovery procedure over the dummy solution
+ ///
+ /// A dummy solution from which to discover tests from
+ /// The IDiscoveryContext to use
+ /// An enumeration of discovered test cases
+ private IEnumerable Discover(DummySolution solution, IDiscoveryContext context)
+ {
+ return Discover(solution.Provider, new string[] { solution.Source }, context);
+ }
+
+ ///
+ /// Applies the discovery procedure over the provided sources
+ ///
+ /// An IVisualStudioInstanceProvider instance
+ /// The sources which to discover tests from
+ /// An enumeration of discovered test cases
+ private IEnumerable Discover(IVisualStudioInstanceProvider provider, IEnumerable sources)
+ {
+ return Discover(provider, sources, new DefaultTestContext());
+ }
+
+ ///
+ /// Applies the discovery procedure over the provided sources
+ ///
+ /// An IVisualStudioInstanceProvider instance
+ /// The sources which to discover tests from
+ /// The IDiscoveryContext to use
+ /// An enumeration of discovered test cases
+ private IEnumerable Discover(IVisualStudioInstanceProvider provider, IEnumerable sources, IDiscoveryContext context)
+ {
+ ConsoleMessageLogger logger = new ConsoleMessageLogger();
+ DefaultTestCaseDiscoverySink sink = new DefaultTestCaseDiscoverySink();
+
+ IBoostTestDiscoverer discoverer = new BoostTestExeDiscoverer(provider);
+ discoverer.DiscoverTests(sources, context, logger, sink);
+
+ return sink.Tests;
+ }
+
+ ///
+ /// Asserts general test details for the test with the requested fully qualified name
+ ///
+ /// The discovered test case enumeration
+ /// The fully qualified name of the test case to test
+ /// The expected test case source
+ /// The test case which has been tested
+ private VSTestCase AssertTestDetails(IEnumerable tests, QualifiedNameBuilder fqn, string source)
+ {
+ VSTestCase vsTest = tests.FirstOrDefault(test => test.FullyQualifiedName == fqn.ToString());
+
+ Assert.That(vsTest, Is.Not.Null);
+ AssertTestDetails(vsTest, fqn, source);
+
+ return vsTest;
+ }
+
+ ///
+ /// Asserts general test details for the provided test case
+ ///
+ /// The test case to test
+ /// The expected test case fully qualified name
+ /// The expected test case source
+ private void AssertTestDetails(VSTestCase vsTest, QualifiedNameBuilder fqn, string source)
+ {
+ Assert.That(vsTest, Is.Not.Null);
+ Assert.That(vsTest.DisplayName, Is.EqualTo(fqn.Peek()));
+ Assert.That(vsTest.ExecutorUri, Is.EqualTo(BoostTestExecutor.ExecutorUri));
+ Assert.That(vsTest.Source, Is.EqualTo(source));
+
+ string suite = fqn.Pop().ToString();
+ if (string.IsNullOrEmpty(suite))
+ {
+ suite = QualifiedNameBuilder.DefaultMasterTestSuiteName;
+ }
+
+ Assert.That(vsTest.Traits.Where((trait) => (trait.Name == VSTestModel.TestSuiteTrait) && (trait.Value == suite)).Count(), Is.EqualTo(1));
+ }
+
+ ///
+ /// Asserts source file details for the provided test case
+ ///
+ /// The test case to test
+ /// The expected source file qualified path
+ /// The expected line number for the test case
+ private void AssertSourceDetails(VSTestCase vsTest, string codeFilePath, int lineNumber)
+ {
+ if (vsTest.CodeFilePath != null)
+ {
+ Assert.That(vsTest.CodeFilePath, Is.EqualTo(codeFilePath));
+ }
+
+ if (lineNumber != -1)
+ {
+ Assert.That(vsTest.LineNumber, Is.EqualTo(lineNumber));
+ }
+ }
+
+ ///
+ /// Asserts test details for tests contained within the "BoostFixtureTestSuite.cpp" source file
+ ///
+ /// The discovered test case enumeration
+ /// The dummy solution which contains a project referencing "BoostFixtureTestSuite.cpp"
+ private void AssertBoostFixtureTestSuiteTestDetails(IEnumerable tests, DummySolution solution)
+ {
+ DummySourceFile codeFile = solution.SourceFileResourcePaths.First((source) => source.TempSourcePath.EndsWith(BoostFixtureTestSuite));
+ AssertBoostFixtureTestSuiteTestDetails(tests, solution.Source, codeFile.TempSourcePath);
+ }
+
+ ///
+ /// Asserts test details for tests contained within the "BoostFixtureTestSuite.cpp" source file
+ ///
+ /// The discovered test case enumeration
+ /// The source for which "BoostFixtureTestSuite.cpp" was compiled to
+ /// The fully qualified path for the on-disk version of "BoostFixtureTestSuite.cpp"
+ private void AssertBoostFixtureTestSuiteTestDetails(IEnumerable tests, string source, string codeFilePath)
+ {
+ VSTestCase test1 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("FixtureSuite1/BoostTest1"), source);
+ AssertSourceDetails(test1, codeFilePath, 30);
+
+ VSTestCase test2 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("FixtureSuite1/BoostTest2"), source);
+ AssertSourceDetails(test2, codeFilePath, 35);
+
+ VSTestCase test3 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("BoostTest3"), source);
+ AssertSourceDetails(test3, codeFilePath, 43);
+
+ VSTestCase test4 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("FixtureSuite2/Fixturetest_case1"), source);
+ AssertSourceDetails(test4, codeFilePath, 50);
+
+ VSTestCase testint = AssertTestDetails(tests, QualifiedNameBuilder.FromString("FixtureSuite2/TemplatedTest"), source);
+ AssertSourceDetails(testint, codeFilePath, 57);
+
+ VSTestCase testlong = AssertTestDetails(tests, QualifiedNameBuilder.FromString("FixtureSuite2/TemplatedTest"), source);
+ AssertSourceDetails(testlong, codeFilePath, 57);
+
+ VSTestCase testchar = AssertTestDetails(tests, QualifiedNameBuilder.FromString("FixtureSuite2/TemplatedTest"), source);
+ AssertSourceDetails(testchar, codeFilePath, 57);
+ }
+
+ ///
+ /// Asserts test details for tests contained within the "BoostUnitTestSample.cpp" source file
+ ///
+ /// The discovered test case enumeration
+ /// The dummy solution which contains a project referencing "BoostUnitTestSample.cpp"
+ private void AssertBoostUnitTestSampleTestDetails(IEnumerable tests, DummySolution solution)
+ {
+ DummySourceFile codeFile = solution.SourceFileResourcePaths.First((source) => source.TempSourcePath.EndsWith(BoostUnitTestSample));
+ AssertBoostUnitTestSampleTestDetails(tests, solution.Source, codeFile.TempSourcePath);
+ }
+
+ ///
+ /// Asserts test details for tests contained within the "BoostUnitTestSample.cpp" source file
+ ///
+ /// The discovered test case enumeration
+ /// The source for which "BoostUnitTestSample.cpp" was compiled to
+ /// The fully qualified path for the on-disk version of "BoostUnitTestSample.cpp"
+ private void AssertBoostUnitTestSampleTestDetails(IEnumerable tests, string source, string codeFilePath)
+ {
+ VSTestCase test123 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suite1/BoostUnitTest123"), source);
+ AssertSourceDetails(test123, codeFilePath, 16);
+
+ VSTestCase test1234 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suite1/BoostUnitTest1234"), source);
+ AssertSourceDetails(test1234, codeFilePath, 20);
+
+ VSTestCase test12345 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("BoostUnitTest12345"), source);
+ AssertSourceDetails(test12345, codeFilePath, 26);
+
+ VSTestCase testint = AssertTestDetails(tests, QualifiedNameBuilder.FromString("my_test"), source);
+ AssertSourceDetails(testint, codeFilePath, 33);
+
+ VSTestCase testlong = AssertTestDetails(tests, QualifiedNameBuilder.FromString("my_test"), source);
+ AssertSourceDetails(testlong, codeFilePath, 33);
+
+ VSTestCase testchar = AssertTestDetails(tests, QualifiedNameBuilder.FromString("my_test"), source);
+ AssertSourceDetails(testchar, codeFilePath, 33);
+ }
+
+ ///
+ /// Asserts test details for tests contained within the "BoostFixtureTestCase.cpp" source file
+ ///
+ /// The discovered test case enumeration
+ /// The source for which "BoostFixtureTestCase.cpp" was compiled to
+ /// The fully qualified path for the on-disk version of "BoostFixtureTestCase.cpp"
+ private void AssertBoostFixtureTestCaseTestDetails(IEnumerable tests, string source, string codeFilePath)
+ {
+ VSTestCase test1 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suit1/BoostUnitTest1"), source);
+ AssertSourceDetails(test1, codeFilePath, 19);
+
+ VSTestCase test2 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suit1/Fixturetest_case1"), source);
+ AssertSourceDetails(test2, codeFilePath, 24);
+
+ VSTestCase test3 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suit1/Fixturetest_case2"), source);
+ AssertSourceDetails(test3, codeFilePath, 30);
+
+ VSTestCase test4 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Fixturetest_case3"), source);
+ AssertSourceDetails(test4, codeFilePath, 37);
+ }
+
+ private void AssertBoostUnitTestSampleRequiringUseOfFilters(IEnumerable tests, DummySolution solution)
+ {
+ DummySourceFile codeFile = solution.SourceFileResourcePaths.First((source) => source.TempSourcePath.EndsWith(BoostUnitTestSampleRequiringUseOfFilters));
+ AssertBoostUnitTestSampleRequiringUseOfFilters(tests, solution.Source, codeFile.TempSourcePath);
+ }
+
+ private void AssertBoostUnitTestSampleRequiringUseOfFilters(IEnumerable tests, string source,
+ string codeFilePath)
+ {
+ VSTestCase test1 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suite1/BoostUnitTest123"), source);
+ AssertSourceDetails(test1, codeFilePath, 16);
+
+ VSTestCase test2 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("Suite1/BoostUnitTest1234"), source);
+ AssertSourceDetails(test2, codeFilePath, 20);
+
+ VSTestCase test3 = AssertTestDetails(tests, QualifiedNameBuilder.FromString("BoostUnitTest12345"), source);
+ AssertSourceDetails(test3, codeFilePath, 26);
+
+ VSTestCase testint = AssertTestDetails(tests, QualifiedNameBuilder.FromString("my_test"), source);
+ AssertSourceDetails(testint, codeFilePath, 40);
+
+ VSTestCase testlong = AssertTestDetails(tests, QualifiedNameBuilder.FromString("my_test"), source);
+ AssertSourceDetails(testlong, codeFilePath, 40);
+
+ VSTestCase testchar = AssertTestDetails(tests, QualifiedNameBuilder.FromString("my_test"), source);
+ AssertSourceDetails(testchar, codeFilePath, 40);
+
+ VSTestCase testConditional = AssertTestDetails(tests, QualifiedNameBuilder.FromString("BoostUnitTestConditional"), source);
+ AssertSourceDetails(testConditional, codeFilePath, 54);
+ }
+
+ #endregion Helper Methods
+
+ #region Tests
+
+ ///
+ /// Given an valid source compiled from a single test source file, the discovery process reports the found tests accordingly.
+ ///
+ /// Test aims:
+ /// - Ensure that tests can be discovered from a valid .cpp file.
+ ///
+ [Test]
+ public void DiscoverTests()
+ {
+ using (DummySolution solution = new DummySolution(Source, new string[] { BoostUnitTestSample }))
+ {
+ IEnumerable tests = Discover(solution);
+ Assert.That(tests.Count(), Is.EqualTo(6));
+
+ AssertBoostUnitTestSampleTestDetails(tests, solution);
+
+ // NOTE BoostUnitTest123 should not be available since it is commented out
+ Assert.That(tests.Any((test) => test.FullyQualifiedName == "BoostUnitTest123"), Is.False);
+ }
+ }
+
+ ///
+ /// Given an valid source compiled from multiple test source files, the discovery process reports the found tests accordingly.
+ ///
+ /// Test aims:
+ /// - Ensure that tests can be discovered from multiple valid .cpp files.
+ ///
+ [Test]
+ public void DiscoverTestsFromMultipleFiles()
+ {
+ using (DummySolution solution = new DummySolution(Source, new string[] { BoostFixtureTestSuite, BoostUnitTestSample }))
+ {
+ IEnumerable tests = Discover(solution);
+ Assert.That(tests.Count(), Is.EqualTo(13));
+
+ AssertBoostFixtureTestSuiteTestDetails(tests, solution);
+ AssertBoostUnitTestSampleTestDetails(tests, solution);
+ }
+ }
+
+ ///
+ /// Given valid sources compiled from multiple test source files, the discovery process reports the found tests accordingly.
+ ///
+ /// Test aims:
+ /// - Ensure that tests can be discovered from multiple sources.
+ /// - Ensure that tests can be discovered from 'complex' project structures which include folders and other source file types.
+ ///
+ [Test]
+ public void DiscoverTestsFromMultipleSources()
+ {
+ const string boostFixtureTestSuiteSource = "BoostFixtureTestSuite.boostd.exe";
+ const string boostUnitTestSampleSource = "BoostUnitTestSample.boostd.exe";
+
+ using (DummySourceFile boostFixtureTestSuiteCodeFile = new DummySourceFile(BoostFixtureTestSuite))
+ using (DummySourceFile boostFixtureTestCaseCodeFile = new DummySourceFile(BoostFixtureTestCase))
+ using (DummySourceFile boostUnitTestSampleSourceCodeFile = new DummySourceFile(BoostUnitTestSample))
+ {
+ IVisualStudio vs = new FakeVisualStudioInstanceBuilder().
+ Solution(
+ new FakeSolutionBuilder().
+ Name("SampleSolution").
+ Project(
+ new FakeProjectBuilder().
+ Name("FixtureSampleProject").
+ PrimaryOutput(boostFixtureTestSuiteSource).
+ Sources(
+ new List()
+ {
+ boostFixtureTestSuiteCodeFile.TempSourcePath,
+ boostFixtureTestCaseCodeFile.TempSourcePath
+ })
+ ).
+ Project(
+ new FakeProjectBuilder().
+ Name("SampleProject").
+ PrimaryOutput(boostUnitTestSampleSource).
+ Sources(
+ new List()
+ {
+ boostUnitTestSampleSourceCodeFile.TempSourcePath,
+ })
+ )
+ ).Build();
+
+ IEnumerable vsTests = Discover(new DummyVSProvider(vs), new string[] { boostFixtureTestSuiteSource, boostUnitTestSampleSource });
+ Assert.That(vsTests.Count(), Is.EqualTo(17));
+
+ AssertBoostFixtureTestSuiteTestDetails(vsTests, boostFixtureTestSuiteSource, boostFixtureTestSuiteCodeFile.TempSourcePath);
+ AssertBoostFixtureTestCaseTestDetails(vsTests, boostFixtureTestSuiteSource, boostFixtureTestCaseCodeFile.TempSourcePath);
+ AssertBoostUnitTestSampleTestDetails(vsTests, boostUnitTestSampleSource, boostUnitTestSampleSourceCodeFile.TempSourcePath);
+ }
+ }
+
+ ///
+ /// The scope of this test is to check the correct discovery of tests when the source file contains:
+ /// 1) Code that is commented (both single and multiline)
+ /// 2) Boost UTF macros that might be as part of literal strings (and that hence should be filtered out)
+ /// 3) Code that its inclusion is controlled by some type of conditional inclusion
+ ///
+ [Test]
+ public void DiscoverTestsFromSourceFileRequiringUseOfFilters()
+ {
+ using (DummySolution solution = new DummySolution(Source, new string[] { BoostUnitTestSampleRequiringUseOfFilters }))
+ {
+ IEnumerable vsTests = Discover(solution);
+ Assert.That(vsTests.Count(), Is.EqualTo(7));
+ AssertBoostUnitTestSampleRequiringUseOfFilters(vsTests, solution);
+ }
+ }
+
+ ///
+ /// Given valid sources containing conditionally compiled tests, based on the .runsettings configuration, tests may still be discovered.
+ ///
+ /// Test aims:
+ /// - Ensure that conditionally compiled tests are still discovered if configured to do so via the .runsettings configuration.
+ ///
+ [Test]
+ public void DiscoverTestsUsingRunSettings()
+ {
+ using (DummySolution solution = new DummySolution(Source, new string[] { BoostUnitTestSampleRequiringUseOfFilters }))
+ {
+ DefaultTestContext context = new DefaultTestContext();
+ context.RegisterSettingProvider(BoostTestAdapterSettings.XmlRootName, new BoostTestAdapterSettingsProvider());
+ context.LoadEmbeddedSettings("BoostTestAdapterNunit.Resources.Settings.conditionalIncludesDisabled.runsettings");
+
+ IEnumerable vsTests = Discover(solution, context);
+
+ Assert.That(vsTests.Count(), Is.EqualTo(8));
+ AssertBoostUnitTestSampleRequiringUseOfFilters(vsTests, solution);
+
+ VSTestCase testConditional = AssertTestDetails(vsTests, QualifiedNameBuilder.FromString("BoostUnitTestShouldNotAppear3"), Source);
+ AssertSourceDetails(testConditional, solution.SourceFileResourcePaths.First().TempSourcePath, 47);
+ }
+ }
+
+ #endregion Tests
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestExecutorTest.cs b/BoostTestAdapterNunit/BoostTestExecutorTest.cs
new file mode 100644
index 0000000..6a411a2
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestExecutorTest.cs
@@ -0,0 +1,689 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BoostTestAdapter;
+using BoostTestAdapter.Boost.Runner;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using BoostTestAdapterNunit.Fakes;
+using BoostTestAdapterNunit.Utility;
+using FakeItEasy;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using NUnit.Framework;
+using TimeoutException = BoostTestAdapter.Boost.Runner.TimeoutException;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+using VSTestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class BoostTestExecutorTest
+ {
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.TempDir = null;
+
+ this.RunnerFactory = new StubBoostTestRunnerFactory(this);
+
+ this.Executor = new BoostTestExecutor(
+ new StubBoostTestDiscovererFactory(this),
+ this.RunnerFactory
+ );
+
+ this.FrameworkHandle = new StubFrameworkHandle();
+
+ this.RunContext = new DefaultTestContext();
+ }
+
+ #endregion Test Setup/Teardown
+
+ #region Test Data
+
+ ///
+ /// Test case fully qualified name which should generate a timeout exception.
+ ///
+ private const string TimeoutTestCase = "Timeout";
+
+ ///
+ /// Timeout threshold.
+ ///
+ private const int Timeout = 10;
+
+ ///
+ /// Default test case fully qualified name.
+ ///
+ private string DefaultTestCase
+ {
+ get
+ {
+ return "XmlDomInterfaceTestSuite/ParseXmlFileWithoutValidationTest";
+ }
+ }
+
+ ///
+ /// Fully qualified path to the default test source.
+ ///
+ private string DefaultSource
+ {
+ get
+ {
+ // Use temporary file path in order to allow NUnit
+ // tests to execute within different environments...
+ //
+ // And to be able to access test result output
+ // since test results are placed relative to the
+ // test source file.
+ return Path.Combine(TempDir, "default");
+ }
+ }
+
+ private string _tempdir = null;
+
+ private string TempDir
+ {
+ get
+ {
+ if (_tempdir == null)
+ {
+ _tempdir = Path.GetDirectoryName(Path.GetTempPath());
+ }
+
+ return _tempdir;
+ }
+
+ set
+ {
+ this._tempdir = value;
+ }
+ }
+
+ ///
+ /// Empty test source fully qualified path.
+ ///
+ private string EmptySource
+ {
+ get
+ {
+ return "empty";
+ }
+ }
+
+ private StubBoostTestRunnerFactory RunnerFactory { get; set; }
+ private BoostTestExecutor Executor { get; set; }
+ private StubFrameworkHandle FrameworkHandle { get; set; }
+ private DefaultTestContext RunContext { get; set; }
+
+ #endregion Test Data
+
+ #region Stubs/Mocks
+
+ ///
+ /// Utility base class allowing access to the parent class.
+ ///
+ ///
+ private abstract class InnerClass
+ {
+ protected InnerClass(T parent)
+ {
+ this.Parent = parent;
+ }
+
+ ///
+ /// Parent class hosting this inner class.
+ ///
+ protected T Parent { get; private set; }
+ }
+
+ ///
+ /// Stub ITestDiscovererFactory implementation. Generates StubBoostTestDiscoverer instances.
+ ///
+ private class StubBoostTestDiscovererFactory : InnerClass, IBoostTestDiscovererFactory
+ {
+ public StubBoostTestDiscovererFactory(BoostTestExecutorTest parent) :
+ base(parent)
+ {
+ }
+
+ #region ITestDiscovererFactory
+
+ public IBoostTestDiscoverer GetTestDiscoverer(string identifier, BoostTestDiscovererFactoryOptions options)
+ {
+ return new StubBoostTestDiscoverer(this.Parent);
+ }
+
+ #endregion ITestDiscovererFactory
+ }
+
+ ///
+ /// Stub ITestDiscoverer implementation. Based on the requested source, generates fake discovery results.
+ ///
+ private class StubBoostTestDiscoverer : InnerClass, IBoostTestDiscoverer
+ {
+ public StubBoostTestDiscoverer(BoostTestExecutorTest parent) :
+ base(parent)
+ {
+ }
+
+ #region ITestDiscoverer
+
+ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
+ {
+ foreach (string source in sources)
+ {
+ foreach (VSTestCase test in this.Parent.GetTests(source))
+ {
+ discoverySink.SendTestCase(test);
+ }
+ }
+ }
+
+ #endregion ITestDiscoverer
+ }
+
+ ///
+ /// Stub IBoostTestRunnerFactory implementation. Provisions fake IBoostTestRunner instances
+ /// which simulate certain conditions based on the requested source.
+ ///
+ private class StubBoostTestRunnerFactory : InnerClass, IBoostTestRunnerFactory
+ {
+ public StubBoostTestRunnerFactory(BoostTestExecutorTest parent) :
+ base(parent)
+ {
+ this.ProvisionedRunners = new List();
+ }
+
+ public IList ProvisionedRunners { get; private set; }
+
+ ///
+ /// Reference to the latest IBoostTestRunner which was provisioned by this factory.
+ ///
+ public IBoostTestRunner LastTestRunner
+ {
+ get { return this.ProvisionedRunners.LastOrDefault(); }
+ }
+
+ #region IBoostTestRunnerFactory
+
+ public IBoostTestRunner GetRunner(string identifier, BoostTestRunnerFactoryOptions options)
+ {
+ switch (identifier)
+ {
+ case TimeoutTestCase:
+ {
+ IBoostTestRunner timeoutRunner = A.Fake();
+ A.CallTo(() => timeoutRunner.Source).Returns(identifier);
+ A.CallTo(() => timeoutRunner.Run(A._, A._)).Throws(new TimeoutException(Timeout));
+ A.CallTo(() => timeoutRunner.Debug(A._, A._, A._)).Throws(new TimeoutException(Timeout));
+
+ return Provision(timeoutRunner);
+ }
+ }
+
+ return Provision(new MockBoostTestRunner(this.Parent, identifier));
+ }
+
+ #endregion IBoostTestRunnerFactory
+
+ private IBoostTestRunner Provision(IBoostTestRunner runner)
+ {
+ this.ProvisionedRunners.Add(runner);
+ return runner;
+ }
+ }
+
+ ///
+ /// Mock IBoostTestRunner implementation.
+ ///
+ /// - Provides access to the latest call information for post-request checking.
+ /// - Allows for mocking test results by using temporary files which can be accessed by the rest of the system.
+ ///
+ private class MockBoostTestRunner : InnerClass, IBoostTestRunner
+ {
+ #region Constructors
+
+ public MockBoostTestRunner(BoostTestExecutorTest parent, string source) :
+ base(parent)
+ {
+ this.Source = source;
+ this.DebugExecution = false;
+ this.RunCount = 0;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ public bool DebugExecution { get; private set; }
+ public BoostTestRunnerCommandLineArgs Args { get; private set; }
+ public BoostTestRunnerSettings Settings { get; private set; }
+ public uint RunCount { get; private set; }
+
+ #endregion Properties
+
+ #region IBoostTestRunner
+
+ public void Debug(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings, IFrameworkHandle framework)
+ {
+ this.DebugExecution = true;
+
+ Execute(args, settings);
+ }
+
+ public void Run(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ Execute(args, settings);
+ }
+
+ public string Source { get; private set; }
+
+ #endregion IBoostTestRunner
+
+ private void Execute(BoostTestRunnerCommandLineArgs args, BoostTestRunnerSettings settings)
+ {
+ ++this.RunCount;
+
+ this.Args = args;
+ this.Settings = settings;
+
+ Assert.That(args.ReportFile, Is.Not.Null);
+ Assert.That(args.ReportFormat, Is.EqualTo(OutputFormat.XML));
+
+ Assert.That(args.LogFile, Is.Not.Null);
+ Assert.That(args.LogFormat, Is.EqualTo(OutputFormat.XML));
+
+ Assert.That(Path.GetDirectoryName(args.ReportFile), Is.EqualTo(this.Parent.TempDir));
+ Assert.That(Path.GetDirectoryName(args.LogFile), Is.EqualTo(this.Parent.TempDir));
+
+ if (!string.IsNullOrEmpty(args.StandardOutFile))
+ {
+ Assert.That(Path.GetDirectoryName(args.StandardOutFile), Is.EqualTo(this.Parent.TempDir));
+ }
+
+ // Copy the default result files to a temporary location so that they can eventually be read as a TestResultCollection
+
+ foreach (string test in args.Tests)
+ {
+ if (ShouldSkipTest(test))
+ {
+ Copy("BoostTestAdapterNunit.Resources.ReportsLogs.NoMatchingTests.sample.test.report.xml", args.ReportFile);
+ Copy("BoostTestAdapterNunit.Resources.ReportsLogs.NoMatchingTests.sample.test.log.xml", args.LogFile);
+ }
+ else
+ {
+ Copy("BoostTestAdapterNunit.Resources.ReportsLogs.PassedTest.sample.test.report.xml", args.ReportFile);
+ Copy("BoostTestAdapterNunit.Resources.ReportsLogs.PassedTest.sample.test.log.xml", args.LogFile);
+ }
+ }
+ }
+
+ private bool ShouldSkipTest(string test)
+ {
+ return test.Contains(' ') || test.Contains(',');
+ }
+
+ private void Copy(string embeddedResource, string path)
+ {
+ using (Stream inStream = TestHelper.LoadEmbeddedResource(embeddedResource))
+ using (FileStream outStream = File.Create(path))
+ {
+ inStream.CopyTo(outStream);
+ }
+ }
+ }
+
+ ///
+ /// Stub IFrameworkHandle implementation. Allows access to recorded TestResults.
+ ///
+ private class StubFrameworkHandle : ConsoleMessageLogger, IFrameworkHandle
+ {
+ public StubFrameworkHandle()
+ {
+ this.Results = new List();
+ }
+
+ public ICollection Results { get; private set; }
+
+ #region IFrameworkHandle
+
+ public bool EnableShutdownAfterTestRun
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public int LaunchProcessWithDebuggerAttached(string filePath, string workingDirectory, string arguments, IDictionary environmentVariables)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion IFrameworkHandle
+
+ #region ITestExecutionRecorder
+
+ public void RecordAttachments(IList attachmentSets)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void RecordEnd(VSTestCase testCase, TestOutcome outcome)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void RecordResult(VSTestResult testResult)
+ {
+ this.Results.Add(testResult);
+ }
+
+ public void RecordStart(VSTestCase testCase)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion ITestExecutionRecorder
+ }
+
+ #endregion Stubs/Mocks
+
+ #region Helper Methods
+
+ ///
+ /// Factory function which returns an enumeration of tests based on the provided test source
+ ///
+ /// The test source
+ /// An enumeration of tests related to the requested source
+ private IEnumerable GetTests(string source)
+ {
+ if (source == DefaultSource)
+ {
+ return GetDefaultTests();
+ }
+
+ return Enumerable.Empty();
+ }
+
+ ///
+ /// Enumerates a sample collection of tests.
+ ///
+ /// An enumeration of sample test cases
+ private IEnumerable GetDefaultTests()
+ {
+ VSTestCase test = CreateTestCase(
+ DefaultTestCase,
+ DefaultSource
+ );
+
+ return new VSTestCase[] { test };
+ }
+
+ ///
+ /// Creates a Visual Studio TestCase based on the provided information
+ ///
+ /// The fully qualified name of the test case
+ /// The test case source
+ /// A Visual Studio TestCase intended for BoostTestExecutor execution
+ 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());
+
+ return test;
+ }
+
+ ///
+ /// Asserts test properties for the DefaultTestCase
+ ///
+ ///
+ private void AssertDefaultTestResultProperties(ICollection results)
+ {
+ Assert.That(this.FrameworkHandle.Results.Count(), Is.EqualTo(1));
+
+ VSTestResult result = results.First();
+
+ Assert.That(result.ComputerName, Is.EqualTo(Environment.MachineName));
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Passed));
+
+ Assert.That(result.TestCase.Source, Is.EqualTo(DefaultSource));
+ Assert.That(result.TestCase.FullyQualifiedName, Is.EqualTo(DefaultTestCase));
+ }
+
+ #endregion Helper Methods
+
+ #region Tests
+
+ ///
+ /// Test execution via the 'Run All' command.
+ ///
+ /// Test aims:
+ /// - Ensure that all tests within a source are executed and reported.
+ ///
+ [Test]
+ public void RunTestsFromSource()
+ {
+ this.Executor.RunTests(
+ new string[] { DefaultSource },
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ AssertDefaultTestResultProperties(this.FrameworkHandle.Results);
+ }
+
+ ///
+ /// A 'Run All' command on an empty source does not fail.
+ ///
+ /// Test aims:
+ /// - Ensure that a request for running no tests operates correctly.
+ ///
+ [Test]
+ public void RunTestsFromEmptySource()
+ {
+ this.Executor.RunTests(
+ new string[] { EmptySource },
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ Assert.That(this.FrameworkHandle.Results.Count(), Is.EqualTo(0));
+ }
+
+ ///
+ /// A selection of tests can be executed.
+ ///
+ /// Test aims:
+ /// - Ensure that when users select a test selection, only those tests are executed.
+ ///
+ [Test]
+ public void RunTestSelection()
+ {
+ this.Executor.RunTests(
+ GetDefaultTests(),
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ AssertDefaultTestResultProperties(this.FrameworkHandle.Results);
+ }
+
+ ///
+ /// Debug test runs are available when selecting 'Debug Tests' for a test selection from the test adapter.
+ ///
+ /// Test aims:
+ /// - Ensure that when users select to perform a debug test run, a debug run is actually performed.
+ ///
+ [Test]
+ public void DebugTestSelection()
+ {
+ this.RunContext.IsBeingDebugged = true;
+
+ this.Executor.RunTests(
+ GetDefaultTests(),
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ MockBoostTestRunner runner = this.RunnerFactory.LastTestRunner as MockBoostTestRunner;
+
+ Assert.That(runner, Is.Not.Null);
+ Assert.That(runner.DebugExecution, Is.True);
+
+ AssertDefaultTestResultProperties(this.FrameworkHandle.Results);
+ }
+
+ ///
+ /// Given a valid .runsettings, test execution should respect the configuration.
+ ///
+ /// Test aims:
+ /// - Ensure that test execution is able to interpret valid .runsettings.
+ ///
+ [Test]
+ public void RunTestsWithTestSettings()
+ {
+ this.RunContext.RegisterSettingProvider(BoostTestAdapterSettings.XmlRootName, new BoostTestAdapterSettingsProvider());
+ this.RunContext.LoadEmbeddedSettings("BoostTestAdapterNunit.Resources.Settings.sample.runsettings");
+
+ this.Executor.RunTests(
+ GetDefaultTests(),
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ AssertDefaultTestResultProperties(this.FrameworkHandle.Results);
+
+ MockBoostTestRunner runner = this.RunnerFactory.LastTestRunner as MockBoostTestRunner;
+
+ Assert.That(runner, Is.Not.Null);
+
+ Assert.That(runner.Settings.Timeout, Is.EqualTo(600000));
+ }
+
+ ///
+ /// Given a test fully-qualified names which contain characters which are not compatible with the Boost Test
+ /// command-line, generate a 'test not found' notification to the user.
+ ///
+ /// Test aims:
+ /// - Ensure that tests which cannot be individually referenced from Boost Test command line are identified
+ /// and marked as skipped.
+ ///
+ [Test]
+ public void TestSkipEdgeCases()
+ {
+ this.Executor.RunTests(
+ new VSTestCase[] {
+ CreateTestCase("my_test", DefaultSource),
+ CreateTestCase("boost::bind(my_other_test,3)", DefaultSource)
+ },
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ Assert.That(this.FrameworkHandle.Results.Count(), Is.EqualTo(2));
+
+ foreach (VSTestResult result in this.FrameworkHandle.Results)
+ {
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Skipped));
+ }
+ }
+
+ ///
+ /// Given a long running test, test execution should stop after a pre-determined timeout threshold and inform the user accordingly.
+ ///
+ /// Test aims:
+ /// - Ensure that with proper configuration, long running tests generate a timeout test failure.
+ ///
+ [Test]
+ public void TestTimeoutException()
+ {
+ this.Executor.RunTests(
+ new VSTestCase[] { CreateTestCase("test", TimeoutTestCase) },
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ Assert.That(this.FrameworkHandle.Results.Count(), Is.EqualTo(1));
+
+ VSTestResult result = this.FrameworkHandle.Results.First();
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Failed));
+ Assert.That(result.Duration, Is.EqualTo(TimeSpan.FromMilliseconds(Timeout)));
+ Assert.That(result.ErrorMessage.ToLowerInvariant().Contains("timeout"), Is.True);
+ }
+
+ ///
+ /// Given a request for code coverage on all tests, tests should execute as usual, possibly in an optimized manner.
+ ///
+ /// Test aims:
+ /// - Ensure that it is possible to run code coverage on a tests of a particular source.
+ ///
+ [Test]
+ public void TestCodeCoverage()
+ {
+ this.RunContext.IsDataCollectionEnabled = true;
+
+ this.Executor.RunTests(
+ new string[] { DefaultSource },
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ IList runners = this.RunnerFactory.ProvisionedRunners.OfType().ToList();
+
+ // Although multiple runners (one per testcase) will be provisioned, only one type of runner (specific to DefaultSource) is used
+ Assert.That(runners.GroupBy(runner => runner.Source).Count(), Is.EqualTo(1));
+
+ // Only one runner is executed and that runner is only executed once
+ MockBoostTestRunner testRunner = runners.FirstOrDefault(runner => runner.RunCount == 1);
+
+ Assert.That(testRunner, Is.Not.Null);
+
+ // All tests are executed
+ Assert.That(testRunner.Args.Tests, Is.Empty);
+ }
+
+ ///
+ /// Given a request for code coverage on selected tests, tests should execute as usual, possibly in an optimized manner.
+ ///
+ /// Test aims:
+ /// - Ensure that it is possible to run code coverage on a selection of tests.
+ ///
+ [Test]
+ public void TestCodeCoverageSelection()
+ {
+ this.RunContext.IsDataCollectionEnabled = true;
+
+ this.Executor.RunTests(
+ new VSTestCase[] { CreateTestCase("Test1", DefaultSource), CreateTestCase("Test2", DefaultSource) },
+ this.RunContext,
+ this.FrameworkHandle
+ );
+
+ IList runners = this.RunnerFactory.ProvisionedRunners.OfType().ToList();
+
+ Assert.That(runners.GroupBy(runner => runner.Source).Count(), Is.EqualTo(1));
+
+ MockBoostTestRunner testRunner = runners.FirstOrDefault(runner => runner.RunCount == 1);
+
+ Assert.That(testRunner, Is.Not.Null);
+
+ // All selected tests are executed
+ Assert.That(testRunner.Args.Tests.Count(), Is.EqualTo(2));
+ }
+
+ #endregion Tests
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestResultTest.cs b/BoostTestAdapterNunit/BoostTestResultTest.cs
new file mode 100644
index 0000000..2f37688
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestResultTest.cs
@@ -0,0 +1,709 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using BoostTestAdapter.Boost.Results;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+using BoostTestAdapter.Utility;
+using BoostTestAdapterNunit.Utility;
+using NUnit.Framework;
+using BoostTestResult = BoostTestAdapter.Boost.Results.TestResult;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ internal class BoostTestResultTest
+ {
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.TestResultCollection = new TestResultCollection();
+ }
+
+ #endregion Test Setup/Teardown
+
+ #region Test Data
+
+ private TestResultCollection TestResultCollection { get; set; }
+
+ #endregion Test Data
+
+ #region Helper Methods
+
+ ///
+ /// Asserts BoostTestResult against the expected details
+ ///
+ /// The BoostTestResult to test
+ /// The expected parent BoostTestResult of testResult
+ /// The expected TestCase display name
+ /// The expected TestCase execution result
+ /// The expected number of passed assertions (e.g. BOOST_CHECKS)
+ /// The expected number of failed assertions (e.g. BOOST_CHECKS, BOOST_REQUIRE, BOOST_FAIL etc.)
+ /// The expected number of expected test failures
+ private void AssertReportDetails(
+ BoostTestResult testResult,
+ BoostTestResult parentTestResult,
+ string name,
+ TestResultType result,
+ uint assertionsPassed,
+ uint assertionsFailed,
+ uint expectedFailures
+ )
+ {
+ Assert.That(testResult.Unit.Name, Is.EqualTo(name));
+
+ if (parentTestResult == null)
+ {
+ Assert.That(testResult.Unit.Parent, Is.Null);
+ }
+ else
+ {
+ Assert.That(parentTestResult.Unit, Is.EqualTo(testResult.Unit.Parent));
+ }
+
+ Assert.That(testResult.Result, Is.EqualTo(result));
+
+ Assert.That(testResult.AssertionsPassed, Is.EqualTo(assertionsPassed));
+ Assert.That(testResult.AssertionsFailed, Is.EqualTo(assertionsFailed));
+ Assert.That(testResult.ExpectedFailures, Is.EqualTo(expectedFailures));
+ }
+
+ ///
+ /// Asserts BoostTestResult against the expected details
+ ///
+ /// The BoostTestResult to test
+ /// The expected parent BoostTestResult of testResult
+ /// The expected TestCase display name
+ /// The expected TestCase execution result
+ /// The expected number of passed assertions (e.g. BOOST_CHECKS)
+ /// The expected number of failed assertions (e.g. BOOST_CHECKS, BOOST_REQUIRE, BOOST_FAIL etc.)
+ /// The expected number of expected test failures
+ /// The expected number of passed child TestCases
+ /// The expected number of failed child TestCases
+ /// The expected number of skipped child TestCases
+ /// The expected number of aborted child TestCases
+ private void AssertReportDetails(
+ BoostTestResult testResult,
+ BoostTestResult parentTestResult,
+ string name,
+ TestResultType result,
+ uint assertionsPassed,
+ uint assertionsFailed,
+ uint expectedFailures,
+ uint testCasesPassed,
+ uint testCasesFailed,
+ uint testCasesSkipped,
+ uint testCasesAborted
+ )
+ {
+ AssertReportDetails(testResult, parentTestResult, name, result, assertionsPassed, assertionsFailed,
+ expectedFailures);
+
+ Assert.That(testResult.TestCasesPassed, Is.EqualTo(testCasesPassed));
+ Assert.That(testResult.TestCasesFailed, Is.EqualTo(testCasesFailed));
+ Assert.That(testResult.TestCasesSkipped, Is.EqualTo(testCasesSkipped));
+ Assert.That(testResult.TestCasesAborted, Is.EqualTo(testCasesAborted));
+ }
+
+ ///
+ /// Asserts general log details contained within a BoostTestResult
+ ///
+ /// The BoostTestResult to test
+ /// The expected test case execution duration
+ private void AssertLogDetails(BoostTestResult testResult, uint duration)
+ {
+ AssertLogDetails(testResult, duration, new List());
+ }
+
+ ///
+ /// Asserts general log details contained within a BoostTestResult
+ ///
+ /// The BoostTestResult to test
+ /// The expected test case execution duration
+ /// The expected list of log entries generated from test case execution
+ private void AssertLogDetails(BoostTestResult testResult, uint duration, IList entries)
+ {
+ Assert.That(testResult.LogEntries.Count, Is.EqualTo(expected: entries.Count));
+
+ Assert.That(testResult.Duration, Is.EqualTo(duration));
+
+ foreach (LogEntry entry in entries)
+ {
+ LogEntry found =
+ testResult.LogEntries.Where(
+ (e) =>
+ {
+ return (e.ToString() == entry.ToString()) && (e.Detail == entry.Detail);
+ })
+ .FirstOrDefault();
+ Assert.That(found, Is.Not.Null);
+
+ AssertSourceInfoDetails(found.Source, entry.Source);
+ }
+
+ var entriesMemLeaks = entries.Where((e) => e is LogEntryMemoryLeak).GetEnumerator();
+ var testResultMemleaks = testResult.LogEntries.Where((e) => e is LogEntryMemoryLeak).GetEnumerator();
+
+ while (testResultMemleaks.MoveNext() && entriesMemLeaks.MoveNext())
+ {
+ AssertMemoryLeakDetails((LogEntryMemoryLeak) testResultMemleaks.Current,
+ (LogEntryMemoryLeak) entriesMemLeaks.Current);
+ }
+ }
+
+ ///
+ /// Compares 2 LogEntryMemoryLeak for equivalence. Issues an assertion failure if leak information is not equivalent.
+ ///
+ /// The left-hand side LogEntryMemoryLeak instance
+ /// The right-hand side LogEntryMemoryLeak instance
+ private void AssertMemoryLeakDetails(LogEntryMemoryLeak lhs, LogEntryMemoryLeak rhs)
+ {
+ Assert.AreEqual(lhs.LeakLineNumber, rhs.LeakLineNumber);
+ Assert.AreEqual(lhs.LeakLeakedDataContents, rhs.LeakLeakedDataContents);
+ Assert.AreEqual(lhs.LeakMemoryAllocationNumber, rhs.LeakMemoryAllocationNumber);
+ Assert.AreEqual(lhs.LeakSizeInBytes, rhs.LeakSizeInBytes);
+ Assert.AreEqual(lhs.LeakSourceFileAndLineNumberReportingActive,
+ rhs.LeakSourceFileAndLineNumberReportingActive);
+ Assert.AreEqual(lhs.LeakSourceFileName, rhs.LeakSourceFileName);
+ Assert.AreEqual(lhs.LeakSourceFilePath, rhs.LeakSourceFilePath);
+ }
+
+ ///
+ /// Tests the provided LogEntryException's properties against the expected values
+ ///
+ /// The LogEntryException to test
+ /// The expected source file information for the exception
+ /// The expected checkpoint message
+ private void AssertLogEntryExceptionDetails(LogEntryException entry, SourceFileInfo checkpointInfo,
+ string checkpointMessage)
+ {
+ AssertSourceInfoDetails(checkpointInfo, entry.LastCheckpoint);
+ Assert.That(entry.CheckpointDetail, Is.EqualTo(checkpointMessage));
+ }
+
+ ///
+ /// Tests the provided LogEntry's general properties against the expected values
+ ///
+ /// The LogEntryException to test
+ /// The expected log entry type
+ /// The expected log message
+ /// The expected source file information for the log entry
+ private void AssertLogEntryDetails(LogEntry entry, string entryType, string message, SourceFileInfo info)
+ {
+ Assert.That(entry.ToString(), Is.EqualTo(entryType));
+ Assert.That(entry.Detail, Is.EqualTo(message));
+
+ AssertSourceInfoDetails(entry.Source, info);
+ }
+
+ ///
+ /// Compares 2 SourceFileInfo for equivalence. Issues an assertion failure if leak information is not equivalent.
+ ///
+ /// The left-hand side SourceFileInfo instance
+ /// The right-hand side SourceFileInfo instance
+ private void AssertSourceInfoDetails(SourceFileInfo lhs, SourceFileInfo rhs)
+ {
+ if (lhs == null)
+ {
+ Assert.That(rhs, Is.Null);
+ }
+ else
+ {
+ Assert.That(lhs.File, Is.EqualTo(rhs.File));
+ Assert.That(lhs.LineNumber, Is.EqualTo(rhs.LineNumber));
+ }
+ }
+
+ ///
+ /// Tests TestResultCollection for the expected contents when populated from 'PassedTest.sample.test.report.xml'
+ ///
+ /// The BoostTestResult for the sole test case present in the 'PassedTest.sample.test.report.xml' report
+ private BoostTestResult AssertPassedReportDetails()
+ {
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "CnvrtTest", TestResultType.Passed, 3, 0, 0, 1, 0, 0, 0);
+
+ BoostTestResult testSuiteResult = this.TestResultCollection["XmlDomInterfaceTestSuite"];
+ Assert.That(testSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(testSuiteResult, masterSuiteResult, "XmlDomInterfaceTestSuite", TestResultType.Passed, 3,
+ 0, 0, 1, 0, 0, 0);
+
+ BoostTestResult testCaseResult =
+ this.TestResultCollection["XmlDomInterfaceTestSuite/ParseXmlFileWithoutValidationTest"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, testSuiteResult, "ParseXmlFileWithoutValidationTest",
+ TestResultType.Passed, 3, 0, 0);
+
+ return testCaseResult;
+ }
+
+ ///
+ /// Parses the *Xml* report stream and the *Xml* log stream into the contained TestResultCollection.
+ /// Additionally, the standard output and standard error can also be parsed.
+ ///
+ /// The Xml report stream to parse.
+ /// The Xml log stream to parse.
+ /// The text standard output stream to parse.
+ /// The text standard error stream to parse.
+ private void Parse(Stream report, Stream log, Stream stdout = null, Stream stderr = null)
+ {
+ this.TestResultCollection.Parse(new IBoostTestResultOutput[]
+ {
+ ((report == null) ? null : new BoostXmlReport(report)),
+ ((log == null) ? null : new BoostXmlLog(log)),
+ ((stdout == null) ? null : new BoostStandardOutput(stdout) {FailTestOnMemoryLeak = true}),
+ ((stderr == null) ? null : new BoostStandardError(stderr))
+ });
+ }
+
+ ///
+ /// Parses the *Xml* report stream and the *Xml* log stream into the contained TestResultCollection.
+ /// Additionally, the standard output and standard error can also be parsed.
+ ///
+ /// the path of the XML file report to parse.
+ /// the path of the log file that is to be parsed
+ /// The text standard output stream to parse.
+ /// The text standard error stream to parse.
+ private void Parse(string reportFilePath, string logFilePath, Stream stdout = null, Stream stderr = null)
+ {
+ this.TestResultCollection.Parse(new IBoostTestResultOutput[]
+ {
+ ((string.IsNullOrEmpty(reportFilePath)) ? null : new BoostXmlReport(reportFilePath)),
+ ((string.IsNullOrEmpty(logFilePath)) ? null : new BoostXmlLog(logFilePath)),
+ ((stdout == null) ? null : new BoostStandardOutput(stdout) {FailTestOnMemoryLeak = true}),
+ ((stderr == null) ? null : new BoostStandardError(stderr))
+ });
+ }
+
+ #endregion Helper Methods
+
+ ///
+ /// Boost Test XML log containing special characters.
+ ///
+ /// Test aims:
+ /// - The aim of the test is to make sure that we are able to handle Boost xml logs that contain special characters. Boost UTF does
+ /// not technically generate a valid xml document so we need to add the encoding declaration ourselves (this is done in class BoostTestXMLOutput)
+ [Test]
+ public void ParseBoostReportLogContainingGermanCharacters()
+ {
+
+ string reportFilePath = TestHelper.CopyEmbeddedResourceToDirectory("BoostTestAdapterNunit.Resources.ReportsLogs.SpecialCharacters", "sample.test.report.xml", Path.GetTempPath());
+ string logFilePath = TestHelper.CopyEmbeddedResourceToDirectory("BoostTestAdapterNunit.Resources.ReportsLogs.SpecialCharacters", "sample.test.log.xml", Path.GetTempPath());
+
+ Parse(reportFilePath, logFilePath);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "MyTest", TestResultType.Failed, 0, 4, 0, 0, 1, 0, 0);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["SpecialCharactersInStringAndIdentifier"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, masterSuiteResult, "SpecialCharactersInStringAndIdentifier", TestResultType.Failed, 0, 4, 0);
+ AssertLogDetails(testCaseResult
+ , 2000
+ , new[]
+ {
+ new LogEntryError("check germanSpecialCharacterString == \"NotTheSameString\" failed [Hello my name is Rüdiger != NotTheSameString]",new SourceFileInfo("boostunittest.cpp", 8)),
+ new LogEntryError("check germanSpecialCharacterString == \"\" failed [üöä != ]",new SourceFileInfo("boostunittest.cpp", 12)),
+ new LogEntryError("check anzahlDerÄnderungen == 1 failed [2 != 1]",new SourceFileInfo("boostunittest.cpp", 17)),
+ new LogEntryError("check üöä == 1 failed [2 != 1]",new SourceFileInfo("boostunittest.cpp", 18)),
+
+ });
+
+ Assert.That(testCaseResult.LogEntries.Count, Is.EqualTo(4));
+
+ }
+
+
+ ///
+ /// Boost Test Xml report + log detailing an exception.
+ ///
+ /// Test aims:
+ /// - Boost Test results for a test case execution resulting in an exception are parsed accordingly.
+ ///
+ [Test]
+ public void ParseExceptionThrownReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.AbortedTest.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.AbortedTest.sample.test.log.xml"))
+ {
+ Parse(report, log);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "MyTest", TestResultType.Failed, 0, 1, 0, 0, 1, 0, 1);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["BoostUnitTest123"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, masterSuiteResult, "BoostUnitTest123", TestResultType.Aborted, 0, 1, 0);
+ AssertLogDetails(testCaseResult, 0, new[] { new LogEntryException("C string: some error", new SourceFileInfo("unknown location", 0)) });
+
+ Assert.That(testCaseResult.LogEntries.Count, Is.EqualTo(1));
+
+ LogEntry entry = testCaseResult.LogEntries.First();
+ AssertLogEntryDetails(entry, "Exception", "C string: some error", new SourceFileInfo("unknown location", 0));
+ AssertLogEntryExceptionDetails((LogEntryException) entry, new SourceFileInfo("boostunittestsample.cpp", 13), "Going to throw an exception");
+ }
+ }
+
+ ///
+ /// Boost Test Xml report + log detailing a test case failure due to BOOST_REQUIRE.
+ ///
+ /// Test aims:
+ /// - Boost Test results for a test case execution resulting in failure are parsed accordingly.
+ ///
+ [Test]
+ public void ParseRequireFailedReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.FailedRequireTest.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.FailedRequireTest.sample.test.log.xml"))
+ {
+ Parse(report, log);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "Test runner test", TestResultType.Failed, 0, 2, 0, 0, 1, 0, 1);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["test1"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, masterSuiteResult, "test1", TestResultType.Aborted, 0, 2, 0);
+ AssertLogDetails(testCaseResult, 0, new LogEntry[] {
+ new LogEntryError("check i == 2 failed [0 != 2]", new SourceFileInfo("test_runner_test.cpp", 26)),
+ new LogEntryFatalError("critical check i == 2 failed [0 != 2]", new SourceFileInfo("test_runner_test.cpp", 28)),
+ });
+ }
+ }
+
+ ///
+ /// Boost Test Xml report + log detailing a test case failure due to BOOST_CHECK.
+ ///
+ /// Test aims:
+ /// - Boost Test results for a test case execution resulting in failure are parsed accordingly.
+ ///
+ [Test]
+ public void ParseBoostFailReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.BoostFailTest.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.BoostFailTest.sample.test.log.xml"))
+ {
+ Parse(report, log);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "Test runner test", TestResultType.Failed, 0, 1, 0, 0, 1, 0, 1);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["test3"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, masterSuiteResult, "test3", TestResultType.Aborted, 0, 1, 0);
+
+ Assert.That(testCaseResult.LogEntries.Count, Is.EqualTo(1));
+
+ LogEntry entry = testCaseResult.LogEntries.First();
+ AssertLogDetails(testCaseResult, 1000, new LogEntry[] {
+ new LogEntryFatalError("Failure", new SourceFileInfo("test_runner_test.cpp", 93)),
+ });
+ }
+ }
+
+ ///
+ /// Boost Test Xml report + log detailing a passed test case.
+ ///
+ /// Test aims:
+ /// - Boost Test results for a positive test case execution are parsed accordingly.
+ ///
+ [Test]
+ public void ParsePassedReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.PassedTest.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.PassedTest.sample.test.log.xml"))
+ {
+
+ Parse(report, log);
+
+ BoostTestResult testCaseResult = AssertPassedReportDetails();
+ AssertLogDetails(testCaseResult, 18457000);
+ }
+ }
+
+ ///
+ /// Boost Test Xml report (only) detailing a passed test case.
+ ///
+ /// Test aims:
+ /// - Boost Test results for a positive test case execution are parsed accordingly.
+ /// - Boost Test results can be built from an Xml report without the need of the Xml log.
+ ///
+ [Test]
+ public void ParsePassedReportOnly()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.PassedTest.sample.test.report.xml"))
+ {
+ Parse(report, null);
+
+ BoostTestResult testCaseResult = AssertPassedReportDetails();
+ AssertLogDetails(testCaseResult, 0);
+ }
+ }
+
+ ///
+ /// Boost Test Xml report + log detailing a passed test case nested within test suites.
+ ///
+ /// Test aims:
+ /// - Boost Test results for a positive test case execution are parsed accordingly.
+ ///
+ [Test]
+ public void ParseNestedTestSuiteReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.NestedTestSuite.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.NestedTestSuite.sample.test.log.xml"))
+ {
+ Parse(report, log);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "Test runner test", TestResultType.Passed, 2, 0, 0, 1, 0, 0, 0);
+
+ BoostTestResult testSuiteResult = this.TestResultCollection["SampleSuite"];
+ Assert.That(testSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(testSuiteResult, masterSuiteResult, "SampleSuite", TestResultType.Passed, 2, 0, 0, 1, 0, 0, 0);
+
+ BoostTestResult nestedTestSuiteResult = this.TestResultCollection["SampleSuite/SampleNestedSuite"];
+ Assert.That(nestedTestSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(nestedTestSuiteResult, testSuiteResult, "SampleNestedSuite", TestResultType.Passed, 2, 0, 0, 1, 0, 0, 0);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["SampleSuite/SampleNestedSuite/test3"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, nestedTestSuiteResult, "test3", TestResultType.Passed, 2, 0, 0);
+ AssertLogDetails(testCaseResult, 1000, new LogEntry[] {
+ new LogEntryMessage("Message from test3", new SourceFileInfo("test_runner_test.cpp", 48)),
+ new LogEntryWarning("condition false == true is not satisfied", new SourceFileInfo("test_runner_test.cpp", 50)),
+ });
+ }
+ }
+
+ ///
+ /// Boost Test Xml report + log detailing multiple passed test cases.
+ ///
+ /// Test aims:
+ /// - Boost Test results for positive test case execution are parsed accordingly.
+ ///
+ [Test]
+ public void ParseMultipleTestResultsReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MultipleTests.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MultipleTests.sample.test.log.xml"))
+ {
+ Parse(report, log);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "CnvrtTest", TestResultType.Passed, 50, 0, 0, 4, 0, 0, 0);
+
+ BoostTestResult testSuiteResult = this.TestResultCollection["CCRootSerialiserTestSuite"];
+ Assert.That(testSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(testSuiteResult, masterSuiteResult, "CCRootSerialiserTestSuite", TestResultType.Passed, 2, 0, 0, 2, 0, 0, 0);
+
+ ICollection results = new HashSet();
+
+ {
+ BoostTestResult testCaseResult = this.TestResultCollection["CCRootSerialiserTestSuite/DeserialiseInvalidFile"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, testSuiteResult, "DeserialiseInvalidFile", TestResultType.Passed, 1, 0, 0);
+ AssertLogDetails(testCaseResult, 5000);
+
+ results.Add(testCaseResult);
+
+ BoostTestResult testCase2Result = this.TestResultCollection["CCRootSerialiserTestSuite/DeserialiseNonExistingFile"];
+ Assert.That(testCase2Result, Is.Not.Null);
+
+ AssertReportDetails(testCase2Result, testSuiteResult, "DeserialiseNonExistingFile", TestResultType.Passed, 1, 0, 0);
+ AssertLogDetails(testCase2Result, 0);
+
+ results.Add(testCase2Result);
+ }
+
+ BoostTestResult testSuite2Result = this.TestResultCollection["DET108781TestSuite"];
+ Assert.That(testSuite2Result, Is.Not.Null);
+
+ AssertReportDetails(testSuite2Result, masterSuiteResult, "DET108781TestSuite", TestResultType.Passed, 48, 0, 0, 2, 0, 0, 0);
+
+ {
+ BoostTestResult testCaseResult = this.TestResultCollection["DET108781TestSuite/ExtendedMultiplexingTest"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, testSuite2Result, "ExtendedMultiplexingTest", TestResultType.Passed, 26, 0, 0);
+ AssertLogDetails(testCaseResult, 792000);
+
+ results.Add(testCaseResult);
+
+ BoostTestResult testCase2Result = this.TestResultCollection["DET108781TestSuite/QPGroupingTest"];
+ Assert.That(testCase2Result, Is.Not.Null);
+
+ AssertReportDetails(testCase2Result, testSuite2Result, "QPGroupingTest", TestResultType.Passed, 22, 0, 0);
+ AssertLogDetails(testCase2Result, 941000);
+
+ results.Add(testCase2Result);
+ }
+
+ // Only *TestCase* results should be enumerated.
+ Assert.That(results.Intersect(this.TestResultCollection).Count(), Is.EqualTo(results.Count));
+ }
+ }
+
+ ///
+ /// Boost Test ''Xml'' report detailing that no tests could be found with the given name.
+ ///
+ /// Test aims:
+ /// - Boost Test results in a non-Xml format throw a parse exception.
+ ///
+ [Test]
+ [ExpectedException(typeof(XmlException))]
+ public void ParseNoMatchingTestsReportLog()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.NoMatchingTests.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.NoMatchingTests.sample.test.log.xml"))
+ {
+ Parse(report, log);
+ }
+ }
+
+ ///
+ /// Tests the correct memory leak discovery for an output that does not contain the source file and the line numbers available
+ ///
+ [Test]
+ public void MemoryLeakNoSourceFileAndLineNumbersAvailable()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MemoryLeakTest.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MemoryLeakTest.sample.test.log.xml"))
+ using (Stream stdout = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MemoryLeakTest.sample.NoSourceFileAndLineNumbersAvailable.test.stdout.log"))
+ {
+ Parse(report, log, stdout);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ // NOTE The values here do not match the Xml report file since the attributes:
+ // 'test_cases_passed', 'test_cases_failed', 'test_cases_skipped' and 'test_cases_aborted'
+ // are computed in case test results are modified from the Xml output
+ // (which in the case of memory leaks, passing tests may be changed to failed).
+ AssertReportDetails(masterSuiteResult, null, "MyTest", TestResultType.Passed, 0, 0, 0, 0, 1, 0, 0);
+
+ BoostTestResult testSuiteResult = this.TestResultCollection["LeakingSuite"];
+ Assert.That(testSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(testSuiteResult, masterSuiteResult, "LeakingSuite", TestResultType.Passed, 0, 0, 0, 0, 1, 0, 0);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["LeakingSuite/LeakingTestCase"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, testSuiteResult, "LeakingTestCase", TestResultType.Failed, 0, 0, 0);
+ AssertLogDetails(testCaseResult, 0, new LogEntry[] {
+ new LogEntryMessage("Test case LeakingTestCase did not check any assertions", new SourceFileInfo("./boost/test/impl/results_collector.ipp", 220)),
+ new LogEntryMemoryLeak(null, null, null, 8, 837, " Data: < > 98 D5 D9 00 00 00 00 00 \n"),
+ new LogEntryMemoryLeak(null, null, null, 32, 836, " Data: <`- Leak... > 60 2D BD 00 4C 65 61 6B 2E 2E 2E 00 CD CD CD CD ")
+ });
+ }
+ }
+
+ ///
+ /// Tests the correct leak discovery for an output that does contain the source file and the line numbers available
+ ///
+ [Test]
+ public void MemoryLeakSourceFileAndLineNumbersAvailable()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MemoryLeakTest.sample.test.report.xml"))
+ using (Stream log = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MemoryLeakTest.sample.test.log.xml"))
+ using (Stream stdout = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.MemoryLeakTest.sample.SourceFileAndLineNumbersAvailable.test.stdout.log"))
+ {
+ Assert.IsNotNull(report);
+ Assert.IsNotNull(log);
+ Assert.IsNotNull(stdout);
+
+ Parse(report, log, stdout);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ // NOTE The values here do not match the Xml report file since the attributes:
+ // 'test_cases_passed', 'test_cases_failed', 'test_cases_skipped' and 'test_cases_aborted'
+ // are computed in case test results are modified from the Xml output
+ // (which in the case of memory leaks, passing tests may be changed to failed).
+ AssertReportDetails(masterSuiteResult, null, "MyTest", TestResultType.Passed, 0, 0, 0, 0, 1, 0, 0);
+
+ BoostTestResult testSuiteResult = this.TestResultCollection["LeakingSuite"];
+ Assert.That(testSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(testSuiteResult, masterSuiteResult, "LeakingSuite", TestResultType.Passed, 0, 0, 0, 0, 1, 0, 0);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["LeakingSuite/LeakingTestCase"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, testSuiteResult, "LeakingTestCase", TestResultType.Failed, 0, 0, 0);
+ AssertLogDetails(testCaseResult, 0, new LogEntry[] {
+ new LogEntryMessage("Test case LeakingTestCase did not check any assertions", new SourceFileInfo("./boost/test/impl/results_collector.ipp", 220)),
+ new LogEntryMemoryLeak(@"d:\hwa\dev\svn\boostunittestadapterdev\branches\tempbugfixing\sample\boostunittest\boostunittest2\",@"adapterbugs.cpp", 60, 4, 935, " Data: < > CD CD CD CD \n")
+ {
+ LeakSourceFileAndLineNumberReportingActive = true
+ },
+ new LogEntryMemoryLeak(@"d:\hwa\dev\svn\boostunittestadapterdev\branches\tempbugfixing\sample\boostunittest\boostunittest2\",@"adapterbugs.cpp", 57, 4, 934, " Data: < > F5 01 00 00 ")
+ {
+ LeakSourceFileAndLineNumberReportingActive = true
+ }
+ });
+ }
+ }
+
+ ///
+ /// Boost Test Xml report + standard out and standard error detailing positive test execution.
+ ///
+ /// Test aims:
+ /// - Boost Test results in can parse and record standard output and standard error text output.
+ ///
+ [Test]
+ public void ParseOutputTestReportLogStdOutStdErr()
+ {
+ using (Stream report = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.OutputTest.sample.test.report.xml"))
+ using (Stream stdout = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.OutputTest.sample.test.stdout.log"))
+ using (Stream stderr = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.ReportsLogs.OutputTest.sample.test.stderr.log"))
+ {
+ Parse(report, null, stdout, stderr);
+
+ BoostTestResult masterSuiteResult = this.TestResultCollection[string.Empty];
+ Assert.That(masterSuiteResult, Is.Not.Null);
+
+ AssertReportDetails(masterSuiteResult, null, "MyTest", TestResultType.Passed, 1, 0, 0, 1, 0, 0, 0);
+
+ BoostTestResult testCaseResult = this.TestResultCollection["MyTestCase"];
+ Assert.That(testCaseResult, Is.Not.Null);
+
+ AssertReportDetails(testCaseResult, masterSuiteResult, "MyTestCase", TestResultType.Passed, 1, 0, 0);
+ AssertLogDetails(testCaseResult, 0, new LogEntry[] {
+ new LogEntryStandardOutputMessage("Hello Standard Output World"),
+ new LogEntryStandardErrorMessage("Hello Standard Error World")
+ });
+ }
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/BoostTestRunnerCommandLineArgsTest.cs b/BoostTestAdapterNunit/BoostTestRunnerCommandLineArgsTest.cs
new file mode 100644
index 0000000..4d18b94
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestRunnerCommandLineArgsTest.cs
@@ -0,0 +1,56 @@
+using BoostTestAdapter.Boost.Runner;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class BoostTestRunnerCommandLineArgsTest
+ {
+ #region Tests
+
+ ///
+ /// Default configuration of a command-line arguments structure.
+ ///
+ /// Test aims:
+ /// - Ensure that a default command-line arguments structure does not generate a command-line string.
+ ///
+ [Test]
+ public void DefaultCommandLineArgs()
+ {
+ BoostTestRunnerCommandLineArgs args = new BoostTestRunnerCommandLineArgs();
+ Assert.That(args.ToString(), Is.Empty);
+ }
+
+ ///
+ /// Non-default configuration of a command-line arguments structure.
+ ///
+ /// Test aims:
+ /// - Ensure that all non-default command-line arguments are listed accordingly.
+ ///
+ [Test]
+ public void SampleCommandLineArgs()
+ {
+ BoostTestRunnerCommandLineArgs args = new BoostTestRunnerCommandLineArgs();
+
+ args.Tests.Add("test");
+ args.Tests.Add("suite/*");
+
+ args.LogFormat = OutputFormat.XML;
+ args.LogLevel = LogLevel.TestSuite;
+ args.LogFile = "log.xml";
+
+ args.ReportFormat = OutputFormat.XML;
+ args.ReportLevel = ReportLevel.Detailed;
+ args.ReportFile = "report.xml";
+
+ args.DetectMemoryLeaks = 0;
+
+ args.StandardOutFile = "stdout.log";
+ args.StandardErrorFile = "stderr.log";
+
+ Assert.That(args.ToString(), Is.EqualTo("\"--run_test=test,suite/*\" \"--log_format=xml\" \"--log_level=test_suite\" \"--log_sink=log.xml\" \"--report_format=xml\" \"--report_level=detailed\" \"--report_sink=report.xml\" \"--detect_memory_leak=0\" > \"stdout.log\" 2> \"stderr.log\""));
+ }
+
+ #endregion Tests
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/BoostTestSettingsTest.cs b/BoostTestAdapterNunit/BoostTestSettingsTest.cs
new file mode 100644
index 0000000..94aa2dd
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestSettingsTest.cs
@@ -0,0 +1,228 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using BoostTestAdapter.Boost.Runner;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+using BoostTestAdapterNunit.Fakes;
+using BoostTestAdapterNunit.Utility;
+using BoostTestAdapterNunit.Utility.Xml;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class BoostTestSettingsTest
+ {
+ #region Helper Methods
+
+ ///
+ /// Deserialises a BoostTestAdapterSettings instance from the provided embedded resource.
+ ///
+ /// Fully qualified path to a .runsettings Xml embedded resource
+ /// The deserialised BoostTestAdapterSettings
+ private BoostTestAdapterSettings Parse(string path)
+ {
+ BoostTestAdapterSettingsProvider provider = new BoostTestAdapterSettingsProvider();
+
+ DefaultTestContext context = new DefaultTestContext();
+ context.RegisterSettingProvider(BoostTestAdapterSettings.XmlRootName, provider);
+ context.LoadEmbeddedSettings(path);
+
+ return provider.Settings;
+ }
+
+ ///
+ /// Tests the contents of a BoostTestAdapterSettings instance, making sure they comply to the default expected values.
+ ///
+ /// The BoostTestAdapterSettings instance to test
+ private void AssertDefaultSettings(BoostTestAdapterSettings settings)
+ {
+ Assert.That(settings.TimeoutMilliseconds, Is.EqualTo(-1));
+ Assert.That(settings.FailTestOnMemoryLeak, Is.False);
+ Assert.That(settings.ConditionalInclusionsFilteringEnabled, Is.True);
+ Assert.That(settings.LogLevel, Is.EqualTo(LogLevel.TestSuite));
+ Assert.That(settings.ExternalTestRunner, Is.Null);
+ }
+
+ ///
+ /// Compares the serialized content of the settings structure against an Xml embedded resource string.
+ ///
+ /// The settings structure whose serialization is to be compared
+ /// The path to an embedded resource which contains the serialized Xml content to compare against
+ private void Compare(BoostTestAdapterSettings settings, string resource)
+ {
+ XmlElement element = settings.ToXml();
+
+ using (Stream stream = TestHelper.LoadEmbeddedResource(resource))
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.Load(stream);
+
+ XmlNode root = doc.DocumentElement.SelectSingleNode("/RunSettings/BoostTest");
+
+ XmlComparer comparer = new XmlComparer();
+ comparer.CompareXML(element, root, XmlNodeTypeFilter.DefaultFilter);
+ }
+ }
+
+ #endregion Helper Methods
+
+ #region Tests
+
+ ///
+ /// Default BoostTestAdapterSettings instance.
+ ///
+ /// Test aims:
+ /// - Ensure that the default state of a BoostTestAdapterSettings complies with requirements.
+ ///
+ [Test]
+ public void DefaultSettings()
+ {
+ BoostTestAdapterSettings settings = new BoostTestAdapterSettings();
+ AssertDefaultSettings(settings);
+ }
+
+ ///
+ /// Deserializing a BoostTestAdapterSettings instance from a .runsettings Xml document.
+ ///
+ /// Test aims:
+ /// - Ensure that BoostTestAdapterSettings can be deserialised from .runsettings files.
+ ///
+ [Test]
+ public void ParseSampleSettings()
+ {
+ BoostTestAdapterSettings settings = Parse("BoostTestAdapterNunit.Resources.Settings.sample.runsettings");
+
+ Assert.That(settings.TimeoutMilliseconds, Is.EqualTo(600000));
+ Assert.That(settings.FailTestOnMemoryLeak, Is.True);
+
+ Assert.That(settings.ExternalTestRunner, Is.Not.Null);
+ Assert.That(settings.ExternalTestRunner.ExtensionType, Is.EqualTo(".dll"));
+ Assert.That(settings.ExternalTestRunner.DiscoveryMethodType, Is.EqualTo(DiscoveryMethodType.DiscoveryCommandLine));
+ Assert.That(settings.ExternalTestRunner.DiscoveryCommandLine.ToString(), Is.EqualTo("C:\\ExternalTestRunner.exe --test \"{source}\" --list-debug \"{out}\""));
+ Assert.That(settings.ExternalTestRunner.ExecutionCommandLine.ToString(), Is.EqualTo("C:\\ExternalTestRunner.exe --test \"{source}\""));
+ }
+
+ ///
+ /// Deserializing a semantically empty .runsettings Xml document provides a default BoostTestAdapterSettings instance.
+ ///
+ /// Test aims:
+ /// - Ensure that empty .runsettings files do not hinder BoostTestAdapterSettings deserialization.
+ ///
+ [Test]
+ public void ParseEmptySettings()
+ {
+ BoostTestAdapterSettings settings = Parse("BoostTestAdapterNunit.Resources.Settings.empty.runsettings");
+ AssertDefaultSettings(settings);
+ }
+
+ ///
+ /// BoostTestAdapterSettings can be deserialised from a simple .runsettings Xml document.
+ ///
+ /// Test aims:
+ /// - Ensure that BoostTestAdapterSettings instances can be deserialised from a .runsettings file.
+ ///
+ [Test]
+ public void ParseDefaultSettings()
+ {
+ BoostTestAdapterSettings settings = Parse("BoostTestAdapterNunit.Resources.Settings.default.runsettings");
+ AssertDefaultSettings(settings);
+ }
+
+ ///
+ /// BoostTestAdapterSettings can be deserialised from a 'complex' .runsettings Xml document.
+ ///
+ /// Test aims:
+ /// - Ensure that BoostTestAdapterSettings instances can be deserialised from a relatively 'complex' .runsettings file.
+ ///
+ [Test]
+ public void ParseComplexSettings()
+ {
+ BoostTestAdapterSettings settings = Parse("BoostTestAdapterNunit.Resources.Settings.sample.2.runsettings");
+ Assert.That(settings.TimeoutMilliseconds, Is.EqualTo(100));
+ }
+
+ ///
+ /// BoostTestAdapterSettings can be serialized as an Xml fragment.
+ ///
+ /// Test aims:
+ /// - Ensure that BoostTestAdapterSettings instances can be serialized to an Xml fragment.
+ /// - Ensure that the generated Xml fragment contains all of the necessary information to deserialize for later use.
+ ///
+ [Test]
+ public void SerializeSettings()
+ {
+ BoostTestAdapterSettings settings = new BoostTestAdapterSettings();
+
+ settings.TimeoutMilliseconds = 600000;
+ settings.FailTestOnMemoryLeak = true;
+
+ settings.ExternalTestRunner = new ExternalBoostTestRunnerSettings()
+ {
+ ExtensionType = ".dll",
+ DiscoveryMethodType = DiscoveryMethodType.DiscoveryCommandLine,
+ DiscoveryCommandLine = new CommandLine("C:\\ExternalTestRunner.exe", "--test \"{source}\" --list-debug \"{out}\""),
+ ExecutionCommandLine = new CommandLine("C:\\ExternalTestRunner.exe", "--test \"{source}\"")
+ };
+
+ Compare(settings, "BoostTestAdapterNunit.Resources.Settings.sample.runsettings");
+ }
+
+ ///
+ /// Ensure that external test runner settings utilitsing a file map can be deserialised from a .runsettings Xml document.
+ ///
+ /// Test aims:
+ /// - Ensure that external test runner settings utilitsing a file map can be deserialised from a .runsettings Xml document.
+ ///
+ [Test]
+ public void ParseExternalTestRunnerDiscoveryMapSettings()
+ {
+ BoostTestAdapterSettings settings = Parse("BoostTestAdapterNunit.Resources.Settings.externalTestRunner.runsettings");
+
+ Assert.That(settings.TimeoutMilliseconds, Is.EqualTo(-1));
+ Assert.That(settings.FailTestOnMemoryLeak, Is.False);
+ Assert.That(settings.ConditionalInclusionsFilteringEnabled, Is.True);
+ Assert.That(settings.LogLevel, Is.EqualTo(LogLevel.TestSuite));
+ Assert.That(settings.ExternalTestRunner, Is.Not.Null);
+
+ Assert.That(settings.ExternalTestRunner.ExtensionType, Is.EqualTo(".dll"));
+ Assert.That(settings.ExternalTestRunner.DiscoveryMethodType, Is.EqualTo(DiscoveryMethodType.DiscoveryFileMap));
+
+ IDictionary fileMap = new Dictionary
+ {
+ {"test_1.dll", "C:\\tests\\test_1.xml"},
+ {"test_2.dll", "C:\\tests\\test_2.xml"}
+ };
+
+ Assert.That(settings.ExternalTestRunner.DiscoveryFileMap, Is.EqualTo(fileMap));
+ Assert.That(settings.ExternalTestRunner.ExecutionCommandLine.ToString(), Is.EqualTo("C:\\ExternalTestRunner.exe --test \"{source}\""));
+ }
+
+ ///
+ /// Ensure that external test runner settings utilitsing a file map can be deserialised from a .runsettings Xml document.
+ ///
+ /// Test aims:
+ /// - Ensure that external test runner settings utilitsing a file map can be deserialised from a .runsettings Xml document.
+ ///
+ [Test]
+ public void SerialiseExternalTestRunnerDiscoveryMapSettings()
+ {
+ BoostTestAdapterSettings settings = new BoostTestAdapterSettings();
+
+ settings.ExternalTestRunner = new ExternalBoostTestRunnerSettings
+ {
+ ExtensionType = ".dll",
+ DiscoveryMethodType = DiscoveryMethodType.DiscoveryFileMap,
+ ExecutionCommandLine = new CommandLine("C:\\ExternalTestRunner.exe", "--test \"{source}\"")
+ };
+
+ settings.ExternalTestRunner.DiscoveryFileMap.Add("test_1.dll", "C:\\tests\\test_1.xml");
+ settings.ExternalTestRunner.DiscoveryFileMap.Add("test_2.dll", "C:\\tests\\test_2.xml");
+
+ Compare(settings, "BoostTestAdapterNunit.Resources.Settings.externalTestRunner.runsettings");
+ }
+
+ #endregion Tests
+ }
+}
diff --git a/BoostTestAdapterNunit/BoostTestTest.cs b/BoostTestAdapterNunit/BoostTestTest.cs
new file mode 100644
index 0000000..47f9b4c
--- /dev/null
+++ b/BoostTestAdapterNunit/BoostTestTest.cs
@@ -0,0 +1,446 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Serialization;
+using BoostTestAdapter.Boost.Test;
+using BoostTestAdapter.Utility;
+using BoostTestAdapterNunit.Utility;
+using BoostTestAdapterNunit.Utility.Xml;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class BoostTestTest
+ {
+ #region Test Data
+
+ ///
+ /// Default test source
+ ///
+ private const string Source = @"C:\tests.dll";
+
+ #endregion Test Data
+
+ #region Helper Classes
+
+ ///
+ /// Base implementation of ITestVisitor which visits children accordingly.
+ ///
+ private class DefaultTestVisitor : ITestVisitor
+ {
+ public virtual void Visit(TestCase testCase)
+ {
+ }
+
+ public virtual void Visit(TestSuite testSuite)
+ {
+ foreach (TestUnit child in testSuite.Children)
+ {
+ child.Apply(this);
+ }
+ }
+ }
+
+ ///
+ /// ITestVisitor implementation which counts the number of test cases.
+ ///
+ private class TestCaseCounter : DefaultTestVisitor
+ {
+ public uint Count { get; private set; }
+
+ public override void Visit(TestCase testCase)
+ {
+ ++this.Count;
+ }
+ }
+
+ ///
+ /// ITestVisitor implementation which counts the number of test suites.
+ ///
+ private class TestSuiteCounter : DefaultTestVisitor
+ {
+ public uint Count { get; private set; }
+
+ public override void Visit(TestSuite testSuite)
+ {
+ ++this.Count;
+
+ base.Visit(testSuite);
+ }
+ }
+
+ ///
+ /// 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
+
+ ///
+ /// States the number of test suites for the provided test unit (including itself)
+ ///
+ /// The root test unit from which to start enumerating test suites
+ /// The number of test suites for the provided test unit
+ private uint GetTestSuiteCount(TestUnit root)
+ {
+ TestSuiteCounter counter = new TestSuiteCounter();
+ root.Apply(counter);
+ return counter.Count;
+ }
+
+ ///
+ /// States the number of test cases for the provided test unit (including itself)
+ ///
+ /// The root test unit from which to start enumerating test cases
+ /// The number of test cases for the provided test unit
+ private uint GetTestCaseCount(TestUnit root)
+ {
+ TestCaseCounter counter = new TestCaseCounter();
+ root.Apply(counter);
+ return counter.Count;
+ }
+
+ ///
+ /// Looks up a test unit by fully qualified name
+ ///
+ /// The root test unit from which to start searching
+ /// The fully qualified name of the test unit to look for
+ /// 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;
+ }
+
+ ///
+ /// Deserialises an embedded resource as a TestFramework
+ ///
+ /// The fully qualified path to the embedded resource
+ /// The TestFramework deserialised from the embedded resource
+ private TestFramework Deserialize(string path)
+ {
+ using (Stream stream = TestHelper.LoadEmbeddedResource(path))
+ {
+ XmlSerializer deserializer = new XmlSerializer(typeof(TestFramework));
+ return deserializer.Deserialize(stream) as TestFramework;
+ }
+ }
+
+ ///
+ /// Serializes the provided TestFramework as an XmlDocument
+ ///
+ /// The TestFramework to serialize
+ /// The serialized Xml for the provided TestFramework
+ private static XmlDocument Serialize(TestFramework framework)
+ {
+ XmlDocument doc = new XmlDocument();
+
+ using (XmlWriter writer = doc.CreateNavigator().AppendChild())
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(TestFramework));
+ serializer.Serialize(writer, framework);
+ }
+
+ return doc;
+ }
+
+ ///
+ /// Asserts test unit details
+ ///
+ /// The test unit to test
+ /// The expected type of the test unit
+ /// The expected Id of the test unit
+ /// The expected parent of the test unit
+ private void AssertTestUnit(TestUnit unit, Type type, int id, TestUnit parent)
+ {
+ Assert.That(unit, Is.Not.Null);
+ Assert.That(unit, Is.TypeOf(type));
+ Assert.That(unit.Id, Is.EqualTo(id));
+ Assert.That(unit.Parent, Is.EqualTo(parent));
+ }
+
+ ///
+ /// Asserts test suite details
+ ///
+ /// The test suite to test
+ /// The expected Id of the test suite
+ /// The expected parent of the test suite
+ private void AssertTestSuite(TestUnit unit, int id, TestUnit parent)
+ {
+ AssertTestUnit(unit, typeof(TestSuite), id, parent);
+ }
+
+ ///
+ /// Asserts test case details
+ ///
+ /// The test case to test
+ /// The expected Id of the test case
+ /// The expected source file information of the test case
+ /// The expected parent of the test case
+ private void AssertTestCase(TestUnit unit, int id, SourceFileInfo info, TestUnit parent)
+ {
+ AssertTestUnit(unit, typeof(TestCase), id, parent);
+
+ TestCase test = ((TestCase) unit);
+
+ Assert.That(test.Children, Is.Empty);
+
+ SourceFileInfo unitInfo = test.Source;
+
+ if (info == null)
+ {
+ Assert.That(unitInfo, Is.Null);
+ }
+ else
+ {
+ Assert.That(unitInfo.File, Is.EqualTo(info.File));
+ Assert.That(unitInfo.LineNumber, Is.EqualTo(info.LineNumber));
+ }
+ }
+
+ #endregion Helper Methods
+
+ #region Tests
+
+ ///
+ /// Test cases cannot be parents of other test units.
+ ///
+ /// Test aims:
+ /// - Ensure that TestCases cannot accept other test units as children.
+ ///
+ [Test]
+ [ExpectedException(typeof(InvalidOperationException))]
+ public void AddChildToTestCase()
+ {
+ TestSuite master = new TestSuite("master", null);
+ TestCase test = new TestCase("test", master);
+
+ test.AddChild(new TestCase());
+ }
+
+ ///
+ /// Tests can be uniquely identified via their qualified name.
+ ///
+ /// Test aims:
+ /// - Tests with similar names can still be distinctly identified based on their fully qualified name.
+ ///
+ [Test]
+ public void TestQualifiedNamingScheme()
+ {
+ TestFramework framework = new TestFrameworkBuilder(Source, "Master Test Suite", 1).
+ TestCase("test", 2).
+ TestSuite("suite", 3).
+ TestSuite("suite", 4).
+ TestCase("test", 5).
+ EndSuite().
+ EndSuite().
+ Build();
+
+ // Master Test Suite fully qualified name is equivalent to the empty string
+ Assert.That(framework.MasterTestSuite.FullyQualifiedName, Is.Empty);
+
+ // Test Units which fall directly under the Master Test Suite will
+ // have their (local) name equivalent to their fully qualified name
+ foreach (TestUnit child in framework.MasterTestSuite.Children)
+ {
+ Assert.That(child.FullyQualifiedName, Is.EqualTo(child.Name));
+ }
+
+ // Test Fully Qualified Name scheme via lookup
+
+ TestUnit test = Lookup(framework.MasterTestSuite, "test");
+ AssertTestCase(test, 2, null, framework.MasterTestSuite);
+
+ TestUnit suite = Lookup(framework.MasterTestSuite, "suite");
+ AssertTestSuite(suite, 3, framework.MasterTestSuite);
+
+ TestUnit suiteSuite = Lookup(framework.MasterTestSuite, "suite/suite");
+ AssertTestSuite(suiteSuite, 4, suite);
+
+ TestUnit suiteSuiteTest = Lookup(framework.MasterTestSuite, "suite/suite/test");
+ AssertTestCase(suiteSuiteTest, 5, null, suiteSuite);
+ }
+
+ ///
+ /// TestFramework instances can be deserialised from Xml listings.
+ ///
+ /// Test aims:
+ /// - Given a valid Xml fragment, a TestFramework can be deserialised from the contained information.
+ ///
+ [Test]
+ public void ParseTestList()
+ {
+ TestFramework framework = Deserialize("BoostTestAdapterNunit.Resources.TestLists.sample.test.list.xml");
+
+ Assert.That(framework.Source, Is.EqualTo(Source));
+
+ Assert.That(framework.MasterTestSuite, Is.Not.Null);
+ Assert.That(framework.MasterTestSuite.Name, Is.EqualTo("Test runner test"));
+ Assert.That(framework.MasterTestSuite.Id, Is.EqualTo(1));
+
+ Assert.That(GetTestSuiteCount(framework.MasterTestSuite), Is.EqualTo(4));
+ Assert.That(GetTestCaseCount(framework.MasterTestSuite), Is.EqualTo(7));
+
+ string sourceFile = "test_runner_test.cpp";
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "test1"),
+ 65536,
+ new SourceFileInfo(sourceFile, 26),
+ framework.MasterTestSuite
+ );
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "test2"),
+ 65537,
+ new SourceFileInfo(sourceFile, 35),
+ framework.MasterTestSuite
+ );
+
+ TestUnit sampleSuite = Lookup(framework.MasterTestSuite, "SampleSuite");
+ AssertTestSuite(sampleSuite, 2, framework.MasterTestSuite);
+
+ TestUnit sampleNestedSuite = Lookup(framework.MasterTestSuite, "SampleSuite/SampleNestedSuite");
+ AssertTestSuite(sampleNestedSuite, 3, sampleSuite);
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "SampleSuite/SampleNestedSuite/test3"),
+ 65538,
+ new SourceFileInfo(sourceFile, 48),
+ sampleNestedSuite
+ );
+
+ TestUnit templateSuite = Lookup(framework.MasterTestSuite, "TemplateSuite");
+ AssertTestSuite(templateSuite, 4, framework.MasterTestSuite);
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "TemplateSuite/my_test"),
+ 65539,
+ new SourceFileInfo(sourceFile, 79),
+ templateSuite
+ );
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "TemplateSuite/my_test"),
+ 65540,
+ new SourceFileInfo(sourceFile, 79),
+ templateSuite
+ );
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "TemplateSuite/my_test"),
+ 65541,
+ new SourceFileInfo(sourceFile, 79),
+ templateSuite
+ );
+
+ AssertTestCase(
+ Lookup(framework.MasterTestSuite, "TemplateSuite/my_test"),
+ 65542,
+ new SourceFileInfo(sourceFile, 79),
+ templateSuite
+ );
+ }
+
+ ///
+ /// TestFramework instances can be deserialised from semantically empty Xml listings.
+ ///
+ /// Test aims:
+ /// - Given a valid Xml fragment describing an empty framework, a TestFramework can be deserialised from the contained information.
+ ///
+ [Test]
+ public void ParseEmptyTestList()
+ {
+ TestFramework framework = Deserialize("BoostTestAdapterNunit.Resources.TestLists.empty.test.list.xml");
+
+ Assert.That(framework.MasterTestSuite, Is.Not.Null);
+ Assert.That(framework.Source, Is.Empty);
+ Assert.That(GetTestSuiteCount(framework.MasterTestSuite), Is.EqualTo(1));
+ Assert.That(GetTestCaseCount(framework.MasterTestSuite), Is.EqualTo(0));
+ }
+
+ ///
+ /// TestFramework can be serialized as an Xml listing.
+ ///
+ /// Test aims:
+ /// - A TestFramework can be serialized to Xml successfully.
+ ///
+ [Test]
+ public void SerializeTestFramework()
+ {
+ string sourceFile = "test_runner_test.cpp";
+
+ TestFramework framework = new TestFrameworkBuilder(Source, "Test runner test", 1).
+ TestCase("test1", 65536, new SourceFileInfo(sourceFile, 26)).
+ TestCase("test2", 65537, new SourceFileInfo(sourceFile, 35)).
+ TestSuite("SampleSuite", 2).
+ TestSuite("SampleNestedSuite", 3).
+ TestCase("test3", 65538, new SourceFileInfo(sourceFile, 48)).
+ EndSuite().
+ EndSuite().
+ TestSuite("TemplateSuite", 4).
+ TestCase("my_test", 65539, new SourceFileInfo(sourceFile, 79)).
+ TestCase("my_test", 65540, new SourceFileInfo(sourceFile, 79)).
+ TestCase("my_test", 65541, new SourceFileInfo(sourceFile, 79)).
+ TestCase("my_test", 65542, new SourceFileInfo(sourceFile, 79)).
+ EndSuite().
+ Build();
+
+ using (Stream stream = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.TestLists.sample.test.list.xml"))
+ {
+ XmlDocument baseXml = new XmlDocument();
+ baseXml.Load(stream);
+
+ XmlComparer comparer = new XmlComparer();
+ comparer.CompareXML(baseXml, Serialize(framework), XmlNodeTypeFilter.DefaultFilter);
+ }
+ }
+
+ #endregion Tests
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/CommandEvaluatorTest.cs b/BoostTestAdapterNunit/CommandEvaluatorTest.cs
new file mode 100644
index 0000000..bd4581b
--- /dev/null
+++ b/BoostTestAdapterNunit/CommandEvaluatorTest.cs
@@ -0,0 +1,45 @@
+using BoostTestAdapter.Utility;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ public class CommandEvaluatorTest
+ {
+ [SetUp]
+ public void SetUp()
+ {
+ CommandEvaluator = new CommandEvaluator();
+ }
+
+ private CommandEvaluator CommandEvaluator { get; set; }
+
+ ///
+ /// Variable placeholder string substition
+ ///
+ /// Test aims:
+ /// - Ensure that variable placholders are properly substituted using their pre-defined value
+ ///
+ /// The un-evaluated string which is to be evaluated
+ /// A list of string pairs consisting of the variable placeholder label and its respective value
+ /// The evaluated string
+ [TestCase("Hello {World}!", "World", "Welt", Result = "Hello Welt!")]
+ [TestCase("Hello {World}!", "World", null, Result = "Hello null!")]
+ [TestCase("C:\\ExternalTestRunner.exe --test \"{source}\" --discover", "source", "boost.test.dll", "timeout", "10", Result = "C:\\ExternalTestRunner.exe --test \"boost.test.dll\" --discover")]
+ [TestCase("{source} \"--run_test={test}\" {boost-args}", "source", "test.boostd.exe", "test", "TestSuite/TestCase", "timeout", "10", "boost-args", "--log_level=all", Result = "test.boostd.exe \"--run_test=TestSuite/TestCase\" --log_level=all")]
+ [TestCase("{$}", "$", "jQuery", Result = "jQuery")]
+ [TestCase("{MyVariable} {Is} {Not} {Available}", "MyVariable", "Cikku", Result = "Cikku {Is} {Not} {Available}")]
+ [TestCase("{A}{B}{A}", "A", "1", "B", "2", Result = "121")]
+ public string Evaluate(string input, params string[] variables)
+ {
+ Assert.That(variables.Length % 2, Is.EqualTo(0));
+
+ for (int i = 0; i < variables.Length; i += 2)
+ {
+ CommandEvaluator.SetVariable(variables[i], variables[i + 1]);
+ }
+
+ return CommandEvaluator.Evaluate(input).Result;
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/ConditionalInclusionsFilterTest.cs b/BoostTestAdapterNunit/ConditionalInclusionsFilterTest.cs
new file mode 100644
index 0000000..22f4672
--- /dev/null
+++ b/BoostTestAdapterNunit/ConditionalInclusionsFilterTest.cs
@@ -0,0 +1,273 @@
+using System.Collections.Generic;
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapterNunit.Utility;
+using NUnit.Framework;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class ConditionalInclusionsFilterTest
+ {
+ ///
+ /// The scope of this test is to make sure of the proper handling of the if conditionals of the conditional inclusions filter. The filter is supplied with a fairly
+ /// complex nested if structure and the result generated is compared against the expected filtered output. For this test the expression complexity of the conditional
+ /// is kept to a fairly low level.
+ ///
+ [Test]
+ public void ConditionalInclusionsIfTests()
+ {
+ #region setup
+
+ var definesHandler = new Defines();
+
+ definesHandler.Define("DEBUG", "");
+ definesHandler.Define("NDEBUG", "");
+ definesHandler.Define("DEBUGGER", "");
+
+ var expectedPreprocessorDefines = new HashSet()
+ {
+ "DEBUG",
+ "NDEBUG",
+ "DEBUGGER",
+ "DLEVEL"
+ };
+
+ var filter = new ConditionalInclusionsFilter( new ExpressionEvaluation() );
+
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "ConditionalInclusionsIfTests_UnFilteredSourceCode.cpp";
+ const string filteredSourceCodeResourceName = "ConditionalInclusionsIfTests_FilteredSourceCode.cpp";
+
+ string sourceCodeOriginal = TestHelper.ReadEmbeddedResource(nameSpace + unfilteredSourceCodeResourceName);
+ string sourceCodeExpected = TestHelper.ReadEmbeddedResource(nameSpace + filteredSourceCodeResourceName);
+
+ var cppSourceFile = new CppSourceFile()
+ {
+ FileName = nameSpace + unfilteredSourceCodeResourceName,
+ SourceCode = sourceCodeOriginal
+ };
+
+ #endregion
+
+ #region excercise
+
+ filter.Filter(cppSourceFile, definesHandler);
+
+ #endregion
+
+ #region verify
+
+ Assert.AreEqual(sourceCodeExpected, cppSourceFile.SourceCode);
+ Assert.AreEqual(expectedPreprocessorDefines, definesHandler.NonSubstitutionTokens);
+ Assert.AreEqual(0, definesHandler.SubstitutionTokens.Count);
+
+ #endregion
+
+ }
+
+ ///
+ /// The scope of this test is to test the proper handling of the #define, #undef, #if, #else, #elif and #endif...with particular focus on the #define.
+ /// This test limits itself on checking that the ConditionalInclusionsFilter handles properly the presence of the tokens, so text substitutions and
+ /// and parametrized macro are out of scope.
+ ///
+ [Test]
+ public void ConditionalInclusionsIfdefTests()
+ {
+
+ #region setup
+
+ var definesHandler = new Defines();
+
+ definesHandler.Define("DEBUG", "");
+ definesHandler.Define("NDEBUG", "");
+ definesHandler.Define("DEBUGGER", "");
+
+ var expectedPreprocessorDefines = new HashSet()
+ {
+ "NDEBUG",
+ "DEBUGGER"
+ };
+
+ var filter = new ConditionalInclusionsFilter( new ExpressionEvaluation());
+
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "ConditionalInclusionsIfdefTests_UnFilteredSourceCode.cpp";
+ const string filteredSourceCodeResourceName = "ConditionalInclusionsIfdefTests_FilteredSourceCode.cpp";
+
+ string sourceCodeOriginal = TestHelper.ReadEmbeddedResource(nameSpace + unfilteredSourceCodeResourceName);
+ string sourceCodeExpected = TestHelper.ReadEmbeddedResource(nameSpace + filteredSourceCodeResourceName);
+
+ var cppSourceFile = new CppSourceFile()
+ {
+ FileName = nameSpace + unfilteredSourceCodeResourceName,
+ SourceCode = sourceCodeOriginal
+ };
+
+ #endregion
+
+ #region excercise
+
+ filter.Filter(cppSourceFile, definesHandler);
+
+ #endregion
+
+ #region verify
+
+ Assert.AreEqual(sourceCodeExpected, cppSourceFile.SourceCode);
+ Assert.AreEqual(expectedPreprocessorDefines, definesHandler.NonSubstitutionTokens);
+ Assert.AreEqual(0, definesHandler.SubstitutionTokens.Count);
+
+ #endregion
+
+ }
+
+ ///
+ /// The scope of this test is to make sure that if the state machine does not return to the "normal state", we bypass all filtering
+ /// done on the file and return the source code unfiltered
+ ///
+ [Test]
+ public void ConditionalInclusionsBadSourceFileNesting()
+ {
+ #region setup
+
+ var definesHandler = new Defines(); //no defines supplied
+
+ var filter = new ConditionalInclusionsFilter(new ExpressionEvaluation());
+
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "ConditionalInclusionsBadSourceFileNesting.cpp";
+
+ string sourceCodeOriginal = TestHelper.ReadEmbeddedResource(nameSpace + unfilteredSourceCodeResourceName);
+ string sourceCodeExpected = sourceCodeOriginal;
+
+ var cppSourceFile = new CppSourceFile()
+ {
+ FileName = nameSpace + unfilteredSourceCodeResourceName,
+ SourceCode = sourceCodeOriginal
+ };
+
+ #endregion
+
+ #region excercise
+
+ filter.Filter(cppSourceFile, definesHandler);
+
+ #endregion
+
+ #region verify
+
+ Assert.AreEqual(sourceCodeExpected, cppSourceFile.SourceCode); //no filtering should be done due to missing #endif
+
+ #endregion
+
+ }
+
+ ///
+ /// The scope of this test is to supply the filter with expressions that is not able to handle, so as to make sure
+ /// that in case the filter is not able to handle the expression complexity we abort any filtering on the entire sourcecode
+ /// and return back the source code unfiltered
+ ///
+ [Test]
+ public void ConditionalInclusionsComplexExpressionEvaluationFail()
+ {
+ #region setup
+
+ var definesHandler = new Defines(); //no defines supplied
+
+ var filter = new ConditionalInclusionsFilter(new ExpressionEvaluation());
+
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "ConditionalInclusionsComplexEvaluationFail.cpp";
+
+ string sourceCodeOriginal = TestHelper.ReadEmbeddedResource(nameSpace + unfilteredSourceCodeResourceName);
+ string sourceCodeExpected = sourceCodeOriginal;
+
+ var cppSourceFile = new CppSourceFile()
+ {
+ FileName = nameSpace + unfilteredSourceCodeResourceName,
+ SourceCode = sourceCodeOriginal
+ };
+
+ #endregion
+
+ #region excercise
+
+ filter.Filter(cppSourceFile, definesHandler);
+
+ #endregion
+
+ #region verify
+
+ Assert.AreEqual(sourceCodeExpected, cppSourceFile.SourceCode); //no filtering should be done due to inability to evaluate an expression
+
+ #endregion
+ }
+
+ ///
+ /// The scope of this test is to supply the filter with expressions that it can handle and to make sure that it filters in and
+ /// out the source code in a correct fashion.
+ ///
+ [Test]
+ public void ConditionalInclusionsComplexExpressionEvaluationSuccess()
+ {
+ #region setup
+
+ var definesHandler = new Defines();
+
+ var expectedNonSubstitutionTokens = new HashSet()
+ {
+ "VERSION",
+ "HALF",
+ "THIRD",
+ "DEBUG",
+ "SIN",
+ "MAX",
+ "CUBE",
+ "fPRINT",
+ "ASSERT",
+ };
+
+ var expectedSubtitutionTokens = new Dictionary()
+ {
+ {"LEVEL", "19"},
+ {"EVER", ";;"},
+ {"BIG", "(512)"},
+ {"PRINT", "cout << #x"},
+ };
+
+ var filter = new ConditionalInclusionsFilter(new ExpressionEvaluation());
+
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "ConditionalInclusionsComplexEvaluationSuccess_UnfilteredSourceCode.cpp";
+ const string filteredSourceCodeResourceName = "ConditionalInclusionsComplexEvaluationSuccess_FilteredSourceCode.cpp";
+
+ string sourceCodeOriginal = TestHelper.ReadEmbeddedResource(nameSpace + unfilteredSourceCodeResourceName);
+
+ string sourceCodeExpected = TestHelper.ReadEmbeddedResource(nameSpace + filteredSourceCodeResourceName);
+
+ var cppSourceFile = new CppSourceFile()
+ {
+ FileName = nameSpace + unfilteredSourceCodeResourceName,
+ SourceCode = sourceCodeOriginal
+ };
+
+ #endregion
+
+ #region excercise
+
+ filter.Filter(cppSourceFile, definesHandler);
+
+ #endregion
+
+ #region verify
+
+ Assert.AreEqual(sourceCodeExpected, cppSourceFile.SourceCode);
+ Assert.AreEqual(expectedNonSubstitutionTokens, definesHandler.NonSubstitutionTokens);
+ Assert.AreEqual(expectedSubtitutionTokens, definesHandler.SubstitutionTokens);
+
+ #endregion
+ }
+
+ }
+}
diff --git a/BoostTestAdapterNunit/CorrectReferencedAssembliesTest.cs b/BoostTestAdapterNunit/CorrectReferencedAssembliesTest.cs
new file mode 100644
index 0000000..099631c
--- /dev/null
+++ b/BoostTestAdapterNunit/CorrectReferencedAssembliesTest.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using System.Reflection;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ internal class CorrectReferencesAssembliesTest
+ {
+ ///
+ /// It is critical that assemblies are compiled with the correct references. The scope of these tests are to ensure that a user
+ /// is referencing the correct Microsoft.VisualStudio.TestPlatform.ObjectModel and the correct Microsoft.VisualStudio.VCProjectEngine
+ /// in all the complied assemblies. The idea is taken off the Nunit tests of NUnit nunit3-vs-adapter/src/NUnitTestAdapterTests/ProjectTests.cs on GitHub
+ ///
+ /// the dll name
+ /// the assembly that we are going to check that is properly referenced
+ /// version number that assembly must have
+ [TestCase("BoostTestAdapter.dll", "Microsoft.VisualStudio.TestPlatform.ObjectModel", 11, TestName = "CorrectlyReferencedBoostTestAdapter", Description = "Microsoft.VisualStudio.TestPlatform.ObjectModel in BoostTestAdapter must point to the VS2012 version")]
+ [TestCase("VisualStudio2012Adapter.dll", "Microsoft.VisualStudio.VCProjectEngine", 11, TestName = "CorrectlyReferencedVisualStudio2012Adapter", Description = "Microsoft.VisualStudio.VCProjectEngine in VisualStudio2012Adapter must point to the VS2012 version")]
+ [TestCase("VisualStudio2013Adapter.dll", "Microsoft.VisualStudio.VCProjectEngine", 12, TestName = "CorrectlyReferencedVisualStudio2013Adapter", Description = "Microsoft.VisualStudio.VCProjectEngine in VisualStudio2013Adapter must point to the VS2013 version")]
+ [TestCase("VisualStudio2015Adapter.dll", "Microsoft.VisualStudio.VCProjectEngine", 14, TestName = "CorrectlyReferencedVisualStudio2015Adapter", Description = "Microsoft.VisualStudio.VCProjectEngine in VisualStudio2015Adapter must point to the VS2015 version")]
+ public void CorrectReferences(string dll, string assemblyReferenceName, int versionMajor)
+ {
+ var assembly = Assembly.LoadFrom(TestContext.CurrentContext.TestDirectory + "/" + dll);
+ var referencedAssemblies = assembly.GetReferencedAssemblies().Where(ass => ass.Name == assemblyReferenceName).ToList();
+ Assert.IsTrue(referencedAssemblies != null && referencedAssemblies.Count() == 1, "No reference to " + assemblyReferenceName + " found");
+ Assert.IsTrue(referencedAssemblies[0].Version.Major == versionMajor,
+ assemblyReferenceName + " in " + dll + " is referenced to an incorrect version");
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/DefaultBoostTestRunnerFactoryTest.cs b/BoostTestAdapterNunit/DefaultBoostTestRunnerFactoryTest.cs
new file mode 100644
index 0000000..f9e1c71
--- /dev/null
+++ b/BoostTestAdapterNunit/DefaultBoostTestRunnerFactoryTest.cs
@@ -0,0 +1,72 @@
+using System;
+using BoostTestAdapter.Boost.Runner;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class DefaultBoostTestRunnerFactoryTest
+ {
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.Factory = new DefaultBoostTestRunnerFactory();
+ }
+
+ #endregion Test Setup/Teardown
+
+ #region Test Data
+
+ private DefaultBoostTestRunnerFactory Factory { get; set; }
+
+ #endregion Test Data
+
+ #region Tests
+
+ ///
+ /// Provisions internal and external IBoostTestRunner instances based on the requested source and settings.
+ ///
+ /// Test aims:
+ /// - Ensure that the proper IBoostTestRunner type is provided for the requested source.
+ ///
+ // Exe types
+ [TestCase("test.exe", null, typeof(BoostTestRunner))]
+ [TestCase("test.exe", ".dll", typeof(BoostTestRunner))]
+ [TestCase("test.exe", ".exe", typeof(ExternalBoostTestRunner))]
+ // Dll types
+ [TestCase("test.dll", null, null)]
+ [TestCase("test.dll", ".dll", typeof(ExternalBoostTestRunner))]
+ [TestCase("test.dll", ".exe", null)]
+ // Invalid extension types
+ [TestCase("test.txt", null, null)]
+ [TestCase("test.txt", ".dll", null)]
+ [TestCase("test.txt", ".exe", null)]
+ public void ExternalBoostTestRunnerProvisioning(string source, string externalExtension, Type type)
+ {
+ BoostTestRunnerFactoryOptions options = new BoostTestRunnerFactoryOptions();
+ options.ExternalTestRunnerSettings = new ExternalBoostTestRunnerSettings
+ {
+ ExtensionType = externalExtension,
+ DiscoveryCommandLine = new CommandLine(),
+ ExecutionCommandLine = new CommandLine()
+ };
+
+ IBoostTestRunner runner = this.Factory.GetRunner(source, options);
+
+ if (runner == null)
+ {
+ Assert.That(type, Is.Null);
+ }
+ else
+ {
+ Assert.That(runner, Is.AssignableTo(type));
+ }
+ }
+
+ #endregion Tests
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/DefaultTestDiscovererFactoryTest.cs b/BoostTestAdapterNunit/DefaultTestDiscovererFactoryTest.cs
new file mode 100644
index 0000000..bff9642
--- /dev/null
+++ b/BoostTestAdapterNunit/DefaultTestDiscovererFactoryTest.cs
@@ -0,0 +1,68 @@
+using System;
+using BoostTestAdapter;
+using BoostTestAdapter.Settings;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class DefaultTestDiscovererFactoryTest
+ {
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.Factory = new DefaultBoostTestDiscovererFactory();
+ }
+
+ #endregion Test Setup/Teardown
+
+ #region Test Data
+
+ private DefaultBoostTestDiscovererFactory Factory { get; set; }
+
+ #endregion Test Data
+
+ #region Tests
+
+ ///
+ /// Provisions internal and external TestDiscoverer instances ones based on the requested source and settings.
+ ///
+ /// Test aims:
+ /// - Ensure that the proper ITestDiscoverer type is provided for the requested source.
+ ///
+ // Exe types
+ [TestCase("test.exe", null, Result = typeof(BoostTestExeDiscoverer))]
+ [TestCase("test.exe", ".dll", Result = typeof(BoostTestExeDiscoverer))]
+ [TestCase("test.exe", ".exe", Result = typeof(ExternalBoostTestDiscoverer))]
+ // Dll types
+ [TestCase("test.dll", null, Result = null)]
+ [TestCase("test.dll", ".dll", Result = typeof(ExternalBoostTestDiscoverer))]
+ [TestCase("test.dll", ".exe", Result = null)]
+ // Invalid extension types
+ [TestCase("test.txt", null, Result = null)]
+ [TestCase("test.txt", ".dll", Result = null)]
+ [TestCase("test.txt", ".exe", Result = null)]
+ public Type TestDiscovererProvisioning(string source, string externalExtension)
+ {
+ ExternalBoostTestRunnerSettings settings = null;
+
+ if (!string.IsNullOrEmpty(externalExtension))
+ {
+ settings = new ExternalBoostTestRunnerSettings { ExtensionType = externalExtension };
+ }
+
+ BoostTestDiscovererFactoryOptions options = new BoostTestDiscovererFactoryOptions
+ {
+ ExternalTestRunnerSettings = settings
+ };
+
+ IBoostTestDiscoverer discoverer = this.Factory.GetTestDiscoverer(source, options);
+
+ return (discoverer == null) ? null : discoverer.GetType();
+ }
+
+ #endregion Tests
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/ExpressionEvaluationTest.cs b/BoostTestAdapterNunit/ExpressionEvaluationTest.cs
new file mode 100644
index 0000000..6dd9b84
--- /dev/null
+++ b/BoostTestAdapterNunit/ExpressionEvaluationTest.cs
@@ -0,0 +1,64 @@
+using BoostTestAdapter.SourceFilter;
+using NUnit.Framework;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class ExpressionEvaluationTest
+ {
+ ///
+ /// Tests the expression evaluator with different expressions of varying complexities
+ ///
+ [TestCase("0", Result = EvaluationResult.IsFalse)]
+ [TestCase("1", Result = EvaluationResult.IsTrue)]
+ [TestCase("1+1", Result = EvaluationResult.IsTrue)]
+ [TestCase("1+1-0", Result = EvaluationResult.IsTrue)]
+ [TestCase("1+(1)", Result = EvaluationResult.IsTrue)]
+ [TestCase("(1)", Result = EvaluationResult.IsTrue)]
+ [TestCase("1.0", Result = EvaluationResult.IsTrue)]
+ [TestCase("VERSION > 0", "VERSION", "5", Result = EvaluationResult.IsTrue)]
+ [TestCase("VERSION > 0", "VERSION", "0", Result = EvaluationResult.IsFalse)]
+ [TestCase("0 || 2", Result = EvaluationResult.IsTrue)]
+ [TestCase("1 && 2", Result = EvaluationResult.IsTrue)]
+ [TestCase("0 && 1", Result = EvaluationResult.IsFalse)]
+ [TestCase("VERSION > 0", Result = EvaluationResult.UnDetermined)]
+ [TestCase("(1", Result = EvaluationResult.UnDetermined)]
+ [TestCase("1/0", Result = EvaluationResult.IsTrue)]
+ [TestCase("version > 0", "VERSION", "1", Result = EvaluationResult.UnDetermined)] /* case sensitivity test */
+ [TestCase("VALUE1 > VALUE2", "VALUE1", "10", "VALUE2", "9", Result = EvaluationResult.IsTrue)]
+ [TestCase("-1", Result = EvaluationResult.IsTrue)]
+ [TestCase("BIG == 512", "BIG", "(512)", Result = EvaluationResult.IsTrue)]
+ [TestCase("BIG == 512", "BIG", "(SMO + 1)", "SMO", "(511)", Result = EvaluationResult.IsTrue)]
+ [TestCase("BIG == 512", "BIG", "(SMO + 1)", Result = EvaluationResult.UnDetermined)]
+ [TestCase("BIGA == 512", Result = EvaluationResult.UnDetermined)]
+ [TestCase("defined(TEST1) && !defined(TEST2)", Result = EvaluationResult.IsFalse)]
+ [TestCase("defined(TEST1) && !defined(TEST2)", "TEST1", "", "TEST2", "", Result = EvaluationResult.IsFalse)]
+ [TestCase("defined(TEST1) && !defined(TEST2)", "TEST1", "", Result = EvaluationResult.IsTrue)]
+ public EvaluationResult ExpressionEvaluation(string expression, params string[] definitions)
+ {
+ ExpressionEvaluation e = new ExpressionEvaluation();
+ return e.EvaluateExpression(expression, GenerateDefines(definitions));
+ }
+
+ ///
+ /// Given a parameter list of strings, generates a Defines structure
+ /// where pairs of strings are treated as a definition and its value.
+ ///
+ /// The string pair definitions array from which to generate the Defines structue
+ /// A Defines structure built out of string pairs available in the definitions array
+ private Defines GenerateDefines(string[] definitions)
+ {
+ Assert.That(definitions.Length % 2, Is.EqualTo(0));
+
+ Defines definesHandler = new Defines();
+
+ for (int i = 1; i < definitions.Length; i += 2)
+ {
+ definesHandler.Define(definitions[i - 1], definitions[i]);
+ }
+
+ return definesHandler;
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/ExternalBoostTestDiscovererTest.cs b/BoostTestAdapterNunit/ExternalBoostTestDiscovererTest.cs
new file mode 100644
index 0000000..da3bb79
--- /dev/null
+++ b/BoostTestAdapterNunit/ExternalBoostTestDiscovererTest.cs
@@ -0,0 +1,121 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BoostTestAdapter;
+using BoostTestAdapter.Settings;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using BoostTestAdapterNunit.Fakes;
+using BoostTestAdapterNunit.Utility;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using NUnit.Framework;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ public class ExternalBoostTestDiscovererTest
+ {
+ #region Helper Methods
+
+ ///
+ /// Asserts general test properties
+ ///
+ /// The enumeration of discovered tests
+ /// The qualified test name which is to be tested
+ /// The source from which the test should have been discovered
+ /// Optional source file information related to the test under question
+ private void AssertVSTestCaseProperties(IEnumerable tests, QualifiedNameBuilder qualifiedName, string source, SourceFileInfo info)
+ {
+ VSTestCase test = tests.FirstOrDefault((_test) => (_test.FullyQualifiedName == qualifiedName.ToString()));
+
+ Assert.That(test, Is.Not.Null);
+ Assert.That(test.DisplayName, Is.EqualTo(qualifiedName.Peek()));
+ Assert.That(test.Source, Is.EqualTo(source));
+ Assert.That(test.ExecutorUri, Is.EqualTo(BoostTestExecutor.ExecutorUri));
+
+ if (info != null)
+ {
+ Assert.That(test.CodeFilePath, Is.EqualTo(info.File));
+ Assert.That(test.LineNumber, Is.EqualTo(info.LineNumber));
+ }
+
+ Assert.That(test.Traits.Count(), Is.EqualTo(1));
+
+ Trait trait = test.Traits.First();
+ Assert.That(trait.Name, Is.EqualTo(VSTestModel.TestSuiteTrait));
+
+ string suite = qualifiedName.Pop().ToString();
+ if (string.IsNullOrEmpty(suite))
+ {
+ suite = qualifiedName.MasterTestSuite;
+ }
+
+ Assert.That(trait.Value, Is.EqualTo(suite));
+ }
+
+ #endregion Helper Methods
+
+ #region Tests
+
+ ///
+ /// External test discovery based on static test listings
+ ///
+ /// Test aims:
+ /// - Ensure that if configured to use static test listing, test discovery lists only the tests which can be mapped to a valid test source.
+ ///
+ [Test]
+ public void DiscoveryFileMapDiscovery()
+ {
+ string listing = TestHelper.CopyEmbeddedResourceToDirectory("BoostTestAdapterNunit.Resources.TestLists", "sample.test.list.xml", Path.GetTempPath());
+
+ try
+ {
+ ExternalBoostTestRunnerSettings settings = new ExternalBoostTestRunnerSettings
+ {
+ ExtensionType = ".dll",
+ DiscoveryMethodType = DiscoveryMethodType.DiscoveryFileMap
+ };
+
+ settings.DiscoveryFileMap["test_1.dll"] = listing;
+
+ ExternalBoostTestDiscoverer discoverer = new ExternalBoostTestDiscoverer(settings);
+
+ DefaultTestContext context = new DefaultTestContext();
+ ConsoleMessageLogger logger = new ConsoleMessageLogger();
+ DefaultTestCaseDiscoverySink sink = new DefaultTestCaseDiscoverySink();
+
+ const string mappedSource = "C:\\test_1.dll";
+ const string unmappedSource = "C:\\test_2.dll";
+
+ discoverer.DiscoverTests(new string[] { mappedSource, unmappedSource }, context, logger, sink);
+
+ // A total of 7 tests should be discovered as described in the Xml descriptor
+ Assert.That(sink.Tests.Count(), Is.EqualTo(7));
+
+ // All of the discovered tests should originate from C:\test_1.dll.
+ // No mapping to C:\test_2.dll exist so no tests should be discovered from that source.
+ Assert.That(sink.Tests.Count((test) => test.Source == mappedSource), Is.EqualTo(7));
+
+ const string masterTestSuite = "Test runner test";
+
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "test1"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 26));
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "test2"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 35));
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "SampleSuite/SampleNestedSuite/test3"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 48));
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "TemplateSuite/my_test"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 79));
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "TemplateSuite/my_test"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 79));
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "TemplateSuite/my_test"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 79));
+ AssertVSTestCaseProperties(sink.Tests, QualifiedNameBuilder.FromString(masterTestSuite, "TemplateSuite/my_test"), mappedSource, new SourceFileInfo("test_runner_test.cpp", 79));
+ }
+ finally
+ {
+ if (File.Exists(listing))
+ {
+ File.Delete(listing);
+ }
+ }
+ }
+
+ #endregion Tests
+ }
+}
diff --git a/BoostTestAdapterNunit/Fakes/ConsoleMessageLogger.cs b/BoostTestAdapterNunit/Fakes/ConsoleMessageLogger.cs
new file mode 100644
index 0000000..2339c77
--- /dev/null
+++ b/BoostTestAdapterNunit/Fakes/ConsoleMessageLogger.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace BoostTestAdapterNunit.Fakes
+{
+ ///
+ /// IMessageLogger implementation. Writes all logged messages to standard output.
+ ///
+ public class ConsoleMessageLogger : IMessageLogger
+ {
+ #region IMessageLogger
+
+ public void SendMessage(TestMessageLevel testMessageLevel, string message)
+ {
+ Console.WriteLine("{0}: {1}", testMessageLevel, message);
+ }
+
+ #endregion IMessageLogger
+ }
+}
diff --git a/BoostTestAdapterNunit/Fakes/DefaultTestContext.cs b/BoostTestAdapterNunit/Fakes/DefaultTestContext.cs
new file mode 100644
index 0000000..a45d6da
--- /dev/null
+++ b/BoostTestAdapterNunit/Fakes/DefaultTestContext.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using System.Xml.XPath;
+using BoostTestAdapterNunit.Utility;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace BoostTestAdapterNunit.Fakes
+{
+ ///
+ /// Default implementation of IRunContext/IDiscoveryContext/IRunSettings for testing purposes.
+ ///
+ public class DefaultTestContext : IRunContext, IRunSettings
+ {
+ ///
+ /// Default constructor
+ ///
+ public DefaultTestContext() :
+ this(false, string.Empty)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// Flag which identifies whether or not this RunContext is a debug run context or not.
+ public DefaultTestContext(bool debug) :
+ this(debug, string.Empty)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// Flag which identifies whether or not this RunContext is a debug run context or not.
+ /// An Xml string which identifies the runsettings in use.
+ public DefaultTestContext(bool debug, string settings)
+ {
+ this.IsBeingDebugged = debug;
+ this.SettingsXml = settings;
+
+ this.IsDataCollectionEnabled = false;
+
+ this.SettingProviders = new Dictionary();
+ }
+
+ ///
+ /// Map of SettingProvider name to a respective SettingProvider instance.
+ ///
+ private IDictionary SettingProviders { get; set; }
+
+ #region IRunContext
+
+ public ITestCaseFilterExpression GetTestCaseFilter(IEnumerable supportedProperties, Func propertyProvider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool InIsolation { get; set; }
+
+ public bool IsBeingDebugged { get; set; }
+
+ public bool IsDataCollectionEnabled { get; set; }
+
+ public bool KeepAlive { get; set; }
+
+ public string SolutionDirectory { get; set; }
+
+ public string TestRunDirectory { get; set; }
+
+ #region IDiscoveryContext
+
+ public IRunSettings RunSettings
+ {
+ get { return this; }
+ }
+
+ #endregion IDiscoveryContext
+ #endregion IRunContext
+
+ #region IRunSettings
+
+ public ISettingsProvider GetSettings(string settingsName)
+ {
+ SettingProviderContext context = null;
+ if (this.SettingProviders.TryGetValue(settingsName, out context))
+ {
+ // If no Xml fragment is found for a particular provider, return null
+ return (context.IsSet) ? context.Provider : null;
+ }
+
+ return null;
+ }
+
+ public string SettingsXml
+ {
+ get;
+ set;
+ }
+
+ #endregion IRunSettings
+
+ ///
+ /// An attempt to emulate the C# MEF export.
+ ///
+ /// The settings name
+ /// The settings provider to register under the provided name
+ public void RegisterSettingProvider(string name, ISettingsProvider provider)
+ {
+ this.SettingProviders[name] = new SettingProviderContext(provider);
+ }
+
+ ///
+ /// Loads the embedded resource path and populates the registered providers accordingly.
+ ///
+ /// The path to the embedded resource
+ public void LoadEmbeddedSettings(string path)
+ {
+ this.SettingsXml = TestHelper.ReadEmbeddedResource(path);
+
+ // Populate SettingProviders
+ using (StringReader reader = new StringReader(this.SettingsXml))
+ {
+ XPathDocument doc = new XPathDocument(reader);
+ XPathNavigator nav = doc.CreateNavigator();
+
+ foreach (XPathNavigator child in nav.Select("/RunSettings/*"))
+ {
+ if (this.SettingProviders.ContainsKey(child.LocalName))
+ {
+ this.SettingProviders[child.LocalName].Load(child.ReadSubtree());
+ }
+ }
+ }
+ }
+
+ ///
+ /// An internal class used to aggregate a SettingsProvider
+ /// and a flag which states the result of the loading attempt.
+ ///
+ private class SettingProviderContext
+ {
+ public SettingProviderContext(ISettingsProvider provider)
+ {
+ this.Provider = provider;
+ this.IsSet = false;
+ }
+
+ public ISettingsProvider Provider { get; set; }
+ public bool IsSet { get; set; }
+
+ public void Load(XmlReader reader)
+ {
+ this.Provider.Load(reader);
+ this.IsSet = true;
+ }
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/LoggerTest.cs b/BoostTestAdapterNunit/LoggerTest.cs
new file mode 100644
index 0000000..5c19f21
--- /dev/null
+++ b/BoostTestAdapterNunit/LoggerTest.cs
@@ -0,0 +1,166 @@
+using System.IO;
+using System.Text.RegularExpressions;
+using BoostTestAdapter.Utility;
+using BoostTestAdapterNunit.Utility;
+using FakeItEasy;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ ///
+ /// Tests which cover the Logger class
+ ///
+ [TestFixture]
+ class LoggerTest
+ {
+
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ }
+
+ #endregion
+
+ ///
+ /// The scope of this test is to make sure that in case a message is sent to an initalized loggerInstance, the loggerInstance SendMessage methods are called
+ /// with the right type of message severity and message text
+ ///
+ [Test]
+ public void InitializedLogger_loggerInstanceSendMessageCalled()
+ {
+ var messageLogger = A.Fake();
+ Logger.Initialize(messageLogger);
+ Logger.SendMessage(TestMessageLevel.Informational, "This is an informational type test message");
+ A.CallTo(() => messageLogger.SendMessage(TestMessageLevel.Informational, "This is an informational type test message")).MustHaveHappened();
+ Logger.SendMessage(TestMessageLevel.Warning, "This is an warning type test message");
+ A.CallTo(() => messageLogger.SendMessage(TestMessageLevel.Warning, "This is an warning type test message")).MustHaveHappened();
+ Logger.SendMessage(TestMessageLevel.Error, "This is an error type test message");
+ A.CallTo(() => messageLogger.SendMessage(TestMessageLevel.Error, "This is an error type test message")).MustHaveHappened();
+ }
+
+ ///
+ /// The scope of this test is to make sure that in case a logger is left uninitialized, the loggerInstance related functions are never called
+ ///
+ [Test]
+ public void UninitializedLoggerNeverCalled()
+ {
+ var messageLogger = A.Fake();
+ //Logger is never initialized
+ Logger.SendMessage(TestMessageLevel.Informational, "test");
+ A.CallTo(() => messageLogger.SendMessage(A.Ignored, A.Ignored)).MustNotHaveHappened();
+ }
+
+ ///
+ /// The scope of this test is to test that the logging to file is working
+ ///
+ [Test]
+ public void Log4NetCorrectLoggingToFileVerification()
+ {
+ string resourceFileName = "BoostTestAdapter.dll.config";
+ string logFileName = "BoostTestAdapter.dll.log";
+
+ string resourceFilePath = Path.Combine(Directory.GetCurrentDirectory(), resourceFileName);
+ string logFilePath = Path.Combine(Directory.GetCurrentDirectory(), logFileName);
+
+ #region cleanup from possible previous test executions
+ //deletion of the log4net config file
+ DeleteFileIfExists(resourceFilePath);
+ //deletion of the log4net log file
+ DeleteFileIfExists(logFilePath);
+ #endregion
+
+ /*the config file is copied over to the working directory of the assembly. To please note that in case of unit tests this path is different
+ * from the executing assembly directory (due to the shadow copying) so that cannot be used. Additionally the executing assembly directory
+ * of the NUnit project will be different from the executing assembly directory of code/project under test
+ */
+
+ #region test setup
+ CopyLog4NetConfigFileFromAssemblyToWorkingDirectory(resourceFileName);
+ string path = Directory.GetCurrentDirectory();
+ Assert.That(File.Exists(resourceFilePath), Is.True);
+ #endregion
+
+ #region test
+ var messageLogger = A.Fake();
+ Logger.Initialize(messageLogger);
+ Logger.SendMessage(TestMessageLevel.Informational, "This is an informational type test message");
+ Logger.SendMessage(TestMessageLevel.Warning, "This is a warning type test message");
+ Logger.SendMessage(TestMessageLevel.Error, "This is an error type test message");
+ Logger.Shutdown();
+ #endregion
+
+ #region results verification
+
+ Assert.That(File.Exists(logFilePath), Is.True);
+ string logFileContents = File.ReadAllText(logFilePath);
+ //check that the logger initialization message exists in file and is of type informational
+ if (
+ !Regex.IsMatch(logFileContents, @"INFO(.+)Logger initialized",
+ RegexOptions.IgnoreCase))
+ {
+ Assert.Fail("Failed to find logger initialization message in log file");
+ }
+ //check that the informational test message exists and has the expected contents
+ if (
+ !Regex.IsMatch(logFileContents, @"INFO(.+)This is an informational type test message",
+ RegexOptions.IgnoreCase))
+ {
+ Assert.Fail("Failed to find informational type test message in log file");
+ }
+ //check that the warning test message exists and has the expected contents
+ if (
+ !Regex.IsMatch(logFileContents, @"WARN(.+)This is a warning type test message",
+ RegexOptions.IgnoreCase))
+ {
+ Assert.Fail("Failed to find warning type test message in log file");
+ }
+ //check that the error test message exists and has the expected contents
+ if (
+ !Regex.IsMatch(logFileContents, @"ERROR(.+)This is an error type test message",
+ RegexOptions.IgnoreCase))
+ {
+ Assert.Fail("Failed to find error type test message in log file");
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Helper method so as to copy the log4net config file from the assembly (because it is included as an embedded resource) to
+ /// the working directory (and not the executing directory!) of the assembly
+ ///
+ /// the filename of the embedded resource that needs to copied over
+ static private void CopyLog4NetConfigFileFromAssemblyToWorkingDirectory(string resourceName)
+ {
+ using (Stream stream = TestHelper.LoadEmbeddedResource("BoostTestAdapterNunit.Resources.Log4NetConfigFile." + resourceName))
+ using (FileStream fileStream = new FileStream(Path.Combine(Directory.GetCurrentDirectory(), resourceName), FileMode.Create, FileAccess.Write))
+ {
+ if (stream == null)
+ {
+ Assert.Fail("Failed to load the requested embedded resource. Please check that the resource exists and the fully qualified name is correct");
+ }
+ stream.CopyTo(fileStream);
+ }
+ }
+
+ ///
+ /// Helper method that deletes file if exists
+ ///
+ /// complete file path of the file to be deleted
+ static private void DeleteFileIfExists(string filePath)
+ {
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/MultiLineCommentFilterTest.cs b/BoostTestAdapterNunit/MultiLineCommentFilterTest.cs
new file mode 100644
index 0000000..5cfdb13
--- /dev/null
+++ b/BoostTestAdapterNunit/MultiLineCommentFilterTest.cs
@@ -0,0 +1,29 @@
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapterNunit.Utility;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class MultiLineCommentFilterTest : SourceFilterTestBase
+ {
+ ///
+ /// Tests the correct operation (greediness wise) of the multiline comment filter
+ ///
+ [Test]
+ public void MultiLineComment()
+ {
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "MultiLineCommentTest_UnFilteredSourceCode.cpp";
+ const string filteredSourceCodeResourceName = "MultiLineCommentTest_FilteredSourceCode.cpp";
+
+ FilterAndCompareResources(
+ new MultilineCommentFilter(),
+ null,
+ nameSpace + unfilteredSourceCodeResourceName,
+ nameSpace + filteredSourceCodeResourceName
+ );
+ }
+
+ }
+}
diff --git a/BoostTestAdapterNunit/ProjectOutPutCheckerVs12Nunit.cs b/BoostTestAdapterNunit/ProjectOutPutCheckerVs12Nunit.cs
new file mode 100644
index 0000000..752d8ac
--- /dev/null
+++ b/BoostTestAdapterNunit/ProjectOutPutCheckerVs12Nunit.cs
@@ -0,0 +1,93 @@
+using CheckForProjectOutPut_VS12;
+using CheckForprojectOutPut_VS13;
+using FakeItEasy;
+using NUnit.Framework;
+using EnvDTE;
+using Microsoft.VisualStudio.VCProjectEngine;
+
+namespace BoostTestAdaptorNunit
+{
+ [TestFixture]
+ class ProjectOutPutCheckerVs12Nunit
+ {
+ Project _fackProjObj;
+ ConfigurationManager _fakeConfigurationManager;
+ Configuration _fakeActiveConfiguration;
+ VCProject _fackVcProject;
+ IVCCollection _fakeCollection;
+ VCConfiguration _fakeVcConfiguration;
+
+ /**
+ * Bellow test cases uses faked object for testing IsProjectOutputSame method
+ * Faked objects are created using FakeItEasy and configured in testcases
+ * If any method or property is not configured on fake object created it will try to call the actual implementation
+ * call to actual implementation from fake object causes exception
+ */
+ [TestFixtureSetUp]
+ public void FakeTheProjectInterface()
+ {
+ _fackProjObj = A.Fake();
+ _fakeConfigurationManager = A.Fake();
+ _fakeActiveConfiguration = A.Fake();
+ _fackVcProject = A.Fake();
+ _fakeCollection = A.Fake();
+ _fakeVcConfiguration = A.Fake();
+ }
+
+ [Test]
+ public void CheckOutputForVsProj2012_OutputPathMatchTrue()
+ {
+ A.CallTo(() => _fackProjObj.ConfigurationManager).Returns(_fakeConfigurationManager);
+ A.CallTo(() => _fakeConfigurationManager.ActiveConfiguration).Returns(_fakeActiveConfiguration);
+ A.CallTo(() => _fakeActiveConfiguration.ConfigurationName).Returns("Debug");
+ A.CallTo(() => _fakeActiveConfiguration.PlatformName).Returns("Win32");
+ A.CallTo(() => _fackProjObj.Object).Returns(_fackVcProject);
+ A.CallTo(() => _fackVcProject.Configurations).Returns(_fakeCollection);
+ A.CallTo(() => _fakeCollection.Item("Debug|Win32")).Returns(_fakeVcConfiguration);
+ A.CallTo(() => _fakeVcConfiguration.PrimaryOutput).Returns("exePath");
+
+ Assert.AreEqual(true, ProjectOutputCheckerVs12.IsProjectOutputSame(_fackProjObj, "exePath"));
+ }
+
+ [Test]
+ public void CheckOutputForVsProj2012_OutputPathMatchFalse()
+ {
+ A.CallTo(() => _fackProjObj.ConfigurationManager).Returns(_fakeConfigurationManager);
+ A.CallTo(() => _fakeConfigurationManager.ActiveConfiguration).Returns(_fakeActiveConfiguration);
+ A.CallTo(() => _fakeActiveConfiguration.ConfigurationName).Returns("Release");
+ A.CallTo(() => _fakeActiveConfiguration.PlatformName).Returns("Win32");
+ A.CallTo(() => _fackProjObj.Object).Returns(_fackVcProject);
+ A.CallTo(() => _fackVcProject.Configurations).Returns(_fakeCollection);
+ A.CallTo(() => _fakeCollection.Item("Release|Win32")).Returns(_fakeVcConfiguration);
+ A.CallTo(() => _fakeVcConfiguration.PrimaryOutput).Returns("exePathDiff");
+
+ Assert.AreEqual(false, ProjectOutputCheckerVs12.IsProjectOutputSame(_fackProjObj, "exePath"));
+ }
+
+ [Test]
+ public void CheckOutputForVsProj2013_OutputPathMatchFalse()
+ {
+
+ A.CallTo(() => _fackProjObj.ConfigurationManager).Returns(_fakeConfigurationManager);
+ A.CallTo(() => _fakeConfigurationManager.ActiveConfiguration).Returns(_fakeActiveConfiguration);
+ A.CallTo(() => _fakeActiveConfiguration.ConfigurationName).Returns("Release");
+ A.CallTo(() => _fakeActiveConfiguration.PlatformName).Returns("Win32");
+ A.CallTo(() => _fackProjObj.Object).Returns(_fackVcProject);
+ A.CallTo(() => _fackVcProject.Configurations).Returns(_fakeCollection);
+ A.CallTo(() => _fakeCollection.Item("Release|Win32")).Returns(_fakeVcConfiguration);
+ A.CallTo(() => _fakeVcConfiguration.PrimaryOutput).Returns("exePathDiff");
+
+ Assert.AreEqual(false, ProjectOutputCheckerVs13.IsProjectOutputSame(_fackProjObj, "exePath"));
+ }
+
+ [TestFixtureTearDown]
+ public void ClearTheFakeObject()
+ {
+ _fackProjObj = null;
+ _fakeActiveConfiguration = null;
+ _fakeConfigurationManager = null;
+ _fakeCollection = null;
+ _fakeVcConfiguration = null;
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/Properties/AssemblyInfo.cs b/BoostTestAdapterNunit/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2961920
--- /dev/null
+++ b/BoostTestAdapterNunit/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BoostTestAdaptorNunit")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("BoostTestAdaptorNunit")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: CLSCompliant(false)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2cf57374-a6f8-4410-a9a2-41dfc0ccd7d6")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BoostTestAdapterNunit/QuotedStringsFilterTest.cs b/BoostTestAdapterNunit/QuotedStringsFilterTest.cs
new file mode 100644
index 0000000..3aca613
--- /dev/null
+++ b/BoostTestAdapterNunit/QuotedStringsFilterTest.cs
@@ -0,0 +1,28 @@
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapterNunit.Utility;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class QuotedStringsFilterTest : SourceFilterTestBase
+ {
+ ///
+ /// Tests the correct operation of the quoted strings filter on C++ source files
+ ///
+ [Test]
+ public void QuotedStringsFilter()
+ {
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "QuotedStringsFilterTest_UnFilteredSourceCode.cpp";
+ const string filteredSourceCodeResourceName = "QuotedStringsFilterTest_FilteredSourceCode.cpp";
+
+ FilterAndCompareResources(
+ new QuotedStringsFilter(),
+ null,
+ nameSpace + unfilteredSourceCodeResourceName,
+ nameSpace + filteredSourceCodeResourceName
+ );
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/Resources/CppSources/BoostFixtureTestCase.cpp b/BoostTestAdapterNunit/Resources/CppSources/BoostFixtureTestCase.cpp
new file mode 100644
index 0000000..9bc6b4d
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/CppSources/BoostFixtureTestCase.cpp
@@ -0,0 +1,40 @@
+#include "stdafx.h"
+
+struct F
+{
+ F() : i(0)
+ {
+ BOOST_TEST_MESSAGE("setup fixture");
+ }
+ ~F()
+ {
+ BOOST_TEST_MESSAGE("teardown fixture");
+ }
+
+ int i;
+};
+
+BOOST_AUTO_TEST_SUITE(Suit1)
+
+BOOST_AUTO_TEST_CASE(BoostUnitTest1)
+{
+ BOOST_CHECK(1 == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(Fixturetest_case1, F)
+{
+ BOOST_CHECK(i == 1);
+ ++i;
+}
+
+BOOST_FIXTURE_TEST_CASE(Fixturetest_case2, F)
+{
+ BOOST_CHECK_EQUAL(i, 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_FIXTURE_TEST_CASE(Fixturetest_case3, F)
+{
+ BOOST_CHECK_EQUAL(i, 1);
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/CppSources/BoostFixtureTestSuite.cpp b/BoostTestAdapterNunit/Resources/CppSources/BoostFixtureTestSuite.cpp
new file mode 100644
index 0000000..ceefa9e
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/CppSources/BoostFixtureTestSuite.cpp
@@ -0,0 +1,61 @@
+#include "stdafx.h"
+#include
+#include
+
+class TestClassA
+{
+public:
+ TestClassA()
+ {
+ m_testVar = 999;
+ }
+ ~TestClassA()
+ {}
+
+ int m_testVar;
+};
+
+class TestClassB
+{
+public:
+ TestClassB()
+ {
+ }
+ ~TestClassB()
+ {}
+};
+
+BOOST_FIXTURE_TEST_SUITE(FixtureSuite1, TestClassA);
+
+BOOST_AUTO_TEST_CASE(BoostTest1)
+{
+ BOOST_CHECK(m_testVar == 999);
+}
+
+BOOST_AUTO_TEST_CASE(BoostTest2)
+{
+ m_testVar = 0;
+ BOOST_CHECK(m_testVar == 999);
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+
+BOOST_AUTO_TEST_CASE(BoostTest3)
+{
+ BOOST_TEST_MESSAGE("Outside the Fixture test Suite");
+}
+
+BOOST_FIXTURE_TEST_SUITE(FixtureSuite2, TestClassA);
+
+BOOST_FIXTURE_TEST_CASE(Fixturetest_case1, TestClassB)
+{
+ BOOST_CHECK_EQUAL(1, 1);
+}
+
+typedef boost::mpl::list type_list;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(TemplatedTest, T, type_list)
+{
+ BOOST_CHECK_EQUAL(sizeof(T), (unsigned)4);
+}
+BOOST_AUTO_TEST_SUITE_END();
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/CppSources/BoostUnitTestSample.cpp b/BoostTestAdapterNunit/Resources/CppSources/BoostUnitTestSample.cpp
new file mode 100644
index 0000000..a61477e
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/CppSources/BoostUnitTestSample.cpp
@@ -0,0 +1,42 @@
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "stdafx.h"
+#include
+#include
+
+int add(int i, int j)
+{
+ return i + j;
+}
+
+BOOST_AUTO_TEST_SUITE(Suite1)
+BOOST_AUTO_TEST_CASE(BoostUnitTest123)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+BOOST_AUTO_TEST_CASE(BoostUnitTest1234)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_CASE(BoostUnitTest12345)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+
+typedef boost::mpl::list test_types;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(my_test, T, test_types)
+{
+ BOOST_CHECK_EQUAL(sizeof(T), (unsigned)4)
+}
+/*
+BOOST_AUTO_TEST_CASE( BoostUnitTest123 )
+{
+ BOOST_WARN( sizeof(int) == sizeof(short) );
+}
+*/
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/CppSources/BoostUnitTestSampleRequiringUseOfFilters.cpp b/BoostTestAdapterNunit/Resources/CppSources/BoostUnitTestSampleRequiringUseOfFilters.cpp
new file mode 100644
index 0000000..48628a2
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/CppSources/BoostUnitTestSampleRequiringUseOfFilters.cpp
@@ -0,0 +1,66 @@
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "stdafx.h"
+#include
+#include
+
+int add(int i, int j)
+{
+ return i + j;
+}
+
+BOOST_AUTO_TEST_SUITE(Suite1)
+ BOOST_AUTO_TEST_CASE(BoostUnitTest123)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+BOOST_AUTO_TEST_CASE(BoostUnitTest1234)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+BOOST_AUTO_TEST_SUITE_END()
+
+ BOOST_AUTO_TEST_CASE(BoostUnitTest12345)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+
+std::cout << "BOOST_AUTO_TEST_CASE(BoostUnitTestShouldNotAppear1) " << std::endl;
+
+//BOOST_AUTO_TEST_CASE(BoostUnitTestShouldNotAppear2)
+//{
+// BOOST_WARN(sizeof(int) == sizeof(short));
+//}
+
+typedef boost::mpl::list test_types;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(my_test, T, test_types)
+{
+ BOOST_CHECK_EQUAL(sizeof(T), (unsigned)4)
+}
+
+#if defined(WINNT)
+
+BOOST_AUTO_TEST_CASE(BoostUnitTestShouldNotAppear3)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+
+#else
+
+BOOST_AUTO_TEST_CASE(BoostUnitTestConditional)
+{
+ BOOST_WARN(sizeof(int) == sizeof(short));
+}
+
+#endif
+
+/*
+BOOST_AUTO_TEST_CASE( BoostUnitTestShouldNotAppear4 )
+{
+BOOST_WARN( sizeof(int) == sizeof(short) );
+}
+*/
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/Log4NetConfigFile/BoostTestAdapter.dll.config b/BoostTestAdapterNunit/Resources/Log4NetConfigFile/BoostTestAdapter.dll.config
new file mode 100644
index 0000000..400ca7b
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/Log4NetConfigFile/BoostTestAdapter.dll.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/AbortedTest/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/AbortedTest/sample.test.log.xml
new file mode 100644
index 0000000..b275209
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/AbortedTest/sample.test.log.xml
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/AbortedTest/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/AbortedTest/sample.test.report.xml
new file mode 100644
index 0000000..c3190b0
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/AbortedTest/sample.test.report.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/BoostFailTest/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/BoostFailTest/sample.test.log.xml
new file mode 100644
index 0000000..9291efa
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/BoostFailTest/sample.test.log.xml
@@ -0,0 +1 @@
+1000
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/BoostFailTest/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/BoostFailTest/sample.test.report.xml
new file mode 100644
index 0000000..59ebaa1
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/BoostFailTest/sample.test.report.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/FailedRequireTest/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/FailedRequireTest/sample.test.log.xml
new file mode 100644
index 0000000..0525e7d
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/FailedRequireTest/sample.test.log.xml
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/FailedRequireTest/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/FailedRequireTest/sample.test.report.xml
new file mode 100644
index 0000000..e74d3d9
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/FailedRequireTest/sample.test.report.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/MemoryLeakTest/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/MemoryLeakTest/sample.test.log.xml
new file mode 100644
index 0000000..b718dd8
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/MemoryLeakTest/sample.test.log.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/MemoryLeakTest/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/MemoryLeakTest/sample.test.report.xml
new file mode 100644
index 0000000..3a45c7d
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/MemoryLeakTest/sample.test.report.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/MultipleTests/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/MultipleTests/sample.test.log.xml
new file mode 100644
index 0000000..1e9f406
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/MultipleTests/sample.test.log.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ 5000
+
+
+ 0
+
+
+
+
+ 792000
+
+
+ 941000
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/MultipleTests/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/MultipleTests/sample.test.report.xml
new file mode 100644
index 0000000..ce9b8c9
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/MultipleTests/sample.test.report.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/NestedTestSuite/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/NestedTestSuite/sample.test.log.xml
new file mode 100644
index 0000000..5e460c7
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/NestedTestSuite/sample.test.log.xml
@@ -0,0 +1 @@
+1000
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/NestedTestSuite/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/NestedTestSuite/sample.test.report.xml
new file mode 100644
index 0000000..f85b880
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/NestedTestSuite/sample.test.report.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/NoMatchingTests/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/NoMatchingTests/sample.test.log.xml
new file mode 100644
index 0000000..e69de29
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/NoMatchingTests/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/NoMatchingTests/sample.test.report.xml
new file mode 100644
index 0000000..7a23c44
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/NoMatchingTests/sample.test.report.xml
@@ -0,0 +1 @@
+Test setup error: no test cases matching filter
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/OutputTest/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/OutputTest/sample.test.report.xml
new file mode 100644
index 0000000..0a60f92
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/OutputTest/sample.test.report.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/PassedTest/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/PassedTest/sample.test.log.xml
new file mode 100644
index 0000000..fe55c41
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/PassedTest/sample.test.log.xml
@@ -0,0 +1 @@
+18457000
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/PassedTest/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/PassedTest/sample.test.report.xml
new file mode 100644
index 0000000..3585b6e
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/PassedTest/sample.test.report.xml
@@ -0,0 +1 @@
+
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/SpecialCharacters/sample.test.log.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/SpecialCharacters/sample.test.log.xml
new file mode 100644
index 0000000..3ee4f2f
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/SpecialCharacters/sample.test.log.xml
@@ -0,0 +1 @@
+2000
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/ReportsLogs/SpecialCharacters/sample.test.report.xml b/BoostTestAdapterNunit/Resources/ReportsLogs/SpecialCharacters/sample.test.report.xml
new file mode 100644
index 0000000..8285af1
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/ReportsLogs/SpecialCharacters/sample.test.report.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/Settings/conditionalIncludesDisabled.runsettings b/BoostTestAdapterNunit/Resources/Settings/conditionalIncludesDisabled.runsettings
new file mode 100644
index 0000000..0968355
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/Settings/conditionalIncludesDisabled.runsettings
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+ false
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/Settings/default.runsettings b/BoostTestAdapterNunit/Resources/Settings/default.runsettings
new file mode 100644
index 0000000..acbb042
Binary files /dev/null and b/BoostTestAdapterNunit/Resources/Settings/default.runsettings differ
diff --git a/BoostTestAdapterNunit/Resources/Settings/empty.runsettings b/BoostTestAdapterNunit/Resources/Settings/empty.runsettings
new file mode 100644
index 0000000..48f1ec5
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/Settings/empty.runsettings
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/Settings/externalTestRunner.runsettings b/BoostTestAdapterNunit/Resources/Settings/externalTestRunner.runsettings
new file mode 100644
index 0000000..0d0aba1
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/Settings/externalTestRunner.runsettings
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+ C:\tests\test_1.xml
+ C:\tests\test_2.xml
+
+ C:\ExternalTestRunner.exe --test "{source}"
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/Settings/sample.2.runsettings b/BoostTestAdapterNunit/Resources/Settings/sample.2.runsettings
new file mode 100644
index 0000000..aea874c
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/Settings/sample.2.runsettings
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ .\TestResults
+
+
+ x86
+
+
+ Framework40
+
+
+
+
+
+
+
+
+
+
+ .*CPPUnitTestFramework.*
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/Settings/sample.runsettings b/BoostTestAdapterNunit/Resources/Settings/sample.runsettings
new file mode 100644
index 0000000..65666d8
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/Settings/sample.runsettings
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ 600000
+
+ true
+
+
+
+ C:\ExternalTestRunner.exe --test "{source}" --list-debug "{out}"
+ C:\ExternalTestRunner.exe --test "{source}"
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsBadSourceFileNesting.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsBadSourceFileNesting.cpp
new file mode 100644
index 0000000..7c5d134
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsBadSourceFileNesting.cpp
@@ -0,0 +1,156 @@
+
+
+#ifdef DEBUG
+ std:cout << "Hello 01\n";
+ #undef DEBUG
+ std:cout << "Hello 02\n";
+#endif
+
+#ifndef DEBUG
+ std:cout << "Hello 03\n";
+#elif 0
+ std:cout << "Hello 04\n";
+#endif
+
+#define DEBUG
+#undef DEBUG
+#define VER
+
+#ifdef DEBUG
+ #define CLIENT1
+ std:cout << "Hello 05\n";
+ #ifdef VER
+ std:cout << "Hello 06\n";
+ #undef VER
+ std:cout << "Hello 07\n";
+ #endif
+
+ #ifndef VER
+ std:cout << "Hello 08\n";
+ #elif 0
+ std:cout << "Hello 09\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 10\n";
+ #elif 1
+ std:cout << "Hello 12\n";
+ #elif 1
+ std:cout << "Hello 13\n";
+ #endif
+#elif 1
+ std:cout << "Hello 14\n";
+ #ifdef VER
+ std:cout << "Hello 15\n";
+ #undef VER
+ std:cout << "Hello 16\n";
+ #endif
+
+ #ifndef VER
+ std:cout << "Hello 17\n";
+ #elif 0
+ std:cout << "Hello 18\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 19\n";
+ #elif 0
+ std:cout << "Hello 20\n";
+ #elif 1
+ #define VER
+ std:cout << "Hello 21\n";
+ #if 0
+ std:cout << "Hello 22\n";
+ #else
+ std:cout << "Hello 23\n";
+ #endif
+
+ #if 1
+ std:cout << "Hello 24\n";
+ #else
+ std:cout << "Hello 25\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 26\n";
+ #elif 0
+ std:cout << "Hello 27\n";
+ #elif 0
+ std:cout << "Hello 28\n";
+ #elif 0
+ std:cout << "Hello 29\n";
+ #elif 0
+ std:cout << "Hello 30\n";
+ #elif 1
+ std:cout << "Hello 31\n";
+ #else
+ std:cout << "Hello 32\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 33\n";
+ #elif 0
+ std:cout << "Hello 34\n";
+ #else
+ std:cout << "Hello 35\n";
+ #ifdef VER
+ std:cout << "Hello 36\n";
+ #undef VER
+ std:cout << "Hello 37\n";
+ #endif
+
+ #ifndef VER
+ std:cout << "Hello 38\n";
+ #elif 0
+ std:cout << "Hello 39\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 40\n";
+ #elif 1
+ std:cout << "Hello 41\n";
+ #elif 1
+ std:cout << "Hello 42\n";
+ #endif
+
+ #define VER
+
+ #if defined VER
+ std:cout << "Hello 42\n";
+ #undef VER
+ std:cout << "Hello 43\n";
+ #endif
+
+ #if !defined VER
+ std:cout << "Hello 44\n";
+ #elif 0
+ std:cout << "Hello 45\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 46\n";
+ #elif 1
+ std:cout << "Hello 47\n";
+ #elif 1
+ std:cout << "Hello 48\n";
+ #endif
+
+ #endif
+ #elif 1
+ std:cout << "Hello 49\n";
+ #endif
+#elif 1
+ std:cout << "Hello 50\n";
+//missing endif
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationFail.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationFail.cpp
new file mode 100644
index 0000000..518f467
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationFail.cpp
@@ -0,0 +1,6 @@
+
+#if LEVEL > 18
+ cout << "correct evaluation";
+#else
+ cout << "incorrect evaluation";
+#endif
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationSuccess_FilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationSuccess_FilteredSourceCode.cpp
new file mode 100644
index 0000000..6ab032a
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationSuccess_FilteredSourceCode.cpp
@@ -0,0 +1,112 @@
+//all defines are expected to be read and filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+//multiline defines are expected to be filtered
+
+
+
+
+
+
+
+
+#pragma region testing whether the tokens are defined
+
+
+ cout << "VERSION defined";
+
+
+
+ cout << "LEVEL defined";
+
+
+
+ cout << "EVER defined";
+
+
+
+ cout << "HALF defined";
+
+
+
+ cout << "THIRD defined";
+
+
+
+ cout << "BIG defined";
+
+
+
+ cout << "DEBUG defined";
+
+
+
+ cout << "SIN defined";
+
+
+
+ cout << "SIN defined";
+
+
+
+ cout << "CUBE defined";
+
+
+
+ cout << "PRINT defined";
+
+
+
+ cout << "fPRINT defined";
+
+
+
+ cout << "ASSERT defined";
+
+
+#pragma endregion testing whether the tokens are defined
+
+#pragma region correct evaluation testing
+
+
+ cout << "correct evaluation";
+
+
+
+
+
+ cout << "correct evaluation";
+
+
+
+
+
+ cout << "correct evaluation";
+
+
+
+
+
+ cout << "correct evaluation";
+
+
+
+
+
+ cout << "correct evaluation";
+
+
+
+
+#pragma endregion correct evaluation testing
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationSuccess_UnfilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationSuccess_UnfilteredSourceCode.cpp
new file mode 100644
index 0000000..a511899
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsComplexEvaluationSuccess_UnfilteredSourceCode.cpp
@@ -0,0 +1,112 @@
+//all defines are expected to be read and filtered
+#define VERSION
+#define LEVEL 19
+#define EVER ;;
+#define HALF(x) x/2
+#define THIRD(x) ((x)/(3))
+#define BIG (512)
+#define DEBUG
+#define SIN(x) sin(x)
+#define MAX(x,y) ( (x) > (y) ? (x) : (y) )
+#define CUBE(a) ( (a) * (a) * (a) )
+#define PRINT cout << #x
+#define fPRINT(x) f ## x ## Print
+
+//multiline defines are expected to be filtered
+#define ASSERT(x) \
+if (! (x)) \
+{ \
+ cout << "ERROR!! Assert " << #x << " failed << endl; \
+ cout << " on line " << __LINE__ << endl; \
+ cout << " in file " << __FILE__ << endl; \
+}
+
+#pragma region testing whether the tokens are defined
+
+#if defined VERSION
+ cout << "VERSION defined";
+#endif
+
+#if defined LEVEL
+ cout << "LEVEL defined";
+#endif
+
+#if defined EVER
+ cout << "EVER defined";
+#endif
+
+#if defined HALF
+ cout << "HALF defined";
+#endif
+
+#if defined THIRD
+ cout << "THIRD defined";
+#endif
+
+#if defined BIG
+ cout << "BIG defined";
+#endif
+
+#if defined DEBUG
+ cout << "DEBUG defined";
+#endif
+
+#if defined SIN
+ cout << "SIN defined";
+#endif
+
+#if defined MAX
+ cout << "SIN defined";
+#endif
+
+#if defined CUBE
+ cout << "CUBE defined";
+#endif
+
+#if defined PRINT
+ cout << "PRINT defined";
+#endif
+
+#if defined fPRINT
+ cout << "fPRINT defined";
+#endif
+
+#if defined ASSERT
+ cout << "ASSERT defined";
+#endif
+
+#pragma endregion testing whether the tokens are defined
+
+#pragma region correct evaluation testing
+
+#if LEVEL > 18
+ cout << "correct evaluation";
+#else
+ cout << "incorrect evaluation";
+#endif
+
+#if LEVEL > (18)
+ cout << "correct evaluation";
+#else
+ cout << "incorrect evaluation";
+#endif
+
+#if LEVEL > LEVEL/2
+ cout << "correct evaluation";
+#else
+ cout << "incorrect evaluation";
+#endif
+
+#if LEVEL > (LEVEL/2)
+ cout << "correct evaluation";
+#else
+ cout << "incorrect evaluation";
+#endif
+
+#if BIG == 512
+ cout << "correct evaluation";
+#else
+ cout << "incorrect evaluation";
+#endif
+
+#pragma endregion correct evaluation testing
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfTests_FilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfTests_FilteredSourceCode.cpp
new file mode 100644
index 0000000..87aae9f
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfTests_FilteredSourceCode.cpp
@@ -0,0 +1,518 @@
+
+
+
+//std:cout' that are commented "should not filtered" should not be filtered. The others we assume that have to be filtered
+
+int _tmain(int argc, _TCHAR* argv[])
+{
+
+#region testing positive if along with an else statement. The "Hello 02" is expected to be left unfiltered
+
+
+ std:cout << "Hello 01\n"; //should not be filtered
+
+
+
+
+#endregion
+
+
+#region testing negative if along with an else statement. The "Hello 04" is expected to be left unfiltered
+
+
+
+
+ std:cout << "Hello 04\n"; //should not be filtered
+
+
+#endregion
+
+#region testing simple elif. The "Hello 06" is expected to be left unfiltered
+
+
+
+
+ std:cout << "Hello 06\n"; //should not be filtered
+
+
+
+
+#endregion testing a more complex elif. The "Hello 10" is expected to be left unfiltered
+
+
+
+
+
+
+ std:cout << "Hello 10\n"; //should not be filtered
+
+
+
+
+#endregion
+
+#region testing a case where if and elif failed, The "Hello 15" is expected to be left unfiltered
+
+
+
+
+
+
+
+
+ std:cout << "Hello 15\n"; //should not be filtered
+
+
+#endregion
+
+#region testing of medium complexity nesting with only if, elif and endif
+
+
+ std:cout << "Hello 16\n"; //should not be filtered
+ //-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 48\n"; //should not be filtered
+
+
+
+
+ std:cout << "Hello 50\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 51\n"; //should not be filtered
+
+
+
+
+
+
+
+ std:cout << "Hello 54\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+ //-->
+ std:cout << "Hello 58\n"; //should not be filtered
+
+
+
+
+ std:cout << "Hello 60\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 61\n"; //should not be filtered
+
+
+
+
+
+
+
+ std:cout << "Hello 64\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 68\n"; //should not be filtered
+
+
+
+
+ std:cout << "Hello 69\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 71\n"; //should not be filtered
+
+
+ std:cout << "Hello 72\n"; //should not be filtered
+
+
+
+
+
+
+ std:cout << "Hello 75\n"; //should not be filtered
+
+
+
+
+
+
+
+
+ std:cout << "Hello 79\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 102\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 104\n"; //should not be filtered
+
+
+ std:cout << "Hello 105\n"; //should not be filtered
+
+
+
+
+
+
+ std:cout << "Hello 108\n"; //should not be filtered
+
+
+
+
+
+
+
+
+ std:cout << "Hello 112\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 146\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 148\n"; //should not be filtered
+
+
+ std:cout << "Hello 149\n"; //should not be filtered
+
+
+
+
+
+
+ std:cout << "Hello 152\n"; //should not be filtered
+
+
+
+
+
+
+
+
+ std:cout << "Hello 156\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 179\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 181\n"; //should not be filtered
+
+
+ std:cout << "Hello 182\n"; //should not be filtered
+
+
+
+
+
+
+ std:cout << "Hello 185\n"; //should not be filtered
+
+
+
+
+
+
+
+
+ std:cout << "Hello 189\n"; //should not be filtered
+
+
+
+
+#endregion
+
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfTests_UnFilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfTests_UnFilteredSourceCode.cpp
new file mode 100644
index 0000000..255b825
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfTests_UnFilteredSourceCode.cpp
@@ -0,0 +1,518 @@
+#define DLEVEL
+#define NDEBUG
+
+//std:cout' that are commented "should not filtered" should not be filtered. The others we assume that have to be filtered
+
+int _tmain(int argc, _TCHAR* argv[])
+{
+
+#region testing positive if along with an else statement. The "Hello 02" is expected to be left unfiltered
+
+#if 1
+ std:cout << "Hello 01\n"; //should not be filtered
+#else
+ std:cout << "Hello 02\n";
+#endif
+
+#endregion
+
+
+#region testing negative if along with an else statement. The "Hello 04" is expected to be left unfiltered
+
+#if 0
+ std:cout << "Hello 03\n";
+#else
+ std:cout << "Hello 04\n"; //should not be filtered
+#endif
+
+#endregion
+
+#region testing simple elif. The "Hello 06" is expected to be left unfiltered
+
+#if 0
+ std:cout << "Hello 05\n";
+#elif 1
+ std:cout << "Hello 06\n"; //should not be filtered
+#else
+ std:cout << "Hello 07\n";
+#endif
+
+#endregion testing a more complex elif. The "Hello 10" is expected to be left unfiltered
+
+#if 0
+ std:cout << "Hello 08\n";
+#elif 0
+ std:cout << "Hello 09\n";
+#elif 1
+ std:cout << "Hello 10\n"; //should not be filtered
+#else
+ std:cout << "Hello 11\n";
+#endif
+
+#endregion
+
+#region testing a case where if and elif failed, The "Hello 15" is expected to be left unfiltered
+
+#if 0
+ std:cout << "Hello 12\n";
+#elif 0
+ std:cout << "Hello 13\n";
+#elif 0
+ std:cout << "Hello 14\n";
+#else
+ std:cout << "Hello 15\n"; //should not be filtered
+#endif
+
+#endregion
+
+#region testing of medium complexity nesting with only if, elif and endif
+
+#if 1
+ std:cout << "Hello 16\n"; //should not be filtered
+ //-->
+ #if 0
+ //-->
+ std:cout << "Hello 17\n";
+ #if 0
+ //-->
+ std:cout << "Hello 18\n";
+ #if 0
+ std:cout << "Hello 19\n";
+ #else
+ std:cout << "Hello 20\n";
+ #endif
+
+ #if 1
+ std:cout << "Hello 21\n";
+ #else
+ std:cout << "Hello 22\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 23\n";
+ #elif 1
+ std:cout << "Hello 24\n";
+ #else
+ std:cout << "Hello 25\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 26\n";
+ #elif 0
+ std:cout << "Hello 27\n";
+ #else
+ std:cout << "Hello 28\n";
+ #endif
+
+ #else
+
+ std:cout << "Hello 29\n";
+
+ #if 0
+ std:cout << "Hello 30\n";
+ #else
+ std:cout << "Hello 31\n";
+ #endif
+
+ #if 1
+ std:cout << "Hello 32\n";
+ #else
+ std:cout << "Hello 33\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 34\n";
+ #elif 1
+ std:cout << "Hello 35\n";
+ #else
+ std:cout << "Hello 36\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 37\n";
+ #elif 0
+ std:cout << "Hello 38\n";
+ #else
+ std:cout << "Hello 39\n";
+ #endif
+
+ #endif
+
+ #if 1
+ std:cout << "Hello 40\n";
+ #else
+ std:cout << "Hello 41\n";
+ #endif
+ #if 0
+ std:cout << "Hello 42\n";
+ #elif 1
+ std:cout << "Hello 43\n";
+ #else
+ std:cout << "Hello 44\n";
+ #endif
+ #if 0
+ std:cout << "Hello 45\n";
+ #elif 0
+ std:cout << "Hello 46\n";
+ #else
+ std:cout << "Hello 47\n";
+ #endif
+ #else
+ std:cout << "Hello 48\n"; //should not be filtered
+
+ #if 0
+ std:cout << "Hello 49\n";
+ #else
+ std:cout << "Hello 50\n"; //should not be filtered
+ #endif
+
+ #if 1
+ std:cout << "Hello 51\n"; //should not be filtered
+ #else
+ std:cout << "Hello 52\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 53\n";
+ #elif 1
+ std:cout << "Hello 54\n"; //should not be filtered
+ #else
+ std:cout << "Hello 55\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 56\n";
+ #elif 0
+ std:cout << "Hello 57\n";
+ #else
+ //-->
+ std:cout << "Hello 58\n"; //should not be filtered
+
+ #if 0
+ std:cout << "Hello 59\n";
+ #else
+ std:cout << "Hello 60\n"; //should not be filtered
+ #endif
+
+ #if 1
+ std:cout << "Hello 61\n"; //should not be filtered
+ #else
+ std:cout << "Hello 62\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 63\n";
+ #elif 1
+ std:cout << "Hello 64\n"; //should not be filtered
+ #else
+ std:cout << "Hello 65\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 66\n";
+ #elif 0
+ std:cout << "Hello 67\n";
+ #else
+ std:cout << "Hello 68\n"; //should not be filtered
+ #endif
+ #endif
+
+ #if 1
+ std:cout << "Hello 69\n"; //should not be filtered
+ #if 0
+ std:cout << "Hello 70\n";
+ #else
+ std:cout << "Hello 71\n"; //should not be filtered
+ #endif
+ #if 1
+ std:cout << "Hello 72\n"; //should not be filtered
+ #else
+ std:cout << "Hello 73\n";
+ #endif
+ #if 0
+ std:cout << "Hello 74\n";
+ #elif 1
+ std:cout << "Hello 75\n"; //should not be filtered
+ #else
+ std:cout << "Hello 76\n";
+ #endif
+ #if 0
+ std:cout << "Hello 77\n";
+ #elif 0
+ std:cout << "Hello 78\n";
+ #else
+ std:cout << "Hello 79\n"; //should not be filtered
+ #endif
+ #else
+ std:cout << "Hello 80\n";
+ #if 0
+ std:cout << "Hello 81\n";
+ #else
+ std:cout << "Hello 82\n";
+ #endif
+ #if 1
+ std:cout << "Hello 83\n";
+ #else
+ std:cout << "Hello 84\n";
+ #endif
+ #if 0
+ std:cout << "Hello 85\n";
+ #elif 1
+ std:cout << "Hello 86\n";
+ #else
+ std:cout << "Hello 87\n";
+ #endif
+ #if 0
+ std:cout << "Hello 88\n";
+ #elif 0
+ std:cout << "Hello 89\n";
+ #else
+ std:cout << "Hello 90\n";
+ #endif
+ #endif
+
+ #if 0
+ std:cout << "Hello 91\n";
+ #if 0
+ std:cout << "Hello 92\n";
+ #else
+ std:cout << "Hello 93\n";
+ #endif
+ #if 1
+ std:cout << "Hello 94\n";
+ #else
+ std:cout << "Hello 95\n";
+ #endif
+ #if 0
+ std:cout << "Hello 96\n";
+ #elif 1
+ std:cout << "Hello 97\n";
+ #else
+ std:cout << "Hello 98\n";
+ #endif
+ #if 0
+ std:cout << "Hello 99\n";
+ #elif 0
+ std:cout << "Hello 100\n";
+ #else
+ std:cout << "Hello 101\n";
+ #endif
+ #elif 1
+ std:cout << "Hello 102\n"; //should not be filtered
+ #if 0
+ std:cout << "Hello 103\n";
+ #else
+ std:cout << "Hello 104\n"; //should not be filtered
+ #endif
+ #if 1
+ std:cout << "Hello 105\n"; //should not be filtered
+ #else
+ std:cout << "Hello 106\n";
+ #endif
+ #if 0
+ std:cout << "Hello 107\n"; //should be filtered
+ #elif 1
+ std:cout << "Hello 108\n"; //should not be filtered
+ #else
+ std:cout << "Hello 109\n";
+ #endif
+ #if 0
+ std:cout << "Hello 110\n";
+ #elif 0
+ std:cout << "Hello 111\n";
+ #else
+ std:cout << "Hello 112\n"; //should not be filtered
+ #endif
+ #else
+ std:cout << "Hello 113\n";
+ #if 0
+ std:cout << "Hello 114\n";
+ #else
+ std:cout << "Hello 115\n";
+ #endif
+ #if 1
+ std:cout << "Hello 116\n";
+ #else
+ std:cout << "Hello 117\n";
+ #endif
+ #if 0
+ std:cout << "Hello 118\n";
+ #elif 1
+ std:cout << "Hello 119\n";
+ #else
+ std:cout << "Hello 120\n";
+ #endif
+ #if 0
+ std:cout << "Hello 121\n";
+ #elif 0
+ std:cout << "Hello 122\n";
+ #else
+ std:cout << "Hello 123\n";
+ #endif
+ #endif
+ #if 0
+ std:cout << "Hello 124\n";
+ #if 0
+ std:cout << "Hello 125\n";
+ #else
+ std:cout << "Hello 126\n";
+ #endif
+ #if 1
+ std:cout << "Hello 127\n";
+ #else
+ std:cout << "Hello 128\n";
+ #endif
+ #if 0
+ std:cout << "Hello 129\n";
+ #elif 1
+ std:cout << "Hello 130\n";
+ #else
+ std:cout << "Hello 131\n";
+ #endif
+ #if 0
+ std:cout << "Hello 132\n";
+ #elif 0
+ std:cout << "Hello 133\n";
+ #else
+ std:cout << "Hello 134\n";
+ #endif
+ #elif 0
+ std:cout << "Hello 135\n";
+ #if 0
+ std:cout << "Hello 136\n";
+ #else
+ std:cout << "Hello 137\n";
+ #endif
+ #if 1
+ std:cout << "Hello 138\n";
+ #else
+ std:cout << "Hello 139\n";
+ #endif
+ #if 0
+ std:cout << "Hello 140\n";
+ #elif 1
+ std:cout << "Hello 141\n";
+ #else
+ std:cout << "Hello 142\n";
+ #endif
+ #if 0
+ std:cout << "Hello 143\n";
+ #elif 0
+ std:cout << "Hello 144\n";
+ #else
+ std:cout << "Hello 145\n";
+ #endif
+ #else
+ std:cout << "Hello 146\n"; //should not be filtered
+ #if 0
+ std:cout << "Hello 147\n";
+ #else
+ std:cout << "Hello 148\n"; //should not be filtered
+ #endif
+ #if 1
+ std:cout << "Hello 149\n"; //should not be filtered
+ #else
+ std:cout << "Hello 150\n";
+ #endif
+ #if 0
+ std:cout << "Hello 151\n";
+ #elif 1
+ std:cout << "Hello 152\n"; //should not be filtered
+ #else
+ std:cout << "Hello 153\n";
+ #endif
+ #if 0
+ std:cout << "Hello 154\n";
+ #elif 0
+ std:cout << "Hello 155\n";
+ #else
+ std:cout << "Hello 156\n"; //should not be filtered
+ #endif
+ #endif
+ #endif
+
+ #if 0
+ #elif 0
+ std:cout << "Hello 157\n";
+ #if 0
+ std:cout << "Hello 158\n";
+ #else
+ std:cout << "Hello 159\n";
+ #endif
+ #if 1
+ std:cout << "Hello 160\n";
+ #else
+ std:cout << "Hello 161\n";
+ #endif
+ #if 0
+ std:cout << "Hello 162\n";
+ #elif 1
+ std:cout << "Hello 163\n";
+ #else
+ std:cout << "Hello 164\n";
+ #endif
+ #if 0
+ std:cout << "Hello 165\n";
+ #elif 0
+ std:cout << "Hello 166\n";
+ #else
+ std:cout << "Hello 167\n";
+ #endif
+ #elif 0
+ std:cout << "Hello 168\n";
+ #if 0
+ std:cout << "Hello 169\n";
+ #else
+ std:cout << "Hello 170\n";
+ #endif
+ #if 1
+ std:cout << "Hello 171\n";
+ #else
+ std:cout << "Hello 172\n";
+ #endif
+ #if 0
+ std:cout << "Hello 173\n";
+ #elif 1
+ std:cout << "Hello 174\n";
+ #else
+ std:cout << "Hello 175\n";
+ #endif
+ #if 0
+ std:cout << "Hello 176\n";
+ #elif 0
+ std:cout << "Hello 177\n";
+ #else
+ std:cout << "Hello 178\n";
+ #endif
+ #else
+ std:cout << "Hello 179\n"; //should not be filtered
+ #if 0
+ std:cout << "Hello 180\n";
+ #else
+ std:cout << "Hello 181\n"; //should not be filtered
+ #endif
+ #if 1
+ std:cout << "Hello 182\n"; //should not be filtered
+ #else
+ std:cout << "Hello 183\n";
+ #endif
+ #if 0
+ std:cout << "Hello 184\n";
+ #elif 1
+ std:cout << "Hello 185\n"; //should not be filtered
+ #else
+ std:cout << "Hello 186\n";
+ #endif
+ #if 0
+ std:cout << "Hello 187\n";
+ #elif 0
+ std:cout << "Hello 188\n";
+ #else
+ std:cout << "Hello 189\n"; //should not be filtered
+ #endif
+ #endif
+#endif
+
+#endregion
+
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfdefTests_FilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfdefTests_FilteredSourceCode.cpp
new file mode 100644
index 0000000..f25a568
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfdefTests_FilteredSourceCode.cpp
@@ -0,0 +1,155 @@
+//std:cout' that are commented "should not be filtered" should not be filtered. The others we assume that have to be filtered
+
+
+ std:cout << "Hello 01\n"; //should not be filtered
+
+ std:cout << "Hello 02\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 03\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 14\n"; //should not be filtered
+
+ std:cout << "Hello 15\n"; //should not be filtered
+
+ std:cout << "Hello 16\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 17\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 21\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 23\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 24\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 31\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 35\n"; //should not be filtered
+
+ std:cout << "Hello 36\n"; //should not be filtered
+
+ std:cout << "Hello 37\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 38\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 41\n"; //should not be filtered
+
+
+
+
+
+
+
+ std:cout << "Hello 42\n"; //should not be filtered
+
+ std:cout << "Hello 43\n"; //should not be filtered
+
+
+
+ std:cout << "Hello 44\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
+ std:cout << "Hello 47\n"; //should not be filtered
+
+
+
+
+
+
+
+
+
+
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfdefTests_UnFilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfdefTests_UnFilteredSourceCode.cpp
new file mode 100644
index 0000000..d0c1a49
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/ConditionalInclusionsIfdefTests_UnFilteredSourceCode.cpp
@@ -0,0 +1,156 @@
+//std:cout' that are commented "should not be filtered" should not be filtered. The others we assume that have to be filtered
+
+#ifdef DEBUG
+ std:cout << "Hello 01\n"; //should not be filtered
+ #undef DEBUG
+ std:cout << "Hello 02\n"; //should not be filtered
+#endif
+
+#ifndef DEBUG
+ std:cout << "Hello 03\n"; //should not be filtered
+#elif 0
+ std:cout << "Hello 04\n";
+#endif
+
+#define DEBUG
+#undef DEBUG
+#define VER
+
+#ifdef DEBUG
+ #define CLIENT1 //tests that defines during a discarded section are not added to the define list
+ std:cout << "Hello 05\n";
+ #ifdef VER
+ std:cout << "Hello 06\n";
+ #undef VER
+ std:cout << "Hello 07\n";
+ #endif
+
+ #ifndef VER
+ std:cout << "Hello 08\n";
+ #elif 0
+ std:cout << "Hello 09\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 10\n";
+ #elif 1
+ std:cout << "Hello 12\n";
+ #elif 1
+ std:cout << "Hello 13\n";
+ #endif
+#elif 1
+ std:cout << "Hello 14\n"; //should not be filtered
+ #ifdef VER
+ std:cout << "Hello 15\n"; //should not be filtered
+ #undef VER
+ std:cout << "Hello 16\n"; //should not be filtered
+ #endif
+
+ #ifndef VER
+ std:cout << "Hello 17\n"; //should not be filtered
+ #elif 0
+ std:cout << "Hello 18\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 19\n";
+ #elif 0
+ std:cout << "Hello 20\n";
+ #elif 1
+ #define VER
+ std:cout << "Hello 21\n"; //should not be filtered
+ #if 0
+ std:cout << "Hello 22\n";
+ #else
+ std:cout << "Hello 23\n"; //should not be filtered
+ #endif
+
+ #if 1
+ std:cout << "Hello 24\n"; //should not be filtered
+ #else
+ std:cout << "Hello 25\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 26\n";
+ #elif 0
+ std:cout << "Hello 27\n";
+ #elif 0
+ std:cout << "Hello 28\n";
+ #elif 0
+ std:cout << "Hello 29\n";
+ #elif 0
+ std:cout << "Hello 30\n";
+ #elif 1
+ std:cout << "Hello 31\n"; //should not be filtered
+ #else
+ std:cout << "Hello 32\n";
+ #endif
+
+ #if 0
+ std:cout << "Hello 33\n";
+ #elif 0
+ std:cout << "Hello 34\n";
+ #else
+ std:cout << "Hello 35\n"; //should not be filtered
+ #ifdef VER
+ std:cout << "Hello 36\n"; //should not be filtered
+ #undef VER
+ std:cout << "Hello 37\n"; //should not be filtered
+ #endif
+
+ #ifndef VER
+ std:cout << "Hello 38\n"; //should not be filtered
+ #elif 0
+ std:cout << "Hello 39\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 40\n";
+ #elif 1
+ std:cout << "Hello 41\n"; //should not be filtered
+ #elif 1
+ std:cout << "Hello 42\n";
+ #endif
+
+ #define VER
+
+ #if defined VER
+ std:cout << "Hello 42\n"; //should not be filtered
+ #undef VER
+ std:cout << "Hello 43\n"; //should not be filtered
+ #endif
+
+ #if !defined VER
+ std:cout << "Hello 44\n"; //should not be filtered
+ #elif 0
+ std:cout << "Hello 45\n";
+ #endif
+
+ #define VER
+ #undef VER
+
+ #ifdef VER
+ std:cout << "Hello 46\n";
+ #elif 1
+ std:cout << "Hello 47\n"; //should not be filtered
+ #elif 1
+ std:cout << "Hello 48\n";
+ #endif
+
+ #endif
+ #elif 1
+ std:cout << "Hello 49\n";
+ #endif
+#elif 1
+ std:cout << "Hello 50\n";
+#endif
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/MultiLineCommentTest_FilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/MultiLineCommentTest_FilteredSourceCode.cpp
new file mode 100644
index 0000000..502a818
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/MultiLineCommentTest_FilteredSourceCode.cpp
@@ -0,0 +1,19 @@
+ std::cout << "Hello World 01\n"; //not filtered
+
+
+
+
+
+ std::cout << "Hello World 04\n"; //not filtered
+*/ //this is technically a syntax error //not filtered
+ std::cout << "Hello World 05\n"; //not filtered
+
+
+
+
+
+
+
+
+
+ std::cout << "Hello World 10\n"; //not filtered
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/MultiLineCommentTest_UnFilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/MultiLineCommentTest_UnFilteredSourceCode.cpp
new file mode 100644
index 0000000..45d6b29
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/MultiLineCommentTest_UnFilteredSourceCode.cpp
@@ -0,0 +1,19 @@
+ std::cout << "Hello World 01\n"; //not filtered
+/*
+ std::cout << "Hello World 02\n";
+
+ std::cout << "Hello World 03\n";
+*/
+ std::cout << "Hello World 04\n"; //not filtered
+*/ //this is technically a syntax error //not filtered
+ std::cout << "Hello World 05\n"; //not filtered
+/*
+ std::cout << "Hello World 06\n";
+
+ std::cout << "Hello World 07\n";
+/*
+ std::cout << "Hello World 08\n";
+
+ std::cout << "Hello World 09\n";
+*/
+ std::cout << "Hello World 10\n"; //not filtered
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/QuotedStringsFilterTest_FilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/QuotedStringsFilterTest_FilteredSourceCode.cpp
new file mode 100644
index 0000000..cb6c95f
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/QuotedStringsFilterTest_FilteredSourceCode.cpp
@@ -0,0 +1,15 @@
+#include "header"
+
+cout << << newline << << endl;
+cout << << tab << << endl;
+cout << << backspace << << endl;
+cout << << backslash << << endl;
+cout << << nullChar << << endl;
+
+
+const wchar_t* raw_wide = ;
+const char* good_parens = ;
+const wchar_t* newline =
+;
+char str[] = ;
+const wchar_t* raw_wide = + ;
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/QuotedStringsFilterTest_UnFilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/QuotedStringsFilterTest_UnFilteredSourceCode.cpp
new file mode 100644
index 0000000..64f07a9
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/QuotedStringsFilterTest_UnFilteredSourceCode.cpp
@@ -0,0 +1,15 @@
+#include "header"
+
+cout << "Newline character: " << newline << "ending" << endl;
+cout << "Tab character: " << tab << "ending" << endl;
+cout << "Backspace character: " << backspace << "ending" << endl;
+cout << "Backslash character: " << backslash << "ending" << endl;
+cout << "Null character: " << nullChar << "ending" << endl;
+
+
+const wchar_t* raw_wide = LR"(An unescaped " character)";
+const char* good_parens = R"xyz()")xyz";
+const wchar_t* newline = LR"(hello
+goodbye)";
+char str[] = "12" "34";
+const wchar_t* raw_wide = LR"(An unescaped " character)" + R"(An unescaped " character)";
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/SingleLineCommentFilterTest_FilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/SingleLineCommentFilterTest_FilteredSourceCode.cpp
new file mode 100644
index 0000000..54de660
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/SingleLineCommentFilterTest_FilteredSourceCode.cpp
@@ -0,0 +1,7 @@
+
+
+std::cout << "Hello World 02\n";
+
+std::cout << "Hello World 03\n";
+
+std::cout << "Hello World 04\n";
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/SourceFiltering/SingleLineCommentFilterTest_UnFilteredSourceCode.cpp b/BoostTestAdapterNunit/Resources/SourceFiltering/SingleLineCommentFilterTest_UnFilteredSourceCode.cpp
new file mode 100644
index 0000000..cf0abcf
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/SourceFiltering/SingleLineCommentFilterTest_UnFilteredSourceCode.cpp
@@ -0,0 +1,7 @@
+//std::cout << "Hello World 01\n";
+
+std::cout << "Hello World 02\n"; //comment
+
+std::cout << "Hello World 03\n";
+
+std::cout << "Hello World 04\n";
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Resources/TestLists/empty.test.list.xml b/BoostTestAdapterNunit/Resources/TestLists/empty.test.list.xml
new file mode 100644
index 0000000..e4c5bb8
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/TestLists/empty.test.list.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/BoostTestAdapterNunit/Resources/TestLists/sample.test.list.xml b/BoostTestAdapterNunit/Resources/TestLists/sample.test.list.xml
new file mode 100644
index 0000000..a06d53c
--- /dev/null
+++ b/BoostTestAdapterNunit/Resources/TestLists/sample.test.list.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/SingleLineCommentFilterTest.cs b/BoostTestAdapterNunit/SingleLineCommentFilterTest.cs
new file mode 100644
index 0000000..e82e409
--- /dev/null
+++ b/BoostTestAdapterNunit/SingleLineCommentFilterTest.cs
@@ -0,0 +1,28 @@
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapterNunit.Utility;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ public class SingleLineCommentFilterTest : SourceFilterTestBase
+ {
+ ///
+ /// Tests the correct operation of the single line comments filter on C++ source files
+ ///
+ [Test]
+ public void SingleLineCommentFilter()
+ {
+ const string nameSpace = "BoostTestAdapterNunit.Resources.SourceFiltering.";
+ const string unfilteredSourceCodeResourceName = "SingleLineCommentFilterTest_UnFilteredSourceCode.cpp";
+ const string filteredSourceCodeResourceName = "SingleLineCommentFilterTest_FilteredSourceCode.cpp";
+
+ FilterAndCompareResources(
+ new SingleLineCommentFilter(),
+ null,
+ nameSpace + unfilteredSourceCodeResourceName,
+ nameSpace + filteredSourceCodeResourceName
+ );
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/SourceFiltersTest.cs b/BoostTestAdapterNunit/SourceFiltersTest.cs
new file mode 100644
index 0000000..ca2f8ca
--- /dev/null
+++ b/BoostTestAdapterNunit/SourceFiltersTest.cs
@@ -0,0 +1,31 @@
+using BoostTestAdapter.SourceFilter;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ class SourceFiltersTest
+ {
+ ///
+ /// Given an source line, the QuotedStringFilter filters out any quoted text.
+ ///
+ /// Test aims:
+ /// - Ensure that the QuotedStringsFilter filters out quoted text as expected.
+ ///
+ [TestCase("std::cout << \"hello world\" << std::endl;", Result = "std::cout << << std::endl;")]
+ [TestCase("BOOST_MESSAGE(\"This is a \\\"test\");", Result = "BOOST_MESSAGE();")]
+ [TestCase("BOOST_MESSAGE(\"This is a \\\"test\",\"\");", Result = "BOOST_MESSAGE(,);")]
+ [TestCase("BOOST_MESSAGE(\"This is a \\\"test\",\"This is a test\");", Result = "BOOST_MESSAGE(,);")]
+ [TestCase("BOOST_MESSAGE(\"This is a test\\\"\");", Result = "BOOST_MESSAGE();")]
+ [TestCase("BOOST_MESSAGE(\"This is a just a\\\" test\");", Result = "BOOST_MESSAGE();")]
+ [TestCase("#include \"stdafx.h\"", Result = "#include \"stdafx.h\"")]
+ public string FilterQuotedString(string input)
+ {
+ ISourceFilter filter = new QuotedStringsFilter();
+ CppSourceFile cppSourceFile = new CppSourceFile(){SourceCode = input};
+ filter.Filter(cppSourceFile, null);
+ return cppSourceFile.SourceCode;
+ }
+
+ }
+}
diff --git a/BoostTestAdapterNunit/TestDiscovererNunit.cs b/BoostTestAdapterNunit/TestDiscovererNunit.cs
new file mode 100644
index 0000000..f4f67b9
--- /dev/null
+++ b/BoostTestAdapterNunit/TestDiscovererNunit.cs
@@ -0,0 +1,246 @@
+using System.Collections.Generic;
+using System.Linq;
+using BoostTestAdapter;
+using BoostTestAdapter.SourceFilter;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using BoostTestAdapterNunit.Fakes;
+using BoostTestAdapterNunit.Utility;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using NUnit.Framework;
+using QualifiedNameBuilder = BoostTestAdapter.Utility.QualifiedNameBuilder;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ internal class TestDiscovererNunit
+ {
+ #region Test Setup/TearDown
+
+ [SetUp]
+ public void SetUp()
+ {
+ Logger.Initialize(new ConsoleMessageLogger());
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ Logger.Shutdown();
+ }
+
+ #endregion Test Setup/TearDown
+
+ #region Test Data
+
+ private const string DefaultSource = "TestCaseCheck.exe";
+
+ #endregion Test Data
+
+ #region Helper Methods
+
+ ///
+ /// Applies the discovery process over the provided DummySolution
+ ///
+ /// The dummy solution on which to apply test discovery
+ /// The list of tests which were discovered from the dummy solution
+ private IList DiscoverTests(DummySolution solution)
+ {
+ DefaultTestCaseDiscoverySink discoverySink = new DefaultTestCaseDiscoverySink();
+
+ ISourceFilter[] filters = new ISourceFilter[]
+ {
+ new QuotedStringsFilter(),
+ new MultilineCommentFilter(),
+ new SingleLineCommentFilter(),
+ new ConditionalInclusionsFilter(new ExpressionEvaluation())
+ };
+
+ BoostTestDiscovererInternal discoverer = new BoostTestDiscovererInternal(solution.Provider, filters);
+ IDictionary solutionInfo = discoverer.PrepareTestCaseData(new string[] { solution.Source });
+ discoverer.GetBoostTests(solutionInfo, discoverySink);
+
+ return discoverySink.Tests.ToList();
+ }
+
+ ///
+ /// Assert that a trait with the provided name exists.
+ ///
+ /// The testcase which contains the trait collection in question
+ /// The name of the trait to look for
+ private void AssertTrait(TestCase testCase, string name)
+ {
+ Assert.That(testCase.Traits.Any((trait) => { return (trait.Name == name); }), Is.True);
+ }
+
+ ///
+ /// Assert that a trait with the provided name and value exists.
+ ///
+ /// The testcase which contains the trait collection in question
+ /// The name of the trait to look for
+ /// The value the looked-up trait should have
+ private void AssertTrait(TestCase testCase, string name, string value)
+ {
+ Assert.That(testCase.Traits.Any((trait) => { return (trait.Name == name) && (trait.Value == value); }), Is.True);
+ }
+
+ ///
+ /// Assert that a 'TestSuite' trait with the provided value exists.
+ ///
+ /// The testcase which contains the trait collection in question
+ /// The test suite value the looked-up trait should have
+ private void AssertTestSuite(TestCase testCase, string value)
+ {
+ AssertTrait(testCase, VSTestModel.TestSuiteTrait, value);
+ }
+
+ ///
+ /// Asserts general test case details
+ ///
+ /// The test case to test
+ /// The expected qualified name of the test case
+ private void AssertTestDetails(TestCase testCase, QualifiedNameBuilder qualifiedName)
+ {
+ AssertTestDetails(testCase, qualifiedName, null, -1);
+ }
+
+ ///
+ /// Asserts general test case details
+ ///
+ /// The test case to test
+ /// The expected qualified name of the test case
+ /// The expected source file path of the test case or null if not available
+ /// The expected line number of the test case or -1 if not available
+ private void AssertTestDetails(TestCase testCase, QualifiedNameBuilder qualifiedName, string sourceFile, int lineNumber)
+ {
+ Assert.That(testCase.DisplayName, Is.EqualTo(qualifiedName.Peek()));
+ Assert.That(testCase.FullyQualifiedName, Is.EqualTo(qualifiedName.ToString()));
+
+ string suite = qualifiedName.Pop().ToString();
+ if (!string.IsNullOrEmpty(suite))
+ {
+ AssertTestSuite(testCase, suite);
+ }
+ else
+ {
+ // The default 'Master Test Suite' trait value should be available
+ AssertTrait(testCase, VSTestModel.TestSuiteTrait);
+ }
+
+ if (!string.IsNullOrEmpty(sourceFile))
+ {
+ Assert.That(testCase.CodeFilePath, Is.EqualTo(sourceFile));
+ }
+
+ if (lineNumber > -1)
+ {
+ Assert.That(testCase.LineNumber, Is.EqualTo(lineNumber));
+ }
+ }
+
+ #endregion Helper Methods
+
+ #region GetBoostTestsCase
+
+ ///
+ /// Tests the proper identification of a testcase
+ ///
+ [Test]
+ public void CorrectDiscoveryGenericBoostTests()
+ {
+ using (DummySolution solution = new DummySolution(DefaultSource, "BoostUnitTestSample.cpp"))
+ {
+ #region excercise
+
+ IList tests = DiscoverTests(solution);
+
+ #endregion excercise
+
+ #region verify
+
+ AssertTestDetails(tests.Last(), QualifiedNameBuilder.FromString("my_test"), solution.SourceFileResourcePaths.First().TempSourcePath, 33);
+
+ #endregion verify
+ }
+ }
+
+ ///
+ /// Tests the correct discovery (and correct generation of the fully qaulifed name)
+ /// of tests for when Boost UTF macro BOOST_FIXTURE_TEST_SUITE is utilized
+ ///
+ [Test]
+ public void CorrectTestsDiscoveryForFIXTURE_TEST_SUITE()
+ {
+ using (DummySolution solution = new DummySolution(DefaultSource, "BoostFixtureTestSuite.cpp"))
+ {
+ #region exercise
+
+ /** The BoostFixtureSuiteTest.cpp file consists of 3 test cases: FixtureTest1, FixtureTest2 and BoostTest,
+ * BOOST_FIXTURE_TEST_SUITE -> FixtureSuite1
+ * -->BoostTest1
+ * -->BoostTest2
+ * -> Master Suite
+ * -->BoostTest3
+ * BOOST_FIXTURE_TEST_SUITE -> FixtureSuite2
+ * -->Fixturetest_case1
+ * -->TemplatedTest (BOOST_AUTO_TEST_CASE_TEMPLATE)
+ * -->TemplatedTest (BOOST_AUTO_TEST_CASE_TEMPLATE)
+ * -->TemplatedTest (BOOST_AUTO_TEST_CASE_TEMPLATE)
+ */
+
+ IList testList = DiscoverTests(solution);
+
+ #endregion exercise
+
+ #region verification
+
+ AssertTestDetails(testList[0], QualifiedNameBuilder.FromString("FixtureSuite1/BoostTest1"));
+ AssertTestDetails(testList[1], QualifiedNameBuilder.FromString("FixtureSuite1/BoostTest2"));
+ AssertTestDetails(testList[2], QualifiedNameBuilder.FromString("BoostTest3"));
+ AssertTestDetails(testList[3], QualifiedNameBuilder.FromString("FixtureSuite2/Fixturetest_case1"));
+ AssertTestDetails(testList[4], QualifiedNameBuilder.FromString("FixtureSuite2/TemplatedTest"));
+ AssertTestDetails(testList[5], QualifiedNameBuilder.FromString("FixtureSuite2/TemplatedTest"));
+ AssertTestDetails(testList[6], QualifiedNameBuilder.FromString("FixtureSuite2/TemplatedTest"));
+
+ #endregion verification
+ }
+ }
+
+ ///
+ /// Tests the correct discovery (and correct generation of the fully qaulifed name)
+ /// of tests for when Boost UTF macro BOOST_FIXTURE_TEST_CASE is utilized
+ ///
+ [Test]
+ public void CorrectTestsDiscoveryForFIXTURE_TEST_CASE()
+ {
+ using (DummySolution solution = new DummySolution(DefaultSource, "BoostFixtureTestCase.cpp"))
+ {
+ #region excercise
+
+ /** The BoostFixtureTestCase.cpp file consists of 4 test cases: BoostUnitTest1, Fixturetest_case1, Fixturetest_case1 and Fixturetest_case1,
+ * BOOST_AUTO_TEST_SUITE -> Suit1
+ * -->BoostUnitTest1
+ * -->Fixturetest_case1
+ * -->Fixturetest_case2
+ * -> Master Suite
+ * -->Fixturetest_case3
+ */
+
+ IList testList = DiscoverTests(solution);
+
+ #endregion excercise
+
+ #region verify
+
+ AssertTestDetails(testList[0], QualifiedNameBuilder.FromString("Suit1/BoostUnitTest1"));
+ AssertTestDetails(testList[1], QualifiedNameBuilder.FromString("Suit1/Fixturetest_case1"));
+ AssertTestDetails(testList[2], QualifiedNameBuilder.FromString("Suit1/Fixturetest_case2"));
+ AssertTestDetails(testList[3], QualifiedNameBuilder.FromString("Fixturetest_case3"));
+
+ #endregion verify
+ }
+ }
+
+ #endregion GetBoostTestsCase
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Utility/DummySolution.cs b/BoostTestAdapterNunit/Utility/DummySolution.cs
new file mode 100644
index 0000000..f25d234
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/DummySolution.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BoostTestAdapter.Utility.VisualStudio;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ ///
+ /// Builds and emulates a Visual Studio solution with a single test project.
+ /// Copies resource files to their intended location for later reference.
+ ///
+ public class DummySolution : IDisposable
+ {
+ ///
+ /// Constructor
+ ///
+ /// The fully qualified path of a test source
+ /// The embedded resource file name which is to be considered as the sole source file for this test project
+ public DummySolution(string source, string sourceFileName) :
+ this(source, new string[] { sourceFileName })
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The fully qualified path of a test source
+ /// An enumeration of embedded resource file names which are to be considered as source files for this test project
+ public DummySolution(string source, IEnumerable sourceFileNames)
+ {
+ this.Source = source;
+ this.SourceFiles = sourceFileNames;
+
+ this.SourceFileResourcePaths = new List();
+
+ SetUpSolutionEnvironment();
+ SetUpSolution();
+ }
+
+ ///
+ /// Sets up the solution environment by copying the embedded resources to disk
+ /// for later reference by the necessary algorithms.
+ ///
+ private void SetUpSolutionEnvironment()
+ {
+ foreach (string source in this.SourceFiles)
+ {
+ this.SourceFileResourcePaths.Add( new DummySourceFile(source) );
+ }
+ }
+
+ ///
+ /// Sets up a fake Visual Studio environment consisting of a solution with 1 test project whose
+ /// primary output is the specified construction-time source and the respective code file sources.
+ ///
+ private void SetUpSolution()
+ {
+ IList sources = this.SourceFileResourcePaths.Select(source => source.TempSourcePath).ToList();
+
+ IVisualStudio vs = new FakeVisualStudioInstanceBuilder().
+ Solution(
+ new FakeSolutionBuilder().
+ Name("SampleSolution").
+ Project(
+ new FakeProjectBuilder().
+ Name("SampleProject").
+ PrimaryOutput(this.Source).
+ Sources(sources)
+ )
+ ).
+ Build();
+
+ this.Provider = new DummyVSProvider(vs);
+ }
+
+ ///
+ /// The test source
+ ///
+ public string Source { get; private set; }
+
+ ///
+ /// The test code source files
+ ///
+ public IEnumerable SourceFiles { get; private set; }
+
+ ///
+ /// The respective DummySourceFiles for the test code source files
+ ///
+ public ICollection SourceFileResourcePaths { get; private set; }
+
+ ///
+ /// The IVisualStudio instance provider for this DummySolution
+ ///
+ public IVisualStudioInstanceProvider Provider { get; private set; }
+
+ #region System.IDisposable
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (DummySourceFile resource in this.SourceFileResourcePaths)
+ {
+ resource.Dispose();
+ }
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose()
+ {
+ this.Dispose(true);
+ }
+
+ #endregion System.IDisposable
+ }
+}
diff --git a/BoostTestAdapterNunit/Utility/DummySourceFile.cs b/BoostTestAdapterNunit/Utility/DummySourceFile.cs
new file mode 100644
index 0000000..112e431
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/DummySourceFile.cs
@@ -0,0 +1,65 @@
+using System;
+using System.IO;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ ///
+ /// Emulates a CPP source file. Copies an embedded resource as an OS temporary file using
+ /// the embedded resource name as the file name.
+ ///
+ /// Performs cleanup on calling Dispose.
+ ///
+ public class DummySourceFile : IDisposable
+ {
+ ///
+ /// Default resource namespace
+ ///
+ private const string DefaultResourceNamespace = "BoostTestAdapterNunit.Resources.CppSources";
+
+ ///
+ /// Constructor
+ ///
+ /// The embedded resource file name located in the default resource namespace
+ public DummySourceFile(string filename) :
+ this(DefaultResourceNamespace, filename)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The embedded resource namespace
+ /// The embedded resource file name located in nameSpace
+ public DummySourceFile(string nameSpace, string filename)
+ {
+ this.TempSourcePath = TestHelper.CopyEmbeddedResourceToDirectory(nameSpace, filename, Path.GetTempPath());
+ }
+
+ ///
+ /// The temporary file path of the copied embedded resource
+ ///
+ public string TempSourcePath { get; private set; }
+
+ #region IDisposable
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (!string.IsNullOrEmpty(this.TempSourcePath) && File.Exists(this.TempSourcePath))
+ {
+ File.Delete(this.TempSourcePath);
+ }
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose()
+ {
+ this.Dispose(true);
+ }
+
+ #endregion IDisposable
+ }
+}
diff --git a/BoostTestAdapterNunit/Utility/DummyVSProvider.cs b/BoostTestAdapterNunit/Utility/DummyVSProvider.cs
new file mode 100644
index 0000000..85e4929
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/DummyVSProvider.cs
@@ -0,0 +1,28 @@
+using BoostTestAdapter.Utility.VisualStudio;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ ///
+ /// A stub implementation for the IVisualStudioInstanceProvider interface.
+ /// Provides an IVisualStudio instance which is defined at construction time.
+ ///
+ public class DummyVSProvider : IVisualStudioInstanceProvider
+ {
+ ///
+ /// Constructor
+ ///
+ /// The IVisualStudio instance which is to be provided on a 'Get()' call
+ public DummyVSProvider(IVisualStudio vs)
+ {
+ this.Instance = vs;
+ }
+
+ #region IVisualStudioInstanceProvider
+
+ public IVisualStudio Instance { get; private set; }
+
+ #endregion IVisualStudioInstanceProvider
+ }
+
+}
diff --git a/BoostTestAdapterNunit/Utility/ReadOnlyDictionaryWrapper.cs b/BoostTestAdapterNunit/Utility/ReadOnlyDictionaryWrapper.cs
new file mode 100644
index 0000000..5195724
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/ReadOnlyDictionaryWrapper.cs
@@ -0,0 +1,79 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ ///
+ /// A wrapper class for a generic Dictionary which provides a read-only access interface.
+ ///
+ /// The key type
+ /// The mapped-value type
+ public class ReadOnlyDictionaryWrapper : IReadOnlyDictionary
+ {
+ #region Members
+
+ ///
+ /// The wrapped Dictionary instance
+ ///
+ private IDictionary _dictionary = null;
+
+ #endregion Members
+
+ #region Constructors
+
+ ///
+ /// Constructor
+ ///
+ /// The Dictionary instance which will be wrapped
+ public ReadOnlyDictionaryWrapper(IDictionary dictionary)
+ {
+ this._dictionary = dictionary;
+ }
+
+ #endregion Constructors
+
+ #region IReadOnlyDictionary
+
+ public bool ContainsKey(TK key)
+ {
+ return this._dictionary.ContainsKey(key);
+ }
+
+ public IEnumerable Keys
+ {
+ get { return this._dictionary.Keys; }
+ }
+
+ public bool TryGetValue(TK key, out TV value)
+ {
+ return this._dictionary.TryGetValue(key, out value);
+ }
+
+ public IEnumerable Values
+ {
+ get { return this._dictionary.Values; }
+ }
+
+ public TV this[TK key]
+ {
+ get { return this._dictionary[key]; }
+ }
+
+ public int Count
+ {
+ get { return this._dictionary.Count; }
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return this._dictionary.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ #endregion IReadOnlyDictionary
+ }
+}
diff --git a/BoostTestAdapterNunit/Utility/SourceFilterTestBase.cs b/BoostTestAdapterNunit/Utility/SourceFilterTestBase.cs
new file mode 100644
index 0000000..6bd25ba
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/SourceFilterTestBase.cs
@@ -0,0 +1,44 @@
+using BoostTestAdapter.SourceFilter;
+using NUnit.Framework;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ ///
+ /// Base class which includes common functionality used throughout SourceFilerTests
+ ///
+ public class SourceFilterTestBase
+ {
+ ///
+ /// Given 2 embedded resources locations, filters the left-hand resource and checks whether the filtered output matches that of the right-hand resource.
+ ///
+ /// The source filter to apply
+ /// The preprocessor definitions which are to be used by the source filter
+ /// The left-hand embedded resource fully qualified location whose content will be filtered
+ /// The right-hand embedded resource fully qualified location whose content is used to compare the filtered result
+ protected void FilterAndCompareResources(ISourceFilter filter, Defines defines, string lhsEmbeddedResource, string rhsEmbeddedResource)
+ {
+ FilterAndCompare(
+ filter,
+ defines,
+ TestHelper.ReadEmbeddedResource(lhsEmbeddedResource),
+ TestHelper.ReadEmbeddedResource(rhsEmbeddedResource)
+ );
+ }
+
+ ///
+ /// Given 2 strings, filters the left-hand string and checks whether the filtered output matches that of the right-hand string.
+ ///
+ /// The source filter to apply
+ /// The preprocessor definitions which are to be used by the source filter
+ /// The left-hand string whose value will be filtered
+ /// The right-hand string whose value is used to compare the filtered result
+ protected void FilterAndCompare(ISourceFilter filter, Defines defines, string lhs, string rhs)
+ {
+ var cppSourceFile = new CppSourceFile(){SourceCode = lhs};
+ filter.Filter(cppSourceFile, defines);
+
+ Assert.AreEqual(cppSourceFile.SourceCode, rhs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoostTestAdapterNunit/Utility/TestHelper.cs b/BoostTestAdapterNunit/Utility/TestHelper.cs
new file mode 100644
index 0000000..f9ce554
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/TestHelper.cs
@@ -0,0 +1,71 @@
+using System.IO;
+using System.Reflection;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ internal static class TestHelper
+ {
+ ///
+ /// Loads in an embedded resource as a stream.
+ ///
+ /// The fully qualified path to the embedded resource
+ /// The embedded resource as a stream
+ public static Stream LoadEmbeddedResource(string path)
+ {
+ // Reference: https://support.microsoft.com/en-us/kb/319292
+
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ return assembly.GetManifestResourceStream(path);
+ }
+
+ ///
+ /// Reads in an embedded resource as a string.
+ ///
+ /// The fully qualified path to the embedded resource
+ /// The whole content of the embedded resource as a string
+ public static string ReadEmbeddedResource(string path)
+ {
+ using (Stream stream = LoadEmbeddedResource(path))
+ {
+ StreamReader reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+ }
+
+ ///
+ /// Helper method so as to copy an embedded resource to the path supplied
+ ///
+ /// namespace of the where the resource file is located at
+ /// the filename of the embedded resource that needs to copied over
+ /// the path where the file need to be copied over to
+ /// The output path of the successfully copied resource
+ static public string CopyEmbeddedResourceToDirectory(string nameSpace, string resourceName, string outputDirectoryPath)
+ {
+ if (!Directory.Exists(outputDirectoryPath))
+ {
+ Assert.Fail("The requested output directory is invalid");
+ }
+
+ string input = nameSpace + (nameSpace.EndsWith(".") ? "" : ".") + resourceName;
+ string output = Path.Combine(outputDirectoryPath, resourceName);
+
+ using (Stream stream = LoadEmbeddedResource(input))
+ using (FileStream fileStream = new FileStream(output, FileMode.Create, FileAccess.Write))
+ {
+ if (stream == null)
+ {
+ Assert.Fail("Failed to load the requested embedded resource. Please check that the resource exists and the supplied embedded file namespace is correct");
+ }
+ stream.CopyTo(fileStream);
+ }
+
+ if (!File.Exists(output))
+ {
+ Assert.Fail("Failed to copy embedded resource " + nameSpace + resourceName + " to output directory " + outputDirectoryPath);
+ }
+
+ return output;
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/Utility/VisualStudioInstanceBuilders.cs b/BoostTestAdapterNunit/Utility/VisualStudioInstanceBuilders.cs
new file mode 100644
index 0000000..553db59
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/VisualStudioInstanceBuilders.cs
@@ -0,0 +1,184 @@
+using System.Collections.Generic;
+using FakeItEasy;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit.Utility
+{
+ ///
+ /// Builds fake IVisualStudio instances
+ ///
+ public class FakeVisualStudioInstanceBuilder
+ {
+ private ISolution _solution = null;
+
+ ///
+ /// Assigns a solution to the VisualStudio instance
+ ///
+ /// The fake solution builder which is to be registered with this fake VisualStudio instance
+ /// this
+ public FakeVisualStudioInstanceBuilder Solution(FakeSolutionBuilder solution)
+ {
+ return this.Solution(solution.Build());
+ }
+
+ ///
+ /// Assigns a solution to the VisualStudio instance
+ ///
+ /// The solution which is to be registered with this fake VisualStudio instance
+ /// this
+ public FakeVisualStudioInstanceBuilder Solution(ISolution solution)
+ {
+ this._solution = solution;
+ return this;
+ }
+
+ ///
+ /// Commits any pending changes and builds a fake IVisualStudio instance.
+ ///
+ /// A fake IVisualStudio instance consisting of the previously registered solutions
+ public IVisualStudio Build()
+ {
+ IVisualStudio fake = A.Fake();
+
+ A.CallTo(() => fake.Version).Returns("MockVisualStudioInstance");
+ A.CallTo(() => fake.Solution).Returns(this._solution);
+
+ return fake;
+ }
+ }
+
+ ///
+ /// Builds fake ISolution instances
+ ///
+ public class FakeSolutionBuilder
+ {
+ private string _name = string.Empty;
+ private readonly IList _projects = new List();
+
+ ///
+ /// Identifies the name of the solution
+ ///
+ /// The name for the solution
+ /// this
+ public FakeSolutionBuilder Name(string name)
+ {
+ this._name = name;
+ return this;
+ }
+
+ ///
+ /// Assigns a project to the solution
+ ///
+ /// The fake project builder which is to be registered with this fake solution instance
+ /// this
+ public FakeSolutionBuilder Project(FakeProjectBuilder project)
+ {
+ return this.Project(project.Build());
+ }
+
+ ///
+ /// Assigns a project to the solution
+ ///
+ /// The project which is to be registered with this fake solution instance
+ /// this
+ public FakeSolutionBuilder Project(IProject project)
+ {
+ this._projects.Add(project);
+ return this;
+ }
+
+ ///
+ /// Commits any pending changes and builds a fake ISolution instance.
+ ///
+ /// A fake ISolution instance consisting of the previously registered name and projects
+ public ISolution Build()
+ {
+ ISolution fake = A.Fake();
+
+ A.CallTo(() => fake.Name).Returns(this._name);
+ A.CallTo(() => fake.Projects).Returns(this._projects);
+
+ return fake;
+ }
+ }
+
+ ///
+ /// Builds fake IProject instances
+ ///
+ public class FakeProjectBuilder
+ {
+ private string _name = string.Empty;
+ private string _primaryOutput = string.Empty;
+ private IList _sourcesFullFilePath = new List();
+ private Defines _definitions = new Defines();
+
+ ///
+ /// Identifies the name of the project
+ ///
+ /// The name for the project
+ /// this
+ public FakeProjectBuilder Name(string name)
+ {
+ this._name = name;
+ return this;
+ }
+
+ ///
+ /// Identifies the project's primary output location
+ ///
+ /// The primary output path
+ /// this
+ public FakeProjectBuilder PrimaryOutput(string output)
+ {
+ this._primaryOutput = output;
+ return this;
+ }
+
+ ///
+ /// Identifies the project's configured preprocessor definitions
+ ///
+ /// The preprocessor definitions in use by this project
+ /// this
+ public FakeProjectBuilder Defines(Defines definitions)
+ {
+ this._definitions = definitions;
+ return this;
+ }
+
+ ///
+ /// Assigns the projects resources (source files, filters etc.)
+ ///
+ /// The sources which are to be registered with this fake project instance
+ /// this
+ public FakeProjectBuilder Sources(IList sourcesFullFilePath)
+ {
+ this._sourcesFullFilePath = sourcesFullFilePath;
+ return this;
+ }
+
+ ///
+ /// Commits any pending changes and builds a fake IProject instance.
+ ///
+ /// A fake IProject instance consisting of the previously registered output, definitions and sources
+ public IProject Build()
+ {
+ IProject fake = A.Fake();
+
+ A.CallTo(() => fake.Name).Returns(this._name);
+
+ IProjectConfiguration fakeConfiguration = A.Fake();
+ A.CallTo(() => fakeConfiguration.PrimaryOutput).Returns(this._primaryOutput);
+
+ IVCppCompilerOptions fakeCompilerOptions = A.Fake();
+ A.CallTo(() => fakeCompilerOptions.PreprocessorDefinitions).Returns(this._definitions);
+
+ A.CallTo(() => fakeConfiguration.CppCompilerOptions).Returns(fakeCompilerOptions);
+
+ A.CallTo(() => fake.ActiveConfiguration).Returns(fakeConfiguration);
+ A.CallTo(() => fake.SourceFiles).Returns(this._sourcesFullFilePath);
+
+ return fake;
+ }
+ }
+
+}
diff --git a/BoostTestAdapterNunit/Utility/Xml/XmlComparison.cs b/BoostTestAdapterNunit/Utility/Xml/XmlComparison.cs
new file mode 100644
index 0000000..0746147
--- /dev/null
+++ b/BoostTestAdapterNunit/Utility/Xml/XmlComparison.cs
@@ -0,0 +1,239 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml;
+using NUnit.Framework;
+
+namespace BoostTestAdapterNunit.Utility.Xml
+{
+ ///
+ /// Interface which identifies a mechanism for filtering particular xml nodes.
+ ///
+ public interface IXmlNodeFilter
+ {
+ ///
+ /// States whether the node should be filtered or not
+ ///
+ /// The node to test
+ /// true if the node should be filtered; false otherwise
+ bool Filter(XmlNode node);
+ }
+
+ #region Default IXMLNodeFilter Implementations
+
+ ///
+ /// IXmlNodeFilter implementation. Does not filter any xml node.
+ ///
+ public class NullFilter : IXmlNodeFilter
+ {
+ public bool Filter(XmlNode node)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// IXmlNodeFilter implementation. Filter each and every xml node.
+ ///
+ public class AllFilter : IXmlNodeFilter
+ {
+ public bool Filter(XmlNode node)
+ {
+ return true;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// IXmlNodeFilter implementation. Filters xml nodes based on their NodeType.
+ ///
+ public class XmlNodeTypeFilter : IXmlNodeFilter
+ {
+ ///
+ /// Constructor
+ ///
+ /// The collection of XmlNodeTypes which should be filtered out
+ public XmlNodeTypeFilter(IEnumerable filtered)
+ {
+ this.FilteredTypes = filtered;
+ }
+
+ ///
+ /// A collection of XmlNodeTypes which are to be filtered out
+ ///
+ public IEnumerable FilteredTypes { get; private set; }
+
+ #region IXmlNodeFilter
+
+ ///
+ /// States whether the node should be filtered or not
+ ///
+ /// The node to test
+ /// true if the node should be filtered; false otherwise
+ public bool Filter(XmlNode node)
+ {
+ return Filter(node.NodeType);
+ }
+
+ #endregion IXmlNodeFilter
+
+ ///
+ /// States whether the node type should be filtered or not
+ ///
+ /// The node type to test
+ /// true if the node type should be filtered; false otherwise
+ public bool Filter(XmlNodeType type)
+ {
+ return this.FilteredTypes.Contains(type);
+ }
+
+ ///
+ /// Creates an XmlNodeTypeFilter which does not filter out anything
+ ///
+ public static XmlNodeTypeFilter None
+ {
+ get
+ {
+ return new XmlNodeTypeFilter(Enumerable.Empty());
+ }
+ }
+
+ ///
+ /// Creates a default XmlNodeTypeFilter used within most of the tests
+ ///
+ public static XmlNodeTypeFilter DefaultFilter
+ {
+ get
+ {
+ return new XmlNodeTypeFilter(new XmlNodeType[] { XmlNodeType.CDATA, XmlNodeType.Comment, XmlNodeType.ProcessingInstruction, XmlNodeType.XmlDeclaration });
+ }
+ }
+
+ }
+
+ public class XmlComparer
+ {
+
+ private IEnumerator GetXmlCollectionEnumerator(IEnumerable enumerable)
+ {
+ return (enumerable == null) ? null : enumerable.GetEnumerator();
+ }
+
+ ///
+ /// Loads an Xml string fragment
+ ///
+ /// the Xml string fragment
+ /// The Xml DOM representation
+ private XmlDocument LoadXml(string xml)
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(xml);
+
+ return doc;
+ }
+
+ ///
+ /// Compares the 2 Xml collections.
+ ///
+ /// The left-hand side Xml collection enumerator
+ /// The right-hand side Xml collection enumerator
+ /// The XmlNodeType filter to apply during comparison
+ private void CompareXML(IEnumerator lhs, IEnumerator rhs, IXmlNodeFilter filter)
+ {
+ bool lhsNext = lhs != null && lhs.MoveNext();
+ bool rhsNext = rhs != null && rhs.MoveNext();
+
+ while (lhsNext && rhsNext)
+ {
+ XmlNode lhsChild = (XmlNode)lhs.Current;
+ XmlNode rhsChild = (XmlNode)rhs.Current;
+
+ if (filter.Filter(lhsChild))
+ {
+ lhsNext = lhs.MoveNext();
+ continue;
+ }
+ else if (filter.Filter(rhsChild))
+ {
+ rhsNext = rhs.MoveNext();
+ continue;
+ }
+
+ _CompareXML(lhsChild, rhsChild, filter);
+
+ lhsNext = lhs.MoveNext();
+ rhsNext = rhs.MoveNext();
+ }
+
+ // Ensure that any remaining nodes are filtered
+ // and not important for the comparison process
+ while (lhsNext)
+ {
+ Assert.IsTrue(filter.Filter((XmlNode)lhs.Current));
+ lhsNext = lhs.MoveNext();
+ }
+
+ while (rhsNext)
+ {
+ Assert.IsTrue(filter.Filter((XmlNode)rhs.Current));
+ rhsNext = rhs.MoveNext();
+ }
+ }
+
+ ///
+ /// Internal version of CompareXML. Compares the 2 Xml subtrees.
+ ///
+ /// In contrast to the public CompareXML, this version avoids checking whether both elements should be filtered or not.
+ /// The left-hand side Xml subtree
+ /// The right-hand side Xml subtree
+ /// The XmlNodeType filter to apply during comparison
+ private void _CompareXML(XmlNode lhs, XmlNode rhs, IXmlNodeFilter filter)
+ {
+ Assert.AreEqual(lhs.NodeType, rhs.NodeType);
+
+ Assert.AreEqual(lhs.NamespaceURI, rhs.NamespaceURI);
+ Assert.AreEqual(lhs.LocalName, rhs.LocalName);
+
+ if (lhs.NodeType == XmlNodeType.CDATA || lhs.NodeType == XmlNodeType.Comment || lhs.NodeType == XmlNodeType.Text)
+ {
+ Assert.AreEqual(lhs.Value, rhs.Value);
+ }
+
+ CompareXML(GetXmlCollectionEnumerator(lhs.ChildNodes), GetXmlCollectionEnumerator(rhs.ChildNodes), filter);
+ CompareXML(GetXmlCollectionEnumerator(lhs.Attributes), GetXmlCollectionEnumerator(rhs.Attributes), filter);
+ }
+
+ ///
+ /// Compares the 2 Xml subtrees.
+ ///
+ /// The left-hand side Xml subtree
+ /// The right-hand side Xml subtree
+ /// The XmlNodeType filter to apply during comparison
+ public void CompareXML(XmlNode lhs, XmlNode rhs, IXmlNodeFilter filter)
+ {
+ bool lhsFilter = filter.Filter(lhs);
+ bool rhsFilter = filter.Filter(rhs);
+
+ Assert.AreEqual(lhsFilter, rhsFilter);
+
+ // At this point, lhsFilter and rhsFilter are known to be equal.
+ // If both elements should be filtered, avoid comparing the subtrees altogether.
+ if (!lhsFilter)
+ {
+ _CompareXML(lhs, rhs, filter);
+ }
+ }
+
+ ///
+ /// Compares the 2 Xml string fragments.
+ ///
+ /// The left-hand side Xml fragment
+ /// The right-hand side Xml fragment
+ /// The XmlNodeType filter to apply during comparison
+ public void CompareXML(string lhs, string rhs, IXmlNodeFilter filter)
+ {
+ this.CompareXML(LoadXml(lhs), LoadXml(rhs), filter);
+ }
+ }
+}
diff --git a/BoostTestAdapterNunit/VSTestModelTest.cs b/BoostTestAdapterNunit/VSTestModelTest.cs
new file mode 100644
index 0000000..f8442e7
--- /dev/null
+++ b/BoostTestAdapterNunit/VSTestModelTest.cs
@@ -0,0 +1,435 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BoostTestAdapter;
+using BoostTestAdapter.Boost.Results;
+using BoostTestAdapter.Boost.Results.LogEntryTypes;
+using BoostTestAdapter.Boost.Test;
+using BoostTestAdapter.Utility;
+using BoostTestAdapter.Utility.VisualStudio;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using NUnit.Framework;
+using BoostTestResult = BoostTestAdapter.Boost.Results.TestResult;
+using TestCase = BoostTestAdapter.Boost.Test.TestCase;
+using VSTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
+using VSTestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ public class VSTestModelTest
+ {
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.TestCase = new VSTestCase(DefaultTestName, ExecutorUri, DefaultSource);
+ }
+
+ #endregion Test Setup/Teardown
+
+ #region Test Data
+
+ private static readonly Uri ExecutorUri = BoostTestExecutor.ExecutorUri;
+ private const string DefaultTestName = "suite/test";
+ private const string DefaultSource = "void";
+
+ private VSTestCase TestCase { get; set; }
+
+ #endregion Test Data
+
+ #region Helper Classes
+
+ ///
+ /// BoostTestResult Builder. Provides a fluent-api interface to easily express BoostTestResult instances.
+ ///
+ private class BoostTestResultBuilder
+ {
+ public BoostTestResultBuilder()
+ {
+ this.Logs = new List();
+ }
+
+ #region Properties
+
+ private TestResultType ResultType { get; set; }
+ private uint TimeDuration { get; set; }
+ private IList Logs { get; set; }
+ private TestUnit Unit { get; set; }
+
+ #endregion Properties
+
+ ///
+ /// Determine the Visual Studio test case for which this BoostTestResult is associated with.
+ ///
+ /// The Visual Studio test case to associate with the generated result
+ /// this
+ public BoostTestResultBuilder For(VSTestCase test)
+ {
+ TestSuite parent = new TestSuite("Master Test Suite");
+
+ string[] fragments = test.FullyQualifiedName.Split('/');
+ for (int i = 0; i < (fragments.Length - 1); ++i)
+ {
+ parent = new TestSuite(fragments[i], parent);
+ }
+
+ return For(new TestCase(fragments[(fragments.Length - 1)], parent));
+ }
+
+ ///
+ /// Determine the Boost.Test.TestUnit test case for which this BoostTestResult is associated with.
+ ///
+ /// The Boost.Test.TestUnit test case to associate with the generated result
+ /// this
+ public BoostTestResultBuilder For(TestUnit unit)
+ {
+ this.Unit = unit;
+ return this;
+ }
+
+ ///
+ /// States that the test case passed.
+ ///
+ /// this
+ public BoostTestResultBuilder Passed()
+ {
+ return Result(TestResultType.Passed);
+ }
+
+ ///
+ /// States that the test case was aborted during execution.
+ ///
+ /// this
+ public BoostTestResultBuilder Aborted()
+ {
+ return Result(TestResultType.Aborted);
+ }
+
+ ///
+ /// States that the test case failed.
+ ///
+ /// this
+ public BoostTestResultBuilder Failed()
+ {
+ return Result(TestResultType.Failed);
+ }
+
+ ///
+ /// States that the test case was skipped by the testing framework.
+ ///
+ /// this
+ public BoostTestResultBuilder Skipped()
+ {
+ return Result(TestResultType.Skipped);
+ }
+
+ ///
+ /// States that the test case result.
+ ///
+ /// The test case execution result
+ /// this
+ public BoostTestResultBuilder Result(TestResultType result)
+ {
+ this.ResultType = result;
+ return this;
+ }
+
+ ///
+ /// The duration of the test case execution in microseconds.
+ ///
+ /// Test case execution duration in microseconds
+ /// this
+ public BoostTestResultBuilder Duration(uint time)
+ {
+ this.TimeDuration = time;
+ return this;
+ }
+
+ ///
+ /// Registers a log entry.
+ ///
+ /// A log entry generated during test execution
+ /// this
+ public BoostTestResultBuilder Log(LogEntry entry)
+ {
+ this.Logs.Add(entry);
+ return this;
+ }
+
+ ///
+ /// Generates a BoostTestResult instance from the contained configuration.
+ ///
+ /// A BoostTestResult instance based on the pre-set configuration
+ public BoostTestResult Build()
+ {
+ BoostTestResult result = new BoostTestResult(null);
+
+ result.Result = this.ResultType;
+ result.Unit = this.Unit;
+ result.Duration = this.TimeDuration;
+
+ foreach (LogEntry entry in this.Logs)
+ {
+ result.LogEntries.Add(entry);
+ }
+
+ return result;
+ }
+ }
+
+ #endregion Helper Classes
+
+ #region Helper Methods
+
+ ///
+ /// Asserts general Visual Studio TestResult properties for the default TestCase.
+ ///
+ /// The Visual Studio TestResult to test
+ private void AssertVSTestModelProperties(VSTestResult result)
+ {
+ AssertVSTestModelProperties(result, this.TestCase);
+ }
+
+ ///
+ /// Asserts general Visual Studio TestResult properties for the provided TestCase.
+ ///
+ /// The Visual Studio TestResult to test
+ /// The Visual Studio TestCase for which the result is based on
+ private void AssertVSTestModelProperties(VSTestResult result, VSTestCase test)
+ {
+ Assert.That(result.TestCase, Is.EqualTo(test));
+ Assert.That(result.ComputerName, Is.EqualTo(Environment.MachineName));
+ }
+
+ ///
+ /// Asserts that a log entry resulting in a test failure is correctly noted
+ /// in the Visual Studio TestResult.
+ ///
+ /// The Visual Studio TestResult to test
+ /// The log entry detailing a test case error
+ private void AssertVsTestModelError(VSTestResult result, LogEntry entry)
+ {
+ Assert.That(result.ErrorMessage, Is.EqualTo(entry.Detail));
+ Assert.That(result.ErrorStackTrace, Is.EqualTo(entry.Source.ToString()));
+ }
+
+ ///
+ /// Converts from microseconds to a TimeSpan
+ ///
+ /// Duration in microseconds
+ /// A TimeSpan describing the microsecond duration
+ private TimeSpan Microseconds(int value)
+ {
+ return TimeSpan.FromMilliseconds(Math.Truncate(value / 1000F));
+ }
+
+ ///
+ /// Given a LogEntry instance, identifies the respective Visual Studio
+ /// TestResult message category.
+ ///
+ /// The LogEntry instance to test
+ /// The respective Visual Studio Message category for the provided LogEntry
+ private string GetCategory(LogEntry entry)
+ {
+ BoostTestResult testCaseResult = new BoostTestResultBuilder().
+ For(this.TestCase).
+ Log(entry).
+ Build();
+
+ VSTestResult result = testCaseResult.AsVSTestResult(this.TestCase);
+
+ return result.Messages.First().Category;
+ }
+
+ #endregion Helper Methods
+
+ #region Tests
+
+ ///
+ /// Boost Test Case passed
+ ///
+ /// Test aims:
+ /// - Ensure that Boost TestCases which pass are identified accordingly in Visual Studio TestResults.
+ ///
+ [Test]
+ public void ConvertPassToVSTestResult()
+ {
+ BoostTestResult testCaseResult = new BoostTestResultBuilder().
+ For(this.TestCase).
+ Passed().
+ Duration(1000).
+ Log(new LogEntryMessage("BOOST_MESSAGE output")).
+ Build();
+
+ VSTestResult result = testCaseResult.AsVSTestResult(this.TestCase);
+
+ AssertVSTestModelProperties(result);
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Passed));
+ Assert.That(result.Duration, Is.EqualTo(Microseconds(1000)));
+
+ Assert.That(result.Messages.Count, Is.EqualTo(1));
+
+ TestResultMessage message = result.Messages.First();
+ Assert.That(message.Category, Is.EqualTo(TestResultMessage.StandardOutCategory));
+ }
+
+ ///
+ /// Boost Test Case failed
+ ///
+ /// Test aims:
+ /// - Ensure that Boost TestCases which fail are identified accordingly in Visual Studio TestResults.
+ ///
+ [Test]
+ public void ConvertFailToVSTestResult()
+ {
+ LogEntryError error = new LogEntryError("Error: 1 != 2", new SourceFileInfo("file.cpp", 10));
+
+ BoostTestResult testCaseResult = new BoostTestResultBuilder().
+ For(this.TestCase).
+ Failed().
+ Duration(2500).
+ Log(error).
+ Build();
+
+ VSTestResult result = testCaseResult.AsVSTestResult(this.TestCase);
+
+ AssertVSTestModelProperties(result);
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Failed));
+ Assert.That(result.Duration, Is.EqualTo(Microseconds(2500)));
+
+ AssertVsTestModelError(result, error);
+
+ Assert.That(result.Messages.Count, Is.EqualTo(1));
+
+ TestResultMessage message = result.Messages.First();
+ Assert.That(message.Category, Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ }
+
+ ///
+ /// Boost Test Case skipped
+ ///
+ /// Test aims:
+ /// - Ensure that Boost TestCases which are skipped are identified accordingly in Visual Studio TestResults.
+ ///
+ [Test]
+ public void ConvertSkipToVSTestResult()
+ {
+ BoostTestResult testCaseResult = new BoostTestResultBuilder().
+ For(this.TestCase).
+ Skipped().
+ Build();
+
+ VSTestResult result = testCaseResult.AsVSTestResult(this.TestCase);
+
+ AssertVSTestModelProperties(result);
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Skipped));
+ Assert.That(result.Duration, Is.EqualTo(TimeSpan.Zero));
+
+ Assert.That(result.Messages.Count, Is.EqualTo(0));
+ }
+
+ ///
+ /// Boost Test Case exception
+ ///
+ /// Test aims:
+ /// - Ensure that Boost TestCases which throw an exception are identified accordingly in Visual Studio TestResults.
+ ///
+ [Test]
+ public void ConvertExceptionToVSTestResult()
+ {
+ LogEntryException exception = new LogEntryException("C string: some error", new SourceFileInfo("unknown location", 0));
+ exception.LastCheckpoint = new SourceFileInfo("boostunittestsample.cpp", 13);
+ exception.CheckpointDetail = "Going to throw an exception";
+
+ BoostTestResult testCaseResult = new BoostTestResultBuilder().
+ For(this.TestCase).
+ Aborted().
+ Duration(0).
+ Log(exception).
+ Build();
+
+ VSTestResult result = testCaseResult.AsVSTestResult(this.TestCase);
+
+ AssertVSTestModelProperties(result);
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Failed));
+ Assert.That(result.Duration, Is.EqualTo(TimeSpan.FromTicks(0)));
+
+ AssertVsTestModelError(result, exception);
+
+ Assert.That(result.Messages.Count, Is.EqualTo(1));
+
+ TestResultMessage message = result.Messages.First();
+ Assert.That(message.Category, Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ }
+
+
+ ///
+ /// Boost Test Case Memory Leak
+ ///
+ /// Test aims:
+ /// - Ensure that Boost TestCases which leak memory are identified accordingly in Visual Studio TestResults.
+ ///
+ [Test]
+ public void ConvertMemoryLeakToVSTestResult()
+ {
+ LogEntryMemoryLeak leak = new LogEntryMemoryLeak();
+
+ leak.LeakLineNumber = 32;
+ leak.LeakMemoryAllocationNumber = 836;
+ leak.LeakLeakedDataContents = " Data: <`- Leak... > 60 2D BD 00 4C 65 61 6B 2E 2E 2E 00 CD CD CD CD ";
+
+ leak.LeakSourceFilePath = @"C:\boostunittestsample.cpp";
+ leak.LeakSourceFileName = "boostunittestsample.cpp";
+
+ BoostTestResult testCaseResult = new BoostTestResultBuilder().
+ For(this.TestCase).
+ Passed().
+ Duration(1000).
+ Log(leak).
+ Build();
+
+ VSTestResult result = testCaseResult.AsVSTestResult(this.TestCase);
+
+ AssertVSTestModelProperties(result);
+
+ Assert.That(result.Outcome, Is.EqualTo(TestOutcome.Passed));
+ Assert.That(result.Duration, Is.EqualTo(Microseconds(1000)));
+
+ Assert.That(result.Messages.Count, Is.EqualTo(1));
+
+ TestResultMessage message = result.Messages.First();
+ Assert.That(message.Category, Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ }
+
+ ///
+ /// Log entries identification in Visual Studio TestResults
+ ///
+ /// Test aims:
+ /// - Ensure that Boost log entries are categorized as expected when converted into Visual Studio TestResult Messages.
+ ///
+ [Test]
+ public void TestLogEntryCategory()
+ {
+ // Standard Output
+ Assert.That(GetCategory(new LogEntryInfo("Info")), Is.EqualTo(TestResultMessage.StandardOutCategory));
+ Assert.That(GetCategory(new LogEntryMessage("Message")), Is.EqualTo(TestResultMessage.StandardOutCategory));
+ Assert.That(GetCategory(new LogEntryStandardOutputMessage("StdOut")), Is.EqualTo(TestResultMessage.StandardOutCategory));
+
+ // Standard Error
+ Assert.That(GetCategory(new LogEntryWarning("Warning")), Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ Assert.That(GetCategory(new LogEntryError("Error")), Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ Assert.That(GetCategory(new LogEntryFatalError("FatalError")), Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ Assert.That(GetCategory(new LogEntryException("Exception")), Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ Assert.That(GetCategory(new LogEntryMemoryLeak()), Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ Assert.That(GetCategory(new LogEntryStandardErrorMessage("StdErr")), Is.EqualTo(TestResultMessage.StandardErrorCategory));
+ }
+
+ #endregion Tests
+ }
+}
diff --git a/BoostTestAdapterNunit/VisualStudio2012AdapterTest.cs b/BoostTestAdapterNunit/VisualStudio2012AdapterTest.cs
new file mode 100644
index 0000000..47ae329
--- /dev/null
+++ b/BoostTestAdapterNunit/VisualStudio2012AdapterTest.cs
@@ -0,0 +1,91 @@
+using EnvDTE;
+using FakeItEasy;
+using Microsoft.VisualStudio.VCProjectEngine;
+using NUnit.Framework;
+using VisualStudioAdapter;
+
+namespace BoostTestAdapterNunit
+{
+ [TestFixture]
+ public class VisualStudio2012AdapterTest
+ {
+ #region Test Setup/Teardown
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.FakeVS = new FakeVisualStudio2012();
+ this.Project = new VisualStudioAdapter.Shared.Project(this.FakeVS.Project);
+ }
+
+ #endregion Test Setup/Teardown
+
+ #region Test Data
+
+ private FakeVisualStudio2012 FakeVS { get; set; }
+ private IProject Project { get; set; }
+ private const string DefaultProjectName = "SampleProject";
+ private const string DefaultOutput = "test.boostd.exe";
+
+ #endregion Test Data
+
+ #region Helper Classes
+
+ ///
+ /// Aggregates fake Visual Studio EnvDTE and VCProjectEngine structures
+ ///
+ private class FakeVisualStudio2012
+ {
+ public FakeVisualStudio2012()
+ {
+ this.Project = A.Fake();
+ this.ConfigurationManager = A.Fake();
+ this.ActiveConfiguration = A.Fake();
+ this.VCProject = A.Fake();
+ this.ConfigurationCollection = A.Fake();
+ this.VCConfiguration = A.Fake();
+
+ A.CallTo(() => this.Project.FullName).Returns(DefaultProjectName);
+ A.CallTo(() => this.Project.ConfigurationManager).Returns(this.ConfigurationManager);
+
+ A.CallTo(() => this.ConfigurationManager.ActiveConfiguration).Returns(this.ActiveConfiguration);
+ A.CallTo(() => this.Project.Object).Returns(this.VCProject);
+
+ A.CallTo(() => this.ActiveConfiguration.ConfigurationName).Returns("Debug");
+ A.CallTo(() => this.ActiveConfiguration.PlatformName).Returns("Win32");
+
+ A.CallTo(() => this.VCProject.Configurations).Returns(this.ConfigurationCollection);
+
+ A.CallTo(() => this.ConfigurationCollection.Item("Debug|Win32")).Returns(this.VCConfiguration);
+
+ A.CallTo(() => this.VCConfiguration.PrimaryOutput).Returns(DefaultOutput);
+ }
+
+ public Project Project { get; private set; }
+ private ConfigurationManager ConfigurationManager { get; set; }
+ private Configuration ActiveConfiguration { get; set; }
+ private VCProject VCProject { get; set; }
+ private IVCCollection ConfigurationCollection { get; set; }
+ private VCConfiguration VCConfiguration { get; set; }
+ }
+
+ #endregion Helper Classes
+
+ #region Tests
+
+ ///