Skip to content
Open
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
154 changes: 141 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,9 @@ pub async fn debug_recipe(
) -> miette::Result<()> {
let recipe_path = get_recipe_path(&debug_data.recipe_path)?;

let is_test_mode = debug_data.test_mode;
let test_index = debug_data.test_index; // None means run all tests, Some(n) means run test n

let build_data = BuildData {
build_platform: debug_data.build_platform,
target_platform: debug_data.target_platform,
Expand Down Expand Up @@ -1177,22 +1180,147 @@ pub async fn debug_recipe(
}
}

tracing::info!("\nTo run the actual build, use:");
tracing::info!(
"rattler-build build --recipe {}",
output.build_configuration.directories.recipe_path.display()
);
tracing::info!("Or run the build script directly with:");
if cfg!(windows) {
tracing::info!(
"cd {} && ./conda_build.bat",
output.build_configuration.directories.work_dir.display()
);
if is_test_mode {
// Test mode: setup test environment and run test(s)
let tests = &output.recipe.tests;

if tests.is_empty() {
return Err(miette::miette!("No tests found in recipe."));
}

// Determine which tests to run
let tests_to_run: Vec<usize> = match test_index {
Some(idx) => {
if idx >= tests.len() {
return Err(miette::miette!(
"Test index {} out of range. Recipe has {} test(s).",
idx,
tests.len()
));
}
vec![idx]
}
None => (0..tests.len()).collect(),
};

tracing::info!("\n=== Test Debug Mode ===");
if test_index.is_some() {
tracing::info!("Running test {} of {}", tests_to_run[0], tests.len());
} else {
tracing::info!("Running all {} tests", tests.len());
}
tracing::info!("Available tests:");
for (idx, test) in tests.iter().enumerate() {
let test_type = match test {
crate::recipe::parser::TestType::Python { .. } => "Python",
crate::recipe::parser::TestType::Perl { .. } => "Perl",
crate::recipe::parser::TestType::R { .. } => "R",
crate::recipe::parser::TestType::Ruby { .. } => "Ruby",
crate::recipe::parser::TestType::Command(_) => "Command",
crate::recipe::parser::TestType::Downstream(_) => "Downstream",
crate::recipe::parser::TestType::PackageContents { .. } => "PackageContents",
};
let marker = if tests_to_run.contains(&idx) {
">>>"
} else {
" "
};
tracing::info!("{} Test {}: {}", marker, idx, test_type);
}

// Build the package first to be able to run tests
tracing::info!("\nNote: Tests require a built package. Building package first...");

// Create a modified output with debug mode disabled for the actual build
let mut build_output = output.clone();
build_output.build_configuration.debug = crate::types::Debug::new(false);

let (built_output, archive_path) = run_build(
build_output,
&tool_config,
WorkingDirectoryBehavior::Cleanup,
)
.await?;

tracing::info!("Package built successfully: {}", archive_path.display());

// Run the test(s) with environment preservation for debugging
let config = &built_output.build_configuration;

for current_test_index in tests_to_run {
// Skip PackageContents tests as they run at build time
if matches!(
tests[current_test_index],
crate::recipe::parser::TestType::PackageContents { .. }
) {
tracing::info!(
"\nSkipping test {} (PackageContents tests run at build time)",
current_test_index
);
continue;
}

// Calculate the package test index (excluding PackageContents tests before this one)
let package_test_index = tests[..=current_test_index]
.iter()
.filter(|t| {
!matches!(t, crate::recipe::parser::TestType::PackageContents { .. })
})
.count()
- 1;

let test_config = TestConfiguration {
test_prefix: config
.directories
.output_dir
.join(format!("test_debug_{}", current_test_index)),
target_platform: Some(config.target_platform),
host_platform: Some(config.host_platform.clone()),
current_platform: config.build_platform.clone(),
keep_test_prefix: true,
test_index: Some(package_test_index),
channels: config.channels.clone(),
channel_priority: tool_config.channel_priority,
solve_strategy: SolveStrategy::Highest,
tool_configuration: tool_config.clone(),
output_dir: config.directories.output_dir.clone(),
debug: crate::types::Debug::new(false),
exclude_newer: config.exclude_newer,
};

tracing::info!(
"\nRunning test {} with environment preservation...\n",
current_test_index
);

match package_test::run_test(&archive_path, &test_config, None).await {
Ok(_) => {
tracing::info!("\nTest {} passed!", current_test_index);
}
Err(e) => {
return Err(miette::miette!("Test {} failed: {}", current_test_index, e));
}
}
}
} else {
// Build mode: provide instructions to run the build script
tracing::info!("\nTo run the actual build, use:");
tracing::info!(
"cd {} && ./conda_build.sh",
output.build_configuration.directories.work_dir.display()
"rattler-build build --recipe {}",
output.build_configuration.directories.recipe_path.display()
);
tracing::info!("Or run the build script directly with:");
if cfg!(windows) {
tracing::info!(
"cd {} && ./conda_build.bat",
output.build_configuration.directories.work_dir.display()
);
} else {
tracing::info!(
"cd {} && ./conda_build.sh",
output.build_configuration.directories.work_dir.display()
);
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,13 @@ pub struct DebugOpts {
/// Name of the specific output to debug (only required when a recipe has multiple outputs)
#[arg(long, help = "Name of the specific output to debug")]
pub output_name: Option<String>,

/// Run tests in debug mode.
#[arg(
long,
help = "Run tests in debug mode. Optionally specify test index to run a specific test (default: run all tests)"
)]
pub test: Option<Option<usize>>,
}

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

impl DebugData {
Expand All @@ -933,6 +944,8 @@ impl DebugData {
channels: opts.channels,
common: CommonData::from_opts_and_config(opts.common, config.unwrap_or_default()),
output_name: opts.output_name,
test_mode: opts.test.is_some(), // true if --test flag was provided
test_index: opts.test.flatten(), // None = all tests, Some(n) = specific test
}
}
}
Expand Down