diff --git a/examples/rich/recipe.yaml b/examples/rich/recipe.yaml index 0e141737..34ca8fc6 100644 --- a/examples/rich/recipe.yaml +++ b/examples/rich/recipe.yaml @@ -31,13 +31,8 @@ requirements: tests: - python: - imports: + imports: - rich - - script: - - pip check - requirements: - run: - - pip about: homepage: https://github.com/Textualize/rich diff --git a/rust-tests/src/lib.rs b/rust-tests/src/lib.rs index 77a4ebf6..98c42584 100644 --- a/rust-tests/src/lib.rs +++ b/rust-tests/src/lib.rs @@ -429,7 +429,7 @@ mod tests { .join("info/tests/1/test_time_dependencies.json") .exists()); - assert!(pkg.join("info/tests/0/run_test.py").exists()); + assert!(pkg.join("info/tests/0/python_test.json").exists()); // make sure that the entry point does not exist assert!(!pkg.join("python-scripts/flask").exists()); diff --git a/src/package_test.rs b/src/package_test.rs index 74c1247a..036a4c76 100644 --- a/src/package_test.rs +++ b/src/package_test.rs @@ -26,7 +26,9 @@ use rattler_shell::{ }; use crate::{ - env_vars, index, recipe::parser::CommandsTestRequirements, render::solver::create_environment, + env_vars, index, + recipe::parser::{CommandsTestRequirements, PackageContents, PythonTest}, + render::solver::create_environment, tool_configuration, }; @@ -368,19 +370,25 @@ pub async fn run_test(package_file: &Path, config: &TestConfiguration) -> Result async fn run_python_test( pkg: &ArchiveIdentifier, - _path: &Path, + path: &Path, prefix: &Path, config: &TestConfiguration, ) -> Result<(), TestError> { - // create environment with the test dependencies + let test_file = path.join("python_test.json"); + let test: PythonTest = serde_json::from_str(&fs::read_to_string(test_file)?)?; + let match_spec = MatchSpec::from_str(format!("{}={}={}", pkg.name, pkg.version, pkg.build_string).as_str()) .unwrap(); + let mut dependencies = vec![match_spec]; + if test.pip_check { + dependencies.push(MatchSpec::from_str("pip").unwrap()); + } let platform = Platform::current(); create_environment( - &[match_spec], + &dependencies, &platform, prefix, &config.channels, @@ -389,7 +397,29 @@ async fn run_python_test( .await .map_err(TestError::TestEnvironmentSetup)?; - Ok(()) + let default_shell = ShellEnum::default(); + + let mut test_file = tempfile::Builder::new() + .prefix("rattler-test-") + .suffix(".py") + .tempfile()?; + + for import in test.imports { + writeln!(test_file, "import {}", import)?; + } + + run_in_environment( + default_shell.clone(), + format!("python {}", test_file.path().to_string_lossy()), + path, + prefix, + )?; + + if test.pip_check { + run_in_environment(default_shell, "pip check".into(), path, prefix) + } else { + Ok(()) + } } async fn run_shell_test( @@ -398,7 +428,6 @@ async fn run_shell_test( prefix: &Path, config: &TestConfiguration, ) -> Result<(), TestError> { - // read the test dependencies let deps = if path.join("test_time_dependencies.json").exists() { let test_dep_json = path.join("test_time_dependencies.json"); serde_json::from_str(&fs::read_to_string(test_dep_json)?)? @@ -441,21 +470,10 @@ async fn run_shell_test( path.join("run_test.sh") }; - let contents = fs::read_to_string(&test_file_path)?; - let is_path_ext = |ext: &str| { - test_file_path - .extension() - .map(|s| s.eq(ext)) - .unwrap_or_default() - }; + let contents = fs::read_to_string(test_file_path)?; - if Platform::current().is_windows() && is_path_ext("bat") { - tracing::info!("Testing commands:"); - run_in_environment(default_shell, contents, path, prefix)?; - } else if Platform::current().is_unix() && is_path_ext("sh") { - tracing::info!("Testing commands:"); - run_in_environment(default_shell, contents, path, prefix)?; - } + tracing::info!("Testing commands:"); + run_in_environment(default_shell, contents, path, prefix)?; Ok(()) } @@ -466,9 +484,7 @@ async fn run_individual_test( prefix: &Path, config: &TestConfiguration, ) -> Result<(), TestError> { - // detect which of the test files we have - if path.join("run_test.py").exists() { - // run python test + if path.join("python_test.json").exists() { run_python_test(pkg, path, prefix, config).await?; } else if path.join("run_test.sh").exists() || path.join("run_test.bat").exists() { // run shell test @@ -490,7 +506,7 @@ async fn run_individual_test( /// * `Ok(())` if the test was successful /// * `Err(TestError::TestFailed)` if the test failed pub async fn run_package_content_tests( - package_content: &crate::recipe::parser::PackageContents, + package_content: &PackageContents, paths_json: &PathsJson, target_platform: &Platform, ) -> Result<(), TestError> { diff --git a/src/packaging.rs b/src/packaging.rs index 286026f3..beec68d5 100644 --- a/src/packaging.rs +++ b/src/packaging.rs @@ -625,17 +625,6 @@ fn copy_license_files( let licenses_folder = tmp_dir_path.join("info/licenses/"); fs::create_dir_all(&licenses_folder)?; - for license_glob in license_globs - .iter() - // Only license globs that do not end with '/' or '*' - .filter(|license_glob| !license_glob.ends_with('/') && !license_glob.ends_with('*')) - { - let filepath = licenses_folder.join(license_glob); - if !filepath.exists() { - tracing::warn!(path = %filepath.display(), "File does not exist"); - } - } - let copy_dir = crate::source::copy_dir::CopyDir::new( &output.build_configuration.directories.recipe_dir, &licenses_folder, @@ -828,12 +817,8 @@ fn serialize_python_test( let folder = tmp_dir_path.join(format!("info/tests/{}", idx)); fs::create_dir_all(&folder)?; - let path = folder.join("run_test.py"); - let mut file = File::create(&path)?; - - for el in &python_test.imports { - writeln!(file, "import {}", el)?; - } + let path = folder.join("python_test.json"); + serde_json::to_writer(&File::create(&path)?, python_test)?; Ok(vec![path]) } diff --git a/src/recipe/parser/test.rs b/src/recipe/parser/test.rs index 2aa11e6b..845f49be 100644 --- a/src/recipe/parser/test.rs +++ b/src/recipe/parser/test.rs @@ -18,9 +18,11 @@ use super::FlattenErrors; pub struct CommandsTestRequirements { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub run: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub build: Vec, } + #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct CommandsTestFiles { // TODO parse as globs @@ -51,10 +53,23 @@ impl CommandsTestFiles { } } +fn default_pip_check() -> bool { + true +} + +fn is_true(value: &bool) -> bool { + *value +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PythonTest { + /// List of imports to test pub imports: Vec, + /// Wether to run `pip check` or not (default to true) + #[serde(default = "default_pip_check", skip_serializing_if = "is_true")] + pub pip_check: bool, } + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct DownstreamTest { pub downstream: String, @@ -216,12 +231,14 @@ impl TryConvertNode for RenderedMappingNode { impl TryConvertNode for RenderedMappingNode { fn try_convert(&self, name: &str) -> Result> { let mut imports = vec![]; + let mut pip_check = true; self.iter() .map(|(key, value)| { let key_str = key.as_str(); match key_str { "imports" => imports = value.try_convert(key_str)?, + "pip_check" => pip_check = value.try_convert(key_str)?, invalid => Err(vec![_partialerror!( *key.span(), ErrorKind::InvalidField(invalid.to_string().into()), @@ -240,7 +257,7 @@ impl TryConvertNode for RenderedMappingNode { )])?; } - Ok(PythonTest { imports }) + Ok(PythonTest { imports, pip_check }) } }