diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7b1dd49113daaacbe0eccdaf670e4fe5e397015f..7865e8cebc9af41ccc553f06794af4d467b003b3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,7 @@ base_add_subdir(Code)
 base_add_subdir(Config)
 base_add_subdir(Debug)
 base_add_subdir(Journal)
+base_add_subdir(Paths)
 base_add_subdir(Progress)
 base_add_subdir(Security)
 base_add_subdir(Test)
diff --git a/Paths/CMakeLists.txt b/Paths/CMakeLists.txt
new file mode 100755
index 0000000000000000000000000000000000000000..92db745614852ba13fdd8260586ac5169b838dc3
--- /dev/null
+++ b/Paths/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(my_headers
+   ${CMAKE_CURRENT_SOURCE_DIR}/Filesystem.hh
+   ${CMAKE_CURRENT_SOURCE_DIR}/PathLink.hh
+   PARENT_SCOPE
+)
+
+set(my_sources
+   PARENT_SCOPE
+)
diff --git a/Test/TestFilesystem.hh b/Paths/Filesystem.hh
similarity index 66%
rename from Test/TestFilesystem.hh
rename to Paths/Filesystem.hh
index 96593766865a037a1204670ea01eafe49d71210f..93ac9f80dde23682a90758efa154a26e48a0815e 100755
--- a/Test/TestFilesystem.hh
+++ b/Paths/Filesystem.hh
@@ -1,34 +1,29 @@
 // (C) Copyright 2021 by Autodesk, Inc.
 
-#ifndef BASE_TEST_FILESYSTEM_HH_INCLUDE
-#define BASE_TEST_FILESYSTEM_HH_INCLUDE
-
-#ifdef TEST_ON
+#ifndef BASE_FILESYSTEM_HH_INCLUDE
+#define BASE_FILESYSTEM_HH_INCLUDE
 
 #include <Base/Security/Mandatory.hh>
 
 #ifdef __APPLE__
 #include <boost/filesystem.hpp>
-namespace Test
+namespace Base
 {
 namespace filesystem = boost::filesystem;
 typedef boost::filesystem::path Path;
 typedef boost::system::error_code error_code;
-} // namespace Test
+} // namespace Base
 #else // __APPLE__
 // Include <filesystem> but silence resulting C4995 warnings
 INSECURE_INCLUDE_SECTION_BEGIN
 #include <filesystem>
 INSECURE_INCLUDE_SECTION_END
 
-namespace Test
+namespace Base
 {
 namespace filesystem = std::filesystem;
-typedef std::filesystem::path Path;
 typedef std::error_code error_code;
-} // namespace Test
+} // namespace Base
 #endif // __APPLE__
 
