Skip to content

Commit

Permalink
Add: CI mutants output and update rust version
Browse files Browse the repository at this point in the history
- the version update to 1.81 is required to support mutation testing
  • Loading branch information
ASuciuX committed Jan 16, 2025
1 parent 4afb201 commit ef723d4
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 3 deletions.
250 changes: 250 additions & 0 deletions .github/workflows/pr-mutants.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
name: PR Differences Mutants

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

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

jobs:
mutants:
name: Mutation Testing
runs-on: ubuntu-latest
steps:
# Cleanup Runner
- name: Cleanup Runner
id: runner_cleanup
uses: stacks-network/actions/cleanup/disk@main

- name: Checkout repo
id: git_checkout
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
fetch-depth: 0

- name: Relative diff
id: relative_diff
run: |
git diff $(git merge-base origin/${{ github.base_ref || 'main' }} HEAD)..HEAD > git.diff
- name: Install cargo-mutants
id: install_cargo_mutants
run: |
cargo install --version 24.11.2 cargo-mutants --locked # v24.11.2
- name: Install cargo-nextest
id: install_cargo_nextest
uses: taiki-e/install-action@2f990e9c484f0590cb76a07296e9677b417493e9 # v2.33.23
with:
tool: nextest # Latest version

- name: Update git diff
id: update_git_diff
run: |
input_file="git.diff"
temp_file="temp_diff_file.diff"
# Check if the file exists and is not empty
if [ ! -s "$input_file" ]; then
echo "Diff file ($input_file) is missing or empty!"
exit 1
fi
# Remove all lines related to deleted files including the first 'diff --git' line
awk '
/^diff --git/ {
diff_line = $0
getline
if ($0 ~ /^deleted file mode/) {
in_deleted_file_block = 1
} else {
if (diff_line != "") {
print diff_line
diff_line = ""
}
in_deleted_file_block = 0
}
}
!in_deleted_file_block
' "$input_file" > "$temp_file" && mv "$temp_file" "$input_file"
# Remove 'diff --git' lines only when followed by 'similarity index', 'rename from', and 'rename to'
awk '
/^diff --git/ {
diff_line = $0
getline
if ($0 ~ /^similarity index/) {
getline
if ($0 ~ /^rename from/) {
getline
if ($0 ~ /^rename to/) {
next
}
}
}
print diff_line
}
{ print }
' "$input_file" > "$temp_file" && mv "$temp_file" "$input_file"
- name: Run docker-compose
uses: hoverkraft-tech/compose-action@f1ca7fefe3627c2dab0ae1db43a106d82740245e # v2.0.2
with:
compose-file: "./dockerfiles/docker-compose.dev.postgres.yml"

- name: Run mutants
id: run_mutants
run: |
# Disable immediate exit on error
set +e
cargo mutants --workspace --timeout-multiplier 1.5 --no-shuffle -vV --in-diff git.diff --output ./ --test-tool=nextest -- --all-targets --test-threads 1
exit_code=$?
# Create the folder only containing the outcomes (.txt files) and make a file containing the exit code of the command
mkdir mutants
echo "$exit_code" > ./mutants/exit_code.txt
mv ./mutants.out/*.txt mutants/
# Enable immediate exit on error again
set -e
- name: Print mutants
id: print_tested_mutants
shell: bash
run: |
# Info for creating the link that paths to the specific mutation tested
server_url="${{ github.server_url }}"
organisation="${{ github.repository_owner }}"
repository="${{ github.event.repository.name }}"
commit="${{ github.sha }}"
# Function to write to github step summary with specific info depending on the mutation category
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':' '{gsub("%", "%%"); printf "- [ ] [%s](%s/%s/%s/blob/%s/%s#L%d)\n\n", $0, "'"$server_url"'", "'"$organisation"'", "'"$repository"'", "'"$commit"'", $1, $2-1}' "$file_name" >> "$GITHUB_STEP_SUMMARY"
else
awk -F':' '{gsub("%", "%%"); printf "- [x] [%s](%s/%s/%s/blob/%s/%s#L%d)\n\n", $0, "'"$server_url"'", "'"$organisation"'", "'"$repository"'", "'"$commit"'", $1, $2-1}' "$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
}
# Print uncaught (missed/timeout/unviable) mutants to summary
if [ -s ./mutants/missed.txt -o -s ./mutants/timeout.txt -o -s ./mutants/unviable.txt ]; then
echo "# Uncaught Mutants" >> "$GITHUB_STEP_SUMMARY"
echo "[Documentation - How to treat Mutants Output](https://github.com/stacks-network/actions/tree/main/stacks-core/mutation-testing#how-mutants-output-should-be-treated)" >> "$GITHUB_STEP_SUMMARY"
write_section "Missed:" "./mutants/missed.txt"
write_section "Timeout:" "./mutants/timeout.txt"
write_section "Unviable:" "./mutants/unviable.txt"
fi
# Print caught mutants to summary
if [ -s ./mutants/caught.txt ]; then
echo "# Caught Mutants" >> "$GITHUB_STEP_SUMMARY"
write_section "" "./mutants/caught.txt"
fi
# Get exit code from the file and match it
exit_code=$(<"mutants/exit_code.txt")
yellow_bold="\033[1;33m"
reset="\033[0m"
summary_link_message="${yellow_bold}Click here for more information on how to fix:${reset} ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#:~:text=Output%20Mutants%20summary"
case $exit_code in
0)
if [[ ! -f ./mutants/caught.txt && ! -f ./mutants/missed.txt && ! -f ./mutants/timeout.txt && ! -f ./mutants/unviable.txt ]]; then
echo "No mutants found to test!"
elif [[ -s ./mutants/unviable.txt ]]; then
echo -e "$summary_link_message"
echo "Found unviable mutants!"
exit 5
else
echo "All new and updated functions are caught!"
fi
;;
1)
echo -e "$summary_link_message"
echo "Invalid command line arguments!"
exit $exit_code
;;
2)
echo -e "$summary_link_message"
echo "Found missed mutants!"
exit $exit_code
;;
3)
echo -e "$summary_link_message"
echo "Found timeout mutants!"
exit $exit_code
;;
4)
echo -e "$summary_link_message"
echo "Building the packages failed without any mutations!"
exit $exit_code
;;
*)
echo -e "$summary_link_message"
echo "Unknown exit code: $exit_code"
exit $exit_code
;;
esac
exit 0
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,7 @@ Cargo.lock
!.yarn/versions

*.node

# Mutation Testing
mutants.out/
mutants.out.old/
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dockerfiles/components/ordhook.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ENV GIT_COMMIT=${GIT_COMMIT}
WORKDIR /src

RUN apt-get update && apt-get install -y ca-certificates pkg-config libssl-dev libclang-11-dev libunwind-dev libunwind8 curl gnupg
RUN rustup update 1.80.1 && rustup default 1.80.1
RUN rustup update 1.81 && rustup default 1.81

RUN mkdir /out
COPY ./Cargo.toml /src/Cargo.toml
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[toolchain]
channel = "1.80.1"
channel = "1.81"

0 comments on commit ef723d4

Please sign in to comment.