Skip to content

Commit b04d121

Browse files
authored
Merge branch 'develop' into ci/fix-mock-miner-test
2 parents b800565 + 2feb784 commit b04d121

File tree

11 files changed

+260
-23
lines changed

11 files changed

+260
-23
lines changed

clarity/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,5 @@ developer-mode = ["stacks_common/developer-mode"]
5555
slog_json = ["stacks_common/slog_json"]
5656
testing = ["canonical"]
5757
devtools = []
58+
rollback_value_check = []
59+
disable-costs = []

clarity/src/vm/database/key_value_wrapper.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ use crate::vm::types::{
3131
};
3232
use crate::vm::{StacksEpoch, Value};
3333

34-
#[cfg(rollback_value_check)]
34+
#[cfg(feature = "rollback_value_check")]
3535
type RollbackValueCheck = String;
36-
#[cfg(not(rollback_value_check))]
36+
#[cfg(not(feature = "rollback_value_check"))]
3737
type RollbackValueCheck = ();
3838

39-
#[cfg(not(rollback_value_check))]
39+
#[cfg(not(feature = "rollback_value_check"))]
4040
fn rollback_value_check(_value: &str, _check: &RollbackValueCheck) {}
4141

42-
#[cfg(not(rollback_value_check))]
42+
#[cfg(not(feature = "rollback_value_check"))]
4343
fn rollback_edits_push<T>(edits: &mut Vec<(T, RollbackValueCheck)>, key: T, _value: &str) {
4444
edits.push((key, ()));
4545
}
4646
// this function is used to check the lookup map when committing at the "bottom" of the
4747
// wrapper -- i.e., when committing to the underlying store. for the _unchecked_ implementation
4848
// this is used to get the edit _value_ out of the lookupmap, for used in the subsequent `put_all`
4949
// command.
50-
#[cfg(not(rollback_value_check))]
50+
#[cfg(not(feature = "rollback_value_check"))]
5151
fn rollback_check_pre_bottom_commit<T>(
5252
edits: Vec<(T, RollbackValueCheck)>,
5353
lookup_map: &mut HashMap<T, Vec<String>>,
@@ -71,11 +71,11 @@ where
7171
output
7272
}
7373