-#endif // TEST_ON
-
-#endif // BASE_TEST_FILESYSTEM_HH_INCLUDE
+#endif // BASE_FILESYSTEM_HH_INCLUDE
diff --git a/Paths/PathLink.hh b/Paths/PathLink.hh
new file mode 100755
index 0000000000000000000000000000000000000000..695d40f9638e59d247d74d751455cbcdd59c4ac3
--- /dev/null
+++ b/Paths/PathLink.hh
@@ -0,0 +1,32 @@
+// (C) Copyright 2020 by Autodesk, Inc.
+
+#ifndef BASE_PATHLINK_HH_INCLUDED
+#define BASE_PATHLINK_HH_INCLUDED
+
+#include <Base/Paths/Filesystem.hh>
+
+namespace Base
+{
+namespace fs = Base::filesystem;
+
+class PathLink
+{
+public:
+  explicit PathLink(const fs::path& _path) : path_(_path) {}
+
+  template <class StreamT>
+  friend StreamT& operator<<(StreamT& _os, const PathLink& _link)
+  {
+    _os << "\""
+        << "file://" << fs::weakly_canonical(_link.path_).generic_string()
+        << "\"";
+    return _os;
+  }
+
+private:
+  const fs::path& path_;
+};
+
+} // namespace Base
+
+#endif // BASE_PATHLINK_HH_INCLUDED
diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt
index a28da381b1201bdcdc8eb08fae1a30e066cab11f..fa9d180af00456b476960f2555d843be3637b05d 100644
--- a/Test/CMakeLists.txt
+++ b/Test/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(my_headers
    ${CMAKE_CURRENT_SOURCE_DIR}/LongestCommonSubsequenceT.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestArgs.hh
+   ${CMAKE_CURRENT_SOURCE_DIR}/TestCase.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksum.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumCompare.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumCompletion.hh
@@ -11,7 +12,6 @@ set(my_headers
    ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumLevel.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumNumberT.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumOutcome.hh
-   ${CMAKE_CURRENT_SOURCE_DIR}/TestFilesystem.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestOutcome.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestPaths.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/TestReport.hh
diff --git a/Test/TestCase.hh b/Test/TestCase.hh
new file mode 100755
index 0000000000000000000000000000000000000000..6a1dea61d1fc27f674f4f2c56516aff1f5cc514d
--- /dev/null
+++ b/Test/TestCase.hh
@@ -0,0 +1,80 @@
+// (C) Copyright 2021 by Autodesk, Inc.
+
+#ifndef BASE_TESTCASE_HH_INCLUDED
+#define BASE_TESTCASE_HH_INCLUDED
+
+#ifdef TEST_ON
+
+/*
+The BASE_TEST() macro wraps around an existing test declaration macro (e.g.
+TEST_CASE() for Catch2), augmenting it with a checksum comparison. For now, the
+existing test declaration macro must define a void function.
+
+In order to use BASE_TEST(), the following macros *must* be defined (before this
+file is included):
+
+- TEST_DECLARATION: a placeholder for the existing test declaration macro;
+
+- TEST_COMPARE_CHECKSUMS: a placeholder for a function that compares checksums
+  output by the currently-running test against the corresponding baseline.
+
+As an example, to use this with Catch2, one would write:
+
+    #define TEST_DECLARATION TEST_CASE
+
+And to compare the checksums using Test::compare_checksums(), one would write:
+
+    #define TEST_COMPARE_CHECKSUMS Test::compare_checksums()
+
+It is not required to use BASE_TEST(), and it is straightforward to define a
+custom macro with a similar function. The following macros are available for
+use:
+
+- INTERNAL_UNIQUE_NAME(STEM): uses the compiler extension __COUNTER__ to provide
+  a unique name that starts with STEM.
+
+- INTERNAL_BASE_TEST_FUNCTION_STEM: A stem that can be used when defining your
+  own "Test and Compare" macros.
+
+- INTERNAL_UNIQUE_BASE_TEST_FUNCTION_NAME: A unique function name that can be
+  utilised in your own "Test and Compare" macros.
+*/
+
+#define INTERNAL_UNIQUE_NAME3(STEM, COUNTER) STEM##COUNTER
+#define INTERNAL_UNIQUE_NAME2(STEM, COUNTER) \
+  INTERNAL_UNIQUE_NAME3(STEM, COUNTER)
+#define INTERNAL_UNIQUE_NAME(STEM) INTERNAL_UNIQUE_NAME2(STEM, __COUNTER__)
+
+#define INTERNAL_BASE_TEST_FUNCTION_STEM ____B_A_S_E____T_E_S_T____
+#define INTERNAL_UNIQUE_BASE_TEST_FUNCTION_NAME \
+  INTERNAL_UNIQUE_NAME(BASE_TEST_FUNCTION_STEM)
+
+// Both TEST_DECLARATION and TEST_COMPARE_CHECKSUMS must be defined in order to
+// use the BASE_TEST() macro.
+#if defined(TEST_DECLARATION) && defined(TEST_COMPARE_CHECKSUMS)
+
+#define INTERNAL_BASE_TEST_VOID(TEST_FUNCTION, ...) \
+  static void TEST_FUNCTION(); \
+  TEST_DECLARATION(__VA_ARGS__) \
+  { \
+    TEST_FUNCTION(); \
+    TEST_COMPARE_CHECKSUMS; \
+  } \
+  void TEST_FUNCTION()
+
+#define BASE_TEST(...) \
+  INTERNAL_BASE_TEST_VOID(INTERNAL_UNIQUE_BASE_TEST_FUNCTION_NAME, __VA_ARGS__)
+
+#else // defined(TEST_DECLARATION) && defined(TEST_COMPARE_CHECKSUMS)
+
+#define BASE_TEST(...) \
+  static_assert(false, \
+      "Both TEST_DECLARATION and TEST_COMPARE_CHECKSUMS must be " \
+      "defined in order to use the BASE_TEST() macro. Please ensure " \
+      "they are defined before TestCase.hh is included."); \
+  static void INTERNAL_UNIQUE_BASE_TEST_FUNCTION_NAME()
+
+#endif // defined(TEST_DECLARATION) && defined(TEST_COMPARE_CHECKSUMS)
+
+#endif // TEST_ON
+#endif // BASE_TESTCASE_HH_INCLUDED
diff --git a/Test/TestChecksum.cc b/Test/TestChecksum.cc
index 2d5f486b0f3c9cd3b9ef650bc585e1f8929a0aef..bfe1f659c7d037ead4e39a13e7098576aa823070 100644
--- a/Test/TestChecksum.cc
+++ b/Test/TestChecksum.cc
@@ -5,6 +5,7 @@
 #include "TestChecksum.hh"
 #include "Base/Debug/DebCallStack.hh"
 
+#include <algorithm>
 #include <fstream>
 #include <iostream>
 #include <map>
@@ -31,6 +32,15 @@ Registry& registry_modify()
   return chksm_reg;
 }
 
+// Trim (remove whitespace) from the end of a std::string (in place)
+inline void rtrim(std::string& s)
+{
+  s.erase(std::find_if(s.rbegin(), s.rend(),
+              [](unsigned char ch) { return !std::isspace(ch); })
+              .base(),
+      s.end());
+}
+
 }//namespace
 
 const Registry& registry()
