Skip to content

Commit

Permalink
feat: introduce python_version in tests[0].python (#1170)
Browse files Browse the repository at this point in the history
  • Loading branch information
hadim authored Nov 15, 2024
1 parent 5e83d19 commit 5bfd56e
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 9 deletions.
71 changes: 66 additions & 5 deletions src/package_test/run_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ use url::Url;
use crate::{
env_vars,
metadata::PlatformWithVirtualPackages,
recipe::parser::{CommandsTest, DownstreamTest, PythonTest, Script, ScriptContent, TestType},
recipe::parser::{
CommandsTest, DownstreamTest, PythonTest, PythonVersion, Script, ScriptContent, TestType,
},
render::solver::create_environment,
source::copy_dir::CopyDir,
tool_configuration,
Expand Down Expand Up @@ -461,18 +463,78 @@ impl PythonTest {
prefix: &Path,
config: &TestConfiguration,
) -> Result<(), TestError> {
let span = tracing::info_span!("Running python test");
let span = tracing::info_span!("Running python test(s)");
let _guard = span.enter();

// The version spec of the package being built
let match_spec = MatchSpec::from_str(
format!("{}={}={}", pkg.name, pkg.version, pkg.build_string).as_str(),
ParseStrictness::Lenient,
)?;
let mut dependencies = vec![match_spec];

// The dependencies for the test environment
// - python_version: null -> { "": ["mypackage=xx=xx"]}
// - python_version: 3.12 -> { "3.12": ["python=3.12", "mypackage=xx=xx"]}
// - python_version: [3.12, 3.13] -> { "3.12": ["python=3.12", "mypackage=xx=xx"], "3.13": ["python=3.13", "mypackage=xx=xx"]}
let mut dependencies_map: HashMap<String, Vec<MatchSpec>> = match &self.python_version {
PythonVersion::Multiple(versions) => versions
.iter()
.map(|version| {
(
version.clone(),
vec![
MatchSpec::from_str(
&format!("python={}", version),
ParseStrictness::Lenient,
)
.unwrap(),
match_spec.clone(),
],
)
})
.collect(),
PythonVersion::Single(version) => HashMap::from([(
version.clone(),
vec![
MatchSpec::from_str(&format!("python={}", version), ParseStrictness::Lenient)
.unwrap(),
match_spec,
],
)]),
PythonVersion::None => HashMap::from([("".to_string(), vec![match_spec])]),
};

// Add `pip` if pip_check is set to true
if self.pip_check {
dependencies.push(MatchSpec::from_str("pip", ParseStrictness::Strict).unwrap());
dependencies_map.iter_mut().for_each(|(_, v)| {
v.push(MatchSpec::from_str("pip", ParseStrictness::Strict).unwrap())
});
}

// Run tests for each python version
for (python_version, dependencies) in dependencies_map {
self.run_test_inner(python_version, dependencies, path, prefix, config)
.await?;
}

Ok(())
}

async fn run_test_inner(
&self,
python_version: String,
dependencies: Vec<MatchSpec>,
path: &Path,
prefix: &Path,
config: &TestConfiguration,
) -> Result<(), TestError> {
let span_message = match python_version.as_str() {
"" => "Testing with default python version".to_string(),
_ => format!("Testing with python {}", python_version),
};
let span = tracing::info_span!("", message = %span_message);
let _guard = span.enter();

create_environment(
"test",
&dependencies,
Expand Down Expand Up @@ -526,7 +588,6 @@ impl PythonTest {
console::style(console::Emoji("✔", "")).green()
);
}

Ok(())
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/recipe/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub use self::{
source::{GitRev, GitSource, GitUrl, PathSource, Source, UrlSource},
test::{
CommandsTest, CommandsTestFiles, CommandsTestRequirements, DownstreamTest,
PackageContentsTest, PythonTest, TestType,
PackageContentsTest, PythonTest, PythonVersion, TestType,
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: src/recipe/parser/test.rs
assertion_line: 505
expression: yaml_serde
snapshot_kind: text
---
- python:
imports:
- pandas
python_version: '3.10'
- python:
imports:
- pandas
python_version:
- '3.10'
- '3.12'
116 changes: 113 additions & 3 deletions src/recipe/parser/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ fn is_true(value: &bool) -> bool {
*value
}

/// The Python version(s) to test the imports against.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PythonVersion {
/// A single python version
Single(String),
/// Multiple python versions
Multiple(Vec<String>),
/// No python version specified
#[default]
None,
}

impl PythonVersion {
/// Check if the python version is none
pub fn is_none(&self) -> bool {
matches!(self, PythonVersion::None)
}
}

/// A special Python test that checks if the imports are available and runs `pip check`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PythonTest {
Expand All @@ -86,13 +106,17 @@ pub struct PythonTest {
/// Whether to run `pip check` or not (default to true)
#[serde(default = "pip_check_true", skip_serializing_if = "is_true")]
pub pip_check: bool,
/// Python version(s) to test against. If not specified, the default python version is used.
#[serde(default, skip_serializing_if = "PythonVersion::is_none")]
pub python_version: PythonVersion,
}

impl Default for PythonTest {
fn default() -> Self {
Self {
imports: Vec::new(),
pip_check: true,
python_version: PythonVersion::None,
}
}
}
Expand Down Expand Up @@ -206,7 +230,6 @@ impl TryConvertNode<TestType> for RenderedMappingNode {

self.iter().map(|(key, value)| {
let key_str = key.as_str();

match key_str {
"python" => {
let python = as_mapping(value, key_str)?.try_convert(key_str)?;
Expand Down Expand Up @@ -245,7 +268,7 @@ impl TryConvertNode<PythonTest> for RenderedMappingNode {
fn try_convert(&self, _name: &str) -> Result<PythonTest, Vec<PartialParsingError>> {
let mut python_test = PythonTest::default();

validate_keys!(python_test, self.iter(), imports, pip_check);
validate_keys!(python_test, self.iter(), imports, pip_check, python_version);

if python_test.imports.is_empty() {
Err(vec![_partialerror!(
Expand All @@ -259,6 +282,35 @@ impl TryConvertNode<PythonTest> for RenderedMappingNode {
}
}

impl TryConvertNode<PythonVersion> for RenderedNode {
fn try_convert(&self, _name: &str) -> Result<PythonVersion, Vec<PartialParsingError>> {
let python_version = match self {
RenderedNode::Mapping(_) => Err(vec![_partialerror!(
*self.span(),
ErrorKind::InvalidField("expected string, sequence or null".into()),
)])?,
RenderedNode::Scalar(version) => PythonVersion::Single(version.to_string()),
RenderedNode::Sequence(versions) => versions
.iter()
.map(|v| {
v.as_scalar()
.ok_or_else(|| {
vec![_partialerror!(
*self.span(),
ErrorKind::InvalidField("invalid value".into()),
)]
})
.map(|s| s.to_string())
})
.collect::<Result<Vec<String>, _>>()
.map(PythonVersion::Multiple)?,
RenderedNode::Null(_) => PythonVersion::None,
};

Ok(python_version)
}
}

///////////////////////////
/// Downstream Test ///
///////////////////////////
Expand Down Expand Up @@ -372,7 +424,10 @@ mod test {
use super::TestType;
use insta::assert_snapshot;

use crate::recipe::custom_yaml::{RenderedNode, TryConvertNode};
use crate::recipe::{
custom_yaml::{RenderedNode, TryConvertNode},
parser::test::PythonVersion,
};

#[test]
fn test_parsing() {
Expand Down Expand Up @@ -424,4 +479,59 @@ mod test {
let yaml_serde = serde_yaml::to_string(&tests).unwrap();
assert_snapshot!(yaml_serde);
}

#[test]
fn test_python_parsing() {
let test_section = r#"
tests:
- python:
imports:
- pandas
python_version: "3.10"
- python:
imports:
- pandas
python_version: ["3.10", "3.12"]
"#;

// parse the YAML
let yaml_root = RenderedNode::parse_yaml(0, test_section)
.map_err(|err| vec![err])
.unwrap();
let tests_node = yaml_root.as_mapping().unwrap().get("tests").unwrap();
let tests: Vec<TestType> = tests_node.try_convert("tests").unwrap();

let yaml_serde = serde_yaml::to_string(&tests).unwrap();
assert_snapshot!(yaml_serde);

// from yaml
let tests: Vec<TestType> = serde_yaml::from_str(&yaml_serde).unwrap();
let t = tests.first();

match t {
Some(TestType::Python { python }) => {
assert_eq!(python.imports, vec!["pandas"]);
assert!(python.pip_check);
assert_eq!(
python.python_version,
PythonVersion::Single("3.10".to_string())
);
}
_ => panic!("expected python test"),
}

let t2 = tests.get(1);

match t2 {
Some(TestType::Python { python }) => {
assert_eq!(python.imports, vec!["pandas"]);
assert!(python.pip_check);
assert_eq!(
python.python_version,
PythonVersion::Multiple(vec!["3.10".to_string(), "3.12".to_string()])
);
}
_ => panic!("expected python test"),
}
}
}

0 comments on commit 5bfd56e

Please sign in to comment.