Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8370213
feat: mutation testing initial integration
BowTiedDeployer Nov 24, 2023
06d68b2
Merge branch 'develop' into test/cargo-mutants-testing
ASuciuX Nov 24, 2023
1c5a75c
fix: made functions discoverable to be mutants
BowTiedDeployer Nov 24, 2023
19f56ea
feat: added mutants output before fix clarity package
BowTiedDeployer Nov 27, 2023
a3caebb
feat: added mutants output after fix clarity package
BowTiedDeployer Nov 27, 2023
e41e89a
Update mutants-testing-general.sh
ASuciuX Nov 27, 2023
b1649a3
feat: renamed mod to lib.rs
BowTiedDeployer Nov 29, 2023
32aa967
Delete Dockerfile.mutation-testing as it is also run locally with cargo
ASuciuX Nov 30, 2023
92b4e23
feat: modular mutations on shell
BowTiedDeployer Dec 1, 2023
5387f44
feat: restructure mutants to new CI workflows
BowTiedDeployer Dec 11, 2023
0bf76dd
feat: update link to stacks action repo
BowTiedDeployer Dec 11, 2023
9616e07
Merge branch 'develop' into test/cargo-mutants-testing
ASuciuX Dec 11, 2023
7c91912
feat: keep only filter pr workflow file
BowTiedDeployer Dec 15, 2023
491557d
added specific triggers for the CI action on PR
BowTiedDeployer Dec 15, 2023
ff3124e
modularize mutants' runs for different package size cases
BowTiedDeployer Jan 7, 2024
b90dc06
feat: rename from `filter-pr` to `pr-differences`
BowTiedDeployer Jan 8, 2024
13f7197
feat: shorter names for steps
BowTiedDeployer Jan 8, 2024
921a01d
feat: check if `tac`, `awk` and `sed` commands exist on host
BowTiedDeployer Jan 8, 2024
c6927a7
feat: check files before accessing them
BowTiedDeployer Jan 9, 2024
298e8e0
feat: move shell runs from main workflow to composite action
BowTiedDeployer Jan 10, 2024
12388b7
feat: add documentation for mutation testing
BowTiedDeployer Jan 11, 2024
5a4f9b3
feat: mutation testing - update composite branch
BowTiedDeployer Jan 16, 2024
16b7bff
feat: renamed back the lib files as cargo-mutants supports them now
BowTiedDeployer Jan 17, 2024
9b54fcb
Merge branch 'develop' into test/mutants-filter-pr
wileyj Jan 18, 2024
0875d10
feat: mutants docs - time related outcomes
BowTiedDeployer Jan 23, 2024
c16d87c
feat: update mutants doc format & some extra context
BowTiedDeployer Jan 25, 2024
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
363 changes: 363 additions & 0 deletions .github/workflows/pr-differences-mutants.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
name: PR Differences Mutants

on:
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
paths:
- "**.rs"

concurrency:
group: pr-differences-${{ github.head_ref || github.ref || github.run_id }}
# Always cancel duplicate jobs
cancel-in-progress: true

jobs:
# Check and output whether to run big (`stacks-node`/`stackslib`) or small (others) packages with or without shards
check-big-packages-and-shards:
name: Check Packages and Shards

runs-on: ubuntu-latest

outputs:
run_big_packages: ${{ steps.check_packages_and_shards.outputs.run_big_packages }}
big_packages_with_shards: ${{ steps.check_packages_and_shards.outputs.big_packages_with_shards }}
run_small_packages: ${{ steps.check_packages_and_shards.outputs.run_small_packages }}
small_packages_with_shards: ${{ steps.check_packages_and_shards.outputs.small_packages_with_shards }}

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- run: cargo install --version 23.12.2 cargo-mutants

- name: Relative diff
run: |
git diff origin/${{ github.base_ref }}.. > git.diff

- name: Remove deleted file's lines from git.diff file
Comment thread
ASuciuX marked this conversation as resolved.
Outdated
run: |
input_file="git.diff"
temp_file="temp_diff_file.diff"

# Reverse the file, remove 4 lines after '+++ /dev/null', then reverse it back (editors can't go backwards - to remove lines above)
tac "$input_file" > "$temp_file" && mv "$temp_file" "$input_file"
sed '/+++ \/dev\/null/{n;N;N;N;d;}' "$input_file" > "$temp_file" && mv "$temp_file" "$input_file"
tac "$input_file" > "$temp_file" && mv "$temp_file" "$input_file"

