Skip to content

Add fuzz testing to JSON and EMF formatters#237

Merged
rcoh merged 5 commits intoawslabs:mainfrom
yulnr:test/json-validity-fuzzing
Mar 17, 2026
Merged

Add fuzz testing to JSON and EMF formatters#237
rcoh merged 5 commits intoawslabs:mainfrom
yulnr:test/json-validity-fuzzing

Conversation

@yulnr
Copy link
Copy Markdown
Collaborator

@yulnr yulnr commented Mar 16, 2026

Closes #220

Summary

Adds fuzz testing infrastructure for the JSON and EMF formatters.

  • fuzz_json: Validates that successful formatting produces exactly one valid, newline-terminated JSON object. Exercises both regular and sampled paths.
  • fuzz_emf: Validates that successful formatting produces valid newline-delimited JSON objects. Exercises both regular and sampled paths, including EMF-specific flag modes.
  • fuzz_entry: Shared, format-agnostic entry generator that exercises the EntryWriter API. This is now mostly derive-first (Arbitrary) with a few small shaping choices to avoid spending too much time in known low-signal validation failures.

CI

We didn't discuss CI integration, but as a placeholder I'm adding an example setup that runs both targets nightly via with corpus caching and coverage-guided corpus minimization (cargo fuzz cmin).
Edit: we've updated this to do 1-min runs on PRs, and a nightly 5 min run per target without corpus caching.

Misc

A simpler version of this setup is what caught this issue earlier, in a 3 min run: #221

🔏 By submitting this pull request

  • I confirm that I've made a best effort attempt to update all relevant documentation.
  • I confirm that my contribution is made under the terms of the Apache 2.0 license.

@yulnr yulnr marked this pull request as ready for review March 16, 2026 17:25
Copy link
Copy Markdown
Contributor

@rcoh rcoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some ways we can improve this but doesn't block. Thanks for taking this on!

Comment thread .github/workflows/fuzz.yml Outdated
Comment on lines +68 to +77
# Policy:
# - one evolving cache lineage per branch per day
# - restore from same-day first, then same week, then branch, then OS-wide fallback
# - keep corpus in cache (not committed) to avoid repository bloat
key: fuzz-corpus-${{ runner.os }}-${{ github.ref_name }}-${{ steps.corpus_bucket.outputs.day }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ runner.os }}-${{ github.ref_name }}-${{ steps.corpus_bucket.outputs.day }}-
fuzz-corpus-${{ runner.os }}-${{ github.ref_name }}-${{ steps.corpus_bucket.outputs.week }}-
fuzz-corpus-${{ runner.os }}-${{ github.ref_name }}-
fuzz-corpus-${{ runner.os }}-
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think we're getting a lot of value out of these corpuses? (basically, should we just check in something once and avoid relying on the github cache?)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not sure, for json validation and how we're planning to run this (frequent short runs) we might actually not evolve a super useful corpus. I also considered at first to just check in something now as seed corpus and not cache at all, it sounds like you also think that should be good enough, so I'll simplify this and remove this CI machinery.

Comment thread fuzz/fuzz_targets/fuzz_emf.rs Outdated
Comment thread fuzz/fuzz_targets/fuzz_entry.rs Outdated

impl FuzzTimestamp {
pub fn to_system_time(&self) -> SystemTime {
// Keep values bounded to avoid pathological durations.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these pathological durations?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, not a very useful comment. Initially I put the cap there because I was doing UNIX_EPOCH + duration, which would panic on overflow.

I’ve now switched that to checked_add (which I should've done, since I was already using checked_sub). But after removing the cap, I found another issue. It seems like (at least on macOS) unbounded timestamp values can hit a std time edge case: duration_since(UNIX_EPOCH) can stack-overflow inside std (Timespec::sub_timespec) under ASAN. I can reproduce that with a tiny standalone program using -Zsanitizer=address.

I’ll double-check this tomorrow, but we may still need either a cap or to limit secs to u32. (I'll document it clearer of course)

Copy link
Copy Markdown
Collaborator Author

@yulnr yulnr Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I believe this was a bug in nightly with SystemTime::duration_since, tracked in #146228, fixed by #146556. After updating nightly I can’t reproduce it, so I removed the bound.

Comment thread fuzz/fuzz_targets/fuzz_entry.rs Outdated
}

impl<'a> Arbitrary<'a> for FuzzEntry {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason we can't derive arb for this?

Copy link
Copy Markdown
Collaborator Author

@yulnr yulnr Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason that'd prevent it. Initially I went for manual impls for a lot of things due to a mix of necessity (can't derive on foreign types like Unit) and also it gave me a chance to add some biases either towards edge cases or towards valid inputs, in case it'd save us some fuzzing cycles.
But tbh I wasn't super confident those biases were helping, I'm now looking at coverage reports and it seems like we can get away with simplifying by deriving arb for most things (with some caveats, like avoiding too many empty strings which wastes a few mins of fuzzing on failed validations).
I'll find a good spot and update this tomorrow, it should also make this implementation a lot smaller.

@yulnr
Copy link
Copy Markdown
Collaborator Author

yulnr commented Mar 17, 2026

Thanks for the feedback! I've simplified the CI (removed corpus caching) and simplified the FuzzEntry setup, making it derive Arbitrary (similar for other types as well).
Also added a 1-min run (per target) on PRs.

@yulnr yulnr force-pushed the test/json-validity-fuzzing branch from 655f274 to 4bfd69b Compare March 17, 2026 12:44
@rcoh rcoh merged commit 7acdb62 into awslabs:main Mar 17, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add fuzz testing to validate JSON output of formatters

2 participants