Added TestCaseInfoHasher and tests.
This PR tries to fix #2304 "Test case name hashing should consider tags and class name too".
The goal is to "allow test cases with same name, but different tags, or same names and tags but different class name".

See the following code example that describes what is wanted:

```cpp
Catch::StringRef className1{std::string("C1")};
Catch::NameAndTags nameAndTag1{std::string("N1"), std::string("[T1]")};

Catch::StringRef className2{std::string("C2")};
Catch::NameAndTags nameAndTag2{std::string("N2"), std::string("[T2]")};
```

this set of test cases satisfies the wanted unique test case invariant as soon as:

```
if (N1 == N2 || C1 == C2) then [T1] != [T2]
AND
if (C1 == C2 || [T1] == [T2]) then N1 != N2
```

Before this PR, the code behaves as follows:
When `TestRunOrder::Randomized` is set, `getAllTestsSorted()` does the following:
1) Use `TestHasher` hash the class name of a `TestCaseInfo` (using the FNV-1a algorithm) and push them to a vector.
2) Sort the vector by the hashes, on equality, compare name, if equal compare class name, if also equal compare tags.

After this PR, the code behaves as follows:
When `TestRunOrder::Randomized` is set, `getAllTestsSorted()` does the following:
1) Use `TestCaseInfoHasher` to generate hashes that considers class name, name and tag and push them to a vector.
2) Sorting the vector by the hashes. When the unique test case invariant described above is satisfied, the hashes will be unique.

This is my first contribution to Catch2, hope i went in the right direction and would be happy about feedback, comments, etc.

{"label": "catchorg:devel", "ref": "devel", "sha": "1a8a793178d50b74b0f9a0adb3eec937b61039a9"}
**Describe the bug**
Recently I changed the "unique test case" criteria to allow test cases with same name, but different tags, or same names and tags but different class name (when a test case hangs off a type).

However, we also hash test cases when ordering them randomly (to get subset-stable ordering), and the hashing currently only considers test case name, which means that hash collisions are now much more realistic (in fact the collision is guaranteed if two test cases have same name, which is a desired property by some users).

_Note that this doesn't break the subset invariant, because we use the ordering of test case infos to break ties, which handles tags and class name properly_

**Expected behavior**
To fix this, the hasher should also consider tags and class name.

**Additional context**
This change also makes the hasher complex enough that it should be promoted to a header and receive direct unit tests.
Test case name hashing should consider tags and class name too
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 78d95956dd..16efa3fe18 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -157,6 +157,7 @@ set(INTERNAL_HEADERS
     ${SOURCES_DIR}/internal/catch_wildcard_pattern.hpp
     ${SOURCES_DIR}/internal/catch_windows_h_proxy.hpp
     ${SOURCES_DIR}/internal/catch_xmlwriter.hpp
+    ${SOURCES_DIR}/internal/catch_test_case_info_hasher.hpp
 )
 set(IMPL_SOURCES
     ${SOURCES_DIR}/catch_approx.cpp
@@ -213,6 +214,7 @@ set(IMPL_SOURCES
     ${SOURCES_DIR}/catch_version.cpp
     ${SOURCES_DIR}/internal/catch_wildcard_pattern.cpp
     ${SOURCES_DIR}/internal/catch_xmlwriter.cpp
+    ${SOURCES_DIR}/internal/catch_test_case_info_hasher.cpp
 )
 set(INTERNAL_FILES ${IMPL_SOURCES} ${INTERNAL_HEADERS})
 
diff --git a/src/catch2/catch_all.hpp b/src/catch2/catch_all.hpp
index 92cdc205da..656417f485 100644
--- a/src/catch2/catch_all.hpp
+++ b/src/catch2/catch_all.hpp
@@ -95,6 +95,7 @@
 #include <catch2/internal/catch_stringref.hpp>
 #include <catch2/internal/catch_tag_alias_registry.hpp>
 #include <catch2/internal/catch_template_test_registry.hpp>
+#include <catch2/internal/catch_test_case_info_hasher.hpp>
 #include <catch2/internal/catch_test_case_registry_impl.hpp>
 #include <catch2/internal/catch_test_case_tracker.hpp>
 #include <catch2/internal/catch_test_failure_exception.hpp>

diff --git a/src/catch2/internal/catch_test_case_info_hasher.cpp b/src/catch2/internal/catch_test_case_info_hasher.cpp
new file mode 100644
index 0000000000..75acc978bc
--- /dev/null
+++ b/src/catch2/internal/catch_test_case_info_hasher.cpp
@@ -0,0 +1,31 @@
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/internal/catch_test_case_info_hasher.hpp>
+
+namespace Catch {
+    TestCaseInfoHasher::TestCaseInfoHasher( hash_t seed ): m_seed( seed ) {}
+
+    uint32_t TestCaseInfoHasher::operator()( TestCaseInfo const& t ) const {
+        // FNV-1a hash algorithm that is designed for uniqueness:
+        const hash_t prime = 1099511628211u;
+        hash_t hash = 14695981039346656037u;
+        for ( const char c : t.name ) {
+            hash ^= c;
+            hash *= prime;
+        }
+        for ( const char c : t.className ) {
+            hash ^= c;
+            hash *= prime;
+        }
+        for ( const Tag& tag : t.tags ) {
+            for ( const char c : tag.original ) {
+                hash ^= c;
+                hash *= prime;
+            }
+        }
+        hash ^= m_seed;
+        hash *= prime;
+        const uint32_t low{ static_cast<uint32_t>( hash ) };
+        const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) };
+        return low * high;
+    }
+} // namespace Catch
diff --git a/src/catch2/internal/catch_test_case_info_hasher.hpp b/src/catch2/internal/catch_test_case_info_hasher.hpp
new file mode 100644
index 0000000000..954bdcdebd
--- /dev/null
+++ b/src/catch2/internal/catch_test_case_info_hasher.hpp
@@ -0,0 +1,22 @@
+#ifndef CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED
+#define CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED
+
+#include <cstdint>
+
+namespace Catch {
+
+    struct TestCaseInfo;
+
+    class TestCaseInfoHasher {
+    public:
+        using hash_t = std::uint64_t;
+        TestCaseInfoHasher( hash_t seed );
+        uint32_t operator()( TestCaseInfo const& t ) const;
+
+    private:
+        hash_t m_seed;
+    };
+
+} // namespace Catch
+
+#endif /* CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED */
diff --git a/src/catch2/internal/catch_test_case_registry_impl.cpp b/src/catch2/internal/catch_test_case_registry_impl.cpp
index 3c1ab54b2a..6c491a959f 100644
--- a/src/catch2/internal/catch_test_case_registry_impl.cpp
+++ b/src/catch2/internal/catch_test_case_registry_impl.cpp
@@ -16,38 +16,13 @@
 #include <catch2/catch_test_case_info.hpp>
 #include <catch2/catch_test_spec.hpp>
 #include <catch2/internal/catch_move_and_forward.hpp>
