Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-schema/tests/schema_and_moniker.rs

//! mimir-schema unit tests: schemas, vocabularies, and BYTE-IDENTITY of the moved
//! moniker formatter against the pre-move `nornir::coderoom` code.

use mimir_schema::moniker::{self, Suffix};
use mimir_schema::*;

/// VERBATIM copy of the pre-move formatter in `nornir/src/coderoom.rs`
/// (origin/main c18e6d1, lines 516–556) — the ORACLE. The moved code must
/// produce the same bytes for every (kind, name, scope) it ever saw.
mod oracle {
    pub fn scip_descriptor(kind: &str, name: &str) -> Option<String> {
        let n = escape_scip_name(name);
        Some(match kind {
            "mod" => format!("{n}/"),
            "struct" | "enum" | "trait" | "type" => format!("{n}#"),
            "fn" | "method" => format!("{n}()."),
            "const" | "static" => format!("{n}."),
            "macro" => format!("{n}!"),
            _ => return None,
        })
    }

    pub fn escape_scip_name(name: &str) -> String {
        if name
            .chars()
            .all(|c| c == '_' || c == '+' || c == '-' || c == '$' || c.is_ascii_alphanumeric())
        {
            name.to_string()
        } else {
            format!("`{}`", name.replace('`', "``"))
        }
    }

    pub fn scip_moniker(krate: &str, version: &str, kind: &str, scope: &str, name: &str) -> Option<String> {
        let desc = scip_descriptor(kind, name)?;
        Some(format!("rust-analyzer cargo {krate} {version} {}{desc}", scope))
    }
}

/// What `coderoom::scip_moniker` now does (scope + one descriptor via mimir-schema).
fn moved(krate: &str, version: &str, kind: &str, scope: &str, name: &str) -> Option<String> {
    let desc = moniker::descriptor_for_kind(kind, name)?;
    Some(moniker::moniker(krate, version, &format!("{scope}{desc}")))
}

const KINDS: &[&str] = &[
    "fn", "method", "struct", "enum", "trait", "type", "const", "static", "macro", "mod", "impl",
    "union", "", "variant",
];
const NAMES: &[&str] = &[
    "foo", "Foo", "r#type", "with space", "back`tick", "dash-ok", "plus+ok", "$dollar", "ünï",
    "", "0", "a::b", "`", "``x``", "_", "Self",
];
const SCOPES: &[&str] = &["", "m/", "m/B#", "a/b/c/", "`weird name`/T#", "Trait#"];

#[test]
fn moved_formatter_is_byte_identical_to_coderoom() {
    let mut checked = 0;
    for k in KINDS {
        for n in NAMES {
            for s in SCOPES {
                for (krate, ver) in [("a", "1.0.0"), ("serde_json", "1.0.145"), ("x", "?")] {
                    assert_eq!(
                        moved(krate, ver, k, s, n),
                        oracle::scip_moniker(krate, ver, k, s, n),
                        "kind={k:?} name={n:?} scope={s:?}"
                    );
                    checked += 1;
                }
            }
        }
        for n in NAMES {
            assert_eq!(moniker::escape_name(n), oracle::escape_scip_name(n));
        }
    }
    assert_eq!(checked, KINDS.len() * NAMES.len() * SCOPES.len() * 3);
}

#[test]
fn coderoom_fixture_monikers_unchanged() {
    // The literal strings coderoom's own tests pin (coderoom.rs tests, c18e6d1).
    assert_eq!(
        moved("a", "1.0.0", "fn", "", "foo").unwrap(),
        "rust-analyzer cargo a 1.0.0 foo()."
    );
    assert_eq!(
        moved("a", "1.0.0", "method", "m/B#", "go").unwrap(),
        "rust-analyzer cargo a 1.0.0 m/B#go()."
    );
    // coderoom's inherent-impl scope is now rust-analyzer's `impl#[B]`
    assert_eq!(
        moved("a", "1.0.0", "method", &format!("m/{}", moniker::impl_descriptor("B", None)), "go").unwrap(),
        "rust-analyzer cargo a 1.0.0 m/impl#[B]go()."
    );
    assert_eq!(moved("a", "1.0.0", "mod", "", "m").unwrap(), "rust-analyzer cargo a 1.0.0 m/");
}

#[test]
fn every_suffix_renders_the_scip_grammar() {
    let cases = [
        (Suffix::Namespace, "m/"),
        (Suffix::Type, "m#"),
        (Suffix::Term, "m."),
        (Suffix::Method, "m()."),
        (Suffix::TypeParameter, "[m]"),
        (Suffix::Parameter, "(m)"),
        (Suffix::Meta, "m:"),
        (Suffix::Macro, "m!"),
    ];
    for (s, want) in cases {
        assert_eq!(moniker::descriptor(s, "m"), want);
    }
    assert_eq!(moniker::descriptor(Suffix::TypeParameter, "Vec<T>"), "[`Vec<T>`]");
}

