kache 1.2.0

Compiler cache for Rust, C/C++ and CUDA, with S3, GCS, shared-filesystem and OCI remotes.
use super::*;

fn roots(path: &str) -> Vec<(PathBuf, String, u8)> {
    vec![(path.into(), "checkout".into(), 5)]
}

#[test]
fn locations_and_include_stacks_move_without_rewriting_source_or_message_text() {
    let producer = roots("/work/A");
    let consumer = roots("/work/B");
    let text = concat!(
        "In file included from /work/A/include/a.h:3,\n",
        "                 from /work/A/unit.c:1:\n",
        "\x1b[1m/work/A/unit.c:2:4:\x1b[0m warning: /work/A/message stays literal\n",
        "    2 | const char *s = \"/work/A/source-literal\";\n",
        "/work/A/unit.c: In function 'f':\n",
        "/work/A/unit.c(3,4): warning C4018: signedness\n",
        "/work/AB/other.c:2:4: note: a sibling checkout\n",
        "/sdk/system.h:1:2: note: an external header\n",
        "relative.c:2:4: warning: a relative source\n"
    );
    let expected = text
        .replace("from /work/A/", "from /work/B/")
        .replace("\x1b[1m/work/A/", "\x1b[1m/work/B/")
        .replace("\n/work/A/unit.c", "\n/work/B/unit.c");
    let stored = store(text, &producer);
    assert!(stored.starts_with(PREFIX));
    assert_eq!(restore(&stored, &consumer).unwrap(), expected);
    assert_eq!(restore(&stored, &producer).unwrap(), text);
}

#[test]
fn unnumbered_source_excerpts_that_start_with_a_path_remain_literal() {
    let text = concat!(
        "/A/unit.c:2:4: warning: a multiline source excerpt\n",
        "    /A/raw-string-contents\n",
        "    from /A/more-raw-string-contents\n",
        "    /A/filename:nondiagnostic-text\n"
    );
    let stored = store(text, &roots("/A"));
    assert_eq!(
        restore(&stored, &roots("/B")).unwrap(),
        text.replacen("/A/unit.c", "/B/unit.c", 1)
    );
}

#[test]
fn location_suffixes_require_compiler_location_syntax() {
    for suffix in [
        "/unit.c:2:4: warning: comparison",
        "/unit.c: 2: comparison",
        "/unit.c: In function 'f':",
        "/unit.c: At global scope:",
        "/header.h:4,",
        "/odd:name.h:3: note: here",
        "/unit.c(2,4): warning C4018: comparison",
        "/unit.c(2): warning C4018: comparison",
        "/unit.c(2,4): comparison",
        "/unit.c(2): comparison",
    ] {
        assert!(location_suffix(suffix), "{suffix}");
    }
    for suffix in [
        "/raw-string-contents",
        "/filename:nondiagnostic-text",
        "/unit.c(foo): text",
        "/unit.c(2,4) text",
        "/unit.c(): text",
        "/unit.c(,): text",
        "/unit.c(2,a): text",
    ] {
        assert!(!location_suffix(suffix), "{suffix}");
    }
}

#[test]
fn root_priority_then_specificity_and_designated_consumer_spelling_are_preserved() {
    let producer = vec![
        ("/work/A".into(), "checkout".into(), 5),
        ("/work/A/nested".into(), "nested".into(), 5),
        ("/work/A/nested/higher".into(), "checkout".into(), 1),
    ];
    let consumer = vec![
        ("/consumer/nested/".into(), "nested".into(), 5),
        ("/canonical/nested".into(), "nested".into(), 5),
        ("/consumer/B".into(), "checkout".into(), 5),
    ];
    let stored = store(
        "/work/A/nested/higher/a.h:2: warning: nested\n/work/A/b.c:3: warning: checkout\n",
        &producer,
    );
    assert_eq!(
        restore(&stored, &consumer).unwrap(),
        "/consumer/nested/higher/a.h:2: warning: nested\n/consumer/B/b.c:3: warning: checkout\n"
    );
}