+#include <catch2/internal/catch_test_case_info_hasher.hpp>
 
 #include <algorithm>
 #include <set>
 
 namespace Catch {
 
-namespace {
-    struct TestHasher {
-        using hash_t = uint64_t;
-
-        explicit TestHasher( hash_t hashSuffix ):
-            m_hashSuffix( hashSuffix ) {}
-
-        uint64_t m_hashSuffix;
-
-        uint32_t operator()( TestCaseInfo const& t ) const {
-            // FNV-1a hash with multiplication fold.
-            const hash_t prime = 1099511628211u;
-            hash_t hash = 14695981039346656037u;
-            for (const char c : t.name) {
-                hash ^= c;
-                hash *= prime;
-            }
-            hash ^= m_hashSuffix;
-            hash *= prime;
-            const uint32_t low{ static_cast<uint32_t>(hash) };
-            const uint32_t high{ static_cast<uint32_t>(hash >> 32) };
-            return low * high;
-        }
-    };
-} // end anonymous namespace
-
     std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ) {
         switch (config.runOrder()) {
         case TestRunOrder::Declared:
@@ -66,9 +41,9 @@ namespace {
         }
         case TestRunOrder::Randomized: {
             seedRng(config);
-            using TestWithHash = std::pair<TestHasher::hash_t, TestCaseHandle>;
+            using TestWithHash = std::pair<TestCaseInfoHasher::hash_t, TestCaseHandle>;
 
-            TestHasher h{ config.rngSeed() };
+            TestCaseInfoHasher h{ config.rngSeed() };
             std::vector<TestWithHash> indexed_tests;
             indexed_tests.reserve(unsortedTestCases.size());
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ef1b13eae9..1c52012038 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -88,6 +88,7 @@ set(TEST_SOURCES
         ${SELF_TEST_DIR}/IntrospectiveTests/RandomNumberGeneration.tests.cpp
         ${SELF_TEST_DIR}/IntrospectiveTests/Reporters.tests.cpp
         ${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp
+        ${SELF_TEST_DIR}/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
         ${SELF_TEST_DIR}/IntrospectiveTests/TestSpecParser.tests.cpp
         ${SELF_TEST_DIR}/IntrospectiveTests/TextFlow.tests.cpp
         ${SELF_TEST_DIR}/IntrospectiveTests/Sharding.tests.cpp
diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt
index fadb274770..9df8be0df1 100644
--- a/tests/SelfTest/Baselines/automake.sw.approved.txt
+++ b/tests/SelfTest/Baselines/automake.sw.approved.txt
@@ -255,6 +255,8 @@ Message from section two
 :test-result: PASS Test case with one argument
 :test-result: PASS Test enum bit values
 :test-result: PASS Test with special, characters "in name
+:test-result: PASS TestCaseInfoHasher produces different hashes.
+:test-result: PASS TestCaseInfoHasher produces equal hashes.
 :test-result: PASS Testing checked-if
 :test-result: XFAIL Testing checked-if 2
 :test-result: XFAIL Testing checked-if 3
diff --git a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt
index f05de0ab8c..10db54cd22 100644
--- a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt
@@ -248,6 +248,8 @@
 :test-result: PASS Test case with one argument
 :test-result: PASS Test enum bit values
 :test-result: PASS Test with special, characters "in name
+:test-result: PASS TestCaseInfoHasher produces different hashes.
+:test-result: PASS TestCaseInfoHasher produces equal hashes.
 :test-result: PASS Testing checked-if
 :test-result: XFAIL Testing checked-if 2
 :test-result: XFAIL Testing checked-if 3
diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt
index 4688a2289f..8b404c58d5 100644
--- a/tests/SelfTest/Baselines/compact.sw.approved.txt
+++ b/tests/SelfTest/Baselines/compact.sw.approved.txt
@@ -1868,6 +1868,21 @@ Tag.tests.cpp:<line number>: passed: testCase.tags[0] == Tag( "tag1" ) for: {?}
 VariadicMacros.tests.cpp:<line number>: passed: with 1 message: 'no assertions'
 Tricky.tests.cpp:<line number>: passed: 0x<hex digits> == bit30and31 for: 3221225472 (0x<hex digits>) == 3221225472
 CmdLine.tests.cpp:<line number>: passed:
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>)
+!=
+3472848544 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 869111496 (0x<hex digits>)
+!=
+2870097333 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 1172537240 (0x<hex digits>)
+!=
+1403724645 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: h1(testCase1) != h2(testCase2) for: 1836497244 (0x<hex digits>)
+!=
+430288597 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>)
+==
+764519552 (0x<hex digits>)
 Misc.tests.cpp:<line number>: passed: true
 Misc.tests.cpp:<line number>: passed:
 Misc.tests.cpp:<line number>: failed - but was ok: false
diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt
index c571b0c9c8..3b3e1eef40 100644
--- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt
@@ -1861,6 +1861,21 @@ Tag.tests.cpp:<line number>: passed: testCase.tags[0] == Tag( "tag1" ) for: {?}
 VariadicMacros.tests.cpp:<line number>: passed: with 1 message: 'no assertions'
 Tricky.tests.cpp:<line number>: passed: 0x<hex digits> == bit30and31 for: 3221225472 (0x<hex digits>) == 3221225472
 CmdLine.tests.cpp:<line number>: passed:
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>)
+!=
+3472848544 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 869111496 (0x<hex digits>)
+!=
+2870097333 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 1172537240 (0x<hex digits>)
+!=
+1403724645 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: h1(testCase1) != h2(testCase2) for: 1836497244 (0x<hex digits>)
+!=
+430288597 (0x<hex digits>)
+TestCaseInfoHasher.tests.cpp:<line number>: passed: hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>)
+==
+764519552 (0x<hex digits>)
 Misc.tests.cpp:<line number>: passed: true
 Misc.tests.cpp:<line number>: passed:
 Misc.tests.cpp:<line number>: failed - but was ok: false
diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt
index ca0a77e599..61b1a11230 100644
--- a/tests/SelfTest/Baselines/console.std.approved.txt
+++ b/tests/SelfTest/Baselines/console.std.approved.txt
@@ -1395,6 +1395,6 @@ due to unexpected exception with message:
   Why would you throw a std::string?
 
 ===============================================================================
-test cases:  386 |  310 passed |  69 failed |  7 failed as expected
-assertions: 2219 | 2064 passed | 128 failed | 27 failed as expected
+test cases:  388 |  312 passed |  69 failed |  7 failed as expected
+assertions: 2224 | 2069 passed | 128 failed | 27 failed as expected
 
diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt
index 5655f50ab7..148a3b5388 100644
--- a/tests/SelfTest/Baselines/console.sw.approved.txt
+++ b/tests/SelfTest/Baselines/console.sw.approved.txt
@@ -13286,6 +13286,76 @@ CmdLine.tests.cpp:<line number>
 
 CmdLine.tests.cpp:<line number>: PASSED:
 
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  class names are equal, names are equal but tags are different.
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) )
+with expansion:
+  764519552 (0x<hex digits>)
+  !=
+  3472848544 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  class names are equal, tags are equal but names are different
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) )
+with expansion:
+  869111496 (0x<hex digits>)
+  !=
+  2870097333 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  names are equal, tags are equal but class names are different
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) )
+with expansion:
+  1172537240 (0x<hex digits>)
+  !=
+  1403724645 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  class names and names and tags are equal but hashers are seeded differently.
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( h1(testCase1) != h2(testCase2) )
+with expansion:
+  1836497244 (0x<hex digits>)
+  !=
+  430288597 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces equal hashes.
+  class names and names and tags are equal.
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2) )
+with expansion:
+  764519552 (0x<hex digits>)
+  ==
+  764519552 (0x<hex digits>)
+
 -------------------------------------------------------------------------------
 Testing checked-if
 -------------------------------------------------------------------------------