74-
#[cfg(rollback_value_check)]
74+
#[cfg(feature = "rollback_value_check")]
7575
fn rollback_value_check(value: &String, check: &RollbackValueCheck) {
7676
assert_eq!(value, check)
7777
}
78-
#[cfg(rollback_value_check)]
78+
#[cfg(feature = "rollback_value_check")]
7979
fn rollback_edits_push<T>(edits: &mut Vec<(T, RollbackValueCheck)>, key: T, value: &String)
8080
where
8181
T: Eq + Hash + Clone,
@@ -84,7 +84,7 @@ where
8484
}
8585
// this function is used to check the lookup map when committing at the "bottom" of the
8686
// wrapper -- i.e., when committing to the underlying store.
87-
#[cfg(rollback_value_check)]
87+
#[cfg(feature = "rollback_value_check")]
8888
fn rollback_check_pre_bottom_commit<T>(
8989
edits: Vec<(T, RollbackValueCheck)>,
9090
lookup_map: &mut HashMap<T, Vec<String>>,

clarity/src/vm/types/signatures.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1656,7 +1656,9 @@ impl TypeSignature {
16561656
clarity_version: ClarityVersion,
16571657
) -> Result<BTreeMap<ClarityName, FunctionSignature>> {
16581658
let mut trait_signature: BTreeMap<ClarityName, FunctionSignature> = BTreeMap::new();
1659-
let functions_types = type_args[0]
1659+
let functions_types = type_args
1660+
.get(0)
1661+
.ok_or_else(|| CheckErrors::InvalidTypeDescription)?
16601662
.match_list()
16611663
.ok_or(CheckErrors::DefineTraitBadSignature)?;
16621664

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# Install cargo-mutants
6+
cargo install --version 24.7.1 cargo-mutants --locked
7+
8+
# Create diff file between current branch and develop branch
9+
git diff origin/develop...HEAD > git.diff
10+
11+
# Remove git diff files about removed/renamed files
12+
awk '
13+
/^diff --git/ {
14+
diff_line = $0
15+
getline
16+
if ($0 !~ /^(deleted file mode|similarity index)/) {
17+
print diff_line
18+
print
19+
}
20+
}
21+
!/^(diff --git|deleted file mode|similarity index|rename from|rename to)/ {print}
22+
' git.diff > processed.diff
23+
24+
# Extract mutants based on the processed diff
25+
cargo mutants --in-diff processed.diff --list > all_mutants.txt
26+
27+
# Create a directory for organizing mutants
28+
mkdir -p mutants_by_package
29+
30+
# Organize mutants into files based on their main folder
31+
while IFS= read -r line; do
32+
package=$(echo "$line" | cut -d'/' -f1)
33+
34+
case $package in
35+
"stackslib")
36+
echo "$line" >> "mutants_by_package/stackslib.txt"
37+
;;
38+
"testnet")
39+
echo "$line" >> "mutants_by_package/stacks-node.txt"
40+
;;
41+
"stacks-signer")
42+
echo "$line" >> "mutants_by_package/stacks-signer.txt"
43+
;;
44+
*)
45+
echo "$line" >> "mutants_by_package/small-packages.txt"
46+
;;
47+
esac
48+
done < all_mutants.txt
49+
50+
# Function to run mutants for a package
51+
run_mutants() {
52+
local package=$1
53+
local threshold=$2
54+
local output_dir=$3
55+
local mutant_file="mutants_by_package/${package}.txt"
56+
57+
if [ ! -f "$mutant_file" ]; then
58+
echo "No mutants found for $package"
59+
return 0
60+
fi
61+
62+
local regex_pattern=$(sed 's/[][()\.^$*+?{}|]/\\&/g' "$mutant_file" | paste -sd'|' -)
63+
local mutant_count=$(cargo mutants -F "$regex_pattern" -E ": replace .{1,2} with .{1,2} in " --list | wc -l)
64+
65+
if [ "$mutant_count" -gt "$threshold" ]; then
66+
echo "Running mutants for $package ($mutant_count mutants)"
67+
RUST_BACKTRACE=1 BITCOIND_TEST=1 \
68+
cargo mutants --timeout-multiplier 1.5 --no-shuffle -vV \
69+
-F "$regex_pattern" \
70+
-E ": replace .{1,2} with .{1,2} in " \
71+
--output "$output_dir" \
72+
--test-tool=nextest \
73+
--package "$package" \
74+
-- --all-targets --test-threads 1 || true
75+
76+
echo $? > "${output_dir}/exit_code.txt"
77+
else
78+
echo "Skipping $package, only $mutant_count mutants (threshold: $threshold)"
79+
fi
80+
81+
return 0
82+
}
83+
84+
# Run mutants for each wanted package
85+
run_mutants "stacks-signer" 500 "./stacks-signer_mutants" || true
86+
run_mutants "stacks-node" 540 "./stacks-node_mutants" || true
87+
run_mutants "stackslib" 72 "./stackslib_mutants" || true