#[test]
fn parse_round_trips() {
    let m = moniker::moniker("skade", "0.3.1", "store/Table#open().");
    assert_eq!(m, "rust-analyzer cargo skade 0.3.1 store/Table#open().");
    assert_eq!(moniker::parse(&m), Some(("skade", "0.3.1", "store/Table#open().")));
    assert_eq!(
        moniker::parse(&moniker::crate_moniker("a", "?")),
        Some(("a", "?", "crate/"))
    );
    assert_eq!(moniker::parse("scip-java maven x 1 a#"), None);
}

#[test]
fn vocabularies_round_trip() {
    for k in EdgeKind::ALL {
        assert_eq!(EdgeKind::parse(k.as_str()), Some(*k));
    }
    assert_eq!(EdgeKind::ALL.len(), 18);
    assert_eq!(EdgeKind::CfgSucc.as_str(), "CFG_SUCC");
    assert_eq!(EdgeKind::InstanceOf.as_str(), "INSTANCE_OF");
    for l in Level::ALL {
        assert_eq!(Level::parse(l.as_str()), Some(*l));
    }
    for o in Origin::ALL {
        assert_eq!(Origin::parse(o.as_str()), Some(*o));
    }
    for p in Provenance::ALL {
        assert_eq!(Provenance::parse(p.as_str()), Some(*p));
    }
    assert_eq!(Provenance::SourceOnly.as_str(), "source_only");
}

#[test]
fn schemas_have_the_plan_columns() {
    let n = nodes_schema();
    for c in [
        "id", "kind", "level", "parent", "repo", "crate", "version", "file", "start_line",
        "end_line", "cfg", "origin", "provenance", "valid_from_sha", "valid_to_sha",
    ] {
        assert!(n.field_with_name(c).is_ok(), "code_nodes lacks {c}");
    }
    let e = edges_schema();
    for c in ["from", "to", "kind", "count", "provenance", "valid_from_sha", "valid_to_sha"] {
        assert!(e.field_with_name(c).is_ok(), "code_edges lacks {c}");
    }
    // Positions are not edge identity: no file / line on an edge.
    assert!(e.field_with_name("line").is_err() && e.field_with_name("file").is_err());
    let s = spans_schema();
    for c in ["target", "key", "file", "start_line", "end_line"] {
        assert!(s.field_with_name(c).is_ok(), "code_spans lacks {c}");
    }
    assert_eq!(n.metadata().get(META_TABLE).map(String::as_str), Some("code_nodes"));
    assert_eq!(e.metadata().get(META_TABLE).map(String::as_str), Some("code_edges"));
}

#[test]
fn batches_build() {
    let node = NodeRow {
        id: moniker::crate_moniker("a", "0.1.0"),
        kind: kind::CRATE.into(),
        level: Level::Crate,
        name: "a".into(),
        parent: None,
        repo: Some("fixture".into()),
        krate: "a".into(),
        version: "0.1.0".into(),
        file: Some("a/src/lib.rs".into()),
        start_line: Some(1),
        end_line: Some(9),
        cfg: None,
        origin: Origin::Local,
        provenance: Provenance::SourceOnly,
        valid_from_sha: Some("deadbeef".into()),
        valid_to_sha: None,
        ty: None,
        is_test: false,
        defined_here: true,
        visibility: Some(visibility::PUB.into()),
        effective_visibility: Some(EffectiveVisibility::Direct),
        doc: Some("fixture crate a".into()),
        has_doc: Some(true),
        ignored: None,
        doc_hidden: None,
        link_name: None,
    };
    let b = nodes_batch(&[node.clone(), node]).unwrap();
    assert_eq!(b.num_rows(), 2);
    assert_eq!(b.schema(), nodes_schema());
    let e = edges_batch(&[EdgeRow {
        from: "x".into(),
        to: "y".into(),
        kind: EdgeKind::Calls,
        count: 2,
        provenance: Provenance::SourceOnly,
        valid_from_sha: None,
        valid_to_sha: None,
        negated: None,
        partial: None,
        access: None,
    }])
    .unwrap();
    assert_eq!(e.num_rows(), 1);
    assert_eq!(e.schema(), edges_schema());
    let sp = spans_batch(&[SpanRow {
        target: SPAN_EDGE,
        key: edge_key("x", "y", EdgeKind::Calls),
        file: "a/src/lib.rs".into(),
        start_line: 3,
        end_line: 3,
    }])
    .unwrap();
    assert_eq!(sp.schema(), spans_schema());
    assert!(!edge_key("x", "y", EdgeKind::Calls).contains('\u{1f}'), "edge keys must not use the log's key separator");
}

