test/common/seed.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // | ||
| 2 | // SPDX-FileCopyrightText: Copyright 2025-2026 Arm Limited and/or its affiliates <open-source-office@arm.com> | ||
| 3 | // | ||
| 4 | // SPDX-License-Identifier: Apache-2.0 | ||
| 5 | // | ||
| 6 | |||
| 7 | #pragma once | ||
| 8 | |||
| 9 | #include <cstdint> | ||
| 10 | #include <functional> | ||
| 11 | #include <random> | ||
| 12 | #include <sstream> | ||
| 13 | #include <string> | ||
| 14 | #include <string_view> | ||
| 15 | #include <unordered_map> | ||
| 16 | |||
| 17 | /// KleidiAI tests share a global seed controlled by the test harness. By default | ||
| 18 | /// GoogleTest passes `--gtest_random_seed`, which `global_test_seed()` exposes so | ||
| 19 | /// every test retrieves the same base value. Every test can then derive its own | ||
| 20 | /// deterministic seeds from that base value using `seed_stream(key)`, where `key` | ||
| 21 | /// is a string that identifies the test or test cache. | ||
| 22 | /// | ||
| 23 | /// The default granularity is a single GoogleTest *test case*: `current_test_key()` | ||
| 24 | /// returns a string key representing the current test case, and `seed_stream(current_test_key())` | ||
| 25 | /// returns a stateful generator that produces deterministic seeds for that test case. | ||
| 26 | /// | ||
| 27 | /// `seed_stream()` maintains a map from string keys to `SeedFeed` instances. Each entry | ||
| 28 | /// is seeded by combining the global test seed with a stable hash of the key, ensuring | ||
| 29 | /// that different test cases get different seed streams. | ||
| 30 | /// | ||
| 31 | /// Typical usage for a new test: | ||
| 32 | /// auto& feed = seed_stream(kai::test::current_test_key()); // per-test deterministic stream | ||
| 33 | /// const auto lhs_seed = feed(); // get a seed for lhs | ||
| 34 | /// const auto rhs_seed = feed(); // get a seed for rhs | ||
| 35 | /// | ||
| 36 | /// Tests that cache shared across multiple test cases can create their own key string (e.g. by | ||
| 37 | /// combining test parameters) and pass it to `seed_stream(key)` to ensure the cache stays | ||
| 38 | /// stable across runs. | ||
| 39 | |||
| 40 | namespace kai::test { | ||
| 41 | |||
| 42 | /// Get the global test seed supplied by the harness. | ||
| 43 | /// | ||
| 44 | /// @return Seed value (0 if unavailable). | ||
| 45 | std::uint32_t global_test_seed(); | ||
| 46 | |||
| 47 | /// Get the current test key. | ||
| 48 | /// | ||
| 49 | /// @return String representing the current test key. | ||
| 50 | std::string current_test_key(); | ||
| 51 | |||
| 52 | /// Stateful generator for deterministic test seeds. | ||
| 53 | class SeedFeed { | ||
| 54 | public: | ||
| 55 | /// Default constructor that initializes the seed internally. | ||
| 56 | SeedFeed() : m_gen(global_test_seed()) { | ||
| 57 | } | ||
| 58 | |||
| 59 | // Constructor with explicit seed. | ||
| 60 | 113619 | explicit SeedFeed(const std::uint32_t seed) : m_gen(seed) { | |
| 61 | 113619 | } | |
| 62 | |||
| 63 | /// Generate a random seed. | ||
| 64 | /// | ||
| 65 | /// @return Pseudo-random seed. | ||
| 66 | 116240 | std::uint32_t operator()() { | |
| 67 | 116240 | return m_gen(); | |
| 68 | } | ||
| 69 | |||
| 70 | private: | ||
| 71 | std::mt19937 m_gen; | ||
| 72 | }; | ||
| 73 | |||
| 74 | /// FNV-1a 32-bit hash function for string literals. | ||
| 75 | /// | ||
| 76 | /// @param[in] input The input string to hash. | ||
| 77 | /// | ||
| 78 | /// @return The computed hash value. | ||
| 79 | 75195 | constexpr inline std::uint32_t fnv1a_32(std::string_view input) noexcept { | |
| 80 | 75195 | constexpr std::uint32_t kOffsetBasis = 0x811C9DC5u; | |
| 81 | 75195 | constexpr std::uint32_t kPrime = 0x01000193u; | |
| 82 | |||
| 83 | 75195 | std::uint32_t hash = kOffsetBasis; | |
| 84 |
2/2✓ Branch 0 taken 75195 times.
✓ Branch 1 taken 15185019 times.
|
15260214 | for (unsigned char byte : input) { |
| 85 | 15185019 | hash ^= static_cast<std::uint32_t>(byte); | |
| 86 | 15185019 | hash *= kPrime; | |
| 87 | 15185019 | } | |
| 88 | 150390 | return hash; | |
| 89 | 75195 | } | |
| 90 | |||
| 91 | /// Get the seed stream for a specified key. | ||
| 92 | /// | ||
| 93 | /// @param[in] key The key to get the seed stream for. | ||
| 94 | /// | ||
| 95 | /// @return The seed stream associated with the key. | ||
| 96 | 75195 | inline SeedFeed& seed_stream(std::string_view key) { | |
| 97 | // Create static map of seed feeds. | ||
| 98 | // NB: Tests are single-threaded, so no need for synchronization. | ||
| 99 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 75192 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
75195 | static std::unordered_map<std::string, SeedFeed> feeds; |
| 100 | |||
| 101 | // Combine global test seed with key-specific hash to create a unique seed. | ||
| 102 | // NB: Cannot use std::hash because it is not stable across different standard libraries or versions. | ||
| 103 | 75195 | const auto seed = global_test_seed() ^ fnv1a_32(key); | |
| 104 | |||
| 105 | // Insert or retrieve the seed feed for the given key. | ||
| 106 |
2/4✓ Branch 0 taken 75195 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25079 times.
✗ Branch 3 not taken.
|
75195 | auto [it, _] = feeds.try_emplace(std::string(key), seed); |
| 107 | 150390 | return it->second; | |
| 108 | 75195 | } | |
| 109 | |||
| 110 | } // namespace kai::test | ||
| 111 |