about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorJakub Beránek <berykubik@gmail.com>2025-06-12 19:42:03 +0200
committerJakub Beránek <berykubik@gmail.com>2025-06-12 19:42:03 +0200
commit6feb9b792c2780c11574ff2b2b6aa9062882cea3 (patch)
tree2703e76e6762ddf394d13e94f3a6d9c6caed0c6d /src/bootstrap
parent983fe4f2655d6e7c22dfd06be3d2e9a31d33c033 (diff)
downloadrust-6feb9b792c2780c11574ff2b2b6aa9062882cea3.tar.gz
rust-6feb9b792c2780c11574ff2b2b6aa9062882cea3.zip
Add lightweight snapshot testing for bootstrap tests
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/src/core/builder/tests.rs78
-rw-r--r--src/bootstrap/src/utils/cache.rs39
2 files changed, 107 insertions, 10 deletions
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index 6db96668cf5..d07df7f4a84 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -1231,3 +1231,81 @@ fn any_debug() {
     // Downcasting to the underlying type should succeed.
     assert_eq!(x.downcast_ref::<MyStruct>(), Some(&MyStruct { x: 7 }));
 }
+
+/// The staging tests use insta for snapshot testing.
+/// See bootstrap's README on how to bless the snapshots.
+mod staging {
+    use crate::core::builder::tests::{
+        TEST_TRIPLE_1, configure, configure_with_args, render_steps, run_build,
+    };
+
+    #[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>
+        ");
+    }
+}
+
+/// 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).
+fn render_steps(steps: &[(Box<dyn Any>, Box<dyn Any>)]) -> String {
+    steps
+        .iter()
+        .filter_map(|(step, output)| {
+            // FIXME: implement an optional method on Step to produce metadata for test, instead
+            // of this downcasting
+            if let Some((rustc, output)) = downcast_step::<compile::Rustc>(step, output) {
+                Some(format!(
+                    "[build] {} -> {}",
+                    render_compiler(rustc.build_compiler),
+                    // FIXME: return the correct stage from the `Rustc` step, now it behaves weirdly
+                    render_compiler(Compiler::new(rustc.build_compiler.stage + 1, rustc.target)),
+                ))
+            } else if let Some((std, output)) = downcast_step::<compile::Std>(step, output) {
+                Some(format!(
+                    "[build] {} -> std {} <{}>",
+                    render_compiler(std.compiler),
+                    std.compiler.stage,
+                    std.target
+                ))
+            } else if let Some((llvm, output)) = downcast_step::<llvm::Llvm>(step, output) {
+                Some(format!("[build] llvm <{}>", llvm.target))
+            } else {
+                None
+            }
+        })
+        .map(|line| {
+            line.replace(TEST_TRIPLE_1, "target1")
+                .replace(TEST_TRIPLE_2, "target2")
+                .replace(TEST_TRIPLE_3, "target3")
+        })
+        .collect::<Vec<_>>()
+        .join("\n")
+}
+
+fn downcast_step<'a, S: Step>(
+    step: &'a Box<dyn Any>,
+    output: &'a Box<dyn Any>,
+) -> Option<(&'a S, &'a S::Output)> {
+    let Some(step) = step.downcast_ref::<S>() else {
+        return None;
+    };
+    let Some(output) = output.downcast_ref::<S::Output>() else {
+        return None;
+    };
+    Some((step, output))
+}
+
+fn render_compiler(compiler: Compiler) -> String {
+    format!("rustc {} <{}>", compiler.stage, compiler.host)
+}
diff --git a/src/bootstrap/src/utils/cache.rs b/src/bootstrap/src/utils/cache.rs
index 46eeffad88c..0c737470958 100644
--- a/src/bootstrap/src/utils/cache.rs
+++ b/src/bootstrap/src/utils/cache.rs
@@ -17,6 +17,7 @@ use std::borrow::Borrow;
 use std::cell::RefCell;
 use std::cmp::Ordering;
 use std::collections::HashMap;
+use std::fmt::Debug;
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::ops::Deref;
@@ -208,25 +209,30 @@ pub static INTERNER: LazyLock<Interner> = LazyLock::new(Interner::default);
 /// any type in its output. It is a write-once cache; values are never evicted,
 /// which means that references to the value can safely be returned from the
 /// `get()` method.
-#[derive(Debug)]
-pub struct Cache(
-    RefCell<
+#[derive(Debug, Default)]
+pub struct Cache {
+    cache: RefCell<
         HashMap<
             TypeId,
             Box<dyn Any>, // actually a HashMap<Step, Interned<Step::Output>>
         >,
     >,
-);
+    #[cfg(test)]
+    /// Contains steps in the same order in which they were executed
+    /// Useful for tests
+    /// Tuples (step, step output)
+    executed_steps: RefCell<Vec<(Box<dyn Any>, Box<dyn Any>)>>,
+}
 
 impl Cache {
     /// Creates a new empty cache.
     pub fn new() -> Cache {
-        Cache(RefCell::new(HashMap::new()))
+        Cache::default()
     }
 
     /// Stores the result of a computation step in the cache.
     pub fn put<S: Step>(&self, step: S, value: S::Output) {
-        let mut cache = self.0.borrow_mut();
+        let mut cache = self.cache.borrow_mut();
         let type_id = TypeId::of::<S>();
         let stepcache = cache
             .entry(type_id)
@@ -234,12 +240,20 @@ impl Cache {
             .downcast_mut::<HashMap<S, S::Output>>()
             .expect("invalid type mapped");
         assert!(!stepcache.contains_key(&step), "processing {step:?} a second time");
+
+        #[cfg(test)]
+        {
+            let step: Box<dyn Any> = Box::new(step.clone());
+            let output: Box<dyn Any> = Box::new(value.clone());
+            self.executed_steps.borrow_mut().push((step, output));
+        }
+
         stepcache.insert(step, value);
     }
 
     /// Retrieves a cached result for the given step, if available.
     pub fn get<S: Step>(&self, step: &S) -> Option<S::Output> {
-        let mut cache = self.0.borrow_mut();
+        let mut cache = self.cache.borrow_mut();
         let type_id = TypeId::of::<S>();
         let stepcache = cache
             .entry(type_id)
@@ -252,8 +266,8 @@ impl Cache {
 
 #[cfg(test)]
 impl Cache {
-    pub fn all<S: Ord + Clone + Step>(&mut self) -> Vec<(S, S::Output)> {
-        let cache = self.0.get_mut();
+    pub fn all<S: Ord + Step>(&mut self) -> Vec<(S, S::Output)> {
+        let cache = self.cache.get_mut();
         let type_id = TypeId::of::<S>();
         let mut v = cache
             .remove(&type_id)
@@ -265,7 +279,12 @@ impl Cache {
     }
 
     pub fn contains<S: Step>(&self) -> bool {
-        self.0.borrow().contains_key(&TypeId::of::<S>())
+        self.cache.borrow().contains_key(&TypeId::of::<S>())
+    }
+
+    #[cfg(test)]
+    pub fn into_executed_steps(mut self) -> Vec<(Box<dyn Any>, Box<dyn Any>)> {
+        mem::take(&mut self.executed_steps.borrow_mut())
     }
 }