docs/mutation-testing.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Mutation Testing
2+
3+
This document describes how to run mutation testing locally to mimic the outcome of a PR, without the CI limitation it provides by timing out after 6 hours.
4+
[Here is the script](../contrib/tools/local-mutation-testing.sh) to run the tests locally by running the mutants created by the changes between `HEAD` and develop.
5+
It does automatically all the steps explained below.
6+
7+
From the root level of the stacks-core repository run
8+
```sh
9+
./contrib/tools/local-mutation-testing.sh
10+
```
11+
12+
## Prerequirements
13+
14+
Install the cargo mutants library
15+
```sh
16+
cargo install --version 24.7.1 cargo-mutants --locked
17+
```
18+
19+
20+
## Steps
21+
1. Be on source branch you would use for the PR.
22+
2. Create diff file comparing this branch with the `develop` branch
23+
```sh
24+
git diff origin/develop..HEAD > git.diff
25+
```
26+
3. Clean up the diff file and create auxiliary files
27+
```sh
28+
awk '
29+
/^diff --git/ {
30+
diff_line = $0
31+
getline
32+
if ($0 !~ /^(deleted file mode|similarity index)/) {
33+
print diff_line
34+
print
35+
}
36+
}
37+
!/^(diff --git|deleted file mode|similarity index|rename from|rename to)/ {print}
38+
' git.diff > processed.diff
39+
40+
# Extract mutants based on the processed diff
41+
cargo mutants --in-diff processed.diff --list > all_mutants.txt
42+
43+
# Create a directory for organizing mutants
44+
mkdir -p mutants_by_package
45+
46+
# Organize mutants into files based on their main folder
47+
while IFS= read -r line; do
48+
package=$(echo "$line" | cut -d'/' -f1)
49+
50+
case $package in
51+
"stackslib")
52+
echo "$line" >> "mutants_by_package/stackslib.txt"
53+
;;
54+
"testnet")
55+
echo "$line" >> "mutants_by_package/stacks-node.txt"
56+
;;
57+
"stacks-signer")
58+
echo "$line" >> "mutants_by_package/stacks-signer.txt"
59+
;;
60+
*)
61+
echo "$line" >> "mutants_by_package/small-packages.txt"
62+
;;
63+
esac
64+
done < all_mutants.txt
65+
```
66+
4. Based on the package required to run the mutants for
67+
a. Stackslib package
68+
```sh
69+
regex_pattern=$(sed 's/[][()\.^$*+?{}|]/\\&/g' "mutants_by_package/stackslib.txt" | paste -sd'|' -)
70+
71+
RUST_BACKTRACE=1 BITCOIND_TEST=1 \
72+
cargo mutants --timeout-multiplier 1.5 --no-shuffle -vV \
73+
-F "$regex_pattern" \
74+
-E ": replace .{1,2} with .{1,2} in " \
75+
--output "./stackslib_mutants" \
76+
--test-tool=nextest \
77+
-- --all-targets --test-threads 1
78+
```
79+
b. Stacks-node (testnet) package
80+
```sh
81+
regex_pattern=$(sed 's/[][()\.^$*+?{}|]/\\&/g' "mutants_by_package/testnet.txt" | paste -sd'|' -)
82+
83+
RUST_BACKTRACE=1 BITCOIND_TEST=1 \
84+
cargo mutants --timeout-multiplier 1.5 --no-shuffle -vV \
85+
-F "$regex_pattern" \
86+
-E ": replace .{1,2} with .{1,2} in " \
87+
--output "./testnet_mutants" \
88+
--test-tool=nextest \
89+
-- --all-targets --test-threads 1
90+
```
91+
c. Stacks-signer
92+
```sh
93+
regex_pattern=$(sed 's/[][()\.^$*+?{}|]/\\&/g' "mutants_by_package/stacks-signer.txt" | paste -sd'|' -)
94+
95+
RUST_BACKTRACE=1 BITCOIND_TEST=1 \
96+
cargo mutants --timeout-multiplier 1.5 --no-shuffle -vV \
97+
-F "$regex_pattern" \
98+
-E ": replace .{1,2} with .{1,2} in " \
99+
--output "./stacks-signer_mutants" \
100+
--test-tool=nextest \
101+
-- --all-targets --test-threads 1
102+
```
103+
d. All other packages combined
104+
```sh
105+
regex_pattern=$(sed 's/[][()\.^$*+?{}|]/\\&/g' "mutants_by_package/small-packages.txt" | paste -sd'|' -)
106+
107+
cargo mutants --timeout-multiplier 1.5 --no-shuffle -vV \
108+
-F "$regex_pattern" \
109+
-E ": replace .{1,2} with .{1,2} in " \
110+
--output "./small-packages_mutants" \
111+
--test-tool=nextest \
112+
-- --all-targets --test-threads 1
113+
```
114+
115+
## How to run one specific mutant to test it
116+
117+
Example of output which had a missing mutant
118+
```sh
119+
MISSED stacks-signer/src/runloop.rs:424:9: replace <impl SignerRunLoop for RunLoop<Signer, T>>::run_one_pass -> Option<Vec<SignerResult>> with None in 3.0s build + 9.3s test
120+
```
121+
122+
Example of fix for it
123+
```sh
124+
RUST_BACKTRACE=1 BITCOIND_TEST=1 \
125+
cargo mutants -vV \
126+
-F "replace process_stackerdb_event" \
127+
-E ": replace <impl SignerRunLoop for RunLoop<Signer, T>>::run_one_pass -> Option<Vec<SignerResult>> with None in " \
128+
--test-tool=nextest \
129+
-- \
130+
--run-ignored all \
131+
--fail-fast \
132+
--test-threads 1
133+
```
134+
135+
General command to run
136+
```sh
137+
RUST_BACKTRACE=1 BITCOIND_TEST=1 \
138+
cargo mutants -vV \
139+
-F "replace process_stackerdb_event" \
140+
-E ": replace [modify this] with [modify this] in " \
141+
--test-tool=nextest \
142+
-- \
143+
--run-ignored all \
144+
--fail-fast \
145+
--test-threads 1
146+
```

