diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4849d39a..20510442 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,20 @@ exclude: '(\.patch|\.diff|\.snap|\.ambr|test-data/recipes/test-parsing/.+)$' repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black - args: [--safe, --quiet] - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 - hooks: - - id: isort - exclude: tests/data - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - args: ['--max-line-length=120'] - language_version: python3 - additional_dependencies: - - flake8-typing-imports==1.15.0 - - flake8-builtins==2.1.0 - - flake8-bugbear==23.9.16 - - flake8-isort==6.1.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.6 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + - repo: local hooks: - id: rustfmt diff --git a/py-rattler-build/README.md b/py-rattler-build/README.md index 822727f9..f03ae8b7 100644 --- a/py-rattler-build/README.md +++ b/py-rattler-build/README.md @@ -1 +1 @@ -# Python bindings to rattler-build \ No newline at end of file +# Python bindings to rattler-build diff --git a/py-rattler-build/tests/unit/test_basic.py b/py-rattler-build/tests/unit/test_basic.py index 39f146b4..9a59d162 100644 --- a/py-rattler-build/tests/unit/test_basic.py +++ b/py-rattler-build/tests/unit/test_basic.py @@ -1,6 +1,7 @@ -import rattler_build from pathlib import Path +import rattler_build + def test_basic() -> None: parent_cargo_toml = Path(__file__).parent.parent.parent.parent / "Cargo.toml" diff --git a/src/package_test/run_test.rs b/src/package_test/run_test.rs index 7dc54634..0a9fbdf1 100644 --- a/src/package_test/run_test.rs +++ b/src/package_test/run_test.rs @@ -33,7 +33,8 @@ use crate::{ env_vars, metadata::PlatformWithVirtualPackages, recipe::parser::{ - CommandsTest, DownstreamTest, PythonTest, PythonVersion, Script, ScriptContent, TestType, + CommandsTest, DownstreamTest, PerlTest, PythonTest, PythonVersion, Script, ScriptContent, + TestType, }, render::solver::create_environment, source::copy_dir::CopyDir, @@ -425,6 +426,10 @@ pub async fn run_test( .run_test(&pkg, &package_folder, &prefix, &config) .await? } + TestType::Perl { perl } => { + perl.run_test(&pkg, &package_folder, &prefix, &config) + .await? + } TestType::Downstream(downstream) if downstream_package.is_none() => { downstream .run_test(&pkg, package_file, &prefix, &config) @@ -591,6 +596,69 @@ impl PythonTest { } } +impl PerlTest { + /// Execute the Perl test + pub async fn run_test( + &self, + pkg: &ArchiveIdentifier, + path: &Path, + prefix: &Path, + config: &TestConfiguration, + ) -> Result<(), TestError> { + let span = tracing::info_span!("Running perl test"); + let _guard = span.enter(); + + let match_spec = MatchSpec::from_str( + format!("{}={}={}", pkg.name, pkg.version, pkg.build_string).as_str(), + ParseStrictness::Lenient, + )?; + + let dependencies = vec![ + MatchSpec::from_str("perl", ParseStrictness::Strict).unwrap(), + match_spec, + ]; + + create_environment( + "test", + &dependencies, + config + .host_platform + .as_ref() + .unwrap_or(&config.current_platform), + prefix, + &config.channels, + &config.tool_configuration, + config.channel_priority, + config.solve_strategy, + ) + .await + .map_err(TestError::TestEnvironmentSetup)?; + + let mut imports = String::new(); + tracing::info!("Testing perl imports:\n"); + + for module in &self.uses { + writeln!(imports, "use {};", module)?; + tracing::info!(" use {};", module); + } + tracing::info!("\n"); + + let script = Script { + content: ScriptContent::Command(imports.clone()), + interpreter: Some("perl".into()), + ..Script::default() + }; + + let tmp_dir = tempfile::tempdir()?; + script + .run_script(Default::default(), tmp_dir.path(), path, prefix, None, None) + .await + .map_err(|e| TestError::TestFailed(e.to_string()))?; + + Ok(()) + } +} + impl CommandsTest { /// Execute the command test pub async fn run_test( diff --git a/src/recipe/parser.rs b/src/recipe/parser.rs index c740b1bc..f1ab2501 100644 --- a/src/recipe/parser.rs +++ b/src/recipe/parser.rs @@ -49,7 +49,7 @@ pub use self::{ source::{GitRev, GitSource, GitUrl, PathSource, Source, UrlSource}, test::{ CommandsTest, CommandsTestFiles, CommandsTestRequirements, DownstreamTest, - PackageContentsTest, PythonTest, PythonVersion, TestType, + PackageContentsTest, PerlTest, PythonTest, PythonVersion, TestType, }, }; diff --git a/src/recipe/parser/test.rs b/src/recipe/parser/test.rs index 4f940cee..03587b96 100644 --- a/src/recipe/parser/test.rs +++ b/src/recipe/parser/test.rs @@ -121,6 +121,13 @@ impl Default for PythonTest { } } +/// A special Perl test that checks if the imports are available and runs `cpanm check`. +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PerlTest { + /// List of perl `uses` to test + pub uses: Vec, +} + /// A test that runs the tests of a downstream package. #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct DownstreamTest { @@ -137,6 +144,11 @@ pub enum TestType { /// The imports to test and the `pip check` flag python: PythonTest, }, + /// A Perl test that will test if the modules are available + Perl { + /// The modules to test + perl: PerlTest, + }, /// A test that executes multiple commands in a freshly created environment Command(CommandsTest), /// A test that runs the tests of a downstream package @@ -247,10 +259,14 @@ impl TryConvertNode for RenderedMappingNode { let package_contents = as_mapping(value, key_str)?.try_convert(key_str)?; test = TestType::PackageContents { package_contents }; } + "perl" => { + let perl = as_mapping(value, key_str)?.try_convert(key_str)?; + test = TestType::Perl { perl }; + } invalid => Err(vec![_partialerror!( *key.span(), ErrorKind::InvalidField(invalid.to_string().into()), - help = format!("expected fields for {name} is one of `python`, `script`, `downstream`, `package_contents`") + help = format!("expected fields for {name} is one of `python`, `perl`, `script`, `downstream`, `package_contents`") )])? } Ok(()) @@ -383,6 +399,18 @@ impl TryConvertNode for RenderedMappingNode { } } +/////////////////////////// +/// Perl Test /// +/////////////////////////// + +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, _name: &str) -> Result> { + let mut perl_test = PerlTest::default(); + validate_keys!(perl_test, self.iter(), uses); + Ok(perl_test) + } +} + /////////////////////////// /// Package Contents /// /////////////////////////// diff --git a/test-data/recipes/perl-test/recipe.yaml b/test-data/recipes/perl-test/recipe.yaml new file mode 100644 index 00000000..bb4e939d --- /dev/null +++ b/test-data/recipes/perl-test/recipe.yaml @@ -0,0 +1,40 @@ +context: + version: 0.03 + +package: + name: perl-call-context + version: ${{ version }} + +source: + url: https://cpan.metacpan.org/authors/id/F/FE/FELIPE/Call-Context-${{ version }}.tar.gz + sha256: 0ee6bf46bc72755adb7a6b08e79d12e207de5f7809707b3c353b58cb2f0b5a26 + +build: + number: 0 + noarch: generic + script: + - perl Makefile.PL INSTALLDIRS=site NO_PERLLOCAL=1 NO_PACKLIST=1 + - make + - make test + - make install + - echo "LICENSE-ARTISTIC" > LICENSE-ARTISTIC + - echo "LICENSE-GPL" > LICENSE-GPL + +requirements: + build: + - make + host: + - perl + +tests: + - perl: + uses: + - Call::Context + +about: + license: GPL-1.0-or-later OR Artistic-1.0-Perl + license_file: + - LICENSE-ARTISTIC + - LICENSE-GPL + summary: Sanity-check calling context + homepage: http://metacpan.org/pod/Call-Context diff --git a/test/end-to-end/__snapshots__/test_tests.ambr b/test/end-to-end/__snapshots__/test_tests.ambr new file mode 100644 index 00000000..0d2edb45 --- /dev/null +++ b/test/end-to-end/__snapshots__/test_tests.ambr @@ -0,0 +1,9 @@ +# serializer version: 1 +# name: test_perl_tests + ''' + - perl: + uses: + - Call::Context + + ''' +# --- diff --git a/test/end-to-end/test_simple.py b/test/end-to-end/test_simple.py index 1d978737..4e0a1f76 100644 --- a/test/end-to-end/test_simple.py +++ b/test/end-to-end/test_simple.py @@ -8,8 +8,7 @@ import pytest import requests import yaml -from helpers import (RattlerBuild, check_build_output, get_extracted_package, - get_package) +from helpers import RattlerBuild, check_build_output, get_extracted_package, get_package def test_functionality(rattler_build: RattlerBuild): diff --git a/test/end-to-end/test_tests.py b/test/end-to-end/test_tests.py new file mode 100644 index 00000000..f403b13b --- /dev/null +++ b/test/end-to-end/test_tests.py @@ -0,0 +1,20 @@ +import os +from pathlib import Path + +import pytest +from helpers import RattlerBuild, get_extracted_package + + +@pytest.mark.skipif( + os.name == "nt", reason="recipe does not support execution on windows" +) +def test_perl_tests( + rattler_build: RattlerBuild, recipes: Path, tmp_path: Path, snapshot +): + rattler_build.build(recipes / "perl-test", tmp_path) + pkg = get_extracted_package(tmp_path, "perl-call-context") + + assert (pkg / "info" / "tests" / "tests.yaml").exists() + content = (pkg / "info" / "tests" / "tests.yaml").read_text() + + assert snapshot == content