@@ -17871,6 +17941,6 @@ Misc.tests.cpp:<line number>
 Misc.tests.cpp:<line number>: PASSED:
 
 ===============================================================================
-test cases:  386 |  296 passed |  83 failed |  7 failed as expected
-assertions: 2234 | 2064 passed | 143 failed | 27 failed as expected
+test cases:  388 |  298 passed |  83 failed |  7 failed as expected
+assertions: 2239 | 2069 passed | 143 failed | 27 failed as expected
 
diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt
index 32fa068957..412303da9b 100644
--- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt
@@ -13279,6 +13279,76 @@ CmdLine.tests.cpp:<line number>
 
 CmdLine.tests.cpp:<line number>: PASSED:
 
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  class names are equal, names are equal but tags are different.
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) )
+with expansion:
+  764519552 (0x<hex digits>)
+  !=
+  3472848544 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  class names are equal, tags are equal but names are different
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) )
+with expansion:
+  869111496 (0x<hex digits>)
+  !=
+  2870097333 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  names are equal, tags are equal but class names are different
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) )
+with expansion:
+  1172537240 (0x<hex digits>)
+  !=
+  1403724645 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces different hashes.
+  class names and names and tags are equal but hashers are seeded differently.
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( h1(testCase1) != h2(testCase2) )
+with expansion:
+  1836497244 (0x<hex digits>)
+  !=
+  430288597 (0x<hex digits>)
+
+-------------------------------------------------------------------------------
+TestCaseInfoHasher produces equal hashes.
+  class names and names and tags are equal.
+-------------------------------------------------------------------------------
+TestCaseInfoHasher.tests.cpp:<line number>
+...............................................................................
+
+TestCaseInfoHasher.tests.cpp:<line number>: PASSED:
+  CHECK( hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2) )
+with expansion:
+  764519552 (0x<hex digits>)
+  ==
+  764519552 (0x<hex digits>)
+
 -------------------------------------------------------------------------------
 Testing checked-if
 -------------------------------------------------------------------------------