# Remove the lines between '+++ /dev/null' (included) and 'diff --git a/'
awk '
BEGIN { in_block=0 }
/\+\+\+ \/dev\/null/ { in_block=1; next }
in_block && /diff --git a\// { in_block=0; print; next }
!in_block
' "$input_file" > "$temp_file" && mv "$temp_file" "$input_file"

- name: Split diffs into big and small packages
run: |
cargo mutants --in-diff git.diff --list > all_mutants.txt
mkdir -p mutants_by_packages

# Split the differences from git into 2 parts, big packages ('stacks-node' and 'stackslib') and small packages (all others) and put them into separate files
while IFS= read -r line; do
package=$(echo "$line" | cut -d'/' -f1)
if [[ $package == "testnet" || $package == "stackslib" ]]; then
echo "$line" >> "mutants_by_packages/big_packages.txt"
else
echo "$line" >> "mutants_by_packages/small_packages.txt"
fi
done < all_mutants.txt

- id: check_packages_and_shards
run: |
number_of_big_mutants=0
number_of_small_mutants=0

# If big_packages file exists, count how many mutants there are
if [[ -s mutants_by_packages/big_packages.txt ]]; then
number_of_big_mutants=$(cat mutants_by_packages/big_packages.txt | awk 'END { print NR }' | tr -d '[:space:]')
fi

# If small_packages file exists, count how many mutants there are
if [[ -s mutants_by_packages/small_packages.txt ]]; then
number_of_small_mutants=$(cat mutants_by_packages/small_packages.txt | awk 'END { print NR }' | tr -d '[:space:]')
fi

# Set the mutants limit for when to run with shards on the small packages
if [[ $number_of_big_mutants -gt 15 ]]; then
small_packages_shard_limit=119
else
small_packages_shard_limit=79
fi

# If there are mutants from big packages, check whether to run with or without shards, otherwise there's nothing to run
if [[ $number_of_big_mutants -ne 0 ]]; then
echo "run_big_packages=true" >> "$GITHUB_OUTPUT"
if [[ $number_of_big_mutants -gt 15 ]]; then
echo "big_packages_with_shards=true" >> "$GITHUB_OUTPUT"
else
echo "big_packages_with_shards=false" >> "$GITHUB_OUTPUT"
fi
else
echo "run_big_packages=false" >> "$GITHUB_OUTPUT"
fi

# If there are mutants from small packages, check whether to run with or without shards, otherwise there's nothing to run
if [[ $number_of_small_mutants -ne 0 ]]; then
echo "run_small_packages=true" >> "$GITHUB_OUTPUT"
if [[ $number_of_small_mutants -gt $small_packages_shard_limit ]]; then
echo "small_packages_with_shards=true" >> "$GITHUB_OUTPUT"
else
echo "small_packages_with_shards=false" >> "$GITHUB_OUTPUT"
fi
else
echo "run_small_packages=false" >> "$GITHUB_OUTPUT"
fi

# Mutation testing - Execute on PR on small packages that have functions modified (normal run, no shards)
pr-differences-mutants-small-normal:
name: Mutation Testing - Normal, Small

needs: check-big-packages-and-shards

if: ${{ needs.check-big-packages-and-shards.outputs.run_small_packages == 'true' && needs.check-big-packages-and-shards.outputs.small_packages_with_shards == 'false' }}

runs-on: ubuntu-latest

steps:
- name: Run pr differences mutants from actions - no shards, small packages
uses: stacks-network/actions/mutation-testing/pr-differences@feat/mutation-testing
with:
package-dimension: "small"

# Mutation testing - Execute on PR on small packages that have functions modified (run with strategy matrix shards)
pr-differences-mutants-small-shards:
name: Mutation Testing - Shards, Small

needs: check-big-packages-and-shards

if: ${{ needs.check-big-packages-and-shards.outputs.run_small_packages == 'true' && needs.check-big-packages-and-shards.outputs.small_packages_with_shards == 'true' }}

runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3]

steps:
- name: Run pr differences mutants from actions - with shards, small packages
uses: stacks-network/actions/mutation-testing/pr-differences@feat/mutation-testing
with:
shard: ${{ matrix.shard }}
package-dimension: "small"

# Mutation testing - Execute on PR on big packages that have functions modified (normal run, no shards)
pr-differences-mutants-big-normal:
name: Mutation Testing - Normal, Big

needs: check-big-packages-and-shards

if: ${{ needs.check-big-packages-and-shards.outputs.run_big_packages == 'true' && needs.check-big-packages-and-shards.outputs.big_packages_with_shards == 'false' }}

runs-on: ubuntu-latest