/// BYTE-IDENTITY with rust-analyzer: symbols copied verbatim from
/// `rust-analyzer scip` (rust-analyzer 1.101.0-nightly 2822155 2026-10-04) run
/// over crates/mimir-driver/tests/fixture, rebuilt here through the formatter.
#[test]
fn formatter_reproduces_rust_analyzer_scip_symbols() {
    let a = |d: &str| moniker::moniker("a", "0.1.0", d);
    let b = |d: &str| moniker::moniker("b", "0.2.0", d);
    let core = |d: &str| moniker::moniker("core", &moniker::lang_version("core"), d);
    let alloc = |d: &str| moniker::moniker("alloc", &moniker::lang_version("alloc"), d);
    let cases: Vec<(String, &str)> = vec![
        (a(moniker::CRATE_ROOT_DESCRIPTOR), "rust-analyzer cargo a 0.1.0 crate/"),
        (a(&format!("{}{}", moniker::impl_descriptor("Point", None), moniker::descriptor(Suffix::Method, "new"))),
            "rust-analyzer cargo a 0.1.0 impl#[Point]new()."),
        (b(&format!("{}{}", moniker::impl_descriptor("Dog", Some("Greet")), moniker::descriptor(Suffix::Method, "greet"))),
            "rust-analyzer cargo b 0.2.0 impl#[Dog][Greet]greet()."),
        (a(&format!("{}{}", moniker::descriptor(Suffix::Type, "Point"), moniker::descriptor(Suffix::Term, "x"))),
            "rust-analyzer cargo a 0.1.0 Point#x."),
        (a(&format!("{}{}", moniker::descriptor(Suffix::Type, "Greet"), moniker::descriptor(Suffix::Method, "greet"))),
            "rust-analyzer cargo a 0.1.0 Greet#greet()."),
        (a(&moniker::descriptor(Suffix::Macro, "twice")), "rust-analyzer cargo a 0.1.0 twice!"),
        (a(&format!("{}{}", moniker::descriptor(Suffix::Namespace, "tests"), moniker::descriptor(Suffix::Method, "adds"))),
            "rust-analyzer cargo a 0.1.0 tests/adds()."),
        (core(&format!("ops/arith/{}{}", moniker::impl_descriptor("i32", Some("Add<Self>")), moniker::descriptor(Suffix::Method, "add"))),
            "rust-analyzer cargo core https://github.com/rust-lang/rust/library/core ops/arith/impl#[i32][`Add<Self>`]add()."),
        (core(&format!("convert/{}{}", moniker::impl_descriptor("T", Some("Into<U>")), moniker::descriptor(Suffix::Method, "into"))),
            "rust-analyzer cargo core https://github.com/rust-lang/rust/library/core convert/impl#[T][`Into<U>`]into()."),
        (core(&format!("fmt/macros/{}", moniker::descriptor(Suffix::Meta, "Debug"))),
            "rust-analyzer cargo core https://github.com/rust-lang/rust/library/core fmt/macros/Debug:"),
        (alloc(&format!("string/{}{}", moniker::impl_descriptor("String", None), moniker::descriptor(Suffix::Method, "len"))),
            "rust-analyzer cargo alloc https://github.com/rust-lang/rust/library/alloc string/impl#[String]len()."),
    ];
    for (ours, ra) in cases {
        assert_eq!(ours, ra);
    }
}

/// The binary layer's table: linker facts keyed on the node moniker.
#[test]
fn symbols_table_and_mono_instance_ids() {
    let s = symbols_schema();
    for c in ["id", "symbol", "demangled", "role", "crate", "version", "binary", "section", "addr", "size", "provenance"] {
        assert!(s.field_with_name(c).is_ok(), "code_symbols lacks {c}");
    }
    assert_eq!(s.field_with_name("addr").unwrap().data_type(), &arrow_schema::DataType::UInt64);
    assert_eq!(s.metadata().get(META_TABLE).map(String::as_str), Some("code_symbols"));
    let row = SymbolRow {
        id: moniker::moniker("a", "0.1.0", "add()."),
        symbol: "_RNvCs1234_1a3add".into(),
        demangled: Some("a::add".into()),
        role: role::ITEM.into(),
        krate: "a".into(),
        version: "0.1.0".into(),
        binary: Some("bctl".into()),
        section: Some(".text".into()),
        addr: Some(0x1000),
        size: Some(42),
        provenance: Provenance::Both,
        valid_from_sha: None,
        valid_to_sha: None,
    };
    let driver_row = SymbolRow { binary: None, section: None, addr: None, size: None, provenance: Provenance::SourceOnly, ..row.clone() };
    let b = symbols_batch(&[row, driver_row]).unwrap();
    assert_eq!(b.num_rows(), 2);
    assert_eq!(b.schema(), symbols_schema());

    // a mono instance id: the generic item's moniker + a meta descriptor
    let g = moniker::moniker("a", "0.1.0", "ident().");
    let m = moniker::mono_instance(&g, "a::ident::<u32>");
    assert_eq!(m, "rust-analyzer cargo a 0.1.0 ident().`a::ident::<u32>`:");
    assert!(m.starts_with(&g));
    assert_eq!(moniker::parse(&m), Some(("a", "0.1.0", "ident().`a::ident::<u32>`:")));
}

