Commits

Matt Oswald  committed 0bfe841

now only passing raw types/references across the ABI, which means release builds can load debug unit tests, and vice-versa

  • Participants
  • Parent commits 9b50f55

Comments (0)

Files changed (38)

File Tests/UnitTests/Attributes.cpp

         "SkippedTest", "Attributes", std::forward<decltype(attributes)>(attributes), -1, __FILE__, __LINE__, std::forward<decltype(localEventRecorders)>(localEventRecorders));
     (void)reg;
 
-    xUnitpp::RunTests(record, [](const xUnitpp::TestDetails &) { return true; },
+    xUnitpp::RunTests(record, [](const xUnitpp::ITestDetails &) { return true; },
         collection.Tests(), xUnitpp::Time::Duration::zero(), 0);
 }
 

File Tests/UnitTests/Helpers/OutputRecord.cpp

 
 namespace xUnitpp { namespace Tests {
 
-void OutputRecord::ReportStart(const TestDetails &testDetails)
+void OutputRecord::ReportStart(const ITestDetails &testDetails)
 {
     std::lock_guard<std::mutex> guard(lock);
-    orderedTestList.push_back(testDetails);
+    orderedTestList.push_back(static_cast<const TestDetails &>(testDetails));
 }
 
-void OutputRecord::ReportEvent(const TestDetails &testDetails, const TestEvent &evt)
+void OutputRecord::ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt)
 {
     std::lock_guard<std::mutex> guard(lock);
-    events.push_back(std::make_tuple(testDetails, evt));
+    events.push_back(std::make_pair(static_cast<const TestDetails &>(testDetails), static_cast<const TestEvent &>(evt)));
 }
 
-void OutputRecord::ReportSkip(const TestDetails &testDetails, const std::string &reason)
+void OutputRecord::ReportSkip(const ITestDetails &testDetails, const char *reason)
 {
     std::lock_guard<std::mutex> guard(lock);
-    skips.push_back(std::make_tuple(testDetails, reason));
+    skips.push_back(std::make_pair(static_cast<const TestDetails &>(testDetails), reason));
 }
 
-void OutputRecord::ReportFinish(const TestDetails &testDetails, Time::Duration timeTaken)
+void OutputRecord::ReportFinish(const ITestDetails &testDetails, long long nsTaken)
 {
     std::lock_guard<std::mutex> guard(lock);
-    finishedTests.push_back(std::make_tuple(testDetails, timeTaken));
+    finishedTests.push_back(std::make_pair(static_cast<const TestDetails &>(testDetails), Time::Duration(nsTaken)));
 }
 
-void OutputRecord::ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failed, Time::Duration totalTime)
+void OutputRecord::ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failed, long long nsTotal)
 {
     summaryCount = testCount;
     summarySkipped = skipped;
     summaryFailed = failed;
-    summaryDuration = totalTime;
+    summaryDuration = Time::Duration(nsTotal);
 }
 
 }}

File Tests/UnitTests/Helpers/OutputRecord.h

 #include <memory>
 #include <mutex>
 #include <tuple>
+#include <utility>
 #include <vector>
 #include "xUnit++/IOutput.h"
 
+namespace xUnitpp
+{
+    struct TestDetails;
+    class TestEvent;
+}
+
 namespace xUnitpp { namespace Tests {
 
 class OutputRecord : public IOutput
 {
 public:
-    virtual void ReportStart(const TestDetails &testDetails) override;
-    virtual void ReportEvent(const TestDetails &testDetails, const TestEvent &evt) override;
-    virtual void ReportSkip(const TestDetails &testDetails, const std::string &reason) override;
-    virtual void ReportFinish(const TestDetails &testDetails, Time::Duration timeTaken) override;
-    virtual void ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failed, Time::Duration totalTime) override;
+    virtual void __stdcall ReportStart(const ITestDetails &testDetails) override;
+    virtual void __stdcall ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt) override;
+    virtual void __stdcall ReportSkip(const ITestDetails &testDetails, const char *reason) override;
+    virtual void __stdcall ReportFinish(const ITestDetails &testDetails, long long nsTaken) override;
+    virtual void __stdcall ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failed, long long nsTotal) override;
 
     std::vector<TestDetails> orderedTestList;
-    std::vector<std::tuple<TestDetails, TestEvent>> events;
-    std::vector<std::tuple<TestDetails, std::string>> skips;
-    std::vector<std::tuple<TestDetails, Time::Duration>> finishedTests;
+    std::vector<std::pair<TestDetails, TestEvent>> events;
+    std::vector<std::pair<TestDetails, std::string>> skips;
+    std::vector<std::pair<TestDetails, Time::Duration>> finishedTests;
 
     size_t summaryCount;
     size_t summarySkipped;

File Tests/UnitTests/LineInfo.cpp

         -1, __FILE__, __LINE__, std::forward<decltype(localEventRecorders)>(localEventRecorders));
     (void)reg;
 
-    xUnitpp::RunTests(record, [](const xUnitpp::TestDetails &) { return true; }, collection.Tests(), xUnitpp::Time::Duration::zero(), 0);
+    xUnitpp::RunTests(record, [](const xUnitpp::ITestDetails &) { return true; }, collection.Tests(), xUnitpp::Time::Duration::zero(), 0);
 
     Assert.Equal(1U, record.events.size());
     Assert.Equal(file, std::get<1>(record.events[0]).LineInfo().file);

File Tests/UnitTests/TestEvents.cpp

 #include <vector>
 #include <memory>
 #include "xUnit++/xUnit++.h"
+#include "xUnit++/EventLevel.h"
 #include "xUnit++/TestEvent.h"
 #include "xUnit++/TestEventRecorder.h"
 #include "xUnit++/TestCollection.h"
 
     void Run()
     {
-        xUnitpp::RunTests(outputRecord, [](const xUnitpp::TestDetails &) { return true; }, collection.Tests(), xUnitpp::Time::Duration::zero(), 0);
+        xUnitpp::RunTests(outputRecord, [](const xUnitpp::ITestDetails &) { return true; }, collection.Tests(), xUnitpp::Time::Duration::zero(), 0);
     }
 
     const xUnitpp::Check &LocalCheck() const
 
     Run();
 
-    Assert.Equal(xUnitpp::EventLevel::Warning, std::get<1>(outputRecord.events[0]).Level());
-    Assert.Equal(xUnitpp::EventLevel::Check, std::get<1>(outputRecord.events[1]).Level());
-    Assert.Equal(xUnitpp::EventLevel::Warning, std::get<1>(outputRecord.events[2]).Level());
+    Assert.Equal(xUnitpp::EventLevel::Warning, std::get<1>(outputRecord.events[0]).GetLevel());
+    Assert.Equal(xUnitpp::EventLevel::Check, std::get<1>(outputRecord.events[1]).GetLevel());
+    Assert.Equal(xUnitpp::EventLevel::Warning, std::get<1>(outputRecord.events[2]).GetLevel());
 
     Assert.Contains(to_string(std::get<1>(outputRecord.events[0])), "Fail");
     Assert.Contains(to_string(std::get<1>(outputRecord.events[2])), "False");
     Assert.Equal(std::begin(expectedLevels), std::end(expectedLevels), outputRecord.events.begin(), outputRecord.events.end(),
         [](xUnitpp::EventLevel lvl, const std::tuple<xUnitpp::TestDetails, xUnitpp::TestEvent> &result)
         {
-            return lvl == std::get<1>(result).Level();
+            return lvl == std::get<1>(result).GetLevel();
         });
 }
 

File Tests/UnitTests/TestRunner.cpp

 
 namespace Filter
 {
-    bool AllTests(const xUnitpp::TestDetails &) { return true; }
-    bool NoTests(const xUnitpp::TestDetails &) { return false; }
+    bool AllTests(const xUnitpp::ITestDetails &) { return true; }
+    bool NoTests(const xUnitpp::ITestDetails &) { return false; }
 }
 
 struct EmptyTest

