Skip to content

test: add comprehensive unit tests for LLMQ subsystem #6691

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

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ BITCOIN_TESTS =\
test/lcg.h \
test/limitedmap_tests.cpp \
test/llmq_dkg_tests.cpp \
test/llmq_chainlock_tests.cpp \
test/llmq_commitment_tests.cpp \
test/llmq_hash_tests.cpp \
test/llmq_params_tests.cpp \
test/llmq_snapshot_tests.cpp \
test/llmq_test_utils.h \
test/llmq_utils_tests.cpp \
Comment on lines +132 to +138
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify inclusion of header file in BITCOIN_TESTS.

The addition of the LLMQ test files looks correct, but including test/llmq_test_utils.h in the BITCOIN_TESTS list is unusual. Typically, this list contains only .cpp files, with header files included indirectly through #include statements.

Please verify if the header file should be in this list or if it should be handled differently:


🏁 Script executed:

#!/bin/bash
# Description: Check how other test utility headers are handled in the build system
# Expected: Most .h files should not be in BITCOIN_TESTS

echo "Checking for other .h files in BITCOIN_TESTS:"
grep -n "\.h" src/Makefile.test.include | grep -A5 -B5 "BITCOIN_TESTS"

echo "Checking how test utility headers are typically included:"
find src/test -name "*.h" | head -10

Length of output: 576


Remove llmq_test_utils.h from BITCOIN_TESTS

The BITCOIN_TESTS list should only include .cpp source files—no other header files are currently listed. Please remove the following line from src/Makefile.test.include (around lines 132–138):

test/llmq_test_utils.h \

The header will be picked up via the normal #include mechanism from the .cpp tests.

🤖 Prompt for AI Agents
In src/Makefile.test.include around lines 132 to 138, the file
test/llmq_test_utils.h is incorrectly included in the BITCOIN_TESTS list, which
should only contain .cpp files. Remove the line referencing
test/llmq_test_utils.h from this list so that the header is included only
through #include directives in the .cpp test files.

test/logging_tests.cpp \
test/dbwrapper_tests.cpp \
test/validation_tests.cpp \
Expand Down
167 changes: 167 additions & 0 deletions src/test/llmq_chainlock_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) 2024 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <test/llmq_test_utils.h>
#include <test/util/setup_common.h>

#include <llmq/clsig.h>
#include <streams.h>

#include <boost/test/unit_test.hpp>

using namespace llmq;
using namespace llmq::testutils;