stacks-common/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ canonical = ["rusqlite"]
7575
developer-mode = []
7676
slog_json = ["slog-json"]
7777
testing = ["canonical"]
78+
serde = []
79+
bech32_std = []
80+
bech32_strict = []
7881

7982
[target.'cfg(all(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"), not(any(target_os="windows"))))'.dependencies]
8083
sha2 = { version = "0.10", features = ["asm"] }

stacks-common/src/deps_common/bech32/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
//! has more details.
3131
//!
3232
#![cfg_attr(
33-
feature = "std",
33+
feature = "bech32_std",
3434
doc = "
3535
# Examples
3636
```
@@ -54,20 +54,20 @@ assert_eq!(variant, Variant::Bech32);
5454
#![deny(non_camel_case_types)]
5555
#![deny(non_snake_case)]
5656
#![deny(unused_mut)]
57-
#![cfg_attr(feature = "strict", deny(warnings))]
57+
#![cfg_attr(feature = "bech32_strict", deny(warnings))]
5858

59-
#[cfg(all(not(feature = "std"), not(test)))]
59+
#[cfg(all(not(feature = "bech32_std"), not(test)))]
6060
extern crate alloc;
6161

62-
#[cfg(any(test, feature = "std"))]
62+
#[cfg(any(test, feature = "bech32_std"))]
6363
extern crate core;
6464

65-
#[cfg(all(not(feature = "std"), not(test)))]
65+
#[cfg(all(not(feature = "bech32_std"), not(test)))]
6666
use alloc::borrow::Cow;
67-
#[cfg(all(not(feature = "std"), not(test)))]
67+
#[cfg(all(not(feature = "bech32_std"), not(test)))]
6868
use alloc::{string::String, vec::Vec};
6969
use core::{fmt, mem};
70-
#[cfg(any(feature = "std", test))]
70+
#[cfg(any(feature = "bech32_std", test))]
7171
use std::borrow::Cow;
7272

7373
/// Integer in the range `0..32`
@@ -690,7 +690,7 @@ impl fmt::Display for Error {
690690
}
691691
}
692692

693-
#[cfg(any(feature = "std", test))]
693+
#[cfg(any(feature = "bech32_std", test))]
694694
impl std::error::Error for Error {
695695
fn description(&self) -> &str {
696696
match *self {

stacks-common/src/deps_common/bitcoin/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
//!
2727
2828
// Clippy flags
29-
#![cfg_attr(feature = "clippy", allow(needless_range_loop))] // suggests making a big mess of array newtypes
30-
#![cfg_attr(feature = "clippy", allow(extend_from_slice))] // `extend_from_slice` only available since 1.6
29+
#![allow(clippy::needless_range_loop)] // suggests making a big mess of array newtypes
30+
#![allow(clippy::extend_from_slice)] // `extend_from_slice` only available since 1.6
3131

3232
// Coding conventions
3333
#![deny(non_upper_case_globals)]

stacks-common/src/deps_common/httparse/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
#![cfg_attr(test, deny(warnings))]
2323
// we can't upgrade while supporting Rust 1.3
2424
#![allow(deprecated)]
25-
#![cfg_attr(httparse_min_2018, allow(rust_2018_idioms))]
2625

2726
//! # httparse
2827
//!

0 commit comments

Comments
 (0)