Skip to content

Commit 2b41c28

Browse files
committed
test(deploy): add workspace deploy unit tests and e2e test
Add unit tests for workspace deploy edge cases: - all-library workspace correctly errors with "No deployable workspace members found" - mixed library+program workspace builds only the program member Add test helpers `sample_workspace_all_libraries` and `sample_workspace_mixed` to support the above. Add e2e integration test `test_workspace_deploy` that deploys a two-member workspace (token + swap) against devnode, exercising build ordering, shared-dep dedup, workspace deployment plan output, and end-to-end broadcast confirmation.
1 parent abc154c commit 2b41c28

24 files changed

Lines changed: 680 additions & 0 deletions

File tree

crates/leo/src/cli/cli.rs

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,101 @@ mod tests {
926926
let _ = std::fs::remove_dir_all(&ws_root);
927927
}
928928

929+
#[test]
930+
#[serial]
931+
fn workspace_deploy_all_libraries_error_test() {
932+
// Deploying a workspace where every member is a library should error.
933+
let temp_dir = temp_dir();
934+
let ws_root = test_helpers::sample_workspace_all_libraries(&temp_dir, "deploy_all_libs");
935+
936+
let deploy = CLI {
937+
debug: false,
938+
quiet: false,
939+
json_output: None,
940+
disable_update_check: false,
941+
command: Commands::Deploy {
942+
command: crate::cli::commands::LeoDeploy {
943+
fee_options: Default::default(),
944+
action: crate::cli::commands::TransactionAction { print: false, broadcast: false, save: None },
945+
env_override: crate::cli::commands::EnvOptions {
946+
network: Some(NetworkName::TestnetV0),
947+
private_key: Some("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH".to_string()),
948+
endpoint: Some("http://localhost:1".to_string()),
949+
consensus_heights: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]),
950+
..Default::default()
951+
},
952+
extra: crate::cli::commands::ExtraOptions { yes: true, ..Default::default() },
953+
skip: vec![],
954+
build_options: Default::default(),
955+
skip_deploy_certificate: true,
956+
},
957+
},
958+
path: Some(ws_root.clone()),
959+
home: None,
960+
package: None,
961+
};
962+
963+
create_session_if_not_set_then(|_| {
964+
let result = run_with_args(deploy);
965+
assert!(result.is_err(), "deploy of all-library workspace should fail");
966+
let err = result.unwrap_err().to_string();
967+
assert!(
968+
err.contains("No deployable workspace members found"),
969+
"expected 'No deployable workspace members found', got: {err}"
970+
);
971+
});
972+
973+
let _ = std::fs::remove_dir_all(&ws_root);
974+
}
975+
976+
#[test]
977+
#[serial]
978+
fn workspace_deploy_mixed_library_program_test() {
979+
// Workspace with one library and one program member. Only the program
980+
// member should be deployed (library is skipped).
981+
let temp_dir = temp_dir();
982+
let ws_root = test_helpers::sample_workspace_mixed(&temp_dir, "deploy_mixed");
983+
984+
let deploy = CLI {
985+
debug: false,
986+
quiet: false,
987+
json_output: None,
988+
disable_update_check: false,
989+
command: Commands::Deploy {
990+
command: crate::cli::commands::LeoDeploy {
991+
fee_options: Default::default(),
992+
action: crate::cli::commands::TransactionAction { print: false, broadcast: false, save: None },
993+
env_override: crate::cli::commands::EnvOptions {
994+
network: Some(NetworkName::TestnetV0),
995+
private_key: Some("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH".to_string()),
996+
endpoint: Some("http://localhost:1".to_string()),
997+
consensus_heights: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]),
998+
..Default::default()
999+
},
1000+
extra: crate::cli::commands::ExtraOptions { yes: true, ..Default::default() },
1001+
skip: vec![],
1002+
build_options: Default::default(),
1003+
skip_deploy_certificate: true,
1004+
},
1005+
},
1006+
path: Some(ws_root.clone()),
1007+
home: None,
1008+
package: None,
1009+
};
1010+
1011+
create_session_if_not_set_then(|_| {
1012+
// Deploy will fail at network, but build phase should succeed.
1013+
let _ = run_with_args(deploy);
1014+
});
1015+
1016+
// The app program should be built.
1017+
assert!(ws_root.join("app/build/main.aleo").exists(), "app should be built");
1018+
// utils is a library - no main.aleo output.
1019+
assert!(!ws_root.join("utils/build/main.aleo").exists(), "utils library should not produce main.aleo");
1020+
1021+
let _ = std::fs::remove_dir_all(&ws_root);
1022+
}
1023+
9291024
#[test]
9301025
#[serial]
9311026
fn workspace_backward_compat_test() {
@@ -1268,6 +1363,151 @@ program swap.aleo {
12681363
ws_root
12691364
}
12701365

1366+
/// Workspace where every member is a library (no `.aleo` programs).
1367+
pub(crate) fn sample_workspace_all_libraries(temp_dir: &Path, name: &str) -> PathBuf {
1368+
let ws_root = temp_dir.join(format!("ws_{name}"));
1369+
1370+
if ws_root.exists() {
1371+
std::fs::remove_dir_all(&ws_root).unwrap();
1372+
}
1373+
std::fs::create_dir_all(&ws_root).unwrap();
1374+
1375+
let lib_a = ws_root.join("lib_a");
1376+
let lib_b = ws_root.join("lib_b");
1377+
1378+
// Create lib_a.
1379+
std::fs::create_dir_all(lib_a.join("src")).unwrap();
1380+
std::fs::write(lib_a.join("src/lib.leo"), "").unwrap();
1381+
std::fs::write(
1382+
lib_a.join(leo_package::MANIFEST_FILENAME),
1383+
serde_json::to_string_pretty(&serde_json::json!({
1384+
"program": "lib_a",
1385+
"version": "0.1.0",
1386+
"description": "",
1387+
"license": "MIT",
1388+
"leo": env!("CARGO_PKG_VERSION"),
1389+
"dependencies": null,
1390+
"dev_dependencies": null
1391+
}))
1392+
.unwrap(),
1393+
)
1394+
.unwrap();
1395+
1396+
// Create lib_b.
1397+
std::fs::create_dir_all(lib_b.join("src")).unwrap();
1398+
std::fs::write(lib_b.join("src/lib.leo"), "").unwrap();
1399+
std::fs::write(
1400+
lib_b.join(leo_package::MANIFEST_FILENAME),
1401+
serde_json::to_string_pretty(&serde_json::json!({
1402+
"program": "lib_b",
1403+
"version": "0.1.0",
1404+
"description": "",
1405+
"license": "MIT",
1406+
"leo": env!("CARGO_PKG_VERSION"),
1407+
"dependencies": null,
1408+
"dev_dependencies": null
1409+
}))
1410+
.unwrap(),
1411+
)
1412+
.unwrap();
1413+
1414+
// Write workspace.json.
1415+
std::fs::write(
1416+
ws_root.join(leo_package::WORKSPACE_MANIFEST_FILENAME),
1417+
serde_json::to_string_pretty(&serde_json::json!({
1418+
"members": ["lib_a", "lib_b"]
1419+
}))
1420+
.unwrap(),
1421+
)
1422+
.unwrap();
1423+
1424+
ws_root
1425+
}
1426+
1427+
/// Workspace with one library member (`utils`) and one program member
1428+
/// (`app`) that imports it.
1429+
pub(crate) fn sample_workspace_mixed(temp_dir: &Path, name: &str) -> PathBuf {
1430+
let ws_root = temp_dir.join(format!("ws_{name}"));
1431+
1432+
if ws_root.exists() {
1433+
std::fs::remove_dir_all(&ws_root).unwrap();
1434+
}
1435+
std::fs::create_dir_all(&ws_root).unwrap();
1436+
1437+
let utils_dir = ws_root.join("utils");
1438+
let app_dir = ws_root.join("app");
1439+
1440+
// Create utils library.
1441+
std::fs::create_dir_all(utils_dir.join("src")).unwrap();
1442+
std::fs::write(
1443+
utils_dir.join("src/lib.leo"),
1444+
"\
1445+
const FACTOR: u32 = 2u32;
1446+
",
1447+
)
1448+
.unwrap();
1449+
std::fs::write(
1450+
utils_dir.join(leo_package::MANIFEST_FILENAME),
1451+
serde_json::to_string_pretty(&serde_json::json!({
1452+
"program": "utils",
1453+
"version": "0.1.0",
1454+
"description": "",
1455+
"license": "MIT",
1456+
"leo": env!("CARGO_PKG_VERSION"),
1457+
"dependencies": null,
1458+
"dev_dependencies": null
1459+
}))
1460+
.unwrap(),
1461+
)
1462+
.unwrap();
1463+
1464+
// Create app program (depends on utils via workspace).
1465+
std::fs::create_dir_all(app_dir.join("src")).unwrap();
1466+
std::fs::write(
1467+
app_dir.join("src/main.leo"),
1468+
"\
1469+
program app.aleo {
1470+
fn run(x: u32) -> u32 {
1471+
return x * utils::FACTOR;
1472+
}
1473+
1474+
@noupgrade
1475+
constructor() {}
1476+
}
1477+
",
1478+
)
1479+
.unwrap();
1480+
std::fs::write(
1481+
app_dir.join(leo_package::MANIFEST_FILENAME),
1482+
serde_json::to_string_pretty(&serde_json::json!({
1483+
"program": "app.aleo",
1484+
"version": "0.1.0",
1485+
"description": "",
1486+
"license": "MIT",
1487+
"leo": env!("CARGO_PKG_VERSION"),
1488+
"dependencies": [{
1489+
"name": "utils",
1490+
"location": "workspace"
1491+
}],
1492+
"dev_dependencies": null
1493+
}))
1494+
.unwrap(),
1495+
)
1496+
.unwrap();
1497+
1498+
// Write workspace.json (utils first so it builds before app).
1499+
std::fs::write(
1500+
ws_root.join(leo_package::WORKSPACE_MANIFEST_FILENAME),
1501+
serde_json::to_string_pretty(&serde_json::json!({
1502+
"members": ["utils", "app"]
1503+
}))
1504+
.unwrap(),
1505+
)
1506+
.unwrap();
1507+
1508+
ws_root
1509+
}
1510+
12711511
pub(crate) fn sample_nested_package(temp_dir: &Path) {
12721512
let name = "nested";
12731513

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
3+
LEO_BIN=${1}
4+
PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"
5+
COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13"
6+
7+
$LEO_BIN deploy -y $COMMON_OPTS
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0

tests/expectations/cli/test_workspace_deploy/STDERR

Whitespace-only changes.

0 commit comments

Comments
 (0)