diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e91c2a7d15 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: Build and Test + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] + +jobs: + linux: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + cmake \ + ninja-build \ + pkg-config \ + libevent-dev \ + libsqlite3-dev \ + zlib1g-dev \ + libboost-all-dev \ + qt6-base-dev \ + qt6-declarative-dev \ + libqt6quickcontrols2-dev + + - name: Configure (CMake) + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_APP_TESTS=ON + + - name: Build + run: cmake --build build --parallel + + - name: Run tests + run: ctest --test-dir build --output-on-failure + diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd9bb326f..8c774ee13b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,4 +126,11 @@ target_link_libraries(bitcoin-core-app bitcoin_node bitcoinqml bitcoinqt -) \ No newline at end of file +) + +option(BUILD_APP_TESTS "Build unit tests for the app" ON) +if(BUILD_APP_TESTS) + include(CTest) + enable_testing() + add_subdirectory(test) +endif() diff --git a/README.md b/README.md index cef9a9e427..fb28026e3c 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,14 @@ appropriate document for your platform: - [build-unix.md](https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md) -In addition the following dependencies are required for the GUI: +In addition the following dependencies are required for the GUI and tests: #### Debian-based systems: ``` sudo apt install \ qt6-base-dev \ + qt6-base-dev-tools \ qt6-tools-dev \ qt6-l10n-tools \ qt6-tools-dev-tools \ @@ -91,6 +92,8 @@ sudo apt install qt6-wayland ``` brew install qt@6 qrencode +# Help CMake find Qt6 on macOS (adjust if needed) +export CMAKE_PREFIX_PATH="$(brew --prefix qt@6)/lib/cmake" ``` ### Build @@ -116,5 +119,20 @@ Binaries are exported to the `build/` directory: build/bin/bitcoin-core-app ``` +### Run Tests + +- Enable tests at configure time and run them via CTest. + +``` +cmake -B build -DBUILD_APP_TESTS=ON +cmake --build build -j$(nproc) +ctest --test-dir build --output-on-failure +``` + +- If CMake reports "Target Qt6::Test not found", ensure Qt6 Test is available + and discoverable by CMake: + + - Debian/Ubuntu: `qt6-base-dev` provides the Qt Test module (already listed above). + - macOS: set `CMAKE_PREFIX_PATH` as shown so CMake can locate Qt 6 modules. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000000..bba81a2859 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.22) + +# Tests for the QML/Qt6 app + +find_package(Qt 6.2 MODULE REQUIRED COMPONENTS Test Core) + +add_executable(bitcoinqml_tests + test_bitcoinamount.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../qml/bitcoinamount.cpp +) + +target_include_directories(bitcoinqml_tests + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../bitcoin/src +) + +target_link_libraries(bitcoinqml_tests + PRIVATE + Qt6::Core + Qt6::Test +) + +add_test(NAME bitcoinqml_tests COMMAND bitcoinqml_tests) + diff --git a/test/test_bitcoinamount.cpp b/test/test_bitcoinamount.cpp new file mode 100644 index 0000000000..a75ef7e7a3 --- /dev/null +++ b/test/test_bitcoinamount.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +class BitcoinAmountTests : public QObject { + Q_OBJECT + +private Q_SLOTS: + void satsToBtcString_basic(); + void satsToBtcString_negative(); + void btcToSats_roundtrip(); + void sanitize_clampsAndFilters(); + void display_flow_btc(); + void display_flow_sat(); + void flipUnit_changesLabelAndDisplaySignal(); +}; + +void BitcoinAmountTests::satsToBtcString_basic() +{ + QCOMPARE(BitcoinAmount::satsToBtcString(0), QString("0.00000000")); + QCOMPARE(BitcoinAmount::satsToBtcString(1), QString("0.00000001")); + QCOMPARE(BitcoinAmount::satsToBtcString(COIN), QString("1.00000000")); + QCOMPARE(BitcoinAmount::satsToBtcString(21 * COIN), QString("21.00000000")); + QCOMPARE(BitcoinAmount::satsToBtcString(100000123), QString("1.00000123")); +} + +void BitcoinAmountTests::satsToBtcString_negative() +{ + QCOMPARE(BitcoinAmount::satsToBtcString(-1), QString("-0.00000001")); +} + +void BitcoinAmountTests::btcToSats_roundtrip() +{ + BitcoinAmount amt; + amt.setUnit(BitcoinAmount::Unit::BTC); + amt.fromDisplay("1.23456789"); + QCOMPARE(amt.toDisplay(), QString("1.23456789")); +} + +void BitcoinAmountTests::sanitize_clampsAndFilters() +{ + BitcoinAmount amt; + amt.setUnit(BitcoinAmount::Unit::BTC); + amt.fromDisplay("abc1.2.3xyz"); + QCOMPARE(amt.toDisplay(), QString("1.20000000")); +} + +void BitcoinAmountTests::display_flow_btc() +{ + BitcoinAmount amt; + amt.setUnit(BitcoinAmount::Unit::BTC); + amt.setSatoshi(2 * COIN); + QCOMPARE(amt.toDisplay(), QString("2.00000000")); +} + +void BitcoinAmountTests::display_flow_sat() +{ + BitcoinAmount amt; + amt.setUnit(BitcoinAmount::Unit::SAT); + amt.setSatoshi(123); + QCOMPARE(amt.toDisplay(), QString("123")); +} + +void BitcoinAmountTests::flipUnit_changesLabelAndDisplaySignal() +{ + BitcoinAmount amt; + amt.setSatoshi(100); + QSignalSpy spy(&amt, &BitcoinAmount::displayChanged); + amt.flipUnit(); + QVERIFY(spy.count() >= 1); +} + +QTEST_MAIN(BitcoinAmountTests) +#include "test_bitcoinamount.moc"