master
John Lauer PCM: the wiki page as a KiCad PCM repository. Sample library package (10 symbols, 10 footprints) under pcm/src, tools/b… 9ea1ed3 1mo ago
//! The sample PCM package under pcm/src/adom-sample-lib (the wiki page acting as a KiCad
//! PCM repository, docs/pcm.md): the ten symbols and ten footprints parse with the same
//! structural checks kicad_lint_library runs, the packed archive under pcm/packages/
//! matches packages.json byte for byte (sha256, size), and the bridge's own zip reader
//! lays it out under the 3rdparty root exactly as PCM's extract() would.

use std::path::{Path, PathBuf};

use kicad_core::lint::{check_footprint_structure, check_symbol_lib_structure, parse_kicad_mod, parse_kicad_sym};
use kicad_core::pcm::{extract, sha256_hex};

fn repo(rel: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("../../..").join(rel)
}

#[test]
fn sample_symbols_and_footprints_parse() {
    let sym_path = repo("pcm/src/adom-sample-lib/symbols/Adom_Sample.kicad_sym");
    let text = std::fs::read_to_string(&sym_path).expect("symbol library readable");
    check_symbol_lib_structure(&text).expect("balanced kicad_symbol_lib");
    let inv = parse_kicad_sym(&text);
    assert_eq!(inv["symbolCount"], 10, "{inv}");
    let names: Vec<&str> = inv["symbols"].as_array().unwrap().iter().map(|s| s["name"].as_str().unwrap()).collect();
    for n in ["AdomSample_R", "AdomSample_C", "AdomSample_L", "AdomSample_D", "AdomSample_LED", "AdomSample_C_Polarized", "AdomSample_D_Zener", "AdomSample_Fuse", "AdomSample_Crystal", "AdomSample_Battery_Cell"] {
        assert!(names.contains(&n), "missing {n} in {names:?}");
    }
    for s in inv["symbols"].as_array().unwrap() {
        assert!(s["pinCount"].as_u64().unwrap() >= 2, "{s}");
    }
    assert_eq!(inv["fileVersion"], 20251024, "KiCad 10 symbol format");
    assert!(!text.contains("(extends"), "every sample symbol is a base symbol");

    let pretty = repo("pcm/src/adom-sample-lib/footprints/Adom_Sample.pretty");
    let mut mods: Vec<PathBuf> = std::fs::read_dir(&pretty).unwrap().flatten().map(|e| e.path()).filter(|p| p.extension().map(|x| x == "kicad_mod").unwrap_or(false)).collect();
    mods.sort();
    assert_eq!(mods.len(), 10);
    for m in &mods {
        let t = std::fs::read_to_string(m).unwrap();
        check_footprint_structure(&t).unwrap_or_else(|e| panic!("{}: {e}", m.display()));
        let p = parse_kicad_mod(&t);
        let stem = m.file_stem().unwrap().to_string_lossy().to_string();
        assert_eq!(p["name"], serde_json::json!(stem), "declared name matches the file stem");
        assert!(stem.starts_with("AdomSample_"));
        assert!(p["padCount"].as_u64().unwrap() >= 2, "{}: {p}", m.display());
    }
}

#[test]
fn packed_archive_matches_packages_json_and_extracts_like_pcm() {
    let packages: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(repo("pcm/packages.json")).unwrap()).unwrap();
    let repository: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(repo("pcm/repository.json")).unwrap()).unwrap();
    // repository.json's digest of packages.json is the digest of the file on disk.
    let packages_bytes = std::fs::read(repo("pcm/packages.json")).unwrap();
    assert_eq!(repository["packages"]["sha256"], serde_json::json!(sha256_hex(&packages_bytes)));
    let pkg = &packages["packages"][0];
    assert_eq!(pkg["identifier"], "com.adom.kicad-bridge.sample-lib");
    let ident = pkg["identifier"].as_str().unwrap();
    for v in pkg["versions"].as_array().unwrap() {
        let url = v["download_url"].as_str().unwrap();
        let file = url.rsplit('/').next().unwrap();
        let zip = std::fs::read(repo("pcm/packages").join(file)).unwrap_or_else(|e| panic!("{file}: {e}"));
        assert_eq!(v["download_size"], serde_json::json!(zip.len()));
        assert_eq!(v["download_sha256"], serde_json::json!(sha256_hex(&zip)));
        let third = std::env::temp_dir().join(format!("kicad-core-pcm-sample-{}-{}", std::process::id(), v["version"].as_str().unwrap()));
        let _ = std::fs::remove_dir_all(&third);
        let e = extract(&zip, &third, ident).expect("the bridge's zip reader handles the packer's deflate output");
        assert_eq!(e.package_dir, "com_adom_kicad-bridge_sample-lib");
        assert_eq!(e.symbols, vec!["Adom_Sample"]);
        assert_eq!(e.footprints, vec!["Adom_Sample"]);
        assert_eq!(e.files, 12, "10 footprints + 1 symbol library + icon; metadata.json is not extracted");
        assert!(third.join("symbols/com_adom_kicad-bridge_sample-lib/Adom_Sample.kicad_sym").is_file());
        assert!(third.join("footprints/com_adom_kicad-bridge_sample-lib/Adom_Sample.pretty/AdomSample_R_0805_2012Metric.kicad_mod").is_file());
        assert!(third.join("resources/com_adom_kicad-bridge_sample-lib/icon.png").is_file());
        let mut total = 0u64;
        for f in walk(&third) {
            total += std::fs::metadata(&f).unwrap().len();
        }
        // install_size counts metadata.json too, which PCM does not extract.
        assert!(total < v["install_size"].as_u64().unwrap() && total > v["install_size"].as_u64().unwrap() - 4000, "{total} vs {}", v["install_size"]);
        let _ = std::fs::remove_dir_all(&third);
    }
}

fn walk(dir: &Path) -> Vec<PathBuf> {
    let mut out = Vec::new();
    for e in std::fs::read_dir(dir).unwrap().flatten() {
        let p = e.path();
        if p.is_dir() {
            out.extend(walk(&p));
        } else {
            out.push(p);
        }
    }
    out
}