#[test]
fn provenance_is_derived_from_the_two_streams() {
    use mimir_schema::{derive_provenance, BinaryFacts, Provenance};
    let none = BinaryFacts::default();
    let sym = BinaryFacts { has_symbol: true, ..none };
    let inst = BinaryFacts { instances_linked: true, ..none };
    let node = BinaryFacts { binary_node: true, ..none };
    assert_eq!(derive_provenance(true, none), Some(Provenance::SourceOnly));
    assert_eq!(derive_provenance(true, sym), Some(Provenance::Both));
    assert_eq!(derive_provenance(true, inst), Some(Provenance::Both), "generic item with a linked instance");
    assert_eq!(derive_provenance(true, node), Some(Provenance::Both));
    assert_eq!(derive_provenance(false, node), Some(Provenance::BinaryOnly));
    assert_eq!(derive_provenance(false, sym), Some(Provenance::BinaryOnly));
    assert_eq!(derive_provenance(false, none), None);
}

// ---------------------------------------------------------------- schema 2

#[test]
fn schema_2_columns_are_additive_and_nullable() {
    let n = nodes_schema();
    // appended AFTER the schema-1 columns, in this order, all nullable
    let names: Vec<&str> = n.fields().iter().map(|f| f.name().as_str()).collect();
    let at = names.iter().position(|c| *c == "defined_here").unwrap();
    assert_eq!(&names[at + 1..at + 5], ["visibility", "effective_visibility", "doc", "has_doc"]);
    for c in ["visibility", "effective_visibility", "doc", "has_doc"] {
        assert!(n.field_with_name(c).unwrap().is_nullable(), "{c} must be nullable (reference stubs know none of it)");
    }
    assert_eq!(n.field_with_name("has_doc").unwrap().data_type(), &arrow_schema::DataType::Boolean);
    let e = edges_schema();
    let enames: Vec<&str> = e.fields().iter().map(|f| f.name().as_str()).collect();
    let at = enames.iter().position(|c| *c == "valid_to_sha").unwrap();
    assert_eq!(&enames[at + 1..at + 3], ["negated", "partial"]);
    assert!(e.field_with_name("negated").unwrap().is_nullable() && e.field_with_name("partial").unwrap().is_nullable());
}

// ---------------------------------------------------------------- schema 3

/// Schema 3 only ADDS, at the end, nullable: that is what lets `mimir-ingest`
/// evolve a schema-2 lake in place instead of refusing it.
#[test]
fn schema_3_columns_are_appended_and_nullable() {
    let n = nodes_schema();
    let names: Vec<&str> = n.fields().iter().map(|f| f.name().as_str()).collect();
    assert_eq!(&names[names.len() - 4..names.len() - 1], ["has_doc", "ignored", "doc_hidden"]);
    for c in ["ignored", "doc_hidden"] {
        let f = n.field_with_name(c).unwrap();
        assert!(f.is_nullable() && f.data_type() == &arrow_schema::DataType::Boolean, "{c}");
    }
    // three values each: yes, no, and "no such notion here" (null)
    use arrow_array::cast::AsArray;
    use arrow_array::Array;
    let mut heavy = NodeRow::new("k slow().", kind::FN, Level::Item, "slow", "k", "0.1.0", Origin::Local);
    heavy.is_test = true;
    heavy.ignored = Some(true);
    heavy.doc_hidden = Some(false);
    let plain = NodeRow::new("k f().", kind::FN, Level::Item, "f", "k", "0.1.0", Origin::Local);
    let b = nodes_batch(&[heavy, plain]).unwrap();
    let (ignored, hidden) = (b.column_by_name("ignored").unwrap().as_boolean(), b.column_by_name("doc_hidden").unwrap().as_boolean());
    assert!(ignored.value(0) && !hidden.value(0) && !hidden.is_null(0));
    assert!(ignored.is_null(1) && hidden.is_null(1));
}