steps:
- name: Run pr differences mutants from actions - no shards, big packages
env:
BITCOIND_TEST: 1
RUST_BACKTRACE: full
uses: stacks-network/actions/mutation-testing/pr-differences@feat/mutation-testing
with:
package-dimension: "big"

# Mutation testing - Execute on PR on big packages that have functions modified (run with strategy matrix shards)
pr-differences-mutants-big-shards:
name: Mutation Testing - Shards, Big

needs: check-big-packages-and-shards

if: ${{ needs.check-big-packages-and-shards.outputs.run_big_packages == 'true' && needs.check-big-packages-and-shards.outputs.big_packages_with_shards == 'true' }}

runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3, 4, 5, 6, 7]

steps:
- name: Run pr differences mutants from actions - with shards, big packages
env:
BITCOIND_TEST: 1
RUST_BACKTRACE: full
uses: stacks-network/actions/mutation-testing/pr-differences@feat/mutation-testing
with:
shard: ${{ matrix.shard }}
package-dimension: "big"

# Output the mutants and fail the workflow if there are missed/timeout/unviable mutants
output-mutants:
name: Output Mutants

runs-on: ubuntu-latest

if: always()
Comment thread
ASuciuX marked this conversation as resolved.
Outdated
needs:
[
pr-differences-mutants-small-normal,
pr-differences-mutants-small-shards,
pr-differences-mutants-big-normal,
pr-differences-mutants-big-shards,
]

steps:
- name: Download all workflow run artifacts
uses: actions/download-artifact@v3

- name: Append output from all shards
run: |
folders=("mutants-shard-big--1" "mutants-shard-big-0" "mutants-shard-big-1" "mutants-shard-big-2" "mutants-shard-big-3" "mutants-shard-big-4" "mutants-shard-big-5" "mutants-shard-big-6" "mutants-shard-big-7" "mutants-shard-small--1" "mutants-shard-small-0" "mutants-shard-small-1" "mutants-shard-small-2" "mutants-shard-small-3")
files=("missed.txt" "caught.txt" "timeout.txt" "unviable.txt")
mkdir -p mutants-shards

for file in "${files[@]}"; do
for folder in "${folders[@]}"; do
if [[ -s "$folder/$file" ]]; then
cat "$folder/$file" >> "mutants-shards/$file"
fi
done
done

for folder in "${folders[@]}"; do
if [[ -s "$folder" ]]; then
exit_code=$(<"${folder}/exit_code.txt")
most_relevant_exit_code=0

case $exit_code in
4)
most_relevant_exit_code=4
;;
1)
[ "$most_relevant_exit_code" -eq 0 ] && most_relevant_exit_code=1
;;
2)
[ "$most_relevant_exit_code" -eq 0 ] && most_relevant_exit_code=2
;;
3)
[ "$most_relevant_exit_code" -eq 0 ] && most_relevant_exit_code=3
;;
0)
;;
*)
echo "Unknown exit code $exit_code"
most_relevant_exit_code=$exit_code
;;
esac
fi
done

echo "$most_relevant_exit_code" > './mutants-shards/exit_code.txt'

- name: Print mutants and handle exit codes
run: |
server_url="${{ github.server_url }}"
organisation="${{ github.repository_owner }}"
repository="${{ github.event.repository.name }}"
commit="${{ github.sha }}"

