vdev/commands/compose_tests/
test.rs

1use std::iter::once;
2
3use anyhow::{Result, bail};
4
5use crate::testing::{
6    config::ComposeTestConfig,
7    integration::{ComposeTest, ComposeTestLocalConfig},
8    runner::{LOCAL_COVERAGE_OUTPUT_DIR, coverage_filename},
9};
10
11use super::active_projects::find_active_environment_for_integration;
12
13pub fn exec(
14    local_config: ComposeTestLocalConfig,
15    integration: &str,
16    environment: Option<&String>,
17    retries: u8,
18    args: &[String],
19    coverage: bool,
20) -> Result<()> {
21    let (_test_dir, config) = ComposeTestConfig::load(local_config.directory, integration)?;
22    let envs = config.environments();
23
24    let active =
25        find_active_environment_for_integration(local_config.directory, integration, &config)?;
26    debug!("Active environment: {active:#?}");
27
28    let environments: Box<dyn Iterator<Item = &String>> = match (environment, &active) {
29        (Some(environment), Some(active)) if environment != active => {
30            bail!("Requested environment {environment:?} does not match active one {active:?}")
31        }
32        (Some(environment), _) => Box::new(once(environment)),
33        (None, Some(active)) => Box::new(once(active)),
34        (None, None) => Box::new(envs.keys()),
35    };
36
37    let mut ran_environments: Vec<String> = Vec::new();
38    for environment in environments {
39        ComposeTest::generate(local_config, integration, environment, retries, coverage)?
40            .test(args.to_owned())?;
41        if coverage {
42            ran_environments.push(environment.clone());
43        }
44    }
45
46    // Consolidate per-environment coverage files into the canonical lcov.info
47    // so callers get a single, predictable output path regardless of how many
48    // environments ran.
49    if coverage && !ran_environments.is_empty() {
50        let coverage_dir = std::path::Path::new(LOCAL_COVERAGE_OUTPUT_DIR);
51        let merged_path = coverage_dir.join(coverage_filename(None));
52        // Remove any stale lcov.info from a previous run so callers never pick
53        // up outdated data if the merge below fails to read a per-env file.
54        let _ = std::fs::remove_file(&merged_path);
55        let mut merged = String::new();
56        for env_name in &ran_environments {
57            let env_file = coverage_dir.join(coverage_filename(Some(env_name)));
58            match std::fs::read_to_string(&env_file) {
59                Ok(contents) => {
60                    merged.push_str(&contents);
61                    let _ = std::fs::remove_file(&env_file);
62                }
63                Err(e) => {
64                    warn!("Could not read coverage file {}: {e}", env_file.display());
65                }
66            }
67        }
68        if !merged.is_empty() {
69            std::fs::write(&merged_path, merged)?;
70            info!(
71                "Wrote coverage for {} environment(s) to {}",
72                ran_environments.len(),
73                merged_path.display()
74            );
75        }
76    }
77
78    Ok(())
79}