@@ -17863,6 +17933,6 @@ Misc.tests.cpp:<line number>
 Misc.tests.cpp:<line number>: PASSED:
 
 ===============================================================================
-test cases:  386 |  296 passed |  83 failed |  7 failed as expected
-assertions: 2234 | 2064 passed | 143 failed | 27 failed as expected
+test cases:  388 |  298 passed |  83 failed |  7 failed as expected
+assertions: 2239 | 2069 passed | 143 failed | 27 failed as expected
 
diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt
index 3a099b4f84..7c82e5b3a9 100644
--- a/tests/SelfTest/Baselines/junit.sw.approved.txt
+++ b/tests/SelfTest/Baselines/junit.sw.approved.txt
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <testsuitesloose text artifact
 >
-  <testsuite name="<exe-name>" errors="17" failures="126" tests="2234" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
+  <testsuite name="<exe-name>" errors="17" failures="126" tests="2239" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
     <properties>
       <property name="random-seed" value="1"/>
       <property name="filters" value="~[!nonportable]~[!benchmark]~[approvals] *"/>
@@ -1355,6 +1355,11 @@ Misc.tests.cpp:<line number>
     <testcase classname="<exe-name>.global" name="Test case with one argument" time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Test enum bit values" time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Test with special, characters &quot;in name" time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./class names are equal, names are equal but tags are different." time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./class names are equal, tags are equal but names are different" time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./names are equal, tags are equal but class names are different" time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./class names and names and tags are equal but hashers are seeded differently." time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces equal hashes./class names and names and tags are equal." time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Testing checked-if" time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Testing checked-if 2" time="{duration}" status="run">
       <skipped message="TEST_CASE tagged with !mayfail"/>
diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt
index 527f7ba0d6..d850f16a42 100644
--- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <testsuites>
-  <testsuite name="<exe-name>" errors="17" failures="126" tests="2234" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
+  <testsuite name="<exe-name>" errors="17" failures="126" tests="2239" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
     <properties>
       <property name="random-seed" value="1"/>
       <property name="filters" value="~[!nonportable]~[!benchmark]~[approvals] *"/>
@@ -1354,6 +1354,11 @@ Misc.tests.cpp:<line number>
     <testcase classname="<exe-name>.global" name="Test case with one argument" time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Test enum bit values" time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Test with special, characters &quot;in name" time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./class names are equal, names are equal but tags are different." time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./class names are equal, tags are equal but names are different" time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./names are equal, tags are equal but class names are different" time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces different hashes./class names and names and tags are equal but hashers are seeded differently." time="{duration}" status="run"/>
+    <testcase classname="<exe-name>.global" name="TestCaseInfoHasher produces equal hashes./class names and names and tags are equal." time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Testing checked-if" time="{duration}" status="run"/>
     <testcase classname="<exe-name>.global" name="Testing checked-if 2" time="{duration}" status="run">
       <skipped message="TEST_CASE tagged with !mayfail"/>
diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt
index 92db65df57..1a9d215df4 100644
--- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt
+++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt
@@ -273,6 +273,13 @@
     <testCase name="shortened hide tags are split apart" duration="{duration}"/>
     <testCase name="tags with dots in later positions are not parsed as hidden" duration="{duration}"/>
   </file>
+  <file path="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp">
+    <testCase name="TestCaseInfoHasher produces different hashes./class names are equal, names are equal but tags are different." duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces different hashes./class names are equal, tags are equal but names are different" duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces different hashes./names are equal, tags are equal but class names are different" duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces different hashes./class names and names and tags are equal but hashers are seeded differently." duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces equal hashes./class names and names and tags are equal." duration="{duration}"/>
+  </file>
   <file path="tests/<exe-name>/IntrospectiveTests/TestSpecParser.tests.cpp">
     <testCase name="Parsed tags are matched case insensitive" duration="{duration}"/>
     <testCase name="Parsing tags with non-alphabetical characters is pass-through" duration="{duration}"/>
diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
index c9074bf03b..cbefb06363 100644
--- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
@@ -272,6 +272,13 @@
     <testCase name="shortened hide tags are split apart" duration="{duration}"/>
     <testCase name="tags with dots in later positions are not parsed as hidden" duration="{duration}"/>
   </file>
+  <file path="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp">
+    <testCase name="TestCaseInfoHasher produces different hashes./class names are equal, names are equal but tags are different." duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces different hashes./class names are equal, tags are equal but names are different" duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces different hashes./names are equal, tags are equal but class names are different" duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces different hashes./class names and names and tags are equal but hashers are seeded differently." duration="{duration}"/>
+    <testCase name="TestCaseInfoHasher produces equal hashes./class names and names and tags are equal." duration="{duration}"/>
+  </file>
   <file path="tests/<exe-name>/IntrospectiveTests/TestSpecParser.tests.cpp">
     <testCase name="Parsed tags are matched case insensitive" duration="{duration}"/>
     <testCase name="Parsing tags with non-alphabetical characters is pass-through" duration="{duration}"/>
diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt
index 456e80eedf..67ceddfcd9 100644
--- a/tests/SelfTest/Baselines/tap.sw.approved.txt
+++ b/tests/SelfTest/Baselines/tap.sw.approved.txt
@@ -3297,6 +3297,16 @@ ok {test-number} - with 1 message: 'no assertions'
 ok {test-number} - 0x<hex digits> == bit30and31 for: 3221225472 (0x<hex digits>) == 3221225472
 # Test with special, characters "in name
 ok {test-number} -
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>) != 3472848544 (0x<hex digits>)
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 869111496 (0x<hex digits>) != 2870097333 (0x<hex digits>)
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 1172537240 (0x<hex digits>) != 1403724645 (0x<hex digits>)
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - h1(testCase1) != h2(testCase2) for: 1836497244 (0x<hex digits>) != 430288597 (0x<hex digits>)
+# TestCaseInfoHasher produces equal hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>) == 764519552 (0x<hex digits>)
 # Testing checked-if
 ok {test-number} - true
 # Testing checked-if
@@ -4470,5 +4480,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
 ok {test-number} -
 # xmlentitycheck
 ok {test-number} -
-1..2234
+1..2239
 
diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt
index 534005dffc..d369326e51 100644
--- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt
@@ -3290,6 +3290,16 @@ ok {test-number} - with 1 message: 'no assertions'
 ok {test-number} - 0x<hex digits> == bit30and31 for: 3221225472 (0x<hex digits>) == 3221225472
 # Test with special, characters "in name
 ok {test-number} -
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>) != 3472848544 (0x<hex digits>)
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 869111496 (0x<hex digits>) != 2870097333 (0x<hex digits>)
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2) for: 1172537240 (0x<hex digits>) != 1403724645 (0x<hex digits>)
+# TestCaseInfoHasher produces different hashes.
+ok {test-number} - h1(testCase1) != h2(testCase2) for: 1836497244 (0x<hex digits>) != 430288597 (0x<hex digits>)
+# TestCaseInfoHasher produces equal hashes.
+ok {test-number} - hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2) for: 764519552 (0x<hex digits>) == 764519552 (0x<hex digits>)
 # Testing checked-if
 ok {test-number} - true
 # Testing checked-if
@@ -4462,5 +4472,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
 ok {test-number} -
 # xmlentitycheck
 ok {test-number} -
-1..2234
+1..2239
 
diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt
index 43324e060f..d9b98f709b 100644
--- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt
+++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt
@@ -615,6 +615,10 @@ Misc.tests.cpp:<line number>|nexpression failed|n  CHECK( s1 == s2 )|nwith expan
 ##teamcity[testFinished name='Test enum bit values' duration="{duration}"]
 ##teamcity[testStarted name='Test with special, characters "in name']
 ##teamcity[testFinished name='Test with special, characters "in name' duration="{duration}"]
+##teamcity[testStarted name='TestCaseInfoHasher produces different hashes.']
+##teamcity[testFinished name='TestCaseInfoHasher produces different hashes.' duration="{duration}"]
+##teamcity[testStarted name='TestCaseInfoHasher produces equal hashes.']
+##teamcity[testFinished name='TestCaseInfoHasher produces equal hashes.' duration="{duration}"]
 ##teamcity[testStarted name='Testing checked-if']
 ##teamcity[testFinished name='Testing checked-if' duration="{duration}"]
 ##teamcity[testStarted name='Testing checked-if 2']
diff --git a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt
index 3bc028a404..ffdae89ceb 100644
--- a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt
@@ -615,6 +615,10 @@ Misc.tests.cpp:<line number>|nexpression failed|n  CHECK( s1 == s2 )|nwith expan
 ##teamcity[testFinished name='Test enum bit values' duration="{duration}"]
 ##teamcity[testStarted name='Test with special, characters "in name']
 ##teamcity[testFinished name='Test with special, characters "in name' duration="{duration}"]
+##teamcity[testStarted name='TestCaseInfoHasher produces different hashes.']
+##teamcity[testFinished name='TestCaseInfoHasher produces different hashes.' duration="{duration}"]
+##teamcity[testStarted name='TestCaseInfoHasher produces equal hashes.']
+##teamcity[testFinished name='TestCaseInfoHasher produces equal hashes.' duration="{duration}"]
 ##teamcity[testStarted name='Testing checked-if']
 ##teamcity[testFinished name='Testing checked-if' duration="{duration}"]
 ##teamcity[testStarted name='Testing checked-if 2']
diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt
index f9518c3d0c..4facaec2a9 100644
--- a/tests/SelfTest/Baselines/xml.sw.approved.txt
+++ b/tests/SelfTest/Baselines/xml.sw.approved.txt
@@ -15579,6 +15579,77 @@ Message from section two
   <TestCase name="Test with special, characters &quot;in name" tags="[cli][regression]" filename="tests/<exe-name>/IntrospectiveTests/CmdLine.tests.cpp" >
     <OverallResult success="true"/>
   </TestCase>