#[test]
fn schema_2_values_reach_the_batch() {
    use arrow_array::cast::AsArray;
    use arrow_array::Array;
    let mut documented = NodeRow::new("k f().", kind::FN, Level::Item, "f", "k", "0.1.0", Origin::Local);
    documented.visibility = Some(visibility::pub_in("a::b"));
    documented.effective_visibility = Some(EffectiveVisibility::Reexported);
    documented.doc = doc_summary(" Opens the store.\n\n Long text.");
    documented.has_doc = Some(true);
    let mut stub = NodeRow::new("dep g().", kind::FN, Level::Item, "g", "dep", "1.0.0", Origin::Library);
    stub.defined_here = false;
    let b = nodes_batch(&[documented, stub]).unwrap();
    let col = |c: &str| b.column_by_name(c).unwrap().clone();
    assert_eq!(col("visibility").as_string::<i32>().value(0), "pub(in crate::a::b)");
    assert_eq!(col("effective_visibility").as_string::<i32>().value(0), "reexported");
    assert_eq!(col("doc").as_string::<i32>().value(0), "Opens the store.");
    assert!(col("has_doc").as_boolean().value(0));
    // a reference stub knows none of it: NULL, not `false` / ""
    for c in ["visibility", "effective_visibility", "doc", "has_doc"] {
        assert!(col(c).is_null(1), "{c} of a reference stub must be null");
    }

    let mut gate = EdgeRow::new("k f().", moniker::feature("k", "0.1.0", "s3"), EdgeKind::GatedBy);
    gate.negated = Some(true);
    gate.partial = Some(false);
    let e = edges_batch(&[gate, EdgeRow::new("a", "b", EdgeKind::Calls)]).unwrap();
    let neg = e.column_by_name("negated").unwrap().as_boolean().clone();
    assert!(neg.value(0) && neg.is_null(1), "negated is set on GATED_BY, null on every other kind");
    let part = e.column_by_name("partial").unwrap().as_boolean().clone();
    assert!(!part.is_null(0) && !part.value(0) && part.is_null(1), "partial likewise");
}

#[test]
fn schema_2_vocabularies_round_trip() {
    for v in EffectiveVisibility::ALL {
        assert_eq!(EffectiveVisibility::parse(v.as_str()), Some(*v));
    }
    assert_eq!(
        EffectiveVisibility::ALL.iter().filter(|v| v.is_public_api()).map(|v| v.as_str()).collect::<Vec<_>>(),
        ["direct", "reexported"]
    );
    assert_eq!(EdgeKind::parse("ENABLES"), Some(EdgeKind::Enables));
    assert_eq!(EdgeKind::parse("ENABLES_WEAK"), Some(EdgeKind::EnablesWeak));
    assert_eq!(EdgeKind::GatedBy.as_str(), "GATED_BY");
    // appended: the kinds that existed keep their position (mimir-room keys a
    // bit mask on it), and the whole set still fits its u16
    assert_eq!(EdgeKind::ALL.iter().position(|k| *k == EdgeKind::InstanceOf), Some(12));
    assert_eq!(&EdgeKind::ALL[13..15], [EdgeKind::Enables, EdgeKind::EnablesWeak]);
    // builder 2's BUILT_FROM (artifact → crate / feature / commit) is the 16th:
    // the masks are full now
    assert_eq!(EdgeKind::ALL[15], EdgeKind::BuiltFrom);
    assert_eq!(EdgeKind::parse("BUILT_FROM"), Some(EdgeKind::BuiltFrom));
    assert_eq!(moniker::parse(&moniker::artifact("ab12")), Some((moniker::ARTIFACT_PACKAGE, ".", "`artifact=ab12`:")));
    assert_eq!(moniker::parse(&moniker::commit("edda", "284d87b2")), Some((moniker::COMMIT_PACKAGE, ".", "`commit=edda@284d87b2`:")));
    // the system boundary's SYSCALLS is the 17th: past a u16 kind mask
    // (mimir-room keys its masks on u32 since)
    assert_eq!(EdgeKind::ALL[16], EdgeKind::Syscalls);
    assert_eq!(EdgeKind::parse("SYSCALLS"), Some(EdgeKind::Syscalls));
    assert!(EdgeKind::ALL.len() <= 32);
    assert_eq!((visibility::PUB, visibility::PUB_CRATE, visibility::PRIVATE), ("pub", "pub(crate)", "private"));
    assert_eq!((kind::FEATURE, kind::CFG_GATED_OUT), ("feature", "cfg_gated_out"));
}