@@ -162,12 +172,14 @@ std::string Object::format_checksum(const Result& _rslt, const String& _data)
 
 void Object::add(const Result& _rslt, const String& _data)
 {
-  static std::mutex mtx; //synchronize access to the checksum report stream
+  static std::mutex mtx; // synchronize access to the checksum report stream
   std::lock_guard<std::mutex> lock(mtx);
 
-  static Base::OutputStreamAdaptT<std::ofstream> test_strm(REPORT_FILENAME);
+  static Base::OutputStreamAdaptT<std::ofstream> rprt_strm(REPORT_FILENAME);
   static bool tag_out = false;
 
+  Base::OStringStream strm;
+
 #ifdef DEB_ON
   static String prev_call_stck;
   String call_stck("/");
@@ -175,21 +187,30 @@ void Object::add(const Result& _rslt, const String& _data)
 
   if (prev_call_stck != call_stck)
   {
-    test_strm << call_stck << ::Base::ENDL;
+    strm << call_stck << ::Base::ENDL;
     prev_call_stck = call_stck;
   }
-#endif//DEB_ON
+#endif // DEB_ON
 
   if (!tag_out)
   {
     tag_out = true;
-    test_strm << REPORT_LEVEL_TAG << LEVEL_TEXT[run_lvl] << ::Base::ENDL;
+    strm << REPORT_LEVEL_TAG << LEVEL_TEXT[run_lvl] << ::Base::ENDL;
+  }
+
+  // Write checksum data and remove trailing whitespace
+  strm << format_checksum(_rslt, _data);
+  rtrim(strm.str);
+
+  // Write stream data to the report file if non-empty
+  if (!strm.str.empty())
+  {
+    rprt_strm << strm.str << ::Base::ENDL;
+    rprt_strm.stream().flush();
   }
-  test_strm << format_checksum(_rslt, _data);
-  test_strm.stream().flush();
 }
 
-}//Checksum
-}//namespace Test
+} // namespace Checksum
+} // namespace Test
 
 #endif//TEST_ON
diff --git a/Test/TestChecksumCompletion.cc b/Test/TestChecksumCompletion.cc
index 22d191324347fb6e209118fc76b44fe42f2775cd..e8b2077603bf4e932a08e868b7b2ddc3c27b7027 100644
--- a/Test/TestChecksumCompletion.cc
+++ b/Test/TestChecksumCompletion.cc
@@ -36,19 +36,12 @@ const char* const END = "END";
 
 } // namespace
 