+  <TestCase name="TestCaseInfoHasher produces different hashes." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+    <Section name="class names are equal, names are equal but tags are different." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          764519552 (0x<hex digits>)
+!=
+3472848544 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <Section name="class names are equal, tags are equal but names are different" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          869111496 (0x<hex digits>)
+!=
+2870097333 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <Section name="names are equal, tags are equal but class names are different" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          1172537240 (0x<hex digits>)
+!=
+1403724645 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <Section name="class names and names and tags are equal but hashers are seeded differently." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          h1(testCase1) != h2(testCase2)
+        </Original>
+        <Expanded>
+          1836497244 (0x<hex digits>)
+!=
+430288597 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <OverallResult success="true"/>
+  </TestCase>
+  <TestCase name="TestCaseInfoHasher produces equal hashes." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+    <Section name="class names and names and tags are equal." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          764519552 (0x<hex digits>)
+==
+764519552 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <OverallResult success="true"/>
+  </TestCase>
   <TestCase name="Testing checked-if" tags="[checked-if]" filename="tests/<exe-name>/UsageTests/Misc.tests.cpp" >
     <Expression success="true" type="CHECKED_IF" filename="tests/<exe-name>/UsageTests/Misc.tests.cpp" >
       <Original>
@@ -20991,6 +21062,6 @@ loose text artifact
     </Section>
     <OverallResult success="true"/>
   </TestCase>
-  <OverallResults successes="2064" failures="143" expectedFailures="27"/>
-  <OverallResultsCases successes="296" failures="83" expectedFailures="7"/>
+  <OverallResults successes="2069" failures="143" expectedFailures="27"/>
+  <OverallResultsCases successes="298" failures="83" expectedFailures="7"/>
 </Catch2TestRun>
diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt
index 6617afe324..f6b172f590 100644
--- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt
+++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt
@@ -15579,6 +15579,77 @@ Message from section two
   <TestCase name="Test with special, characters &quot;in name" tags="[cli][regression]" filename="tests/<exe-name>/IntrospectiveTests/CmdLine.tests.cpp" >
     <OverallResult success="true"/>
   </TestCase>
+  <TestCase name="TestCaseInfoHasher produces different hashes." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+    <Section name="class names are equal, names are equal but tags are different." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          764519552 (0x<hex digits>)
+!=
+3472848544 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <Section name="class names are equal, tags are equal but names are different" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          869111496 (0x<hex digits>)
+!=
+2870097333 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <Section name="names are equal, tags are equal but class names are different" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          1172537240 (0x<hex digits>)
+!=
+1403724645 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <Section name="class names and names and tags are equal but hashers are seeded differently." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          h1(testCase1) != h2(testCase2)
+        </Original>
+        <Expanded>
+          1836497244 (0x<hex digits>)
+!=
+430288597 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <OverallResult success="true"/>
+  </TestCase>
+  <TestCase name="TestCaseInfoHasher produces equal hashes." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+    <Section name="class names and names and tags are equal." filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+      <Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/TestCaseInfoHasher.tests.cpp" >
+        <Original>
+          hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2)
+        </Original>
+        <Expanded>
+          764519552 (0x<hex digits>)
+==
+764519552 (0x<hex digits>)
+        </Expanded>
+      </Expression>
+      <OverallResults successes="1" failures="0" expectedFailures="0"/>
+    </Section>
+    <OverallResult success="true"/>
+  </TestCase>
   <TestCase name="Testing checked-if" tags="[checked-if]" filename="tests/<exe-name>/UsageTests/Misc.tests.cpp" >
     <Expression success="true" type="CHECKED_IF" filename="tests/<exe-name>/UsageTests/Misc.tests.cpp" >
       <Original>
@@ -20990,6 +21061,6 @@ There is no extra whitespace here
     </Section>
     <OverallResult success="true"/>
   </TestCase>
-  <OverallResults successes="2064" failures="143" expectedFailures="27"/>
-  <OverallResultsCases successes="296" failures="83" expectedFailures="7"/>
+  <OverallResults successes="2069" failures="143" expectedFailures="27"/>
+  <OverallResultsCases successes="298" failures="83" expectedFailures="7"/>
 </Catch2TestRun>
diff --git a/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp b/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
new file mode 100644
index 0000000000..450073601c
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
@@ -0,0 +1,51 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/internal/catch_test_case_info_hasher.hpp>
+
+static constexpr Catch::SourceLineInfo dummySourceLineInfo = CATCH_INTERNAL_LINEINFO;
+
+TEST_CASE( "TestCaseInfoHasher produces equal hashes." ) {
+    SECTION( "class names and names and tags are equal." ) {
+        Catch::TestCaseInfo testCase1("", {"name", "[.magic-tag1]"}, dummySourceLineInfo);
+        Catch::TestCaseInfo testCase2("", {"name", "[.magic-tag1]"}, dummySourceLineInfo);
+
+        Catch::TestCaseInfoHasher hasherWithCustomSeed(123456789u);
+        CHECK(hasherWithCustomSeed(testCase1) == hasherWithCustomSeed(testCase2));
+    }
+}
+
+TEST_CASE( "TestCaseInfoHasher produces different hashes." ) {
+    SECTION( "class names are equal, names are equal but tags are different." ) {
+        Catch::TestCaseInfo testCase1("", {"name", "[.magic-tag1]"}, dummySourceLineInfo);
+        Catch::TestCaseInfo testCase2("", {"name", "[.magic-tag2]"}, dummySourceLineInfo);
+
+        Catch::TestCaseInfoHasher hasherWithCustomSeed(123456789u);
+        CHECK(hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2));
+    }
+
+    SECTION( "class names are equal, tags are equal but names are different" ) {
+        Catch::TestCaseInfo testCase1("", {"name1", "[.magic-tag]"}, dummySourceLineInfo);
+        Catch::TestCaseInfo testCase2("", {"name2", "[.magic-tag]"}, dummySourceLineInfo);
+
+        Catch::TestCaseInfoHasher hasherWithCustomSeed(123456789u);
+        CHECK(hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2));
+    }
+
+    SECTION( "names are equal, tags are equal but class names are different" ) {
+        Catch::TestCaseInfo testCase1("class1", {"name", "[.magic-tag]"}, dummySourceLineInfo);
+        Catch::TestCaseInfo testCase2("class2", {"name", "[.magic-tag]"}, dummySourceLineInfo);
+
+        Catch::TestCaseInfoHasher hasherWithCustomSeed(123456789u);
+        CHECK(hasherWithCustomSeed(testCase1) != hasherWithCustomSeed(testCase2));
+    }
+
+    SECTION( "class names and names and tags are equal but hashers are seeded differently." ) {
+        Catch::TestCaseInfo testCase1("", {"name", "[.magic-tag1]"}, dummySourceLineInfo);
+        Catch::TestCaseInfo testCase2("", {"name", "[.magic-tag1]"}, dummySourceLineInfo);
+
+        Catch::TestCaseInfoHasher h1(14695981039346656037u);
+        Catch::TestCaseInfoHasher h2(14695981039346656038u);
+
+        CHECK(h1(testCase1) != h2(testCase2));
+    }
+}

