From 6feb9b792c2780c11574ff2b2b6aa9062882cea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 12 Jun 2025 19:42:03 +0200 Subject: [PATCH] Add lightweight snapshot testing for bootstrap tests --- src/bootstrap/src/core/builder/tests.rs | 78 +++++++++++++++++++++++++ src/bootstrap/src/utils/cache.rs | 39 +++++++++---- 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 6db96668cf5d..d07df7f4a841 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::(), 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 -> std 0 + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 0 -> rustc 1 + "); + } +} + +/// 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, Box)]) -> 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::(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::(step, output) { + Some(format!( + "[build] {} -> std {} <{}>", + render_compiler(std.compiler), + std.compiler.stage, + std.target + )) + } else if let Some((llvm, output)) = downcast_step::(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::>() + .join("\n") +} + +fn downcast_step<'a, S: Step>( + step: &'a Box, + output: &'a Box, +) -> Option<(&'a S, &'a S::Output)> { + let Some(step) = step.downcast_ref::() else { + return None; + }; + let Some(output) = output.downcast_ref::() 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 46eeffad88c3..0c7374709584 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 = 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, // actually a HashMap> >, >, -); + #[cfg(test)] + /// Contains steps in the same order in which they were executed + /// Useful for tests + /// Tuples (step, step output) + executed_steps: RefCell, Box)>>, +} 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(&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::(); let stepcache = cache .entry(type_id) @@ -234,12 +240,20 @@ impl Cache { .downcast_mut::>() .expect("invalid type mapped"); assert!(!stepcache.contains_key(&step), "processing {step:?} a second time"); + + #[cfg(test)] + { + let step: Box = Box::new(step.clone()); + let output: Box = 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(&self, step: &S) -> Option { - let mut cache = self.0.borrow_mut(); + let mut cache = self.cache.borrow_mut(); let type_id = TypeId::of::(); let stepcache = cache .entry(type_id) @@ -252,8 +266,8 @@ impl Cache { #[cfg(test)] impl Cache { - pub fn all(&mut self) -> Vec<(S, S::Output)> { - let cache = self.0.get_mut(); + pub fn all(&mut self) -> Vec<(S, S::Output)> { + let cache = self.cache.get_mut(); let type_id = TypeId::of::(); let mut v = cache .remove(&type_id) @@ -265,7 +279,12 @@ impl Cache { } pub fn contains(&self) -> bool { - self.0.borrow().contains_key(&TypeId::of::()) + self.cache.borrow().contains_key(&TypeId::of::()) + } + + #[cfg(test)] + pub fn into_executed_steps(mut self) -> Vec<(Box, Box)> { + mem::take(&mut self.executed_steps.borrow_mut()) } }