File size: 6,149 Bytes
52a9af3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use std::ffi::OsStr;

use anyhow::Result;
use codex_utils_path_uri::LegacyAppPathString;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;

pub const TEST_ENVIRONMENT_ENV_VAR: &str = "CODEX_TEST_ENVIRONMENT";
pub const LEGACY_REMOTE_ENV_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV";
pub const DOCKER_CONTAINER_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV_CONTAINER_NAME";

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TestTargetOs {
    Linux,
    MacOs,
    Windows,
}

impl TestTargetOs {
    const fn host() -> Self {
        if cfg!(target_os = "macos") {
            Self::MacOs
        } else if cfg!(target_os = "windows") {
            Self::Windows
        } else if cfg!(target_os = "linux") {
            Self::Linux
        } else {
            unreachable!()
        }
    }

    const fn path_convention(self) -> PathConvention {
        match self {
            Self::Linux | Self::MacOs => PathConvention::Posix,
            Self::Windows => PathConvention::Windows,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum TestEnvironment {
    Local,
    Docker { container_name: String },
    WineExec,
}

impl TestEnvironment {
    pub(crate) fn is_remote(&self) -> bool {
        !matches!(self, Self::Local)
    }

    pub(crate) fn docker_container_name(&self) -> Option<&str> {
        match self {
            Self::Docker { container_name } => Some(container_name),
            Self::Local | Self::WineExec => None,
        }
    }

    pub(crate) const fn target_os(&self) -> TestTargetOs {
        match self {
            Self::Local => TestTargetOs::host(),
            Self::Docker { .. } => TestTargetOs::Linux,
            Self::WineExec => TestTargetOs::Windows,
        }
    }

    pub(crate) fn remote_cwd(&self, instance_id: &str) -> Result<Option<LegacyAppPathString>> {
        let path_uri = match self {
            Self::Local => return Ok(None),
            Self::Docker { .. } => {
                PathUri::parse(&format!("file:///tmp/codex-core-test-cwd-{instance_id}"))?
            }
            Self::WineExec => {
                // Each Wine-exec test process has an isolated filesystem root, so this drive-root
                // path cannot collide with a different Bazel shard.
                PathUri::parse(&format!("file:///C:/codex-core-test-cwd-{instance_id}"))?
            }
        };
        Ok(Some(LegacyAppPathString::from_path_uri(
            &path_uri,
            self.path_convention(),
        )?))
    }

    pub(crate) fn path_convention(&self) -> PathConvention {
        self.target_os().path_convention()
    }
}

pub(crate) fn test_environment() -> TestEnvironment {
    let environment = parse_test_environment(
        std::env::var_os(TEST_ENVIRONMENT_ENV_VAR).as_deref(),
        std::env::var_os(LEGACY_REMOTE_ENV_ENV_VAR).as_deref(),
        std::env::var_os(DOCKER_CONTAINER_ENV_VAR).as_deref(),
    )
    .expect("invalid test environment configuration");

    if matches!(environment, TestEnvironment::WineExec) && !cfg!(target_os = "linux") {
        panic!("{TEST_ENVIRONMENT_ENV_VAR}=wine-exec is only supported on Linux");
    }

    environment
}

/// Returns the operating system used by the selected test execution environment.
pub fn test_target_os() -> TestTargetOs {
    test_environment().target_os()
}

/// Returns whether the selected test execution environment is remote.
pub fn is_remote_test_environment() -> bool {
    test_environment().is_remote()
}

/// Returns the selected Docker test container, when the harness requires direct access to it.
#[doc(hidden)]
pub fn test_docker_container_name() -> Option<String> {
    match test_environment() {
        TestEnvironment::Docker { container_name } => Some(container_name),
        TestEnvironment::Local | TestEnvironment::WineExec => None,
    }
}

/// Returns whether the Wine-backed executor is selected.
#[doc(hidden)]
pub fn is_wine_exec_test_environment() -> bool {
    matches!(test_environment(), TestEnvironment::WineExec)
}

fn parse_test_environment(
    configured_environment: Option<&OsStr>,
    legacy_remote_environment: Option<&OsStr>,
    docker_container: Option<&OsStr>,
) -> Result<TestEnvironment, String> {
    let configured_environment = configured_environment
        .map(|value| {
            value
                .to_str()
                .ok_or_else(|| format!("{TEST_ENVIRONMENT_ENV_VAR} must contain valid UTF-8"))
        })
        .transpose()?;

    match configured_environment {
        None => match legacy_remote_environment {
            Some(container_name) => Ok(TestEnvironment::Docker {
                container_name: non_empty_utf8(LEGACY_REMOTE_ENV_ENV_VAR, container_name)?,
            }),
            None => Ok(TestEnvironment::Local),
        },
        Some("local") => Ok(TestEnvironment::Local),
        Some("docker") => {
            let (container_name_env_var, container_name) = match docker_container {
                Some(container_name) => (DOCKER_CONTAINER_ENV_VAR, container_name),
                None => (
                    LEGACY_REMOTE_ENV_ENV_VAR,
                    legacy_remote_environment.ok_or_else(|| {
                        format!(
                            "{DOCKER_CONTAINER_ENV_VAR} must be set when {TEST_ENVIRONMENT_ENV_VAR}=docker"
                        )
                    })?,
                ),
            };
            Ok(TestEnvironment::Docker {
                container_name: non_empty_utf8(container_name_env_var, container_name)?,
            })
        }
        Some("wine-exec") => Ok(TestEnvironment::WineExec),
        Some(value) => Err(format!(
            "{TEST_ENVIRONMENT_ENV_VAR} must be one of local, docker, or wine-exec; got {value:?}"
        )),
    }
}

fn non_empty_utf8(name: &str, value: &OsStr) -> Result<String, String> {
    let value = value
        .to_str()
        .ok_or_else(|| format!("{name} must contain valid UTF-8"))?;
    if value.trim().is_empty() {
        return Err(format!("{name} must not be empty"));
    }
    Ok(value.to_string())
}

#[cfg(test)]
#[path = "test_environment_tests.rs"]
mod tests;