Skip to content

Commit f9ff702

Browse files
Rustify ci/integration.sh script (#6817)
* Rustify `ci/integration.sh` script * Replace `map!` macro with plain `HashMap` calls * Only create `rustfmt.toml` if it does not already exist * Stick closer to original shell script behaviour * Improve `ci/integration.rs` code
1 parent 5e73127 commit f9ff702

4 files changed

Lines changed: 254 additions & 122 deletions

File tree

.github/workflows/integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,5 @@ jobs:
7676
env:
7777
INTEGRATION: ${{ matrix.integration }}
7878
TARGET: x86_64-unknown-linux-gnu
79-
run: ./ci/integration.sh
79+
run: cargo run --bin ci-integration
8080
continue-on-error: ${{ matrix.allow-failure == true }}

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ path = "src/format-diff/main.rs"
2626
name = "git-rustfmt"
2727
path = "src/git-rustfmt/main.rs"
2828

29+
[[bin]]
30+
name = "ci-integration"
31+
path = "ci/integration.rs"
32+
2933
[features]
3034
default = ["cargo-fmt", "rustfmt-format-diff"]
3135
cargo-fmt = []
@@ -70,3 +74,4 @@ tempfile = "3.23.0"
7074
[package.metadata.rust-analyzer]
7175
# This package uses #[feature(rustc_private)]
7276
rustc_private = true
77+

ci/integration.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use std::collections::HashMap;
2+
use std::env::var;
3+
use std::ffi::OsStr;
4+
use std::path::Path;
5+
use std::process::Command;
6+
7+
fn write_file(file_path: impl AsRef<Path>, content: &str) -> Result<(), String> {
8+
std::fs::write(&file_path, content).map_err(|error| {
9+
format!(
10+
"Failed to create empty `{}` file: {error:?}",
11+
file_path.as_ref().display(),
12+
)
13+
})
14+
}
15+
16+
fn run_command_with_env<I, S>(
17+
bin: &str,
18+
args: I,
19+
current_dir: &str,
20+
env: &HashMap<&str, &str>,
21+
) -> Result<(), String>
22+
where
23+
I: IntoIterator<Item = S>,
24+
S: AsRef<OsStr>,
25+
{
26+
let exit_status = Command::new(bin)
27+
.args(args)
28+
.envs(env)
29+
.current_dir(current_dir)
30+
.spawn()
31+
.map_err(|error| format!("Failed to spawn command `{bin}`: {error:?}"))?
32+
.wait()
33+
.map_err(|error| format!("Failed to wait command `{bin}`: {error:?}"))?;
34+
if exit_status.success() {
35+
Ok(())
36+
} else {
37+
Err(format!("Command `{bin}` failed"))
38+
}
39+
}
40+
41+
fn run_command<I, S>(bin: &str, args: I, current_dir: &str) -> Result<(), String>
42+
where
43+
I: IntoIterator<Item = S>,
44+
S: AsRef<OsStr>,
45+
{
46+
run_command_with_env(bin, args, current_dir, &HashMap::new())
47+
}
48+
49+
struct CommandOutput {
50+
output: String,
51+
exited_successfully: bool,
52+
}
53+
54+
fn run_command_with_output_and_env<I, S>(
55+
bin: &str,
56+
args: I,
57+
current_dir: &str,
58+
env: &HashMap<&str, &str>,
59+
) -> Result<CommandOutput, String>
60+
where
61+
I: IntoIterator<Item = S>,
62+
S: AsRef<OsStr>,
63+
{
64+
let cmd_output = Command::new(bin)
65+
.args(args)
66+
.envs(env)
67+
.current_dir(current_dir)
68+
.output()
69+
.map_err(|error| format!("Failed to spawn command `{bin}`: {error:?}"))?;
70+
let mut output = String::from_utf8_lossy(&cmd_output.stdout).into_owned();
71+
output.push_str(&String::from_utf8_lossy(&cmd_output.stderr));
72+
Ok(CommandOutput {
73+
output,
74+
exited_successfully: cmd_output.status.success(),
75+
})
76+
}
77+
78+
fn run_command_with_output<I, S>(
79+
bin: &str,
80+
args: I,
81+
current_dir: &str,
82+
) -> Result<CommandOutput, String>
83+
where
84+
I: IntoIterator<Item = S>,
85+
S: AsRef<OsStr>,
86+
{
87+
run_command_with_output_and_env(bin, args, current_dir, &HashMap::new())
88+
}
89+
90+
// Checks that:
91+
//
92+
// * `cargo fmt --all` succeeds without any warnings or errors
93+
// * `cargo fmt --all -- --check` after formatting returns success
94+
// * `cargo test --all` still passes (formatting did not break the build)
95+
fn check_fmt_with_all_tests(env: HashMap<&str, &str>, current_dir: &str) -> Result<(), String> {
96+
check_fmt_base("--all", env, current_dir)
97+
}
98+
99+
// Checks that:
100+
//
101+
// * `cargo fmt --all` succeeds without any warnings or errors
102+
// * `cargo fmt --all -- --check` after formatting returns success
103+
// * `cargo test --lib` still passes (formatting did not break the build)
104+
fn check_fmt_with_lib_tests(env: HashMap<&str, &str>, current_dir: &str) -> Result<(), String> {
105+
check_fmt_base("--lib", env, current_dir)
106+
}
107+
108+
fn check_fmt_base(
109+
test_args: &str,
110+
env: HashMap<&str, &str>,
111+
current_dir: &str,
112+
) -> Result<(), String> {
113+
fn check_output_does_not_contain(output: &str, needle: &str) -> Result<(), String> {
114+
if output.contains(needle) {
115+
Err(format!("`cargo fmt --all -v` contains `{needle}`"))
116+
} else {
117+
Ok(())
118+
}
119+
}
120+
121+
let output =
122+
run_command_with_output_and_env("cargo", &["test", test_args], current_dir, &env)?.output;
123+
if ["build failed", "test result: FAILED."]
124+
.iter()
125+
.any(|needle| output.contains(needle))
126+
{
127+
println!("`cargo test {test_args}` failed: {output}");
128+
return Ok(());
129+
}
130+
131+
let rustfmt_toml = Path::new(current_dir).join("rustfmt.toml");
132+
if !rustfmt_toml.is_file() {
133+
write_file(rustfmt_toml, "")?;
134+
}
135+
136+
let output =
137+
run_command_with_output_and_env("cargo", &["fmt", "--all", "-v"], current_dir, &env)?;
138+
println!("{}", output.output);
139+
140+
if !output.exited_successfully {
141+
return Err("`cargo fmt --all -v` failed".to_string());
142+
}
143+
144+
let output = &output.output;
145+
check_output_does_not_contain(output, "internal error")?;
146+
check_output_does_not_contain(output, "internal compiler error")?;
147+
check_output_does_not_contain(output, "warning")?;
148+
check_output_does_not_contain(output, "Warning")?;
149+
150+
let output = run_command_with_output_and_env(
151+
"cargo",
152+
&["fmt", "--all", "--", "--check"],
153+
current_dir,
154+
&env,
155+
)?;
156+
157+
if !output.exited_successfully {
158+
return Err("`cargo fmt --all -- -v --check` failed".to_string());
159+
}
160+
let output = &output.output;
161+
if let Err(error) = write_file(Path::new(current_dir).join("rustfmt_check_output"), output) {
162+
println!("{output}");
163+
return Err(error);
164+
}
165+
166+
// This command allows to ensure that no source file was modified while running the tests.
167+
run_command_with_env("cargo", &["test", test_args], current_dir, &env)
168+
}
169+
170+
fn show_head(integration: &str) -> Result<(), String> {
171+
let head = run_command_with_output("git", &["rev-parse", "HEAD"], integration)?.output;
172+
println!("Head commit of {integration}: {head}");
173+
Ok(())
174+
}
175+
176+
fn run_test<F: FnOnce(HashMap<&str, &str>, &str) -> Result<(), String>>(
177+
integration: &str,
178+
git_repository: String,
179+
env: HashMap<&str, &str>,
180+
test_fn: F,
181+
) -> Result<(), String> {
182+
run_command_with_output("git", &["clone", "--depth=1", git_repository.as_str()], ".")?;
183+
show_head(integration)?;
184+
test_fn(env, integration)
185+
}
186+
187+
fn runner() -> Result<(), String> {
188+
let integration = match var("INTEGRATION") {
189+
Ok(value) if !value.is_empty() => value,
190+
_ => {
191+
return Err("The INTEGRATION environment variable must be set.".into());
192+
}
193+
};
194+
195+
run_command_with_env(
196+
"cargo",
197+
&["install", "--path", ".", "--force", "--locked"],
198+
".",
199+
&HashMap::from([
200+
("CFG_RELEASE", "nightly"),
201+
("CFG_RELEASE_CHANNEL", "nightly"),
202+
]),
203+
)?;
204+
205+
println!("Integration tests for {integration}");
206+
207+
run_command("cargo", &["fmt", "--", "--version"], ".")?;
208+
209+
match integration.as_str() {
210+
"cargo" => run_test(
211+
&integration,
212+
format!("https://github.com/rust-lang/{integration}.git"),
213+
HashMap::from([("CFG_DISABLE_CROSS_TESTS", "1")]),
214+
check_fmt_with_all_tests,
215+
),
216+
"crater" => run_test(
217+
&integration,
218+
format!("https://github.com/rust-lang/{integration}.git"),
219+
HashMap::new(),
220+
check_fmt_with_lib_tests,
221+
),
222+
"bitflags" => run_test(
223+
&integration,
224+
format!("https://github.com/bitflags/{integration}.git"),
225+
HashMap::new(),
226+
check_fmt_with_all_tests,
227+
),
228+
"tempdir" => run_test(
229+
&integration,
230+
format!("https://github.com/rust-lang-deprecated/{integration}.git"),
231+
HashMap::new(),
232+
check_fmt_with_all_tests,
233+
),
234+
_ => run_test(
235+
&integration,
236+
format!("https://github.com/rust-lang/{integration}.git"),
237+
HashMap::new(),
238+
check_fmt_with_all_tests,
239+
),
240+
}
241+
}
242+
243+
fn main() {
244+
if let Err(error) = runner() {
245+
eprintln!("{error}");
246+
std::process::exit(1);
247+
}
248+
}

ci/integration.sh

Lines changed: 0 additions & 121 deletions
This file was deleted.

0 commit comments

Comments
 (0)