File Tests/UnitTests/Theory.cpp

 
     void Run()
     {
-        RunTests(record, [](const xUnitpp::TestDetails &) { return true; },
+        RunTests(record, [](const xUnitpp::ITestDetails &) { return true; },
             collection.Tests(), xUnitpp::Time::Duration::zero(), 0);
     }
 

File xUnit++.Utility/XmlReporter.cpp

 #include "XmlReporter.h"
+#include <algorithm>
 #include <chrono>
 #include <fstream>
 #include <iostream>
-#include "xUnit++/TestDetails.h"
-#include "xUnit++/TestEvent.h"
+#include <vector>
+#include "xUnit++/ITestDetails.h"
+#include "xUnit++/ITestEvent.h"
+#include "xUnit++/LineInfo.h"
 #include "xUnit++/xUnitTime.h"
 
 namespace
 {
     struct TestResult
     {
-        TestResult()
-        {
-        }
-
-        TestResult(const xUnitpp::TestDetails &testDetails)
+        TestResult(const xUnitpp::ITestDetails &testDetails)
             : testDetails(testDetails)
             , status(Success)
         {
         }
 
-        xUnitpp::TestDetails testDetails;
+        const xUnitpp::ITestDetails &testDetails;
 
         enum
         {
 
         xUnitpp::Time::Duration time;
         std::vector<std::string> messages;
+
+    private:
+        TestResult &operator =(TestResult other) /* = delete */;
     };
 }
 
     int failures;
     int skipped;
 
-    std::map<std::string, TestResult> testResults;
+    std::vector<TestResult> testResults;
 };
 
 namespace
 {
+    TestResult &GetTestResult(std::vector<TestResult> &testResults, const std::string &fullName)
+    {
+        // if fullName isn't found it's a bug: go ahead and crash :P
+        return *std::find_if(testResults.begin(), testResults.end(),
+            [&](const TestResult &test)
+            {
+                return test.testDetails.GetFullName() == fullName;
+            });
+    }
+
     float SuiteTime(const xUnitpp::Utilities::XmlReporter::SuiteResult &suiteResult)
     {
         xUnitpp::Time::Duration timeTaken = xUnitpp::Time::Duration::zero();
 
         for (const auto &test : suiteResult.testResults)
         {
-            timeTaken += test.second.time;
+            timeTaken += test.time;
         }
 
         return xUnitpp::Time::ToSeconds(timeTaken).count();
             " ?>\n";
     }
 
-    std::string XmlBeginResults(size_t tests, size_t failures, xUnitpp::Time::Duration totalTime)
+    std::string XmlBeginResults(size_t tests, size_t failures, long long nsTotal)
     {
+        xUnitpp::Time::Duration totalTime(nsTotal);
         return
             "<testsuites" +
                 XmlAttribute("tests", tests) +
 {
 }
 
-void XmlReporter::ReportAllTestsComplete(size_t testCount, size_t, size_t failureCount, Time::Duration totalTime)
+void XmlReporter::ReportAllTestsComplete(size_t testCount, size_t, size_t failureCount, long long nsTotal)
 {
     auto report = [&](std::ostream &stream)
         {
             stream << XmlBeginDoc();
-            stream << XmlBeginResults(testCount, failureCount, totalTime);
+            stream << XmlBeginResults(testCount, failureCount, nsTotal);
 
             for (const auto &itSuite : suiteResults)
             {
                 stream << XmlBeginSuite(itSuite.second);
 
-                for (const auto &itTest : itSuite.second.testResults)
+                for (const auto &test : itSuite.second.testResults)
                 {
-                    const auto &test = itTest.second;
+                    stream << XmlBeginTest(test.testDetails.GetFullName(), test);
 
-                    stream << XmlBeginTest(itTest.first, test);
-
-                    if (test.status != TestResult::Success || !test.testDetails.Attributes.empty())
+                    if (test.status != TestResult::Success || test.testDetails.GetAttributeCount() == 0)
                     {
                         // close <TestCase>
                         stream << ">\n";
                     }
 
-                    for (const auto &att : test.testDetails.Attributes)
+                    for (auto i = 0U; i != test.testDetails.GetAttributeCount(); ++i)
                     {
-                        if (att.first != "Skip")
+                        std::string key = test.testDetails.GetAttributeKey(i);
+                        if (key != "Skip")
                         {
-                            stream << XmlTestAttribute(att.first, att.second);
-                            break;
+                            std::string value = test.testDetails.GetAttributeValue(i);
+
+                            stream << XmlTestAttribute(key, value);
                         }
                     }
 
                     if (test.status == TestResult::Failure)
                     {
-                        stream << XmlTestFailed(to_string(test.testDetails.LineInfo), test.messages);
+                        xUnitpp::LineInfo li(test.testDetails.GetFile(), test.testDetails.GetLine());
+                        stream << XmlTestFailed(to_string(li), test.messages);
                     }
                     else if (test.status == TestResult::Skipped)
                     {
                         stream << XmlTestSkipped(test.messages[0]);
                     }
 
-                    stream << XmlEndTest(test.status == TestResult::Success && test.testDetails.Attributes.empty());
+                    stream << XmlEndTest(test.status == TestResult::Success && test.testDetails.GetAttributeCount() == 0);
                 }
 
                 stream << XmlEndSuite();
     }
 }
 
-void XmlReporter::ReportStart(const TestDetails &testDetails)
+void XmlReporter::ReportStart(const ITestDetails &testDetails)
 {
-    if (suiteResults.find(testDetails.Suite) == suiteResults.end())
+    std::string suite = testDetails.GetSuite();
+    if (suiteResults.find(suite) == suiteResults.end())
     {
-        suiteResults.insert(std::make_pair(testDetails.Suite, SuiteResult(testDetails.Suite)));
+        suiteResults.insert(std::make_pair(suite, SuiteResult(suite)));
     }
 
-    suiteResults[testDetails.Suite].tests++;
-    suiteResults[testDetails.Suite].testResults.insert(std::make_pair(testDetails.Name, TestResult(testDetails)));
+    suiteResults[suite].tests++;
+    suiteResults[suite].testResults.push_back(TestResult(testDetails));
 }
 
-void XmlReporter::ReportEvent(const TestDetails &testDetails, const TestEvent &evt)
+void XmlReporter::ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt)
 {
-    if (evt.IsFailure())
+    if (evt.GetIsFailure())
     {
-        suiteResults[testDetails.Suite].failures++;
-        suiteResults[testDetails.Suite].testResults[testDetails.Name].messages.push_back(to_string(evt));
-        suiteResults[testDetails.Suite].testResults[testDetails.Name].status = TestResult::Failure;
+        std::string suite = testDetails.GetSuite();
+        std::string name = testDetails.GetFullName();
+        
+        suiteResults[suite].failures++;
+
+        auto &testResult = GetTestResult(suiteResults[suite].testResults, name);
+        testResult.messages.push_back(evt.GetToString());
+        testResult.status = TestResult::Failure;
     }
 }
 
-void XmlReporter::ReportSkip(const TestDetails &testDetails, const std::string &reason)
+void XmlReporter::ReportSkip(const ITestDetails &testDetails, const char *reason)
 {
     ReportStart(testDetails);
-    suiteResults[testDetails.Suite].skipped++;
-    suiteResults[testDetails.Suite].testResults[testDetails.Name].messages.push_back(reason);
-    suiteResults[testDetails.Suite].testResults[testDetails.Name].status = TestResult::Skipped;
+
+    std::string suite = testDetails.GetSuite();
+    std::string name = testDetails.GetFullName();
+
+    suiteResults[suite].skipped++;
+
+    auto &testResult = GetTestResult(suiteResults[suite].testResults, name);
+    testResult.messages.push_back(reason);
+    testResult.status = TestResult::Skipped;
 }
 
-void XmlReporter::ReportFinish(const TestDetails &testDetails, Time::Duration timeTaken)
+void XmlReporter::ReportFinish(const ITestDetails &testDetails, long long nsTaken)
 {
-    suiteResults[testDetails.Suite].testResults[testDetails.Name].time = timeTaken;
+    GetTestResult(suiteResults[testDetails.GetSuite()].testResults, testDetails.GetFullName()).time = Time::Duration(nsTaken);
 }
 
 }}

File xUnit++.Utility/XmlReporter.h

 public:
     XmlReporter(const std::string &filename);
 
-    virtual void ReportStart(const TestDetails &td) override;
-    virtual void ReportEvent(const TestDetails &testDetails, const TestEvent &evt) override;
-    virtual void ReportSkip(const TestDetails &testDetails, const std::string &reason) override;
-    virtual void ReportFinish(const TestDetails &testDetails, Time::Duration timeTaken) override;
-    virtual void ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, Time::Duration totalTime) override;
+    virtual void _stdcall ReportStart(const ITestDetails &td) override;
+    virtual void _stdcall ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt) override;
+    virtual void _stdcall ReportSkip(const ITestDetails &testDetails, const char *reason) override;
+    virtual void _stdcall ReportFinish(const ITestDetails &testDetails, long long nsTaken) override;
+    virtual void _stdcall ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, long long nsTotal) override;
 
 public:
     struct SuiteResult;

File xUnit++.VsRunner/xUnit++.VsRunner.cpp

 #include <memory>
 #include <vector>
 #include <msclr/marshal_cppstd.h>
+#include "xUnit++/EventLevel.h"
 #include "xUnit++/ExportApi.h"
 #include "xUnit++/IOutput.h"
 #include "xUnit++/LineInfo.h"
