1- # # Github workflow to run bitcoin tests
1+ # # Github workflow to run bitcoin tests in multiple github matrices
22
33name : Tests::Bitcoin
44
@@ -13,100 +13,160 @@ env:
1313 TEST_TIMEOUT : 30
1414
1515concurrency :
16- group : bitcoin-tests-${{ github.head_ref || github.ref || github.run_id}}
16+ group : bitcoin-tests-${{ github.head_ref || github.ref || github.run_id }}
1717 # # Only cancel in progress if this is for a PR
1818 cancel-in-progress : ${{ github.event_name == 'pull_request' }}
1919
2020jobs :
2121 generate-tests :
22- name : Generate JSON of tests to run
22+ name : Generate balanced test matrix
2323 runs-on : ubuntu-latest
2424 outputs :
25- matrix : ${{ steps.set-matrix.outputs.matrix }}
25+ matrix1 : ${{ steps.set-matrix.outputs.matrix1 }}
26+ matrix2 : ${{ steps.set-matrix.outputs.matrix2 }}
2627 steps :
2728 # # Setup test environment
2829 - name : Setup Test Environment
2930 id : setup_tests
3031 uses : stacks-network/actions/stacks-core/testenv@main
3132 with :
3233 btc-version : " 25.0"
33- - name : Generate tests JSON
34- id : generate_tests_json
35- # List all of the tests using the nextest archive (so we don't need to do another build task)
36- # Filter them such that we only select tests from `--bin stacks-node` which are marked `ignored`
37- # Transform the output JSON into something that can be used as the matrix input
34+
35+ - name : Build Test Archive
3836 run : |
39- cargo nextest list --archive-file ~/test_archive.tar.zst -Tjson | \
40- jq -c '.["rust-suites"]["stacks-node::bin/stacks-node"]["testcases"] | map_values(select(.["ignored"] == true)) | keys' > ./tests.json
41- - id : set-matrix
37+ set -euo pipefail
38+ cargo nextest archive --archive-file ~/test_archive.tar.zst --bin stacks-node
39+
40+ - name : List ignored tests
41+ id : list
4242 run : |
43- json_obj=`cat ./tests.json`
44- echo "matrix=$json_obj" >> $GITHUB_OUTPUT
43+ set -euo pipefail
44+ cargo nextest list --archive-file ~/test_archive.tar.zst -Tjson > nextest_output.json
4545
46- # Bitcoin integration tests with code coverage
47- integration-tests :
48- needs : generate-tests
49- name : Integration Tests
50- runs-on : ubuntu-latest
51- strategy :
52- # # Continue with the test matrix even if we've had a failure
53- fail-fast : false
54- # # Run a maximum of 32 concurrent tests from the test matrix
55- max-parallel : 32
56- matrix :
57- test-name : ${{fromJson(needs.generate-tests.outputs.matrix)}}
58- exclude :
46+ jq -c '
47+ .["rust-suites"]["stacks-node::bin/stacks-node"]["testcases"]
48+ | [to_entries[] | select(.value.ignored) | .key]
49+ ' nextest_output.json > ignored_tests.json
50+
51+ echo "Ignored tests count: $(jq 'length' ignored_tests.json)"
52+
53+ # The following tests are excluded from CI runs. Some of these may be
54+ # worth investigating adding back into the CI
55+ - name : Excluded tests
56+ id : exclude
57+ run : |
58+ cat << 'EOF' > raw_exclude.txt
5959 # The following tests are excluded from CI runs. Some of these may be
6060 # worth investigating adding back into the CI
61- - test-name : tests::nakamoto_integrations::consensus_hash_event_dispatcher
62- - test-name : tests::neon_integrations::atlas_integration_test
63- - test-name : tests::neon_integrations::atlas_stress_integration_test
64- - test-name : tests::neon_integrations::bitcoind_resubmission_test
65- - test-name : tests::neon_integrations::block_replay_integration_test
66- - test-name : tests::neon_integrations::deep_contract
67- - test-name : tests::neon_integrations::filter_txs_by_origin
68- - test-name : tests::neon_integrations::filter_txs_by_type
69- - test-name : tests::neon_integrations::lockup_integration
70- - test-name : tests::neon_integrations::most_recent_utxo_integration_test
71- - test-name : tests::neon_integrations::run_with_custom_wallet
72- - test-name : tests::neon_integrations::test_competing_miners_build_anchor_blocks_on_same_chain_without_rbf
73- - test-name : tests::neon_integrations::test_one_miner_build_anchor_blocks_on_same_chain_without_rbf
74- - test-name : tests::signer::v0::tenure_extend_after_2_bad_commits
75- - test-name : tests::stackerdb::test_stackerdb_event_observer
76- - test-name : tests::stackerdb::test_stackerdb_load_store
61+ tests::nakamoto_integrations::consensus_hash_event_dispatcher
62+ tests::neon_integrations::atlas_integration_test
63+ tests::neon_integrations::atlas_stress_integration_test
64+ tests::neon_integrations::bitcoind_resubmission_test
65+ tests::neon_integrations::block_replay_integration_test
66+ tests::neon_integrations::deep_contract
67+ tests::neon_integrations::filter_txs_by_origin
68+ tests::neon_integrations::filter_txs_by_type
69+ tests::neon_integrations::lockup_integration
70+ tests::neon_integrations::most_recent_utxo_integration_test
71+ tests::neon_integrations::run_with_custom_wallet
72+ tests::neon_integrations::test_competing_miners_build_anchor_blocks_on_same_chain_without_rbf
73+ tests::neon_integrations::test_one_miner_build_anchor_blocks_on_same_chain_without_rbf
74+ tests::signer::v0::tenure_extend_after_2_bad_commits
75+ tests::stackerdb::test_stackerdb_event_observer
76+ tests::stackerdb::test_stackerdb_load_store
7777 # Epoch tests are covered by the epoch-tests CI workflow, and don't need to run
7878 # on every PR (for older epochs)
79- - test-name : tests::epoch_205::test_cost_limit_switch_version205
80- - test-name : tests::epoch_205::test_dynamic_db_method_costs
81- - test-name : tests::epoch_205::test_exact_block_costs
82- - test-name : tests::epoch_205::transition_empty_blocks
83- - test-name : tests::epoch_21::test_sortition_divergence_pre_21
84- - test-name : tests::epoch_21::test_v1_unlock_height_with_current_stackers
85- - test-name : tests::epoch_21::test_v1_unlock_height_with_delay_and_current_stackers
86- - test-name : tests::epoch_21::trait_invocation_cross_epoch
87- - test-name : tests::epoch_21::transition_adds_burn_block_height
88- - test-name : tests::epoch_21::transition_adds_get_pox_addr_recipients
89- - test-name : tests::epoch_21::transition_adds_mining_from_segwit
90- - test-name : tests::epoch_21::transition_adds_pay_to_alt_recipient_contract
91- - test-name : tests::epoch_21::transition_adds_pay_to_alt_recipient_principal
92- - test-name : tests::epoch_21::transition_empty_blocks
93- - test-name : tests::epoch_21::transition_fixes_bitcoin_rigidity
94- - test-name : tests::epoch_21::transition_removes_pox_sunset
95- - test-name : tests::epoch_22::disable_pox
96- - test-name : tests::epoch_22::pox_2_unlock_all
97- - test-name : tests::epoch_23::trait_invocation_behavior
98- - test-name : tests::epoch_24::fix_to_pox_contract
99- - test-name : tests::epoch_24::verify_auto_unlock_behavior
79+ tests::epoch_205::test_cost_limit_switch_version205
80+ tests::epoch_205::test_dynamic_db_method_costs
81+ tests::epoch_205::test_exact_block_costs
82+ tests::epoch_205::transition_empty_blocks
83+ tests::epoch_21::test_sortition_divergence_pre_21
84+ tests::epoch_21::test_v1_unlock_height_with_current_stackers
85+ tests::epoch_21::test_v1_unlock_height_with_delay_and_current_stackers
86+ tests::epoch_21::trait_invocation_cross_epoch
87+ tests::epoch_21::transition_adds_burn_block_height
88+ tests::epoch_21::transition_adds_get_pox_addr_recipients
89+ tests::epoch_21::transition_adds_mining_from_segwit
90+ tests::epoch_21::transition_adds_pay_to_alt_recipient_contract
91+ tests::epoch_21::transition_adds_pay_to_alt_recipient_principal
92+ tests::epoch_21::transition_empty_blocks
93+ tests::epoch_21::transition_fixes_bitcoin_rigidity
94+ tests::epoch_21::transition_removes_pox_sunset
95+ tests::epoch_22::disable_pox
96+ tests::epoch_22::pox_2_unlock_all
97+ tests::epoch_23::trait_invocation_behavior
98+ tests::epoch_24::fix_to_pox_contract
99+ tests::epoch_24::verify_auto_unlock_behavior
100100 # Disable this flaky test. We don't need continue testing Epoch 2 -> 3 transition
101- - test-name : tests::nakamoto_integrations::flash_blocks_on_epoch_3_FLAKY
101+ tests::nakamoto_integrations::flash_blocks_on_epoch_3_FLAKY
102102 # These mempool tests take a long time to run, and are meant to be run manually
103- - test-name : tests::nakamoto_integrations::large_mempool_original_constant_fee
104- - test-name : tests::nakamoto_integrations::large_mempool_original_random_fee
105- - test-name : tests::nakamoto_integrations::large_mempool_next_constant_fee
106- - test-name : tests::nakamoto_integrations::large_mempool_next_random_fee
107- - test-name : tests::nakamoto_integrations::larger_mempool
108- - test-name : tests::signer::v0::larger_mempool
103+ tests::nakamoto_integrations::large_mempool_original_constant_fee
104+ tests::nakamoto_integrations::large_mempool_original_random_fee
105+ tests::nakamoto_integrations::large_mempool_next_constant_fee
106+ tests::nakamoto_integrations::large_mempool_next_random_fee
107+ tests::nakamoto_integrations::larger_mempool
108+ tests::signer::v0::larger_mempool
109+ EOF
110+
111+ # Clean: remove empty lines and comments
112+ grep -v '^\s*$' raw_exclude.txt | grep -v '^\s*#' > clean_exclude.txt
113+
114+ # Convert to proper JSON array
115+ jq -R . clean_exclude.txt | jq -s . > exclude.json
116+
117+ echo "Excluded tests count: $(jq length exclude.json)"
118+
119+ - name : Filter out excluded tests
120+ run : |
121+ set -euo pipefail
109122
123+ # Validate inputs
124+ jq -e 'type == "array"' ignored_tests.json > /dev/null
125+ jq -e 'type == "array"' exclude.json > /dev/null
126+
127+ # Keep only ignored tests NOT in exclude list
128+ # First sort the inputs to use comm -23
129+ jq -r '.[]' ignored_tests.json | sort > ignored_sorted.txt
130+ jq -r '.[]' exclude.json | sort > exclude_sorted.txt
131+
132+ comm -23 ignored_sorted.txt exclude_sorted.txt > filtered.txt
133+
134+ echo "Final test count: $(wc -l < filtered.txt)"
135+
136+ - name : Split into two balanced matrices
137+ id : set-matrix
138+ run : |
139+ set -euo pipefail
140+ mapfile -t tests < filtered.txt
141+ total=${#tests[@]}
142+ echo "Total filtered tests: $total"
143+
144+ if (( total > 512 )); then
145+ echo "ERROR: $total tests exceed 512 limit (2 × 256)"
146+ echo "Split into 3+ jobs required."
147+ exit 1
148+ fi
149+
150+ base=$(( total / 2 ))
151+ remainder=$(( total % 2 ))
152+ size1=$(( base + remainder ))
153+
154+ # Compact JSON output for fromJSON()
155+ matrix1=$(printf '%s\n' "${tests[@]:0:$size1}" | jq -R . | jq -s -c .)
156+ matrix2=$(printf '%s\n' "${tests[@]:$size1}" | jq -R . | jq -s -c .)
157+
158+ echo "matrix1=$matrix1" >> "$GITHUB_OUTPUT"
159+ echo "matrix2=$matrix2" >> "$GITHUB_OUTPUT"
160+
161+ integration-tests-1 :
162+ needs : generate-tests
163+ runs-on : ubuntu-latest
164+ if : ${{ needs.generate-tests.outputs.matrix1 != '[]' }}
165+ strategy :
166+ fail-fast : false
167+ max-parallel : 32
168+ matrix :
169+ test-name : ${{ fromJSON(needs.generate-tests.outputs.matrix1) }}
110170 steps :
111171 # # Setup test environment
112172 - name : Setup Test Environment
@@ -130,9 +190,40 @@ jobs:
130190 test-name : ${{ matrix.test-name }}
131191 threads : 1
132192
133- # # Create and upload code coverage file
134193 - name : Code Coverage
135- id : codecov
194+ uses : stacks-network/actions/codecov@main
195+ with :
196+ test-name : ${{ matrix.test-name }}
197+
198+ integration-tests-2 :
199+ needs : generate-tests
200+ runs-on : ubuntu-latest
201+ if : ${{ needs.generate-tests.outputs.matrix2 != '[]' }}
202+ strategy :
203+ fail-fast : false
204+ max-parallel : 32
205+ matrix :
206+ test-name : ${{ fromJSON(needs.generate-tests.outputs.matrix2) }}
207+ steps :
208+ - name : Setup Test Environment
209+ uses : stacks-network/actions/stacks-core/testenv@main
210+ with :
211+ btc-version : " 25.0"
212+
213+ # # Increase open file descriptors limit
214+ - name : Increase Open File Descriptors
215+ run : |
216+ sudo prlimit --nofile=4096:4096
217+
218+ - name : Run Test
219+ id : run_tests
220+ timeout-minutes : ${{ fromJSON(env.TEST_TIMEOUT) }}
221+ uses : stacks-network/actions/stacks-core/run-tests@main
222+ with :
223+ test-name : ${{ matrix.test-name }}
224+ threads : 1
225+
226+ - name : Code Coverage
136227 uses : stacks-network/actions/codecov@main
137228 with :
138229 test-name : ${{ matrix.test-name }}
@@ -142,7 +233,8 @@ jobs:
142233 runs-on : ubuntu-latest
143234 if : always()
144235 needs :
145- - integration-tests
236+ - integration-tests-1
237+ - integration-tests-2
146238 steps :
147239 - name : Check Tests Status
148240 id : check_tests_status
0 commit comments