From 8e1f6d8899a4cbfcc4cdefb72ef9fbb6f0e229cf Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 26 Jan 2024 15:02:01 +0100 Subject: [PATCH] add many more package content tests and refactor the implementation --- docs/recipe_file.md | 8 +- examples/rich/recipe.yaml | 3 + examples/xtensor/recipe.yaml | 7 + src/build.rs | 10 +- src/package_test/content_test.rs | 668 ++++++++++-------- src/package_test/mod.rs | 1 - src/recipe/parser.rs | 4 +- src/recipe/parser/test.rs | 20 +- ...recipe__parser__tests__recipe_windows.snap | 14 + ...d__recipe__parser__tests__unix_recipe.snap | 14 + test-data/package_content/test_bin_unix.yaml | 6 + test-data/package_content/test_bin_win.yaml | 16 + test-data/package_content/test_files.yaml | 16 + .../package_content/test_include_unix.yaml | 8 + .../package_content/test_include_win.yaml | 8 + test-data/package_content/test_lib_linux.yaml | 10 + test-data/package_content/test_lib_macos.yaml | 10 + test-data/package_content/test_lib_win.yaml | 13 + .../test_site_packages_unix.yaml | 13 + .../test_site_packages_win.yaml | 11 + 20 files changed, 544 insertions(+), 316 deletions(-) create mode 100644 test-data/package_content/test_bin_unix.yaml create mode 100644 test-data/package_content/test_bin_win.yaml create mode 100644 test-data/package_content/test_files.yaml create mode 100644 test-data/package_content/test_include_unix.yaml create mode 100644 test-data/package_content/test_include_win.yaml create mode 100644 test-data/package_content/test_lib_linux.yaml create mode 100644 test-data/package_content/test_lib_macos.yaml create mode 100644 test-data/package_content/test_lib_win.yaml create mode 100644 test-data/package_content/test_site_packages_unix.yaml create mode 100644 test-data/package_content/test_site_packages_win.yaml diff --git a/docs/recipe_file.md b/docs/recipe_file.md index 272315c04..b1949b1d3 100644 --- a/docs/recipe_file.md +++ b/docs/recipe_file.md @@ -727,14 +727,14 @@ import bsdiff4 import bspatch4 ``` -### Check for package-contents +### Check for package contents Checks if the built package contains the mentioned items. These checks are executed directly at the end of the build process to make sure that all expected files are present in the package. ```yaml tests: - - package-contents: + - package_contents: # checks for the existence of files inside $PREFIX or %PREFIX% # or, checks that there is at least one file matching the specified `glob` # pattern inside the prefix @@ -761,8 +761,8 @@ tests: - mamba # searches for `$PREFIX/include/libmamba/mamba.hpp` on unix, and - # on Windows for `%PREFIX%\Library\include\mamba.hpp` - includes: + # on Windows for `%PREFIX%\Library\include\libmamba\mamba.hpp` + include: - libmamba/mamba.hpp ``` diff --git a/examples/rich/recipe.yaml b/examples/rich/recipe.yaml index 34ca8fc67..f7d43f0d8 100644 --- a/examples/rich/recipe.yaml +++ b/examples/rich/recipe.yaml @@ -30,6 +30,9 @@ requirements: - typing_extensions >=4.0.0,<5.0.0 tests: + - package_contents: + site_packages: + - rich - python: imports: - rich diff --git a/examples/xtensor/recipe.yaml b/examples/xtensor/recipe.yaml index 3c90907c8..5752a3258 100644 --- a/examples/xtensor/recipe.yaml +++ b/examples/xtensor/recipe.yaml @@ -44,6 +44,13 @@ requirements: - xsimd >=8.0.3,<10 tests: + - package_contents: + include: + - xtensor/xarray.hpp + files: + - share/cmake/xtensor/xtensorConfig.cmake + - share/cmake/xtensor/xtensorConfigVersion.cmake + - script: - if: unix or emscripten then: diff --git a/src/build.rs b/src/build.rs index aefc589a2..0d8ce506d 100644 --- a/src/build.rs +++ b/src/build.rs @@ -338,13 +338,9 @@ pub async fn run_build( for test in output.recipe.tests() { // TODO we could also run each of the (potentially multiple) test scripts and collect the errors if let TestType::PackageContents(package_contents) = test { - package_test::run_package_content_test( - package_contents, - &paths_json, - &output.build_configuration.target_platform, - ) - .await - .into_diagnostic()?; + package_contents + .run_test(&paths_json, &output.build_configuration.target_platform) + .into_diagnostic()?; } } diff --git a/src/package_test/content_test.rs b/src/package_test/content_test.rs index 59dd19001..ea27edfe2 100644 --- a/src/package_test/content_test.rs +++ b/src/package_test/content_test.rs @@ -1,363 +1,342 @@ +use std::path::PathBuf; + use crate::package_test::TestError; -use crate::recipe::parser::PackageContents; -use globset::{Glob, GlobSet}; +use crate::recipe::parser::PackageContentsTest; +use globset::{Glob, GlobBuilder, GlobSet}; use rattler_conda_types::{package::PathsJson, Platform}; -impl PackageContents { +fn build_glob(glob: String) -> Result { + tracing::debug!("Building glob: {}", glob); + GlobBuilder::new(&glob).empty_alternates(true).build() +} + +fn display_success(matches: &Vec<&PathBuf>, glob: &str, section: &str) { + tracing::info!( + "{} {section}: {} matched:", + console::style(console::Emoji("✔", "")).green(), + glob + ); + for m in matches[0..std::cmp::min(5, matches.len())].iter() { + tracing::info!("- {}", m.display()); + } + if matches.len() > 5 { + tracing::info!("... and {} more", matches.len() - 5); + } +} + +impl PackageContentsTest { pub fn include_as_globs( &self, target_platform: &Platform, - ) -> Result, globset::Error> { - self.include - .iter() - .map(|include| { - if target_platform.is_windows() { - format!("Library/include/{include}") - } else { - format!("include/{include}") - } - }) - .map(|include| GlobSet::builder().add(Glob::new(&include)?).build()) - .collect::, globset::Error>>() + ) -> Result, globset::Error> { + let mut result = Vec::new(); + for include in &self.include { + let glob = if target_platform.is_windows() { + format!("Library/include/{include}") + } else { + format!("include/{include}") + }; + + result.push(( + include.clone(), + GlobSet::builder().add(build_glob(glob)?).build()?, + )); + } + + Ok(result) } - pub fn bin_as_globs(&self, target_platform: &Platform) -> Result, globset::Error> { - self.bin - .iter() - .map(|bin| { - if target_platform.is_windows() { - GlobSet::builder() - .add(Glob::new(bin)?) - .add(Glob::new(&format!("Library/mingw-w64/bin/{bin}"))?) - .add(Glob::new(&format!("Library/usr/bin/{bin}"))?) - .add(Glob::new(&format!("Library/bin/{bin}"))?) - .add(Glob::new(&format!("Scripts/{bin}"))?) - .add(Glob::new(&format!("bin/{bin}"))?) - .build() - } else { - GlobSet::builder() - .add(Glob::new(&format!("bin/{bin}"))?) - .build() - } - }) - .collect::, globset::Error>>() + pub fn bin_as_globs( + &self, + target_platform: &Platform, + ) -> Result, globset::Error> { + let mut result = Vec::new(); + + for bin in &self.bin { + let globset = if target_platform.is_windows() { + // This is usually encoded as `PATHEXT` in the environment + let path_ext = "{,.exe,.bat,.cmd,.com,.ps1}"; + GlobSet::builder() + .add(build_glob(format!("{bin}{path_ext}"))?) + .add(build_glob(format!( + "Library/mingw-w64/bin/{bin}{path_ext}" + ))?) + .add(build_glob(format!("Library/usr/bin/{bin}{path_ext}"))?) + .add(build_glob(format!("Library/bin/{bin}{path_ext}"))?) + .add(build_glob(format!("Scripts/{bin}{path_ext}"))?) + .add(build_glob(format!("bin/{bin}{path_ext}"))?) + .build() + } else { + GlobSet::builder() + .add(Glob::new(&format!("bin/{bin}"))?) + .build() + }?; + + result.push((bin.clone(), globset)); + } + + Ok(result) } - pub fn lib_as_globs(&self, target_platform: &Platform) -> Result, globset::Error> { + pub fn lib_as_globs( + &self, + target_platform: &Platform, + ) -> Result, globset::Error> { + let mut result = Vec::new(); + if target_platform.is_windows() { // Windows is special because it requires both a `.dll` and a `.bin` file - let mut result = Vec::new(); for lib in &self.lib { - result.push( - GlobSet::builder() - .add(Glob::new(&format!("Library/lib/{lib}.dll"))?) - .build()?, - ); - result.push( - GlobSet::builder() - .add(Glob::new(&format!("Library/bin/{lib}.bin"))?) - .build()?, - ); + if lib.ends_with(".dll") { + result.push(( + lib.clone(), + GlobSet::builder() + .add(Glob::new(&format!("bin/{lib}"))?) + .build()?, + )); + } else if lib.ends_with(".lib") { + result.push(( + lib.clone(), + GlobSet::builder() + .add(Glob::new(&format!("lib/{lib}"))?) + .build()?, + )); + } else { + result.push(( + lib.clone(), + GlobSet::builder() + .add(Glob::new(&format!("Library/bin/{lib}.dll"))?) + .build()?, + )); + result.push(( + lib.clone(), + GlobSet::builder() + .add(Glob::new(&format!("Library/lib/{lib}.lib"))?) + .build()?, + )); + } } - Ok(result) } else { - self.lib - .iter() - .map(|lib| { - if target_platform.is_osx() { - if lib.ends_with(".dylib") || lib.ends_with(".a") { - GlobSet::builder() - .add(Glob::new(&format!("lib/{lib}"))?) - .build() - } else { - GlobSet::builder() - .add(Glob::new(&format!("lib/{lib}.dylib"))?) - .add(Glob::new(&format!("lib/{lib}.*.dylib"))?) - .add(Glob::new(&format!("lib/lib{lib}.dylib"))?) - .add(Glob::new(&format!("lib/lib{lib}.*.dylib"))?) - .build() - } - } else if target_platform.is_linux() { - if lib.ends_with(".so") || lib.ends_with(".a") { - GlobSet::builder() - .add(Glob::new(&format!("lib/{lib}"))?) - .build() - } else { - GlobSet::builder() - .add(Glob::new(&format!("lib/{lib}.so"))?) - .add(Glob::new(&format!("lib/{lib}.*.so"))?) - .add(Glob::new(&format!("lib/lib{lib}.so"))?) - .add(Glob::new(&format!("lib/lib{lib}.*.so"))?) - .build() - } + for lib in &self.lib { + let globset = if target_platform.is_osx() { + if lib.ends_with(".dylib") || lib.ends_with(".a") { + GlobSet::builder() + .add(Glob::new(&format!("lib/{lib}"))?) + .build() + } else { + GlobSet::builder() + .add(build_glob(format!("lib/{{,lib}}{lib}.dylib"))?) + .add(build_glob(format!("lib/{{,lib}}{lib}.*.dylib"))?) + .build() + } + } else if target_platform.is_linux() { + if lib.ends_with(".so") || lib.contains(".so.") || lib.ends_with(".a") { + GlobSet::builder() + .add(Glob::new(&format!("lib/{lib}"))?) + .build() } else { - // TODO - unimplemented!("lib_as_globs for target platform: {:?}", target_platform) + GlobSet::builder() + .add(build_glob(format!("lib/{{,lib}}{lib}.so"))?) + .add(build_glob(format!("lib/{{,lib}}{lib}.so.*"))?) + .build() } - }) - .collect::, globset::Error>>() + } else { + // TODO + unimplemented!("lib_as_globs for target platform: {:?}", target_platform) + }?; + result.push((lib.clone(), globset)); + } } - } -} -/// Run package content tests. -/// # Arguments -/// -/// * `package_content` : The package content test format struct ref. -/// -/// # Returns -/// -/// * `Ok(())` if the test was successful -/// * `Err(TestError::TestFailed)` if the test failed -pub async fn run_package_content_test( - package_content: &PackageContents, - paths_json: &PathsJson, - target_platform: &Platform, -) -> Result<(), TestError> { - // files globset - let mut file_globs = vec![]; - for file_path in &package_content.files { - file_globs.push((file_path, globset::Glob::new(file_path)?.compile_matcher())); + Ok(result) } - // site packages - let site_package_path = globset::Glob::new("**/site-packages/**")?.compile_matcher(); - let mut site_packages = vec![]; - for sp in &package_content.site_packages { - let mut s = String::new(); - s.extend(sp.split('.').flat_map(|s| [s, "/"])); - s.push_str("/__init__.py"); - site_packages.push((sp, s)); - } + pub fn site_packages_as_globs( + &self, + target_platform: &Platform, + ) -> Result, globset::Error> { + let mut result = Vec::new(); - // binaries - let binary_dir = if target_platform.is_windows() { - "**/Library/bin/**" - } else { - "**/bin/**" - }; - let binary_dir = globset::Glob::new(binary_dir)?.compile_matcher(); - let mut binary_names = package_content - .bin - .iter() - .map(|bin| { - if target_platform.is_windows() { - bin.to_owned() + ".exe" - } else { - bin.to_owned() - } - }) - .collect::>(); - - // libraries - let library_dir = if target_platform.is_windows() { - "Library" - } else { - "lib" - }; - let mut libraries = vec![]; - for lib in &package_content.lib { - if target_platform.is_windows() { - libraries.push(( - lib, - globset::Glob::new(format!("**/{library_dir}/lib/{lib}.dll").as_str())? - .compile_matcher(), - globset::Glob::new(format!("**/{library_dir}/bin/{lib}.lib").as_str())? - .compile_matcher(), - )); - } else if target_platform.is_osx() { - libraries.push(( - lib, - globset::Glob::new(format!("**/{library_dir}/{lib}.dylib").as_str())? - .compile_matcher(), - globset::Glob::new(format!("**/{library_dir}/{lib}.a").as_str())?.compile_matcher(), - )); - } else if target_platform.is_unix() { - libraries.push(( - lib, - globset::Glob::new(format!("**/{library_dir}/{lib}.so").as_str())? - .compile_matcher(), - globset::Glob::new(format!("**/{library_dir}/{lib}.a").as_str())?.compile_matcher(), - )); + let site_packages_base = if target_platform.is_windows() { + "Lib/site-packages" + } else if matches!(target_platform, Platform::NoArch) { + "site-packages" } else { - return Err(TestError::PackageContentTestFailedStr( - "Package test on target not supported.", - )); + "lib/python*/site-packages" + }; + + for site_package in &self.site_packages { + let mut globset = GlobSet::builder(); + + if site_package.contains('/') { + globset.add(build_glob(format!("{site_packages_base}/{site_package}"))?); + } else { + let mut splitted = site_package.split('.').collect::>(); + let last_elem = splitted.pop().unwrap_or_default(); + let mut site_package_path = splitted.join("/"); + if !site_package_path.is_empty() { + site_package_path.push('/'); + } + + globset.add(build_glob(format!( + "{site_packages_base}/{site_package_path}{last_elem}.py" + ))?); + globset.add(build_glob(format!( + "{site_packages_base}/{site_package_path}{last_elem}/__init__.py" + ))?); + }; + let globset = globset.build()?; + result.push((site_package.clone(), globset)); } + + Ok(result) } - // includes - let include_path = if target_platform.is_windows() { - "Library/include/" - } else { - "include/" - }; - let include_path = globset::Glob::new(include_path)?.compile_matcher(); - let mut includes = vec![]; - for include in &package_content.include { - includes.push(( - include, - globset::Glob::new(include.as_str())?.compile_matcher(), - )); + pub fn files_as_globs(&self) -> Result, globset::Error> { + let mut result = Vec::new(); + + for file in &self.files { + let globset = GlobSet::builder().add(Glob::new(file)?).build()?; + result.push((file.clone(), globset)); + } + + Ok(result) } - for path in &paths_json.paths { - // check if all site_packages present - if !site_packages.is_empty() && site_package_path.is_match(&path.relative_path) { - let mut s = None; - for (i, sp) in site_packages.iter().enumerate() { - // this checks for exact component level match - if path.relative_path.ends_with(&sp.1) { - s = Some(i); - break; + pub fn run_test(&self, paths: &PathsJson, target_platform: &Platform) -> Result<(), TestError> { + let paths = paths + .paths + .iter() + .map(|p| &p.relative_path) + .collect::>(); + + let include_globs = self.include_as_globs(target_platform)?; + let bin_globs = self.bin_as_globs(target_platform)?; + let lib_globs = self.lib_as_globs(target_platform)?; + let site_package_globs = self.site_packages_as_globs(target_platform)?; + let file_globs = self.files_as_globs()?; + + fn match_glob<'a>(glob: &GlobSet, paths: &'a Vec<&PathBuf>) -> Vec<&'a PathBuf> { + let mut matches: Vec<&'a PathBuf> = Vec::new(); + for path in paths { + if glob.is_match(path) { + matches.push(path); } } - if let Some(i) = s { - // can panic, but panic here is unreachable - site_packages.swap_remove(i); - } + matches } - // check if all file globs have a match - if !file_globs.is_empty() { - let mut s = None; - for (i, (_, fm)) in file_globs.iter().enumerate() { - if fm.is_match(&path.relative_path) { - s = Some(i); - break; - } + let mut collected_issues = Vec::new(); + + for glob in include_globs { + let matches = match_glob(&glob.1, &paths); + + if !matches.is_empty() { + display_success(&matches, &glob.0, "include"); } - if let Some(i) = s { - // can panic, but panic here is unreachable - file_globs.swap_remove(i); + + if matches.is_empty() { + collected_issues.push(format!("No match for include glob: {}", glob.0)); } } - // check if all includes have a match - if !includes.is_empty() && include_path.is_match(&path.relative_path) { - let mut s = None; - for (i, inc) in includes.iter().enumerate() { - if inc.1.is_match(&path.relative_path) { - s = Some(i); - break; - } + for glob in bin_globs { + let matches = match_glob(&glob.1, &paths); + + if !matches.is_empty() { + display_success(&matches, &glob.0, "bin"); } - if let Some(i) = s { - // can panic, but panic here is unreachable - includes.swap_remove(i); + + if matches.is_empty() { + collected_issues.push(format!("No match for bin glob: {}", glob.0)); } } - // check if for all all, either a static or dynamic library have a match - if !libraries.is_empty() { - let mut s = None; - for (i, l) in libraries.iter().enumerate() { - if l.1.is_match(&path.relative_path) || l.2.is_match(&path.relative_path) { - s = Some(i); - break; - } + for glob in lib_globs { + let matches = match_glob(&glob.1, &paths); + + if !matches.is_empty() { + display_success(&matches, &glob.0, "bin"); } - if let Some(i) = s { - // can panic, but panic here is unreachable - libraries.swap_remove(i); + + if matches.is_empty() { + collected_issues.push(format!("No match for lib glob: {}", glob.0)); } } - // check if all binaries have a match - if !binary_names.is_empty() && binary_dir.is_match(&path.relative_path) { - let mut s = None; - for (i, b) in binary_names.iter().enumerate() { - // the matches component-wise as b is single level, - // it just matches the last component - if path.relative_path.ends_with(b) { - s = Some(i); - break; - } + for glob in site_package_globs { + let matches = match_glob(&glob.1, &paths); + + if !matches.is_empty() { + display_success(&matches, &glob.0, "bin"); } - if let Some(i) = s { - // can panic, but panic here is unreachable - binary_names.swap_remove(i); + + if matches.is_empty() { + collected_issues.push(format!("No match for site_package glob: {}", glob.0)); } } - } - let mut error = String::new(); - if !file_globs.is_empty() { - error.push_str(&format!( - "Some file glob matches not found in package contents.\n{:?}", - file_globs - .into_iter() - .map(|s| s.0) - .collect::>() - )); - } - if !site_packages.is_empty() { - if !error.is_empty() { - error.push('\n'); - } - error.push_str(&format!( - "Some site packages not found in package contents.\n{:?}", - site_packages - .into_iter() - .map(|s| s.0) - .collect::>() - )); - } - if !includes.is_empty() { - if !error.is_empty() { - error.push('\n'); - } - error.push_str(&format!( - "Some includes not found in package contents.\n{:?}", - includes.into_iter().map(|s| s.0).collect::>() - )); - } - if !libraries.is_empty() { - if !error.is_empty() { - error.push('\n'); + + for glob in file_globs { + let matches = match_glob(&glob.1, &paths); + + if !matches.is_empty() { + display_success(&matches, &glob.0, "bin"); + } + + if matches.is_empty() { + collected_issues.push(format!("No match for site_package glob: {}", glob.0)); + } } - error.push_str(&format!( - "Some libraries not found in package contents.\n{:?}", - libraries.into_iter().map(|s| s.0).collect::>() - )); - } - if !binary_names.is_empty() { - if !error.is_empty() { - error.push('\n'); + + if !collected_issues.is_empty() { + tracing::error!("Package content test failed:"); + for issue in &collected_issues { + tracing::error!( + "- {} {}", + console::style(console::Emoji("❌", " ")).red(), + issue + ); + } + + return Err(TestError::PackageContentTestFailed( + collected_issues.join("\n"), + )); } - error.push_str(&format!( - "Some binaries not found in package contents.\n{:?}", - binary_names - )); - } - if error.is_empty() { + Ok(()) - } else { - Err(TestError::PackageContentTestFailed(error)) } } #[cfg(test)] mod tests { - use super::PackageContents; + use std::path::Path; + + use super::PackageContentsTest; + use globset::GlobSet; use rattler_conda_types::Platform; + use serde::Deserialize; #[derive(Debug)] enum MatchError { NoMatch, } - fn test_glob_matches(globs: Vec, paths: Vec<&str>) -> Result<(), MatchError> { + fn test_glob_matches( + globs: &Vec<(String, GlobSet)>, + paths: &[String], + ) -> Result<(), MatchError> { let mut matches = Vec::new(); for path in paths { let mut has_match = false; for (idx, glob) in globs.iter().enumerate() { - if glob.is_match(path) { + if glob.1.is_match(path) { has_match = true; matches.push((idx, path)); } } if !has_match { + println!("No match for path: {}", path); return Err(MatchError::NoMatch); } } @@ -367,7 +346,7 @@ mod tests { #[test] fn test_include_globs() { - let package_contents = PackageContents { + let package_contents = PackageContentsTest { include: vec!["foo".into(), "bar".into()], ..Default::default() }; @@ -376,9 +355,10 @@ mod tests { .include_as_globs(&Platform::Linux64) .unwrap(); - test_glob_matches(globs, vec!["include/foo", "include/bar"]).unwrap(); + let paths = &["include/foo".to_string(), "include/bar".to_string()]; + test_glob_matches(&globs, paths).unwrap(); - let package_contents = PackageContents { + let package_contents = PackageContentsTest { include: vec!["foo".into(), "bar".into()], ..Default::default() }; @@ -387,6 +367,110 @@ mod tests { .include_as_globs(&Platform::Linux64) .unwrap(); - test_glob_matches(globs, vec!["lib/foo", "asd/bar"]).unwrap_err(); + let paths = &["lib/foo".to_string(), "asd/bar".to_string()]; + test_glob_matches(&globs, paths).unwrap_err(); + } + + #[derive(Debug, Deserialize)] + struct TestCase { + platform: Platform, + package_contents: PackageContentsTest, + paths: Vec, + #[serde(default)] + fail_paths: Vec, + } + + fn load_test_case(path: &Path) -> TestCase { + let test_data_dir = + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data/package_content"); + let file = std::fs::File::open(test_data_dir.join(path)).unwrap(); + serde_yaml::from_reader(file).unwrap() + } + + fn evaluate_test_case(test_case: TestCase) -> Result<(), MatchError> { + let tests = test_case.package_contents; + + if !tests.include.is_empty() { + println!("include globs: {:?}", tests.include); + let globs = tests.include_as_globs(&test_case.platform).unwrap(); + test_glob_matches(&globs, &test_case.paths)?; + if !test_case.fail_paths.is_empty() { + test_glob_matches(&globs, &test_case.fail_paths).unwrap_err(); + } + } + + if !tests.bin.is_empty() { + println!("bin globs: {:?}", tests.bin); + let globs = tests.bin_as_globs(&test_case.platform).unwrap(); + test_glob_matches(&globs, &test_case.paths)?; + if !test_case.fail_paths.is_empty() { + test_glob_matches(&globs, &test_case.fail_paths).unwrap_err(); + } + } + + if !tests.lib.is_empty() { + println!("lib globs: {:?}", tests.lib); + let globs = tests.lib_as_globs(&test_case.platform).unwrap(); + test_glob_matches(&globs, &test_case.paths)?; + if !test_case.fail_paths.is_empty() { + test_glob_matches(&globs, &test_case.fail_paths).unwrap_err(); + } + } + + if !tests.site_packages.is_empty() { + println!("site_package globs: {:?}", tests.site_packages); + let globs = tests.site_packages_as_globs(&test_case.platform).unwrap(); + test_glob_matches(&globs, &test_case.paths)?; + if !test_case.fail_paths.is_empty() { + test_glob_matches(&globs, &test_case.fail_paths).unwrap_err(); + } + } + + Ok(()) + } + + #[test] + fn test_include_globs_yaml() { + let test_case = load_test_case(Path::new("test_include_unix.yaml")); + evaluate_test_case(test_case).unwrap(); + + let test_case = load_test_case(Path::new("test_include_win.yaml")); + evaluate_test_case(test_case).unwrap(); + } + + #[test] + fn test_bin_globs() { + let test_case = load_test_case(Path::new("test_bin_unix.yaml")); + evaluate_test_case(test_case).unwrap(); + + let test_case = load_test_case(Path::new("test_bin_win.yaml")); + evaluate_test_case(test_case).unwrap(); + } + + #[test] + fn test_lib_globs() { + let test_case = load_test_case(Path::new("test_lib_linux.yaml")); + evaluate_test_case(test_case).unwrap(); + + let test_case = load_test_case(Path::new("test_lib_macos.yaml")); + evaluate_test_case(test_case).unwrap(); + + let test_case = load_test_case(Path::new("test_lib_win.yaml")); + evaluate_test_case(test_case).unwrap(); + } + + #[test] + fn test_site_package_globs() { + let test_case = load_test_case(Path::new("test_site_packages_unix.yaml")); + evaluate_test_case(test_case).unwrap(); + + let test_case = load_test_case(Path::new("test_site_packages_win.yaml")); + evaluate_test_case(test_case).unwrap(); + } + + #[test] + fn test_file_globs() { + let test_case = load_test_case(Path::new("test_files.yaml")); + evaluate_test_case(test_case).unwrap(); } } diff --git a/src/package_test/mod.rs b/src/package_test/mod.rs index b4361eb27..697fe6c89 100644 --- a/src/package_test/mod.rs +++ b/src/package_test/mod.rs @@ -2,6 +2,5 @@ mod content_test; mod run_test; mod serialize_test; -pub use content_test::run_package_content_test; pub use run_test::{run_test, TestConfiguration, TestError}; pub(crate) use serialize_test::write_test_files; diff --git a/src/recipe/parser.rs b/src/recipe/parser.rs index c1c708e54..95c85871f 100644 --- a/src/recipe/parser.rs +++ b/src/recipe/parser.rs @@ -39,8 +39,8 @@ pub use self::{ script::{Script, ScriptContent}, source::{Checksum, GitRev, GitSource, GitUrl, PathSource, Source, UrlSource}, test::{ - CommandsTest, CommandsTestFiles, CommandsTestRequirements, DownstreamTest, PackageContents, - PythonTest, TestType, + CommandsTest, CommandsTestFiles, CommandsTestRequirements, DownstreamTest, + PackageContentsTest, PythonTest, TestType, }, }; diff --git a/src/recipe/parser/test.rs b/src/recipe/parser/test.rs index 121ae9eb3..4ca140cda 100644 --- a/src/recipe/parser/test.rs +++ b/src/recipe/parser/test.rs @@ -86,12 +86,12 @@ pub enum TestType { /// A test that runs the tests of a downstream package Downstream(DownstreamTest), /// A test that checks the contents of the package - PackageContents(PackageContents), + PackageContents(PackageContentsTest), } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] /// PackageContent -pub struct PackageContents { +pub struct PackageContentsTest { /// file paths, direct and/or globs #[serde(default, skip_serializing_if = "Vec::is_empty")] pub files: Vec, @@ -142,7 +142,7 @@ impl TryConvertNode for RenderedNode { *self.span(), ErrorKind::ExpectedMapping, )])?, - RenderedNode::Null(_) => Ok(TestType::PackageContents(PackageContents::default())), + RenderedNode::Null(_) => Ok(TestType::PackageContents(PackageContentsTest::default())), } } } @@ -162,7 +162,7 @@ pub fn as_mapping( impl TryConvertNode for RenderedMappingNode { fn try_convert(&self, name: &str) -> Result> { - let mut test = TestType::PackageContents(PackageContents::default()); + let mut test = TestType::PackageContents(PackageContentsTest::default()); self.iter().map(|(key, value)| { let key_str = key.as_str(); @@ -365,21 +365,21 @@ impl TryConvertNode for RenderedMappingNode { /// Package Contents /// /////////////////////////// -impl TryConvertNode for RenderedNode { - fn try_convert(&self, name: &str) -> Result> { +impl TryConvertNode for RenderedNode { + fn try_convert(&self, name: &str) -> Result> { match self { RenderedNode::Mapping(map) => map.try_convert(name), RenderedNode::Sequence(_) | RenderedNode::Scalar(_) => Err(vec![_partialerror!( *self.span(), ErrorKind::ExpectedMapping, )])?, - RenderedNode::Null(_) => Ok(PackageContents::default()), + RenderedNode::Null(_) => Ok(PackageContentsTest::default()), } } } -impl TryConvertNode for RenderedMappingNode { - fn try_convert(&self, name: &str) -> Result> { +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { let mut files = vec![]; let mut site_packages = vec![]; let mut lib = vec![]; @@ -403,7 +403,7 @@ impl TryConvertNode for RenderedMappingNode { Ok(()) }).flatten_errors()?; - Ok(PackageContents { + Ok(PackageContentsTest { files, site_packages, bin, diff --git a/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap b/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap index eb5e1269c..6df733b7f 100644 --- a/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap +++ b/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap @@ -268,6 +268,20 @@ Recipe { }, }, tests: [ + PackageContents( + PackageContentsTest { + files: [ + "share/cmake/xtensor/xtensorConfig.cmake", + "share/cmake/xtensor/xtensorConfigVersion.cmake", + ], + site_packages: [], + bin: [], + lib: [], + include: [ + "xtensor/xarray.hpp", + ], + }, + ), Command( CommandsTest { script: [ diff --git a/src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap b/src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap index 4b45ea1f1..0fb0677e6 100644 --- a/src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap +++ b/src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap @@ -287,6 +287,20 @@ Recipe { }, }, tests: [ + PackageContents( + PackageContentsTest { + files: [ + "share/cmake/xtensor/xtensorConfig.cmake", + "share/cmake/xtensor/xtensorConfigVersion.cmake", + ], + site_packages: [], + bin: [], + lib: [], + include: [ + "xtensor/xarray.hpp", + ], + }, + ), Command( CommandsTest { script: [ diff --git a/test-data/package_content/test_bin_unix.yaml b/test-data/package_content/test_bin_unix.yaml new file mode 100644 index 000000000..0c734130b --- /dev/null +++ b/test-data/package_content/test_bin_unix.yaml @@ -0,0 +1,6 @@ +platform: linux-64 +package_contents: + bin: + - curl +paths: + - bin/curl diff --git a/test-data/package_content/test_bin_win.yaml b/test-data/package_content/test_bin_win.yaml new file mode 100644 index 000000000..67a848b0a --- /dev/null +++ b/test-data/package_content/test_bin_win.yaml @@ -0,0 +1,16 @@ +platform: win-64 +package_contents: + bin: + - curl + - curl-mingw2 + - python-entry-point + - noextension + - oldschool + - batch +paths: + - Library/bin/curl.exe + - Library/bin/noextension + - Library/mingw-w64/bin/curl.exe + - Scripts/python-entry-point.exe + - oldschool.com + - batch.bat diff --git a/test-data/package_content/test_files.yaml b/test-data/package_content/test_files.yaml new file mode 100644 index 000000000..38ad86d51 --- /dev/null +++ b/test-data/package_content/test_files.yaml @@ -0,0 +1,16 @@ +platform: linux-64 +package_contents: + files: + - assets/**/*.mp3 + - images/cover.jpg + - pkg-config/test.pc + - pkg-config/*.pc +paths: + - assets/music/track1.mp3 + - assets/music/interpret/track2.mp3 + - images/cover.jpg + - pkg-config/test.pc + - pkg-config/bla.pc +fail_paths: + - assets/music/track1.ogg + diff --git a/test-data/package_content/test_include_unix.yaml b/test-data/package_content/test_include_unix.yaml new file mode 100644 index 000000000..c20871f9b --- /dev/null +++ b/test-data/package_content/test_include_unix.yaml @@ -0,0 +1,8 @@ +platform: linux-64 +package_contents: + include: + - xtensor.hpp + - xtensor/xarray.hpp +paths: + - include/xtensor.hpp + - include/xtensor/xarray.hpp diff --git a/test-data/package_content/test_include_win.yaml b/test-data/package_content/test_include_win.yaml new file mode 100644 index 000000000..9545232a7 --- /dev/null +++ b/test-data/package_content/test_include_win.yaml @@ -0,0 +1,8 @@ +platform: win-64 +package_contents: + include: + - xtensor.hpp + - xtensor/xarray.hpp +paths: + - Library/include/xtensor.hpp + - Library/include/xtensor/xarray.hpp diff --git a/test-data/package_content/test_lib_linux.yaml b/test-data/package_content/test_lib_linux.yaml new file mode 100644 index 000000000..2b661cbc3 --- /dev/null +++ b/test-data/package_content/test_lib_linux.yaml @@ -0,0 +1,10 @@ +platform: linux-64 +package_contents: + lib: + - test + - testb + - testc +paths: + - lib/libtest.so + - lib/testb.so + - lib/testc.so.1.0.0 diff --git a/test-data/package_content/test_lib_macos.yaml b/test-data/package_content/test_lib_macos.yaml new file mode 100644 index 000000000..a8c6a2184 --- /dev/null +++ b/test-data/package_content/test_lib_macos.yaml @@ -0,0 +1,10 @@ +platform: osx-64 +package_contents: + lib: + - test + - testb + - testc +paths: + - lib/libtest.dylib + - lib/testb.4.dylib + - lib/libtestc.1.0.0.dylib diff --git a/test-data/package_content/test_lib_win.yaml b/test-data/package_content/test_lib_win.yaml new file mode 100644 index 000000000..60d3aecf5 --- /dev/null +++ b/test-data/package_content/test_lib_win.yaml @@ -0,0 +1,13 @@ +platform: win-64 +package_contents: + lib: + - test + - testb + - testc +paths: + - Library/lib/test.lib + - Library/bin/test.dll + - Library/lib/testb.lib + - Library/bin/testb.dll + - Library/lib/testc.lib + - Library/bin/testc.dll diff --git a/test-data/package_content/test_site_packages_unix.yaml b/test-data/package_content/test_site_packages_unix.yaml new file mode 100644 index 000000000..63ce8029c --- /dev/null +++ b/test-data/package_content/test_site_packages_unix.yaml @@ -0,0 +1,13 @@ +platform: linux-64 +package_contents: + site_packages: + - numpy + - numpy.superfunc + - numpy.xfunc + - numpy.blabla + - numpy/blabla* + +paths: + - lib/python3.8/site-packages/numpy/__init__.py + - lib/python3.8/site-packages/numpy/blabla.py + - lib/python3.8/site-packages/numpy/superfunc/__init__.py diff --git a/test-data/package_content/test_site_packages_win.yaml b/test-data/package_content/test_site_packages_win.yaml new file mode 100644 index 000000000..2e76fafca --- /dev/null +++ b/test-data/package_content/test_site_packages_win.yaml @@ -0,0 +1,11 @@ +platform: win-64 +package_contents: + site_packages: + - numpy + - numpy.superfunc + - numpy.blabla + +paths: + - Lib/site-packages/numpy/__init__.py + - Lib/site-packages/numpy/blabla.py + - Lib/site-packages/numpy/superfunc/__init__.py