#[test]
fn doc_summary_is_the_first_non_blank_line() {
    assert_eq!(doc_summary(" libc `abs` — the extern \"C\" border."), Some("libc `abs` — the extern \"C\" border.".into()));
    assert_eq!(doc_summary("\n\n  second line is first\n third"), Some("second line is first".into()));
    // a block comment's gutter
    assert_eq!(doc_summary("*\n * Opens it.\n * More.\n "), Some("Opens it.".into()));
    // markdown emphasis at the start of a line is not a gutter
    assert_eq!(doc_summary(" *never* inlined"), Some("*never* inlined".into()));
    assert_eq!(doc_summary("   \n\t\n"), None);
    let long = "å".repeat(DOC_SUMMARY_MAX + 50);
    let cut = doc_summary(&long).unwrap();
    assert_eq!(cut.chars().count(), DOC_SUMMARY_MAX);
    assert!(cut.ends_with('…'));
}

/// Two targets of one package never share an id: the lib has rust-analyzer's
/// ids, every other target is prefixed with its kind and crate name.
#[test]
fn targets_of_one_package_have_ids_of_their_own() {
    assert_eq!(moniker::target_prefix("lib", "lgz"), "");
    assert_eq!(moniker::target_prefix("proc-macro", "lgz_derive"), "");
    assert_eq!(moniker::target_prefix("bin", "lgz"), "`bin=lgz`:");
    assert_eq!(moniker::target_prefix("test", "deflate_scan"), "`test=deflate_scan`:");
    assert_eq!(moniker::target_prefix("bench", "deflate_scan"), "`bench=deflate_scan`:");
    // the default layout: `src/lib.rs` + `src/main.rs`, both crates called `lgz`
    let id = |kind: &str, d: &str| moniker::moniker("lgz", "0.3.14", &format!("{}{d}", moniker::target_prefix(kind, "lgz")));
    let all: Vec<String> = ["lib", "bin", "test", "bench", "example"].iter().flat_map(|k| [id(k, "crate/"), id(k, "main().")]).collect();
    assert_eq!(all.iter().collect::<std::collections::BTreeSet<_>>().len(), all.len(), "{all:#?}");
    assert_eq!(id("lib", "crate/"), moniker::crate_moniker("lgz", "0.3.14"), "the lib keeps rust-analyzer's id");
    // parseable, and never an item's own descriptor (a macro named `bin`, a module named `test`)
    assert_eq!(moniker::parse(&id("bin", "main().")), Some(("lgz", "0.3.14", "`bin=lgz`:main().")));
    assert_ne!(id("bin", "crate/"), moniker::moniker("lgz", "0.3.14", "bin:lgz:crate/"));
    assert_ne!(moniker::target_prefix("test", "x"), moniker::descriptor(Suffix::Meta, "test"));
}

#[test]
fn feature_and_gated_out_monikers() {
    let f = moniker::feature("a", "0.1.0", "extra");
    assert_eq!(f, "rust-analyzer cargo a 0.1.0 `feature=extra`:");
    // never the id of an item: a module / derive macro named `feature` or
    // `extra` formats differently
    assert_ne!(f, moniker::moniker("a", "0.1.0", &moniker::descriptor(Suffix::Meta, "extra")));
    assert_ne!(f, moniker::moniker("a", "0.1.0", "feature/extra:"));
    assert_eq!(moniker::parse(&f), Some(("a", "0.1.0", "`feature=extra`:")));
    // one node per crate@version
    assert_ne!(f, moniker::feature("a", "0.0.9", "extra"));
    assert_eq!(moniker::feature("tokio", "1.40.0", "rt-multi-thread"), "rust-analyzer cargo tokio 1.40.0 `feature=rt-multi-thread`:");

    // the scope a node's children are named in
    assert_eq!(moniker::child_scope("rust-analyzer cargo a 0.1.0 crate/"), "rust-analyzer cargo a 0.1.0 ");
    assert_eq!(moniker::child_scope("rust-analyzer cargo b 0.2.0 `bin=bctl`:crate/"), "rust-analyzer cargo b 0.2.0 `bin=bctl`:");
    assert_eq!(moniker::child_scope("rust-analyzer cargo a 0.1.0 store/"), "rust-analyzer cargo a 0.1.0 store/");
    assert_eq!(moniker::child_scope("rust-analyzer cargo a 0.1.0 impl#[Point]"), "rust-analyzer cargo a 0.1.0 impl#[Point]");

    let scope = moniker::child_scope("rust-analyzer cargo a 0.1.0 crate/");
    let on = moniker::cfg_gated_out(scope, &moniker::descriptor(Suffix::Method, "f"), "cfg(feature = \"x\")");
    let off = moniker::cfg_gated_out(scope, &moniker::descriptor(Suffix::Method, "f"), "cfg(not(feature = \"x\"))");
    assert_eq!(on, "rust-analyzer cargo a 0.1.0 f().`cfg(feature = \"x\")`:");
    // the usual on/off pair is two ids, and neither is the resolved item's id
    assert_ne!(on, off);
    assert_ne!(on, "rust-analyzer cargo a 0.1.0 f().");
    assert!(on.starts_with("rust-analyzer cargo a 0.1.0 f()."));
    assert_eq!(moniker::parse(&on).map(|p| (p.0, p.1)), Some(("a", "0.1.0")));
    // a backtick in the predicate is escaped, the id stays one descriptor
    let odd = moniker::cfg_gated_out(scope, "g().", "cfg(feature = \"a`b\")");
    assert!(odd.ends_with("`cfg(feature = \"a``b\")`:"));
}

