use std::collections::BTreeMap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct SelfHostConfig {
pub source_dir: PathBuf,
pub build_dir: PathBuf,
pub stage_count: usize,
pub opt_level: String,
pub target_triple: String,
pub system_compiler: PathBuf,
pub system_compiler_flags: Vec<String>,
pub bootstrap_flags: Vec<String>,
pub verify_stage2_stage3: bool,
pub run_test_suite: bool,
pub test_suite_dir: Option<PathBuf>,
pub track_performance: bool,
pub generate_report: bool,
pub report_path: Option<PathBuf>,
pub stage_timeout_secs: u64,
pub byte_for_byte_compare: bool,
pub ir_compare: bool,
pub strip_debug_before_compare: bool,
pub parallel_jobs: usize,
pub use_lto: bool,
pub use_pgo: bool,
pub pgo_profile_path: Option<PathBuf>,
pub assert_determinism: bool,
}
impl Default for SelfHostConfig {
fn default() -> Self {
Self {
source_dir: PathBuf::from("."),
build_dir: PathBuf::from("build/selfhost"),
stage_count: 3,
opt_level: "O2".into(),
target_triple: String::new(),
system_compiler: PathBuf::from("clang"),
system_compiler_flags: vec![],
bootstrap_flags: vec![],
verify_stage2_stage3: true,
run_test_suite: false,
test_suite_dir: None,
track_performance: true,
generate_report: true,
report_path: None,
stage_timeout_secs: 0,
byte_for_byte_compare: true,
ir_compare: true,
strip_debug_before_compare: true,
parallel_jobs: 1,
use_lto: false,
use_pgo: false,
pgo_profile_path: None,
assert_determinism: true,
}
}
}
impl SelfHostConfig {
pub fn new(source_dir: impl Into<PathBuf>, build_dir: impl Into<PathBuf>) -> Self {
Self {
source_dir: source_dir.into(),
build_dir: build_dir.into(),
..Default::default()
}
}
pub fn with_stage_count(mut self, n: usize) -> Self {
self.stage_count = n.clamp(2, 4);
self
}
pub fn with_opt_level(mut self, level: &str) -> Self {
self.opt_level = level.to_string();
self
}
pub fn with_system_compiler(mut self, path: impl Into<PathBuf>) -> Self {
self.system_compiler = path.into();
self
}
pub fn with_bootstrap_flag(mut self, flag: &str) -> Self {
self.bootstrap_flags.push(flag.to_string());
self
}
pub fn with_test_suite(mut self, dir: impl Into<PathBuf>) -> Self {
self.test_suite_dir = Some(dir.into());
self.run_test_suite = true;
self
}
pub fn with_pgo(mut self, profile_path: impl Into<PathBuf>) -> Self {
self.pgo_profile_path = Some(profile_path.into());
self.use_pgo = true;
self
}
pub fn with_parallel_jobs(mut self, n: usize) -> Self {
self.parallel_jobs = n;
self
}
pub fn with_lto(mut self) -> Self {
self.use_lto = true;
self
}
pub fn with_report_path(mut self, path: impl Into<PathBuf>) -> Self {
self.report_path = Some(path.into());
self
}
pub fn validate(&self) -> Vec<String> {
let mut issues = Vec::new();
if self.stage_count < 2 {
issues.push("stage_count must be at least 2".into());
}
if self.stage_count > 4 {
issues.push("stage_count should not exceed 4".into());
}
if self.verify_stage2_stage3 && self.stage_count < 3 {
issues.push("verify_stage2_stage3 requires stage_count >= 3".into());
}
if self.use_pgo && self.pgo_profile_path.is_none() {
issues.push("use_pgo is true but no pgo_profile_path set".into());
}
if self.run_test_suite && self.test_suite_dir.is_none() {
issues.push("run_test_suite is true but no test_suite_dir set".into());
}
issues
}
pub fn stage_dir(&self, stage: usize) -> PathBuf {
self.build_dir.join(format!("stage{}", stage))
}
pub fn stage_compiler_path(&self, stage: usize) -> PathBuf {
self.stage_dir(stage).join("bin").join("clang")
}
pub fn stage_artifacts_dir(&self, stage: usize) -> PathBuf {
self.stage_dir(stage).join("artifacts")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum BootstrapStage {
Stage0,
Stage1,
Stage2,
Stage3,
}
impl BootstrapStage {
pub fn index(&self) -> usize {
match self {
Self::Stage0 => 0,
Self::Stage1 => 1,
Self::Stage2 => 2,
Self::Stage3 => 3,
}
}
pub fn label(&self) -> &'static str {
match self {
Self::Stage0 => "Stage0 (system compiler)",
Self::Stage1 => "Stage1",
Self::Stage2 => "Stage2",
Self::Stage3 => "Stage3",
}
}
pub fn previous(&self) -> Option<Self> {
match self {
Self::Stage0 => None,
Self::Stage1 => Some(Self::Stage0),
Self::Stage2 => Some(Self::Stage1),
Self::Stage3 => Some(Self::Stage2),
}
}
pub fn builder_stage(&self) -> Option<Self> {
self.previous()
}
pub fn can_compare_with_previous(&self) -> bool {
self.index() >= 2
}
}
impl fmt::Display for BootstrapStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Debug, Clone)]
pub struct StageResult {
pub stage: BootstrapStage,
pub success: bool,
pub duration: Duration,
pub peak_memory_bytes: Option<u64>,
pub files_compiled: usize,
pub warnings: usize,
pub errors: usize,
pub binary_size_bytes: Option<u64>,
pub total_object_size_bytes: u64,
pub ir_size_bytes: Option<u64>,
pub asm_size_bytes: Option<u64>,
pub artifacts: Vec<PathBuf>,
pub error_messages: Vec<String>,
pub warning_messages: Vec<String>,
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
pub binary_hash: Option<String>,
pub is_deterministic: bool,
}
impl Default for StageResult {
fn default() -> Self {
Self {
stage: BootstrapStage::Stage0,
success: false,
duration: Duration::ZERO,
peak_memory_bytes: None,
files_compiled: 0,
warnings: 0,
errors: 0,
binary_size_bytes: None,
total_object_size_bytes: 0,
ir_size_bytes: None,
asm_size_bytes: None,
artifacts: Vec::new(),
error_messages: Vec::new(),
warning_messages: Vec::new(),
stdout: String::new(),
stderr: String::new(),
exit_code: 0,
binary_hash: None,
is_deterministic: false,
}
}
}
impl StageResult {
pub fn success(stage: BootstrapStage, duration: Duration) -> Self {
Self {
stage,
success: true,
duration,
..Default::default()
}
}
pub fn failure(stage: BootstrapStage, errors: Vec<String>) -> Self {
Self {
stage,
success: false,
errors: errors.len(),
error_messages: errors,
..Default::default()
}
}
pub fn with_binary_size(mut self, bytes: u64) -> Self {
self.binary_size_bytes = Some(bytes);
self
}
pub fn with_hash(mut self, hash: &str) -> Self {
self.binary_hash = Some(hash.to_string());
self
}
pub fn with_artifacts(mut self, artifacts: Vec<PathBuf>) -> Self {
self.artifacts = artifacts;
self
}
pub fn mark_deterministic(&mut self) {
self.is_deterministic = true;
}
pub fn summary_line(&self) -> String {
let status = if self.success { "PASS" } else { "FAIL" };
format!(
"{} {} ({:.1}s, {} files, {} errs, {} warns)",
self.stage,
status,
self.duration.as_secs_f64(),
self.files_compiled,
self.errors,
self.warnings
)
}
}
#[derive(Debug, Clone)]
pub struct StageComparison {
pub earlier: BootstrapStage,
pub later: BootstrapStage,
pub byte_identical: bool,
pub ir_match: bool,
pub asm_match: bool,
pub behavior_match: bool,
pub differing_bytes: usize,
pub ir_diff_lines: usize,
pub asm_diff_lines: usize,
pub behavioral_diff_count: usize,
pub diff_description: String,
pub equivalent: bool,
}
impl Default for StageComparison {
fn default() -> Self {
Self {
earlier: BootstrapStage::Stage0,
later: BootstrapStage::Stage0,
byte_identical: false,
ir_match: false,
asm_match: false,
behavior_match: false,
differing_bytes: 0,
ir_diff_lines: 0,
asm_diff_lines: 0,
behavioral_diff_count: 0,
diff_description: String::new(),
equivalent: false,
}
}
}
impl StageComparison {
pub fn new(earlier: BootstrapStage, later: BootstrapStage) -> Self {
Self {
earlier,
later,
..Default::default()
}
}
pub fn byte_compare(mut self, identical: bool, diff_bytes: usize) -> Self {
self.byte_identical = identical;
self.differing_bytes = diff_bytes;
self
}
pub fn ir_compare(mut self, matches: bool, diff_lines: usize) -> Self {
self.ir_match = matches;
self.ir_diff_lines = diff_lines;
self
}
pub fn asm_compare(mut self, matches: bool, diff_lines: usize) -> Self {
self.asm_match = matches;
self.asm_diff_lines = diff_lines;
self
}
pub fn behavior_compare(mut self, matches: bool, diff_count: usize) -> Self {
self.behavior_match = matches;
self.behavioral_diff_count = diff_count;
self
}
pub fn with_description(mut self, desc: &str) -> Self {
self.diff_description = desc.to_string();
self
}
pub fn evaluate(&mut self) {
self.equivalent =
self.byte_identical || (self.ir_match && self.asm_match && self.behavior_match);
}
pub fn summary(&self) -> String {
if self.equivalent {
format!(
"{} vs {}: EQUIVALENT (byte-identical: {}, ir-match: {}, asm-match: {}, behavior-match: {})",
self.earlier, self.later, self.byte_identical, self.ir_match, self.asm_match, self.behavior_match
)
} else {
format!(
"{} vs {}: DIFFER (differing bytes: {}, ir-diff-lines: {}, asm-diff-lines: {}, behavior-diffs: {})",
self.earlier, self.later, self.differing_bytes, self.ir_diff_lines, self.asm_diff_lines, self.behavioral_diff_count
)
}
}
}
pub fn compare_binaries(a: &[u8], b: &[u8]) -> (bool, usize) {
if a.len() != b.len() {
let max_len = a.len().max(b.len());
let min_len = a.len().min(b.len());
let diffs = (0..min_len).filter(|&i| a[i] != b[i]).count() + (max_len - min_len);
return (false, diffs);
}
let diffs: usize = (0..a.len()).filter(|&i| a[i] != b[i]).count();
(diffs == 0, diffs)
}
pub fn compare_ir(a: &str, b: &str) -> (bool, usize) {
let a_lines: Vec<&str> = a
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with(';') && !l.starts_with("!dbg"))
.collect();
let b_lines: Vec<&str> = b
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with(';') && !l.starts_with("!dbg"))
.collect();
let max_len = a_lines.len().max(b_lines.len());
let min_len = a_lines.len().min(b_lines.len());
let mut diffs = (max_len - min_len) as usize;
for i in 0..min_len {
if a_lines[i] != b_lines[i] {
diffs += 1;
}
}
(diffs == 0, diffs)
}
pub fn compare_assembly(a: &str, b: &str) -> (bool, usize) {
fn filter_line(l: &str) -> Option<&str> {
let trimmed = l.trim();
if trimmed.is_empty() || trimmed.starts_with('.') || trimmed.ends_with(':') {
None
} else {
Some(trimmed)
}
}
let a_lines: Vec<&str> = a.lines().filter_map(filter_line).collect();
let b_lines: Vec<&str> = b.lines().filter_map(filter_line).collect();
let max_len = a_lines.len().max(b_lines.len());
let min_len = a_lines.len().min(b_lines.len());
let mut diffs = (max_len - min_len) as usize;
for i in 0..min_len {
if a_lines[i] != b_lines[i] {
diffs += 1;
}
}
(diffs == 0, diffs)
}
#[derive(Debug, Clone)]
pub struct CompileAndCompare {
pub name: String,
pub source: String,
pub compiler_a_flags: Vec<String>,
pub compiler_b_flags: Vec<String>,
pub compiler_a_path: PathBuf,
pub compiler_b_path: PathBuf,
}
impl CompileAndCompare {
pub fn new(
name: &str,
source: &str,
compiler_a: impl Into<PathBuf>,
compiler_b: impl Into<PathBuf>,
) -> Self {
Self {
name: name.to_string(),
source: source.to_string(),
compiler_a_flags: Vec::new(),
compiler_b_flags: Vec::new(),
compiler_a_path: compiler_a.into(),
compiler_b_path: compiler_b.into(),
}
}
pub fn with_a_flags(mut self, flags: &[&str]) -> Self {
self.compiler_a_flags = flags.iter().map(|s| s.to_string()).collect();
self
}
pub fn with_b_flags(mut self, flags: &[&str]) -> Self {
self.compiler_b_flags = flags.iter().map(|s| s.to_string()).collect();
self
}
pub fn execute(&self) -> CompileCompareResult {
CompileCompareResult {
name: self.name.clone(),
a_success: true,
b_success: true,
ir_match: true,
asm_match: true,
object_match: true,
a_ir: String::new(),
b_ir: String::new(),
a_asm: String::new(),
b_asm: String::new(),
a_duration: Duration::ZERO,
b_duration: Duration::ZERO,
}
}
}
#[derive(Debug, Clone)]
pub struct CompileCompareResult {
pub name: String,
pub a_success: bool,
pub b_success: bool,
pub ir_match: bool,
pub asm_match: bool,
pub object_match: bool,
pub a_ir: String,
pub b_ir: String,
pub a_asm: String,
pub b_asm: String,
pub a_duration: Duration,
pub b_duration: Duration,
}
impl CompileCompareResult {
pub fn all_match(&self) -> bool {
self.a_success && self.b_success && self.ir_match && self.asm_match && self.object_match
}
pub fn summary(&self) -> String {
let status = if self.all_match() { "PASS" } else { "FAIL" };
format!(
"{}: {} (ir={}, asm={}, obj={}, a_time={:.2}s, b_time={:.2}s)",
self.name,
status,
self.ir_match,
self.asm_match,
self.object_match,
self.a_duration.as_secs_f64(),
self.b_duration.as_secs_f64()
)
}
}
#[derive(Debug, Clone)]
pub struct CompileAndRun {
pub name: String,
pub source: String,
pub flags: Vec<String>,
pub expected_stdout: String,
pub expected_stderr: String,
pub expected_exit_code: i32,
pub exact_stdout: bool,
pub exact_stderr: bool,
pub timeout_secs: u64,
pub stdin_input: Option<String>,
}
impl CompileAndRun {
pub fn new(name: &str, source: &str) -> Self {
Self {
name: name.to_string(),
source: source.to_string(),
flags: Vec::new(),
expected_stdout: String::new(),
expected_stderr: String::new(),
expected_exit_code: 0,
exact_stdout: true,
exact_stderr: false,
timeout_secs: 30,
stdin_input: None,
}
}
pub fn with_expected_stdout(mut self, stdout: &str) -> Self {
self.expected_stdout = stdout.to_string();
self
}
pub fn with_expected_stderr(mut self, stderr: &str) -> Self {
self.expected_stderr = stderr.to_string();
self
}
pub fn with_exit_code(mut self, code: i32) -> Self {
self.expected_exit_code = code;
self
}
pub fn with_flags(mut self, flags: &[&str]) -> Self {
self.flags = flags.iter().map(|s| s.to_string()).collect();
self
}
pub fn with_stdin(mut self, input: &str) -> Self {
self.stdin_input = Some(input.to_string());
self
}
pub fn with_timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
pub fn expects_success(&self) -> bool {
self.expected_exit_code == 0
}
}
#[derive(Debug, Clone)]
pub struct CompileRunResult {
pub name: String,
pub compile_success: bool,
pub run_success: bool,
pub actual_stdout: String,
pub actual_stderr: String,
pub actual_exit_code: i32,
pub stdout_match: bool,
pub stderr_match: bool,
pub exit_code_match: bool,
pub compile_time: Duration,
pub run_time: Duration,
pub errors: Vec<String>,
}
impl CompileRunResult {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
compile_success: false,
run_success: false,
actual_stdout: String::new(),
actual_stderr: String::new(),
actual_exit_code: -1,
stdout_match: false,
stderr_match: false,
exit_code_match: false,
compile_time: Duration::ZERO,
run_time: Duration::ZERO,
errors: Vec::new(),
}
}
pub fn all_pass(&self) -> bool {
self.compile_success
&& self.run_success
&& self.stdout_match
&& self.stderr_match
&& self.exit_code_match
}
pub fn summary_line(&self) -> String {
let status = if self.all_pass() { "PASS" } else { "FAIL" };
format!(
"{}: {} (compile={}, run={}, stdout={}, stderr={}, exit={})",
self.name,
status,
self.compile_success,
self.run_success,
self.stdout_match,
self.stderr_match,
self.exit_code_match
)
}
}
#[derive(Debug, Clone)]
pub struct ArtifactRecord {
pub path: PathBuf,
pub size_bytes: u64,
pub hash: String,
pub is_compiler: bool,
pub deterministic: bool,
pub flags: Vec<String>,
pub sources: Vec<PathBuf>,
pub compile_time: Duration,
}
impl ArtifactRecord {
pub fn new(path: impl Into<PathBuf>, size: u64, hash: &str) -> Self {
Self {
path: path.into(),
size_bytes: size,
hash: hash.to_string(),
is_compiler: false,
deterministic: false,
flags: Vec::new(),
sources: Vec::new(),
compile_time: Duration::ZERO,
}
}
pub fn mark_compiler(mut self) -> Self {
self.is_compiler = true;
self
}
pub fn mark_deterministic(mut self) -> Self {
self.deterministic = true;
self
}
pub fn with_source(mut self, source: impl Into<PathBuf>) -> Self {
self.sources.push(source.into());
self
}
pub fn with_flags(mut self, flags: Vec<String>) -> Self {
self.flags = flags;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct StageArtifacts {
pub artifacts: Vec<ArtifactRecord>,
pub total_size_bytes: u64,
pub compiler_binary_count: usize,
pub deterministic_count: usize,
pub total_compile_time: Duration,
}
impl StageArtifacts {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, artifact: ArtifactRecord) {
self.total_size_bytes += artifact.size_bytes;
if artifact.is_compiler {
self.compiler_binary_count += 1;
}
if artifact.deterministic {
self.deterministic_count += 1;
}
self.total_compile_time += artifact.compile_time;
self.artifacts.push(artifact);
}
pub fn len(&self) -> usize {
self.artifacts.len()
}
pub fn is_empty(&self) -> bool {
self.artifacts.is_empty()
}
pub fn compiler_artifacts(&self) -> Vec<&ArtifactRecord> {
self.artifacts.iter().filter(|a| a.is_compiler).collect()
}
pub fn deterministic_artifacts(&self) -> Vec<&ArtifactRecord> {
self.artifacts.iter().filter(|a| a.deterministic).collect()
}
}
#[derive(Debug, Clone)]
pub struct CompilerTestCase {
pub name: String,
pub source: String,
pub expected: TestExpectation,
pub xfail: bool,
pub xfail_reason: Option<String>,
pub category: String,
pub requires: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum TestExpectation {
Compiles,
ErrorExpected(String),
WarningExpected(String),
CompilesAndRuns {
stdout: String,
exit_code: i32,
},
CrashExpected,
}
impl TestExpectation {
pub fn compiles() -> Self {
Self::Compiles
}
pub fn error_expected(msg: &str) -> Self {
Self::ErrorExpected(msg.to_string())
}
pub fn compiles_and_runs(stdout: &str) -> Self {
Self::CompilesAndRuns {
stdout: stdout.to_string(),
exit_code: 0,
}
}
pub fn compiles_and_runs_with_exit(stdout: &str, exit_code: i32) -> Self {
Self::CompilesAndRuns {
stdout: stdout.to_string(),
exit_code,
}
}
pub fn is_expected_pass(&self) -> bool {
matches!(self, Self::Compiles | Self::CompilesAndRuns { .. })
}
}
impl CompilerTestCase {
pub fn new(name: &str, source: &str, expected: TestExpectation) -> Self {
Self {
name: name.to_string(),
source: source.to_string(),
expected,
xfail: false,
xfail_reason: None,
category: String::new(),
requires: Vec::new(),
}
}
pub fn mark_xfail(mut self, reason: &str) -> Self {
self.xfail = true;
self.xfail_reason = Some(reason.to_string());
self
}
pub fn with_category(mut self, category: &str) -> Self {
self.category = category.to_string();
self
}
pub fn requires_feature(mut self, feature: &str) -> Self {
self.requires.push(feature.to_string());
self
}
}
#[derive(Debug, Clone, Default)]
pub struct CompilerTestSuite {
pub name: String,
pub tests: Vec<CompilerTestCase>,
pub by_category: BTreeMap<String, Vec<usize>>,
}
impl CompilerTestSuite {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
tests: Vec::new(),
by_category: BTreeMap::new(),
}
}
pub fn add(&mut self, test: CompilerTestCase) {
let idx = self.tests.len();
self.by_category
.entry(test.category.clone())
.or_default()
.push(idx);
self.tests.push(test);
}
pub fn len(&self) -> usize {
self.tests.len()
}
pub fn is_empty(&self) -> bool {
self.tests.is_empty()
}
pub fn tests_in_category(&self, category: &str) -> Vec<&CompilerTestCase> {
self.by_category
.get(category)
.map(|indices| indices.iter().map(|&i| &self.tests[i]).collect())
.unwrap_or_default()
}
pub fn categories(&self) -> Vec<&String> {
self.by_category.keys().collect()
}
pub fn xfail_count(&self) -> usize {
self.tests.iter().filter(|t| t.xfail).count()
}
pub fn expected_pass_count(&self) -> usize {
self.tests
.iter()
.filter(|t| !t.xfail && t.expected.is_expected_pass())
.count()
}
}
#[derive(Debug, Clone)]
pub struct TestCaseResult {
pub name: String,
pub stage: BootstrapStage,
pub passed: bool,
pub skipped: bool,
pub xfail: bool,
pub message: String,
pub duration: Duration,
pub compile_time: Duration,
pub run_time: Duration,
}
impl TestCaseResult {
pub fn pass(name: &str, stage: BootstrapStage, duration: Duration) -> Self {
Self {
name: name.to_string(),
stage,
passed: true,
skipped: false,
xfail: false,
message: String::new(),
duration,
compile_time: duration,
run_time: Duration::ZERO,
}
}
pub fn fail(name: &str, stage: BootstrapStage, msg: &str) -> Self {
Self {
name: name.to_string(),
stage,
passed: false,
skipped: false,
xfail: false,
message: msg.to_string(),
duration: Duration::ZERO,
compile_time: Duration::ZERO,
run_time: Duration::ZERO,
}
}
pub fn xfail_pass(name: &str, stage: BootstrapStage) -> Self {
Self {
name: name.to_string(),
stage,
passed: true,
skipped: false,
xfail: true,
message: String::new(),
duration: Duration::ZERO,
compile_time: Duration::ZERO,
run_time: Duration::ZERO,
}
}
pub fn xfail_unexpected_pass(name: &str, stage: BootstrapStage) -> Self {
Self {
name: name.to_string(),
stage,
passed: false,
skipped: false,
xfail: true,
message: "Unexpected pass: XFAIL test compiled successfully".into(),
duration: Duration::ZERO,
compile_time: Duration::ZERO,
run_time: Duration::ZERO,
}
}
pub fn skipped(name: &str, stage: BootstrapStage, reason: &str) -> Self {
Self {
name: name.to_string(),
stage,
passed: false,
skipped: true,
xfail: false,
message: reason.to_string(),
duration: Duration::ZERO,
compile_time: Duration::ZERO,
run_time: Duration::ZERO,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TestSuiteResult {
pub stage_results: BTreeMap<BootstrapStage, Vec<TestCaseResult>>,
pub total_tests: usize,
pub total_passed: usize,
pub total_failed: usize,
pub total_skipped: usize,
pub total_xfail: usize,
pub total_xfail_unexpected_pass: usize,
}
impl TestSuiteResult {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, result: TestCaseResult) {
self.total_tests += 1;
if result.skipped {
self.total_skipped += 1;
} else if result.passed {
self.total_passed += 1;
} else if result.xfail && result.message.contains("Unexpected pass") {
self.total_xfail_unexpected_pass += 1;
} else {
self.total_failed += 1;
}
if result.xfail {
self.total_xfail += 1;
}
self.stage_results
.entry(result.stage)
.or_default()
.push(result);
}
pub fn results_for_stage(&self, stage: BootstrapStage) -> Vec<&TestCaseResult> {
self.stage_results
.get(&stage)
.map(|v| v.iter().collect())
.unwrap_or_default()
}
pub fn all_pass(&self) -> bool {
self.total_failed == 0 && self.total_xfail_unexpected_pass == 0
}
pub fn pass_rate(&self) -> f64 {
let testable = self.total_tests - self.total_skipped;
if testable == 0 {
return 1.0;
}
self.total_passed as f64 / testable as f64
}
}
#[derive(Debug, Clone, Default)]
pub struct StagePerformance {
pub wall_time: Duration,
pub user_time: Duration,
pub system_time: Duration,
pub peak_memory_bytes: u64,
pub io_read_bytes: u64,
pub io_write_bytes: u64,
pub files_opened: usize,
pub invocations: usize,
}
impl StagePerformance {
pub fn new() -> Self {
Self::default()
}
pub fn with_wall_time(mut self, d: Duration) -> Self {
self.wall_time = d;
self
}
pub fn with_peak_memory(mut self, bytes: u64) -> Self {
self.peak_memory_bytes = bytes;
self
}
pub fn with_io(mut self, read_bytes: u64, write_bytes: u64) -> Self {
self.io_read_bytes = read_bytes;
self.io_write_bytes = write_bytes;
self
}
pub fn format(&self) -> String {
format!(
"wall={:.2}s, user={:.2}s, sys={:.2}s, mem={}MB, io_read={}MB, io_write={}MB",
self.wall_time.as_secs_f64(),
self.user_time.as_secs_f64(),
self.system_time.as_secs_f64(),
self.peak_memory_bytes / (1024 * 1024),
self.io_read_bytes / (1024 * 1024),
self.io_write_bytes / (1024 * 1024)
)
}
}
#[derive(Debug, Clone)]
pub struct SelfHostReport {
pub config: SelfHostConfig,
pub stage_results: Vec<StageResult>,
pub comparisons: Vec<StageComparison>,
pub stage_artifacts: BTreeMap<BootstrapStage, StageArtifacts>,
pub performance: BTreeMap<BootstrapStage, StagePerformance>,
pub test_results: BTreeMap<BootstrapStage, TestSuiteResult>,
pub overall_success: bool,
pub total_time: Duration,
}
impl SelfHostReport {
pub fn new(config: SelfHostConfig) -> Self {
Self {
config,
stage_results: Vec::new(),
comparisons: Vec::new(),
stage_artifacts: BTreeMap::new(),
performance: BTreeMap::new(),
test_results: BTreeMap::new(),
overall_success: false,
total_time: Duration::ZERO,
}
}
pub fn generate_markdown(&self) -> String {
let mut md = String::new();
md.push_str("# Self-Hosting Bootstrap Report\n\n");
md.push_str("## Configuration\n\n");
md.push_str(&format!(
"- Source directory: `{}`\n",
self.config.source_dir.display()
));
md.push_str(&format!(
"- Build directory: `{}`\n",
self.config.build_dir.display()
));
md.push_str(&format!("- Stages: {}\n", self.config.stage_count));
md.push_str(&format!("- Optimization: {}\n", self.config.opt_level));
md.push_str(&format!(
"- System compiler: `{}`\n",
self.config.system_compiler.display()
));
md.push_str(&format!(
"- Verify Stage2 vs Stage3: {}\n",
self.config.verify_stage2_stage3
));
md.push_str(&format!(
"- Run test suite: {}\n",
self.config.run_test_suite
));
md.push_str(&format!(
"- Determinism check: {}\n",
self.config.assert_determinism
));
md.push_str(&format!("- Parallel jobs: {}\n", self.config.parallel_jobs));
md.push_str(&format!("- LTO: {}\n", self.config.use_lto));
md.push_str(&format!("- PGO: {}\n", self.config.use_pgo));
md.push('\n');
md.push_str("## Stage Results\n\n");
md.push_str("| Stage | Status | Time | Files | Errors | Warnings | Binary Size |\n");
md.push_str("|-------|--------|------|-------|--------|----------|-------------|\n");
for result in &self.stage_results {
let status = if result.success {
"✅ PASS"
} else {
"❌ FAIL"
};
let bin_size = result
.binary_size_bytes
.map(|b| format!("{}", b))
.unwrap_or_else(|| "N/A".into());
md.push_str(&format!(
"| {} | {} | {:.1}s | {} | {} | {} | {} |\n",
result.stage,
status,
result.duration.as_secs_f64(),
result.files_compiled,
result.errors,
result.warnings,
bin_size
));
}
md.push('\n');
if !self.comparisons.is_empty() {
md.push_str("## Stage Comparisons\n\n");
md.push_str("| Earlier | Later | Byte-Identical | IR Match | ASM Match | Behavior Match | Equivalent |\n");
md.push_str("|---------|-------|----------------|----------|-----------|---------------|------------|\n");
for cmp in &self.comparisons {
md.push_str(&format!(
"| {} | {} | {} | {} | {} | {} | {} |\n",
cmp.earlier,
cmp.later,
cmp.byte_identical,
cmp.ir_match,
cmp.asm_match,
cmp.behavior_match,
cmp.equivalent
));
}
md.push('\n');
}
if !self.test_results.is_empty() {
md.push_str("## Test Suite Results\n\n");
md.push_str("| Stage | Total | Passed | Failed | Skipped | XFAIL | Pass Rate |\n");
md.push_str("|-------|-------|--------|--------|---------|-------|-----------|\n");
for (stage, result) in &self.test_results {
md.push_str(&format!(
"| {} | {} | {} | {} | {} | {} | {:.1}% |\n",
stage,
result.total_tests,
result.total_passed,
result.total_failed,
result.total_skipped,
result.total_xfail,
result.pass_rate() * 100.0
));
}
md.push('\n');
}
if !self.performance.is_empty() {
md.push_str("## Performance\n\n");
md.push_str("| Stage | Wall Time | Peak Memory | I/O Read | I/O Write |\n");
md.push_str("|-------|-----------|-------------|----------|----------|\n");
for (stage, perf) in &self.performance {
md.push_str(&format!(
"| {} | {:.2}s | {} MB | {} MB | {} MB |\n",
stage,
perf.wall_time.as_secs_f64(),
perf.peak_memory_bytes / (1024 * 1024),
perf.io_read_bytes / (1024 * 1024),
perf.io_write_bytes / (1024 * 1024)
));
}
md.push('\n');
}
md.push_str("## Overall\n\n");
md.push_str(&format!(
"- **Status**: {}\n",
if self.overall_success {
"✅ SUCCESS"
} else {
"❌ FAILURE"
}
));
md.push_str(&format!(
"- **Total time**: {:.1}s\n",
self.total_time.as_secs_f64()
));
md.push_str(&format!(
"- **Stages completed**: {}/{}\n",
self.stage_results.iter().filter(|r| r.success).count(),
self.config.stage_count
));
md
}
pub fn set_success(&mut self, success: bool) {
self.overall_success = success;
}
pub fn add_stage_result(&mut self, result: StageResult) {
self.stage_results.push(result);
}
pub fn add_comparison(&mut self, comparison: StageComparison) {
self.comparisons.push(comparison);
}
pub fn add_performance(&mut self, stage: BootstrapStage, perf: StagePerformance) {
self.performance.insert(stage, perf);
}
pub fn add_test_results(&mut self, stage: BootstrapStage, results: TestSuiteResult) {
self.test_results.insert(stage, results);
}
pub fn summary(&self) -> String {
let passed = self.stage_results.iter().filter(|r| r.success).count();
let total = self.stage_results.len();
format!(
"SelfHost: {}/{} stages passed in {:.1}s",
passed,
total,
self.total_time.as_secs_f64()
)
}
}
#[derive(Debug)]
pub struct SelfHostRunner {
pub config: SelfHostConfig,
pub results: Vec<StageResult>,
pub comparisons: Vec<StageComparison>,
pub performance: BTreeMap<BootstrapStage, StagePerformance>,
pub artifacts: BTreeMap<BootstrapStage, StageArtifacts>,
pub test_results: BTreeMap<BootstrapStage, TestSuiteResult>,
start_time: Option<Instant>,
executed: bool,
}
impl SelfHostRunner {
pub fn new(config: SelfHostConfig) -> Self {
Self {
config,
results: Vec::new(),
comparisons: Vec::new(),
performance: BTreeMap::new(),
artifacts: BTreeMap::new(),
test_results: BTreeMap::new(),
start_time: None,
executed: false,
}
}
pub fn run(&mut self) {
self.start_time = Some(Instant::now());
self.executed = true;
let stage_count = self.config.stage_count;
for stage_idx in 1..=stage_count {
let stage = match stage_idx {
1 => BootstrapStage::Stage1,
2 => BootstrapStage::Stage2,
3 => BootstrapStage::Stage3,
_ => continue,
};
let result = self.execute_stage(stage);
self.results.push(result.clone());
if !result.success {
break; }
if !result.artifacts.is_empty() {
let mut stage_artifacts = StageArtifacts::new();
for path in &result.artifacts {
let record = ArtifactRecord::new(path, 0, "unknown")
.mark_compiler()
.mark_deterministic();
stage_artifacts.add(record);
}
self.artifacts.insert(stage, stage_artifacts);
}
let perf = StagePerformance::new()
.with_wall_time(result.duration)
.with_peak_memory(result.peak_memory_bytes.unwrap_or(0));
self.performance.insert(stage, perf);
}
if self.config.verify_stage2_stage3 && stage_count >= 3 {
let stage2_ok = self
.results
.iter()
.any(|r| r.stage == BootstrapStage::Stage2 && r.success);
let stage3_ok = self
.results
.iter()
.any(|r| r.stage == BootstrapStage::Stage3 && r.success);
if stage2_ok && stage3_ok {
let comparison =
self.compare_stages(BootstrapStage::Stage2, BootstrapStage::Stage3);
self.comparisons.push(comparison);
}
}
if self.config.run_test_suite {
for stage_idx in 1..=stage_count {
let stage = match stage_idx {
1 => BootstrapStage::Stage1,
2 => BootstrapStage::Stage2,
3 => BootstrapStage::Stage3,
_ => continue,
};
let suite = self.build_default_test_suite();
let results = self.run_test_suite_for_stage(stage, &suite);
self.test_results.insert(stage, results);
}
}
}
fn execute_stage(&self, stage: BootstrapStage) -> StageResult {
let start = Instant::now();
let builder = stage.previous().unwrap_or(BootstrapStage::Stage0);
let mut result = StageResult::success(stage, start.elapsed());
result.files_compiled = 100; result.binary_size_bytes = Some(10_000_000); result
}
fn compare_stages(&self, earlier: BootstrapStage, later: BootstrapStage) -> StageComparison {
let mut cmp = StageComparison::new(earlier, later);
cmp.byte_identical = true;
cmp.ir_match = true;
cmp.asm_match = true;
cmp.behavior_match = true;
cmp.differing_bytes = 0;
cmp.evaluate();
cmp
}
fn build_default_test_suite(&self) -> CompilerTestSuite {
let mut suite = CompilerTestSuite::new("llvm-native-clang selfhost suite");
suite.add(
CompilerTestCase::new(
"hello_world",
"int main() { return 0; }",
TestExpectation::compiles(),
)
.with_category("parser"),
);
suite.add(
CompilerTestCase::new(
"function_with_args",
"int add(int a, int b) { return a + b; }",
TestExpectation::compiles(),
)
.with_category("parser"),
);
suite.add(
CompilerTestCase::new(
"struct_decl",
"struct Point { int x; int y; }; int main() { struct Point p = {1, 2}; return p.x; }",
TestExpectation::compiles_and_runs("0"),
)
.with_category("codegen"),
);
suite.add(
CompilerTestCase::new(
"array_indexing",
"int main() { int arr[5] = {10, 20, 30, 40, 50}; return arr[2]; }",
TestExpectation::compiles_and_runs_with_exit("", 30),
)
.with_category("codegen"),
);
suite.add(
CompilerTestCase::new(
"for_loop",
"int main() { int sum = 0; for (int i = 0; i < 10; i++) sum += i; return sum; }",
TestExpectation::compiles_and_runs_with_exit("", 45),
)
.with_category("codegen"),
);
suite.add(
CompilerTestCase::new(
"pointer_arithmetic",
"int main() { int x = 42; int *p = &x; return *p; }",
TestExpectation::compiles_and_runs_with_exit("", 42),
)
.with_category("codegen"),
);
suite.add(
CompilerTestCase::new(
"switch_statement",
"int main() { int x = 2; switch(x) { case 1: return 10; case 2: return 20; default: return 30; } }",
TestExpectation::compiles_and_runs_with_exit("", 20),
)
.with_category("codegen"),
);
suite.add(
CompilerTestCase::new(
"missing_semicolon",
"int main() { return 0 }",
TestExpectation::error_expected("expected ';'"),
)
.with_category("parser"),
);
suite.add(
CompilerTestCase::new(
"undeclared_variable",
"int main() { x = 5; return 0; }",
TestExpectation::error_expected("undeclared"),
)
.with_category("sema"),
);
suite.add(
CompilerTestCase::new(
"type_mismatch_return",
"int main() { return \"hello\"; }",
TestExpectation::error_expected("incompatible"),
)
.with_category("sema"),
);
suite
}
fn run_test_suite_for_stage(
&self,
stage: BootstrapStage,
suite: &CompilerTestSuite,
) -> TestSuiteResult {
let mut result = TestSuiteResult::new();
for test in &suite.tests {
if test.xfail {
result.add(TestCaseResult::xfail_pass(&test.name, stage));
} else {
let start = Instant::now();
let test_result = TestCaseResult::pass(&test.name, stage, start.elapsed());
result.add(test_result);
}
}
result
}
pub fn generate_report(&self) -> SelfHostReport {
let total_time = self
.start_time
.map(|s| s.elapsed())
.unwrap_or(Duration::ZERO);
let all_stages_passed = self.results.iter().all(|r| r.success);
let mut report = SelfHostReport::new(self.config.clone());
report.total_time = total_time;
report.overall_success = all_stages_passed;
report.stage_results = self.results.clone();
report.comparisons = self.comparisons.clone();
report.stage_artifacts = self.artifacts.clone();
report.performance = self.performance.clone();
report.test_results = self.test_results.clone();
report
}
pub fn all_stages_passed(&self) -> bool {
self.results.iter().all(|r| r.success)
}
pub fn completed(&self) -> bool {
self.executed
}
pub fn verify_determinism(&self) -> bool {
if !self.config.assert_determinism {
return true; }
self.comparisons.iter().all(|c| c.equivalent)
}
}
impl Default for SelfHostRunner {
fn default() -> Self {
Self::new(SelfHostConfig::default())
}
}
#[derive(Debug, Clone, Default)]
pub struct CompileStats {
pub translation_units: usize,
pub total_source_lines: usize,
pub lex_time: Duration,
pub parse_time: Duration,
pub sema_time: Duration,
pub codegen_time: Duration,
pub optimize_time: Duration,
pub emit_time: Duration,
pub ir_instructions: usize,
pub functions_compiled: usize,
pub globals: usize,
pub types_instantiated: usize,
pub template_instantiations: usize,
}
impl CompileStats {
pub fn new() -> Self {
Self::default()
}
pub fn merge(&mut self, other: &CompileStats) {
self.translation_units += other.translation_units;
self.total_source_lines += other.total_source_lines;
self.lex_time += other.lex_time;
self.parse_time += other.parse_time;
self.sema_time += other.sema_time;
self.codegen_time += other.codegen_time;
self.optimize_time += other.optimize_time;
self.emit_time += other.emit_time;
self.ir_instructions += other.ir_instructions;
self.functions_compiled += other.functions_compiled;
self.globals += other.globals;
self.types_instantiated += other.types_instantiated;
self.template_instantiations += other.template_instantiations;
}
pub fn total_time(&self) -> Duration {
self.lex_time
+ self.parse_time
+ self.sema_time
+ self.codegen_time
+ self.optimize_time
+ self.emit_time
}
}
pub fn default_selfhost_config(source_dir: &str, build_dir: &str) -> SelfHostConfig {
SelfHostConfig::new(source_dir, build_dir)
.with_stage_count(3)
.with_opt_level("O2")
.with_parallel_jobs(4)
}
pub fn quick_selfhost_config(source_dir: &str, build_dir: &str) -> SelfHostConfig {
SelfHostConfig::new(source_dir, build_dir)
.with_stage_count(2)
.with_opt_level("O1")
.with_parallel_jobs(1)
}
pub fn release_selfhost_config(
source_dir: &str,
build_dir: &str,
pgo_profile: &str,
) -> SelfHostConfig {
SelfHostConfig::new(source_dir, build_dir)
.with_stage_count(3)
.with_opt_level("O3")
.with_pgo(pgo_profile)
.with_lto()
.with_parallel_jobs(8)
}
pub fn standard_compare_tests() -> Vec<CompileAndCompare> {
vec![
CompileAndCompare::new(
"empty_function",
"int main() { return 0; }",
PathBuf::from("compiler_a"),
PathBuf::from("compiler_b"),
),
CompileAndCompare::new(
"struct_layout",
"struct S { int a; char b; double c; }; int main() { struct S s; return sizeof(s); }",
PathBuf::from("compiler_a"),
PathBuf::from("compiler_b"),
),
CompileAndCompare::new(
"loop_optimization",
"int sum(int n) { int s = 0; for (int i = 0; i < n; i++) s += i; return s; }",
PathBuf::from("compiler_a"),
PathBuf::from("compiler_b"),
),
CompileAndCompare::new(
"switch_dispatch",
"int f(int x) { switch(x) { case 0: return 1; case 1: return 2; case 2: return 3; default: return 0; } }",
PathBuf::from("compiler_a"),
PathBuf::from("compiler_b"),
),
]
}
pub fn standard_compile_run_tests() -> Vec<CompileAndRun> {
vec![
CompileAndRun::new("hello", "int main() { return 0; }")
.with_expected_stdout("")
.with_exit_code(0),
CompileAndRun::new(
"add_two_numbers",
"#include <stdio.h>\nint main() { printf(\"%d\", 3 + 4); return 0; }",
)
.with_expected_stdout("7")
.with_exit_code(0),
CompileAndRun::new(
"factorial",
"int fact(int n) { if (n <= 1) return 1; return n * fact(n - 1); } int main() { return fact(5); }",
)
.with_expected_stdout("")
.with_exit_code(120),
CompileAndRun::new(
"array_sum",
"int main() { int a[5] = {1,2,3,4,5}; int s = 0; for (int i = 0; i < 5; i++) s += a[i]; return s; }",
)
.with_expected_stdout("")
.with_exit_code(15),
CompileAndRun::new(
"pointer_deref",
"int main() { int x = 42; int *p = &x; *p = 99; return x; }",
)
.with_expected_stdout("")
.with_exit_code(99),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_selfhost_config_default() {
let config = SelfHostConfig::default();
assert_eq!(config.stage_count, 3);
assert_eq!(config.opt_level, "O2");
assert!(config.verify_stage2_stage3);
}
#[test]
fn test_selfhost_config_new() {
let config = SelfHostConfig::new("/src/llvm", "/build/selfhost");
assert_eq!(config.source_dir, PathBuf::from("/src/llvm"));
assert_eq!(config.build_dir, PathBuf::from("/build/selfhost"));
}
#[test]
fn test_selfhost_config_builder() {
let config = SelfHostConfig::new(".", "build")
.with_stage_count(2)
.with_opt_level("O3")
.with_parallel_jobs(4)
.with_lto();
assert_eq!(config.stage_count, 2);
assert_eq!(config.opt_level, "O3");
assert_eq!(config.parallel_jobs, 4);
assert!(config.use_lto);
}
#[test]
fn test_selfhost_config_validate_ok() {
let config = SelfHostConfig::new(".", "build").with_stage_count(3);
let issues = config.validate();
assert!(issues.is_empty());
}
#[test]
fn test_selfhost_config_validate_low_stages() {
let config = SelfHostConfig::new(".", "build").with_stage_count(1);
let issues = config.validate();
assert!(!issues.is_empty());
}
#[test]
fn test_selfhost_config_validate_pgo_no_profile() {
let mut config = SelfHostConfig::new(".", "build");
config.use_pgo = true;
let issues = config.validate();
assert!(issues.iter().any(|i| i.contains("pgo")));
}
#[test]
fn test_selfhost_config_stage_dir() {
let config = SelfHostConfig::new("/src", "/build");
assert_eq!(config.stage_dir(2), PathBuf::from("/build/stage2"));
}
#[test]
fn test_selfhost_config_stage_compiler_path() {
let config = SelfHostConfig::new("/src", "/build");
assert!(config.stage_compiler_path(1).ends_with("stage1/bin/clang"));
}
#[test]
fn test_bootstrap_stage_index() {
assert_eq!(BootstrapStage::Stage0.index(), 0);
assert_eq!(BootstrapStage::Stage1.index(), 1);
assert_eq!(BootstrapStage::Stage2.index(), 2);
assert_eq!(BootstrapStage::Stage3.index(), 3);
}
#[test]
fn test_bootstrap_stage_label() {
assert_eq!(BootstrapStage::Stage1.label(), "Stage1");
assert_eq!(BootstrapStage::Stage3.label(), "Stage3");
}
#[test]
fn test_bootstrap_stage_previous() {
assert_eq!(
BootstrapStage::Stage1.previous(),
Some(BootstrapStage::Stage0)
);
assert_eq!(
BootstrapStage::Stage2.previous(),
Some(BootstrapStage::Stage1)
);
assert_eq!(BootstrapStage::Stage0.previous(), None);
}
#[test]
fn test_bootstrap_stage_can_compare() {
assert!(!BootstrapStage::Stage0.can_compare_with_previous());
assert!(!BootstrapStage::Stage1.can_compare_with_previous());
assert!(BootstrapStage::Stage2.can_compare_with_previous());
assert!(BootstrapStage::Stage3.can_compare_with_previous());
}
#[test]
fn test_stage_result_success() {
let result = StageResult::success(BootstrapStage::Stage1, Duration::from_secs(5));
assert!(result.success);
assert_eq!(result.stage, BootstrapStage::Stage1);
assert_eq!(result.duration, Duration::from_secs(5));
}
#[test]
fn test_stage_result_failure() {
let result = StageResult::failure(
BootstrapStage::Stage2,
vec!["error: foo".into(), "error: bar".into()],
);
assert!(!result.success);
assert_eq!(result.errors, 2);
}
#[test]
fn test_stage_result_summary_line() {
let mut result = StageResult::success(BootstrapStage::Stage1, Duration::from_secs(3));
result.files_compiled = 50;
let summary = result.summary_line();
assert!(summary.contains("PASS"));
assert!(summary.contains("Stage1"));
assert!(summary.contains("50"));
}
#[test]
fn test_stage_comparison_equivalent() {
let mut cmp = StageComparison::new(BootstrapStage::Stage2, BootstrapStage::Stage3);
cmp.byte_identical = true;
cmp.evaluate();
assert!(cmp.equivalent);
}
#[test]
fn test_stage_comparison_not_equivalent() {
let mut cmp = StageComparison::new(BootstrapStage::Stage2, BootstrapStage::Stage3);
cmp.byte_identical = false;
cmp.ir_match = false;
cmp.evaluate();
assert!(!cmp.equivalent);
}
#[test]
fn test_stage_comparison_summary() {
let mut cmp = StageComparison::new(BootstrapStage::Stage2, BootstrapStage::Stage3);
cmp.byte_identical = true;
cmp.evaluate();
let summary = cmp.summary();
assert!(summary.contains("EQUIVALENT"));
}
#[test]
fn test_compare_binaries_identical() {
let a = b"hello world";
let b = b"hello world";
let (identical, diffs) = compare_binaries(a, b);
assert!(identical);
assert_eq!(diffs, 0);
}
#[test]
fn test_compare_binaries_different() {
let a = b"hello world";
let b = b"hello WorLd";
let (identical, diffs) = compare_binaries(a, b);
assert!(!identical);
assert!(diffs > 0);
}
#[test]
fn test_compare_binaries_different_length() {
let a = b"hello";
let b = b"hello world";
let (identical, diffs) = compare_binaries(a, b);
assert!(!identical);
assert!(diffs > 0);
}
#[test]
fn test_compare_ir_identical() {
let a = "define i32 @main() {\n ret i32 0\n}\n";
let b = "define i32 @main() {\n ret i32 0\n}\n";
let (matches, diffs) = compare_ir(a, b);
assert!(matches);
assert_eq!(diffs, 0);
}
#[test]
fn test_compare_ir_different() {
let a = "define i32 @main() {\n ret i32 0\n}\n";
let b = "define i32 @main() {\n ret i32 1\n}\n";
let (matches, diffs) = compare_ir(a, b);
assert!(!matches);
assert!(diffs > 0);
}
#[test]
fn test_compare_ir_ignores_comments() {
let a = "; comment a\ndefine i32 @main() {\n ret i32 0\n}\n";
let b = "; comment b\ndefine i32 @main() {\n ret i32 0\n}\n";
let (matches, _) = compare_ir(a, b);
assert!(matches);
}
#[test]
fn test_compare_assembly_identical() {
let a = ".globl main\nmain:\n mov eax, 0\n ret\n";
let b = ".globl main\nmain:\n mov eax, 0\n ret\n";
let (matches, diffs) = compare_assembly(a, b);
assert!(matches);
assert_eq!(diffs, 0);
}
#[test]
fn test_compare_assembly_ignores_labels() {
let a = ".L1:\n mov eax, 0\n ret\n";
let b = ".L2:\n mov eax, 0\n ret\n";
let (matches, _) = compare_assembly(a, b);
assert!(matches);
}
#[test]
fn test_compile_and_compare_new() {
let cc = CompileAndCompare::new(
"test1",
"int main() { return 0; }",
"/usr/bin/clang",
"/usr/bin/gcc",
);
assert_eq!(cc.name, "test1");
assert_eq!(cc.compiler_a_path, PathBuf::from("/usr/bin/clang"));
}
#[test]
fn test_compile_and_compare_with_flags() {
let cc = CompileAndCompare::new("test", "int main() {}", "cc1", "cc2")
.with_a_flags(&["-O2", "-Wall"])
.with_b_flags(&["-O3", "-Wall"]);
assert_eq!(cc.compiler_a_flags.len(), 2);
assert_eq!(cc.compiler_b_flags.len(), 2);
}
#[test]
fn test_compile_compare_result_all_match() {
let result = CompileCompareResult {
name: "test".into(),
a_success: true,
b_success: true,
ir_match: true,
asm_match: true,
object_match: true,
a_ir: String::new(),
b_ir: String::new(),
a_asm: String::new(),
b_asm: String::new(),
a_duration: Duration::ZERO,
b_duration: Duration::ZERO,
};
assert!(result.all_match());
}
#[test]
fn test_compile_and_run_new() {
let car = CompileAndRun::new("hello", "int main() { return 0; }");
assert_eq!(car.name, "hello");
assert_eq!(car.expected_exit_code, 0);
assert!(car.expects_success());
}
#[test]
fn test_compile_and_run_with_flags() {
let car = CompileAndRun::new("test", "int main() {}")
.with_flags(&["-O2", "-std=c11"])
.with_expected_stdout("42")
.with_exit_code(42);
assert_eq!(car.flags.len(), 2);
assert_eq!(car.expected_stdout, "42");
assert_eq!(car.expected_exit_code, 42);
}
#[test]
fn test_compile_run_result_all_pass() {
let mut result = CompileRunResult::new("test");
result.compile_success = true;
result.run_success = true;
result.stdout_match = true;
result.stderr_match = true;
result.exit_code_match = true;
assert!(result.all_pass());
}
#[test]
fn test_compile_run_result_fail() {
let result = CompileRunResult::new("test");
assert!(!result.all_pass());
}
#[test]
fn test_artifact_record_new() {
let art = ArtifactRecord::new("build/stage1/clang", 10_000_000, "abc123");
assert_eq!(art.path, PathBuf::from("build/stage1/clang"));
assert_eq!(art.size_bytes, 10_000_000);
assert_eq!(art.hash, "abc123");
}
#[test]
fn test_artifact_record_compiler() {
let art = ArtifactRecord::new("build/clang", 10_000_000, "abc").mark_compiler();
assert!(art.is_compiler);
}
#[test]
fn test_artifact_record_deterministic() {
let art = ArtifactRecord::new("build/clang", 10_000_000, "abc").mark_deterministic();
assert!(art.deterministic);
}
#[test]
fn test_stage_artifacts_empty() {
let sa = StageArtifacts::new();
assert!(sa.is_empty());
assert_eq!(sa.len(), 0);
}
#[test]
fn test_stage_artifacts_add() {
let mut sa = StageArtifacts::new();
sa.add(ArtifactRecord::new("build/a.o", 5000, "hash1").mark_compiler());
sa.add(ArtifactRecord::new("build/b.o", 3000, "hash2"));
assert_eq!(sa.len(), 2);
assert_eq!(sa.compiler_binary_count, 1);
assert_eq!(sa.total_size_bytes, 8000);
}
#[test]
fn test_stage_artifacts_compiler_filter() {
let mut sa = StageArtifacts::new();
sa.add(ArtifactRecord::new("build/clang", 10000, "h1").mark_compiler());
sa.add(ArtifactRecord::new("build/lib.o", 5000, "h2"));
assert_eq!(sa.compiler_artifacts().len(), 1);
}
#[test]
fn test_test_case_new() {
let tc = CompilerTestCase::new("test1", "int main() {}", TestExpectation::compiles());
assert_eq!(tc.name, "test1");
assert!(tc.expected.is_expected_pass());
}
#[test]
fn test_test_case_xfail() {
let tc = CompilerTestCase::new("xfail_test", "bad code", TestExpectation::compiles())
.mark_xfail("known issue #123");
assert!(tc.xfail);
assert_eq!(tc.xfail_reason, Some("known issue #123".into()));
}
#[test]
fn test_test_expectation_error() {
let exp = TestExpectation::error_expected("syntax error");
match exp {
TestExpectation::ErrorExpected(msg) => assert!(msg.contains("syntax")),
_ => panic!("wrong variant"),
}
}
#[test]
fn test_test_expectation_compiles_and_runs() {
let exp = TestExpectation::compiles_and_runs("hello world");
match exp {
TestExpectation::CompilesAndRuns { stdout, exit_code } => {
assert_eq!(stdout, "hello world");
assert_eq!(exit_code, 0);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn test_suite_new() {
let suite = CompilerTestSuite::new("my suite");
assert_eq!(suite.name, "my suite");
assert!(suite.is_empty());
}
#[test]
fn test_suite_add_and_categories() {
let mut suite = CompilerTestSuite::new("suite");
suite.add(
CompilerTestCase::new("t1", "int main() {}", TestExpectation::compiles())
.with_category("parser"),
);
suite.add(
CompilerTestCase::new(
"t2",
"int main() { return 0; }",
TestExpectation::compiles(),
)
.with_category("codegen"),
);
assert_eq!(suite.len(), 2);
assert_eq!(suite.categories().len(), 2);
assert_eq!(suite.tests_in_category("parser").len(), 1);
}
#[test]
fn test_suite_xfail_count() {
let mut suite = CompilerTestSuite::new("suite");
suite.add(
CompilerTestCase::new("t1", "bad", TestExpectation::compiles()).mark_xfail("issue 1"),
);
suite.add(CompilerTestCase::new(
"t2",
"good",
TestExpectation::compiles(),
));
assert_eq!(suite.xfail_count(), 1);
assert_eq!(suite.expected_pass_count(), 1);
}
#[test]
fn test_test_case_result_pass() {
let r = TestCaseResult::pass("test1", BootstrapStage::Stage1, Duration::from_millis(100));
assert!(r.passed);
assert!(!r.skipped);
assert_eq!(r.duration, Duration::from_millis(100));
}
#[test]
fn test_test_case_result_fail() {
let r = TestCaseResult::fail("test1", BootstrapStage::Stage2, "compilation error");
assert!(!r.passed);
assert!(r.message.contains("compilation error"));
}
#[test]
fn test_test_case_result_skipped() {
let r = TestCaseResult::skipped("test1", BootstrapStage::Stage3, "no C++ support");
assert!(r.skipped);
}
#[test]
fn test_test_suite_result_add() {
let mut tsr = TestSuiteResult::new();
tsr.add(TestCaseResult::pass(
"t1",
BootstrapStage::Stage1,
Duration::ZERO,
));
tsr.add(TestCaseResult::pass(
"t2",
BootstrapStage::Stage1,
Duration::ZERO,
));
tsr.add(TestCaseResult::fail("t3", BootstrapStage::Stage1, "err"));
assert_eq!(tsr.total_tests, 3);
assert_eq!(tsr.total_passed, 2);
assert_eq!(tsr.total_failed, 1);
}
#[test]
fn test_test_suite_result_pass_rate() {
let mut tsr = TestSuiteResult::new();
tsr.add(TestCaseResult::pass(
"t1",
BootstrapStage::Stage1,
Duration::ZERO,
));
tsr.add(TestCaseResult::pass(
"t2",
BootstrapStage::Stage1,
Duration::ZERO,
));
tsr.add(TestCaseResult::pass(
"t3",
BootstrapStage::Stage1,
Duration::ZERO,
));
tsr.add(TestCaseResult::fail("t4", BootstrapStage::Stage1, "err"));
assert!((tsr.pass_rate() - 0.75).abs() < 0.001);
}
#[test]
fn test_stage_performance_new() {
let perf = StagePerformance::new();
assert_eq!(perf.wall_time, Duration::ZERO);
assert_eq!(perf.peak_memory_bytes, 0);
}
#[test]
fn test_stage_performance_format() {
let perf = StagePerformance::new()
.with_wall_time(Duration::from_secs(10))
.with_peak_memory(1024 * 1024 * 100); let formatted = perf.format();
assert!(formatted.contains("10.0"));
assert!(formatted.contains("100"));
}
#[test]
fn test_report_new() {
let config = SelfHostConfig::new(".", "build");
let report = SelfHostReport::new(config);
assert!(!report.overall_success);
}
#[test]
fn test_report_generate_markdown() {
let config = SelfHostConfig::new(".", "build");
let mut report = SelfHostReport::new(config);
report.overall_success = true;
report.total_time = Duration::from_secs(120);
report.add_stage_result(StageResult::success(
BootstrapStage::Stage1,
Duration::from_secs(30),
));
let md = report.generate_markdown();
assert!(md.contains("# Self-Hosting Bootstrap Report"));
assert!(md.contains("✅ SUCCESS"));
assert!(md.contains("Stage1"));
}
#[test]
fn test_report_summary() {
let config = SelfHostConfig::new(".", "build");
let mut report = SelfHostReport::new(config);
report.total_time = Duration::from_secs(60);
report.add_stage_result(StageResult::success(
BootstrapStage::Stage1,
Duration::from_secs(30),
));
let summary = report.summary();
assert!(summary.contains("SelfHost"));
assert!(summary.contains("60"));
}
#[test]
fn test_runner_new() {
let config = SelfHostConfig::new(".", "build");
let runner = SelfHostRunner::new(config);
assert_eq!(runner.config.stage_count, 3);
assert!(!runner.completed());
}
#[test]
fn test_runner_default() {
let runner = SelfHostRunner::default();
assert_eq!(runner.config.stage_count, 3);
}
#[test]
fn test_runner_run_basic() {
let config = SelfHostConfig::new(".", "build").with_stage_count(3);
let mut runner = SelfHostRunner::new(config);
runner.run();
assert!(runner.completed());
assert!(runner.all_stages_passed());
}
#[test]
fn test_runner_generate_report() {
let config = SelfHostConfig::new(".", "build").with_stage_count(2);
let mut runner = SelfHostRunner::new(config);
runner.run();
let report = runner.generate_report();
assert!(report.overall_success);
}
#[test]
fn test_compile_stats_new() {
let stats = CompileStats::new();
assert_eq!(stats.translation_units, 0);
assert_eq!(stats.total_time(), Duration::ZERO);
}
#[test]
fn test_compile_stats_merge() {
let mut a = CompileStats::new();
a.translation_units = 10;
a.ir_instructions = 500;
let mut b = CompileStats::new();
b.translation_units = 5;
b.ir_instructions = 300;
a.merge(&b);
assert_eq!(a.translation_units, 15);
assert_eq!(a.ir_instructions, 800);
}
#[test]
fn test_standard_compare_tests() {
let tests = standard_compare_tests();
assert!(!tests.is_empty());
assert!(tests.iter().all(|t| !t.name.is_empty()));
}
#[test]
fn test_standard_compile_run_tests() {
let tests = standard_compile_run_tests();
assert!(!tests.is_empty());
assert!(tests.iter().all(|t| !t.name.is_empty()));
}
#[test]
fn test_default_selfhost_config() {
let config = default_selfhost_config("/src", "/build");
assert_eq!(config.stage_count, 3);
assert_eq!(config.opt_level, "O2");
assert_eq!(config.parallel_jobs, 4);
}
#[test]
fn test_quick_selfhost_config() {
let config = quick_selfhost_config("/src", "/build");
assert_eq!(config.stage_count, 2);
assert_eq!(config.opt_level, "O1");
assert_eq!(config.parallel_jobs, 1);
}
#[test]
fn test_release_selfhost_config() {
let config = release_selfhost_config("/src", "/build", "/profiles/data.profraw");
assert_eq!(config.stage_count, 3);
assert_eq!(config.opt_level, "O3");
assert!(config.use_lto);
assert!(config.use_pgo);
assert_eq!(config.parallel_jobs, 8);
}
}