Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ test-threads = 4
# These fixtures own temporary state and loopback endpoints. Live tests stay opt-in.
# Selecting the fake ssh relay's ignored test also archives nemoclaw-test-fixtures'
# executables beside the extracted tests: the relay, which fake engines need on
# platforms without Unix sockets, the SSH simulator, and the fixture provider.
# platforms without Unix sockets, and the SSH simulator. The fixture provider's
# ignored test does the same for the provider protocol tests.
default-filter = '''
test(native_state_json_preserves_module_and_instance_identity_without_refresh)
| (package(nemoclaw-sdk) & test(/^deployment::runtime::helm_recovery::tests::/))
| (package(nemoclaw-sdk) & test(runtime_export_declares_the_native_helm_provider_inputs))
| ((package(nemoclaw-provider) | package(openshell-provider) | package(fabric-provider)) & binary(=contract))
| (package(nemoclaw-test-fixtures) & binary(=ssh_relay))
| (package(nemoclaw-fixture-provider) & binary(=archive))
| (package(nemoclaw-e2e) & binary(=integration) & (
test(/^sandbox_readiness::/) | test(/^deployment::/)
| test(/^export_observations::/) | test(/^fabric_deployment::/)
Expand Down
20 changes: 15 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"crates/nemoclaw-provider",
"crates/nemoclaw-e2e",
"crates/nemoclaw-test-fixtures",
"crates/nemoclaw-fixture-provider",
"crates/nemoclaw-cli",
"crates/nemoclaw-runtime",
"crates/nemoclaw-build",
Expand Down
5 changes: 3 additions & 2 deletions crates/fabric-provider/tests/contract/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ output "observation" {{ value = data.fabric_capabilities.agent.observation_json
/// an explicit read policy that omits the runtime's paths, is unsupported, and
/// a missing image is unknown; none of these fail apply or change the engine.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "requires NEMOCLAW_TEST_TOFU and NEMOCLAW_TEST_PROVIDER; fake Docker engine"]
#[ignore = "requires NEMOCLAW_TEST_BUNDLE; fake Docker engine"]
async fn capabilities_judge_the_image_for_the_configuration() {
let digest = format!("registry/agent@sha256:{}", "a".repeat(64));
let engine = engine(digest.clone()).await;
Expand All @@ -100,7 +100,8 @@ async fn capabilities_judge_the_image_for_the_configuration() {
),
("missing:image", "aarch64", runtime_reads, "unknown"),
] {
let workspace = TofuWorkspace::with_providers(tofu(), &[("fabric", &provider("fabric"))]);
let workspace =
TofuWorkspace::with_providers(Bundle::from_env().tofu(), &[("fabric", &fabric())]);
fs::write(
workspace.path().join("main.tf"),
configuration(image, architecture, reads),
Expand Down
36 changes: 11 additions & 25 deletions crates/fabric-provider/tests/contract/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod capabilities;
mod http;
mod readiness;

use nemoclaw_test_fixtures::{executable, openshell::Fixture, tofu::TofuWorkspace};
use nemoclaw_test_fixtures::{Bundle, openshell::Fixture, tofu::TofuWorkspace};
use serde_json::{Value, json};
use std::{
collections::BTreeSet,
Expand All @@ -23,34 +23,20 @@ use std::{

const FIXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/contract/fixtures");

fn tofu() -> PathBuf {
let tofu = PathBuf::from(
std::env::var_os("NEMOCLAW_TEST_TOFU").expect("explicit pinned OpenTofu path"),
);
assert!(tofu.is_absolute());
tofu
}

/// A provider built beside NEMOCLAW_TEST_PROVIDER, as the lifecycle tests
/// receive it.
fn provider(name: &str) -> PathBuf {
let nemoclaw = PathBuf::from(
std::env::var_os("NEMOCLAW_TEST_PROVIDER").expect("explicit provider build path"),
);
assert!(nemoclaw.is_absolute());
nemoclaw
.parent()
.unwrap()
.join(executable(&format!("terraform-provider-{name}")))
/// The fabric provider Cargo built from this checkout, the one under test.
fn fabric() -> PathBuf {
nemoclaw_test_fixtures::package_executable!("terraform-provider-fabric")
}

/// A workspace with the openshell and fabric providers.
/// A workspace with the fabric provider under test and the bundle's openshell
/// provider.
fn workspace() -> TofuWorkspace {
let bundle = Bundle::from_env();
TofuWorkspace::with_providers(
tofu(),
bundle.tofu(),
&[
("openshell", &provider("openshell")),
("fabric", &provider("fabric")),
("openshell", &bundle.provider("openshell")),
("fabric", &fabric()),
],
)
}
Expand Down Expand Up @@ -132,7 +118,7 @@ fn every_fabric_type_has_a_contract_test() {
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "requires NEMOCLAW_TEST_TOFU and NEMOCLAW_TEST_PROVIDER; fake OpenShell gateway"]
#[ignore = "requires NEMOCLAW_TEST_BUNDLE; fake OpenShell gateway"]
async fn pi_configuration_updates_without_replacing_the_sandbox() {
let fixture = Fixture::start().await;
let tofu = Standalone::new(&fixture.endpoint, &[]);
Expand Down
2 changes: 1 addition & 1 deletion crates/fabric-provider/tests/contract/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn output(tofu: &Standalone, name: &str) -> Value {
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "requires NEMOCLAW_TEST_TOFU and NEMOCLAW_TEST_PROVIDER; fake OpenShell gateway"]
#[ignore = "requires NEMOCLAW_TEST_BUNDLE; fake OpenShell gateway"]
async fn readiness_reports_the_configured_agent_and_its_failed_health() {
let fixture = Fixture::start().await;
let tofu = Standalone::new(&fixture.endpoint, &["readiness.tf"]);
Expand Down
15 changes: 3 additions & 12 deletions crates/nemoclaw-build/src/run_ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,18 +248,9 @@ fn run_step(
)?;
command.env("PATH", path);
}
command
.env("NEMOCLAW_TEST_BUNDLE", &bundle)
.env(
"NEMOCLAW_TEST_TOFU",
bundle
.join("libexec")
.join(nemoclaw_build_executable("tofu")),
)
.env(
"NEMOCLAW_TEST_PROVIDER",
std::path::absolute(tool("terraform-provider-nemoclaw"))?,
);
// The bundle supplies pinned OpenTofu and the providers; each
// provider's contract tests use the one Cargo built with them.
command.env("NEMOCLAW_TEST_BUNDLE", &bundle);
}
run(&mut command)?;
}
Expand Down
18 changes: 6 additions & 12 deletions crates/nemoclaw-build/src/run_ci/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,12 @@ pub(super) fn package_inputs(tools: &Tools<'_>) -> Result<()> {
std::env::current_exe()?,
Path::new(".build/ci").join(nemoclaw_build_executable("nemoclaw-build")),
)?;
// Protocol fixtures locate sibling providers beside this explicit binary,
// and the lifecycle step puts the fake ssh first on PATH where Unix
// sockets are missing.
for name in [
"terraform-provider-nemoclaw",
"terraform-provider-openshell",
"terraform-provider-fabric",
"nemoclaw-fixture-ssh",
] {
let path = Path::new("target/debug").join(nemoclaw_build_executable(name));
archive.append_path(&path)?;
}
// The lifecycle step puts the fake ssh first on PATH where Unix sockets
// are missing. The providers need no copy here: the bundle ships them, and
// nextest archives each provider package's executables with its tests.
archive.append_path(
Path::new("target/debug").join(nemoclaw_build_executable("nemoclaw-fixture-ssh")),
)?;
archive.finish()?;
Ok(())
}
Expand Down
26 changes: 16 additions & 10 deletions crates/nemoclaw-build/tests/ci_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ if [ "$1 $2" = 'nextest list' ]; then
printf '%s\n' "$CARGO_LIST"
exit "${CARGO_RESULT:-0}"
fi
printf '%s\n' "$NEMOCLAW_TEST_BUNDLE" "$NEMOCLAW_TEST_TOFU" "$NEMOCLAW_TEST_PROVIDER" > inputs
printf '%s\n' "$NEMOCLAW_TEST_BUNDLE" "${NEMOCLAW_TEST_TOFU-unset}" "${NEMOCLAW_TEST_PROVIDER-unset}" > inputs
exit "${CARGO_RESULT:-0}"
"#,
),
Expand Down Expand Up @@ -63,6 +63,8 @@ exit "${CARGO_RESULT:-0}"
.env("PROTOC", self.0.path().join("bin/protoc"))
.env("PATH", self.0.path().join("bin"))
.env("TEST_PLATFORM", "linux_arm64")
.env_remove("NEMOCLAW_TEST_TOFU")
.env_remove("NEMOCLAW_TEST_PROVIDER")
.env_remove("GITHUB_ENV")
.env_remove("GITHUB_PATH");
command
Expand Down Expand Up @@ -92,12 +94,14 @@ fn lifecycle_partition_reaches_nextest_without_changing_the_selected_suite() {
args.contains("--profile\nlifecycle\n--run-ignored\nonly\n"),
"{args}"
);
// The bundle supplies OpenTofu and the providers; each provider's own
// tests use the one Cargo built.
let inputs = fs::read_to_string(fixture.0.path().join("inputs")).unwrap();
assert!(inputs.contains("dist/linux_arm64/libexec/tofu"), "{inputs}");
assert!(
inputs.contains("target/debug/terraform-provider-nemoclaw"),
"{inputs}"
);
let inputs: Vec<_> = inputs.lines().collect();
assert_eq!(inputs.len(), 3, "{inputs:?}");
assert!(inputs[0].ends_with("/dist/linux_arm64"), "{inputs:?}");
assert!(std::path::Path::new(inputs[0]).is_absolute(), "{inputs:?}");
assert_eq!(inputs[1..], ["unset", "unset"]);
}

#[test]
Expand Down Expand Up @@ -164,7 +168,7 @@ fn lifecycle_options_cannot_silently_narrow_default_or_other_ci_steps() {
}

#[test]
fn archive_packages_tools_and_provider_helpers_but_leaves_the_bundle_to_its_own_job() {
fn archive_packages_tools_and_the_ssh_relay_but_leaves_providers_to_the_bundle_and_nextest() {
let fixture = Fixture::new();
for path in [
"dist/linux_arm64/bin/nemoclaw",
Expand Down Expand Up @@ -202,11 +206,13 @@ fn archive_packages_tools_and_provider_helpers_but_leaves_the_bundle_to_its_own_
tar::Archive::new(packed).unpack(unpacked.path()).unwrap();
// The bundle job uploads dist/PLATFORM separately, in parallel with this build.
assert!(!unpacked.path().join("dist").exists());
// Nextest archives each provider package's executables with its tests.
for name in ["nemoclaw", "openshell", "fabric"] {
let path = format!("target/debug/terraform-provider-{name}");
assert!(!unpacked.path().join(&path).exists(), "{path}");
}
for path in [
".tools/nextest-0.9.144/cargo-nextest",
"target/debug/terraform-provider-nemoclaw",
"target/debug/terraform-provider-openshell",
"target/debug/terraform-provider-fabric",
"target/debug/nemoclaw-fixture-ssh",
] {
let file = unpacked.path().join(path);
Expand Down
9 changes: 4 additions & 5 deletions crates/nemoclaw-e2e/tests/managed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ use serde_json::{Value, json};
use std::{fs, path::PathBuf};

#[test]
#[ignore = "requires explicit retained runtime state, OpenTofu and production provider paths; reads owned storage only"]
#[ignore = "requires explicit retained runtime state and NEMOCLAW_TEST_BUNDLE; reads owned storage only"]
fn provider_refreshes_retained_storage_without_changes() {
let path = |name| PathBuf::from(std::env::var_os(name).expect("explicit qualification path"));
let tofu = path("NEMOCLAW_TEST_TOFU");
let provider = path("NEMOCLAW_TEST_PROVIDER");
let bundle = nemoclaw_test_fixtures::Bundle::from_env();
let retained = path("NEMOCLAW_TEST_RUNTIME_STATE");
assert!(tofu.is_absolute() && provider.is_absolute() && retained.is_absolute());
assert!(retained.is_absolute());
let original = fs::read(&retained).unwrap();
let mut state: Value = serde_json::from_slice(&original).unwrap();
state["resources"].as_array_mut().unwrap().retain(|r| {
Expand All @@ -26,7 +25,7 @@ fn provider_refreshes_retained_storage_without_changes() {
2,
"requires the two retained storage resources"
);
let directory = TofuWorkspace::new(tofu, provider);
let directory = TofuWorkspace::new(&bundle, bundle.provider("nemoclaw"));
let mut graph = json!({"terraform":{"required_providers":{"nemoclaw":{"source":"registry.opentofu.org/nvidia/nemoclaw","version":"0.1.0"}}},"provider":{"nemoclaw":{}},"resource":{}});
for resource in resources {
assert_eq!(resource["instances"].as_array().unwrap().len(), 1);
Expand Down
9 changes: 4 additions & 5 deletions crates/nemoclaw-e2e/tests/sandbox_readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
use nemoclaw_e2e::{openshell::Fixture, tofu::TofuWorkspace};
use nemoclaw_sdk::{compile, config::Document};
use serde_json::{Value, json};
use std::{fs, path::PathBuf};
use std::fs;

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "requires NEMOCLAW_TEST_TOFU and NEMOCLAW_TEST_PROVIDER; isolated gateway fixture"]
#[ignore = "requires NEMOCLAW_TEST_BUNDLE; isolated gateway fixture"]
async fn standalone_sandbox_completion_rejects_unknown_health_and_retains_bindings() {
let tofu = PathBuf::from(std::env::var_os("NEMOCLAW_TEST_TOFU").unwrap());
let provider = PathBuf::from(std::env::var_os("NEMOCLAW_TEST_PROVIDER").unwrap());
let directory = TofuWorkspace::new(tofu, provider);
let bundle = nemoclaw_test_fixtures::Bundle::from_env();
let directory = TofuWorkspace::new(&bundle, bundle.provider("nemoclaw"));
let root = directory.path();
let fixture = Fixture::start().await;
let mut document = Document::parse(
Expand Down
34 changes: 34 additions & 0 deletions crates/nemoclaw-fixture-provider/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# A fixture nemoclaw provider for the provider protocol tests. It serves SDK
# resource definitions, so it lives apart from nemoclaw-test-fixtures: the
# openshell and fabric provider tests use those fixtures without compiling the
# SDK. Tests find the executable beside theirs (fixture_executable).
[package]
name = "nemoclaw-fixture-provider"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false

[[bin]]
name = "nemoclaw-fixture-provider"
path = "src/main.rs"

[dependencies]
nemoclaw-sdk = { path = "../nemoclaw-sdk" }
nemoclaw-runtime = { path = "../nemoclaw-runtime", default-features = false }
nemoclaw-tofu = { path = "../nemoclaw-tofu" }
tf-provider.workspace = true
async-trait.workspace = true
rustls.workspace = true
serde_json.workspace = true
tokio.workspace = true

[dev-dependencies]
nemoclaw-test-fixtures = { path = "../nemoclaw-test-fixtures" }

[lints]
workspace = true
13 changes: 13 additions & 0 deletions crates/nemoclaw-fixture-provider/tests/archive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//! Building this test also builds the fixture provider that the provider
//! protocol tests find beside their test executables.

/// The lifecycle profile selects this ignored test, so nextest archives the
/// fixture provider beside the extracted lifecycle tests.
#[test]
#[ignore = "run by the lifecycle profile to archive the fixture provider with its tests"]
fn the_fixture_provider_is_found_beside_the_test_executables() {
let provider = nemoclaw_test_fixtures::fixture_executable("nemoclaw-fixture-provider");
assert!(provider.is_file());
}
Loading
Loading