write_section() {
local section_title=$1
local file_name=$2

if [ -s "$file_name" ]; then
if [[ "$section_title" != "" ]]; then
echo "## $section_title" >> "$GITHUB_STEP_SUMMARY"
fi

if [[ "$section_title" == "Missed:" ]]; then
echo "<details>" >> "$GITHUB_STEP_SUMMARY"
echo "<summary>What are missed mutants?</summary>" >> "$GITHUB_STEP_SUMMARY"
echo "<br>" >> "$GITHUB_STEP_SUMMARY"
echo "No test failed with this mutation applied, which seems to indicate a gap in test coverage. Or, it may be that the mutant is undistinguishable from the correct code. You may wish to add a better test, or mark that the function should be skipped." >> "$GITHUB_STEP_SUMMARY"
echo "</details>" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
elif [[ "$section_title" == "Timeout:" ]]; then
echo "<details>" >> "$GITHUB_STEP_SUMMARY"
echo "<summary>What are timeout mutants?</summary>" >> "$GITHUB_STEP_SUMMARY"
echo "<br>" >> "$GITHUB_STEP_SUMMARY"
echo "The mutation caused the test suite to run for a long time, until it was eventually killed. You might want to investigate the cause and potentially mark the function to be skipped." >> "$GITHUB_STEP_SUMMARY"
echo "</details>" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
elif [[ "$section_title" == "Unviable:" ]]; then
echo "<details>" >> "$GITHUB_STEP_SUMMARY"
echo "<summary>What are unviable mutants?</summary>" >> "$GITHUB_STEP_SUMMARY"
echo "<br>" >> "$GITHUB_STEP_SUMMARY"
echo "The attempted mutation doesn't compile. This is inconclusive about test coverage and no action is needed, unless you wish to test the specific function, in which case you may wish to add a 'Default::default()' implementation for the specific return type." >> "$GITHUB_STEP_SUMMARY"
echo "</details>" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
fi

if [[ "$section_title" != "" ]]; then
awk -F':' '{printf "- [ ] " "[" $0 "]"; file_path=$1; line=$2; $1=""; $2=""; printf "(" "'"$server_url"'/'"$organisation"'/'"$repository"'/blob/'"$commit"'/" file_path "#L" line-1 ")\n\n"}' "$file_name" >> "$GITHUB_STEP_SUMMARY"
else
awk -F':' '{printf "- [x] " "[" $0 "]"; file_path=$1; line=$2; $1=""; $2=""; printf "(" "'"$server_url"'/'"$organisation"'/'"$repository"'/blob/'"$commit"'/" file_path "#L" line-1 ")\n\n"}' "$file_name" >> "$GITHUB_STEP_SUMMARY"
fi

if [[ "$section_title" == "Missed:" ]]; then
echo "### To resolve this issue, consider one of the following options:" >> "$GITHUB_STEP_SUMMARY"
echo "- Modify or add tests including this function." >> "$GITHUB_STEP_SUMMARY"
echo "- If you are absolutely certain that this function should not undergo mutation testing, add '#[mutants::skip]' or '#[cfg_attr(test, mutants::skip)]' function header to skip it." >> "$GITHUB_STEP_SUMMARY"
elif [[ "$section_title" == "Timeout:" ]]; then
echo "### To resolve this issue, consider one of the following options:" >> "$GITHUB_STEP_SUMMARY"
echo "- Modify the tests that include this funcion." >> "$GITHUB_STEP_SUMMARY"
echo "- Add '#[mutants::skip]' or '#[cfg_attr(test, mutants::skip)]' function header to skip it." >> "$GITHUB_STEP_SUMMARY"
elif [[ "$section_title" == "Unviable:" ]]; then
echo "### To resolve this issue, consider one of the following options:" >> "$GITHUB_STEP_SUMMARY"
echo "- Create 'Default::default()' implementation for the specific structure." >> "$GITHUB_STEP_SUMMARY"
echo "- Add '#[mutants::skip]' or '#[cfg_attr(test, mutants::skip)]' function header to skip it." >> "$GITHUB_STEP_SUMMARY"
fi

echo >> "$GITHUB_STEP_SUMMARY"
fi
}

echo "# Uncaught Mutants" >> "$GITHUB_STEP_SUMMARY"
write_section "Missed:" "./mutants-shards/missed.txt"
write_section "Timeout:" "./mutants-shards/timeout.txt"
write_section "Unviable:" "./mutants-shards/unviable.txt"

echo "# Caught Mutants" >> "$GITHUB_STEP_SUMMARY"
write_section "" "./mutants-shards/caught.txt"

exit_code=$(<"mutants-shards/exit_code.txt")

case $exit_code in
0)
if [ -s ./mutants-shards/unviable.txt ]; then
echo "Found unviable mutants!"
exit 1
fi
echo "All new and updated functions are caught!"
;;
1)
echo "Invalid command line arguments!"
exit 1
;;
2 | 3)
echo "Found missed/timeout/unviable mutants!"
exit 1
;;
4)
echo "Building the packages failed without any mutations!"
exit 1
;;
*)
echo "Unknown exit code: $exit_code"
exit 1
;;
esac
2 changes: 1 addition & 1 deletion clarity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ resolver = "2"

[lib]
name = "clarity"
path = "./src/libclarity.rs"
path = "./src/lib.rs"

[dependencies]
rand = "0.7.3"
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion libsigner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition = "2021"

[lib]
name = "libsigner"
path = "./src/libsigner.rs"
path = "./src/lib.rs"

[dependencies]
clarity = { path = "../clarity" }
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion stacks-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ edition = "2021"

[lib]
name = "stacks_common"
path = "./src/libcommon.rs"
path = "./src/lib.rs"

[dependencies]
rand = "0.7.3"
Expand Down
File renamed without changes.