Skip to content

Commit 8d1e241

Browse files
committed
feat: add test execution in debug mode
1 parent d4d9a38 commit 8d1e241

File tree

2 files changed

+154
-13
lines changed

2 files changed

+154
-13
lines changed

src/lib.rs

Lines changed: 141 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,9 @@ pub async fn debug_recipe(
10721072
) -> miette::Result<()> {
10731073
let recipe_path = get_recipe_path(&debug_data.recipe_path)?;
10741074

1075+
let is_test_mode = debug_data.test_mode;
1076+
let test_index = debug_data.test_index; // None means run all tests, Some(n) means run test n
1077+
10751078
let build_data = BuildData {
10761079
build_platform: debug_data.build_platform,
10771080
target_platform: debug_data.target_platform,
@@ -1177,22 +1180,147 @@ pub async fn debug_recipe(
11771180
}
11781181
}
11791182

1180-
tracing::info!("\nTo run the actual build, use:");
1181-
tracing::info!(
1182-
"rattler-build build --recipe {}",
1183-
output.build_configuration.directories.recipe_path.display()
1184-
);
1185-
tracing::info!("Or run the build script directly with:");
1186-
if cfg!(windows) {
1187-
tracing::info!(
1188-
"cd {} && ./conda_build.bat",
1189-
output.build_configuration.directories.work_dir.display()
1190-
);
1183+
if is_test_mode {
1184+
// Test mode: setup test environment and run test(s)
1185+
let tests = &output.recipe.tests;
1186+
1187+
if tests.is_empty() {
1188+
return Err(miette::miette!("No tests found in recipe."));
1189+
}
1190+
1191+
// Determine which tests to run
1192+
let tests_to_run: Vec<usize> = match test_index {
1193+
Some(idx) => {
1194+
if idx >= tests.len() {
1195+
return Err(miette::miette!(
1196+
"Test index {} out of range. Recipe has {} test(s).",
1197+
idx,
1198+
tests.len()
1199+
));
1200+
}
1201+
vec![idx]
1202+
}
1203+
None => (0..tests.len()).collect(),
1204+
};
1205+
1206+
tracing::info!("\n=== Test Debug Mode ===");
1207+
if test_index.is_some() {
1208+
tracing::info!("Running test {} of {}", tests_to_run[0], tests.len());
1209+
} else {
1210+
tracing::info!("Running all {} tests", tests.len());
1211+
}
1212+
tracing::info!("Available tests:");
1213+
for (idx, test) in tests.iter().enumerate() {
1214+
let test_type = match test {
1215+
crate::recipe::parser::TestType::Python { .. } => "Python",
1216+
crate::recipe::parser::TestType::Perl { .. } => "Perl",
1217+
crate::recipe::parser::TestType::R { .. } => "R",
1218+
crate::recipe::parser::TestType::Ruby { .. } => "Ruby",
1219+
crate::recipe::parser::TestType::Command(_) => "Command",
1220+
crate::recipe::parser::TestType::Downstream(_) => "Downstream",
1221+
crate::recipe::parser::TestType::PackageContents { .. } => "PackageContents",
1222+
};
1223+
let marker = if tests_to_run.contains(&idx) {
1224+
">>>"
1225+
} else {
1226+
" "
1227+
};
1228+
tracing::info!("{} Test {}: {}", marker, idx, test_type);
1229+
}
1230+
1231+
// Build the package first to be able to run tests
1232+
tracing::info!("\nNote: Tests require a built package. Building package first...");
1233+
1234+
// Create a modified output with debug mode disabled for the actual build
1235+
let mut build_output = output.clone();
1236+
build_output.build_configuration.debug = crate::types::Debug::new(false);
1237+
1238+
let (built_output, archive_path) = run_build(
1239+
build_output,
1240+
&tool_config,
1241+
WorkingDirectoryBehavior::Cleanup,
1242+
)
1243+
.await?;
1244+
1245+
tracing::info!("Package built successfully: {}", archive_path.display());
1246+
1247+
// Run the test(s) with environment preservation for debugging
1248+
let config = &built_output.build_configuration;
1249+
1250+
for current_test_index in tests_to_run {
1251+
// Skip PackageContents tests as they run at build time
1252+
if matches!(
1253+
tests[current_test_index],
1254+
crate::recipe::parser::TestType::PackageContents { .. }
1255+
) {
1256+
tracing::info!(
1257+
"\nSkipping test {} (PackageContents tests run at build time)",
1258+
current_test_index
1259+
);
1260+
continue;
1261+
}
1262+
1263+
// Calculate the package test index (excluding PackageContents tests before this one)
1264+
let package_test_index = tests[..=current_test_index]
1265+
.iter()
1266+
.filter(|t| {
1267+
!matches!(t, crate::recipe::parser::TestType::PackageContents { .. })
1268+
})
1269+
.count()
1270+
- 1;
1271+
1272+
let test_config = TestConfiguration {
1273+
test_prefix: config
1274+
.directories
1275+
.output_dir
1276+
.join(format!("test_debug_{}", current_test_index)),
1277+
target_platform: Some(config.target_platform),
1278+
host_platform: Some(config.host_platform.clone()),
1279+
current_platform: config.build_platform.clone(),
1280+
keep_test_prefix: true,
1281+
test_index: Some(package_test_index),
1282+
channels: config.channels.clone(),
1283+
channel_priority: tool_config.channel_priority,
1284+
solve_strategy: SolveStrategy::Highest,
1285+
tool_configuration: tool_config.clone(),
1286+
output_dir: config.directories.output_dir.clone(),
1287+
debug: crate::types::Debug::new(false),
1288+
exclude_newer: config.exclude_newer,
1289+
};
1290+
1291+
tracing::info!(
1292+
"\nRunning test {} with environment preservation...\n",
1293+
current_test_index
1294+
);
1295+
1296+
match package_test::run_test(&archive_path, &test_config, None).await {
1297+
Ok(_) => {
1298+
tracing::info!("\nTest {} passed!", current_test_index);
1299+
}
1300+
Err(e) => {
1301+
return Err(miette::miette!("Test {} failed: {}", current_test_index, e));
1302+
}
1303+
}
1304+
}
11911305
} else {
1306+
// Build mode: provide instructions to run the build script
1307+
tracing::info!("\nTo run the actual build, use:");
11921308
tracing::info!(
1193-
"cd {} && ./conda_build.sh",
1194-
output.build_configuration.directories.work_dir.display()
1309+
"rattler-build build --recipe {}",
1310+
output.build_configuration.directories.recipe_path.display()
11951311
);
1312+
tracing::info!("Or run the build script directly with:");
1313+
if cfg!(windows) {
1314+
tracing::info!(
1315+
"cd {} && ./conda_build.bat",
1316+
output.build_configuration.directories.work_dir.display()
1317+
);
1318+
} else {
1319+
tracing::info!(
1320+
"cd {} && ./conda_build.sh",
1321+
output.build_configuration.directories.work_dir.display()
1322+
);
1323+
}
11961324
}
11971325
}
11981326

src/opt.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,13 @@ pub struct DebugOpts {
895895
/// Name of the specific output to debug (only required when a recipe has multiple outputs)
896896
#[arg(long, help = "Name of the specific output to debug")]
897897
pub output_name: Option<String>,
898+
899+
/// Run tests in debug mode.
900+
#[arg(
901+
long,
902+
help = "Run tests in debug mode. Optionally specify test index to run a specific test (default: run all tests)"
903+
)]
904+
pub test: Option<Option<usize>>,
898905
}
899906

900907
#[derive(Debug, Clone)]
@@ -916,6 +923,10 @@ pub struct DebugData {
916923
pub common: CommonData,
917924
/// Name of the specific output to debug (if recipe has multiple outputs)
918925
pub output_name: Option<String>,
926+
/// Whether test mode is enabled
927+
pub test_mode: bool,
928+
/// Test index to run in debug mode. None means run all tests, Some(n) means run test n.
929+
pub test_index: Option<usize>,
919930
}
920931

921932
impl DebugData {
@@ -933,6 +944,8 @@ impl DebugData {
933944
channels: opts.channels,
934945
common: CommonData::from_opts_and_config(opts.common, config.unwrap_or_default()),
935946
output_name: opts.output_name,
947+
test_mode: opts.test.is_some(), // true if --test flag was provided
948+
test_index: opts.test.flatten(), // None = all tests, Some(n) = specific test
936949
}
937950
}
938951
}

0 commit comments

Comments
 (0)