-#include "xUnit++/TestDetails.h"
-#include "xUnit++/TestEvent.h"
+#include "xUnit++/ITestDetails.h"
+#include "xUnit++/ITestEvent.h"
 #include "TestAssembly.h"
 
 using namespace System;
 
 namespace
 {
-    ref struct ManagedReporter
+    ref class ManagedReporter
     {
+    public:
         ManagedReporter(ITestExecutionRecorder ^recorder, const std::vector<gcroot<TestCase ^>> &testCases)
             : recorder(recorder)
         {
             }
         }
 
-        void ReportStart(const xUnitpp::TestDetails &td)
+        void ReportStart(const xUnitpp::ITestDetails &td)
         {
-            auto key = marshal_as<String ^>(td.FullName());
-            auto name = marshal_as<String ^>(td.Name);
+            auto key = marshal_as<String ^>(td.GetFullName());
+            auto name = marshal_as<String ^>(td.GetName());
             recorder->RecordStart(testCases[name]);
 
             auto result = gcnew TestResult(testCases[name]);
             testResults.Add(key, result);
         }
 
-        void ReportEvent(const xUnitpp::TestDetails &td, const xUnitpp::TestEvent &evt)
+        void ReportEvent(const xUnitpp::ITestDetails &td, const xUnitpp::ITestEvent &evt)
         {
-            auto result = testResults[marshal_as<String ^>(td.Name)];
+            auto key = marshal_as<String ^>(td.GetName());
+            auto result = testResults[key];
 
-            if (evt.IsFailure())
+            if (evt.GetIsFailure())
             {
                 result->Outcome = TestOutcome::Failed;
             }
 
-            result->Messages->Add(gcnew TestResultMessage(marshal_as<String ^>(to_string(evt.Level())), marshal_as<String ^>(to_string(evt))));
+            result->Messages->Add(gcnew TestResultMessage(marshal_as<String ^>(to_string(evt.GetLevel())), marshal_as<String ^>(evt.GetToString())));
         }
 
-        void ReportSkip(const xUnitpp::TestDetails &td, const std::string &)
+        void ReportSkip(const xUnitpp::ITestDetails &td, const std::string &)
         {
-            auto testCase = testCases[marshal_as<String ^>(td.FullName())];
+            auto key = marshal_as<String ^>(td.GetFullName());
+            auto testCase = testCases[key];
             auto result = gcnew TestResult(testCase);
             result->ComputerName = Environment::MachineName;
-            result->DisplayName = marshal_as<String ^>(td.Name);
+            result->DisplayName = marshal_as<String ^>(td.GetName());
             result->Duration = TimeSpan::FromSeconds(0);
             result->Outcome = TestOutcome::Skipped;
             recorder->RecordEnd(testCase, result->Outcome);
             recorder->RecordResult(result);
         }
 
-        void ReportFinish(const xUnitpp::TestDetails &td, xUnitpp::Time::Duration timeTaken)
+        void ReportFinish(const xUnitpp::ITestDetails &td, xUnitpp::Time::Duration timeTaken)
         {
-            auto result = testResults[marshal_as<String ^>(td.Name)];
+            auto key = marshal_as<String ^>(td.GetFullName());
+            auto result = testResults[key];
             result->Duration = TimeSpan::FromSeconds(xUnitpp::Time::ToSeconds(timeTaken).count());
 
             if (result->Outcome == TestOutcome::None)
                 result->Outcome = TestOutcome::Passed;
             }
 
-            recorder->RecordEnd(testCases[marshal_as<String ^>(td.FullName())], result->Outcome);
+            recorder->RecordEnd(testCases[marshal_as<String ^>(td.GetFullName())], result->Outcome);
             recorder->RecordResult(result);
         }
 
         {
         }
 
-        virtual void ReportStart(const xUnitpp::TestDetails &td) override
+        virtual void __stdcall ReportStart(const xUnitpp::ITestDetails &td) override
         {
             reporter->ReportStart(td);
         }
 
-        virtual void ReportEvent(const xUnitpp::TestDetails &testDetails, const xUnitpp::TestEvent &evt) override
+        virtual void __stdcall ReportEvent(const xUnitpp::ITestDetails &testDetails, const xUnitpp::ITestEvent &evt) override
         {
             reporter->ReportEvent(testDetails, evt);
         }
 
-        virtual void ReportSkip(const xUnitpp::TestDetails &testDetails, const std::string &reason) override
+        virtual void __stdcall ReportSkip(const xUnitpp::ITestDetails &testDetails, const char *reason) override
         {
             reporter->ReportSkip(testDetails, reason);
         }
 
-        virtual void ReportFinish(const xUnitpp::TestDetails &testDetails, xUnitpp::Time::Duration timeTaken) override
+        virtual void __stdcall ReportFinish(const xUnitpp::ITestDetails &testDetails, long long nsTaken) override
         {
-            reporter->ReportFinish(testDetails, timeTaken);
+            reporter->ReportFinish(testDetails, xUnitpp::Time::Duration(nsTaken));
         }
 
-        virtual void ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, xUnitpp::Time::Duration totalTime) override
+        virtual void __stdcall ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, long long nsTotal) override
         {
-            reporter->ReportAllTestsComplete(testCount, skipped, failureCount, totalTime);
+            reporter->ReportAllTestsComplete(testCount, skipped, failureCount, xUnitpp::Time::Duration(nsTotal));
         }
 
     private:
         {
             NativeReporter reporter(recorder, tests);
             FilteredTestsRunner(0, 0, reporter,
-                [&](const xUnitpp::TestDetails &testDetails)
+                [&](const xUnitpp::ITestDetails &testDetails)
                 {
                     return !cancelled && std::find_if(tests.begin(), tests.end(),
                         [&](gcroot<TestCase ^> test)
                         {
-                            return marshal_as<std::string>(test->DisplayName) == testDetails.Name;
+                            return marshal_as<std::string>(test->DisplayName) == testDetails.GetName();
                         }) != tests.end();
                 });
         }
         std::vector<gcroot<TestCase ^>> tests;
     };
 