BOOST_FIXTURE_TEST_SUITE(llmq_chainlock_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(chainlock_construction_test)
{
// Test default constructor
CChainLockSig clsig1;
BOOST_CHECK(clsig1.IsNull());
BOOST_CHECK_EQUAL(clsig1.getHeight(), -1);
BOOST_CHECK(clsig1.getBlockHash().IsNull());
BOOST_CHECK(!clsig1.getSig().IsValid());

// Test parameterized constructor
int32_t height = 12345;
uint256 blockHash = GetTestBlockHash(1);
CBLSSignature sig = CreateRandomBLSSignature();

CChainLockSig clsig2(height, blockHash, sig);
BOOST_CHECK(!clsig2.IsNull());
BOOST_CHECK_EQUAL(clsig2.getHeight(), height);
BOOST_CHECK(clsig2.getBlockHash() == blockHash);
BOOST_CHECK(clsig2.getSig() == sig);
}

BOOST_AUTO_TEST_CASE(chainlock_null_test)
{
CChainLockSig clsig;

// Default constructed should be null
BOOST_CHECK(clsig.IsNull());

// With height set but null hash, should not be null
clsig = CChainLockSig(100, uint256(), CBLSSignature());
BOOST_CHECK(!clsig.IsNull());

// With valid data should not be null
clsig = CreateChainLock(100, GetTestBlockHash(1));
BOOST_CHECK(!clsig.IsNull());
}

BOOST_AUTO_TEST_CASE(chainlock_serialization_test)
{
// Test with valid chainlock
CChainLockSig clsig = CreateChainLock(67890, GetTestBlockHash(42));

// Test serialization preserves all fields
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << clsig;

CChainLockSig deserialized;
ss >> deserialized;

BOOST_CHECK_EQUAL(clsig.getHeight(), deserialized.getHeight());
BOOST_CHECK(clsig.getBlockHash() == deserialized.getBlockHash());
BOOST_CHECK(clsig.getSig() == deserialized.getSig());
BOOST_CHECK_EQUAL(clsig.IsNull(), deserialized.IsNull());
}

BOOST_AUTO_TEST_CASE(chainlock_tostring_test)
{
// Test null chainlock
CChainLockSig nullClsig;
std::string nullStr = nullClsig.ToString();
BOOST_CHECK(!nullStr.empty());

// Test valid chainlock
int32_t height = 123456;
uint256 blockHash = GetTestBlockHash(789);
CChainLockSig clsig = CreateChainLock(height, blockHash);

std::string str = clsig.ToString();
BOOST_CHECK(!str.empty());

// ToString should contain height and hash info
BOOST_CHECK(str.find(std::to_string(height)) != std::string::npos);
BOOST_CHECK(str.find(blockHash.ToString().substr(0, 10)) != std::string::npos);
}

BOOST_AUTO_TEST_CASE(chainlock_edge_cases_test)
{
// Test with edge case heights
CChainLockSig clsig1 = CreateChainLock(0, GetTestBlockHash(1));
BOOST_CHECK_EQUAL(clsig1.getHeight(), 0);
BOOST_CHECK(!clsig1.IsNull());

CChainLockSig clsig2 = CreateChainLock(std::numeric_limits<int32_t>::max(), GetTestBlockHash(2));
BOOST_CHECK_EQUAL(clsig2.getHeight(), std::numeric_limits<int32_t>::max());

// Test serialization with extreme values
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << clsig1;
CChainLockSig clsig1_deserialized;
ss1 >> clsig1_deserialized;
BOOST_CHECK_EQUAL(clsig1.getHeight(), clsig1_deserialized.getHeight());

CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << clsig2;
CChainLockSig clsig2_deserialized;
ss2 >> clsig2_deserialized;
BOOST_CHECK_EQUAL(clsig2.getHeight(), clsig2_deserialized.getHeight());
}

BOOST_AUTO_TEST_CASE(chainlock_comparison_test)
{
// Create identical chainlocks
int32_t height = 5000;
uint256 blockHash = GetTestBlockHash(10);
CBLSSignature sig = CreateRandomBLSSignature();

CChainLockSig clsig1(height, blockHash, sig);
CChainLockSig clsig2(height, blockHash, sig);

// Verify getters return same values
BOOST_CHECK_EQUAL(clsig1.getHeight(), clsig2.getHeight());
BOOST_CHECK(clsig1.getBlockHash() == clsig2.getBlockHash());
BOOST_CHECK(clsig1.getSig() == clsig2.getSig());

// Different chainlocks
CChainLockSig clsig3(height + 1, blockHash, sig);
BOOST_CHECK(clsig1.getHeight() != clsig3.getHeight());

CChainLockSig clsig4(height, GetTestBlockHash(11), sig);
BOOST_CHECK(clsig1.getBlockHash() != clsig4.getBlockHash());
}

BOOST_AUTO_TEST_CASE(chainlock_malformed_data_test)
{
// Test deserialization of truncated data
CChainLockSig clsig = CreateChainLock(1000, GetTestBlockHash(5));

CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << clsig;

// Truncate the stream
std::string data = ss.str();
for (size_t truncateAt = 1; truncateAt < data.size(); truncateAt += 10) {
CDataStream truncated(std::vector<unsigned char>(data.begin(), data.begin() + truncateAt), SER_NETWORK,
PROTOCOL_VERSION);

CChainLockSig deserialized;
try {
truncated >> deserialized;
// If no exception, verify it's either complete or default
if (truncateAt < sizeof(int32_t)) {
BOOST_CHECK(deserialized.IsNull());
}
} catch (const std::exception&) {
// Expected for most truncation points
}
}
}

BOOST_AUTO_TEST_SUITE_END()
Loading
Loading