Skip to content

Commit 30fd74a

Browse files
committed
Add tests
1 parent a1af5a6 commit 30fd74a

File tree

5 files changed

+324
-39
lines changed

5 files changed

+324
-39
lines changed

src/ci/citool/Cargo.lock

+70
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ version = "0.1.0"
6464
dependencies = [
6565
"anyhow",
6666
"clap",
67+
"insta",
6768
"serde",
6869
"serde_json",
6970
"serde_yaml",
@@ -115,6 +116,24 @@ version = "1.0.3"
115116
source = "registry+https://github.com/rust-lang/crates.io-index"
116117
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
117118

119+
[[package]]
120+
name = "console"
121+
version = "0.15.10"
122+
source = "registry+https://github.com/rust-lang/crates.io-index"
123+
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
124+
dependencies = [
125+
"encode_unicode",
126+
"libc",
127+
"once_cell",
128+
"windows-sys",
129+
]
130+
131+
[[package]]
132+
name = "encode_unicode"
133+
version = "1.0.0"
134+
source = "registry+https://github.com/rust-lang/crates.io-index"
135+
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
136+
118137
[[package]]
119138
name = "equivalent"
120139
version = "1.0.1"
@@ -143,6 +162,19 @@ dependencies = [
143162
"hashbrown",
144163
]
145164

165+
[[package]]
166+
name = "insta"
167+
version = "1.42.1"
168+
source = "registry+https://github.com/rust-lang/crates.io-index"
169+
checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86"
170+
dependencies = [
171+
"console",
172+
"linked-hash-map",
173+
"once_cell",
174+
"pin-project",
175+
"similar",
176+
]
177+
146178
[[package]]
147179
name = "is_terminal_polyfill"
148180
version = "1.70.1"
@@ -155,6 +187,18 @@ version = "1.0.14"
155187
source = "registry+https://github.com/rust-lang/crates.io-index"
156188
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
157189

190+
[[package]]
191+
name = "libc"
192+
version = "0.2.169"
193+
source = "registry+https://github.com/rust-lang/crates.io-index"
194+
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
195+
196+
[[package]]
197+
name = "linked-hash-map"
198+
version = "0.5.6"
199+
source = "registry+https://github.com/rust-lang/crates.io-index"
200+
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
201+
158202
[[package]]
159203
name = "memchr"
160204
version = "2.7.4"
@@ -167,6 +211,26 @@ version = "1.20.3"
167211
source = "registry+https://github.com/rust-lang/crates.io-index"
168212
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
169213

214+
[[package]]
215+
name = "pin-project"
216+
version = "1.1.9"
217+
source = "registry+https://github.com/rust-lang/crates.io-index"
218+
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
219+
dependencies = [
220+
"pin-project-internal",
221+
]
222+
223+
[[package]]
224+
name = "pin-project-internal"
225+
version = "1.1.9"
226+
source = "registry+https://github.com/rust-lang/crates.io-index"
227+
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
228+
dependencies = [
229+
"proc-macro2",
230+
"quote",
231+
"syn",
232+
]
233+
170234
[[package]]
171235
name = "proc-macro2"
172236
version = "1.0.93"
@@ -236,6 +300,12 @@ dependencies = [
236300
"unsafe-libyaml",
237301
]
238302

303+
[[package]]
304+
name = "similar"
305+
version = "2.7.0"
306+
source = "registry+https://github.com/rust-lang/crates.io-index"
307+
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
308+
239309
[[package]]
240310
name = "strsim"
241311
version = "0.11.1"

src/ci/citool/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ serde = { version = "1", features = ["derive"] }
1010
serde_yaml = "0.9"
1111
serde_json = "1"
1212

13+
[dev-dependencies]
14+
insta = "1"
15+
1316
# Tell cargo that citool is its own workspace.
1417
# If this is omitted, cargo will look for a workspace elsewhere.
1518
# We want to avoid this, since citool is independent of the other crates.

src/ci/citool/src/main.rs

+42-39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::collections::HashMap;
2-
use std::path::Path;
1+
use std::collections::BTreeMap;
2+
use std::path::{Path, PathBuf};
33
use std::process::Command;
44

55
use anyhow::Context;
@@ -17,13 +17,13 @@ struct Job {
1717
name: String,
1818
/// GitHub runner on which the job should be executed
1919
os: String,
20-
env: HashMap<String, Value>,
20+
env: BTreeMap<String, Value>,
2121
/// Should the job be only executed on a specific channel?
2222
#[serde(default)]
2323
only_on_channel: Option<String>,
2424
/// Rest of attributes that will be passed through to GitHub actions
2525
#[serde(flatten)]
26-
extra_keys: HashMap<String, Value>,
26+
extra_keys: BTreeMap<String, Value>,
2727
}
2828

2929
impl Job {
@@ -44,11 +44,11 @@ impl Job {
4444
#[derive(serde::Deserialize, Debug)]
4545
struct JobEnvironments {
4646
#[serde(rename = "pr")]
47-
pr_env: HashMap<String, Value>,
47+
pr_env: BTreeMap<String, Value>,
4848
#[serde(rename = "try")]
49-
try_env: HashMap<String, Value>,
49+
try_env: BTreeMap<String, Value>,
5050
#[serde(rename = "auto")]
51-
auto_env: HashMap<String, Value>,
51+
auto_env: BTreeMap<String, Value>,
5252
}
5353

5454
#[derive(serde::Deserialize, Debug)]
@@ -71,7 +71,7 @@ impl JobDatabase {
7171
}
7272

7373
fn load_job_db(path: &Path) -> anyhow::Result<JobDatabase> {
74-
let db = std::fs::read_to_string(path)?;
74+
let db = read_to_string(path)?;
7575
let mut db: Value = serde_yaml::from_str(&db)?;
7676

7777
// We need to expand merge keys (<<), because serde_yaml can't deal with them
@@ -92,9 +92,9 @@ struct GithubActionsJob {
9292
/// prefix (PR/try/auto).
9393
full_name: String,
9494
os: String,
95-
env: HashMap<String, String>,
95+
env: BTreeMap<String, String>,
9696
#[serde(flatten)]
97-
extra_keys: HashMap<String, serde_json::Value>,
97+
extra_keys: BTreeMap<String, serde_json::Value>,
9898
}
9999

100100
/// Type of workflow that is being executed on CI
@@ -116,27 +116,17 @@ struct GitHubContext {
116116

117117
impl GitHubContext {
118118
fn get_run_type(&self) -> Option<RunType> {
119-
if self.event_name == "pull_request" {
120-
return Some(RunType::PullRequest);
121-
} else if self.event_name == "push" {
122-
let is_try_build =
123-
["refs/heads/try", "refs/heads/try-perf", "refs/heads/automation/bors/try"]
124-
.iter()
125-
.any(|r| **r == self.branch_ref);
126-
// Unrolled branch from a rollup for testing perf
127-
// This should **not** allow custom try jobs
128-
let is_unrolled_perf_build = self.branch_ref == "refs/heads/try-perf";
129-
if is_try_build {
130-
let custom_jobs =
131-
if !is_unrolled_perf_build { Some(self.get_custom_jobs()) } else { None };
132-
return Some(RunType::TryJob { custom_jobs });
133-
}
134-
135-
if self.branch_ref == "refs/heads/auto" {
136-
return Some(RunType::AutoJob);
119+
match (self.event_name.as_str(), self.branch_ref.as_str()) {
120+
("pull_request", _) => Some(RunType::PullRequest),
121+
("push", "refs/heads/try-perf") => Some(RunType::TryJob { custom_jobs: None }),
122+
("push", "refs/heads/try" | "refs/heads/automation/bors/try") => {
123+
let custom_jobs = self.get_custom_jobs();
124+
let custom_jobs = if !custom_jobs.is_empty() { Some(custom_jobs) } else { None };
125+
Some(RunType::TryJob { custom_jobs })
137126
}
127+
("push", "refs/heads/auto") => Some(RunType::AutoJob),
128+
_ => None,
138129
}
139-
None
140130
}
141131

142132
/// Tries to parse names of specific CI jobs that should be executed in the form of
@@ -175,7 +165,7 @@ fn skip_jobs(jobs: Vec<Job>, channel: &str) -> Vec<Job> {
175165
.collect()
176166
}
177167

178-
fn to_string_map(map: &HashMap<String, Value>) -> HashMap<String, String> {
168+
fn to_string_map(map: &BTreeMap<String, Value>) -> BTreeMap<String, String> {
179169
map.iter()
180170
.map(|(key, value)| {
181171
(
@@ -232,7 +222,7 @@ fn calculate_jobs(
232222
let jobs = jobs
233223
.into_iter()
234224
.map(|job| {
235-
let mut env: HashMap<String, String> = to_string_map(base_env);
225+
let mut env: BTreeMap<String, String> = to_string_map(base_env);
236226
env.extend(to_string_map(&job.env));
237227
let full_name = format!("{prefix} - {}", job.name);
238228

@@ -312,9 +302,9 @@ fn run_workflow_locally(db: JobDatabase, job_type: JobType, name: String) -> any
312302
JobType::Auto => &db.auto_jobs,
313303
JobType::PR => &db.pr_jobs,
314304
};
315-
let job = find_linux_job(&jobs, &name).with_context(|| format!("Cannot find job {name}"))?;
305+
let job = find_linux_job(jobs, &name).with_context(|| format!("Cannot find job {name}"))?;
316306

317-
let mut custom_env: HashMap<String, String> = HashMap::new();
307+
let mut custom_env: BTreeMap<String, String> = BTreeMap::new();
318308
// Replicate src/ci/scripts/setup-environment.sh
319309
// Adds custom environment variables to the job
320310
if name.starts_with("dist-") {
@@ -340,7 +330,10 @@ fn run_workflow_locally(db: JobDatabase, job_type: JobType, name: String) -> any
340330
enum Args {
341331
/// Calculate a list of jobs that should be executed on CI.
342332
/// Should only be used on CI inside GitHub actions.
343-
CalculateJobMatrix,
333+
CalculateJobMatrix {
334+
#[clap(long)]
335+
jobs_file: Option<PathBuf>,
336+
},
344337
/// Execute a given CI job locally.
345338
#[clap(name = "run-local")]
346339
RunJobLocally {
@@ -362,19 +355,29 @@ enum JobType {
362355

363356
fn main() -> anyhow::Result<()> {
364357
let args = Args::parse();
365-
let db = load_job_db(Path::new(JOBS_YML_PATH)).context("Cannot load jobs.yml")?;
358+
let default_jobs_file = Path::new(JOBS_YML_PATH);
359+
let load_db = |jobs_path| load_job_db(jobs_path).context("Cannot load jobs.yml");
366360

367361
match args {
368-
Args::CalculateJobMatrix => {
362+
Args::CalculateJobMatrix { jobs_file } => {
363+
let jobs_path = jobs_file.as_deref().unwrap_or(default_jobs_file);
369364
let gh_ctx = load_github_ctx()
370365
.context("Cannot load environment variables from GitHub Actions")?;
371-
let channel = std::fs::read_to_string(Path::new(CI_DIRECTORY).join("channel"))
366+
let channel = read_to_string(Path::new(CI_DIRECTORY).join("channel"))
372367
.context("Cannot read channel file")?;
373368

374-
calculate_job_matrix(db, gh_ctx, &channel).context("Failed to calculate job matrix")?;
369+
calculate_job_matrix(load_db(jobs_path)?, gh_ctx, &channel)
370+
.context("Failed to calculate job matrix")?;
371+
}
372+
Args::RunJobLocally { job_type, name } => {
373+
run_workflow_locally(load_db(default_jobs_file)?, job_type, name)?
375374
}
376-
Args::RunJobLocally { job_type, name } => run_workflow_locally(db, job_type, name)?,
377375
}
378376

379377
Ok(())
380378
}
379+
380+
fn read_to_string<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {
381+
let error = format!("Cannot read file {:?}", path.as_ref());
382+
std::fs::read_to_string(path).context(error)
383+
}

src/ci/citool/tests/jobs.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use std::process::{Command, Stdio};
2+
3+
const TEST_JOBS_YML_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/test-jobs.yml");
4+
5+
#[test]
6+
fn auto_jobs() {
7+
let stdout = get_matrix("push", "commit", "refs/heads/auto");
8+
insta::assert_snapshot!(stdout, @r#"
9+
jobs=[{"name":"aarch64-gnu","full_name":"auto - aarch64-gnu","os":"ubuntu-22.04-arm","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","DEPLOY_BUCKET":"rust-lang-ci2","TOOLSTATE_PUBLISH":"1"},"free_disk":true},{"name":"x86_64-gnu-llvm-18-1","full_name":"auto - x86_64-gnu-llvm-18-1","os":"ubuntu-24.04","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","DEPLOY_BUCKET":"rust-lang-ci2","DOCKER_SCRIPT":"stage_2_test_set1.sh","IMAGE":"x86_64-gnu-llvm-18","READ_ONLY_SRC":"'0'","RUST_BACKTRACE":"1","TOOLSTATE_PUBLISH":"1"},"free_disk":true},{"name":"aarch64-apple","full_name":"auto - aarch64-apple","os":"macos-14","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","DEPLOY_BUCKET":"rust-lang-ci2","MACOSX_DEPLOYMENT_TARGET":"11.0","MACOSX_STD_DEPLOYMENT_TARGET":"11.0","NO_DEBUG_ASSERTIONS":"1","NO_LLVM_ASSERTIONS":"1","NO_OVERFLOW_CHECKS":"1","RUSTC_RETRY_LINKER_ON_SEGFAULT":"1","RUST_CONFIGURE_ARGS":"--enable-sanitizers --enable-profiler --set rust.jemalloc","SCRIPT":"./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin","SELECT_XCODE":"/Applications/Xcode_15.4.app","TOOLSTATE_PUBLISH":"1","USE_XCODE_CLANG":"1"}},{"name":"dist-i686-msvc","full_name":"auto - dist-i686-msvc","os":"windows-2022","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","CODEGEN_BACKENDS":"llvm,cranelift","DEPLOY_BUCKET":"rust-lang-ci2","DIST_REQUIRE_ALL_TOOLS":"1","RUST_CONFIGURE_ARGS":"--build=i686-pc-windows-msvc --host=i686-pc-windows-msvc --target=i686-pc-windows-msvc,i586-pc-windows-msvc --enable-full-tools --enable-profiler","SCRIPT":"python x.py dist bootstrap --include-default-paths","TOOLSTATE_PUBLISH":"1"}}]
10+
run_type=auto
11+
"#);
12+
}
13+
14+
#[test]
15+
fn try_jobs() {
16+
let stdout = get_matrix("push", "commit", "refs/heads/try");
17+
insta::assert_snapshot!(stdout, @r###"
18+
jobs=[{"name":"dist-x86_64-linux","full_name":"try - dist-x86_64-linux","os":"ubuntu-22.04-16core-64gb","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","CODEGEN_BACKENDS":"llvm,cranelift","DEPLOY_BUCKET":"rust-lang-ci2","DIST_TRY_BUILD":"1","TOOLSTATE_PUBLISH":"1"}}]
19+
run_type=try
20+
"###);
21+
}
22+
23+
#[test]
24+
fn try_custom_jobs() {
25+
let stdout = get_matrix(
26+
"push",
27+
r#"This is a test PR
28+
29+
try-job: aarch64-gnu
30+
try-job: dist-i686-msvc"#,
31+
"refs/heads/try",
32+
);
33+
insta::assert_snapshot!(stdout, @r###"
34+
jobs=[{"name":"aarch64-gnu","full_name":"try - aarch64-gnu","os":"ubuntu-22.04-arm","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","DEPLOY_BUCKET":"rust-lang-ci2","DIST_TRY_BUILD":"1","TOOLSTATE_PUBLISH":"1"},"free_disk":true},{"name":"dist-i686-msvc","full_name":"try - dist-i686-msvc","os":"windows-2022","env":{"ARTIFACTS_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZN24CBO55","AWS_REGION":"us-west-1","CACHES_AWS_ACCESS_KEY_ID":"AKIA46X5W6CZI5DHEBFL","CODEGEN_BACKENDS":"llvm,cranelift","DEPLOY_BUCKET":"rust-lang-ci2","DIST_REQUIRE_ALL_TOOLS":"1","DIST_TRY_BUILD":"1","RUST_CONFIGURE_ARGS":"--build=i686-pc-windows-msvc --host=i686-pc-windows-msvc --target=i686-pc-windows-msvc,i586-pc-windows-msvc --enable-full-tools --enable-profiler","SCRIPT":"python x.py dist bootstrap --include-default-paths","TOOLSTATE_PUBLISH":"1"}}]
35+
run_type=try
36+
"###);
37+
}
38+
39+
#[test]
40+
fn pr_jobs() {
41+
let stdout = get_matrix("pull_request", "commit", "refs/heads/pr/1234");
42+
insta::assert_snapshot!(stdout, @r###"
43+
jobs=[{"name":"mingw-check","full_name":"PR - mingw-check","os":"ubuntu-24.04","env":{"PR_CI_JOB":"1"},"free_disk":true},{"name":"mingw-check-tidy","full_name":"PR - mingw-check-tidy","os":"ubuntu-24.04","env":{"PR_CI_JOB":"1"},"continue_on_error":true,"free_disk":true}]
44+
run_type=pr
45+
"###);
46+
}
47+
48+
fn get_matrix(event_name: &str, commit_msg: &str, branch_ref: &str) -> String {
49+
let output = Command::new("cargo")
50+
.args(["run", "-q", "calculate-job-matrix", "--jobs-file", TEST_JOBS_YML_PATH])
51+
.env("GITHUB_EVENT_NAME", event_name)
52+
.env("COMMIT_MESSAGE", commit_msg)
53+
.env("GITHUB_REF", branch_ref)
54+
.stdout(Stdio::piped())
55+
.output()
56+
.expect("Failed to execute command");
57+
58+
let stdout = String::from_utf8(output.stdout).unwrap();
59+
let stderr = String::from_utf8(output.stderr).unwrap();
60+
if !output.status.success() {
61+
panic!("cargo run failed: {}\n{}", stdout, stderr);
62+
}
63+
stdout
64+
}

0 commit comments

Comments
 (0)