-    IEnumerable<TestCase ^> ^SingleSourceTestCases(String ^_source, Uri ^_uri)
+    IEnumerable<TestCase ^> ^SingleSourceTestCases(String ^_source, Uri ^_uri, IMessageLogger ^logger)
     {
-        auto result = gcnew Dictionary<String ^, TestCase ^>();
+        logger->SendMessage(TestMessageLevel::Informational, "Inside SingleSourceTestCases");
+
+        auto results = gcnew List<TestCase ^>();
+
+        logger->SendMessage(TestMessageLevel::Informational, "SingleSourceTestCases _source: " + _source);
 
         auto source = marshal_as<std::string>(_source);
+
         if (auto assembly = ManagedTestAssembly(source))
         {
+            struct testDetails
+            {
+                std::string fullName;
+                std::string name;
+                std::string file;
+                int line;
+            };
+
+            std::vector<testDetails> tests;
+
+            assembly.EnumerateTestDetails(
+                [&](const xUnitpp::ITestDetails &td)
+                {
+                    testDetails testDetails = { td.GetFullName(), td.GetName(), td.GetFile(), td.GetLine() };
+                    tests.push_back(testDetails);
+                });
+
             auto uri = gcroot<Uri ^>(_uri);
-            auto dict = gcroot<Dictionary<String ^, TestCase ^> ^>(result);
-            assembly.EnumerateTestDetails([&](const xUnitpp::TestDetails &td)
-                {
-                    if (!dict->ContainsKey(marshal_as<String ^>(td.Name)))
-                    {
-                        TestCase ^testCase = gcnew TestCase(marshal_as<String ^>(td.Name), uri, marshal_as<String ^>(source));
-                        testCase->DisplayName = marshal_as<String ^>(td.Name);
-                        testCase->CodeFilePath = marshal_as<String ^>(td.LineInfo.file);
-                        testCase->LineNumber = td.LineInfo.line;
 
-                        dict->Add(marshal_as<String ^>(td.FullName()), testCase);
-                    }
-                });
+            for (const auto &test : tests)
+            {
+                TestCase ^testCase = gcnew TestCase(marshal_as<String ^>(test.fullName), uri, marshal_as<String ^>(source));
+                testCase->DisplayName = marshal_as<String ^>(test.name);
+                testCase->CodeFilePath = marshal_as<String ^>(test.file);
+                testCase->LineNumber = test.line;
+
+                results->Add(testCase);
+            }
         }
 
-        return result->Values;
+        return results;
     }
 }
 
 namespace xUnitpp { namespace VsRunner
 {
 
-VsRunner::VsRunner()
+xUnitppVsRunner::xUnitppVsRunner()
     : mUri(gcnew Uri(VSRUNNER_URI))
     , mCancelled(false)
 {
 }
 
-void VsRunner::DiscoverTests(IEnumerable<String ^> ^sources, IDiscoveryContext ^, IMessageLogger ^, ITestCaseDiscoverySink ^discoverySink)
+void xUnitppVsRunner::DiscoverTests(IEnumerable<String ^> ^sources, IDiscoveryContext ^, IMessageLogger ^logger, ITestCaseDiscoverySink ^discoverySink)
 {
+    logger->SendMessage(TestMessageLevel::Informational, "Test discovery starting.");
+
+    try
+    {
+
     for each (String ^source in sources)
     {
-        for each (TestCase ^test in SingleSourceTestCases(source, mUri))
+        logger->SendMessage(TestMessageLevel::Informational, source);
+        logger->SendMessage(TestMessageLevel::Informational, "about to call singlesourcetestcases");
+        for each (TestCase ^test in SingleSourceTestCases(source, mUri, logger))
         {
             discoverySink->SendTestCase(test);
         }
     }
+
+    }
+    catch(Exception ^e)
+    {
+        logger->SendMessage(TestMessageLevel::Error, e->ToString());
+    }
+
+    logger->SendMessage(TestMessageLevel::Informational, "Test discovery finished.");
 }
 
-void VsRunner::RunTests(IEnumerable<String ^> ^sources, IRunContext ^, IFrameworkHandle ^framework)
+void xUnitppVsRunner::RunTests(IEnumerable<String ^> ^sources, IRunContext ^, IFrameworkHandle ^framework)
 {
     mCancelled = false;
 
     for each (String ^source in sources)
     {
-        if (RunTests(SingleSourceTestCases(source, mUri), framework))
+        if (RunTests(SingleSourceTestCases(source, mUri, nullptr), framework))
         {
             // cancelled
             break;
     }
 }
 
-void VsRunner::RunTests(IEnumerable<TestCase ^> ^tests, IRunContext ^, IFrameworkHandle ^framework)
+void xUnitppVsRunner::RunTests(IEnumerable<TestCase ^> ^tests, IRunContext ^, IFrameworkHandle ^framework)
 {
     mCancelled = false;
     RunTests(tests, framework);
 }
 
-void VsRunner::Cancel()
+void xUnitppVsRunner::Cancel()
 {
     mCancelled = true;
 }
 
-bool VsRunner::RunTests(IEnumerable<TestCase ^> ^tests, ITestExecutionRecorder ^recorder)
+bool xUnitppVsRunner::RunTests(IEnumerable<TestCase ^> ^tests, ITestExecutionRecorder ^recorder)
 {
     std::map<std::string, std::shared_ptr<ManagedTestAssembly>> assemblies;
 

File xUnit++.VsRunner/xUnit++.VsRunner.h

     [OM::FileExtension(".exe")]
     [OM::DefaultExecutorUri(VSRUNNER_URI)]
     [OM::ExtensionUri(VSRUNNER_URI)]
-    public ref class VsRunner : public A::ITestDiscoverer, public A::ITestExecutor
+    public ref class xUnitppVsRunner : public A::ITestDiscoverer, public A::ITestExecutor
     {
     public:
-        VsRunner();
+        xUnitppVsRunner();
 
         // ITestDiscoverer
         virtual void DiscoverTests(G::IEnumerable<System::String ^> ^sources, A::IDiscoveryContext ^ctx, L::IMessageLogger ^logger, A::ITestCaseDiscoverySink ^discoverySink);

File xUnit++.console/ConsoleReporter.cpp

 #include "ConsoleReporter.h"
+#include <algorithm>
 #include <chrono>
 #include <cstdio>
 #include <iostream>
 #include <memory>
 #include <mutex>
+#include <string>
 #include <unordered_map>
+#include "xUnit++/EventLevel.h"
+#include "xUnit++/LineInfo.h"
+#include "xUnit++/ITestDetails.h"
+#include "xUnit++/ITestEvent.h"
 
 #if defined (_WIN32)
 #include <Windows.h>
 #undef ReportEvent
+#undef GetMessage
 
 namespace
 {
 #else
 #endif
 
-#include "xUnit++/LineInfo.h"
-#include "xUnit++/TestDetails.h"
-#include "xUnit++/TestEvent.h"
-
 namespace
 {
     static const std::string TestSeparator = "\n            ";
         return "\033[m";
     }
 
-    std::string FileAndLine(const xUnitpp::TestDetails &td, const xUnitpp::LineInfo &lineInfo)
+    template<typename T>
+    xUnitpp::LineInfo GetSafeLineInfo(const T &t)
+    {
+        return xUnitpp::LineInfo(t.GetFile(), t.GetLine());
+    }
+
+    std::string FileAndLine(const xUnitpp::ITestDetails &td, const xUnitpp::LineInfo &lineInfo)
     {
         auto result = to_string(lineInfo);
         if (result.empty())
         {
-            result = to_string(td.LineInfo);
+            result = to_string(GetSafeLineInfo(td));
         }
 
         return result;
 
         return stream;
     }
+
+    std::string safestr(const char *s)
+    {
+        return s == nullptr ? "" : s;
+    }
 }
 
 namespace xUnitpp
             std::string message;
         };
 
-        TestOutput(const xUnitpp::TestDetails &td, bool verbose)
+        TestOutput(const xUnitpp::ITestDetails &td, bool verbose)
             : testDetails(td)
             , failed(false)
             , verbose(verbose)
 
                 if (!grouped)
                 {
-                    if (!testDetails.Suite.empty())
+                    auto suite = safestr(testDetails.GetSuite());
+                    if (!suite.empty())
                     {
-                        std::cout << Fragment(Color::Suite, testDetails.Suite);
+                        std::cout << Fragment(Color::Suite, suite);
                         std::cout << Fragment(Color::Separator, TestSeparator);
                     }
                 }
 
-                std::cout << Fragment(Color::TestName, testDetails.FullName() + "\n");
+                auto fullName = safestr(testDetails.GetFullName());
+                std::cout << Fragment(Color::TestName, fullName + "\n");
 
                 for (auto &&msg : fragments)
                 {
 
         void Skip(const std::string &reason)
         {
-            fragments.emplace_back(Color::FileAndLine, to_string(testDetails.LineInfo));
+            fragments.emplace_back(Color::FileAndLine, to_string(xUnitpp::LineInfo(testDetails.GetFile(), testDetails.GetLine())));
             fragments.emplace_back(Color::Separator, ": ");
             fragments.emplace_back(Color::Skip, reason);
             fragments.emplace_back(Color::Default, "\n");
             skipped = true;
         }
 
-        TestOutput &operator <<(const xUnitpp::TestEvent &event)
+        TestOutput &operator <<(const xUnitpp::ITestEvent &event)
         {
-            if (event.IsFailure())
+            if (event.GetIsFailure())
             {
                 failed = true;
             }
 
-            fragments.emplace_back(Color::FileAndLine, FileAndLine(testDetails, event.LineInfo()));
+            fragments.emplace_back(Color::FileAndLine, FileAndLine(testDetails, GetSafeLineInfo(event)));
             fragments.emplace_back(Color::Separator, ": ");
-            fragments.emplace_back(to_color(event.Level()), to_string(event.Level()));
+            fragments.emplace_back(to_color(event.GetLevel()), to_string(event.GetLevel()));
             fragments.emplace_back(Color::Separator, ": ");
 
             // unfortunately, this code should almost completely match the to_string(const TestEvent &)
             // function found in TestEvent.cpp, but is kept separate because of the color coding
-            if (event.IsAssertType())
+            if (event.GetIsAssertType())
             {
-                auto &&assert = event.Assert();
-
-                fragments.emplace_back(Color::Call, assert.Call() + "()");
+                auto &&assert = event.GetAssertInterface();
+                fragments.emplace_back(Color::Call, assert.GetCall() + std::string("()"));
 
                 std::string message = " failure";
 
-                std::string userMessage = assert.UserMessage();
+                std::string userMessage = assert.GetUserMessage();
+                std::string customMessage = assert.GetCustomMessage();
                 if (!userMessage.empty())
                 {
                     message += ": " + userMessage;
 
-                    if (!assert.CustomMessage().empty())
+                    if (!customMessage.empty())
                     {
-                        message += "\n     " + assert.CustomMessage();
+                        message += "\n     " + customMessage;
                     }
                 }
-                else if (!assert.CustomMessage().empty())
+                else if (!customMessage.empty())
                 {
-                    message += ": " + assert.CustomMessage();
+                    message += ": " + customMessage;
                 }
                 else
                 {
 
                 fragments.emplace_back(Color::Default, message);
 
-                if (!assert.Expected().empty() || !assert.Actual().empty())
+                std::string expected = assert.GetExpected();
+                std::string actual = assert.GetActual();
+                if (!expected.empty() || !actual.empty())
                 {
                     fragments.emplace_back(Color::Expected, "\n     Expected: ");
-                    fragments.emplace_back(Color::Default, assert.Expected());
+                    fragments.emplace_back(Color::Default, expected);
                     fragments.emplace_back(Color::Actual, "\n       Actual: ");
-                    fragments.emplace_back(Color::Default, assert.Actual());
+                    fragments.emplace_back(Color::Default, actual);
                 }
             }
             else
             {
-                fragments.emplace_back(Color::Default, event.Message());
+                std::string message = event.GetMessage();
+                fragments.emplace_back(Color::Default, message);
             }
 
             fragments.emplace_back(Color::Default, "\n");
             return *this;
         }
 
-        const xUnitpp::TestDetails &TestDetails() const
+        const xUnitpp::ITestDetails &TestDetails() const
         {
             return testDetails;
         }
         TestOutput &operator =(TestOutput) /* = delete */;
 
     private:
-        const xUnitpp::TestDetails &testDetails;
+        const xUnitpp::ITestDetails &testDetails;
         std::vector<Fragment> fragments;
         bool failed;
         bool verbose;
         std::cout << TestOutput::Fragment(color, message);
     }
 
-    TestOutput &Cache(const xUnitpp::TestDetails &td)
+    TestOutput &Cache(const xUnitpp::ITestDetails &td)
     {
-        auto it = cache.find(td.Id);
+        auto id = td.GetId();
+
+        auto it = cache.find(id);
         if (it == cache.end())
         {
-            cache.insert(std::make_pair(td.Id, std::make_shared<TestOutput>(td, verbose)));
+            cache.insert(std::make_pair(id, std::make_shared<TestOutput>(td, verbose)));
         }
 
-        return *cache[td.Id];
+        return *cache[id];
     }
 
-    void Skip(const xUnitpp::TestDetails &testDetails, const std::string &reason)
+    void Skip(const xUnitpp::ITestDetails &testDetails, const std::string &reason)
     {
         if (!sort)
         {
             Instant(Color::Skip, "\n[ Skipped ] ");
 
-            if (!testDetails.Suite.empty())
+            auto suite = safestr(testDetails.GetSuite());
+            if (!suite.empty())
             {
-                Instant(Color::Suite, testDetails.Suite);
+                Instant(Color::Suite, suite);
                 Instant(Color::Separator, TestSeparator);
             }
 
-            Instant(Color::TestName, testDetails.Name + "\n");
-            Instant(Color::FileAndLine, to_string(testDetails.LineInfo) + ": ");
+            auto name = safestr(testDetails.GetName());
+            Instant(Color::TestName, name + "\n");
+            Instant(Color::FileAndLine, to_string(GetSafeLineInfo(testDetails)) + ": ");
             Instant(Color::Skip, reason);
             Instant(Color::Default, "\n");
         }
         }
     }
 
-    void Finish(const xUnitpp::TestDetails &td)
+    void Finish(const xUnitpp::ITestDetails &td)
     {
         if (!sort)
         {
-            auto it = cache.find(td.Id);
+            auto it = cache.find(td.GetId());
             if (it != cache.end())
             {
                 it->second->Print(false);
             std::sort(finalResults.begin(), finalResults.end(),
                 [](const std::shared_ptr<TestOutput> &lhs, const std::shared_ptr<TestOutput> &rhs)
                 {
-                    if (lhs->TestDetails().Suite != rhs->TestDetails().Suite)
+                    auto lhsSuite = safestr(lhs->TestDetails().GetSuite());
+                    auto rhsSuite = safestr(rhs->TestDetails().GetSuite());
+                    if (lhsSuite != rhsSuite)
                     {
-                        return lhs->TestDetails().Suite < rhs->TestDetails().Suite;
+                        return lhsSuite < rhsSuite;
                     }
 
-                    return lhs->TestDetails().Name < rhs->TestDetails().Name;
+                    auto lhsName = lhs->TestDetails().GetName();
+                    auto rhsName = rhs->TestDetails().GetName();
+                    return lhsName < rhsName;
                 });
 
             std::string curSuite = "";
                 {
                     if (group)
                     {
-                        if (curSuite != result->TestDetails().Suite)
+                        auto suite = safestr(result->TestDetails().GetSuite());
+                        if (curSuite != suite)
                         {
-                            curSuite = result->TestDetails().Suite;
+                            curSuite = suite;
 
                             std::string sep(curSuite.length() + 4, '=');
                             std::cout << TestOutput::Fragment(Color::Suite, "\n\n" + sep + "\n[ " + curSuite + " ]\n" + sep + "\n");
     //std::cout.sync_with_stdio(false);
 }
 
-void ConsoleReporter::ReportStart(const TestDetails &)
+void ConsoleReporter::ReportStart(const ITestDetails &)
 {
 }
 
-void ConsoleReporter::ReportEvent(const TestDetails &testDetails, const TestEvent &evt)
+void ConsoleReporter::ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt)
 {
     cache->Cache(testDetails) << evt;
 }
 
-void ConsoleReporter::ReportSkip(const TestDetails &testDetails, const std::string &reason)
+void ConsoleReporter::ReportSkip(const ITestDetails &testDetails, const char *reason)
 {
     cache->Skip(testDetails, reason);
 }
 
-void ConsoleReporter::ReportFinish(const TestDetails &testDetails, Time::Duration timeTaken)
+void ConsoleReporter::ReportFinish(const ITestDetails &testDetails, long long nsTaken)
 {
-    cache->Cache(testDetails) << timeTaken;
+    cache->Cache(testDetails) << Time::Duration(nsTaken);
     cache->Finish(testDetails);
 }
 
-void ConsoleReporter::ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, Time::Duration totalTime)
+void ConsoleReporter::ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, long long nsTotal)
 {
+    auto totalTime = Time::Duration(nsTotal);
+
     cache->Finish();
 
     Color failColor = Color::Default;

File xUnit++.console/ConsoleReporter.h

 public:
     ConsoleReporter(bool verbose, bool sort, bool group);
 
-    virtual void ReportStart(const TestDetails &) override;
-    virtual void ReportEvent(const TestDetails &testDetails, const TestEvent &evt) override;
-    virtual void ReportSkip(const TestDetails &testDetails, const std::string &reason) override;
-    virtual void ReportFinish(const TestDetails &, Time::Duration) override;
-    virtual void ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, Time::Duration totalTime) override;
+    virtual void __stdcall ReportStart(const ITestDetails &) override;
+    virtual void __stdcall ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt) override;
+    virtual void __stdcall ReportSkip(const ITestDetails &testDetails, const char *reason) override;
+    virtual void __stdcall ReportFinish(const ITestDetails &, long long nsTaken) override;
+    virtual void __stdcall ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failureCount, long long nsTotal) override;
 
 private:
     class ReportCache;

File xUnit++.console/main.cpp

 #include <tuple>
 #include <vector>
 #include "xUnit++/ExportApi.h"
-#include "xUnit++/TestDetails.h"
+#include "xUnit++/ITestDetails.h"
 #include "CommandLine.h"
 #include "ConsoleReporter.h"
 #include "TestAssembly.h"
         }
 
         std::vector<int> activeTestIds;
-        auto onList = [&](const xUnitpp::TestDetails &td)
+        auto onList = [&](const xUnitpp::ITestDetails &td)
             {
                 if (options.list)
                 {
                     std::cout << std::endl;
-                    for (const auto &att : td.Attributes)
+                    for (auto i = 0U; i != td.GetAttributeCount(); ++i)
                     {
-                        std::cout << ("[" + att.first + " = " + att.second + "]") << std::endl;
+                        std::cout << (std::string("[") + td.GetAttributeKey(i) + " = " + td.GetAttributeValue(i) + "]") << std::endl;
                     }
 
-                    std::cout << (td.Suite + " :: " + td.Name) << std::endl;
+                    std::cout << (td.GetSuite() + std::string(" :: ") + td.GetName()) << std::endl;
                 }
                 else
                 {
-                    activeTestIds.push_back(td.Id);
+                    activeTestIds.push_back(td.GetId());
                 }
             };
 
-        testAssembly.EnumerateTestDetails([&](const xUnitpp::TestDetails &td)
+        testAssembly.EnumerateTestDetails([&](const xUnitpp::ITestDetails &td)
             {
                 // check suites:
                 // if any suites are specified, a test has to belong to one of them to be run
                     {
                         std::regex regex(suite, std::regex_constants::icase);
 
-                        if (std::regex_search(td.Suite, regex))
+                        std::string suite = td.GetSuite() == nullptr ? "" : td.GetSuite();
+                        if (std::regex_search(suite, regex))
                         {
                             included = true;
                             break;
                     {
                         std::regex regex(name, std::regex_constants::icase);
 
-                        if (std::regex_search(td.Name, regex))
+                        if (std::regex_search(td.GetName(), regex))
                         {
                             included = true;
                             break;
 
                     for (auto test = options.inclusiveAttributes.begin(); !included && test != options.inclusiveAttributes.end(); ++test)
                     {
-                        auto range = td.Attributes.find(*test);
+                        size_t begin, end;
+                        td.FindAttributeKey(test->first.c_str(), begin, end);
 
-                        if (range.first != range.second)
+                        if (begin != end)
                         {
                             if (test->second == "")
                             {
                                 break;
                             }
 
-                            for (auto it = range.first; it != range.second; ++it)
+                            for (auto it = begin; it != end; ++it)
                             {
-                                if (range.first->second == test->second)
+                                if (td.GetAttributeValue(it) == test->second)
                                 {
                                     included = true;
                                     break;
                     bool matchFailed = false;
                     for (auto test = options.exclusiveAttributes.begin(); !matchFailed && test != options.exclusiveAttributes.end(); ++test)
                     {
-                        auto range = td.Attributes.find(*test);
+                        size_t begin, end;
+                        td.FindAttributeKey(test->first.c_str(), begin, end);
 
-                        if (range.first != range.second)
+                        if (begin != end)
                         {
                             // key matched, and we want to exclude all tests with a specific value
                             if (test->second != "")
                             {
-                                auto it = range.first;
-                                for (; it != range.second; ++it)
+                                for (; begin != end; ++begin)
                                 {
-                                    if (it->second == test->second)
+                                    if (td.GetAttributeValue(begin) == test->second)
                                     {
                                         break;
                                     }
                                 }
 
-                                if (it == range.second)
+                                if (begin == end)
                                 {
                                     matchFailed = true;
                                 }
                 (xUnitpp::IOutput *)new xUnitpp::ConsoleReporter(options.verbose, options.sort, options.group) :
                 (xUnitpp::IOutput *)new xUnitpp::Utilities::XmlReporter(options.xmlOutput));
             totalFailures += testAssembly.FilteredTestsRunner(options.timeLimit, options.threadLimit, *reporter,
-                [&](const xUnitpp::TestDetails &testDetails)
+                [&](const xUnitpp::ITestDetails &testDetails)
                 {
-                    return std::binary_search(activeTestIds.begin(), activeTestIds.end(), testDetails.Id);
+                    return std::binary_search(activeTestIds.begin(), activeTestIds.end(), testDetails.GetId());
                 });
         }
     }

File xUnit++/src/Attributes.cpp

     return sortedAttributes.end();
 }
 
+size_t AttributeCollection::size() const
+{
+    return sortedAttributes.size();
+}
+
+const AttributeCollection::Attribute &AttributeCollection::operator[](size_t index) const
+{
+    return sortedAttributes[index];
+}
+
 void AttributeCollection::sort()
 {
     std::sort(sortedAttributes.begin(), sortedAttributes.end());

File xUnit++/src/IOutput.cpp

-#include "IOutput.h"
-
-namespace xUnitpp
-{
-
-IOutput::~IOutput()
-{
-}
-
-}

File xUnit++/src/TestCollection.cpp

         }
     }
 
-    extern "C" __declspec(dllexport) int FilteredTestsRunner(int timeLimit, int threadLimit, xUnitpp::IOutput &testReporter, std::function<bool(const xUnitpp::TestDetails &)> filter)
+    extern "C" __declspec(dllexport) int FilteredTestsRunner(int timeLimit, int threadLimit, xUnitpp::IOutput &testReporter, xUnitpp::TestFilterCallback filter)
     {
         return xUnitpp::RunTests(testReporter, filter, xUnitpp::TestCollection::Instance().Tests(),
             xUnitpp::Time::ToDuration(xUnitpp::Time::ToMilliseconds(timeLimit)), threadLimit);

File xUnit++/src/TestDetails.cpp

         static int id = 0;
         return id++;
     }
+
+    std::string GetFullName(const std::string &name, int testInstance, const std::string &params)
+    {
+        if (params.empty())
+        {
+            return name;
+        }
+
+        return name + "[" + std::to_string(testInstance) + "]" + params;
+    }
 }
 
 namespace xUnitpp
     , TestInstance(testInstance)
     , Name(std::move(name))
     , Params(std::move(params))
+    , FullName(::GetFullName(Name, testInstance, Params))
     , Suite(suite)
     , Attributes(std::move(attributes))
     , TimeLimit(timeLimit)
 {
 }
 
-void swap(TestDetails &td0, TestDetails &td1)
+int __stdcall TestDetails::GetId() const
 {
-    using std::swap;
-
-    swap(td0.Id, td1.Id);
-    swap(td0.TestInstance, td1.TestInstance);
-    swap(td0.Name, td1.Name);
-    swap(td0.Params, td1.Params);
-    swap(td0.Suite, td1.Suite);
-    swap(td0.Attributes, td1.Attributes);
-    swap(td0.TimeLimit, td1.TimeLimit);
-    swap(td0.LineInfo, td1.LineInfo);
+    return Id;
 }
 
-std::string TestDetails::FullName() const
+const char * __stdcall TestDetails::GetName() const 
 {
-    if (Params.empty())
-    {
-        return Name;
-    }
-
-    return Name + "[" + std::to_string(TestInstance) + "]" + Params;
+    return Name.c_str();
 }
 
+const char * __stdcall TestDetails::GetFullName() const 
+{
+    return FullName.c_str();
 }
+
+const char * __stdcall TestDetails::GetSuite() const 
+{
+    return Suite.c_str();
+}
+
+size_t __stdcall TestDetails::GetAttributeCount() const 
+{
+    return Attributes.size();
+}
+
+const char * __stdcall TestDetails::GetAttributeKey(size_t index) const 
+{
+    return std::get<0>(Attributes[index]).c_str();
+}
+
+const char * __stdcall TestDetails::GetAttributeValue(size_t index) const 
+{
+    return std::get<1>(Attributes[index]).c_str();
+}
+
+void __stdcall TestDetails::FindAttributeKey(const char *key, size_t &begin, size_t &end) const
+{
+    auto range = Attributes.find(AttributeCollection::Attribute(key, ""));
+
+    begin = std::distance(Attributes.begin(), range.first);
+    end = std::distance(Attributes.end(), range.second);
+}
+
+const char * __stdcall TestDetails::GetFile() const 
+{
+    return LineInfo.file.c_str();
+}
+
+int __stdcall TestDetails::GetLine() const 
+{
+    return LineInfo.line;
+}
+
+
+}

File xUnit++/src/TestEvent.cpp

 #include "TestEvent.h"
+#include "EventLevel.h"
 #include "xUnitAssert.h"
 
 namespace xUnitpp
 
 std::string to_string(const TestEvent &event)
 {
-    if (event.IsAssertType())
+    if (event.GetIsAssertType())
     {
         auto &&assert = event.Assert();
         auto message = assert.Call() + "() failure";
 {
 }
 
-bool TestEvent::IsFailure() const
+bool TestEvent::GetIsFailure() const
 {
     return level > EventLevel::Warning;
 }
 
-EventLevel TestEvent::Level() const
+const char *TestEvent::GetToString() const
+{
+    if (toString.empty())
+    {
+        toString = to_string(*this);
+    }
+
+    return toString.c_str();
+}
+
+const char *TestEvent::GetFile() const
+{
+    return lineInfo.file.c_str();
+}
+
+int TestEvent::GetLine() const
+{
+    return lineInfo.line;
+}
+
+const xUnitpp::ITestAssert &TestEvent::GetAssertInterface() const
+{
+    return *this;
+}
+
+EventLevel TestEvent::GetLevel() const
 {
     return level;
 }
 
-bool TestEvent::IsAssertType() const
+const char *TestEvent::GetMessage() const
+{
+    return message.c_str();
+}
+
+bool TestEvent::GetIsAssertType() const
 {
     return !assert.Call().empty();
 }
     return lineInfo;
 }
 
+const char *TestEvent::GetCall() const 
+{
+    return assert.Call().c_str();
+}
+
+const char *TestEvent::GetUserMessage() const 
+{
+    return assert.UserMessage().c_str();
+}
+
+const char *TestEvent::GetCustomMessage() const 
+{
+    return assert.CustomMessage().c_str();
+}
+
+const char *TestEvent::GetExpected() const 
+{
+    return assert.Expected().c_str();
+}
+
+const char *TestEvent::GetActual() const 
+{
+    return assert.Actual().c_str();
+}
+
 }

File xUnit++/src/xUnitCheck.cpp

 #include "xUnitCheck.h"
+#include "EventLevel.h"
 #include "TestEvent.h"
 #include "TestEventRecorder.h"
 

File xUnit++/src/xUnitLog.cpp

 #include "xUnitLog.h"
+#include "EventLevel.h"
 #include "TestEventRecorder.h"
 
 namespace xUnitpp

File xUnit++/src/xUnitTest.cpp

 #include "xUnitTest.h"
+#include "EventLevel.h"
 #include "TestEventRecorder.h"
 #include "xUnitAssert.h"
 
 
     testEvents.push_back(std::move(evt));
 
-    if (testEvents.back().IsFailure())
+    if (testEvents.back().GetIsFailure())
     {
         failureEventLogged = true;
     }

File xUnit++/src/xUnitTestRunner.cpp

 #include <random>
 #include <stdexcept>
 #include <vector>
+#include "EventLevel.h"
+#include "ExportApi.h"
 #include "IOutput.h"
 #include "TestCollection.h"
 #include "TestDetails.h"
 #include "xUnitAssert.h"
 #include "xUnitTime.h"
 
-#include <iostream>
-
 namespace
 {
 
-class SharedOutput : public xUnitpp::IOutput
+class SharedOutput
 {
 public:
     SharedOutput(xUnitpp::IOutput &testReporter)
     {
     }
 
-    virtual void ReportStart(const xUnitpp::TestDetails &details) override
+    void ReportStart(const xUnitpp::TestDetails &details)
     {
         std::lock_guard<std::mutex> guard(mLock);
         mOutput.get().ReportStart(details);
     }
 
-    virtual void ReportEvent(const xUnitpp::TestDetails &details, const xUnitpp::TestEvent &evt) override
+    void ReportEvent(const xUnitpp::TestDetails &details, const xUnitpp::TestEvent &evt)
     {
         std::lock_guard<std::mutex> guard(mLock);
         mOutput.get().ReportEvent(details, evt);
     }
 
-    virtual void ReportSkip(const xUnitpp::TestDetails &details, const std::string &reason) override
+    void ReportSkip(const xUnitpp::TestDetails &details, const std::string &reason)
     {
         std::lock_guard<std::mutex> guard(mLock);
-        mOutput.get().ReportSkip(details, reason);
+        mOutput.get().ReportSkip(details, reason.c_str());
     }
 
-    virtual void ReportFinish(const xUnitpp::TestDetails &details, xUnitpp::Time::Duration time) override
+    void ReportFinish(const xUnitpp::TestDetails &details, xUnitpp::Time::Duration time)
     {
         std::lock_guard<std::mutex> guard(mLock);
-        mOutput.get().ReportFinish(details, time);
+        mOutput.get().ReportFinish(details, time.count());
     }
 
-    virtual void ReportAllTestsComplete(size_t total, size_t skipped, size_t failed, xUnitpp::Time::Duration totalTime) override
+    void ReportAllTestsComplete(size_t total, size_t skipped, size_t failed, xUnitpp::Time::Duration totalTime)
     {
-        mOutput.get().ReportAllTestsComplete(total, skipped, failed, totalTime);
+        mOutput.get().ReportAllTestsComplete(total, skipped, failed, totalTime.count());
     }
 
 private:
 namespace xUnitpp
 {
 
-int RunTests(IOutput &output, std::function<bool(const TestDetails &)> filter, const std::vector<std::shared_ptr<xUnitTest>> &tests, Time::Duration maxTestRunTime, size_t maxConcurrent)
+int RunTests(IOutput &output, TestFilterCallback filter, const std::vector<std::shared_ptr<xUnitTest>> &tests, Time::Duration maxTestRunTime, size_t maxConcurrent)
 {
     auto timeStart = Time::Clock::now();
 
     std::vector<std::future<void>> futures;
     for (auto &test : activeTests)
     {
+        if (test->TestDetails().Attributes.Skipped().first)
         {
-            auto skip = test->TestDetails().Attributes.Skipped();
-            if (skip.first)
-            {
-                skippedTests++;
-                sharedOutput.ReportSkip(test->TestDetails(), skip.second);
-                continue;
-            }
+            skippedTests++;
+            sharedOutput.ReportSkip(test->TestDetails(), test->TestDetails().Attributes.Skipped().second);
+            continue;
         }
 
         futures.push_back(std::async([&]()

File xUnit++/src/xUnitWarn.cpp

 #include "xUnitWarn.h"
+#include "EventLevel.h"
 #include "TestEvent.h"
 #include "TestEventRecorder.h"
 

File xUnit++/xUnit++.vcxproj

     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="src/TestEvent.cpp" />
-    <ClCompile Include="src/IOutput.cpp" />
-    <ClCompile Include="src/LineInfo.cpp" />
-    <ClCompile Include="src/TestCollection.cpp" />
-    <ClCompile Include="src/TestDetails.cpp" />
-    <ClCompile Include="src/xUnitAssert.cpp" />
-    <ClCompile Include="src/xUnitTest.cpp" />
-    <ClCompile Include="src/xUnitTestRunner.cpp" />
-    <ClCompile Include="src/TestEventRecorder.cpp" />
-    <ClCompile Include="src/xUnitCheck.cpp" />
-    <ClCompile Include="src/xUnitLog.cpp" />
-    <ClCompile Include="src/xUnitWarn.cpp" />
+    <ClCompile Include="src\TestEvent.cpp" />
+    <ClCompile Include="src\LineInfo.cpp" />
+    <ClCompile Include="src\TestCollection.cpp" />
+    <ClCompile Include="src\TestDetails.cpp" />
+    <ClCompile Include="src\xUnitAssert.cpp" />
+    <ClCompile Include="src\xUnitTest.cpp" />
+    <ClCompile Include="src\xUnitTestRunner.cpp" />
+    <ClCompile Include="src\TestEventRecorder.cpp" />
+    <ClCompile Include="src\xUnitCheck.cpp" />
+    <ClCompile Include="src\xUnitLog.cpp" />
+    <ClCompile Include="src\xUnitWarn.cpp" />
     <ClCompile Include="src\Attributes.cpp" />
   </ItemGroup>
   <ItemGroup>
-    <ClInclude Include="xUnit++/Attributes.h" />
-    <ClInclude Include="xUnit++/ExportApi.h" />
-    <ClInclude Include="xUnit++/IOutput.h" />
-    <ClInclude Include="xUnit++/LineInfo.h" />
-    <ClInclude Include="xUnit++/Suite.h" />
-    <ClInclude Include="xUnit++/TestCollection.h" />
-    <ClInclude Include="xUnit++/xUnitMacros.h" />
-    <ClInclude Include="xUnit++/TestDetails.h" />
-    <ClInclude Include="xUnit++/xUnit++.h" />
-    <ClInclude Include="xUnit++/xUnitAssert.h" />
-    <ClInclude Include="xUnit++/xUnitMacroHelpers.h" />
-    <ClInclude Include="xUnit++/xUnitTest.h" />
-    <ClInclude Include="xUnit++/xUnitTestRunner.h" />
-    <ClInclude Include="xUnit++/xUnitTime.h" />
+    <ClInclude Include="xUnit++\Attributes.h" />
+    <ClInclude Include="xUnit++\ExportApi.h" />
+    <ClInclude Include="xUnit++\IOutput.h" />
+    <ClInclude Include="xUnit++\LineInfo.h" />
+    <ClInclude Include="xUnit++\Suite.h" />
+    <ClInclude Include="xUnit++\TestCollection.h" />
+    <ClInclude Include="xUnit++\TestDetails.h" />
+    <ClInclude Include="xUnit++\TestEvent.h" />
+    <ClInclude Include="xUnit++\xUnitMacros.h" />
+    <ClInclude Include="xUnit++\xUnit++.h" />
+    <ClInclude Include="xUnit++\xUnitAssert.h" />
+    <ClInclude Include="xUnit++\xUnitMacroHelpers.h" />
+    <ClInclude Include="xUnit++\xUnitTest.h" />
+    <ClInclude Include="xUnit++\xUnitTestRunner.h" />
+    <ClInclude Include="xUnit++\xUnitTime.h" />
+    <ClInclude Include="xUnit++\EventLevel.h" />
+    <ClInclude Include="xUnit++\ITestDetails.h" />
+    <ClInclude Include="xUnit++\ITestEvent.h" />
     <ClInclude Include="xUnit++\TestEventRecorder.h" />
-    <ClInclude Include="xUnit++/TestEvent.h" />
-    <ClInclude Include="xUnit++/xUnitCheck.h" />
+    <ClInclude Include="xUnit++\xUnitCheck.h" />
     <ClInclude Include="xUnit++\xUnitLog.h" />
     <ClInclude Include="xUnit++\xUnitToString.h" />
     <ClInclude Include="xUnit++\xUnitWarn.h" />

File xUnit++/xUnit++.vcxproj.filters

 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <ClCompile Include="src/xUnitAssert.cpp" />
-    <ClCompile Include="src/TestCollection.cpp" />
-    <ClCompile Include="src/TestDetails.cpp" />
-    <ClCompile Include="src/xUnitTestRunner.cpp" />
-    <ClCompile Include="src/LineInfo.cpp" />
-    <ClCompile Include="src/xUnitTest.cpp" />
-    <ClCompile Include="src/IOutput.cpp" />
-    <ClCompile Include="src/xUnitCheck.cpp" />
-    <ClCompile Include="src/TestEvent.cpp" />
-    <ClCompile Include="src/xUnitWarn.cpp" />
-    <ClCompile Include="src/TestEventRecorder.cpp" />
-    <ClCompile Include="src/xUnitLog.cpp" />
     <ClCompile Include="src\Attributes.cpp" />
+    <ClCompile Include="src\TestEvent.cpp" />
+    <ClCompile Include="src\LineInfo.cpp" />
+    <ClCompile Include="src\TestCollection.cpp" />
+    <ClCompile Include="src\TestDetails.cpp" />
+    <ClCompile Include="src\xUnitAssert.cpp" />
+    <ClCompile Include="src\xUnitTest.cpp" />
+    <ClCompile Include="src\xUnitTestRunner.cpp" />
+    <ClCompile Include="src\TestEventRecorder.cpp" />
+    <ClCompile Include="src\xUnitCheck.cpp" />
+    <ClCompile Include="src\xUnitLog.cpp" />
+    <ClCompile Include="src\xUnitWarn.cpp" />
   </ItemGroup>
   <ItemGroup>
-    <ClInclude Include="xUnit++/xUnitAssert.h" />
-    <ClInclude Include="xUnit++/TestCollection.h" />
-    <ClInclude Include="xUnit++/Suite.h" />
-    <ClInclude Include="xUnit++/TestDetails.h" />
-    <ClInclude Include="xUnit++/xUnitTime.h" />
-    <ClInclude Include="xUnit++/xUnit++.h" />
-    <ClInclude Include="xUnit++/xUnitTestRunner.h" />
-    <ClInclude Include="xUnit++/Attributes.h" />
-    <ClInclude Include="xUnit++/xUnitMacroHelpers.h" />
-    <ClInclude Include="xUnit++/IOutput.h" />
-    <ClInclude Include="xUnit++/ExportApi.h" />
-    <ClInclude Include="xUnit++/LineInfo.h" />
-    <ClInclude Include="xUnit++/xUnitTest.h" />
-    <ClInclude Include="xUnit++/xUnitMacros.h" />
-    <ClInclude Include="xUnit++/xUnitCheck.h" />
-    <ClInclude Include="xUnit++/TestEvent.h" />
     <ClInclude Include="xUnit++\xUnitWarn.h" />
     <ClInclude Include="xUnit++\TestEventRecorder.h" />
     <ClInclude Include="xUnit++\xUnitLog.h" />
     <ClInclude Include="xUnit++\xUnitToString.h" />
+    <ClInclude Include="xUnit++\ITestEvent.h" />
+    <ClInclude Include="xUnit++\ITestDetails.h" />
+    <ClInclude Include="xUnit++\EventLevel.h" />
+    <ClInclude Include="xUnit++\Attributes.h" />
+    <ClInclude Include="xUnit++\ExportApi.h" />
+    <ClInclude Include="xUnit++\IOutput.h" />
+    <ClInclude Include="xUnit++\LineInfo.h" />
+    <ClInclude Include="xUnit++\Suite.h" />
+    <ClInclude Include="xUnit++\TestCollection.h" />
+    <ClInclude Include="xUnit++\xUnitMacros.h" />
+    <ClInclude Include="xUnit++\xUnit++.h" />
+    <ClInclude Include="xUnit++\xUnitAssert.h" />
+    <ClInclude Include="xUnit++\xUnitMacroHelpers.h" />
+    <ClInclude Include="xUnit++\xUnitTestRunner.h" />
+    <ClInclude Include="xUnit++\xUnitTime.h" />
+    <ClInclude Include="xUnit++\xUnitCheck.h" />
+    <ClInclude Include="xUnit++\xUnitTest.h" />
+    <ClInclude Include="xUnit++\TestDetails.h" />
+    <ClInclude Include="xUnit++\TestEvent.h" />
   </ItemGroup>
 </Project>

File xUnit++/xUnit++/Attributes.h

 
     const_iterator begin() const;
     const_iterator end() const;
+    size_t size() const;
+    const Attribute &operator[](size_t index) const;
 
     void sort();
 

File xUnit++/xUnit++/EventLevel.h

+#ifndef EVENTLEVEL_H_
+#define EVENTLEVEL_H_
+
+#include <string>
+
+namespace xUnitpp
+{
+    enum class EventLevel
+    {
+        Debug,
+        Info,
+        Warning,
+        Check,
+        Assert,
+        Fatal
+    };
+
+    const std::string &to_string(EventLevel level);
+}
+
+#endif

File xUnit++/xUnit++/ExportApi.h

-#ifndef EXPORTSAPI_H_
-#define EXPORTSAPI_H_
+#ifndef EXPORTAPI_H_
+#define EXPORTAPI_H_
 
 #include <functional>
 #include <memory>
 namespace xUnitpp
 {
     struct IOutput;
-    struct TestDetails;
+    struct ITestDetails;
 
-    typedef std::function<void(const TestDetails &)> EnumerateTestDetailsCallback;
+    typedef std::function<void(const ITestDetails &)> EnumerateTestDetailsCallback;
     typedef void(*EnumerateTestDetails)(EnumerateTestDetailsCallback callback);
 
-    typedef std::function<bool(const TestDetails &)> TestFilterCallback;
+    typedef std::function<bool(const ITestDetails &)> TestFilterCallback;
     typedef int(*FilteredTestsRunner)(int, int, IOutput &, TestFilterCallback);
 }
 

File xUnit++/xUnit++/IOutput.h

 #ifndef IOUTPUT_H_
 #define IOUTPUT_H_
 
+// !!!VS remove the #if/#endif when VS can compile this code
+#if defined(_MSC_VER)
+# define DEFAULT {}
+#else
+# define DEFAULT = default;
+#endif
+
 #include <string>
 #include "xUnitTime.h"
 
 namespace xUnitpp
 {
 
-struct LineInfo;
-struct TestDetails;
-class TestEvent;
+struct ITestDetails;
+struct ITestEvent;
 
 struct IOutput
 {
-    virtual ~IOutput();
+    virtual ~IOutput() DEFAULT
 
-    virtual void ReportStart(const TestDetails &testDetails) = 0;
-    virtual void ReportEvent(const TestDetails &testDetails, const TestEvent &evt) = 0;
-    virtual void ReportSkip(const TestDetails &testDetails, const std::string &reason) = 0;
-    virtual void ReportFinish(const TestDetails &testDetails, Time::Duration timeTaken) = 0;
-    virtual void ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failed, Time::Duration totalTime) = 0; 
+    virtual void __stdcall ReportStart(const ITestDetails &testDetails) = 0;
+    virtual void __stdcall ReportEvent(const ITestDetails &testDetails, const ITestEvent &evt) = 0;
+    virtual void __stdcall ReportSkip(const ITestDetails &testDetails, const char *reason) = 0;
+    virtual void __stdcall ReportFinish(const ITestDetails &testDetails, long long ns) = 0;
+    virtual void __stdcall ReportAllTestsComplete(size_t testCount, size_t skipped, size_t failed, long long nsTotal) = 0; 
 };
 
 }

File xUnit++/xUnit++/ITestDetails.h

+#ifndef ITESTDETAILS_H_
+#define ITESTDETAILS_H_
+
+// !!!VS remove the #if/#endif when VS can compile this code
+#if defined(_MSC_VER)
+# define DEFAULT {}
+#else
+# define DEFAULT = default;
+#endif
+
+namespace xUnitpp
+{
+
+struct ITestDetails
+{
+protected:
+    virtual ~ITestDetails() DEFAULT
+
+public:
+    // These are explicitly NOT returning C++ classes, as they will cross dll boundaries.
+    // If one side is Debug and the other Release, or if they are different compilers,
+    // bad things will happen.
+    virtual int __stdcall GetId() const = 0;
+    virtual const char * __stdcall GetName() const = 0;
+    virtual const char * __stdcall GetFullName() const = 0;
+    virtual const char * __stdcall GetSuite() const = 0;
+    virtual size_t __stdcall GetAttributeCount() const = 0;
+    virtual const char * __stdcall GetAttributeKey(size_t index) const = 0;
+    virtual const char * __stdcall GetAttributeValue(size_t index) const = 0;
+    virtual void __stdcall FindAttributeKey(const char *key, size_t &begin, size_t &end) const = 0;
+    virtual const char * __stdcall GetFile() const = 0;
+    virtual int __stdcall GetLine() const = 0;
+};
+
+}
+
+#endif

File xUnit++/xUnit++/ITestEvent.h

+#ifndef ITESTEVENT_H_
+#define ITESTEVENT_H_
+
+// !!!VS remove the #if/#endif when VS can compile this code
+#if defined(_MSC_VER)
+# define DEFAULT {}
+#else
+# define DEFAULT = default;
+#endif
+
+namespace xUnitpp
+{
+
+enum class EventLevel;
+
+struct ITestAssert
+{
+protected:
+    virtual ~ITestAssert() DEFAULT
+
+public:
+    virtual const char * __stdcall GetCall() const = 0;
+    virtual const char * __stdcall GetUserMessage() const = 0;
+    virtual const char * __stdcall GetCustomMessage() const = 0;
+    virtual const char * __stdcall GetExpected() const = 0;
+    virtual const char * __stdcall GetActual() const = 0;