#[test]
fn json_file_locations_round_trip_escaped_windows_paths_and_keep_messages() {
    let producer = roots(r#"C:\producer\a"quoted"#);
    let consumer = roots(r#"D:\consumer\b"quoted"#);
    let value = serde_json::json!([{
        "locations": [{"caret": {"file": "C:\\producer\\a\"quoted\\unit.c", "line": 2}}],
        "message": "C:\\producer\\a\"quoted\\message remains literal"
    }]);
    let original = serde_json::to_string_pretty(&value).unwrap();
    let stored = store(&original, &producer);
    assert!(stored.starts_with(PREFIX));
    let restored = restore(&stored, &consumer).unwrap();
    let restored: serde_json::Value = serde_json::from_str(&restored).unwrap();
    assert_eq!(
        restored[0]["locations"][0]["caret"]["file"],
        "D:\\consumer\\b\"quoted\\unit.c"
    );
    assert_eq!(restored[0]["message"], value[0]["message"]);
    assert_eq!(restore(&stored, &producer).unwrap(), original);
}

#[test]
fn many_unicode_locations_keep_their_order_and_exact_text() {
    let text = (0..1000)
        .map(|line| format!("/生产/é/unit_{line}.c:{line}: warning\r\n"))
        .collect::<String>();
    let stored = store(&text, &roots("/生产/é"));
    assert_eq!(
        restore(&stored, &roots("/消费/β")).unwrap(),
        text.replace("/生产/é", "/消费/β")
    );
    assert_eq!(restore(&stored, &roots("/生产/é")).unwrap(), text);
}

#[test]
fn unchanged_diagnostics_stay_borrowed_and_literal_record_markers_are_escaped() {
    for text in ["", "warning: no path\n", " /work/AB/a.c:1: warning\n"] {
        assert!(matches!(store(text, &roots("/work/A")), Cow::Borrowed(_)));
        assert!(matches!(restore(text, &[]).unwrap(), Cow::Borrowed(_)));
    }
    let literal = format!("{PREFIX}not a record, just compiler text");
    let stored = store(&literal, &[]);
    assert_eq!(restore(&stored, &[]).unwrap(), literal);
    assert!(matches!(
        store("/a.c:1: warning\n", &roots("/")),
        Cow::Borrowed(_)
    ));
}

#[test]
fn malformed_ranges_and_missing_roots_are_rejected_without_panics() {
    assert!(restore(&format!("{PREFIX}bad json"), &[]).is_err());
    let encoded = |text: &str, paths: Vec<Location>| {
        format!(
            "{PREFIX}{}",
            serde_json::to_string(&Record {
                text: text.into(),
                paths
            })
            .unwrap()
        )
    };
    let location = |start, end, root: &str| Location {
        start,
        end,
        root: root.into(),
        json: false,
    };
    for paths in [
        vec![location(0, 0, "checkout")],
        vec![location(2, 1, "checkout")],
        vec![location(100, 101, "checkout")],
        vec![location(0, 100, "checkout")],
        vec![location(1, 3, "checkout")],
        vec![location(0, 1, "checkout")],
        vec![location(0, 2, "checkout"), location(0, 2, "checkout")],
        vec![location(0, 2, "absent")],
    ] {
        assert!(restore(&encoded("é/x", paths), &roots("/B")).is_err());
    }
    let stored = store("/A/a.c:1: warning\n", &roots("/A"));
    assert!(restore(&stored, &[]).is_err());
    assert!(restore(&stored, &roots("/")).is_err());
}

#[test]
fn json_location_prefixes_and_color_sequences_are_recognized_conservatively() {
    assert!(json_file_prefix("{\"file\": \""));
    assert!(json_file_prefix("{\"file\" :\""));
    for prefix in ["", "{\"message\": \"", "{\"file\" \"", "{\"file\": "] {
        assert!(!json_file_prefix(prefix));
    }
    assert_eq!(
        strip_sgr("In file \x1b[1mincluded\x1b[0m from "),
        "In file included from "
    );
    assert_eq!(strip_sgr("plain"), "plain");
    assert_eq!(strip_sgr("\x1b[unterminated"), "\x1b[unterminated");
    assert_eq!(strip_sgr("\x1b[1;38:2mfrom \x1b[m"), "from ");
    assert_eq!(strip_sgr("\x1b[123;"), "\x1b[123;");
}

#[cfg(unix)]
#[test]
fn non_utf8_roots_do_not_guess_a_path_spelling() {
    use std::{ffi::OsString, os::unix::ffi::OsStringExt};
    let opaque_roots = vec![(
        PathBuf::from(OsString::from_vec(b"/A/\xff".to_vec())),
        "checkout".into(),
        5,
    )];
    assert!(matches!(
        store("/A/�/a.c:1: warning\n", &opaque_roots),
        Cow::Borrowed(_)
    ));
    let stored = store("/A/a.c:1: warning\n", &roots("/A"));
    assert!(restore(&stored, &opaque_roots).is_err());
}