// ---------------------------------------------------------------- schema 4

/// Schema 4 ADDS one nullable column at the end of `code_edges` (`access`),
/// one at the end of `code_nodes` (`link_name`, its own test below) and two
/// values (node kind `table`, the table id): additions only, which is what
/// lets `mimir-ingest` evolve a schema-3 lake in place.
#[test]
fn schema_4_adds_access_at_the_end_of_edges() {
    assert_eq!(SCHEMA_VERSION, "5"); // 5 changed identities (moniker::stable), no column
    let e = edges_schema();
    assert_eq!(e.metadata().get(META_SCHEMA).map(String::as_str), Some("5"));
    let names: Vec<&str> = e.fields().iter().map(|f| f.name().as_str()).collect();
    assert_eq!(names, ["from", "to", "kind", "count", "provenance", "valid_from_sha", "valid_to_sha", "negated", "partial", "access"]);
    let f = e.field_with_name("access").unwrap();
    assert!(f.is_nullable() && f.data_type() == &arrow_schema::DataType::Utf8);
    // nodes gained only `link_name` (schema 4 too); spans did not move
    let n = nodes_schema();
    assert_eq!(n.fields().len(), 26);
    assert_eq!(n.fields()[24].name(), "doc_hidden");
    assert_eq!(n.fields().last().unwrap().name(), "link_name");
    assert_eq!(spans_schema().fields().len(), 5);

    // set on TOUCHES_TABLE, null on every other kind
    use arrow_array::cast::AsArray;
    use arrow_array::Array;
    let mut touch = EdgeRow::new("k f().", moniker::table("bench_runs"), EdgeKind::TouchesTable);
    touch.access = Some(Access::Both);
    let b = edges_batch(&[touch, EdgeRow::new("a", "b", EdgeKind::Calls)]).unwrap();
    let a = b.column_by_name("access").unwrap().as_string::<i32>().clone();
    assert_eq!(a.value(0), "both");
    assert!(a.is_null(1));
}

#[test]
fn access_is_a_union_of_two_bits() {
    for a in Access::ALL {
        assert_eq!(Access::parse(a.as_str()), Some(*a));
        assert_eq!(Access::from_bits(a.bits()), Some(*a));
    }
    assert_eq!(Access::from_bits(Access::READ | Access::WRITE), Some(Access::Both));
    assert_eq!(Access::from_bits(0), None);
    assert!(Access::Read.reads() && !Access::Read.writes());
    assert!(Access::Write.writes() && !Access::Write.reads());
    assert!(Access::Both.reads() && Access::Both.writes());
}

/// The table id is the table's NAME and nothing else — no crate, no version,
/// no repo — so two crates of two repos that touch one table meet at one
/// node; and it can never be an item's id.
#[test]
fn a_table_id_is_its_name_only() {
    let id = moniker::table("bench_runs");
    assert_eq!(id, "rust-analyzer cargo @warehouse . `table=bench_runs`:");
    assert_eq!(moniker::parse(&id), Some(("@warehouse", ".", "`table=bench_runs`:")));
    assert_eq!(moniker::table_name(&id).as_deref(), Some("bench_runs"));
    // the unresolved node: one id, a name no table can have
    let u = moniker::table(TABLE_UNRESOLVED);
    assert_eq!(moniker::table_name(&u).as_deref(), Some("<unresolved>"));
    assert_ne!(u, id);
    // a name with a backtick survives the round trip (SCIP escaping)
    assert_eq!(moniker::table_name(&moniker::table("a`b")).as_deref(), Some("a`b"));
    // not a table: an item, a feature, another package
    assert_eq!(moniker::table_name("rust-analyzer cargo k 0.1.0 table()."), None);
    assert_eq!(moniker::table_name(&moniker::feature("k", "0.1.0", "table")), None);
    assert_eq!(moniker::table_name("rust-analyzer cargo k 0.1.0 `table=x`:"), None);
    assert_eq!(kind::TABLE, "table");
}

