summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorJakub Beránek <berykubik@gmail.com>2025-06-20 20:03:20 +0200
committerGitHub <noreply@github.com>2025-06-20 20:03:20 +0200
commit7f9c3a3dc678bc01e9c33a6d96cc1ff0979cea72 (patch)
tree44f572d10619befb4c24552281b7c294199571f5 /src/bootstrap
parent9c4ff566babe632af5e30281a822d1ae9972873b (diff)
parentd475e10dcb1bbd3193edb9968dc9e8f60b7247df (diff)
downloadrust-7f9c3a3dc678bc01e9c33a6d96cc1ff0979cea72.tar.gz
rust-7f9c3a3dc678bc01e9c33a6d96cc1ff0979cea72.zip
Rollup merge of #142629 - Kobzol:bootstrap-tests-builder, r=jieyouxu
Add config builder for bootstrap tests

I started writing a bunch of snapshot tests for build/check steps, and quickly realized that the current interface for defining them won't be enough, so I created a simple builder, which can scale to pretty much any kind of configuration in the future.
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/src/core/builder/tests.rs53
-rw-r--r--src/bootstrap/src/core/config/config.rs4
-rw-r--r--src/bootstrap/src/utils/helpers.rs5
-rw-r--r--src/bootstrap/src/utils/tests/mod.rs71
4 files changed, 114 insertions, 19 deletions
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index 6268a2b59d6..f1af2b285a2 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -9,6 +9,7 @@ use crate::Flags;
 use crate::core::build_steps::doc::DocumentationFormat;
 use crate::core::config::Config;
 use crate::utils::cache::ExecutedStep;
+use crate::utils::helpers::get_host_target;
 use crate::utils::tests::git::{GitCtx, git_test};
 
 static TEST_TRIPLE_1: &str = "i686-unknown-haiku";
@@ -1236,29 +1237,48 @@ fn any_debug() {
 /// The staging tests use insta for snapshot testing.
 /// See bootstrap's README on how to bless the snapshots.
 mod staging {
+    use crate::Build;
+    use crate::core::builder::Builder;
     use crate::core::builder::tests::{
         TEST_TRIPLE_1, configure, configure_with_args, render_steps, run_build,
     };
+    use crate::utils::tests::{ConfigBuilder, TestCtx};
 
     #[test]
     fn build_compiler_stage_1() {
-        let mut cache = run_build(
-            &["compiler".into()],
-            configure_with_args(&["build", "--stage", "1"], &[TEST_TRIPLE_1], &[TEST_TRIPLE_1]),
-        );
-        let steps = cache.into_executed_steps();
-        insta::assert_snapshot!(render_steps(&steps), @r"
-        [build] rustc 0 <target1> -> std 0 <target1>
-        [build] llvm <target1>
-        [build] rustc 0 <target1> -> rustc 1 <target1>
-        [build] rustc 0 <target1> -> rustc 1 <target1>
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("build")
+                .path("compiler")
+                .stage(1)
+                .get_steps(), @r"
+        [build] rustc 0 <host> -> std 0 <host>
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
         ");
     }
+
+    impl ConfigBuilder {
+        fn get_steps(self) -> String {
+            let config = self.create_config();
+
+            let kind = config.cmd.kind();
+            let build = Build::new(config);
+            let builder = Builder::new(&build);
+            builder.run_step_descriptions(&Builder::get_step_descriptions(kind), &builder.paths);
+            render_steps(&builder.cache.into_executed_steps())
+        }
+    }
 }
 
 /// Renders the executed bootstrap steps for usage in snapshot tests with insta.
 /// Only renders certain important steps.
 /// Each value in `steps` should be a tuple of (Step, step output).
+///
+/// The arrow in the rendered output (`X -> Y`) means `X builds Y`.
+/// This is similar to the output printed by bootstrap to stdout, but here it is
+/// generated purely for the purpose of tests.
 fn render_steps(steps: &[ExecutedStep]) -> String {
     steps
         .iter()
@@ -1275,18 +1295,17 @@ fn render_steps(steps: &[ExecutedStep]) -> String {
             }
             let stage =
                 if let Some(stage) = metadata.stage { format!("{stage} ") } else { "".to_string() };
-            write!(record, "{} {stage}<{}>", metadata.name, metadata.target);
+            write!(record, "{} {stage}<{}>", metadata.name, normalize_target(metadata.target));
             Some(record)
         })
-        .map(|line| {
-            line.replace(TEST_TRIPLE_1, "target1")
-                .replace(TEST_TRIPLE_2, "target2")
-                .replace(TEST_TRIPLE_3, "target3")
-        })
         .collect::<Vec<_>>()
         .join("\n")
 }
 
