diff --git a/Cargo.lock b/Cargo.lock index 86570f8d..b44381b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ dependencies = [ "once_cell", "os_pipe", "rand 0.8.5", + "rstest", "semver", "serde", "serde_json", diff --git a/process/Cargo.toml b/process/Cargo.toml index b2e2477d..43a8c3b2 100644 --- a/process/Cargo.toml +++ b/process/Cargo.toml @@ -43,6 +43,10 @@ typed-builder.workspace = true users.workspace = true uuid.workspace = true +[dev-dependencies] +rstest.workspace = true +blue-build-utils = { version = "=0.8.13", path = "../utils", features = ["test"] } + [lints] workspace = true diff --git a/process/drivers.rs b/process/drivers.rs index ab3064f3..e0ab4b82 100644 --- a/process/drivers.rs +++ b/process/drivers.rs @@ -197,8 +197,9 @@ impl Driver { #[cfg(test)] { use miette::IntoDiagnostic; + let _ = recipe; // silence lint - if std::env::var(crate::test::BB_UNIT_TEST_MOCK_GET_OS_VERSION).is_ok() { + if true { return crate::test::create_test_recipe() .image_version .parse() @@ -223,11 +224,11 @@ impl Driver { let inspection = Self::get_metadata(&inspect_opts)?; let os_version = inspection.get_version().ok_or_else(|| { - miette!( - help = format!("Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."), - "Unable to get the OS version from the labels" - ) - })?; + miette!( + help = format!("Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."), + "Unable to get the OS version from the labels" + ) + })?; trace!("os_version: {os_version}"); os_version diff --git a/process/drivers/github_driver.rs b/process/drivers/github_driver.rs index 16301db6..21aba072 100644 --- a/process/drivers/github_driver.rs +++ b/process/drivers/github_driver.rs @@ -1,13 +1,16 @@ -use blue_build_utils::{ - constants::{ - GITHUB_EVENT_NAME, GITHUB_REF_NAME, GITHUB_SHA, GITHUB_TOKEN_ISSUER_URL, - GITHUB_WORKFLOW_REF, PR_EVENT_NUMBER, - }, - get_env_var, +use blue_build_utils::constants::{ + GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, GITHUB_TOKEN_ISSUER_URL, + GITHUB_WORKFLOW_REF, PR_EVENT_NUMBER, }; use event::Event; use log::trace; +#[cfg(not(test))] +use blue_build_utils::get_env_var; + +#[cfg(test)] +use blue_build_utils::test_utils::get_env_var; + use super::{CiDriver, Driver}; mod event; @@ -16,16 +19,8 @@ pub struct GithubDriver; impl CiDriver for GithubDriver { fn on_default_branch() -> bool { - Event::try_new().map_or_else( - |_| false, - |event| match (event.commit_ref, event.head) { - (Some(commit_ref), _) => { - commit_ref.trim_start_matches("refs/heads/") == event.repository.default_branch - } - (_, Some(head)) => event.repository.default_branch == head.commit_ref, - _ => false, - }, - ) + get_env_var(GITHUB_EVENT_PATH) + .is_ok_and(|path| Event::try_new(path).is_ok_and(|e| e.on_default_branch())) } fn keyless_cert_identity() -> miette::Result { @@ -74,120 +69,100 @@ impl CiDriver for GithubDriver { } fn get_repo_url() -> miette::Result { - Ok(Event::try_new()?.repository.html_url) + Ok(Event::try_new(get_env_var(GITHUB_EVENT_PATH)?)? + .repository + .html_url) } fn get_registry() -> miette::Result { Ok(format!( "ghcr.io/{}", - Event::try_new()?.repository.owner.login + Event::try_new(get_env_var(GITHUB_EVENT_PATH)?)? + .repository + .owner + .login )) } } #[cfg(test)] mod test { - use std::env; - - use blue_build_utils::constants::{ - GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER, + use blue_build_utils::{ + constants::{ + GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER, + }, + test_utils::set_env_var, }; - use crate::{ - drivers::CiDriver, - test::{create_test_recipe, BB_UNIT_TEST_MOCK_GET_OS_VERSION, ENV_LOCK}, - }; + use crate::{drivers::CiDriver, test::create_test_recipe}; use super::GithubDriver; fn setup_default_branch() { setup(); - env::set_var( + set_env_var( GITHUB_EVENT_PATH, "../test-files/github-events/default-branch.json", ); - env::set_var(GITHUB_REF_NAME, "main"); + set_env_var(GITHUB_REF_NAME, "main"); } fn setup_pr_branch() { setup(); - env::set_var( + set_env_var( GITHUB_EVENT_PATH, "../test-files/github-events/pr-branch.json", ); - env::set_var(GITHUB_EVENT_NAME, "pull_request"); - env::set_var(GITHUB_REF_NAME, "test"); - env::set_var(PR_EVENT_NUMBER, "12"); + set_env_var(GITHUB_EVENT_NAME, "pull_request"); + set_env_var(GITHUB_REF_NAME, "test"); + set_env_var(PR_EVENT_NUMBER, "12"); } fn setup_branch() { setup(); - env::set_var(GITHUB_EVENT_PATH, "../test-files/github-events/branch.json"); - env::set_var(GITHUB_REF_NAME, "test"); + set_env_var(GITHUB_EVENT_PATH, "../test-files/github-events/branch.json"); + set_env_var(GITHUB_REF_NAME, "test"); } fn setup() { - env::set_var(GITHUB_EVENT_NAME, "push"); - env::set_var(GITHUB_SHA, "1234567890"); - env::set_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION, ""); - } - - fn teardown() { - env::remove_var(GITHUB_EVENT_NAME); - env::remove_var(GITHUB_EVENT_PATH); - env::remove_var(GITHUB_REF_NAME); - env::remove_var(PR_EVENT_NUMBER); - env::remove_var(GITHUB_SHA); - env::remove_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION); + set_env_var(GITHUB_EVENT_NAME, "push"); + set_env_var(GITHUB_SHA, "1234567890"); } #[test] fn get_registry() { - let _env = ENV_LOCK.lock().unwrap(); - setup_default_branch(); let registry = GithubDriver::get_registry().unwrap(); assert_eq!(registry, "ghcr.io/test-owner"); - teardown(); } #[test] fn on_default_branch_true() { - let _env = ENV_LOCK.lock().unwrap(); - setup_default_branch(); assert!(GithubDriver::on_default_branch()); - teardown(); } #[test] fn on_default_branch_false() { - let _env = ENV_LOCK.lock().unwrap(); - setup_pr_branch(); assert!(!GithubDriver::on_default_branch()); - teardown(); } #[test] fn get_repo_url() { - let _env = ENV_LOCK.lock().unwrap(); - setup_branch(); let url = GithubDriver::get_repo_url().unwrap(); assert_eq!(url, "https://example.com/"); - teardown(); } #[test] fn generate_tags_default_branch() { - let _env = ENV_LOCK.lock().unwrap(); let timestamp = blue_build_utils::get_tag_timestamp(); setup_default_branch(); @@ -205,13 +180,10 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } #[test] fn generate_tags_default_branch_alt_tags() { - let _env = ENV_LOCK.lock().unwrap(); let timestamp = blue_build_utils::get_tag_timestamp(); setup_default_branch(); @@ -232,14 +204,10 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } #[test] fn generate_tags_pr_branch() { - let _env = ENV_LOCK.lock().unwrap(); - setup_pr_branch(); let mut tags = GithubDriver::generate_tags(&create_test_recipe()).unwrap(); @@ -249,14 +217,10 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } #[test] fn generate_tags_branch() { - let _env = ENV_LOCK.lock().unwrap(); - setup_branch(); let mut tags = GithubDriver::generate_tags(&create_test_recipe()).unwrap(); @@ -266,7 +230,5 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } } diff --git a/process/drivers/github_driver/event.rs b/process/drivers/github_driver/event.rs index fa07d559..e2dfa8f0 100644 --- a/process/drivers/github_driver/event.rs +++ b/process/drivers/github_driver/event.rs @@ -1,9 +1,16 @@ -use std::{fs, path::PathBuf}; +use std::path::Path; -use blue_build_utils::{constants::GITHUB_EVENT_PATH, get_env_var}; -use miette::{IntoDiagnostic, Result}; +use blue_build_utils::constants::GITHUB_REF_NAME; +use log::debug; +use miette::{Context, IntoDiagnostic, Result}; use serde::Deserialize; +#[cfg(test)] +use blue_build_utils::test_utils::get_env_var; + +#[cfg(not(test))] +use blue_build_utils::get_env_var; + #[derive(Debug, Deserialize, Clone)] pub(super) struct Event { pub repository: EventRepository, @@ -14,17 +21,57 @@ pub(super) struct Event { pub commit_ref: Option, } +impl TryFrom<&str> for Event { + type Error = miette::Report; + + fn try_from(value: &str) -> std::prelude::v1::Result { + serde_json::from_str(value).into_diagnostic() + } +} + impl Event { - pub fn try_new() -> Result { - get_env_var(GITHUB_EVENT_PATH) - .map(PathBuf::from) - .and_then(|event_path| { - serde_json::from_str::(&fs::read_to_string(event_path).into_diagnostic()?) - .into_diagnostic() - }) + pub fn try_new

(path: P) -> Result + where + P: AsRef, + { + fn inner(path: &Path) -> Result { + let contents = std::fs::read_to_string(path) + .into_diagnostic() + .with_context(|| format!("Path: {}", path.display()))?; + Event::try_from(contents.as_str()) + } + inner(path.as_ref()) + } + + pub fn on_default_branch(&self) -> bool { + debug!("{self:#?}"); + + match ( + self.commit_ref.as_ref(), + self.head.as_ref(), + get_env_var(GITHUB_REF_NAME), + ) { + (Some(commit_ref), _, _) => { + commit_ref.trim_start_matches("refs/heads/") == self.repository.default_branch + } + (_, Some(head), _) => self.repository.default_branch == head.commit_ref, + (_, _, Ok(ref_name)) => self.repository.default_branch == ref_name, + _ => false, + } } } +// impl Event { +// pub fn try_new() -> Result { +// get_env_var(GITHUB_EVENT_PATH) +// .map(PathBuf::from) +// .and_then(|event_path| { +// serde_json::from_str::(&fs::read_to_string(event_path).into_diagnostic()?) +// .into_diagnostic() +// }) +// } +// } + #[derive(Debug, Deserialize, Clone)] pub(super) struct EventRepository { pub default_branch: String, @@ -42,3 +89,25 @@ pub(super) struct EventRefInfo { #[serde(alias = "ref")] pub commit_ref: String, } + +#[cfg(test)] +mod test { + use blue_build_utils::{constants::GITHUB_REF_NAME, test_utils::set_env_var}; + use rstest::rstest; + + use super::Event; + + #[rstest] + #[case::scheduled_main("../test-files/github-events/scheduled.json", "main", true)] + #[case::push_main("../test-files/github-events/default-branch.json", "main", true)] + #[case::pr("../test-files/github-events/pr-branch.json", "test", false)] + #[case::branch("../test-files/github-events/branch.json", "test", false)] + fn test_on_default_branch(#[case] path: &str, #[case] ref_name: &str, #[case] expected: bool) { + set_env_var(GITHUB_REF_NAME, ref_name); + + let event = Event::try_new(path).unwrap(); + eprintln!("{event:?}"); + + assert_eq!(expected, event.on_default_branch()); + } +} diff --git a/process/drivers/gitlab_driver.rs b/process/drivers/gitlab_driver.rs index 8d05f6c4..ca71ab42 100644 --- a/process/drivers/gitlab_driver.rs +++ b/process/drivers/gitlab_driver.rs @@ -1,15 +1,16 @@ -use std::env; - -use blue_build_utils::{ - constants::{ - CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, - CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_URL, CI_REGISTRY, - CI_SERVER_HOST, CI_SERVER_PROTOCOL, - }, - get_env_var, +use blue_build_utils::constants::{ + CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, + CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_URL, CI_REGISTRY, + CI_SERVER_HOST, CI_SERVER_PROTOCOL, }; use log::{debug, trace}; +#[cfg(not(test))] +use blue_build_utils::get_env_var; + +#[cfg(test)] +use blue_build_utils::test_utils::get_env_var; + use crate::drivers::Driver; use super::CiDriver; @@ -18,8 +19,8 @@ pub struct GitlabDriver; impl CiDriver for GitlabDriver { fn on_default_branch() -> bool { - env::var(CI_DEFAULT_BRANCH).is_ok_and(|default_branch| { - env::var(CI_COMMIT_REF_NAME).is_ok_and(|branch| default_branch == branch) + get_env_var(CI_DEFAULT_BRANCH).is_ok_and(|default_branch| { + get_env_var(CI_COMMIT_REF_NAME).is_ok_and(|branch| default_branch == branch) }) } @@ -57,7 +58,7 @@ impl CiDriver for GitlabDriver { tags.push("latest".into()); tags.push(timestamp); } - } else if let Ok(mr_iid) = env::var(CI_MERGE_REQUEST_IID) { + } else if let Ok(mr_iid) = get_env_var(CI_MERGE_REQUEST_IID) { trace!("{CI_MERGE_REQUEST_IID}={mr_iid}"); let pipeline_source = get_env_var(CI_PIPELINE_SOURCE)?; @@ -105,110 +106,80 @@ impl CiDriver for GitlabDriver { #[cfg(test)] mod test { - use std::env; - - use blue_build_utils::constants::{ - CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, - CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST, - CI_SERVER_PROTOCOL, + use blue_build_utils::{ + constants::{ + CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, + CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST, + CI_SERVER_PROTOCOL, + }, + test_utils::set_env_var, }; - use crate::{ - drivers::CiDriver, - test::{create_test_recipe, BB_UNIT_TEST_MOCK_GET_OS_VERSION, ENV_LOCK}, - }; + use crate::{drivers::CiDriver, test::create_test_recipe}; use super::GitlabDriver; fn setup_default_branch() { setup(); - env::set_var(CI_COMMIT_REF_NAME, "main"); + set_env_var(CI_COMMIT_REF_NAME, "main"); } fn setup_mr_branch() { setup(); - env::set_var(CI_MERGE_REQUEST_IID, "12"); - env::set_var(CI_PIPELINE_SOURCE, "merge_request_event"); - env::set_var(CI_COMMIT_REF_NAME, "test"); + set_env_var(CI_MERGE_REQUEST_IID, "12"); + set_env_var(CI_PIPELINE_SOURCE, "merge_request_event"); + set_env_var(CI_COMMIT_REF_NAME, "test"); } fn setup_branch() { setup(); - env::set_var(CI_COMMIT_REF_NAME, "test"); + set_env_var(CI_COMMIT_REF_NAME, "test"); } fn setup() { - env::set_var(CI_DEFAULT_BRANCH, "main"); - env::set_var(CI_COMMIT_SHORT_SHA, "1234567"); - env::set_var(CI_REGISTRY, "registry.example.com"); - env::set_var(CI_PROJECT_NAMESPACE, "test-project"); - env::set_var(CI_PROJECT_NAME, "test"); - env::set_var(CI_SERVER_PROTOCOL, "https"); - env::set_var(CI_SERVER_HOST, "gitlab.example.com"); - env::set_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION, ""); - } - - fn teardown() { - env::remove_var(CI_COMMIT_REF_NAME); - env::remove_var(CI_MERGE_REQUEST_IID); - env::remove_var(CI_PIPELINE_SOURCE); - env::remove_var(CI_DEFAULT_BRANCH); - env::remove_var(CI_COMMIT_SHORT_SHA); - env::remove_var(CI_REGISTRY); - env::remove_var(CI_PROJECT_NAMESPACE); - env::remove_var(CI_PROJECT_NAME); - env::remove_var(CI_SERVER_PROTOCOL); - env::remove_var(CI_SERVER_HOST); - env::remove_var(BB_UNIT_TEST_MOCK_GET_OS_VERSION); + set_env_var(CI_DEFAULT_BRANCH, "main"); + set_env_var(CI_COMMIT_SHORT_SHA, "1234567"); + set_env_var(CI_REGISTRY, "registry.example.com"); + set_env_var(CI_PROJECT_NAMESPACE, "test-project"); + set_env_var(CI_PROJECT_NAME, "test"); + set_env_var(CI_SERVER_PROTOCOL, "https"); + set_env_var(CI_SERVER_HOST, "gitlab.example.com"); } #[test] fn get_registry() { - let _env = ENV_LOCK.lock().unwrap(); - setup(); let registry = GitlabDriver::get_registry().unwrap(); assert_eq!(registry, "registry.example.com/test-project/test"); - teardown(); } #[test] fn on_default_branch_true() { - let _env = ENV_LOCK.lock().unwrap(); - setup_default_branch(); assert!(GitlabDriver::on_default_branch()); - teardown(); } #[test] fn on_default_branch_false() { - let _env = ENV_LOCK.lock().unwrap(); - setup_branch(); assert!(!GitlabDriver::on_default_branch()); - teardown(); } #[test] fn get_repo_url() { - let _env = ENV_LOCK.lock().unwrap(); - setup(); let url = GitlabDriver::get_repo_url().unwrap(); assert_eq!(url, "https://gitlab.example.com/test-project/test"); - teardown(); } #[test] fn generate_tags_default_branch() { - let _env = ENV_LOCK.lock().unwrap(); let timestamp = blue_build_utils::get_tag_timestamp(); setup_default_branch(); @@ -226,13 +197,10 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } #[test] fn generate_tags_default_branch_alt_tags() { - let _env = ENV_LOCK.lock().unwrap(); let timestamp = blue_build_utils::get_tag_timestamp(); setup_default_branch(); @@ -253,14 +221,10 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } #[test] fn generate_tags_mr_branch() { - let _env = ENV_LOCK.lock().unwrap(); - setup_mr_branch(); let mut tags = GitlabDriver::generate_tags(&create_test_recipe()).unwrap(); @@ -270,14 +234,10 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } #[test] fn generate_tags_branch() { - let _env = ENV_LOCK.lock().unwrap(); - setup_branch(); let mut tags = GitlabDriver::generate_tags(&create_test_recipe()).unwrap(); @@ -287,7 +247,5 @@ mod test { expected_tags.sort(); assert_eq!(tags, expected_tags); - - teardown(); } } diff --git a/process/process.rs b/process/process.rs index 628b4bd0..38c6a160 100644 --- a/process/process.rs +++ b/process/process.rs @@ -21,23 +21,8 @@ pub(crate) static RT: Lazy = Lazy::new(|| { #[cfg(test)] pub(crate) mod test { - use std::sync::Mutex; - use blue_build_recipe::{Module, ModuleExt, Recipe}; use indexmap::IndexMap; - use once_cell::sync::Lazy; - - pub const BB_UNIT_TEST_MOCK_GET_OS_VERSION: &str = "BB_UNIT_TEST_MOCK_GET_OS_VERSION"; - - /// This mutex is used for tests that require the reading of - /// environment variables. Env vars are an inheritly unsafe - /// as they can be changed and affect other threads functionality. - /// - /// For tests that require setting env vars, they need to lock this - /// mutex before making changes to the env. Any changes made to the env - /// MUST be undone in the same test before dropping the lock. Failure to - /// do so will cause unpredictable behavior with other tests. - pub static ENV_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); pub fn create_test_recipe() -> Recipe<'static> { Recipe::builder() diff --git a/test-files/github-events/scheduled.json b/test-files/github-events/scheduled.json new file mode 100644 index 00000000..2fee1124 --- /dev/null +++ b/test-files/github-events/scheduled.json @@ -0,0 +1,9 @@ +{ + "repository": { + "default_branch": "main", + "html_url": "https://github.com/gmpinder/testos", + "owner": { + "login": "gmpinder" + } + } +} \ No newline at end of file diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 594c415d..7960e31f 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -37,3 +37,5 @@ rstest.workspace = true [lints] workspace = true +[features] +test = [] diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 53377db6..332b9506 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -3,6 +3,8 @@ pub mod constants; pub mod credentials; mod macros; pub mod syntax_highlighting; +#[cfg(feature = "test")] +pub mod test_utils; use std::{ os::unix::ffi::OsStrExt, diff --git a/utils/src/test_utils.rs b/utils/src/test_utils.rs new file mode 100644 index 00000000..dc1a6371 --- /dev/null +++ b/utils/src/test_utils.rs @@ -0,0 +1,59 @@ +use std::{ + collections::HashMap, + sync::{Arc, LazyLock, RwLock}, + thread::{self, ThreadId}, +}; + +use miette::{miette, Result}; + +use crate::string; + +#[allow(clippy::type_complexity)] +static ENV_VARS: LazyLock>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); + +/// Test harness function for getting env variables. +/// +/// # Errors +/// Will error if the env variable doesn't exist. +pub fn get_env_var(key: S) -> Result +where + S: AsRef, +{ + fn inner(key: &str) -> Result { + let thr_id = thread::current().id(); + + let env_vars = ENV_VARS.read().unwrap(); + let key = (thr_id, string!(key)); + + env_vars + .get(&key) + .map(ToOwned::to_owned) + .inspect(|val| eprintln!("get: {key:?} = {val}")) + .ok_or_else(|| miette!("Failed to retrieve env var '{key:?}'")) + } + inner(key.as_ref()) +} + +pub fn set_env_var(key: S, value: T) +where + S: AsRef, + T: AsRef, +{ + fn inner(key: &str, value: &str) { + let thr_id = thread::current().id(); + + let mut env_vars = ENV_VARS.write().unwrap(); + + let key = (thr_id, string!(key)); + eprintln!("set: {key:?} = {value}"); + + env_vars + .entry(key) + .and_modify(|val| { + *val = string!(value); + }) + .or_insert_with(|| string!(value)); + } + inner(key.as_ref(), value.as_ref()); +}