test: Add and use ElapseTime helper #32430

pull maflcko wants to merge 6 commits into bitcoin:master from maflcko:2505-elapse-time changing 38 files +174 −106
  1. maflcko commented at 10:39 pm on May 6, 2025: member

    Currently mocktime is written to a global, which may leak between sub-tests (albeit some tests try to reset the mocktime on a best-effort basis). Also, when advancing it, one has to keep a counter variable around.

    Fix both issues by a new ElapseTime helper, which resets the mocktime once it goes out of scope. Also, it has a method to advance the mocktime by a delta.

  2. DrahtBot commented at 10:39 pm on May 6, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32430.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK l0rinc
    Stale ACK w0xlt

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #33106 (policy: lower the default blockmintxfee, incrementalrelayfee, minrelaytxfee by glozow)
    • #32941 (p2p: TxOrphanage revamp cleanups by glozow)
    • #32414 (validation: periodically flush dbcache during reindex-chainstate by andrewtoth)
    • #29700 (kernel, refactor: return error status on all fatal errors by ryanofsky)
    • #28676 (Cluster mempool implementation by sdaftuar)
    • #17783 (common: Disallow calling IsArgSet() on ALLOW_LIST options by ryanofsky)
    • #17580 (refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags by ryanofsky)
    • #16545 (refactor: Implement missing error checking for ArgsManager flags by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. DrahtBot added the label Tests on May 6, 2025
  4. maflcko force-pushed on May 6, 2025
  5. DrahtBot added the label CI failed on May 6, 2025
  6. DrahtBot commented at 10:54 pm on May 6, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/runs/41759458930 LLM reason (✨ experimental): (empty)

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  7. maflcko force-pushed on May 6, 2025
  8. maflcko force-pushed on May 7, 2025
  9. maflcko force-pushed on May 7, 2025
  10. maflcko force-pushed on May 7, 2025
  11. maflcko force-pushed on May 7, 2025
  12. l0rinc approved
  13. l0rinc commented at 9:44 am on May 7, 2025: contributor
    Concept ACK - was thinking of something similar when reviewing 41479ed (#32414)
  14. maflcko commented at 9:47 am on May 7, 2025: member
    Sorry for the force-pushes, but this uncovered pre-existing issues in the fuzz tests that were not using mocktime during init. So I went ahead and fixed those as well in a separate commit. Should be good now, hopefully :sweat_smile:
  15. DrahtBot removed the label CI failed on May 7, 2025
  16. in src/test/util/setup_common.h:291 in fae29c5dd7 outdated
    285@@ -286,6 +286,12 @@ inline std::ostream& operator<<(std::ostream& os, const std::optional<T>& v)
    286     return v ? os << *v
    287              : os << "std::nullopt";
    288 }
    289+
    290+template <typename Clock, typename Duration>
    291+inline std::ostream& operator<<(std::ostream& os, const std::chrono::time_point<Clock, Duration>& tp)
    


    w0xlt commented at 6:24 pm on May 7, 2025:
    Where is this being used ?

    l0rinc commented at 6:32 pm on May 7, 2025:
    If you remove it and try to compile, you’ll notice that Boost requires these for the test to be able to print out the values when there’s a mismatch, see: https://github.com/bitcoin/bitcoin/blob/fae29c5dd71b263fb3bda8d554c9e6a4908175db/src/test/testnet4_miner_tests.cpp#L49

    l0rinc commented at 4:21 am on July 27, 2025:
    Maybe we could merge this commit with its first usage - I’m also not a fan of adding dead code even if it’s only for a single commit. And since it’s meant for debugging, maybe we could format it in a more humanly readable way

    maflcko commented at 10:30 am on July 31, 2025:
    It is split up, because you agree that the commit is already large in #32430 (review). Will leave as-is for now.
  17. DrahtBot requested review from l0rinc on May 7, 2025
  18. DrahtBot added the label Needs rebase on May 16, 2025
  19. maflcko force-pushed on Jul 8, 2025
  20. DrahtBot added the label CI failed on Jul 8, 2025
  21. DrahtBot commented at 8:27 pm on July 8, 2025: contributor

    🚧 At least one of the CI tasks failed. Task previous releases, depends DEBUG: https://github.com/bitcoin/bitcoin/runs/45588216821 LLM reason (✨ experimental): The CI failure is caused by a compilation error in src/util/time.cpp due to incorrect usage of std::chrono::duration, which prevents building.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  22. DrahtBot removed the label Needs rebase on Jul 8, 2025
  23. maflcko commented at 3:04 pm on July 9, 2025: member

    Sorry for the force-pushes, but this uncovered pre-existing issues in the fuzz tests that were not using mocktime during init. So I went ahead and fixed those as well in a separate commit. Should be good now, hopefully 😅

    Went ahead and cherry-picked out the fuzz-fixes into #32927. Should be easy to (re-)review that one.

  24. DrahtBot added the label Needs rebase on Jul 11, 2025
  25. maflcko force-pushed on Jul 11, 2025
  26. maflcko force-pushed on Jul 11, 2025
  27. maflcko commented at 1:44 pm on July 11, 2025: member
    rebased to drop merged commits and included some minimal fixups
  28. DrahtBot removed the label Needs rebase on Jul 11, 2025
  29. DrahtBot removed the label CI failed on Jul 11, 2025
  30. maflcko requested review from w0xlt on Jul 18, 2025
  31. DrahtBot added the label Needs rebase on Jul 18, 2025
  32. maflcko force-pushed on Jul 24, 2025
  33. maflcko force-pushed on Jul 24, 2025
  34. DrahtBot removed the label Needs rebase on Jul 24, 2025
  35. DrahtBot added the label Needs rebase on Jul 25, 2025
  36. maflcko force-pushed on Jul 25, 2025
  37. DrahtBot removed the label Needs rebase on Jul 25, 2025
  38. in src/test/fuzz/headerssync.cpp:61 in fa38a4e462 outdated
    59     CBlockHeader genesis_header{Params().GenesisBlock()};
    60     CBlockIndex start_index(genesis_header);
    61 
    62-    if (mock_time < start_index.GetMedianTimePast()) return;
    63-    SetMockTime(mock_time);
    64+    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast()));
    


    l0rinc commented at 4:28 am on July 27, 2025:

    👍 for using the built-in min instead.

    Could we add it to the commit message that this isn’t just a simple refactor (and not just about the return value), but as @hodlinator stated in a different PR where he did the same:

    That way we can always proceed instead of just exiting the fuzz target, wasting cycles. Also reduces code complexity.


    maflcko commented at 10:30 am on July 31, 2025:
    thx, split up into a new commit
  39. in src/test/util/time.h:1 in fa4f014007 outdated


    l0rinc commented at 4:31 am on July 27, 2025:

    typo in commit message:

    no state is leaked between test cases


    l0rinc commented at 5:46 am on July 27, 2025:
    faaaaac58a447c4f8da67ff55a35f7e85cda369f is quite big… And some of the changes are non-trivial, if you can, please consider doing it in multiple commits

    maflcko commented at 10:24 am on July 31, 2025:
    It is just 50 lines, so it should be manageable. Anyone is free to review it file-by-file, if they want to split it up, but I am not sure about splitting it up too much?

    maflcko commented at 10:30 am on July 31, 2025:
    thx, fixed
  40. in src/test/util/time.h:30 in fa4f014007 outdated
    30+    ElapseSteady& operator=(const ElapseSteady&) = delete;
    31+
    32+    /** Change mocktime by the given duration delta */
    33     void operator()(std::chrono::milliseconds d)
    34     {
    35+        Assert(d >= 0s); // Steady time can only move forward.
    


    l0rinc commented at 4:32 am on July 27, 2025:

    nit: the code isn’t in exact alignment with the comment - and since we are calling it with 0, maybe something like:

    0        Assert(d >= 0ms); // Steady time cannot move backward.
    

    (nit2: I know it’s the same, but to clarify that we don’t just mean that time-jumps cannot be 0.5 seconds, we might want to use 0ms instead)


    maflcko commented at 10:29 am on July 31, 2025:
    thx, reworded comment a bit
  41. in src/test/util/time.h:41 in fa4f014007 outdated
    41+class ElapseTime
    42+{
    43+    NodeSeconds m_t{std::chrono::seconds::max()};
    44+
    45+public:
    46+    /** Initialize with initial time */
    


    l0rinc commented at 4:33 am on July 27, 2025:
    nit: I understand that it’s meant to be symmetric with ElapseSteady, but I don’t find the comment helpful as it is.

    maflcko commented at 10:29 am on July 31, 2025:
    thx, reworded comment
  42. in src/test/util/time.h:58 in fa4f014007 outdated
    58+    /** Set mocktime */
    59+    void set(NodeSeconds t) { SetMockTime(m_t = t); }
    60+    void set(std::chrono::seconds t) { set(NodeSeconds{t}); }
    61+
    62+    /** Change mocktime by the given duration delta */
    63+    void operator()(std::chrono::seconds d) { SetMockTime(m_t += d); }
    


    l0rinc commented at 4:44 am on July 27, 2025:

    maybe we could reuse the previous setters like we do in other methods:

    0    void operator()(std::chrono::seconds d) { set(m_t + d); }
    

    or maybe even:

    0    void operator()(std::chrono::seconds d)
    1    {
    2        Assert(d >= 0s); // Elapse time cannot move backward.
    3        set(m_t + d);
    4    }
    

    nit: I don’t find the operator to be intuitive here, maybe we could call it Advance instead?


    maflcko commented at 10:27 am on July 31, 2025:
    thx, done

    maflcko commented at 10:29 am on July 31, 2025:

    nit: I don’t find the operator to be intuitive here, maybe we could call it Advance instead?

    I think elapse_time(4h) or elapse_steady(4h) is self-explanatory. So leaving as-is for now.

  43. in src/test/util/time.h:48 in fa4f014007 outdated
    48+    ElapseTime(std::chrono::seconds init_time) { set(init_time); }
    49+    /** Initialize with current time, using the next tick to avoid going back by rounding to seconds */
    50+    ElapseTime() { set(++Now<NodeSeconds>().time_since_epoch()); }
    51+
    52+    /** Unset mocktime */
    53+    ~ElapseTime() { SetMockTime(0s); }
    


    l0rinc commented at 4:47 am on July 27, 2025:

    In the constructors we’re using the setters, can we use it here, too, to minimize the places where we’re adjusting the local/global state?

    0    ~ElapseTime() { set(0s); }
    

    maflcko commented at 10:27 am on July 31, 2025:
    thx, done
  44. in src/test/util/time.h:20 in fa4f014007 outdated
    18-    {
    19-        (*this)(0s); // init
    20-    }
    21+
    22+public:
    23+    /** Initialize with INITIAL_MOCK_TIME. */
    


    l0rinc commented at 4:51 am on July 27, 2025:
    It doesn’t just initialize the state of the object, but modifies the global state as well - can we make that more obvious to make sure the users understand that?

    maflcko commented at 10:27 am on July 31, 2025:
    thx, mentioned global in a new struct-level-doc
  45. in src/test/util/time.h:58 in fa4f014007 outdated
    54+
    55+    ElapseTime(const ElapseTime&) = delete;
    56+    ElapseTime& operator=(const ElapseTime&) = delete;
    57+
    58+    /** Set mocktime */
    59+    void set(NodeSeconds t) { SetMockTime(m_t = t); }
    


    l0rinc commented at 4:53 am on July 27, 2025:

    I think the part that a setter modifies both the local and global state should be made more obvious, it’s not intuitive:

    0    void Set(NodeSeconds t) noexcept
    1    {
    2        m_t = t;
    3        SetMockTime(m_t); // Set the global mock time.
    4    }
    

    nit: capitalized + noexcept


    maflcko commented at 10:27 am on July 31, 2025:
    thx, mentioned global in a new struct-level-doc
  46. in src/test/util/time.h:22 in fa4f014007 outdated
    22+public:
    23+    /** Initialize with INITIAL_MOCK_TIME. */
    24+    ElapseSteady() { (*this)(0s); }
    25+
    26+    /** Unset mocktime */
    27+    ~ElapseSteady() { MockableSteadyClock::SetMockTime(0s); }
    


    l0rinc commented at 4:56 am on July 27, 2025:
    We might as well use the more descriptive MockableSteadyClock::ClearMockTime(); here

    maflcko commented at 10:27 am on July 31, 2025:
    thx, done
  47. in src/test/util/time.h:19 in fa4f014007 outdated
    19-        (*this)(0s); // init
    20-    }
    21+
    22+public:
    23+    /** Initialize with INITIAL_MOCK_TIME. */
    24+    ElapseSteady() { (*this)(0s); }
    


    l0rinc commented at 5:00 am on July 27, 2025:

    What would happen if we ran multiple such tests on different threads? Could we guard against that via something like:

    0    ElapseSteady()
    1    {
    2        assert(GetMockTime() == 0s); // Ensure that the steady clock is not mocked yet.
    3        MockableSteadyClock::SetMockTime(0s);
    4    }
    

    nit: (*this)(0s); is not very intuitive in my opinion


    maflcko commented at 10:26 am on July 31, 2025:
    I think it should be allowed to have it set previously, as there may be valid use-cases. Using it in multiple threads is not a thing right now and I can’t imagine a use-case in the future. However, if there is one, it seems fine to support it and allow it as well.
  48. in src/test/fuzz/addrman.cpp:120 in fa15b34744 outdated
    116@@ -116,7 +117,7 @@ FUZZ_TARGET(addrman, .init = initialize_addrman)
    117 {
    118     SeedRandomStateForTest(SeedRand::ZEROS);
    119     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
    120-    SetMockTime(ConsumeTime(fuzzed_data_provider));
    121+    ElapseTime elapse_time{ConsumeTime(fuzzed_data_provider)};
    


    l0rinc commented at 5:04 am on July 27, 2025:
    are we planning on using the new declared object? Could we rather skip the name, or call it a time_mock or something to obviate why it’s unused?

    maflcko commented at 10:26 am on July 31, 2025:

    are we planning on using the new declared object?

    Yes, it can trivially be used, if there is need to. Possibly it isn’t used in this specific instance any further, but it seems beneficial to allow it and also beneficial to be consistent in the naming and usage.

  49. in src/test/banman_tests.cpp:20 in faaaaac58a outdated
    17 BOOST_FIXTURE_TEST_SUITE(banman_tests, BasicTestingSetup)
    18 
    19 BOOST_AUTO_TEST_CASE(file)
    20 {
    21-    SetMockTime(777s);
    22+    ElapseTime elapse_time{777s};
    


    l0rinc commented at 5:12 am on July 27, 2025:

    I find it non-intuitive that we have constructor and operator doing different things, i.e. why is the absolute setter:

    0    ElapseTime elapse_time{777s};
    

    different from the relative one:

    0    ElapseTime elapse_time{};
    1    elapse_time(777s);
    

    Looking at the code I understand it of course, but maybe something like:

    0    ElapseTime elapse_time{};
    1    elapse_time.Advance(777s);
    

    would be easier to read


    maflcko commented at 10:25 am on July 31, 2025:

    Looking at the code I understand it of course, but maybe something like:

    0    ElapseTime elapse_time{};
    1    elapse_time.Advance(777s);
    

    Not sure. This is doing something else, as explained above by yourself? So this replacement is not correct to apply here.

  50. in src/test/chainstate_write_tests.cpp:36 in faaaaac58a outdated
    32     m_node.validation_signals->SyncWithValidationInterfaceQueue();
    33     BOOST_CHECK(!sub->m_did_flush);
    34 
    35     // The periodic flush interval is between 50 and 70 minutes (inclusive)
    36-    SetMockTime(GetTime<std::chrono::minutes>() + 49min);
    37+    elapse_time(49min);
    


    l0rinc commented at 5:13 am on July 27, 2025:
    👍 nice, cc @andrewtoth
  51. in src/test/denialofservice_tests.cpp:171 in faaaaac58a outdated
    167@@ -167,7 +168,7 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
    168         BOOST_CHECK(node->fDisconnect == false);
    169     }
    170 
    171-    SetMockTime(time_later);
    172+    elapse_time(delta);
    


    l0rinc commented at 5:14 am on July 27, 2025:

    any reason for defining the delta so far away from first usage?

    0    const auto delta{3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s};
    1    elapse_time(delta);
    

    maflcko commented at 10:25 am on July 31, 2025:
    Just to make review easier, because you agree that the commit is already large in #32430 (review). Will leave as-is for now.
  52. in src/test/mempool_tests.cpp:577 in faaaaac58a outdated
    579-    SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
    580+    elapse_time(HALFLIFE / 4);
    581     BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/8.0));
    582     // ... with a 1/4 halflife when mempool is < 1/4 its target size
    583 
    584-    SetMockTime(42 + 7*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
    


    l0rinc commented at 5:20 am on July 27, 2025:
    wow, this was a mess before :|
  53. in src/test/fuzz/p2p_handshake.cpp:48 in faaaaac58a outdated
    44@@ -44,7 +45,7 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize)
    45 
    46     auto& connman = static_cast<ConnmanTestMsg&>(*g_setup->m_node.connman);
    47     auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
    48-    SetMockTime(1610000000); // any time to successfully reset ibd
    49+    ElapseTime elapse_time{1610000000s}; // any time to successfully reset ibd
    


    l0rinc commented at 5:29 am on July 27, 2025:
    do we still need to keep these constants or is there maybe a cleaner way now?

    maflcko commented at 10:25 am on July 31, 2025:
    seems unrelated? But i am happy to review a pull request changing it
  54. in src/test/util/time.h:42 in faaaaac58a outdated
    42+{
    43+    NodeSeconds m_t{std::chrono::seconds::max()};
    44+
    45+public:
    46+    /** Initialize with initial time */
    47+    ElapseTime(NodeSeconds init_time) { set(init_time); }
    


    l0rinc commented at 5:34 am on July 27, 2025:
    nit: we might as well make the constructors explicit

    maflcko commented at 10:25 am on July 31, 2025:
    thx, done
  55. l0rinc commented at 5:46 am on July 27, 2025: contributor
    Thanks for fixing these, left quite a few comments, hope you’ll find them useful
  56. fuzz: Use min option in ConsumeTime
    This is less code and also required for the next commit.
    faaccbf670
  57. fuzz: Return chrono duration from ConsumeTime()
    This is a bit more type-safe than a raw i64.
    fa892b8eb9
  58. test: Add ElapseTime context
    This makes it easier to use mock-time in tests. Also, it resets the
    global mocktime, so that no state is leaked between test cases.
    
    Also, it resets the global steady mock-time for the same reason.
    44440d6c03
  59. fuzz: Use ElapseTime faec029c61
  60. test: Allow time_point in boost checks
    This is required in the next commit.
    fa9d494764
  61. test: Use ElapseTime in more tests fa80e750e7
  62. maflcko force-pushed on Jul 31, 2025
  63. maflcko commented at 10:41 am on July 31, 2025: member
    Force pushed with some minor doc-changes and small refactoring in src/test/util/time.h
  64. l0rinc approved
  65. l0rinc commented at 5:08 pm on July 31, 2025: contributor
    code review ACK fa80e750e702e2f0429fc6b83e9af2b8ed9b7b3c
  66. DrahtBot added the label Needs rebase on Aug 4, 2025
  67. DrahtBot commented at 5:58 pm on August 4, 2025: contributor
    🐙 This pull request conflicts with the target branch and needs rebase.

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-08-21 00:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me