+fn normalize_target(target: TargetSelection) -> String {
+    target.to_string().replace(&get_host_target().to_string(), "host")
+}
+
 fn render_compiler(compiler: Compiler) -> String {
-    format!("rustc {} <{}>", compiler.stage, compiler.host)
+    format!("rustc {} <{}>", compiler.stage, normalize_target(compiler.host))
 }
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index ff0fda2d2e6..d3393afcae0 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -49,7 +49,7 @@ use crate::core::download::is_download_ci_available;
 use crate::utils::channel;
 use crate::utils::exec::command;
 use crate::utils::execution_context::ExecutionContext;
-use crate::utils::helpers::exe;
+use crate::utils::helpers::{exe, get_host_target};
 use crate::{GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, t};
 
 /// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic.
@@ -349,7 +349,7 @@ impl Config {
             stderr_is_tty: std::io::stderr().is_terminal(),
 
             // set by build.rs
-            host_target: TargetSelection::from_user(env!("BUILD_TRIPLE")),
+            host_target: get_host_target(),
 
             src: {
                 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs
index 2f18fb60318..3c5f612daa7 100644
--- a/src/bootstrap/src/utils/helpers.rs
+++ b/src/bootstrap/src/utils/helpers.rs
@@ -178,6 +178,11 @@ pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result<
     }
 }
 
+/// Return the host target on which we are currently running.
+pub fn get_host_target() -> TargetSelection {
+    TargetSelection::from_user(env!("BUILD_TRIPLE"))
+}
+
 /// Rename a file if from and to are in the same filesystem or
 /// copy and remove the file otherwise
 pub fn move_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
diff --git a/src/bootstrap/src/utils/tests/mod.rs b/src/bootstrap/src/utils/tests/mod.rs
index 73c500f6e36..91877fd0da4 100644
--- a/src/bootstrap/src/utils/tests/mod.rs
+++ b/src/bootstrap/src/utils/tests/mod.rs
@@ -1,3 +1,74 @@
 //! This module contains shared utilities for bootstrap tests.
 
+use std::path::{Path, PathBuf};
+use std::thread;
+
+use tempfile::TempDir;
+
+use crate::core::builder::Builder;
+use crate::core::config::DryRun;
+use crate::{Build, Config, Flags, t};
+
 pub mod git;
+
+/// Holds temporary state of a bootstrap test.
+/// Right now it is only used to redirect the build directory of the bootstrap
+/// invocation, in the future it would be great if we could actually execute
+/// the whole test with this directory set as the workdir.
+pub struct TestCtx {
+    directory: TempDir,
+}
+
+impl TestCtx {
+    pub fn new() -> Self {
+        let directory = TempDir::new().expect("cannot create temporary directory");
+        eprintln!("Running test in {}", directory.path().display());
+        Self { directory }
+    }
+
+    /// Starts a new invocation of bootstrap that executes `kind` as its top level command
+    /// (i.e. `x <kind>`). Returns a builder that configures the created config through CLI flags.
+    pub fn config(&self, kind: &str) -> ConfigBuilder {
+        ConfigBuilder::from_args(&[kind], self.directory.path().to_owned())
+    }
+}
+
+/// Used to configure an invocation of bootstrap.
+/// Currently runs in the rustc checkout, long-term it should be switched
+/// to run in a (cache-primed) temporary directory instead.
+pub struct ConfigBuilder {
+    args: Vec<String>,
+    directory: PathBuf,
+}
+
+impl ConfigBuilder {
+    fn from_args(args: &[&str], directory: PathBuf) -> Self {
+        Self { args: args.iter().copied().map(String::from).collect(), directory }
+    }
+
+    pub fn path(mut self, path: &str) -> Self {
+        self.args.push(path.to_string());
+        self
+    }
+
+    pub fn stage(mut self, stage: u32) -> Self {
+        self.args.push("--stage".to_string());
+        self.args.push(stage.to_string());
+        self
+    }
+
+    pub fn create_config(mut self) -> Config {
+        // Run in dry-check, otherwise the test would be too slow
+        self.args.push("--dry-run".to_string());
+
+        // Ignore submodules
+        self.args.push("--set".to_string());
+        self.args.push("build.submodules=false".to_string());
+
+        // Do not mess with the local rustc checkout build directory
+        self.args.push("--build-dir".to_string());
+        self.args.push(self.directory.join("build").display().to_string());
+
+        Config::parse(Flags::parse(&self.args))
+    }
+}