From b8d01488efb514511a837eaff2c678c95384dbb1 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 15:24:55 +0200 Subject: Add test: parsing BuildDependency Signed-off-by: Matthias Beyer --- Cargo.toml | 3 +++ src/package/dependency/build.rs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 71ff5e6..3332ce7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,3 +100,6 @@ funty = "=1.1.0" # the pin here, we enforce the build to not use 1.4.0 or newer. zeroize = ">=1.3.0, <1.4.0" +[dev-dependencies] +toml = "0.5" + diff --git a/src/package/dependency/build.rs b/src/package/dependency/build.rs index 8eb8eb3..4fb9f74 100644 --- a/src/package/dependency/build.rs +++ b/src/package/dependency/build.rs @@ -39,3 +39,23 @@ impl ParseDependency for BuildDependency { crate::package::dependency::parse_package_dependency_string_into_name_and_version(&self.0) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(serde::Deserialize)] + #[allow(unused)] + pub struct TestSetting { + setting: BuildDependency, + } + + #[test] + fn test_parse_dependency() { + let dependency_str = r#"setting = "foo""#; + let d: TestSetting = toml::from_str(dependency_str).unwrap(); + + assert_eq!(d.setting.0, "foo"); + } +} + -- cgit v1.2.3 From 5ce210127ee09fbc648e2570b9f240446dbbede6 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 16:00:44 +0200 Subject: Rewrite BuildDependency type as enum This patch reimplements the BuildDependency type as an enum. This is required because later we want to be able to use an "if" expression in the build dependencies, for example (not decided yet) something like build = [ { d = "libfoobar", if = [ { has_env = [ "TARGET_RH7" ]; } ] } ] (no valid toml here, formatting for readability) to make a dependency optional on a condition. Signed-off-by: Matthias Beyer --- src/package/dependency/build.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/package/dependency/build.rs b/src/package/dependency/build.rs index 4fb9f74..78d3fad 100644 --- a/src/package/dependency/build.rs +++ b/src/package/dependency/build.rs @@ -19,24 +19,30 @@ use crate::package::PackageVersionConstraint; /// A dependency that is packaged and is only required during build time #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -#[serde(transparent)] -pub struct BuildDependency(String); +#[serde(untagged)] +pub enum BuildDependency { + Simple(String), +} impl AsRef for BuildDependency { fn as_ref(&self) -> &str { - self.0.as_ref() + match self { + BuildDependency::Simple(name) => name, + } } } impl StringEqual for BuildDependency { fn str_equal(&self, s: &str) -> bool { - self.0 == s + match self { + BuildDependency::Simple(name) => name == s, + } } } impl ParseDependency for BuildDependency { fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersionConstraint)> { - crate::package::dependency::parse_package_dependency_string_into_name_and_version(&self.0) + crate::package::dependency::parse_package_dependency_string_into_name_and_version(self.as_ref()) } } @@ -52,10 +58,10 @@ mod tests { #[test] fn test_parse_dependency() { - let dependency_str = r#"setting = "foo""#; - let d: TestSetting = toml::from_str(dependency_str).unwrap(); - - assert_eq!(d.setting.0, "foo"); + let s: TestSetting = toml::from_str(r#"setting = "foo""#).expect("Parsing TestSetting failed"); + match s.setting { + BuildDependency::Simple(name) => assert_eq!(name, "foo", "Expected 'foo', got {}", name), + } } } -- cgit v1.2.3 From ef10100c0152ef12ee77aaae966ff9a603a36a0d Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 15:24:55 +0200 Subject: Add test: parsing Dependency Signed-off-by: Matthias Beyer --- src/package/dependency/runtime.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 62338ac..50e96de 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -45,3 +45,22 @@ impl ParseDependency for Dependency { crate::package::dependency::parse_package_dependency_string_into_name_and_version(&self.0) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(serde::Deserialize)] + #[allow(unused)] + pub struct TestSetting { + setting: Dependency, + } + + #[test] + fn test_parse_dependency() { + let s: TestSetting = toml::from_str(r#"setting = "foo""#).expect("Parsing TestSetting failed"); + + assert_eq!(s.setting.0, "foo"); + } +} + -- cgit v1.2.3 From 1d7dd070642bc826dca28bf22f70bd967775413b Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 16:00:44 +0200 Subject: Rewrite Dependency type as enum This patch reimplements the Dependency type as an enum. This is required because later we want to be able to use an "if" expression in the build dependencies, for example (not decided yet) something like runtime = [ { d = "libfoobar", if = [ { has_env = [ "TARGET_RH7" ]; } ] } ] (no valid toml here, formatting for readability) to make a dependency optional on a condition. Signed-off-by: Matthias Beyer --- src/package/dependency/runtime.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 50e96de..3916b22 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -19,30 +19,36 @@ use crate::package::PackageVersionConstraint; /// A dependency that is packaged and is required during runtime #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -#[serde(transparent)] -pub struct Dependency(String); +#[serde(untagged)] +pub enum Dependency { + Simple(String), +} impl AsRef for Dependency { fn as_ref(&self) -> &str { - self.0.as_ref() + match self { + Dependency::Simple(name) => name, + } } } impl StringEqual for Dependency { fn str_equal(&self, s: &str) -> bool { - self.0 == s + match self { + Dependency::Simple(name) => name == s, + } } } impl From for Dependency { fn from(s: String) -> Dependency { - Dependency(s) + Dependency::Simple(s) } } impl ParseDependency for Dependency { fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersionConstraint)> { - crate::package::dependency::parse_package_dependency_string_into_name_and_version(&self.0) + crate::package::dependency::parse_package_dependency_string_into_name_and_version(self.as_ref()) } } @@ -60,7 +66,9 @@ mod tests { fn test_parse_dependency() { let s: TestSetting = toml::from_str(r#"setting = "foo""#).expect("Parsing TestSetting failed"); - assert_eq!(s.setting.0, "foo"); + match s.setting { + Dependency::Simple(name) => assert_eq!(name, "foo", "Expected 'foo', got {}", name), + } } } -- cgit v1.2.3 From 288b7860b2d216f5aefce252b3fe51928325ff33 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 16:46:06 +0200 Subject: Add types for conditional dependencies This patch adds the first implementation for conditional dependencies in the dependency fields (that is the "build" and "runtime" keys in the package defintion). This is only the deserialization-interface, not the actual condition resolving code. Signed-off-by: Matthias Beyer --- src/package/dependency/condition.rs | 120 ++++++++++++++++++++++++++++++++++++ src/package/dependency/mod.rs | 2 + 2 files changed, 122 insertions(+) create mode 100644 src/package/dependency/condition.rs diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs new file mode 100644 index 0000000..df65a5c --- /dev/null +++ b/src/package/dependency/condition.rs @@ -0,0 +1,120 @@ +// +// Copyright (c) 2020-2021 science+computing ag and other contributors +// +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// + +use std::collections::HashMap; + +use serde::Deserialize; + +use crate::util::EnvironmentVariableName; + +/// The Condition type +/// +/// This type represents a condition whether a dependency should be included in the package tree or +/// not. +/// +/// Right now, we are supporting condition by environment (set or equal) or whether a specific +/// build image is used. +/// All these settings are optional, of course. +/// +#[derive(Deserialize, Clone, Debug)] +pub struct Condition { + #[serde(rename = "has_env", skip_serializing_if = "Option::is_none")] + has_env: Option>, + + #[serde(rename = "env_eq", skip_serializing_if = "Option::is_none")] + env_eq: Option>, + + #[serde(rename = "in_image", skip_serializing_if = "Option::is_none")] + in_image: Option>, +} + + +/// Helper type for supporting Vec and T in value +/// position of Condition +#[cfg_attr(test, derive(Eq, PartialEq))] +#[derive(Deserialize, Clone, Debug)] +#[serde(untagged)] +pub enum OneOrMore { + One(T), + More(Vec), +} + +impl Into> for OneOrMore { + fn into(self) -> Vec { + match self { + OneOrMore::One(o) => vec![o], + OneOrMore::More(m) => m, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_has_env_deserialization() { + let s = r#"has_env = "foo""#; + let c: Condition = toml::from_str(s).expect("Deserializing has_env"); + + assert_eq!(c.has_env.unwrap(), OneOrMore::::One(EnvironmentVariableName::from("foo"))); + assert!(c.env_eq.is_none()); + assert!(c.in_image.is_none()); + } + + #[test] + fn test_has_env_list_deserialization() { + let s = r#"has_env = ["foo", "bar"]"#; + let c: Condition = toml::from_str(s).expect("Deserializing has_env"); + + assert_eq!(c.has_env.unwrap(), { + OneOrMore::::More({ + vec![EnvironmentVariableName::from("foo"), EnvironmentVariableName::from("bar")] + }) + }); + assert!(c.env_eq.is_none()); + assert!(c.in_image.is_none()); + } + + #[test] + fn test_env_eq_deserialization() { + let s = r#"env_eq = { "foo" = "bar" }"#; + let c: Condition = toml::from_str(s).expect("Deserializing has_env"); + + assert!(c.has_env.is_none()); + assert_eq!(c.env_eq.unwrap(), { + let mut hm = HashMap::new(); + hm.insert(EnvironmentVariableName::from("foo"), String::from("bar")); + hm + }); + assert!(c.in_image.is_none()); + } + + #[test] + fn test_in_image_deserialization() { + let s = r#"in_image = "foo""#; + let c: Condition = toml::from_str(s).expect("Deserializing has_env"); + + assert!(c.has_env.is_none()); + assert!(c.env_eq.is_none()); + assert_eq!(c.in_image.unwrap(), OneOrMore::::One(String::from("foo"))); + } + + #[test] + fn test_in_image_list_deserialization() { + let s = r#"in_image = ["foo"]"#; + let c: Condition = toml::from_str(s).expect("Deserializing has_env"); + + assert!(c.has_env.is_none()); + assert!(c.env_eq.is_none()); + assert_eq!(c.in_image.unwrap(), OneOrMore::::More(vec![String::from("foo")])); + } + +} diff --git a/src/package/dependency/mod.rs b/src/package/dependency/mod.rs index 37cb82b..8195dd1 100644 --- a/src/package/dependency/mod.rs +++ b/src/package/dependency/mod.rs @@ -24,6 +24,8 @@ pub use build::*; mod runtime; pub use runtime::*; +mod condition; + pub trait StringEqual { fn str_equal(&self, s: &str) -> bool; } -- cgit v1.2.3 From 2c0ad8a6c457a72e952f630c8993efb54c4d50cc Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 17:23:31 +0200 Subject: Add conditional dependency variant This patch adds a conditional dependency variant to the build/runtime dependency deserialization type(s). Signed-off-by: Matthias Beyer --- src/package/dependency/build.rs | 9 +++-- src/package/dependency/condition.rs | 67 +++++++++++++++++++++++++++++++++++-- src/package/dependency/mod.rs | 2 +- src/package/dependency/runtime.rs | 9 +++-- 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/package/dependency/build.rs b/src/package/dependency/build.rs index 78d3fad..42c0763 100644 --- a/src/package/dependency/build.rs +++ b/src/package/dependency/build.rs @@ -12,22 +12,25 @@ use anyhow::Result; use serde::Deserialize; use serde::Serialize; -use crate::package::dependency::ParseDependency; -use crate::package::dependency::StringEqual; use crate::package::PackageName; use crate::package::PackageVersionConstraint; +use crate::package::dependency::ParseDependency; +use crate::package::dependency::StringEqual; +use crate::package::dependency::condition::Condition; /// A dependency that is packaged and is only required during build time #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[serde(untagged)] pub enum BuildDependency { Simple(String), + Conditional(String, Condition), } impl AsRef for BuildDependency { fn as_ref(&self) -> &str { match self { BuildDependency::Simple(name) => name, + BuildDependency::Conditional(name, _) => name, } } } @@ -36,6 +39,7 @@ impl StringEqual for BuildDependency { fn str_equal(&self, s: &str) -> bool { match self { BuildDependency::Simple(name) => name == s, + BuildDependency::Conditional(name, _) => name == s, } } } @@ -61,6 +65,7 @@ mod tests { let s: TestSetting = toml::from_str(r#"setting = "foo""#).expect("Parsing TestSetting failed"); match s.setting { BuildDependency::Simple(name) => assert_eq!(name, "foo", "Expected 'foo', got {}", name), + other => panic!("Unexpected deserialization to other variant: {:?}", other), } } } diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index df65a5c..9de498e 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; use serde::Deserialize; +use serde::Serialize; use crate::util::EnvironmentVariableName; @@ -23,7 +24,7 @@ use crate::util::EnvironmentVariableName; /// build image is used. /// All these settings are optional, of course. /// -#[derive(Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct Condition { #[serde(rename = "has_env", skip_serializing_if = "Option::is_none")] has_env: Option>, @@ -35,11 +36,71 @@ pub struct Condition { in_image: Option>, } +/// Manual implementation of PartialOrd for Condition +/// +/// Because HashMap does not implement PartialOrd +impl PartialOrd for Condition { + fn partial_cmp(&self, other: &Self) -> Option { + use std::cmp::Ordering as O; + + let cmp_has_env = match (self.has_env.as_ref(), other.has_env.as_ref()) { + (Some(a), Some(b)) => a.partial_cmp(b), + (Some(_), None) => Some(O::Greater), + (None, Some(_)) => Some(O::Less), + (None, None) => Some(O::Equal), + }; + + if cmp_has_env.as_ref().map(|o| *o != O::Equal).unwrap_or(false) { + return cmp_has_env + } + + let cmp_env_eq = match (self.env_eq.as_ref(), other.env_eq.as_ref()) { + // TODO: Is this safe? We ignore the HashMaps here and just say they are equal. They are most certainly not. + (Some(_), Some(_)) => Some(O::Equal), + (Some(_), None) => Some(O::Greater), + (None, Some(_)) => Some(O::Less), + (None, None) => Some(O::Equal), + }; + + if cmp_env_eq.as_ref().map(|o| *o != O::Equal).unwrap_or(false) { + return cmp_env_eq + } + + match (self.in_image.as_ref(), other.in_image.as_ref()) { + (Some(a), Some(b)) => a.partial_cmp(b), + (Some(_), None) => Some(O::Greater), + (None, Some(_)) => Some(O::Less), + (None, None) => Some(O::Equal), + } + } +} + +/// Manual implementation of Ord for Condition +/// +/// Because HashMap does not implement Ord +impl Ord for Condition { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal) + } +} + +/// Manual implementation of Hash for Condition +/// +/// Because HashMap does not implement Hash +impl std::hash::Hash for Condition { + fn hash(&self, state: &mut H) { + self.has_env.hash(state); + if let Some(hm) = self.env_eq.as_ref() { + hm.iter().for_each(|(k, v)| (k, v).hash(state)); + }; + self.in_image.hash(state); + } +} + /// Helper type for supporting Vec and T in value /// position of Condition -#[cfg_attr(test, derive(Eq, PartialEq))] -#[derive(Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[serde(untagged)] pub enum OneOrMore { One(T), diff --git a/src/package/dependency/mod.rs b/src/package/dependency/mod.rs index 8195dd1..e55dfc2 100644 --- a/src/package/dependency/mod.rs +++ b/src/package/dependency/mod.rs @@ -24,7 +24,7 @@ pub use build::*; mod runtime; pub use runtime::*; -mod condition; +pub(self) mod condition; pub trait StringEqual { fn str_equal(&self, s: &str) -> bool; diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 3916b22..26679e2 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -12,22 +12,25 @@ use anyhow::Result; use serde::Deserialize; use serde::Serialize; -use crate::package::dependency::ParseDependency; -use crate::package::dependency::StringEqual; use crate::package::PackageName; use crate::package::PackageVersionConstraint; +use crate::package::dependency::ParseDependency; +use crate::package::dependency::StringEqual; +use crate::package::dependency::condition::Condition; /// A dependency that is packaged and is required during runtime #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[serde(untagged)] pub enum Dependency { Simple(String), + Conditional(String, Condition), } impl AsRef for Dependency { fn as_ref(&self) -> &str { match self { Dependency::Simple(name) => name, + Dependency::Conditional(name, _) => name, } } } @@ -36,6 +39,7 @@ impl StringEqual for Dependency { fn str_equal(&self, s: &str) -> bool { match self { Dependency::Simple(name) => name == s, + Dependency::Conditional(name, _) => name == s, } } } @@ -68,6 +72,7 @@ mod tests { match s.setting { Dependency::Simple(name) => assert_eq!(name, "foo", "Expected 'foo', got {}", name), + other => panic!("Unexpected deserialization to other variant: {:?}", other), } } } -- cgit v1.2.3 From a5906c5d0708f3739428c1957ba40d20e1923eda Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 17:36:48 +0200 Subject: Add getters for Condition members Signed-off-by: Matthias Beyer --- src/package/dependency/condition.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index 9de498e..2f46dfe 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use serde::Deserialize; use serde::Serialize; +use getset::Getters; use crate::util::EnvironmentVariableName; @@ -24,15 +25,18 @@ use crate::util::EnvironmentVariableName; /// build image is used. /// All these settings are optional, of course. /// -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Getters, Clone, Debug, Eq, PartialEq)] pub struct Condition { #[serde(rename = "has_env", skip_serializing_if = "Option::is_none")] + #[getset(get = "pub")] has_env: Option>, #[serde(rename = "env_eq", skip_serializing_if = "Option::is_none")] + #[getset(get = "pub")] env_eq: Option>, #[serde(rename = "in_image", skip_serializing_if = "Option::is_none")] + #[getset(get = "pub")] in_image: Option>, } -- cgit v1.2.3 From e3be9e5300f96f35fcdf8b28984cba6b06327b15 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 17:53:05 +0200 Subject: Add conditional dependency support for build dependency type Signed-off-by: Matthias Beyer --- src/package/dependency/build.rs | 111 ++++++++++++++++++++++++++++++++++-- src/package/dependency/condition.rs | 20 ++++++- 2 files changed, 124 insertions(+), 7 deletions(-) diff --git a/src/package/dependency/build.rs b/src/package/dependency/build.rs index 42c0763..4d51335 100644 --- a/src/package/dependency/build.rs +++ b/src/package/dependency/build.rs @@ -23,14 +23,17 @@ use crate::package::dependency::condition::Condition; #[serde(untagged)] pub enum BuildDependency { Simple(String), - Conditional(String, Condition), + Conditional { + name: String, + condition: Condition, + }, } impl AsRef for BuildDependency { fn as_ref(&self) -> &str { match self { BuildDependency::Simple(name) => name, - BuildDependency::Conditional(name, _) => name, + BuildDependency::Conditional { name, .. } => name, } } } @@ -39,7 +42,7 @@ impl StringEqual for BuildDependency { fn str_equal(&self, s: &str) -> bool { match self { BuildDependency::Simple(name) => name == s, - BuildDependency::Conditional(name, _) => name == s, + BuildDependency::Conditional { name, .. } => name == s, } } } @@ -53,8 +56,9 @@ impl ParseDependency for BuildDependency { #[cfg(test)] mod tests { use super::*; + use crate::package::dependency::condition::OneOrMore; - #[derive(serde::Deserialize)] + #[derive(serde::Serialize, serde::Deserialize)] #[allow(unused)] pub struct TestSetting { setting: BuildDependency, @@ -68,5 +72,104 @@ mod tests { other => panic!("Unexpected deserialization to other variant: {:?}", other), } } + + #[test] + fn test_parse_conditional_dependency() { + let s: TestSetting = toml::from_str(r#"setting = { name = "foo", condition = { in_image = "bar"} }"#).expect("Parsing TestSetting failed"); + match s.setting { + BuildDependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + #[test] + fn test_parse_conditional_dependency_pretty() { + let pretty = r#" + [setting] + name = "foo" + [setting.condition] + in_image = "bar" + "#; + + let s: TestSetting = toml::from_str(pretty).expect("Parsing TestSetting failed"); + + match s.setting { + BuildDependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + + #[derive(serde::Serialize, serde::Deserialize)] + #[allow(unused)] + pub struct TestSettings { + settings: Vec, + } + + #[test] + fn test_parse_conditional_dependencies() { + let s: TestSettings = toml::from_str(r#"settings = [{ name = "foo", condition = { in_image = "bar"} }]"#).expect("Parsing TestSetting failed"); + match s.settings.get(0).expect("Has not one dependency") { + BuildDependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + #[test] + fn test_parse_conditional_dependencies_pretty() { + let pretty = r#" + [[settings]] + name = "foo" + condition = { in_image = "bar" } + "#; + + let s: TestSettings = toml::from_str(pretty).expect("Parsing TestSetting failed"); + + match s.settings.get(0).expect("Has not one dependency") { + BuildDependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + #[test] + fn test_parse_conditional_dependencies_pretty_2() { + let pretty = r#" + [[settings]] + name = "foo" + condition.in_image = "bar" + "#; + + let s: TestSettings = toml::from_str(pretty).expect("Parsing TestSetting failed"); + + match s.settings.get(0).expect("Has not one dependency") { + BuildDependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } } diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index 2f46dfe..a3eeaaf 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -29,15 +29,15 @@ use crate::util::EnvironmentVariableName; pub struct Condition { #[serde(rename = "has_env", skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] - has_env: Option>, + pub(super) has_env: Option>, #[serde(rename = "env_eq", skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] - env_eq: Option>, + pub(super) env_eq: Option>, #[serde(rename = "in_image", skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] - in_image: Option>, + pub(super) in_image: Option>, } /// Manual implementation of PartialOrd for Condition @@ -120,6 +120,20 @@ impl Into> for OneOrMore { } } +#[cfg(test)] +impl From> for OneOrMore { + fn from(v: Vec) -> Self { + OneOrMore::More(v) + } +} + +#[cfg(test)] +impl From for OneOrMore { + fn from(s: String) -> Self { + OneOrMore::One(s) + } +} + #[cfg(test)] mod tests { use super::*; -- cgit v1.2.3 From 7902138f93e397c8a41f0699e970924916d613b0 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 17:53:05 +0200 Subject: Add conditional dependency support for runtime dependency type Signed-off-by: Matthias Beyer --- src/package/dependency/runtime.rs | 109 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 26679e2..40ec93c 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -23,14 +23,17 @@ use crate::package::dependency::condition::Condition; #[serde(untagged)] pub enum Dependency { Simple(String), - Conditional(String, Condition), + Conditional { + name: String, + condition: Condition, + }, } impl AsRef for Dependency { fn as_ref(&self) -> &str { match self { Dependency::Simple(name) => name, - Dependency::Conditional(name, _) => name, + Dependency::Conditional { name, .. } => name, } } } @@ -39,7 +42,7 @@ impl StringEqual for Dependency { fn str_equal(&self, s: &str) -> bool { match self { Dependency::Simple(name) => name == s, - Dependency::Conditional(name, _) => name == s, + Dependency::Conditional { name, .. } => name == s, } } } @@ -59,6 +62,7 @@ impl ParseDependency for Dependency { #[cfg(test)] mod tests { use super::*; + use crate::package::dependency::condition::OneOrMore; #[derive(serde::Deserialize)] #[allow(unused)] @@ -75,5 +79,104 @@ mod tests { other => panic!("Unexpected deserialization to other variant: {:?}", other), } } + + #[test] + fn test_parse_conditional_dependency() { + let s: TestSetting = toml::from_str(r#"setting = { name = "foo", condition = { in_image = "bar"} }"#).expect("Parsing TestSetting failed"); + match s.setting { + Dependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + #[test] + fn test_parse_conditional_dependency_pretty() { + let pretty = r#" + [setting] + name = "foo" + [setting.condition] + in_image = "bar" + "#; + + let s: TestSetting = toml::from_str(pretty).expect("Parsing TestSetting failed"); + + match s.setting { + Dependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + + #[derive(serde::Serialize, serde::Deserialize)] + #[allow(unused)] + pub struct TestSettings { + settings: Vec, + } + + #[test] + fn test_parse_conditional_dependencies() { + let s: TestSettings = toml::from_str(r#"settings = [{ name = "foo", condition = { in_image = "bar"} }]"#).expect("Parsing TestSetting failed"); + match s.settings.get(0).expect("Has not one dependency") { + Dependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + #[test] + fn test_parse_conditional_dependencies_pretty() { + let pretty = r#" + [[settings]] + name = "foo" + condition = { in_image = "bar" } + "#; + + let s: TestSettings = toml::from_str(pretty).expect("Parsing TestSetting failed"); + + match s.settings.get(0).expect("Has not one dependency") { + Dependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } + + #[test] + fn test_parse_conditional_dependencies_pretty_2() { + let pretty = r#" + [[settings]] + name = "foo" + condition.in_image = "bar" + "#; + + let s: TestSettings = toml::from_str(pretty).expect("Parsing TestSetting failed"); + + match s.settings.get(0).expect("Has not one dependency") { + Dependency::Conditional { name, condition } => { + assert_eq!(name, "foo", "Expected 'foo', got {}", name); + assert_eq!(*condition.has_env(), None); + assert_eq!(*condition.env_eq(), None); + assert_eq!(condition.in_image().as_ref(), Some(&OneOrMore::::One(String::from("bar")))); + }, + other => panic!("Unexpected deserialization to other variant: {:?}", other), + } + } } -- cgit v1.2.3 From 3cc328a5f5857b95299d0ef998be5dcc3ddde6a7 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 19:09:52 +0200 Subject: Add passing of data for condition-check This patch extends the interface for building the package DAG with a parameter for the data that is required to check conditions for conditional dependencies. A "DTO" type is added for this data. The actual condition-checking is not implemented in this patch. Signed-off-by: Matthias Beyer --- src/commands/build.rs | 8 +++++++- src/commands/tree_of.rs | 11 ++++++++++- src/package/dag.rs | 38 ++++++++++++++++++++++++++++++++----- src/package/dependency/condition.rs | 8 ++++++++ src/package/dependency/mod.rs | 2 +- 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/commands/build.rs b/src/commands/build.rs index d0e473b..8148a33 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -43,6 +43,7 @@ use crate::package::Dag; use crate::package::PackageName; use crate::package::PackageVersion; use crate::package::Shebang; +use crate::package::condition::ConditionData; use crate::repository::Repository; use crate::schema; use crate::source::SourceCache; @@ -226,7 +227,12 @@ pub async fn build( let dag = { let bar_tree_building = progressbars.bar(); - let dag = Dag::for_root_package(package.clone(), &repo, Some(&bar_tree_building))?; + let condition_data = ConditionData { + image_name: Some(&image_name), + env: &additional_env, + }; + + let dag = Dag::for_root_package(package.clone(), &repo, Some(&bar_tree_building), &condition_data)?; bar_tree_building.finish_with_message("Finished loading Dag"); dag }; diff --git a/src/commands/tree_of.rs b/src/commands/tree_of.rs index 6b297e3..f5ce223 100644 --- a/src/commands/tree_of.rs +++ b/src/commands/tree_of.rs @@ -20,7 +20,11 @@ use resiter::AndThen; use crate::package::Dag; use crate::package::PackageName; use crate::package::PackageVersionConstraint; +use crate::package::condition::ConditionData; use crate::repository::Repository; +use crate::util::EnvironmentVariableName; +use crate::util::docker::ImageName; +use crate::util::progress::ProgressBars; /// Implementation of the "tree_of" subcommand pub async fn tree_of( @@ -36,6 +40,11 @@ pub async fn tree_of( .map(PackageVersionConstraint::try_from) .transpose()?; + let condition_data = ConditionData { + image_name: None + env: &[], + }; + repo.packages() .filter(|p| pname.as_ref().map(|n| p.name() == n).unwrap_or(true)) .filter(|p| { @@ -44,7 +53,7 @@ pub async fn tree_of( .map(|v| v.matches(p.version())) .unwrap_or(true) }) - .map(|package| Dag::for_root_package(package.clone(), &repo, None)) + .map(|package| Dag::for_root_package(package.clone(), &repo, None, &condition_data)) .and_then_ok(|tree| { let stdout = std::io::stdout(); let mut outlock = stdout.lock(); diff --git a/src/package/dag.rs b/src/package/dag.rs index aea416c..e5dc840 100644 --- a/src/package/dag.rs +++ b/src/package/dag.rs @@ -25,6 +25,7 @@ use resiter::AndThen; use getset::Getters; use crate::package::Package; +use crate::package::condition::ConditionData; use crate::repository::Repository; #[derive(Debug, Getters)] @@ -41,6 +42,7 @@ impl Dag { p: Package, repo: &Repository, progress: Option<&ProgressBar>, + _conditional_data: &ConditionData<'_>, // required for selecting packages with conditional dependencies ) -> Result { fn add_sub_packages<'a>( repo: &'a Repository, @@ -182,7 +184,13 @@ mod tests { let repo = Repository::from(btree); let progress = ProgressBar::hidden(); - let r = Dag::for_root_package(p1, &repo, Some(&progress)); + let condition_data = ConditionData { + image_name: None, + env: &[], + }; + + let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); + assert!(r.is_ok()); } @@ -214,7 +222,12 @@ mod tests { let repo = Repository::from(btree); let progress = ProgressBar::hidden(); - let dag = Dag::for_root_package(p1, &repo, Some(&progress)); + let condition_data = ConditionData { + image_name: None, + env: &[], + }; + + let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); assert!(dag.is_ok()); let dag = dag.unwrap(); let ps = dag.all_packages(); @@ -303,7 +316,12 @@ mod tests { let repo = Repository::from(btree); let progress = ProgressBar::hidden(); - let r = Dag::for_root_package(p1, &repo, Some(&progress)); + let condition_data = ConditionData { + image_name: None, + env: &[], + }; + + let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); assert!(r.is_ok()); let r = r.unwrap(); let ps = r.all_packages(); @@ -446,7 +464,12 @@ mod tests { let repo = Repository::from(btree); let progress = ProgressBar::hidden(); - let r = Dag::for_root_package(p1, &repo, Some(&progress)); + let condition_data = ConditionData { + image_name: None, + env: &[], + }; + + let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); assert!(r.is_ok()); let r = r.unwrap(); let ps = r.all_packages(); @@ -551,7 +574,12 @@ mod tests { let repo = Repository::from(btree); let progress = ProgressBar::hidden(); - let r = Dag::for_root_package(p1, &repo, Some(&progress)); + let condition_data = ConditionData { + image_name: None, + env: &[], + }; + + let r = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); assert!(r.is_ok()); let r = r.unwrap(); let ps = r.all_packages(); diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index a3eeaaf..81892c7 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -15,6 +15,7 @@ use serde::Serialize; use getset::Getters; use crate::util::EnvironmentVariableName; +use crate::util::docker::ImageName; /// The Condition type /// @@ -134,6 +135,13 @@ impl From for OneOrMore { } } + +#[derive(Debug)] +pub struct ConditionData<'a> { + pub(crate) image_name: Option<&'a ImageName>, + pub(crate) env: &'a [(EnvironmentVariableName, String)], +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/package/dependency/mod.rs b/src/package/dependency/mod.rs index e55dfc2..da80a85 100644 --- a/src/package/dependency/mod.rs +++ b/src/package/dependency/mod.rs @@ -24,7 +24,7 @@ pub use build::*; mod runtime; pub use runtime::*; -pub(self) mod condition; +pub mod condition; pub trait StringEqual { fn str_equal(&self, s: &str) -> bool; -- cgit v1.2.3 From 27f6085f0cc2f882656d40e2b4665e20a4a80959 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 19:11:30 +0200 Subject: Add CLI parameters for tree-of subcommand to add condition-checking data This patch adds parameters for the tree-of subcommand which can be used to change the package-DAG-building based on conditional requirements. If a package has a dependency-condition that only includes a dependency if there is a certain image used to build, the "image" parameter can be used to inspect the package tree (DAG) build with that image in mind. Signed-off-by: Matthias Beyer --- src/cli.rs | 29 +++++++++++++++++++++++++++++ src/commands/tree_of.rs | 15 +++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index dbafd91..bc14341 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1061,6 +1061,35 @@ pub fn cli<'a>() -> App<'a> { .value_name("VERSION_CONSTRAINT") .about("A version constraint to search for (optional), E.G. '=1.0.0'") ) + .arg(Arg::new("image") + .required(false) + .multiple(false) + .takes_value(true) + .value_name("IMAGE NAME") + .short('I') + .long("image") + .about("Name of the docker image to use") + .long_about(indoc::indoc!(r#" + Name of the docker image to use. + + Required because tree might look different on different images because of + conditions on dependencies. + "#)) + ) + .arg(Arg::new("env") + .required(false) + .multiple(true) + .short('E') + .long("env") + .validator(env_pass_validator) + .about("Additional env to be passed when building packages") + .long_about(indoc::indoc!(r#" + Additional env to be passed when building packages. + + Required because tree might look different on different images because of + conditions on dependencies. + "#)) + ) ) .subcommand(App::new("metrics") diff --git a/src/commands/tree_of.rs b/src/commands/tree_of.rs index f5ce223..09fcabf 100644 --- a/src/commands/tree_of.rs +++ b/src/commands/tree_of.rs @@ -40,9 +40,20 @@ pub async fn tree_of( .map(PackageVersionConstraint::try_from) .transpose()?; + let image_name = matches + .value_of("image") + .map(String::from) + .map(ImageName::from); + + let additional_env = matches + .values_of("env") + .unwrap_or_default() + .map(crate::util::env::parse_to_env) + .collect::>>()?; + let condition_data = ConditionData { - image_name: None - env: &[], + image_name: image_name.as_ref(), + env: &additional_env, }; repo.packages() -- cgit v1.2.3 From 96920d807ca871c619df8b5ea49c58c126c5df90 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 19:52:02 +0200 Subject: Impl Condition::new() if in testing code Signed-off-by: Matthias Beyer --- src/package/dependency/condition.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index 81892c7..cd8010e 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -41,6 +41,17 @@ pub struct Condition { pub(super) in_image: Option>, } +#[cfg(test)] +impl Condition { + pub fn new(has_env: Option>, + env_eq: Option>, + in_image: Option>) + -> Self + { + Condition { has_env, env_eq, in_image } + } +} + /// Manual implementation of PartialOrd for Condition /// /// Because HashMap does not implement PartialOrd -- cgit v1.2.3 From 415b83f6b19e93a026e8c92250a49fa500b0ccaf Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 19:52:16 +0200 Subject: Impl Dependency::new_conditional() if in testing code Signed-off-by: Matthias Beyer --- src/package/dependency/runtime.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 40ec93c..8c692ed 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -29,6 +29,13 @@ pub enum Dependency { }, } +#[cfg(test)] +impl Dependency { + pub fn new_conditional(name: String, condition: Condition) -> Self { + Dependency::Conditional { name, condition } + } +} + impl AsRef for Dependency { fn as_ref(&self) -> &str { match self { -- cgit v1.2.3 From f9b51a1ea8ff7ce76bec0573e00857c2401fa400 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 19:52:29 +0200 Subject: Impl From<&str> for ImageName if in testing code Signed-off-by: Matthias Beyer --- src/util/docker.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/util/docker.rs b/src/util/docker.rs index 33fc9eb..3a799bf 100644 --- a/src/util/docker.rs +++ b/src/util/docker.rs @@ -32,6 +32,13 @@ impl From for ImageName { } } +#[cfg(test)] +impl From<&str> for ImageName { + fn from(s: &str) -> Self { + ImageName(String::from(s)) + } +} + impl AsRef for ImageName { fn as_ref(&self) -> &str { self.0.as_ref() -- cgit v1.2.3 From 0b65fa0fd980d3fd814f4faeb07df39b47d96a23 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 19:52:58 +0200 Subject: Add tests for DAG-building with conditional dependency Signed-off-by: Matthias Beyer --- src/commands/tree_of.rs | 1 - src/package/dag.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/commands/tree_of.rs b/src/commands/tree_of.rs index 09fcabf..811b2b7 100644 --- a/src/commands/tree_of.rs +++ b/src/commands/tree_of.rs @@ -24,7 +24,6 @@ use crate::package::condition::ConditionData; use crate::repository::Repository; use crate::util::EnvironmentVariableName; use crate::util::docker::ImageName; -use crate::util::progress::ProgressBars; /// Implementation of the "tree_of" subcommand pub async fn tree_of( diff --git a/src/package/dag.rs b/src/package/dag.rs index e5dc840..5956c60 100644 --- a/src/package/dag.rs +++ b/src/package/dag.rs @@ -161,11 +161,14 @@ mod tests { use std::collections::BTreeMap; + use crate::package::Dependencies; + use crate::package::Dependency; + use crate::package::condition::Condition; + use crate::package::condition::OneOrMore; use crate::package::tests::package; use crate::package::tests::pname; use crate::package::tests::pversion; - use crate::package::Dependencies; - use crate::package::Dependency; + use crate::util::docker::ImageName; use indicatif::ProgressBar; @@ -588,5 +591,131 @@ mod tests { assert!(ps.iter().any(|p| *p.name() == pname("p3"))); assert!(ps.iter().any(|p| *p.name() == pname("p4"))); } + + + /// Build a repository with two packages and a condition for their dependency + fn repo_with_ab_packages_with_condition(cond: Condition) -> (Package, Repository) { + let mut btree = BTreeMap::new(); + + let mut p1 = { + let name = "a"; + let vers = "1"; + let pack = package(name, vers, "https://rust-lang.org", "123"); + btree.insert((pname(name), pversion(vers)), pack.clone()); + pack + }; + + { + let name = "b"; + let vers = "2"; + let pack = package(name, vers, "https://rust-lang.org", "124"); + btree.insert((pname(name), pversion(vers)), pack); + } + + { + let d = Dependency::new_conditional(String::from("b =2"), cond); + let ds = Dependencies::with_runtime_dependency(d); + p1.set_dependencies(ds); + } + + (p1, Repository::from(btree)) + } + + // Test whether the dependency DAG is correctly build if there is NO conditional data passed + // + // Because the dependency is conditional with "fooimage" required as build-image, the + // dependency DAG should NOT contain package "b" + #[test] + fn test_add_two_dependent_packages_with_image_conditional() { + let condition = { + let in_image = Some(OneOrMore::::One(String::from("fooimage"))); + Condition::new(None, None, in_image) + }; + let (p1, repo) = repo_with_ab_packages_with_condition(condition); + + let condition_data = ConditionData { + image_name: None, + env: &[], + }; + + let progress = ProgressBar::hidden(); + + let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); + assert!(dag.is_ok()); + let dag = dag.unwrap(); + let ps = dag.all_packages(); + + assert!(ps.iter().any(|p| *p.name() == pname("a"))); + assert!(ps.iter().any(|p| *p.version() == pversion("1"))); + + // Not in the tree: + assert!(!ps.iter().any(|p| *p.name() == pname("b"))); + assert!(!ps.iter().any(|p| *p.version() == pversion("2"))); + } + + // Test whether the dependency DAG is correctly build if a image is used, but not the one + // required + // + // Because the dependency is conditional with "fooimage" required as build-image, but + // "barimage" is used, the dependency DAG should NOT contain package "b" + #[test] + fn test_add_two_dependent_packages_with_image_conditional_but_other_image_provided() { + let condition = { + let in_image = Some(OneOrMore::::One(String::from("fooimage"))); + Condition::new(None, None, in_image) + }; + let (p1, repo) = repo_with_ab_packages_with_condition(condition); + + let img_name = ImageName::from("barimage"); + let condition_data = ConditionData { + image_name: Some(&img_name), + env: &[], + }; + + let progress = ProgressBar::hidden(); + + let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); + assert!(dag.is_ok()); + let dag = dag.unwrap(); + let ps = dag.all_packages(); + + assert!(ps.iter().any(|p| *p.name() == pname("a"))); + assert!(ps.iter().any(|p| *p.version() == pversion("1"))); + + // Not in the tree: + assert!(!ps.iter().any(|p| *p.name() == pname("b"))); + assert!(!ps.iter().any(|p| *p.version() == pversion("2"))); + } + + // Test whether the dependency DAG is correctly build if the right image name is passed + #[test] + fn test_add_two_dependent_packages_with_image_conditional_and_image_provided() { + let condition = { + let in_image = Some(OneOrMore::::One(String::from("fooimage"))); + Condition::new(None, None, in_image) + }; + let (p1, repo) = repo_with_ab_packages_with_condition(condition); + + let img_name = ImageName::from("fooimage"); + let condition_data = ConditionData { + image_name: Some(&img_name), + env: &[], + }; + + let progress = ProgressBar::hidden(); + + let dag = Dag::for_root_package(p1, &repo, Some(&progress), &condition_data); + assert!(dag.is_ok()); + let dag = dag.unwrap(); + let ps = dag.all_packages(); + + assert!(ps.iter().any(|p| *p.name() == pname("a"))); + assert!(ps.iter().any(|p| *p.version() == pversion("1"))); + + // IN the tree: + assert!(ps.iter().any(|p| *p.name() == pname("b"))); + assert!(ps.iter().any(|p| *p.version() == pversion("2"))); + } + } -- cgit v1.2.3 From 132f1c7739ec68f520c07b2e41866d8328f758d3 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 25 Jun 2021 20:39:34 +0200 Subject: Add condition-matching against data This patch implements the matching of the condition(s) against the provided data. The patch includes tests for all three supported conditions. Signed-off-by: Matthias Beyer --- src/package/dependency/condition.rs | 228 +++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index cd8010e..c9b934a 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -13,6 +13,7 @@ use std::collections::HashMap; use serde::Deserialize; use serde::Serialize; use getset::Getters; +use anyhow::Result; use crate::util::EnvironmentVariableName; use crate::util::docker::ImageName; @@ -41,8 +42,8 @@ pub struct Condition { pub(super) in_image: Option>, } -#[cfg(test)] impl Condition { + #[cfg(test)] pub fn new(has_env: Option>, env_eq: Option>, in_image: Option>) @@ -50,6 +51,99 @@ impl Condition { { Condition { has_env, env_eq, in_image } } + + /// Check whether the condition matches a certain set of data + /// + /// # Return value + /// + /// Always returns Ok(_) in the current implementation + pub fn matches(&self, data: &ConditionData<'_>) -> Result { + if !self.matches_env_cond(data)? { + return Ok(false) + } + + if !self.matches_env_eq_cond(data)? { + return Ok(false) + } + + if !self.matches_in_image_cond(data)? { + return Ok(false) + } + + Ok(true) + } + + fn matches_env_cond(&self, data: &ConditionData<'_>) -> Result { + if let Some(has_env_cond) = self.has_env.as_ref() { + let b = match has_env_cond { + OneOrMore::One(env) => data.env.iter().any(|(name, _)| env == name), + OneOrMore::More(envs) => envs.iter().all(|required_env| { + data.env + .iter() + .any(|(name, _)| name == required_env) + }) + }; + + if !b { + return Ok(false) + } + } + + Ok(true) + } + + fn matches_env_eq_cond(&self, data: &ConditionData<'_>) -> Result { + if let Some(env_eq_cond) = self.env_eq.as_ref() { + let b = env_eq_cond.iter() + .all(|(req_env_name, req_env_val)| { + data.env + .iter() + .find(|(env_name, _)| env_name == req_env_name) + .map(|(_, env_val)| env_val == req_env_val) + .unwrap_or(false) + }); + + if !b { + return Ok(false) + } + } + + Ok(true) + } + + fn matches_in_image_cond(&self, data: &ConditionData<'_>) -> Result { + if let Some(in_image_cond) = self.in_image.as_ref() { + let b = match in_image_cond { + OneOrMore::One(req_image) => { + // because the image_name in the ConditionData is Option, + // which is a design-decision because the image can be not-specified (in the + // "tree-of" subcommand), + // we automatically use `false` as value here. + // + // That is because if we need to have a certain image (which is what this + // condition expresses), and there is no image specified in the ConditionData, + // we are by definition are NOT in this image. + data.image_name + .as_ref() + .map(|i| i.as_ref() == req_image) + .unwrap_or(false) + }, + OneOrMore::More(req_images) => { + req_images.iter() + .any(|ri| { + data.image_name + .as_ref() + .map(|inam| inam.as_ref() == ri) + .unwrap_or(false) + }) + }, + }; + + Ok(b) + } else { + Ok(true) + } + } } /// Manual implementation of PartialOrd for Condition @@ -215,4 +309,136 @@ mod tests { assert_eq!(c.in_image.unwrap(), OneOrMore::::More(vec![String::from("foo")])); } + #[test] + fn test_condition_empty() { + let data = ConditionData { + image_name: None, + env: &[], + }; + + let condition = Condition::new(None, None, None); + + assert!(condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_no_image() { + let data = ConditionData { + image_name: None, + env: &[], + }; + + let condition = Condition::new(None, None, { + Some(OneOrMore::::One(String::from("req_image"))) + }); + + assert!(!condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_matching_image() { + let img = ImageName::from("required_image"); + let data = ConditionData { + image_name: Some(&img), + env: &[], + }; + + let condition = Condition::new(None, None, { + Some(OneOrMore::::One(String::from("required_image"))) + }); + + assert!(condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_nonmatching_image() { + let img = ImageName::from("required_image"); + let data = ConditionData { + image_name: Some(&img), + env: &[], + }; + + let condition = Condition::new(None, None, { + Some(OneOrMore::::One(String::from("other_image"))) + }); + + assert!(!condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_required_env_missing() { + let data = ConditionData { + image_name: None, + env: &[], + }; + + let condition = Condition::new({ + Some(OneOrMore::::One(EnvironmentVariableName::from("A"))) + }, None, None); + + assert!(!condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_required_env_present() { + let data = ConditionData { + image_name: None, + env: &[(EnvironmentVariableName::from("A"), String::from("1"))], + }; + + let condition = Condition::new({ + Some(OneOrMore::::One(EnvironmentVariableName::from("A"))) + }, None, None); + + assert!(condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_required_env_values_missing() { + let data = ConditionData { + image_name: None, + env: &[], + }; + + let condition = Condition::new(None, { + let mut hm = HashMap::new(); + hm.insert(EnvironmentVariableName::from("A"), String::from("1")); + Some(hm) + }, None); + + assert!(!condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_required_env_values_present_but_different() { + let data = ConditionData { + image_name: None, + env: &[(EnvironmentVariableName::from("A"), String::from("1"))], + }; + + let condition = Condition::new(None, { + let mut hm = HashMap::new(); + hm.insert(EnvironmentVariableName::from("A"), String::from("2")); + Some(hm) + }, None); + + assert!(!condition.matches(&data).unwrap()); + } + + #[test] + fn test_condition_required_env_values_present_and_equal() { + let data = ConditionData { + image_name: None, + env: &[(EnvironmentVariableName::from("A"), String::from("1"))], + }; + + let condition = Condition::new(None, { + let mut hm = HashMap::new(); + hm.insert(EnvironmentVariableName::from("A"), String::from("1")); + Some(hm) + }, None); + + assert!(condition.matches(&data).unwrap()); + } + } -- cgit v1.2.3 From 3c8fb1b2bf1c5597a10aa65b28d98de6c6d7cb01 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 29 Jun 2021 09:03:18 +0200 Subject: Add testcase with multiple dependencies specified Signed-off-by: Matthias Beyer --- src/package/dependency/runtime.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 8c692ed..0a5b824 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -185,5 +185,40 @@ mod tests { other => panic!("Unexpected deserialization to other variant: {:?}", other), } } + + #[test] + fn test_parse_conditional_dependencies_pretty_3() { + let pretty = r#" + [[settings]] + name = "foo" + condition.in_image = "bar" + + [[settings]] + name = "baz" + condition.in_image = "boogie" + "#; + + let s: TestSettings = toml::from_str(pretty).e