-
Notifications
You must be signed in to change notification settings - Fork 1k
Add CovMark and a test that uses it. #5012
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| // Copyright 2025 Stellar Development Foundation and contributors. Licensed | ||
| // under the Apache License, Version 2.0. See the COPYING file at the root | ||
| // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| #ifdef BUILD_TESTS | ||
|
|
||
| #include "test/CovMark.h" | ||
| #include "crypto/ShortHash.h" | ||
|
|
||
| #include <fmt/format.h> | ||
| #include <stdexcept> | ||
|
|
||
| namespace stellar | ||
| { | ||
|
|
||
| CovMarks gCovMarks; | ||
|
|
||
| CovMarks::CovMarks() | ||
| { | ||
| reset(); | ||
| } | ||
|
|
||
| void | ||
| CovMarks::hit(CovMark mark) | ||
| { | ||
| mCovMarks[static_cast<std::uint64_t>(mark)].fetch_add( | ||
| 1, std::memory_order_relaxed); | ||
| } | ||
|
|
||
| std::uint64_t | ||
| CovMarks::get(CovMark mark) const | ||
| { | ||
| return mCovMarks[static_cast<std::uint64_t>(mark)].load( | ||
| std::memory_order_relaxed); | ||
| } | ||
|
|
||
| void | ||
| CovMarks::reset() | ||
| { | ||
| for (auto& mark : mCovMarks) | ||
| { | ||
| mark.store(0, std::memory_order_relaxed); | ||
| } | ||
| } | ||
|
|
||
| std::uint64_t | ||
| CovMarks::hash() const | ||
| { | ||
| std::array<std::uint8_t, sizeof(std::uint64_t) * CovMark::COVMARK_COUNT> | ||
| markBytes; | ||
| for (size_t i = 0; i < CovMark::COVMARK_COUNT; ++i) | ||
| { | ||
| auto mark = mCovMarks[i].load(std::memory_order_relaxed); | ||
| for (size_t j = 0; j < sizeof(std::uint64_t); ++j) | ||
| { | ||
| markBytes[i * sizeof(std::uint64_t) + j] = (mark >> (j * 8)) & 0xFF; | ||
| } | ||
| } | ||
| return shortHash::computeHash( | ||
| ByteSlice(markBytes.data(), markBytes.size())); | ||
| } | ||
|
|
||
| CovMarkGuard::CovMarkGuard(CovMark mark, char const* file, int line, | ||
| char const* name) | ||
| : mMark(mark) | ||
| , mValueOnEntry(gCovMarks.get(mark)) | ||
| , mFile(file) | ||
| , mLine(line) | ||
| , mName(name) | ||
| { | ||
| } | ||
|
|
||
| CovMarkGuard::~CovMarkGuard() noexcept(false) | ||
| { | ||
| auto valueOnExit = gCovMarks.get(mMark); | ||
| // We only throw if we are not already unwinding due to another exception. | ||
| if (!(valueOnExit > mValueOnEntry) && std::uncaught_exceptions() == 0) | ||
| { | ||
| throw std::runtime_error(fmt::format( | ||
| "expected mark '{}' not hit in scope {}:{}", mFile, mLine, mName)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #endif // BUILD_TESTS |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,80 @@ | ||||||
| #pragma once | ||||||
|
|
||||||
| // Copyright 2025 Stellar Development Foundation and contributors. Licensed | ||||||
| // under the Apache License, Version 2.0. See the COPYING file at the root | ||||||
| // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|
|
||||||
| #include <array> | ||||||
| #include <atomic> | ||||||
| #include <cstdint> | ||||||
|
|
||||||
| // This is a small utility class used in tests to mark coverage points | ||||||
| // that are otherwise hard to verify via external observation. | ||||||
| // | ||||||
| // It is similar to the mechanism described here: | ||||||
| // https://ferrous-systems.com/blog/coverage-marks/ | ||||||
| // | ||||||
| // But it has a few differences: | ||||||
| // | ||||||
| // 1. It is only enabled in test builds | ||||||
| // | ||||||
| // 2. It keeps all atomics in a single global array rather than scattered | ||||||
| // among a bunch of different variables. | ||||||
| // | ||||||
| // 3. This allows us to do reset all counters to zero at the beginning of | ||||||
| // each test run, and also do complex checks if we want to, e.g. verify | ||||||
| // that certain marks were hit in a certain order, or a certain number of | ||||||
| // times, or different marks sum up to some value, etc. | ||||||
| // | ||||||
| // 4. It also allows us to hash all the marks together to get a trajectory | ||||||
| // summmary for a run, which we can feed to a fuzzer to explore different | ||||||
|
||||||
| // summmary for a run, which we can feed to a fuzzer to explore different | |
| // summary for a run, which we can feed to a fuzzer to explore different |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||
| // Copyright 2025 Stellar Development Foundation and contributors. Licensed | ||||||
| // under the Apache License, Version 2.0. See the COPYING file at the root | ||||||
| // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|
|
||||||
| #include "test/CovMark.h" | ||||||
| #include "test/test.h" | ||||||
| #include "test/Catch2.h" | ||||||
| #include "test/TxTests.h" | ||||||
| #include "transactions/test/SorobanTxTestUtils.h" | ||||||
| #include "util/Logging.h" | ||||||
|
|
||||||
| using namespace stellar; | ||||||
| using namespace stellar::txtest; | ||||||
|
|
||||||
| TEST_CASE("defer eviction due to concurrent TTL modify", "[tx][soroban][archival]") | ||||||
| { | ||||||
| auto cfg = getTestConfig(); | ||||||
| SorobanTest test(cfg, true, [](SorobanNetworkConfig& cfg) { | ||||||
| cfg.mStateArchivalSettings.minPersistentTTL = | ||||||
| MinimumSorobanNetworkConfig::MINIMUM_PERSISTENT_ENTRY_LIFETIME; | ||||||
| cfg.mStateArchivalSettings.startingEvictionScanLevel = 1; | ||||||
| }); | ||||||
| ContractStorageTestClient client(test); | ||||||
|
|
||||||
| auto expirationLedger = | ||||||
| test.getLCLSeq() + | ||||||
| MinimumSorobanNetworkConfig::MINIMUM_PERSISTENT_ENTRY_LIFETIME; | ||||||
|
|
||||||
| client.put("key", ContractDataDurability::PERSISTENT, 123); | ||||||
|
|
||||||
| CLOG_INFO(Ledger, "Expiration ledger is {}", expirationLedger); | ||||||
|
|
||||||
| while (test.getLCLSeq() < expirationLedger - 1) | ||||||
| { | ||||||
| CLOG_INFO(Ledger, "Closing ledger as LCL {} < Expiration ledger - 1 = {}", test.getLCLSeq(), expirationLedger - 1); | ||||||
| closeLedger(test.getApp()); | ||||||
| } | ||||||
|
|
||||||
| auto checklive = [&](ExpirationStatus expected) { | ||||||
| auto status = | ||||||
| client.getEntryExpirationStatus("key", ContractDataDurability::PERSISTENT); | ||||||
| CLOG_INFO(Ledger, "Checking status at ledger {}, expected {}, found {}", test.getLCLSeq(), (int)expected, | ||||||
| (int)status); | ||||||
| REQUIRE(status == expected); | ||||||
| }; | ||||||
|
|
||||||
| checklive(ExpirationStatus::LIVE); | ||||||
| closeLedger(test.getApp()); | ||||||
|
||||||
| closeLedger(test.getApp()); | |
| closeLedger(test.getApp()); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |||||
| #include "transactions/InvokeHostFunctionOpFrame.h" | ||||||
| #include "transactions/TransactionUtils.h" | ||||||
| #include "util/XDRCereal.h" | ||||||
| #include "xdr/Stellar-transaction.h" | ||||||
| #include "xdrpp/printer.h" | ||||||
|
|
||||||
| namespace stellar | ||||||
|
|
@@ -2168,6 +2169,23 @@ ContractStorageTestClient::extend(std::string const& key, | |||||
| return *invocation.getResultCode(); | ||||||
| } | ||||||
|
|
||||||
| InvokeHostFunctionResultCode | ||||||
| ContractStorageTestClient::restore(std::string const& key, | ||||||
| ContractDataDurability durability, | ||||||
| std::optional<SorobanInvocationSpec> spec) | ||||||
| { | ||||||
| if (!spec) | ||||||
| { | ||||||
| spec = writeKeySpec(key, durability); | ||||||
| } | ||||||
|
|
||||||
| auto tx = mContract.getTest().createRestoreTx( | ||||||
| spec->getResources(), spec->getInclusionFee(), spec->getResourceFee()); | ||||||
|
|
||||||
| auto result = mContract.getTest().invokeTx(tx); | ||||||
| return result.result.results()[0].tr().invokeHostFunctionResult().code(); | ||||||
|
||||||
| return result.result.results()[0].tr().invokeHostFunctionResult().code(); | |
| return result.result.results()[0].tr().restoreFootprintResult().code(); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |||||
| #include "test/test.h" | ||||||
| #include "transactions/TransactionFrameBase.h" | ||||||
| #include "transactions/TransactionUtils.h" | ||||||
| #include "xdr/Stellar-transaction.h" | ||||||
|
|
||||||
| namespace stellar | ||||||
| { | ||||||
|
|
@@ -472,6 +473,10 @@ class ContractStorageTestClient | |||||
| uint32_t threshold, uint32_t extendTo, | ||||||
| std::optional<SorobanInvocationSpec> spec = std::nullopt); | ||||||
|
|
||||||
| InvokeHostFunctionResultCode | ||||||
|
||||||
| InvokeHostFunctionResultCode | |
| RestoreFootprintResultCode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate word 'the' - should be 'after the last scanned eligible entry'.