//! Fuzzer harness which searches for situations where the parser does not parse or unparse a
//! particular source snippet consistently.

#![no_main]

use libfuzzer_sys::{fuzz_target, Corpus};
use ruff_python_codegen::round_trip;
use similar::TextDiff;

fn do_fuzz(case: &[u8]) -> Corpus {
    let Ok(code) = std::str::from_utf8(case) else {
        return Corpus::Reject;
    };

    // round trip it once to get a formatted version
    if let Ok(first) = round_trip(code, "fuzzed-source.py") {
        // round trip it a second time to get a case to compare against
        if let Ok(second) = round_trip(&first, "fuzzed-source.py") {
            if cfg!(feature = "full-idempotency") {
                // potentially, we don't want to test for full idempotency, but just for unsteady states
                // enable the "full-idempotency" feature when fuzzing for full idempotency
                let diff = TextDiff::from_lines(&first, &second)
                    .unified_diff()
                    .header("Parsed once", "Parsed twice")
                    .to_string();
                assert_eq!(
                    first, second,
                    "\nIdempotency violation (orig => first => second); original: {:?}\ndiff:\n{}",
                    code, diff
                );
            } else if first != second {
                // by the third time we've round-tripped it, we shouldn't be introducing any more
                // changes; if we do, then it's likely that we're in an unsteady parsing state
                let third = round_trip(&second, "fuzzed-source.py")
                    .expect("Couldn't round-trip the processed source.");
                let diff = TextDiff::from_lines(&second, &third)
                    .unified_diff()
                    .header("Parsed twice", "Parsed three times")
                    .to_string();
                assert_eq!(
                    second, third,
                    "\nPotential unsteady state (orig => first => second => third); original: {:?}\ndiff:\n{}",
                    code, diff
                );
            }
        } else {
            panic!(
                "Unable to perform the second round trip!\nbefore: {:?}\nfirst: {:?}",
                code, first
            );
        }
    }

    Corpus::Keep
}

fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) });
