candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
use std::ffi::OsString;

use tempfile::tempdir;

use super::*;

#[test]
fn offline_reads_huggingface_hub_s_true_values() {
  for value in ["1", "on", "Yes", "TRUE"] {
    assert!(offline(Some(value)), "{value}");
  }
  for value in [None, Some(""), Some("0"), Some("false"), Some("2")] {
    assert!(!offline(value), "{value:?}");
  }
}

/// The root `vars` give, with `/home/u` for `~`.
fn root_with(vars: &[(&str, &str)]) -> PathBuf {
  let var = |name: &str| {
    let found = vars.iter().find(|(key, _)| *key == name);
    found.map(|(_, value)| OsString::from(value))
  };
  root::resolve(var, Some(Path::new("/home/u")))
}

#[test]
fn the_root_takes_the_first_variable_in_huggingface_hub_s_order() {
  let all = [
    ("HF_HUB_CACHE", "/hub"),
    ("HUGGINGFACE_HUB_CACHE", "/legacy/hub"),
    ("HF_HOME", "/hf"),
    ("XDG_CACHE_HOME", "/xdg"),
  ];
  for (skip, expected) in [
    (0, "/hub"),
    (1, "/legacy/hub"),
    (2, "/hf/hub"),
    (3, "/xdg/huggingface/hub"),
    (4, "/home/u/.cache/huggingface/hub"),
  ] {
    assert_eq!(root_with(&all[skip..]), PathBuf::from(expected), "{skip}");
  }
}

#[test]
fn a_tilde_becomes_the_home_directory_in_every_variable() {
  for name in ["HF_HUB_CACHE", "HUGGINGFACE_HUB_CACHE"] {
    assert_eq!(root_with(&[(name, "~/c")]), PathBuf::from("/home/u/c"));
    assert_eq!(root_with(&[(name, "~")]), PathBuf::from("/home/u"));
  }
  assert_eq!(
    root_with(&[("HF_HOME", "~/hfhome")]),
    PathBuf::from("/home/u/hfhome/hub")
  );
  let xdg = root_with(&[("XDG_CACHE_HOME", "~/xdg")]);
  assert_eq!(xdg, PathBuf::from("/home/u/xdg/huggingface/hub"));
  assert_eq!(
    root_with(&[("HF_HOME", "~user/h")]),
    PathBuf::from("~user/h/hub")
  );
  assert_eq!(root_with(&[("HF_HOME", "a~/h")]), PathBuf::from("a~/h/hub"));
  let at_root = |name: &str| (name == "HF_HUB_CACHE").then(|| OsString::from("~"));
  assert_eq!(
    root::resolve(at_root, Some(Path::new("/"))),
    PathBuf::from("/")
  );
}

#[test]
fn a_variable_in_a_value_is_expanded_and_an_unset_one_stays() {
  let home = ("HOME", "/Users/u");
  let expect = |value: &str, root: &str| {
    let found = root_with(&[home, ("HF_HUB_CACHE", value), ("A_1", "/a")]);
    assert_eq!(found, PathBuf::from(root), "{value}");
  };
  expect("$HOME/hubcache", "/Users/u/hubcache");
  expect("${HOME}/hubcache", "/Users/u/hubcache");
  expect("$A_1/x$A_1", "/a/x/a");
  expect("$NOPE/x", "$NOPE/x");
  expect("${NOPE}/x", "${NOPE}/x");
  expect("$/x/${}/y/${HOME", "$/x/${}/y/${HOME");
  expect("$HOMEdir", "$HOMEdir");
}

/// Python expands `HF_HOME` and then the default path built from it once more; a legacy value only once.
#[test]
fn the_default_path_is_expanded_twice_and_a_value_put_in_is_not_expanded_again() {
  let vars = [("A", "$B"), ("B", "/b")];
  let home = |value| root_with(&[vars[0], vars[1], ("HF_HOME", value)]);
  assert_eq!(home("$A"), PathBuf::from("/b/hub"));
  let legacy = root_with(&[vars[0], vars[1], ("HUGGINGFACE_HUB_CACHE", "$A")]);
  assert_eq!(legacy, PathBuf::from("$B"));
}

#[test]
fn repo_dir_name_replaces_slash() {
  assert_eq!(
    repo_dir_name("BAAI/bge-small-en-v1.5"),
    "models--BAAI--bge-small-en-v1.5"
  );
}

fn fake_repo(cache: &Path, repo_id: &str, hash: &str) -> PathBuf {
  let repo = cache.join(repo_dir_name(repo_id));
  fs::create_dir_all(repo.join("refs")).unwrap();
  fs::write(repo.join("refs").join("main"), format!("{hash}\n")).unwrap();
  let snapshot = repo.join("snapshots").join(hash);
  fs::create_dir_all(&snapshot).unwrap();
  snapshot
}

#[test]
fn resolves_main_through_refs() {
  let cache = tempdir().unwrap();
  let snapshot = fake_repo(cache.path(), "org/model", "abc123");
  assert_eq!(
    snapshot_dir(cache.path(), "org/model", "main"),
    Some(snapshot)
  );
}

#[test]
fn a_bare_hash_resolves_with_no_ref_as_another_tool_leaves_it() {
  let cache = tempdir().unwrap();
  let repo = cache.path().join(repo_dir_name("org/model"));
  let snapshot = repo.join("snapshots").join("abc123");
  fs::create_dir_all(&snapshot).unwrap();
  assert_eq!(
    snapshot_dir(cache.path(), "org/model", "abc123"),
    Some(snapshot)
  );
}

#[test]
fn a_snapshot_marked_in_progress_does_not_resolve_by_commit_or_by_branch() {
  let cache = tempdir().unwrap();
  let snapshot = fake_repo(cache.path(), "org/model", "abc123");
  let marker = in_progress_marker(cache.path(), "org/model", "abc123");
  assert!(marker.starts_with(cache.path().join(".candding")));
  fs::create_dir_all(marker.parent().unwrap()).unwrap();
  fs::write(&marker, "").unwrap();
  assert_eq!(snapshot_dir(cache.path(), "org/model", "abc123"), None);
  assert_eq!(snapshot_dir(cache.path(), "org/model", "main"), None);
  fs::remove_file(&marker).unwrap();
  let found = snapshot_dir(cache.path(), "org/model", "abc123");
  assert_eq!(found, Some(snapshot));
}

#[test]
fn missing_repo_is_none() {
  let cache = tempdir().unwrap();
  assert_eq!(snapshot_dir(cache.path(), "org/missing", "main"), None);
}