{"testspecs::nomatchedtestsfail": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "randomtestordering": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "libidentitytest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "unmatchedoutputfilter": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::unmatchedtestspecisaccepted": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagsmatching": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "escapespecialcharactersintestnames": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::multiplewarningscanbespecified": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "noassertions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failedassertion": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "errorhandling::invalidtestspecexitsearly": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::capturingreportersdontpropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::noncapturingreporterspropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "runtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::overridefailurewithnomatchedtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::escapespecialcharacters": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::invalidtestnames-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "colours::colourmodecanbeexplicitlysettoansi": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "versioncheck": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failmacro": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "checkconvenienceheaders": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "approvaltests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::shouldfailisrespected": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "negativespecnohiddentests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "outputs::dashasoutlocationsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::quiet": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::simplespecs": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::dashaslocationinreporterspecsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "regressioncheck-1670": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagstest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "tagalias": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::throwingbenchmark": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"have_flag_-wmisleading-indentation": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wcatch-value": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wunreachable-code": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wextra": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wmissing-braces": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wpedantic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wparentheses": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wall": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wunused-parameter": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wstrict-aliasing": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wunused-function": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wdeprecated": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wshadow": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wmissing-noreturn": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wextra-semi": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wold-style-cast": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wvla": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wundef": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wmissing-declarations": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "have_flag_-wsuggest-override": {"run": "PASS", "test": "PASS", "fix": "PASS"}}
{"testspecs::nomatchedtestsfail": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "randomtestordering": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "libidentitytest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "unmatchedoutputfilter": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::unmatchedtestspecisaccepted": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagsmatching": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "escapespecialcharactersintestnames": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "warnings::multiplewarningscanbespecified": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "noassertions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failedassertion": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "errorhandling::invalidtestspecexitsearly": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::capturingreportersdontpropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "multireporter::noncapturingreporterspropagatestdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "runtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::overridefailurewithnomatchedtests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::escapespecialcharacters": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::invalidtestnames-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "colours::colourmodecanbeexplicitlysettoansi": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "versioncheck": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::failmacro": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "checkconvenienceheaders": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tags::output": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "approvaltests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::shouldfailisrespected": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "negativespecnohiddentests": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "outputs::dashasoutlocationsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-2": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testspecs::combiningmatchingandnonmatchingisok-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::tests::quiet": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "testsinfile::simplespecs": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "reporters::dashaslocationinreporterspecsendsoutputtostdout": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "regressioncheck-1670": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::exitcode": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filenameastagstest": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "tagalias": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "list::reporters::xmloutput": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "benchmarking::failurereporting::throwingbenchmark": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "filteredsection::generatorsdontcauseinfiniteloop-1": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 68, "failed_count": 7, "skipped_count": 0, "passed_tests": ["benchmarking::failurereporting::failmacro", "testspecs::nomatchedtestsfail", "list::reporters::output", "randomtestordering", "libidentitytest", "unmatchedoutputfilter", "checkconvenienceheaders", "have_flag_-wmisleading-indentation", "list::tags::output", "list::tests::xmloutput", "list::tags::exitcode", "list::reporters::xmloutput", "warnings::unmatchedtestspecisaccepted", "filenameastagsmatching", "have_flag_-wdeprecated", "have_flag_-wcatch-value", "list::tests::output", "approvaltests", "escapespecialcharactersintestnames", "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec", "benchmarking::failurereporting::shouldfailisrespected", "have_flag_-wunreachable-code", "warnings::multiplewarningscanbespecified", "noassertions", "negativespecnohiddentests", "have_flag_-wshadow", "outputs::dashasoutlocationsendsoutputtostdout", "benchmarking::failurereporting::failedassertion", "filteredsection::generatorsdontcauseinfiniteloop-2", "versioncheck", "testspecs::combiningmatchingandnonmatchingisok-1", "errorhandling::invalidtestspecexitsearly", "have_flag_-wextra", "list::tests::exitcode", "multireporter::capturingreportersdontpropagatestdout", "multireporter::noncapturingreporterspropagatestdout", "have_flag_-wmissing-noreturn", "list::tests::quiet", "have_flag_-wmissing-braces", "runtests", "have_flag_-wpedantic", "testsinfile::simplespecs", "reporters::dashaslocationinreporterspecsendsoutputtostdout", "have_flag_-wextra-semi", "regressioncheck-1670", "have_flag_-wparentheses", "have_flag_-wall", "testspecs::combiningmatchingandnonmatchingisok-2", "testspecs::overridefailurewithnomatchedtests", "have_flag_-wold-style-cast", "list::tags::xmloutput", "filteredsection-2", "list::reporters::exitcode", "filenameastagstest", "testsinfile::escapespecialcharacters", "testsinfile::invalidtestnames-1", "benchmarking::failurereporting::throwingbenchmark", "have_flag_-wvla", "colours::colourmodecanbeexplicitlysettoansi", "have_flag_-wundef", "have_flag_-wunused-parameter", "filteredsection-1", "have_flag_-wmissing-declarations", "tagalias", "have_flag_-wunused-function", "have_flag_-wstrict-aliasing", "have_flag_-wsuggest-override", "filteredsection::generatorsdontcauseinfiniteloop-1"], "failed_tests": ["have_flag_-wglobal-constructors", "have_flag_-wreturn-std-move", "have_flag_-wcall-to-pure-virtual-from-ctor-dtor", "have_flag_-wweak-vtables", "have_flag_-wdeprecated-register", "have_flag_-wexit-time-destructors", "have_flag_-wabsolute-value"], "skipped_tests": []}
{"passed_count": 20, "failed_count": 7, "skipped_count": 0, "passed_tests": ["have_flag_-wmisleading-indentation", "have_flag_-wdeprecated", "have_flag_-wcatch-value", "have_flag_-wunreachable-code", "have_flag_-wshadow", "have_flag_-wextra", "have_flag_-wmissing-noreturn", "have_flag_-wmissing-braces", "have_flag_-wpedantic", "have_flag_-wextra-semi", "have_flag_-wparentheses", "have_flag_-wall", "have_flag_-wold-style-cast", "have_flag_-wvla", "have_flag_-wundef", "have_flag_-wunused-parameter", "have_flag_-wmissing-declarations", "have_flag_-wunused-function", "have_flag_-wstrict-aliasing", "have_flag_-wsuggest-override"], "failed_tests": ["have_flag_-wglobal-constructors", "have_flag_-wreturn-std-move", "have_flag_-wcall-to-pure-virtual-from-ctor-dtor", "have_flag_-wweak-vtables", "have_flag_-wdeprecated-register", "have_flag_-wexit-time-destructors", "have_flag_-wabsolute-value"], "skipped_tests": []}
{"passed_count": 68, "failed_count": 7, "skipped_count": 0, "passed_tests": ["benchmarking::failurereporting::failmacro", "testspecs::nomatchedtestsfail", "list::reporters::output", "randomtestordering", "libidentitytest", "unmatchedoutputfilter", "checkconvenienceheaders", "have_flag_-wmisleading-indentation", "list::tags::output", "list::tests::xmloutput", "list::tags::exitcode", "list::reporters::xmloutput", "warnings::unmatchedtestspecisaccepted", "filenameastagsmatching", "have_flag_-wdeprecated", "have_flag_-wcatch-value", "list::tests::output", "approvaltests", "escapespecialcharactersintestnames", "testspecs::warnunmatchedtestspecfailswithunmatchedtestspec", "benchmarking::failurereporting::shouldfailisrespected", "have_flag_-wunreachable-code", "warnings::multiplewarningscanbespecified", "noassertions", "negativespecnohiddentests", "have_flag_-wshadow", "outputs::dashasoutlocationsendsoutputtostdout", "benchmarking::failurereporting::failedassertion", "filteredsection::generatorsdontcauseinfiniteloop-2", "versioncheck", "testspecs::combiningmatchingandnonmatchingisok-1", "errorhandling::invalidtestspecexitsearly", "have_flag_-wextra", "list::tests::exitcode", "multireporter::capturingreportersdontpropagatestdout", "multireporter::noncapturingreporterspropagatestdout", "have_flag_-wmissing-noreturn", "list::tests::quiet", "have_flag_-wmissing-braces", "runtests", "have_flag_-wpedantic", "testsinfile::simplespecs", "reporters::dashaslocationinreporterspecsendsoutputtostdout", "have_flag_-wextra-semi", "regressioncheck-1670", "have_flag_-wparentheses", "have_flag_-wall", "testspecs::combiningmatchingandnonmatchingisok-2", "testspecs::overridefailurewithnomatchedtests", "have_flag_-wold-style-cast", "list::tags::xmloutput", "filteredsection-2", "list::reporters::exitcode", "filenameastagstest", "testsinfile::escapespecialcharacters", "testsinfile::invalidtestnames-1", "benchmarking::failurereporting::throwingbenchmark", "have_flag_-wvla", "colours::colourmodecanbeexplicitlysettoansi", "have_flag_-wundef", "have_flag_-wunused-parameter", "filteredsection-1", "have_flag_-wmissing-declarations", "tagalias", "have_flag_-wunused-function", "have_flag_-wstrict-aliasing", "have_flag_-wsuggest-override", "filteredsection::generatorsdontcauseinfiniteloop-1"], "failed_tests": ["have_flag_-wglobal-constructors", "have_flag_-wreturn-std-move", "have_flag_-wcall-to-pure-virtual-from-ctor-dtor", "have_flag_-wweak-vtables", "have_flag_-wdeprecated-register", "have_flag_-wexit-time-destructors", "have_flag_-wabsolute-value"], "skipped_tests": []}