Skip to content

Commit d23cc59

Browse files
committed
ci: add daily fuzz job
1 parent c006ff8 commit d23cc59

6 files changed

Lines changed: 399 additions & 4 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: CI Failure Issues
2+
3+
on:
4+
workflow_run: # zizmor: ignore[dangerous-triggers]
5+
workflows:
6+
- Fuzz
7+
types:
8+
- completed
9+
branches:
10+
- master
11+
12+
permissions:
13+
issues: write
14+
15+
concurrency:
16+
group: ci-failure-issues-${{ github.event.workflow_run.name }}
17+
cancel-in-progress: false
18+
19+
jobs:
20+
sync-issue:
21+
if: ${{ contains(fromJson('["failure","success"]'), github.event.workflow_run.conclusion) }}
22+
runs-on: ubuntu-24.04
23+
24+
steps:
25+
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
26+
with:
27+
script: |
28+
const run = context.payload.workflow_run;
29+
const workflow = run.name;
30+
const marker = `<!-- ci-failure-key:${workflow} -->`;
31+
const workflowFiles = {
32+
"Fuzz": "cron-daily-fuzz.yml",
33+
};
34+
35+
const formatTs = iso => {
36+
const d = new Date(iso);
37+
const pad = n => String(n).padStart(2, "0");
38+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} at ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`;
39+
};
40+
const shortSha = sha => (sha || "unknown").slice(0, 7);
41+
const commitUrl = sha => `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${sha}`;
42+
const workflowUrl = workflowFiles[workflow]
43+
? `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/workflows/${workflowFiles[workflow]}`
44+
: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions?query=${encodeURIComponent(`workflow:"${workflow}"`)}`;
45+
46+
function titleFor() {
47+
return `The ${workflow} workflow is failing`;
48+
}
49+
50+
function bodyForFailure() {
51+
return [
52+
marker,
53+
"",
54+
`The [${workflow} workflow](${workflowUrl}) started failing on ${formatTs(run.created_at)}: [${workflow} #${run.run_number}](${run.html_url}) - [\`${shortSha(run.head_sha)}\`](${commitUrl(run.head_sha)})`,
55+
].join("\n");
56+
}
57+
58+
const issues = await github.paginate(github.rest.issues.listForRepo, {
59+
owner: context.repo.owner,
60+
repo: context.repo.repo,
61+
state: "open",
62+
per_page: 100,
63+
});
64+
65+
const existing = issues.find(issue =>
66+
!issue.pull_request && issue.body && issue.body.includes(marker)
67+
);
68+
69+
if (run.conclusion === "failure" && !existing) {
70+
await github.rest.issues.create({
71+
owner: context.repo.owner,
72+
repo: context.repo.repo,
73+
title: titleFor(),
74+
body: bodyForFailure(),
75+
});
76+
}
77+
78+
if (run.conclusion === "success" && existing) {
79+
await github.rest.issues.createComment({
80+
owner: context.repo.owner,
81+
repo: context.repo.repo,
82+
issue_number: existing.number,
83+
body: `The [${workflow} workflow](${workflowUrl}) successfully ran again on ${formatTs(run.created_at)}: [${workflow} #${run.run_number}](${run.html_url}).`,
84+
});
85+
86+
await github.rest.issues.update({
87+
owner: context.repo.owner,
88+
repo: context.repo.repo,
89+
issue_number: existing.number,
90+
state: "closed",
91+
});
92+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Automatically generated by fuzz/generate-files.sh
2+
name: Fuzz
3+
on:
4+
schedule:
5+
# 5am every day UTC, this correlates to:
6+
# - 10pm PDT
7+
# - 6am CET
8+
# - 4pm AEDT
9+
- cron: '00 05 * * *'
10+
permissions: {}
11+
12+
jobs:
13+
fuzz:
14+
if: ${{ !github.event.act }}
15+
runs-on: ubuntu-24.04
16+
permissions:
17+
contents: read
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
fuzz_target: [
22+
compile_parse_tree,
23+
compile_text,
24+
display_parse_tree,
25+
parse_value_rtt,
26+
parse_witness_json_rtt,
27+
reconstruct_value,
28+
]
29+
steps:
30+
- name: Install test dependencies
31+
run: sudo apt-get update -y && sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev
32+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
33+
with:
34+
persist-credentials: false
35+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
36+
id: cache-fuzz
37+
with:
38+
path: |
39+
~/.cargo/bin
40+
fuzz/target
41+
target
42+
key: cache-${{ matrix.fuzz_target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
43+
- uses: dtolnay/rust-toolchain@5d458579430fc14a04a08a1e7d3694f545e91ce6 # stable
44+
with:
45+
toolchain: '1.74.0'
46+
- name: fuzz
47+
run: |
48+
echo "Using RUSTFLAGS $RUSTFLAGS"
49+
cd fuzz && ./fuzz.sh "${{ matrix.fuzz_target }}"
50+
- run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }}
51+
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
52+
with:
53+
name: executed_${{ matrix.fuzz_target }}
54+
path: executed_${{ matrix.fuzz_target }}
55+
56+
verify-execution:
57+
if: ${{ !github.event.act }}
58+
needs: fuzz
59+
runs-on: ubuntu-24.04
60+
permissions:
61+
contents: read
62+
steps:
63+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
64+
with:
65+
persist-credentials: false
66+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
67+
- run: cargo install --locked --version 0.12.0 cargo-fuzz
68+
- name: Display structure of downloaded files
69+
run: ls -R
70+
- run: find executed_* -type f -exec cat {} + | sort > executed
71+
- run: cargo fuzz list | sort | diff - executed

fuzz/Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.0.0"
44
publish = false
55
edition = "2021"
66
rust-version = "1.79.0"
7+
authors = ["Generated by fuzz/generate-files.sh"]
78

89
[package.metadata]
910
cargo-fuzz = true
@@ -26,15 +27,15 @@ base64 = "0.22.1"
2627
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(fuzzing)'] }
2728

2829
[[bin]]
29-
name = "compile_text"
30-
path = "fuzz_targets/compile_text.rs"
30+
name = "compile_parse_tree"
31+
path = "fuzz_targets/compile_parse_tree.rs"
3132
test = false
3233
doc = false
3334
bench = false
3435

3536
[[bin]]
36-
name = "compile_parse_tree"
37-
path = "fuzz_targets/compile_parse_tree.rs"
37+
name = "compile_text"
38+
path = "fuzz_targets/compile_text.rs"
3839
test = false
3940
doc = false
4041
bench = false

fuzz/fuzz-util.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
3+
# Sort order is affected by locale. See `man sort`.
4+
# > Set LC_ALL=C to get the traditional sort order that uses native byte values.
5+
export LC_ALL=C
6+
7+
REPO_DIR=$(git rev-parse --show-toplevel)
8+
9+
listTargetFiles() {
10+
pushd "$REPO_DIR/fuzz" >/dev/null || exit 1
11+
find fuzz_targets/ -type f -name "*.rs" | sort
12+
popd >/dev/null || exit 1
13+
}
14+
15+
targetFileToName() {
16+
echo "$1" |
17+
sed 's/^fuzz_targets\///' |
18+
sed 's/\.rs$//' |
19+
sed 's/\//_/g' |
20+
sed 's/^_//g'
21+
}
22+
23+
# Utility function to avoid CI failures on Windows
24+
checkWindowsFiles() {
25+
incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l)
26+
if [ "$incorrectFilenames" -gt 0 ]; then
27+
echo "Bailing early because there is a Windows-incompatible filename in the tree."
28+
exit 2
29+
fi
30+
}
31+
32+
# Checks whether a fuzz case has artifacts, and dumps them in hex
33+
checkReport() {
34+
artifactDir="fuzz/artifacts/$1"
35+
if [ -d "$artifactDir" ] && [ -n "$(ls -A "$artifactDir" 2>/dev/null)" ]; then
36+
echo "Artifacts found for target: $1"
37+
for artifact in "$artifactDir"/*; do
38+
if [ -f "$artifact" ]; then
39+
echo "Artifact: $(basename "$artifact")"
40+
xxd -p -c10000 <"$artifact"
41+
fi
42+
done
43+
exit 1
44+
fi
45+
}

fuzz/fuzz.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
# This script is used to briefly fuzz every target when no target is provided. Otherwise, it will briefly fuzz the
3+
# provided target
4+
5+
set -euox pipefail
6+
7+
REPO_DIR=$(git rev-parse --show-toplevel)
8+
9+
# can't find the file because of the ENV var
10+
# shellcheck source=/dev/null
11+
source "$REPO_DIR/fuzz/fuzz-util.sh"
12+
13+
target=
14+
max_total_time=100
15+
16+
for arg in "$@"; do
17+
case "$arg" in
18+
-max_total_time=*)
19+
max_total_time="${arg#-max_total_time=}"
20+
;;
21+
-*)
22+
echo "Unknown option: $arg"
23+
exit 2
24+
;;
25+
*)
26+
if [ -n "$target" ]; then
27+
echo "Unexpected argument: $arg"
28+
exit 2
29+
fi
30+
target="$arg"
31+
;;
32+
esac
33+
done
34+
35+
case "$max_total_time" in
36+
''|*[!0-9]*)
37+
echo "-max_total_time must be a non-negative integer number of seconds"
38+
exit 2
39+
;;
40+
esac
41+
42+
# Check that input files are correct Windows file names
43+
checkWindowsFiles
44+
45+
if [ -z "$target" ]; then
46+
targetFiles="$(listTargetFiles)"
47+
else
48+
targetFiles=fuzz_targets/"$target".rs
49+
fi
50+
51+
cargo --version
52+
rustc --version
53+
54+
# Testing
55+
cargo install --force --locked --version 0.12.0 cargo-fuzz
56+
for targetFile in $targetFiles; do
57+
targetName=$(targetFileToName "$targetFile")
58+
echo "Fuzzing target $targetName ($targetFile) for $max_total_time seconds"
59+
# cargo-fuzz will check for the corpus at fuzz/corpus/<target>
60+
cargo +nightly fuzz run "$targetName" -- -max_total_time="$max_total_time"
61+
checkReport "$targetName"
62+
done

0 commit comments

Comments
 (0)