/// Schema 4 appends ONE nullable column, `link_name`: the symbol a foreign
/// item is linked as. Appended and nullable is what lets `mimir-ingest` evolve
/// an older lake in place; null on everything that is not a foreign item.
#[test]
fn schema_4_appends_the_link_name_of_a_foreign_item() {
    use arrow_array::cast::AsArray;
    use arrow_array::Array;
    assert_eq!(SCHEMA_VERSION, "5"); // 5 changed identities (moniker::stable), no column
    let n = nodes_schema();
    assert_eq!(n.metadata().get(META_SCHEMA).map(String::as_str), Some("5"));
    let last = n.fields().last().unwrap();
    assert_eq!((last.name().as_str(), last.is_nullable(), last.data_type()), ("link_name", true, &arrow_schema::DataType::Utf8));
    let mut ffi = NodeRow::new("libc 0.2.1 write().", kind::FFI, Level::Item, "write", "libc", "0.2.1", Origin::Library);
    ffi.link_name = Some("__write_nocancel".into());
    let plain = NodeRow::new("k f().", kind::FN, Level::Item, "f", "k", "0.1.0", Origin::Local);
    let b = nodes_batch(&[ffi, plain]).unwrap();
    let col = b.column_by_name("link_name").unwrap().as_string::<i32>();
    assert_eq!((col.value(0), col.is_null(1)), ("__write_nocancel", true));
}

#[test]
fn stable_strips_only_the_version() {
    let m = moniker::moniker("skade", "0.3.1", "store/Table#open().");
    assert_eq!(moniker::stable(&m), "rust-analyzer cargo skade * store/Table#open().");
    // Already stable: unchanged, and idempotent.
    assert_eq!(moniker::stable(&moniker::stable(&m)), moniker::stable(&m));
    // Two versions of one item collapse to one stable id.
    let a = moniker::moniker("arrow", "59.2.0", "array/RecordBatch#");
    let b = moniker::moniker("arrow", "59.3.1", "array/RecordBatch#");
    assert_eq!(moniker::stable(&a), moniker::stable(&b));
    // Not a cargo moniker: returned byte for byte (tables, artifacts, commits).
    for other in ["mimir table git_heat_facts", "scip-java maven x 1 a#", ""] {
        assert_eq!(moniker::stable(other), other);
    }
    // A descriptor containing a space must not be cut at the wrong field.
    let spaced = moniker::moniker("a", "1.0.0", "m/`fn x`#");
    assert_eq!(moniker::stable(&spaced), "rust-analyzer cargo a * m/`fn x`#");
}

#[test]
fn boundary_ids_carry_no_version_and_parse() {
    let read = moniker::syscall("x86_64", "read");
    assert_eq!(read, "rust-analyzer cargo @syscall . x86_64/read().");
    assert_eq!(moniker::parse(&read), Some((moniker::SYSCALL_PACKAGE, ".", "x86_64/read().")));
    // the run-time-chosen call is one node, its name escaped
    assert_eq!(moniker::syscall("x86_64", "<indirect>"), "rust-analyzer cargo @syscall . x86_64/`<indirect>`().");
    assert_eq!(moniker::syscall_crate(), "rust-analyzer cargo @syscall . crate/");
    assert_eq!(moniker::syscall_arch("x86_64"), "rust-analyzer cargo @syscall . x86_64/");
    assert_eq!(moniker::syscall_map_crate("libc@0.2.177:lib", "gnu"), "rust-analyzer cargo @syscall . `map=libc@0.2.177:lib/gnu`:crate/");
    // the map crate is a crate root: its children start empty
    assert_eq!(moniker::child_scope(&moniker::syscall_map_crate("x@1:lib", "musl")), "rust-analyzer cargo @syscall . `map=x@1:lib/musl`:");
    // kernel items: the release is the version field, so the stable id is release-free
    let ts = moniker::kernel_item("7.0.0-34-generic", "struct", "task_struct", None);
    assert_eq!(ts, "rust-analyzer cargo @kernel 7.0.0-34-generic struct/task_struct#");
    assert_eq!(moniker::stable(&ts), "rust-analyzer cargo @kernel * struct/task_struct#");
    assert_eq!(moniker::kernel_item("6.12", "func", "__x64_sys_read", None), "rust-analyzer cargo @kernel 6.12 func/__x64_sys_read().");
    assert_eq!(moniker::kernel_item("6.12", "struct", "foo", Some(77)), "rust-analyzer cargo @kernel 6.12 struct/foo#`btf=77`:");
    assert_eq!(moniker::kernel_crate("6.12"), "rust-analyzer cargo @kernel 6.12 crate/");
    assert_eq!(moniker::kernel_ns("6.12", "typedef"), "rust-analyzer cargo @kernel 6.12 typedef/");
    assert_eq!(kind::SYSCALL, "syscall");
}