use crate::{
GitReference,
LOCK_FILENAME,
Lock,
MANIFEST_FILENAME,
Package,
WORKSPACE_MANIFEST_FILENAME,
git::resolve,
test_util::{
file_url,
fixture_repo,
git_available,
init_repo,
manifest_json,
read_manifest,
run_git,
unique_dir,
write_consumer,
write_file,
write_library,
write_program,
},
};
use leo_span::Symbol;
#[test]
fn resolves_default_branch_tag_and_rev() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("resolve");
let home = base.join("home");
let (url, c1, c2) = fixture_repo(&base);
let (dir, commit) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, None, false).unwrap();
assert_eq!(commit, c2);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "two");
let (dir, commit) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), None, false).unwrap();
assert_eq!(commit, c1);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "one");
let (dir, _) = resolve(&home, "dep", &url, &GitReference::Branch("feature".into()), None, false).unwrap();
assert_eq!(std::fs::read_to_string(dir.join("b.txt")).unwrap(), "feat");
let (dir, commit) = resolve(&home, "dep", &url, &GitReference::Rev(c1.clone()), None, false).unwrap();
assert_eq!(commit, c1);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "one");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn locked_commit_is_reused_without_network() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("locked");
let home = base.join("home");
let (url, c1, _c2) = fixture_repo(&base);
let (_, commit) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), None, false).unwrap();
assert_eq!(commit, c1);
let bogus = "file:///nonexistent/repo";
let bogus_locked = resolve(&home, "dep", bogus, &GitReference::Tag("v1".into()), Some(&c1), false);
assert!(bogus_locked.is_err());
let (dir, commit) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), Some(&c1), true).unwrap();
assert_eq!(commit, c1);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "one");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn mutable_reference_re_resolves_online_but_reuses_locked_offline() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("mutable");
let home = base.join("home");
let (url, _c1, c2) = fixture_repo(&base);
let src = base.join("src");
let (_, first) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, None, false).unwrap();
assert_eq!(first, c2);
std::fs::write(src.join("a.txt"), "three").unwrap();
run_git(&src, &["commit", "-qam", "c3"]);
let c3 = run_git(&src, &["rev-parse", "HEAD"]);
assert_ne!(c3, c2);
let (dir, refreshed) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, Some(&c2), false).unwrap();
assert_eq!(refreshed, c3);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "three");
let (dir, offline) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, Some(&c2), true).unwrap();
assert_eq!(offline, c2);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "two");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn branch_reference_re_resolves_to_new_tip_online() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("branch");
let home = base.join("home");
let (url, _c1, _c2) = fixture_repo(&base);
let src = base.join("src");
let (_, first) = resolve(&home, "dep", &url, &GitReference::Branch("feature".into()), None, false).unwrap();
run_git(&src, &["checkout", "-q", "feature"]);
std::fs::write(src.join("b.txt"), "feat2").unwrap();
run_git(&src, &["commit", "-qam", "feat2"]);
let advanced = run_git(&src, &["rev-parse", "HEAD"]);
assert_ne!(advanced, first);
let (dir, refreshed) =
resolve(&home, "dep", &url, &GitReference::Branch("feature".into()), Some(&first), false).unwrap();
assert_eq!(refreshed, advanced);
assert_eq!(std::fs::read_to_string(dir.join("b.txt")).unwrap(), "feat2");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn immutable_reference_reuses_locked_commit_online_without_fetching() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("immutable");
let home = base.join("home");
let (url, c1, _c2) = fixture_repo(&base);
let (_, commit) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), None, false).unwrap();
assert_eq!(commit, c1);
std::fs::remove_dir_all(base.join("src")).unwrap();
let (dir, reused) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), Some(&c1), false).unwrap();
assert_eq!(reused, c1);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "one");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn offline_without_cache_errors() {
let base = unique_dir("offline");
let home = base.join("home");
let result = resolve(&home, "dep", "file:///nonexistent/repo", &GitReference::DefaultBranch, None, true);
assert!(result.is_err());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn clone_failure_errors() {
let base = unique_dir("clonefail");
let home = base.join("home");
let result = resolve(&home, "dep", "file:///no/such/leo/repo", &GitReference::DefaultBranch, None, false);
assert!(result.is_err());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn locked_tag_wins_when_checkout_missing() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("relock");
let home = base.join("home");
let (url, c1, c2) = fixture_repo(&base);
let src = base.join("src");
let (_, commit) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), None, false).unwrap();
assert_eq!(commit, c1);
std::fs::remove_dir_all(home.join("git")).unwrap();
run_git(&src, &["tag", "-f", "v1", &c2]);
let (dir, commit) = resolve(&home, "dep", &url, &GitReference::Tag("v1".into()), Some(&c1), false).unwrap();
assert_eq!(commit, c1);
assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "one");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn checkouts_are_shared_across_dependency_names() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("shared");
let home = base.join("home");
let (url, c1, _c2) = fixture_repo(&base);
let (dir_a, _) = resolve(&home, "depa", &url, &GitReference::Tag("v1".into()), None, false).unwrap();
let (dir_b, _) = resolve(&home, "depb", &url, &GitReference::Tag("v1".into()), Some(&c1), false).unwrap();
assert_eq!(dir_a, dir_b, "same repository and commit must share one checkout");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn missing_reference_errors() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let base = unique_dir("missingref");
let home = base.join("home");
let (url, _c1, _c2) = fixture_repo(&base);
assert!(resolve(&home, "dep", &url, &GitReference::Branch("nope".into()), None, false).is_err());
assert!(resolve(&home, "dep", &url, &GitReference::Tag("v9.9.9".into()), None, false).is_err());
let bad_rev = "0000000000000000000000000000000000000000".to_string();
assert!(resolve(&home, "dep", &url, &GitReference::Rev(bad_rev), None, false).is_err());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
#[ignore = "requires network access"]
fn resolves_real_github_repo() {
let base = unique_dir("real");
let home = base.join("home");
let url = "https://github.com/ProvableHQ/leo-examples";
let rev = "6728690fcf10261a4023cf4f64b9c7960296d4e0";
let (dir, commit) = resolve(&home, "helloworld.aleo", url, &GitReference::Rev(rev.into()), None, false).unwrap();
assert_eq!(commit, rev);
let located = crate::find_in_checkout(&dir, "helloworld.aleo").unwrap();
assert!(located.is_dir(), "expected a Leo package directory, found a bytecode file");
assert!(located.join(MANIFEST_FILENAME).is_file());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn find_in_checkout_locates_each_kind_and_errors_when_absent() {
let base = unique_dir("find");
std::fs::create_dir_all(base.join("lib")).unwrap();
std::fs::write(
base.join("lib/program.json"),
r#"{"program":"mylib","version":"0.1.0","description":"","license":"MIT"}"#,
)
.unwrap();
std::fs::create_dir_all(base.join("prog")).unwrap();
std::fs::write(
base.join("prog/program.json"),
r#"{"program":"myprog.aleo","version":"0.1.0","description":"","license":"MIT"}"#,
)
.unwrap();
std::fs::write(base.join("mybytes.aleo"), "// bytecode\n").unwrap();
assert!(crate::find_in_checkout(&base, "mylib").unwrap().is_dir());
assert!(crate::find_in_checkout(&base, "myprog.aleo").unwrap().is_dir());
let bytes = crate::find_in_checkout(&base, "mybytes.aleo").unwrap();
assert!(bytes.is_file() && bytes.extension().and_then(|e| e.to_str()) == Some("aleo"));
assert!(crate::find_in_checkout(&base, "absent").is_err());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn find_in_checkout_prefers_exact_name_form() {
let base = unique_dir("exact");
std::fs::create_dir_all(base.join("lib")).unwrap();
std::fs::write(
base.join("lib/program.json"),
r#"{"program":"foo","version":"0.1.0","description":"","license":"MIT"}"#,
)
.unwrap();
std::fs::create_dir_all(base.join("prog")).unwrap();
std::fs::write(
base.join("prog/program.json"),
r#"{"program":"foo.aleo","version":"0.1.0","description":"","license":"MIT"}"#,
)
.unwrap();
assert!(crate::find_in_checkout(&base, "foo").unwrap().ends_with("lib"));
assert!(crate::find_in_checkout(&base, "foo.aleo").unwrap().ends_with("prog"));
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn find_in_checkout_errors_on_ambiguous_name() {
let base = unique_dir("ambiguous");
for dir in ["examples/dup", "dup"] {
std::fs::create_dir_all(base.join(dir)).unwrap();
std::fs::write(
base.join(dir).join("program.json"),
r#"{"program":"dup","version":"0.1.0","description":"","license":"MIT"}"#,
)
.unwrap();
}
let err = crate::find_in_checkout(&base, "dup").unwrap_err();
assert!(err.to_string().contains("ambiguous"), "expected ambiguity error: {err}");
let _ = std::fs::remove_dir_all(&base);
}
#[cfg(unix)]
#[test]
fn find_in_checkout_does_not_follow_symlinks() {
let base = unique_dir("symlink");
let outside = base.join("outside");
std::fs::create_dir_all(&outside).unwrap();
std::fs::write(
outside.join("program.json"),
r#"{"program":"secret","version":"0.1.0","description":"","license":"MIT"}"#,
)
.unwrap();
let checkout = base.join("checkout");
std::fs::create_dir_all(&checkout).unwrap();
std::os::unix::fs::symlink(&outside, checkout.join("link")).unwrap();
assert!(crate::find_in_checkout(&checkout, "secret").is_err());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn round_trip_and_lookup() {
let dir = unique_dir("lock");
let mut lock = Lock::read(&dir);
assert!(lock.is_empty());
lock.record("foo.aleo".into(), "https://example.com/foo".into(), "tag=v1".into(), "abc123".into());
lock.write(&dir).unwrap();
let reloaded = Lock::read(&dir);
assert_eq!(reloaded.commit_for("foo.aleo", "https://example.com/foo", "tag=v1"), Some("abc123"));
assert_eq!(reloaded.commit_for("foo.aleo", "https://example.com/foo", "tag=v2"), None);
assert_eq!(reloaded.commit_for("foo.aleo", "https://example.com/other", "tag=v1"), None);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn record_replaces_existing_commit() {
let mut lock = Lock::default();
lock.record("foo".into(), "url".into(), "branch=main".into(), "c1".into());
lock.record("foo".into(), "url".into(), "branch=main".into(), "c2".into());
assert_eq!(lock.commit_for("foo", "url", "branch=main"), Some("c2"));
}
#[test]
fn record_keeps_entries_under_other_references() {
let mut lock = Lock::default();
lock.record("foo".into(), "url".into(), "default".into(), "c1".into());
lock.record("foo".into(), "url".into(), "tag=v1".into(), "c2".into());
assert_eq!(lock.commit_for("foo", "url", "default"), Some("c1"));
assert_eq!(lock.commit_for("foo", "url", "tag=v1"), Some("c2"));
}
#[test]
fn carry_over_keeps_only_accepted_unrecorded_entries() {
let mut old = Lock::default();
old.record("foo".into(), "url".into(), "tag=v1".into(), "c1".into());
old.record("bar".into(), "url2".into(), "default".into(), "c2".into());
let mut new = Lock::default();
new.record("foo".into(), "url".into(), "tag=v1".into(), "c9".into());
new.carry_over(&old, |entry| entry.name == "bar");
assert_eq!(new.commit_for("foo", "url", "tag=v1"), Some("c9"));
assert_eq!(new.commit_for("bar", "url2", "default"), Some("c2"));
}
#[test]
fn remove_name_drops_all_entries_for_dependency() {
let mut lock = Lock::default();
lock.record("foo".into(), "url".into(), "tag=v1".into(), "c1".into());
lock.record("foo".into(), "url2".into(), "default".into(), "c2".into());
lock.record("bar".into(), "url".into(), "default".into(), "c3".into());
lock.remove_name("foo");
assert_eq!(lock.commit_for("foo", "url", "tag=v1"), None);
assert_eq!(lock.commit_for("foo", "url2", "default"), None);
assert_eq!(lock.commit_for("bar", "url", "default"), Some("c3"));
}
#[test]
fn empty_lock_is_not_written() {
let dir = unique_dir("lock-empty");
Lock::default().write(&dir).unwrap();
assert!(!dir.join(LOCK_FILENAME).exists());
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn manifest_rejects_git_dependency_without_git_url() {
let err = read_manifest(&manifest_json(r#"[{"name":"foo.aleo","location":"git"}]"#, "null")).unwrap_err();
assert!(err.to_string().contains("invalid dependency `foo.aleo`"));
assert!(err.to_string().contains("`git` dependencies must specify `git`"));
}
#[test]
fn manifest_rejects_git_dependency_with_path() {
let err = read_manifest(&manifest_json(
r#"[{"name":"foo.aleo","location":"git","git":{"url":"https://example.com/foo"},"path":"../foo"}]"#,
"null",
))
.unwrap_err();
assert!(err.to_string().contains("`git` dependencies cannot specify `path`"));
}
#[test]
fn manifest_rejects_git_dependency_with_edition() {
let err = read_manifest(&manifest_json(
r#"[{"name":"foo.aleo","location":"git","git":{"url":"https://example.com/foo"},"edition":1}]"#,
"null",
))
.unwrap_err();
assert!(err.to_string().contains("`git` dependencies cannot specify `edition`"));
}
#[test]
fn manifest_rejects_git_dependency_with_multiple_references() {
let err = read_manifest(&manifest_json(
r#"[{"name":"foo.aleo","location":"git","git":{"url":"https://example.com/foo","branch":"main","tag":"v1"}}]"#,
"null",
))
.unwrap_err();
assert!(err.to_string().contains("at most one of `branch`, `tag`, or `rev`"));
}
#[test]
fn manifest_rejects_git_field_on_non_git_dependency() {
let err = read_manifest(&manifest_json(
r#"[{"name":"foo.aleo","location":"local","path":"../foo","git":{"url":"https://example.com/foo"}}]"#,
"null",
))
.unwrap_err();
assert!(err.to_string().contains("`local` dependencies cannot specify `git`"));
let err = read_manifest(&manifest_json(
r#"[{"name":"foo.aleo","location":"network","edition":1,"git":{"url":"https://example.com/foo"}}]"#,
"null",
))
.unwrap_err();
assert!(err.to_string().contains("`network` dependencies cannot specify `git`"));
}
#[test]
fn manifest_rejects_invalid_git_dependency_name() {
let err = read_manifest(&manifest_json(
r#"[{"name":"not a name","location":"git","git":{"url":"https://example.com/foo"}}]"#,
"null",
))
.unwrap_err();
assert!(err.to_string().contains("must be a valid program or library name"));
}
#[test]
fn manifest_accepts_git_dependency_variants() {
let manifest = read_manifest(&manifest_json(
r#"[
{"name":"git_default.aleo","location":"git","git":{"url":"https://example.com/a"}},
{"name":"git_branch.aleo","location":"git","git":{"url":"https://example.com/b","branch":"main"}},
{"name":"git_tag.aleo","location":"git","git":{"url":"https://example.com/c","tag":"v0.1.0"}},
{"name":"git_rev.aleo","location":"git","git":{"url":"https://example.com/d","rev":"abc123"}}
]"#,
"null",
))
.unwrap();
assert_eq!(manifest.dependencies.unwrap().len(), 4);
}
#[test]
fn manifest_accepts_git_dev_dependency() {
let manifest = read_manifest(&manifest_json(
"null",
r#"[{"name":"mylib","location":"git","git":{"url":"https://example.com/a","tag":"v0.1.0"}}]"#,
))
.unwrap();
assert_eq!(manifest.dev_dependencies.unwrap().len(), 1);
}
#[test]
fn git_dependency_resolves_and_locks() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("e2e");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("mylib_repo");
write_library(&lib, "mylib", "null");
init_repo(&lib, None);
let consumer = root.join("consumer");
let url = file_url(&lib);
write_consumer(&consumer, &format!(r#"{{"name":"mylib","location":"git","git":{{"url":"{url}"}}}}"#));
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let lib_unit = package
.compilation_units
.iter()
.find(|u| u.name == Symbol::intern("mylib"))
.expect("mylib compilation unit present");
assert!(lib_unit.kind.is_library());
let lock = Lock::read(&consumer);
let commit = lock.commit_for("mylib", &url, "default").expect("lock pins mylib");
assert_eq!(commit.len(), 40);
let _ = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn git_dev_dependency_resolves_with_tests() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("dev_dep");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("mylib_repo");
write_library(&lib, "mylib", "null");
init_repo(&lib, None);
let url = file_url(&lib);
let consumer = root.join("consumer");
write_file(
&consumer.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"consumer.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":null,"dev_dependencies":[{{"name":"mylib","location":"git","git":{{"url":"{url}"}}}}]}}"#
),
);
write_file(&consumer.join("src/main.leo"), "// main\n");
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory_with_tests(&consumer, &home, false, false, false, None, None, 3).unwrap();
assert!(
package.compilation_units.iter().any(|u| u.name == Symbol::intern("mylib")),
"git dev-dependency resolved",
);
assert!(Lock::read(&consumer).commit_for("mylib", &url, "default").is_some(), "dev-dependency locked");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn offline_build_uses_locked_commit_and_cache() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("offline_e2e");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("mylib_repo");
write_library(&lib, "mylib", "null");
init_repo(&lib, None);
let url = file_url(&lib);
let consumer = root.join("consumer");
write_consumer(&consumer, &format!(r#"{{"name":"mylib","location":"git","git":{{"url":"{url}"}}}}"#));
leo_span::create_session_if_not_set_then(|_| {
Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let commit = Lock::read(&consumer).commit_for("mylib", &url, "default").expect("locked").to_string();
std::fs::remove_dir_all(&lib).unwrap();
Package::from_directory(&consumer, &home, false, false, true, None, None, 3).unwrap();
assert_eq!(Lock::read(&consumer).commit_for("mylib", &url, "default"), Some(commit.as_str()));
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn library_visible_to_src_and_tests() {
let run = |dev_dependencies: &str| {
let root = unique_dir("lib_visibility");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("mylib");
write_library(&lib, "mylib", "null");
let app = root.join("app");
write_file(
&app.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"app.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":[{{"name":"mylib","location":"local","path":"../mylib"}}],"dev_dependencies":{dev_dependencies}}}"#
),
);
write_file(&app.join("src/main.leo"), "// main\n");
write_file(&app.join("tests/test_app.leo"), "// test\n");
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory_with_tests(&app, &home, false, false, false, None, None, 3).unwrap();
let mylib = Symbol::intern("mylib");
let test_unit = package
.compilation_units
.iter()
.find(|u| u.kind.is_test())
.expect("the in-package test program is a compilation unit");
assert!(
test_unit.dependencies.iter().any(|d| d.name == "mylib"),
"a regular `dependencies` library is visible to the test program",
);
assert_eq!(
package.compilation_units.iter().filter(|u| u.name == mylib).count(),
1,
"the shared local library is resolved exactly once",
);
});
let _ = std::fs::remove_dir_all(&root);
};
run("null");
run(r#"[{"name":"mylib","location":"local","path":"../mylib"}]"#);
}
#[test]
fn plain_build_keeps_dev_dependency_pin() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("dev_pin");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("mylib_repo");
write_library(&lib, "mylib", "null");
init_repo(&lib, None);
let url = file_url(&lib);
let consumer = root.join("consumer");
write_file(
&consumer.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"consumer.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":null,"dev_dependencies":[{{"name":"mylib","location":"git","git":{{"url":"{url}"}}}}]}}"#
),
);
write_file(&consumer.join("src/main.leo"), "// main\n");
leo_span::create_session_if_not_set_then(|_| {
Package::from_directory_with_tests(&consumer, &home, false, false, false, None, None, 3).unwrap();
let commit = Lock::read(&consumer).commit_for("mylib", &url, "default").expect("locked").to_string();
Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
assert_eq!(Lock::read(&consumer).commit_for("mylib", &url, "default"), Some(commit.as_str()));
write_file(
&consumer.join(MANIFEST_FILENAME),
r#"{"program":"consumer.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":null,"dev_dependencies":null}"#,
);
Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
assert!(!consumer.join(LOCK_FILENAME).exists(), "stale lock file removed");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn git_dependency_ref_change_updates_lock() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("refchange");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("mylib_repo");
write_library(&lib, "mylib", "null");
init_repo(&lib, Some("v1"));
let url = file_url(&lib);
let consumer = root.join("consumer");
leo_span::create_session_if_not_set_then(|_| {
write_consumer(&consumer, &format!(r#"{{"name":"mylib","location":"git","git":{{"url":"{url}"}}}}"#));
Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
assert!(Lock::read(&consumer).commit_for("mylib", &url, "default").is_some());
write_consumer(
&consumer,
&format!(r#"{{"name":"mylib","location":"git","git":{{"url":"{url}","tag":"v1"}}}}"#),
);
Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let lock = Lock::read(&consumer);
assert!(lock.commit_for("mylib", &url, "tag=v1").is_some(), "tag entry recorded");
assert!(lock.commit_for("mylib", &url, "default").is_none(), "stale default entry pruned");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn transitive_git_dependency() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("transitive");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let libb = root.join("libb_repo");
write_library(&libb, "libb", "null");
init_repo(&libb, None);
let url_b = file_url(&libb);
let liba = root.join("liba_repo");
write_library(&liba, "liba", &format!(r#"[{{"name":"libb","location":"git","git":{{"url":"{url_b}"}}}}]"#));
init_repo(&liba, None);
let url_a = file_url(&liba);
let consumer = root.join("consumer");
write_consumer(&consumer, &format!(r#"{{"name":"liba","location":"git","git":{{"url":"{url_a}"}}}}"#));
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let names: Vec<String> = package.compilation_units.iter().map(|u| u.name.to_string()).collect();
assert!(names.iter().any(|n| n == "liba"), "liba resolved: {names:?}");
assert!(names.iter().any(|n| n == "libb"), "transitive libb resolved: {names:?}");
let lock = Lock::read(&consumer);
assert!(lock.commit_for("liba", &url_a, "default").is_some());
assert!(lock.commit_for("libb", &url_b, "default").is_some());
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn transitive_git_program_depends_on_library() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("transitive_mixed");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let deeplib = root.join("deeplib_repo");
write_library(&deeplib, "deeplib", "null");
init_repo(&deeplib, None);
let url_lib = file_url(&deeplib);
let midprog = root.join("midprog_repo");
write_program(
&midprog,
"midprog.aleo",
&format!(r#"[{{"name":"deeplib","location":"git","git":{{"url":"{url_lib}"}}}}]"#),
);
init_repo(&midprog, None);
let url_prog = file_url(&midprog);
let consumer = root.join("consumer");
write_consumer(&consumer, &format!(r#"{{"name":"midprog.aleo","location":"git","git":{{"url":"{url_prog}"}}}}"#));
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let prog = package
.compilation_units
.iter()
.find(|u| u.name == Symbol::intern("midprog.aleo"))
.expect("git program resolved");
assert!(prog.kind.is_program());
let lib = package
.compilation_units
.iter()
.find(|u| u.name == Symbol::intern("deeplib"))
.expect("transitive git library resolved");
assert!(lib.kind.is_library());
let lock = Lock::read(&consumer);
assert!(lock.commit_for("midprog.aleo", &url_prog, "default").is_some());
assert!(lock.commit_for("deeplib", &url_lib, "default").is_some());
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn git_dependency_into_workspace_repo_resolves_member_and_sibling() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("git_ws");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let repo = root.join("ws_repo");
write_file(&repo.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["liba","libb"]}"#);
write_library(&repo.join("liba"), "liba", "null");
write_library(&repo.join("libb"), "libb", r#"[{"name":"liba","location":"workspace"}]"#);
init_repo(&repo, None);
let url = file_url(&repo);
let consumer = root.join("consumer");
write_consumer(&consumer, &format!(r#"{{"name":"libb","location":"git","git":{{"url":"{url}"}}}}"#));
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let names: Vec<String> = package.compilation_units.iter().map(|u| u.name.to_string()).collect();
assert!(names.iter().any(|n| n == "libb"), "git workspace member resolved: {names:?}");
assert!(names.iter().any(|n| n == "liba"), "sibling workspace member resolved: {names:?}");
let lock = Lock::read(&consumer);
let libb_commit = lock.commit_for("libb", &url, "default").expect("libb locked");
assert_eq!(lock.commit_for("liba", &url, "default"), Some(libb_commit));
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn direct_and_sibling_route_to_same_git_member_do_not_conflict() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("git_ws_both");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let repo = root.join("ws_repo");
write_file(&repo.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["liba","libb"]}"#);
write_library(&repo.join("liba"), "liba", "null");
write_library(&repo.join("libb"), "libb", r#"[{"name":"liba","location":"workspace"}]"#);
init_repo(&repo, None);
let url = file_url(&repo);
let consumer = root.join("consumer");
write_consumer(
&consumer,
&format!(
r#"{{"name":"liba","location":"git","git":{{"url":"{url}"}}}},{{"name":"libb","location":"git","git":{{"url":"{url}"}}}}"#
),
);
leo_span::create_session_if_not_set_then(|_| {
let package = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap();
let names: Vec<String> = package.compilation_units.iter().map(|u| u.name.to_string()).collect();
assert!(names.iter().any(|n| n == "liba"), "liba resolved: {names:?}");
assert!(names.iter().any(|n| n == "libb"), "libb resolved: {names:?}");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn git_dependency_path_escape_errors() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("escape");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let outside = root.join("outside");
write_library(&outside, "outside", "null");
let outside_path = outside.canonicalize().unwrap().display().to_string().replace('\\', "/");
let evil = root.join("evil_repo");
write_library(&evil, "evil", &format!(r#"[{{"name":"outside","location":"local","path":"{outside_path}"}}]"#));
init_repo(&evil, None);
let url = file_url(&evil);
let consumer = root.join("consumer");
write_consumer(&consumer, &format!(r#"{{"name":"evil","location":"git","git":{{"url":"{url}"}}}}"#));
leo_span::create_session_if_not_set_then(|_| {
let err = Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap_err();
assert!(err.to_string().contains("inside its own repository checkout"), "path escape must be rejected: {err}");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn conflicting_git_dependency_errors() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("conflict");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("shared_repo");
write_library(&lib, "shared", "null");
init_repo(&lib, None);
let url = file_url(&lib);
let consumer = root.join("consumer");
write_consumer(
&consumer,
&format!(
r#"{{"name":"shared","location":"git","git":{{"url":"{url}","branch":"main"}}}},{{"name":"shared","location":"git","git":{{"url":"{url}"}}}}"#
),
);
leo_span::create_session_if_not_set_then(|_| {
let result = Package::from_directory(&consumer, &home, false, false, false, None, None, 3);
assert!(result.is_err(), "conflicting git references must error");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn workspace_members_share_lock_without_clobbering() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("ws_lock");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let liba = root.join("liba_repo");
write_library(&liba, "liba", "null");
init_repo(&liba, None);
let url_a = file_url(&liba);
let libb = root.join("libb_repo");
write_library(&libb, "libb", "null");
init_repo(&libb, None);
let url_b = file_url(&libb);
let ws = root.join("ws");
write_file(&ws.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["mema","memb"]}"#);
let mema = ws.join("mema");
write_file(
&mema.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"mema.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":[{{"name":"liba","location":"git","git":{{"url":"{url_a}"}}}}]}}"#
),
);
write_file(&mema.join("src/main.leo"), "// main\n");
let memb = ws.join("memb");
write_file(
&memb.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"memb.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":[{{"name":"libb","location":"git","git":{{"url":"{url_b}"}}}}]}}"#
),
);
write_file(&memb.join("src/main.leo"), "// main\n");
leo_span::create_session_if_not_set_then(|_| {
Package::from_directory(&mema, &home, false, false, false, None, None, 3).unwrap();
Package::from_directory(&memb, &home, false, false, false, None, None, 3).unwrap();
let lock = Lock::read(&ws);
assert!(lock.commit_for("liba", &url_a, "default").is_some(), "first member's entry retained");
assert!(lock.commit_for("libb", &url_b, "default").is_some(), "second member's entry recorded");
});
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn workspace_members_keep_different_references_to_same_repo() {
if !git_available() {
eprintln!("skipping: `git` CLI not available");
return;
}
let root = unique_dir("ws_refs");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
let lib = root.join("shared_repo");
write_library(&lib, "shared", "null");
init_repo(&lib, Some("v1"));
let url = file_url(&lib);
let ws = root.join("ws");
write_file(&ws.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["mema","memb"]}"#);
let mema = ws.join("mema");
write_file(
&mema.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"mema.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":[{{"name":"shared","location":"git","git":{{"url":"{url}","tag":"v1"}}}}]}}"#
),
);
write_file(&mema.join("src/main.leo"), "// main\n");
let memb = ws.join("memb");
write_file(
&memb.join(MANIFEST_FILENAME),
&format!(
r#"{{"program":"memb.aleo","version":"0.1.0","description":"","license":"MIT","dependencies":[{{"name":"shared","location":"git","git":{{"url":"{url}"}}}}]}}"#
),
);
write_file(&memb.join("src/main.leo"), "// main\n");
leo_span::create_session_if_not_set_then(|_| {
for member in [&mema, &memb, &mema, &memb] {
Package::from_directory(member, &home, false, false, false, None, None, 3).unwrap();
}
let lock = Lock::read(&ws);
assert!(lock.commit_for("shared", &url, "tag=v1").is_some(), "tag entry retained");
assert!(lock.commit_for("shared", &url, "default").is_some(), "default entry retained");
});
let _ = std::fs::remove_dir_all(&root);
}