-Completion::Completion()
-    : Object("Completion", L_STABLE),
-      start_time_(std::chrono::system_clock::now())
-{
-}
+Completion::Completion() : Object("Completion", L_STABLE) {}
 
 void Completion::record_end()
 {
-  auto end_time = std::chrono::system_clock::now();
-  std::chrono::duration<double> time_diff_sec = end_time - start_time_;
-
   std::stringstream mess;
-  mess << "Success: " << END << " Time: " << time_diff_sec.count();
+  mess << "Success: " << END;
 
   add(Result::OK, mess.str());
 }
diff --git a/Test/TestChecksumCompletion.hh b/Test/TestChecksumCompletion.hh
index 78c7fc81cf6ebf6ef0b8b84859381dda30f3ed7d..ee48b3c6bb2c10275aa6a7a3be923bd6a2cd13fa 100644
--- a/Test/TestChecksumCompletion.hh
+++ b/Test/TestChecksumCompletion.hh
@@ -16,7 +16,7 @@ namespace Test
 namespace Checksum
 {
 
-// Checks it the test has completed and, if not, detect the problem for compare.
+// Writes a checksum when the test completes.
 class Completion : public Object
 {
 public:
@@ -27,9 +27,6 @@ public:
 
   //! Get if the line contains the end completion checksum record
   static bool end(const std::string& _line);
-
-private:
-  std::chrono::time_point<std::chrono::system_clock> start_time_;
 };
 
 // Register the checksum to check test completion.
diff --git a/Test/TestOutcome.cc b/Test/TestOutcome.cc
index 86dfce6c67aeeab20041132725ee79614731dd5e..decf45a64e155de6c42579ffdd5f616cae275523 100755
--- a/Test/TestOutcome.cc
+++ b/Test/TestOutcome.cc
@@ -50,7 +50,7 @@ void ignore_unstable_outcome(const char* const _call)
 {
   std::cout
       << "-----------------------\n"
-         "INFO: Unstable outcome from the following call has been ignored:"
+         "Info: Unstable outcome from the following call has been ignored:"
          "\n\n  " << _call << "\n\n"
          "Replace IGNORE_UNSTABLE_OUTCOME() with CHECK_OUTCOME() once "
          "the functionality is stable.\n"
diff --git a/Test/TestReport.cc b/Test/TestReport.cc
index 12cca98e418e626084cb331bfc9700e35a6ae4fc..a191bcafdfb67e32fdb6855c85ac4460fb2b2931 100644
--- a/Test/TestReport.cc
+++ b/Test/TestReport.cc
@@ -3,13 +3,13 @@
 #ifdef TEST_ON
 
 #include <Base/Security/Mandatory.hh>
+#include <Base/Paths/Filesystem.hh>
+#include <Base/Paths/PathLink.hh>
 #include "TestChecksumCompletion.hh"
-#include "TestFilesystem.hh"
 #include "TestReport.hh"
 #include "TestResult.hh"
 #include "TestResultAnalysis.hh"
 
-#include <algorithm>
 #include <array>
 #include <cctype>
 #include <cstring>
@@ -26,13 +26,16 @@
 
 namespace Test
 {
+
+using PathLink = Base::PathLink;
+
 namespace
 {
 
 const char* const INDENT = "    ";
 const char CR = (char)'\r';
 
-namespace fs = Test::filesystem;
+namespace fs = Base::filesystem;
 
 /*!
 Contains the test path and, if the CTest log is parsed, any associated error
@@ -379,20 +382,6 @@ struct TestDiffSummary
   Test::DifferenceDistribution diffs_;
 };
 
-struct PathLink
-{
-  const fs::path& path_;
-  explicit PathLink(const fs::path& _path) : path_(_path) {}
-
-  template <class StreamT>
-  friend StreamT& operator<<(StreamT& _os, const PathLink& _link)
-  {
-    _os << "\"file:////"
-        << fs::canonical(fs::absolute(_link.path_)).generic_string() << "\"";
-    return _os;
-  }
-};
-
 /*!
 Reads the report information from a test output directory and:
 - copies it to the output stream;
@@ -447,77 +436,25 @@ Result parse_report(const fs::path& _test_dir, const std::string& _err_msg,
   return rslt;
 }
 
-// Trim (remove whitespace) from the end of a std::string
-inline std::string& rtrim(std::string& s)
-{
-  s.erase(std::find_if(s.rbegin(), s.rend(),
-              [](unsigned char ch) { return !std::isspace(ch); })
-              .base(),
-      s.end());
-  return s;
-}
-
-// Apply report content filters. For now this only removes blank lines and time
-// information
-void filter_report(const Test::Path& _rprt_src, const Test::Path& _rprt_trgt)
-{
-#if __APPLE__
-  std::ifstream ifs(_rprt_src.string());
-#else // __APPLE__
-  std::ifstream ifs(_rprt_src);
-#endif // __APPLE__
-  std::vector<std::string> rprt_lines;
-
-  // Read the report contents line-by-line (to improve performance) and filter
-  // out the time information
-  for (std::string line; std::getline(ifs, line);)
-  {
-    // Search for time information
-    const auto time_pos = line.find("Time:");
-    if (time_pos != std::string::npos)
-    {
-      // Trim the time information
-      line.resize(time_pos);
-
-      // Filter the line if it is only whitespace
-      if (std::all_of(line.begin(), line.end(), isspace))
-        continue;
-    }
-    rprt_lines.push_back(rtrim(line));
-  }
-  ifs.close();
-
-  // Write all of the stored lines to the target report file
-#if __APPLE__
-  std::ofstream ofs(_rprt_trgt.string());
-#else // __APPLE__
-  std::ofstream ofs(_rprt_trgt);
-#endif // __APPLE__
-  for (const auto& line : rprt_lines)
-    ofs << line << std::endl;
-  ofs.close();
-}
-
 // Make a 'clean' copy of a test directory, i.e. a 'baseline', which contains
 // the same directory structure as the test directory, but only the report.txt
-// files (with some bits filtered out).
-void make_baseline(const Path& _test_root_dir, const Path& _bsln_root_dir,
-    const TestList& _test_list)
+// files.
+void make_baseline(const fs::path& _test_root_dir,
+    const fs::path& _bsln_root_dir, const TestList& _test_list)
 {
   if (fs::exists(_bsln_root_dir))
     return; // Baseline already present.
 
   fs::create_directory(_bsln_root_dir);
 
-  // Copy a 'cleaned' version of the global report to the baseline directory.
+  // Copy the global report to the baseline directory.
   std::cout << "Processing the global report... ";
   const auto main_rep_flnm(_test_root_dir / Test::REPORT_FILENAME);
   const auto bsln_rep_flnm(_bsln_root_dir / Test::REPORT_FILENAME);
-  filter_report(main_rep_flnm, bsln_rep_flnm);
+  fs::copy(main_rep_flnm, bsln_rep_flnm);
   std::cout << " ...done." << std::endl;
 
-  // Copy a 'cleaned' version of each test report to the corresponding place in
-  // the baseline directory.
+  // Copy each test report to the corresponding place in the baseline directory.
   size_t test_idx = 0;
   size_t num_tests = _test_list.tests().size();
   for (auto& test : _test_list.tests())
@@ -531,8 +468,8 @@ void make_baseline(const Path& _test_root_dir, const Path& _bsln_root_dir,
     // Create the baseline directory (and all parents if necessary)
     fs::create_directories(bsln_test_dir);
 
-    // Copy and filter the report
-    filter_report(curr_test_dir / Test::REPORT_FILENAME,
+    // Copy the report to the baseline directory
+    fs::copy(curr_test_dir / Test::REPORT_FILENAME,
         bsln_test_dir / Test::REPORT_FILENAME);
   }
   std::cout << std::endl;
@@ -672,7 +609,7 @@ void copy(const fs::path& _root_from, const fs::path& _root_to,
   fs::create_directories(dir_to);
 
   // Copy the contents of dir_from to dir_to
-  Test::error_code err_code;
+  Base::error_code err_code;
   fs::copy(dir_from, dir_to, err_code);
   if (err_code)
   {
@@ -824,13 +761,15 @@ ExitStatus make_comparison(const char* const _dir_left,
 
     Base::OutputStreamAdaptT<TestStream> test_log;
 
-    // Get the path to the test directory for both suites
-    const auto test_dir_left = test_lists[0].root_dir() / test.path_;
-    const auto test_dir_right = test_lists[1].root_dir() / test.path_;
+    // Get the path to the report for both suites
+    const auto report_path_left =
+        test_lists[0].root_dir() / test.path_ / Test::REPORT_FILENAME;
+    const auto report_path_right =
+        test_lists[1].root_dir() / test.path_ / Test::REPORT_FILENAME;
 
     // Compare the checksums for the test between the two suites
-    const auto diff_stats = compare(test_dir_left, test_dir_right, test_log,
-        _chks_cmpr, _cot == COT_SHORT_DIFF);
+    const auto diff_stats = compare_reports(report_path_left, report_path_right,
+        test_log, _chks_cmpr, _cot == COT_SHORT_DIFF);
 
     if (!diff_stats.empty())
     {
@@ -965,8 +904,8 @@ ExitStatus make_comparison(const char* const _dir_left,
 
   if (create_diff_report && (found_diff || !is_subset[0] || !is_subset[1]))
   {
-    std::cout << "\nPlease check the compare log:\n"
-              << diff_path.generic_string() << std::endl;
+    std::cout << "\nPlease check the compare log: " << PathLink(diff_path)
+              << std::endl;
 
     // Return an error if a difference was found or if there is a test in the
     // right suite that does not exist in the left suite.
diff --git a/Test/TestReport.hh b/Test/TestReport.hh
index aa5ce7040369a6b7ebf55360b2025954081be10f..d1eae96e364a27c371828728df71260e11e26207 100644
--- a/Test/TestReport.hh
+++ b/Test/TestReport.hh
@@ -6,11 +6,11 @@
 #ifdef TEST_ON
 
 #include <Base/Test/TestChecksumCompare.hh>
+
 #include <functional>
 
 namespace Test
 {
-
 enum ExitStatus
 {
   ES_OK = 0,
@@ -50,7 +50,7 @@ enum CompareOutputType
 Produces a summary report (e.g. number of tests executed, which tests had
 errors, how many of each error were observed, etc.) of all test outputs below
 <_root_dir>/. Also produce a clean copy of the test directory (at
-<_root_dir>_base/) that contains only the (cleaned up) individual test reports.
+<_root_dir>_base/) that contains only the individual test reports.
 */
 ExitStatus make_summary_report(const char* const _root_dir);
 
diff --git a/Test/TestResultAnalysis.cc b/Test/TestResultAnalysis.cc
index 98975621d35ea26c44f646f6a13f20a29f8a9b21..b84d36571f7adee473adbc68136cc3ebe5170844 100644
--- a/Test/TestResultAnalysis.cc
+++ b/Test/TestResultAnalysis.cc
@@ -3,13 +3,13 @@
 #ifdef TEST_ON
 
 #include <Base/Security/Mandatory.hh>
+#include <Base/Paths/Filesystem.hh>
 
 #include "TestResultAnalysis.hh"
 #include "LongestCommonSubsequenceT.hh"
 #include "TestChecksum.hh"
 #include "TestChecksumCompletion.hh"
 #include "TestChecksumFile.hh"
-#include "TestFilesystem.hh"
 
 #include <algorithm>
 #include <cctype>
@@ -23,11 +23,12 @@ namespace Test
 namespace
 {
 
-namespace fs = Test::filesystem;
+namespace fs = Base::filesystem;
 
-// Remove code links from _str
+// Remove code links from _str. Code links are expected to be in the format for
+// Base::CodeLink used in IOutputStream.cc
 void erase_code_links(std::string& _str)
-{ // refers to the format for Base::CodeLink used in IOutputStream.cc
+{
   const char* const CODE_LINK_BEGIN = "@ [";
   const char* const CODE_LINK_END = "]";
 
@@ -42,6 +43,15 @@ void erase_code_links(std::string& _str)
   }
 }
 
+// Trim (remove whitespace) from the end of a std::string (in place)
+inline void rtrim(std::string& s)
+{
+  s.erase(std::find_if(s.rbegin(), s.rend(),
+              [](unsigned char ch) { return !std::isspace(ch); })
+              .base(),
+      s.end());
+}
+
 // Read and parse lines of a report to get the checksum Result, name and content.
 class Report
 {
@@ -54,15 +64,22 @@ public:
     explicit Entry(const std::string& _line)
       : line_(_line)
     {
+      // Ignore lines that represent (debug) callstack groups
       if (group())
         return;
-      // the line should be a checksum, extract the checksum data
+
+      // Remove whitespace and newline characters from the end of the line
+      rtrim(line_);
+
+      // The line should be a checksum. Extract the checksum data.
       std::istringstream line_strm(line_);
       line_strm >> std::noskipws >> rcrd_.rslt;
       line_strm >> std::skipws >> name_;
       if (!name_.empty() && name_.back() == ':')
         name_.pop_back();
       std::getline(line_strm, rcrd_.data);
+
+      // Erase any code links of the form "@ [ ..... ]" from the data
       erase_code_links(rcrd_.data);
     }
 
@@ -102,27 +119,26 @@ public:
 
 public:
   Report() : lvl_(Checksum::L_ALL) {}
-  explicit Report(const fs::path& _test_dir) : lvl_(Checksum::L_ALL)
-  {
-    read(_test_dir);
-  }
-
-  void read(const fs::path& _test_dir)
+  explicit Report(const fs::path& _rprt_path) : lvl_(Checksum::L_ALL)
   {
-    std::ifstream rprt((_test_dir / REPORT_FILENAME).string()); // report stream
+    std::ifstream rprt(_rprt_path.string()); // report stream
     for (std::string line; std::getline(rprt, line); )
     {
+      // Skip empty lines
       if (line.empty())
-        continue;// skip empty lines
+        continue;
 
+      // Find the checksum run level for this report by looking for the line
+      // "Report Level: <LEVEL>"
       const auto rprt_lvl_tag_idx = line.find(REPORT_LEVEL_TAG);
-      if (rprt_lvl_tag_idx != std::string::npos) // report level entry?
-      {// find what is the checksum run level for this report
+      if (rprt_lvl_tag_idx != std::string::npos)
+      {
+        // Found such a line. Parse it to extract <LEVEL>.
         for (int i = (int)Checksum::L_NONE; i <= Checksum::L_ALL; ++i)
         {
           if (line.find(Checksum::LEVEL_TEXT[i], rprt_lvl_tag_idx) !=
-            std::string::npos)
-          {// found it!
+              std::string::npos)
+          {
             lvl_ = (Checksum::Level)i;
             break;
           }
@@ -143,10 +159,10 @@ private:
   EntryVector entrs_;
 };
 
-}//namespace
+} // namespace
 
-DifferenceDistribution compare(const fs::path& _old_dir,
-    const fs::path& _new_dir, Base::IOutputStream& _log,
+DifferenceDistribution compare_reports(const fs::path& _rprt_path0,
+    const fs::path& _rprt_path1, Base::IOutputStream& _log,
     const Checksum::Compare& _chks_cmpr, const bool _shrt_frmt)
 {
   using Base::ENDL;
@@ -162,7 +178,7 @@ DifferenceDistribution compare(const fs::path& _old_dir,
   const char* const DIFF_TAG    = "!   ";
   const char* const RESULT_TAG  = "  ";
 
-  const Report rprts[2] = { Report(_old_dir), Report(_new_dir) };
+  const Report rprts[2] = {Report(_rprt_path0), Report(_rprt_path1)};
 
   LongestCommonSubsequenceT<Report> rprt_lcs(rprts[0], rprts[1]);
   rprt_lcs.trace();
@@ -171,10 +187,10 @@ DifferenceDistribution compare(const fs::path& _old_dir,
 
   DifferenceDistribution test_diff;
 
-  for (const auto& ij : rprt_mtch)// iterate the mis-/matched index pairs
+  for (const auto& ij : rprt_mtch) // iterate the mis-/matched index pairs
   {
     if (ij.matched()) // matched entries ?
-    { // compare the checksums, print if anything else
+    {                 // compare the checksums, print if anything else
       const auto& old_entr = rprts[0][ij.i];
       const auto& new_entr = rprts[1][ij.j];
 
@@ -193,24 +209,25 @@ DifferenceDistribution compare(const fs::path& _old_dir,
             _log << SAME_TAG << old_entr.line() << ENDL; // just print it
         }
         else
-        {// print difference
-          _log << NAME_TAG << old_entr.name() << ": " << diff.type_text() <<
-            ENDL;
-          _log << OLD_TAG << old_entr.record().rslt << RESULT_TAG <<
-            old_entr.record().data << ENDL;
-          _log << NEW_TAG << new_entr.record().rslt << RESULT_TAG <<
-            new_entr.record().data << ENDL;
+        { // print difference
+          _log << NAME_TAG << old_entr.name() << ": " << diff.type_text()
+               << ENDL;
+          _log << OLD_TAG << old_entr.record().rslt << RESULT_TAG
+               << old_entr.record().data << ENDL;
+          _log << NEW_TAG << new_entr.record().rslt << RESULT_TAG
+               << new_entr.record().data << ENDL;
           const char rslt_mark =
-            old_entr.record().rslt.type() == new_entr.record().rslt.type() ?
-            ' ' : '!';
+              old_entr.record().rslt.type() == new_entr.record().rslt.type()
+                  ? ' '
+                  : '!';
           _log << DIFF_TAG << rslt_mark << RESULT_TAG << diff.description()
-            << ENDL;
+               << ENDL;
         }
         ++test_diff[diff];
       }
     }
     else if (ij.i_valid() && rprts[0].level() <= rprts[1].level())
-    {// old entry removed and the old checksum level is equal or stricter
+    { // old entry removed and the old checksum level is equal or stricter
       const auto& old_entr = rprts[0][ij.i];
       _log << REMOVED_TAG << old_entr.line() << ENDL; // just print it
       if (!old_entr.group()) // if it is a group change, just ignore it
@@ -222,7 +239,7 @@ DifferenceDistribution compare(const fs::path& _old_dir,
       }
     }
     else if (ij.j_valid() && rprts[0].level() >= rprts[1].level())
-    {// new entry added and the new checksum level is stricter or equal
+    { // new entry added and the new checksum level is stricter or equal
       const auto& new_entr = rprts[1][ij.j];
       _log << ADDED_TAG << new_entr.line() << ENDL; // just print it
       if (!new_entr.group()) // if it is a group change, just ignore it
@@ -238,6 +255,6 @@ DifferenceDistribution compare(const fs::path& _old_dir,
   return test_diff;
 }
 
-}//namespace Test
+} // namespace Test
 
-#endif//TEST_ON
+#endif // TEST_ON
diff --git a/Test/TestResultAnalysis.hh b/Test/TestResultAnalysis.hh
index 0e9fb79c35006cfb469742b7c3d34a4f15833510..65ceec1935ad48f58e1045cc3c8f82c67cab8dc0 100644
--- a/Test/TestResultAnalysis.hh
+++ b/Test/TestResultAnalysis.hh
@@ -6,28 +6,35 @@
 #ifdef TEST_ON
 
 #include <Base/Test/TestChecksumCompare.hh>
-#include <Base/Test/TestFilesystem.hh>
+#include <Base/Paths/Filesystem.hh>
+
 #include <map>
+#include <string>
 
 namespace Test
 {
+namespace fs = Base::filesystem;
 
 typedef Checksum::Difference Difference;
-
 typedef std::map<Difference, size_t> DifferenceDistribution;
 
 /*!
-Compares two test directories to detect differences in test execution and/or
-test results. Returns a map with the difference statistics (counts for each type
-of difference). Equal checksums are reported if _short_frmt = false.
+Compares the checksums listed in two test reports. Returns a map with the
+difference statistics (counts for each type of difference). Equal checksums are
+ignored if _short_frmt = true.
+
+Arguments:
+
+* [in] _rprt_path0: Path to the left-suite report file.
+* [in] _rprt_path1: Path to the right-suite report file.
+* [out] _log: Description of the differences.
+* [in] _chks_cmpr: Checksum compare function.
+* [in] _short_frmt: Remove identical checksums (short format) [default = false].
+
 */
-DifferenceDistribution compare(
-    const Path& _test_dir0, /*< [in] Test directory. */
-    const Path& _test_dir1, /*< [in] Test directory. */
-    Base::IOutputStream& _log, /*< [out] Description of the checksums and differences. */
-    const Checksum::Compare& _chks_cmpr, /*<[in] Checksum compare function */
-    const bool _short_frmt = false       /*![in] Remove identical checksums  */
-);
+DifferenceDistribution compare_reports(const fs::path& _rprt_path0,
+    const fs::path& _rprt_path1, Base::IOutputStream& _log,
+    const Checksum::Compare& _chks_cmpr, const bool _short_frmt = false);
 
 } // namespace Test