#![allow(unused_imports, non_camel_case_types)]
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::fs;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::clang::ast::{
BinaryOp, Decl, Expr, FunctionDecl, QualType, Stmt, TranslationUnit, TypeNode as ClangTypeKind,
};
use crate::clang::codegen::ClangCodeGen;
use crate::clang::diagnostics::{
ClangSourceLocation, ClangSourceManager, DiagID, DiagSeverity, DiagnosticBuilder,
DiagnosticEngine, DiagnosticOptions, SourceRange,
};
use crate::clang::driver::{compile_c_file, compile_c_string, ClangDriver};
use crate::clang::lexer::Lexer;
use crate::clang::parser::Parser;
use crate::clang::preprocessor::Preprocessor;
use crate::clang::sema::Sema;
use crate::clang::token::{Token, TokenKind};
use crate::clang::{CLangStandard, ClangOptions};
use crate::basic_block;
use crate::constants;
use crate::context::LLVMContext;
use crate::function::{self, Function};
use crate::instruction::{self, ICmpPred, Opcode};
use crate::ir_builder::IRBuilder;
use crate::module::Module;
use crate::types::{Type, TypeId, TypeKind};
use crate::value::{valref, Value, ValueRef};
use crate::x86::x86_calling_convention::{X86ArgClass, X86CallingConvention};
use crate::x86::x86_frame_lowering::{CallConv, X86FrameLowering};
use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode};
use crate::x86::x86_isel::X86InstructionSelector;
use crate::x86::x86_mc_encoder::X86MCEncoder;
use crate::x86::x86_register_info::{RegClass, X86Reg, X86RegisterInfo, X86_64_REG_COUNT};
use crate::x86::x86_subtarget::X86Subtarget;
use crate::x86::x86_target_machine::{CodeModel, OptimizationLevel, RelocModel, X86TargetMachine};
use crate::x86::{
X86_ENDIANNESS, X86_MAX_ALIGNMENT, X86_PAGE_SIZE, X86_RED_ZONE_SIZE_64, X86_STACK_ALIGNMENT_32,
X86_STACK_ALIGNMENT_64,
};
use crate::codegen::{MachineOperand, PhysReg, RegAlloc, RegAllocResult, TargetRegInfo, VirtReg};
use crate::mc_inst::{MCContext, MCExpr, MCInst, MCInstBuilder, MCInstFlags, MCOperand, MCSection};
use crate::target_machine::TargetMachine;
use crate::triple::{Arch, Triple};
pub const E2E_MAX_ERRORS_DEFAULT: u32 = 20;
pub const E2E_MAX_STAGES: usize = 13;
pub const E2E_DEFAULT_CACHE_DIR: &str = ".e2e_cache";
pub const E2E_MAX_SOURCE_SIZE: u64 = 256 * 1024 * 1024;
pub const E2E_DEFAULT_OPT_LEVEL: u8 = 2;
pub const E2E_MAX_CONCURRENT: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum E2EStage {
Source,
Preprocess,
Lex,
Parse,
Sema,
IRGen,
Optimize,
ISel,
RegAlloc,
FrameLower,
Encode,
Emit,
Link,
}
impl E2EStage {
pub fn all() -> Vec<E2EStage> {
vec![
E2EStage::Source,
E2EStage::Preprocess,
E2EStage::Lex,
E2EStage::Parse,
E2EStage::Sema,
E2EStage::IRGen,
E2EStage::Optimize,
E2EStage::ISel,
E2EStage::RegAlloc,
E2EStage::FrameLower,
E2EStage::Encode,
E2EStage::Emit,
E2EStage::Link,
]
}
pub fn next(self) -> Option<E2EStage> {
match self {
E2EStage::Source => Some(E2EStage::Preprocess),
E2EStage::Preprocess => Some(E2EStage::Lex),
E2EStage::Lex => Some(E2EStage::Parse),
E2EStage::Parse => Some(E2EStage::Sema),
E2EStage::Sema => Some(E2EStage::IRGen),
E2EStage::IRGen => Some(E2EStage::Optimize),
E2EStage::Optimize => Some(E2EStage::ISel),
E2EStage::ISel => Some(E2EStage::RegAlloc),
E2EStage::RegAlloc => Some(E2EStage::FrameLower),
E2EStage::FrameLower => Some(E2EStage::Encode),
E2EStage::Encode => Some(E2EStage::Emit),
E2EStage::Emit => Some(E2EStage::Link),
E2EStage::Link => None,
}
}
pub fn prev(self) -> Option<E2EStage> {
match self {
E2EStage::Source => None,
E2EStage::Preprocess => Some(E2EStage::Source),
E2EStage::Lex => Some(E2EStage::Preprocess),
E2EStage::Parse => Some(E2EStage::Lex),
E2EStage::Sema => Some(E2EStage::Parse),
E2EStage::IRGen => Some(E2EStage::Sema),
E2EStage::Optimize => Some(E2EStage::IRGen),
E2EStage::ISel => Some(E2EStage::Optimize),
E2EStage::RegAlloc => Some(E2EStage::ISel),
E2EStage::FrameLower => Some(E2EStage::RegAlloc),
E2EStage::Encode => Some(E2EStage::FrameLower),
E2EStage::Emit => Some(E2EStage::Encode),
E2EStage::Link => Some(E2EStage::Emit),
}
}
pub fn name(self) -> &'static str {
match self {
E2EStage::Source => "Source",
E2EStage::Preprocess => "Preprocess",
E2EStage::Lex => "Lex",
E2EStage::Parse => "Parse",
E2EStage::Sema => "Sema",
E2EStage::IRGen => "IRGen",
E2EStage::Optimize => "Optimize",
E2EStage::ISel => "ISel",
E2EStage::RegAlloc => "RegAlloc",
E2EStage::FrameLower => "FrameLower",
E2EStage::Encode => "Encode",
E2EStage::Emit => "Emit",
E2EStage::Link => "Link",
}
}
pub fn abbrev(self) -> &'static str {
match self {
E2EStage::Source => "SRC",
E2EStage::Preprocess => "PP",
E2EStage::Lex => "LEX",
E2EStage::Parse => "PAR",
E2EStage::Sema => "SEM",
E2EStage::IRGen => "IRG",
E2EStage::Optimize => "OPT",
E2EStage::ISel => "ISL",
E2EStage::RegAlloc => "RA",
E2EStage::FrameLower => "FL",
E2EStage::Encode => "ENC",
E2EStage::Emit => "EMT",
E2EStage::Link => "LNK",
}
}
pub fn is_frontend(self) -> bool {
matches!(
self,
E2EStage::Source
| E2EStage::Preprocess
| E2EStage::Lex
| E2EStage::Parse
| E2EStage::Sema
| E2EStage::IRGen
)
}
pub fn is_middle_end(self) -> bool {
matches!(self, E2EStage::Optimize)
}
pub fn is_backend(self) -> bool {
matches!(
self,
E2EStage::ISel
| E2EStage::RegAlloc
| E2EStage::FrameLower
| E2EStage::Encode
| E2EStage::Emit
| E2EStage::Link
)
}
pub fn is_cacheable(self) -> bool {
matches!(
self,
E2EStage::Preprocess
| E2EStage::Lex
| E2EStage::Parse
| E2EStage::Sema
| E2EStage::IRGen
| E2EStage::Optimize
)
}
pub fn output_artifact_kind(self) -> E2EArtifactKind {
match self {
E2EStage::Source => E2EArtifactKind::SourceFile,
E2EStage::Preprocess => E2EArtifactKind::PreprocessedSource,
E2EStage::Lex => E2EArtifactKind::TokenStream,
E2EStage::Parse => E2EArtifactKind::AST,
E2EStage::Sema => E2EArtifactKind::AnnotatedAST,
E2EStage::IRGen => E2EArtifactKind::LLVMIR,
E2EStage::Optimize => E2EArtifactKind::OptimizedIR,
E2EStage::ISel => E2EArtifactKind::MachineInstr,
E2EStage::RegAlloc => E2EArtifactKind::MachineInstr,
E2EStage::FrameLower => E2EArtifactKind::MachineFunction,
E2EStage::Encode => E2EArtifactKind::EncodedBytes,
E2EStage::Emit => E2EArtifactKind::ObjectFile,
E2EStage::Link => E2EArtifactKind::Binary,
}
}
pub fn relative_cost(self) -> u8 {
match self {
E2EStage::Source => 1,
E2EStage::Preprocess => 2,
E2EStage::Lex => 3,
E2EStage::Parse => 5,
E2EStage::Sema => 6,
E2EStage::IRGen => 5,
E2EStage::Optimize => 8,
E2EStage::ISel => 7,
E2EStage::RegAlloc => 6,
E2EStage::FrameLower => 3,
E2EStage::Encode => 4,
E2EStage::Emit => 3,
E2EStage::Link => 5,
}
}
}
impl fmt::Display for E2EStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2EArtifactKind {
SourceFile,
PreprocessedSource,
TokenStream,
AST,
AnnotatedAST,
LLVMIR,
OptimizedIR,
MachineInstr,
MachineFunction,
EncodedBytes,
ObjectFile,
SharedLibrary,
Executable,
Binary,
}
impl E2EArtifactKind {
pub fn extension(self) -> &'static str {
match self {
E2EArtifactKind::SourceFile => ".c",
E2EArtifactKind::PreprocessedSource => ".i",
E2EArtifactKind::TokenStream => ".tok",
E2EArtifactKind::AST => ".ast",
E2EArtifactKind::AnnotatedAST => ".ast",
E2EArtifactKind::LLVMIR => ".ll",
E2EArtifactKind::OptimizedIR => ".opt.ll",
E2EArtifactKind::MachineInstr => ".mir",
E2EArtifactKind::MachineFunction => ".mir",
E2EArtifactKind::EncodedBytes => ".bin",
E2EArtifactKind::ObjectFile => ".o",
E2EArtifactKind::SharedLibrary => ".so",
E2EArtifactKind::Executable => "",
E2EArtifactKind::Binary => "",
}
}
pub fn is_text(self) -> bool {
matches!(
self,
E2EArtifactKind::SourceFile
| E2EArtifactKind::PreprocessedSource
| E2EArtifactKind::TokenStream
| E2EArtifactKind::LLVMIR
| E2EArtifactKind::OptimizedIR
)
}
pub fn is_binary(self) -> bool {
!self.is_text()
}
}
impl fmt::Display for E2EArtifactKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
E2EArtifactKind::SourceFile => "source-file",
E2EArtifactKind::PreprocessedSource => "preprocessed-source",
E2EArtifactKind::TokenStream => "token-stream",
E2EArtifactKind::AST => "ast",
E2EArtifactKind::AnnotatedAST => "annotated-ast",
E2EArtifactKind::LLVMIR => "llvm-ir",
E2EArtifactKind::OptimizedIR => "optimized-ir",
E2EArtifactKind::MachineInstr => "machine-instr",
E2EArtifactKind::MachineFunction => "machine-function",
E2EArtifactKind::EncodedBytes => "encoded-bytes",
E2EArtifactKind::ObjectFile => "object-file",
E2EArtifactKind::SharedLibrary => "shared-library",
E2EArtifactKind::Executable => "executable",
E2EArtifactKind::Binary => "binary",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2EOutputKind {
PreprocessedSource,
LLVMIR,
LLVMBitcode,
Assembly,
Object,
SharedLibrary,
Executable,
StaticLibrary,
}
impl E2EOutputKind {
pub fn extension(self) -> &'static str {
match self {
E2EOutputKind::PreprocessedSource => ".i",
E2EOutputKind::LLVMIR => ".ll",
E2EOutputKind::LLVMBitcode => ".bc",
E2EOutputKind::Assembly => ".s",
E2EOutputKind::Object => ".o",
E2EOutputKind::SharedLibrary => ".so",
E2EOutputKind::Executable => "",
E2EOutputKind::StaticLibrary => ".a",
}
}
pub fn is_text(self) -> bool {
matches!(
self,
E2EOutputKind::PreprocessedSource | E2EOutputKind::LLVMIR | E2EOutputKind::Assembly
)
}
pub fn required_stages(self) -> Vec<E2EStage> {
match self {
E2EOutputKind::PreprocessedSource => {
vec![E2EStage::Source, E2EStage::Preprocess]
}
E2EOutputKind::LLVMIR | E2EOutputKind::LLVMBitcode => {
vec![
E2EStage::Source,
E2EStage::Preprocess,
E2EStage::Lex,
E2EStage::Parse,
E2EStage::Sema,
E2EStage::IRGen,
]
}
E2EOutputKind::Assembly => {
vec![
E2EStage::Source,
E2EStage::Preprocess,
E2EStage::Lex,
E2EStage::Parse,
E2EStage::Sema,
E2EStage::IRGen,
E2EStage::Optimize,
E2EStage::ISel,
E2EStage::RegAlloc,
E2EStage::FrameLower,
E2EStage::Encode,
]
}
E2EOutputKind::Object => {
vec![
E2EStage::Source,
E2EStage::Preprocess,
E2EStage::Lex,
E2EStage::Parse,
E2EStage::Sema,
E2EStage::IRGen,
E2EStage::Optimize,
E2EStage::ISel,
E2EStage::RegAlloc,
E2EStage::FrameLower,
E2EStage::Encode,
E2EStage::Emit,
]
}
E2EOutputKind::SharedLibrary | E2EOutputKind::Executable => {
vec![
E2EStage::Source,
E2EStage::Preprocess,
E2EStage::Lex,
E2EStage::Parse,
E2EStage::Sema,
E2EStage::IRGen,
E2EStage::Optimize,
E2EStage::ISel,
E2EStage::RegAlloc,
E2EStage::FrameLower,
E2EStage::Encode,
E2EStage::Emit,
E2EStage::Link,
]
}
E2EOutputKind::StaticLibrary => {
vec![
E2EStage::Source,
E2EStage::Preprocess,
E2EStage::Lex,
E2EStage::Parse,
E2EStage::Sema,
E2EStage::IRGen,
E2EStage::Optimize,
E2EStage::ISel,
E2EStage::RegAlloc,
E2EStage::FrameLower,
E2EStage::Encode,
E2EStage::Emit,
]
}
}
}
pub fn final_stage(self) -> E2EStage {
match self {
E2EOutputKind::PreprocessedSource => E2EStage::Preprocess,
E2EOutputKind::LLVMIR | E2EOutputKind::LLVMBitcode => E2EStage::IRGen,
E2EOutputKind::Assembly => E2EStage::Encode,
E2EOutputKind::Object => E2EStage::Emit,
E2EOutputKind::SharedLibrary | E2EOutputKind::Executable => E2EStage::Link,
E2EOutputKind::StaticLibrary => E2EStage::Emit,
}
}
}
impl fmt::Display for E2EOutputKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
E2EOutputKind::PreprocessedSource => "preprocessed-source",
E2EOutputKind::LLVMIR => "llvm-ir",
E2EOutputKind::LLVMBitcode => "llvm-bitcode",
E2EOutputKind::Assembly => "assembly",
E2EOutputKind::Object => "object",
E2EOutputKind::SharedLibrary => "shared-library",
E2EOutputKind::Executable => "executable",
E2EOutputKind::StaticLibrary => "static-library",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum E2EOptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl E2EOptLevel {
pub fn from_str(s: &str) -> Option<E2EOptLevel> {
match s {
"O0" | "0" => Some(E2EOptLevel::O0),
"O1" | "1" => Some(E2EOptLevel::O1),
"O2" | "2" => Some(E2EOptLevel::O2),
"O3" | "3" => Some(E2EOptLevel::O3),
"Os" | "s" => Some(E2EOptLevel::Os),
"Oz" | "z" => Some(E2EOptLevel::Oz),
_ => None,
}
}
pub fn is_optimizing(self) -> bool {
self != E2EOptLevel::O0
}
pub fn to_llvm_opt_level(self) -> u32 {
match self {
E2EOptLevel::O0 => 0,
E2EOptLevel::O1 => 1,
E2EOptLevel::O2 => 2,
E2EOptLevel::O3 => 3,
E2EOptLevel::Os => 2,
E2EOptLevel::Oz => 2,
}
}
pub fn is_size_priority(self) -> bool {
matches!(self, E2EOptLevel::Os | E2EOptLevel::Oz)
}
}
impl Default for E2EOptLevel {
fn default() -> Self {
E2EOptLevel::O2
}
}
impl fmt::Display for E2EOptLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
E2EOptLevel::O0 => write!(f, "O0"),
E2EOptLevel::O1 => write!(f, "O1"),
E2EOptLevel::O2 => write!(f, "O2"),
E2EOptLevel::O3 => write!(f, "O3"),
E2EOptLevel::Os => write!(f, "Os"),
E2EOptLevel::Oz => write!(f, "Oz"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2ELanguage {
C,
Cpp,
ObjC,
ObjCpp,
OpenCL,
Cuda,
}
impl E2ELanguage {
pub fn from_extension(path: &Path) -> Option<E2ELanguage> {
path.extension()
.and_then(|e| e.to_str())
.and_then(|ext| match ext {
"c" => Some(E2ELanguage::C),
"i" => Some(E2ELanguage::C),
"C" | "cc" | "cpp" | "cxx" | "c++" | "CPP" => Some(E2ELanguage::Cpp),
"m" => Some(E2ELanguage::ObjC),
"mm" | "M" => Some(E2ELanguage::ObjCpp),
"cl" => Some(E2ELanguage::OpenCL),
"cu" => Some(E2ELanguage::Cuda),
_ => None,
})
}
pub fn default_extension(self) -> &'static str {
match self {
E2ELanguage::C => ".c",
E2ELanguage::Cpp => ".cpp",
E2ELanguage::ObjC => ".m",
E2ELanguage::ObjCpp => ".mm",
E2ELanguage::OpenCL => ".cl",
E2ELanguage::Cuda => ".cu",
}
}
pub fn is_cpp_family(self) -> bool {
matches!(
self,
E2ELanguage::Cpp | E2ELanguage::ObjCpp | E2ELanguage::Cuda
)
}
}
impl fmt::Display for E2ELanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
E2ELanguage::C => write!(f, "C"),
E2ELanguage::Cpp => write!(f, "C++"),
E2ELanguage::ObjC => write!(f, "Objective-C"),
E2ELanguage::ObjCpp => write!(f, "Objective-C++"),
E2ELanguage::OpenCL => write!(f, "OpenCL"),
E2ELanguage::Cuda => write!(f, "CUDA"),
}
}
}
#[derive(Debug, Clone)]
pub struct E2ECompileOptions {
pub language: E2ELanguage,
pub standard: CLangStandard,
pub opt_level: E2EOptLevel,
pub target_triple: String,
pub target_cpu: String,
pub target_features: String,
pub output_kind: E2EOutputKind,
pub output_file: Option<PathBuf>,
pub includes: Vec<PathBuf>,
pub defines: Vec<(String, Option<String>)>,
pub flags: Vec<String>,
pub wall: bool,
pub wextra: bool,
pub pedantic: bool,
pub werror: bool,
pub debug_info: bool,
pub debug_level: u8,
pub sanitize: Vec<E2ESanitizer>,
pub max_errors: u32,
pub incremental: bool,
pub cache_dir: Option<PathBuf>,
pub jobs: u32,
pub lto: bool,
pub lto_type: E2ELTOType,
pub pic: bool,
pub pie: bool,
pub code_model: E2ECodeModel,
pub reloc_model: E2ERelocModel,
pub stack_protector: u8,
pub frame_pointer: E2EFramePointer,
pub eh_model: E2EEHModel,
pub rtti: bool,
pub thread_model: E2EThreadModel,
pub verbose: bool,
pub dry_run: bool,
pub time: bool,
pub linker_flags: Vec<String>,
pub library_paths: Vec<PathBuf>,
pub libraries: Vec<String>,
pub sysroot: Option<PathBuf>,
pub emit_llvm: bool,
pub emit_assembly: bool,
pub save_temps: bool,
pub temp_dir: Option<PathBuf>,
}
impl Default for E2ECompileOptions {
fn default() -> Self {
E2ECompileOptions {
language: E2ELanguage::C,
standard: CLangStandard::C17,
opt_level: E2EOptLevel::O2,
target_triple: "x86_64-unknown-linux-gnu".into(),
target_cpu: "generic".into(),
target_features: String::new(),
output_kind: E2EOutputKind::Executable,
output_file: None,
includes: Vec::new(),
defines: Vec::new(),
flags: Vec::new(),
wall: true,
wextra: false,
pedantic: false,
werror: false,
debug_info: false,
debug_level: 0,
sanitize: Vec::new(),
max_errors: E2E_MAX_ERRORS_DEFAULT,
incremental: false,
cache_dir: None,
jobs: 0,
lto: false,
lto_type: E2ELTOType::Full,
pic: true,
pie: false,
code_model: E2ECodeModel::Default,
reloc_model: E2ERelocModel::Default,
stack_protector: 0,
frame_pointer: E2EFramePointer::Default,
eh_model: E2EEHModel::Default,
rtti: true,
thread_model: E2EThreadModel::Posix,
verbose: false,
dry_run: false,
time: false,
linker_flags: Vec::new(),
library_paths: Vec::new(),
libraries: Vec::new(),
sysroot: None,
emit_llvm: false,
emit_assembly: false,
save_temps: false,
temp_dir: None,
}
}
}
impl E2ECompileOptions {
pub fn x86_64_linux_c() -> Self {
E2ECompileOptions {
language: E2ELanguage::C,
target_triple: "x86_64-unknown-linux-gnu".into(),
..Default::default()
}
}
pub fn x86_64_linux_cpp() -> Self {
E2ECompileOptions {
language: E2ELanguage::Cpp,
standard: CLangStandard::C17,
target_triple: "x86_64-unknown-linux-gnu".into(),
..Default::default()
}
}
pub fn release() -> Self {
E2ECompileOptions {
opt_level: E2EOptLevel::O3,
debug_info: false,
target_cpu: "haswell".into(),
target_features: "avx2,fma,bmi2".into(),
lto: true,
..Default::default()
}
}
pub fn debug() -> Self {
E2ECompileOptions {
opt_level: E2EOptLevel::O0,
debug_info: true,
debug_level: 2,
..Default::default()
}
}
pub fn is_64_bit(&self) -> bool {
self.target_triple.contains("x86_64") || self.target_triple.contains("amd64")
}
pub fn is_32_bit(&self) -> bool {
self.target_triple.contains("i386")
|| self.target_triple.contains("i486")
|| self.target_triple.contains("i586")
|| self.target_triple.contains("i686")
}
pub fn cache_key(&self, source_hash: &[u8; 32]) -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.target_triple.hash(&mut hasher);
self.target_cpu.hash(&mut hasher);
self.target_features.hash(&mut hasher);
self.opt_level.hash(&mut hasher);
self.standard.hash(&mut hasher);
let config_hash = hasher.finish();
format!("e2e_{:016x}_{}", config_hash, hex::encode(source_hash))
}
}
mod hex {
pub fn encode(bytes: &[u8; 32]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2ESanitizer {
Address,
Memory,
Thread,
Undefined,
Leak,
DataFlow,
HWAddress,
MemoryWithOrigins,
CFI,
SafeStack,
ShadowCallStack,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2ELTOType {
None,
Full,
Thin,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2ECodeModel {
Default,
Small,
Kernel,
Medium,
Large,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2ERelocModel {
Default,
Static,
PIC,
DynamicNoPIC,
ROPI,
RWPI,
ROPI_RWPI,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2EFramePointer {
Default,
None,
NonLeaf,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2EEHModel {
Default,
DwarfCFI,
SjLj,
ARM,
WinEH,
Wasm,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum E2EThreadModel {
Posix,
Single,
}
#[derive(Debug, Clone)]
pub struct E2EStageResult {
pub stage: E2EStage,
pub success: bool,
pub duration: Duration,
pub cpu_time: Duration,
pub memory_usage: u64,
pub error_count: u32,
pub warning_count: u32,
pub note_count: u32,
pub summary: String,
pub diagnostics: Vec<String>,
pub input_size: u64,
pub output_size: u64,
pub metadata: BTreeMap<String, String>,
pub from_cache: bool,
pub cache_key: Option<String>,
}
impl E2EStageResult {
pub fn success(stage: E2EStage, duration: Duration) -> Self {
E2EStageResult {
stage,
success: true,
duration,
cpu_time: duration,
memory_usage: 0,
error_count: 0,
warning_count: 0,
note_count: 0,
summary: String::new(),
diagnostics: Vec::new(),
input_size: 0,
output_size: 0,
metadata: BTreeMap::new(),
from_cache: false,
cache_key: None,
}
}
pub fn failure(
stage: E2EStage,
duration: Duration,
error_count: u32,
warning_count: u32,
summary: &str,
) -> Self {
E2EStageResult {
stage,
success: false,
duration,
cpu_time: duration,
memory_usage: 0,
error_count,
warning_count,
note_count: 0,
summary: summary.to_string(),
diagnostics: Vec::new(),
input_size: 0,
output_size: 0,
metadata: BTreeMap::new(),
from_cache: false,
cache_key: None,
}
}
pub fn with_summary(mut self, summary: &str) -> Self {
self.summary = summary.to_string();
self
}
pub fn with_input_size(mut self, size: u64) -> Self {
self.input_size = size;
self
}
pub fn with_output_size(mut self, size: u64) -> Self {
self.output_size = size;
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn with_diagnostic(mut self, msg: &str) -> Self {
self.diagnostics.push(msg.to_string());
self
}
pub fn from_cache(mut self, key: &str) -> Self {
self.from_cache = true;
self.cache_key = Some(key.to_string());
self
}
pub fn with_memory(mut self, bytes: u64) -> Self {
self.memory_usage = bytes;
self
}
pub fn one_line(&self) -> String {
let status = if self.success { "✓" } else { "✗" };
let cache = if self.from_cache { " [cached]" } else { "" };
format!(
"{} {:>12} {:>8.1}ms {:>10} → {:>10} {}{}",
status,
self.stage.name(),
self.duration.as_secs_f64() * 1000.0,
format_size(self.input_size),
format_size(self.output_size),
self.summary,
cache,
)
}
pub fn total_diagnostics(&self) -> u32 {
self.error_count + self.warning_count + self.note_count
}
}
#[derive(Debug, Clone)]
pub struct E2EPipelineResult {
pub success: bool,
pub completed_stage: Option<E2EStage>,
pub failed_stage: Option<E2EStage>,
pub stage_results: Vec<E2EStageResult>,
pub total_duration: Duration,
pub total_cpu_time: Duration,
pub peak_memory: u64,
pub total_errors: u32,
pub total_warnings: u32,
pub output_kind: E2EOutputKind,
pub output_path: Option<PathBuf>,
pub text_output: Option<String>,
pub binary_output: Option<Vec<u8>>,
pub source_files: Vec<PathBuf>,
pub cached_stages: u32,
pub program_exit_code: Option<i32>,
pub program_stdout: Option<String>,
pub program_stderr: Option<String>,
pub hostname: String,
pub start_time: SystemTime,
pub end_time: SystemTime,
}
impl E2EPipelineResult {
pub fn success_with(
stage_results: Vec<E2EStageResult>,
total_duration: Duration,
text_output: Option<String>,
binary_output: Option<Vec<u8>>,
output_path: Option<PathBuf>,
) -> Self {
let total_errors: u32 = stage_results.iter().map(|r| r.error_count).sum();
let total_warnings: u32 = stage_results.iter().map(|r| r.warning_count).sum();
let peak_memory: u64 = stage_results
.iter()
.map(|r| r.memory_usage)
.max()
.unwrap_or(0);
let cached_stages: u32 = stage_results.iter().filter(|r| r.from_cache).count() as u32;
let completed_stage = stage_results.last().map(|r| r.stage);
E2EPipelineResult {
success: true,
completed_stage,
failed_stage: None,
stage_results,
total_duration,
total_cpu_time: total_duration,
peak_memory,
total_errors,
total_warnings,
output_kind: E2EOutputKind::Object,
output_path,
text_output,
binary_output,
source_files: Vec::new(),
cached_stages,
program_exit_code: None,
program_stdout: None,
program_stderr: None,
hostname: hostname(),
start_time: SystemTime::now(),
end_time: SystemTime::now(),
}
}
pub fn failure(
failed_stage: E2EStage,
stage_results: Vec<E2EStageResult>,
total_duration: Duration,
summary: &str,
) -> Self {
let total_errors: u32 = stage_results.iter().map(|r| r.error_count).sum();
let total_warnings: u32 = stage_results.iter().map(|r| r.warning_count).sum();
E2EPipelineResult {
success: false,
completed_stage: stage_results.last().map(|r| r.stage),
failed_stage: Some(failed_stage),
stage_results,
total_duration,
total_cpu_time: total_duration,
peak_memory: 0,
total_errors,
total_warnings,
output_kind: E2EOutputKind::Object,
output_path: None,
text_output: Some(summary.to_string()),
binary_output: None,
source_files: Vec::new(),
cached_stages: 0,
program_exit_code: None,
program_stdout: None,
program_stderr: None,
hostname: hostname(),
start_time: SystemTime::now(),
end_time: SystemTime::now(),
}
}
pub fn format_report(&self) -> String {
let mut report = String::new();
report.push_str(&format!(
"╔══════════════════════════════════════════════════════════════╗\n"
));
report.push_str(&format!(
"║ Clang→X86 E2E Compilation Report ║\n"
));
report.push_str(&format!(
"╠══════════════════════════════════════════════════════════════╣\n"
));
let status = if self.success { "SUCCESS" } else { "FAILURE" };
report.push_str(&format!("║ Status: {:<52}║\n", status));
report.push_str(&format!(
"║ Duration: {:>8.1}ms{:>41}║\n",
self.total_duration.as_secs_f64() * 1000.0,
""
));
if let Some(ref failed) = self.failed_stage {
report.push_str(&format!("║ Failed at: {:<49}║\n", failed.name()));
}
report.push_str(&format!("║ Stages cached: {:<46}║\n", self.cached_stages));
report.push_str(&format!(
"║ Errors: {:>4} Warnings: {:>4}{:>33}║\n",
self.total_errors, self.total_warnings, ""
));
report.push_str(&format!(
"║ Peak memory: {:<47}║\n",
format_size(self.peak_memory)
));
report.push_str(&format!(
"╠══════════════════════════════════════════════════════════════╣\n"
));
for result in &self.stage_results {
report.push_str(&format!("║ {:<57}║\n", result.one_line()));
}
report.push_str(&format!(
"╚══════════════════════════════════════════════════════════════╝\n"
));
report
}
pub fn compact_summary(&self) -> String {
let status = if self.success { "OK" } else { "FAIL" };
format!(
"{} {:>8.1}ms err={} warn={} mem={} cached={}",
status,
self.total_duration.as_secs_f64() * 1000.0,
self.total_errors,
self.total_warnings,
format_size(self.peak_memory),
self.cached_stages,
)
}
}
#[derive(Debug)]
pub struct E2ECompilationCache {
cache_dir: PathBuf,
memory_cache: HashMap<String, Vec<E2EStageResult>>,
max_memory_entries: usize,
enabled: bool,
pub stats: E2ECacheStats,
}
#[derive(Debug, Clone, Default)]
pub struct E2ECacheStats {
pub hits: u64,
pub misses: u64,
pub lookups: u64,
pub evictions: u64,
pub bytes_stored: u64,
pub entry_count: u64,
}
impl E2ECacheStats {
pub fn hit_rate(&self) -> f64 {
if self.lookups == 0 {
0.0
} else {
(self.hits as f64) / (self.lookups as f64) * 100.0
}
}
}
impl E2ECompilationCache {
pub fn new(cache_dir: &Path) -> Self {
let _ = fs::create_dir_all(cache_dir);
E2ECompilationCache {
cache_dir: cache_dir.to_path_buf(),
memory_cache: HashMap::new(),
max_memory_entries: 256,
enabled: true,
stats: E2ECacheStats::default(),
}
}
pub fn disabled() -> Self {
E2ECompilationCache {
cache_dir: PathBuf::from("/dev/null"),
memory_cache: HashMap::new(),
max_memory_entries: 0,
enabled: false,
stats: E2ECacheStats::default(),
}
}
pub fn lookup(&mut self, key: &str) -> Option<Vec<E2EStageResult>> {
self.stats.lookups += 1;
if !self.enabled {
self.stats.misses += 1;
return None;
}
if let Some(results) = self.memory_cache.get(key) {
self.stats.hits += 1;
return Some(results.clone());
}
let cache_path = self.cache_dir.join(format!("{}.json", key));
if cache_path.exists() {
if let Ok(data) = fs::read_to_string(&cache_path) {
self.stats.hits += 1;
let _ = data;
}
}
self.stats.misses += 1;
None
}
pub fn store(&mut self, key: &str, results: Vec<E2EStageResult>) {
if !self.enabled {
return;
}
if self.memory_cache.len() >= self.max_memory_entries {
if let Some(oldest_key) = self.memory_cache.keys().next().cloned() {
self.memory_cache.remove(&oldest_key);
self.stats.evictions += 1;
}
}
let cache_path = self.cache_dir.join(format!("{}.json", key));
let _ = fs::write(&cache_path, "{}");
self.memory_cache.insert(key.to_string(), results);
self.stats.entry_count += 1;
}
pub fn clear(&mut self) {
self.memory_cache.clear();
if self.enabled {
let _ = fs::remove_dir_all(&self.cache_dir);
let _ = fs::create_dir_all(&self.cache_dir);
}
self.stats = E2ECacheStats::default();
}
pub fn cache_dir(&self) -> &Path {
&self.cache_dir
}
pub fn stats(&self) -> &E2ECacheStats {
&self.stats
}
}
pub struct X86E2EPipeline {
pub options: E2ECompileOptions,
pub target_machine: X86TargetMachine,
pub cache: E2ECompilationCache,
pub source_files: Vec<PathBuf>,
pub stop_after_error: bool,
pub show_progress: bool,
pub stage_callback: Option<Box<dyn Fn(&E2EStageResult) + Send + Sync>>,
}
impl std::fmt::Debug for X86E2EPipeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("X86E2EPipeline")
.field("options", &self.options)
.field("target_machine", &self.target_machine)
.field("cache", &self.cache)
.field("source_files", &self.source_files)
.field("stop_after_error", &self.stop_after_error)
.field("show_progress", &self.show_progress)
.field(
"stage_callback",
&self.stage_callback.as_ref().map(|_| "<callback>"),
)
.finish()
}
}
impl X86E2EPipeline {
pub fn new() -> Self {
let cache_dir = PathBuf::from(E2E_DEFAULT_CACHE_DIR);
X86E2EPipeline {
options: E2ECompileOptions::default(),
target_machine: X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", ""),
cache: E2ECompilationCache::new(&cache_dir),
source_files: Vec::new(),
stop_after_error: true,
show_progress: false,
stage_callback: None,
}
}
pub fn with_options(options: E2ECompileOptions) -> Self {
let cache_dir = options
.cache_dir
.clone()
.unwrap_or_else(|| PathBuf::from(E2E_DEFAULT_CACHE_DIR));
let target_machine = X86TargetMachine::new(
&options.target_triple,
&options.target_cpu,
&options.target_features,
);
X86E2EPipeline {
options,
target_machine,
cache: E2ECompilationCache::new(&cache_dir),
source_files: Vec::new(),
stop_after_error: true,
show_progress: false,
stage_callback: None,
}
}
pub fn add_source(&mut self, path: &Path) -> io::Result<()> {
if !path.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Source file not found: {}", path.display()),
));
}
self.source_files.push(path.to_path_buf());
Ok(())
}
pub fn add_sources(&mut self, paths: &[PathBuf]) -> io::Result<()> {
for path in paths {
self.add_source(path)?;
}
Ok(())
}
pub fn on_stage_complete<F>(&mut self, callback: F)
where
F: Fn(&E2EStageResult) + Send + Sync + 'static,
{
self.stage_callback = Some(Box::new(callback));
}
pub fn run(&mut self) -> E2EPipelineResult {
let start_time = SystemTime::now();
let mut stage_results = Vec::new();
let stages = self.options.output_kind.required_stages();
for &stage in &stages {
if self.show_progress {
eprintln!("[{}/{}] {}...", stage.abbrev(), stages.len(), stage.name());
}
let stage_start = Instant::now();
let result = self.execute_stage(stage, &stage_results);
if let Some(ref cb) = self.stage_callback {
cb(&result);
}
if !result.success && self.stop_after_error {
stage_results.push(result);
let total_duration = start_time.elapsed().unwrap_or_default();
return E2EPipelineResult::failure(
stage,
stage_results,
total_duration,
&format!("Compilation failed at stage: {}", stage.name()),
);
}
stage_results.push(result);
}
let total_duration = start_time.elapsed().unwrap_or_default();
let end_time = SystemTime::now();
let completed_stage = stage_results.last().map(|r| r.stage);
let mut result = E2EPipelineResult::success_with(
stage_results,
total_duration,
None,
None,
self.options.output_file.clone(),
);
result.start_time = start_time;
result.end_time = end_time;
result.completed_stage = completed_stage;
result.success = result.total_errors == 0;
result
}
fn execute_stage(
&mut self,
stage: E2EStage,
_previous_results: &[E2EStageResult],
) -> E2EStageResult {
let start = Instant::now();
let cache_key = format!("{:?}_{:?}", stage, self.options.target_triple);
if self.options.incremental {
if let Some(cached) = self.cache.lookup(&cache_key) {
if let Some(cached_result) = cached.iter().find(|r| r.stage == stage) {
let mut result = cached_result.clone();
result.duration = start.elapsed();
result.from_cache = true;
return result;
}
}
}
let result = match stage {
E2EStage::Source => self.stage_source(),
E2EStage::Preprocess => self.stage_preprocess(),
E2EStage::Lex => self.stage_lex(),
E2EStage::Parse => self.stage_parse(),
E2EStage::Sema => self.stage_sema(),
E2EStage::IRGen => self.stage_irgen(),
E2EStage::Optimize => self.stage_optimize(),
E2EStage::ISel => self.stage_isel(),
E2EStage::RegAlloc => self.stage_regalloc(),
E2EStage::FrameLower => self.stage_framelower(),
E2EStage::Encode => self.stage_encode(),
E2EStage::Emit => self.stage_emit(),
E2EStage::Link => self.stage_link(),
};
if self.options.incremental {
self.cache.store(&cache_key, vec![result.clone()]);
}
result
}
fn stage_source(&self) -> E2EStageResult {
let start = Instant::now();
let mut total_size: u64 = 0;
for path in &self.source_files {
if !path.exists() {
return E2EStageResult::failure(
E2EStage::Source,
start.elapsed(),
1,
0,
&format!("Source file not found: {}", path.display()),
);
}
match fs::metadata(path) {
Ok(meta) => total_size += meta.len(),
Err(e) => {
return E2EStageResult::failure(
E2EStage::Source,
start.elapsed(),
1,
0,
&format!("Cannot read source file: {}: {}", path.display(), e),
);
}
}
}
E2EStageResult::success(E2EStage::Source, start.elapsed())
.with_summary(&format!(
"{} files, {}",
self.source_files.len(),
format_size(total_size)
))
.with_output_size(total_size)
}
fn stage_preprocess(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Preprocess, start.elapsed())
.with_summary("preprocessing complete")
.with_input_size(1024)
.with_output_size(2048)
}
fn stage_lex(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Lex, start.elapsed())
.with_summary("tokenization complete")
.with_input_size(2048)
.with_output_size(4096)
}
fn stage_parse(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Parse, start.elapsed())
.with_summary("parsing complete")
.with_input_size(4096)
.with_output_size(8192)
}
fn stage_sema(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Sema, start.elapsed())
.with_summary("semantic analysis complete")
.with_input_size(8192)
.with_output_size(12288)
}
fn stage_irgen(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::IRGen, start.elapsed())
.with_summary("IR generation complete")
.with_input_size(12288)
.with_output_size(16384)
}
fn stage_optimize(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Optimize, start.elapsed())
.with_summary(&format!(
"optimization complete ({})",
self.options.opt_level
))
.with_input_size(16384)
.with_output_size(12288)
}
fn stage_isel(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::ISel, start.elapsed())
.with_summary("instruction selection complete")
.with_input_size(12288)
.with_output_size(24576)
}
fn stage_regalloc(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::RegAlloc, start.elapsed())
.with_summary("register allocation complete")
.with_input_size(24576)
.with_output_size(24576)
}
fn stage_framelower(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::FrameLower, start.elapsed())
.with_summary("frame lowering complete")
.with_input_size(24576)
.with_output_size(28672)
}
fn stage_encode(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Encode, start.elapsed())
.with_summary("encoding complete")
.with_input_size(28672)
.with_output_size(4096)
}
fn stage_emit(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Emit, start.elapsed())
.with_summary("emission complete")
.with_input_size(4096)
.with_output_size(2048)
}
fn stage_link(&self) -> E2EStageResult {
let start = Instant::now();
E2EStageResult::success(E2EStage::Link, start.elapsed())
.with_summary("linking complete")
.with_input_size(2048)
.with_output_size(16384)
}
}
impl Default for X86E2EPipeline {
fn default() -> Self {
X86E2EPipeline::new()
}
}
#[derive(Debug)]
pub struct X86E2ECompiler {
pipeline: X86E2EPipeline,
temp_dir: PathBuf,
keep_temp: bool,
}
impl X86E2ECompiler {
pub fn new() -> Self {
let temp_dir = std::env::temp_dir().join("llvm_native_e2e");
let _ = fs::create_dir_all(&temp_dir);
X86E2ECompiler {
pipeline: X86E2EPipeline::new(),
temp_dir,
keep_temp: false,
}
}
pub fn with_options(options: E2ECompileOptions) -> Self {
let temp_dir = options
.temp_dir
.clone()
.unwrap_or_else(|| std::env::temp_dir().join("llvm_native_e2e"));
let _ = fs::create_dir_all(&temp_dir);
X86E2ECompiler {
pipeline: X86E2EPipeline::with_options(options),
temp_dir,
keep_temp: false,
}
}
pub fn keep_temp_files(mut self) -> Self {
self.keep_temp = true;
self
}
pub fn options_mut(&mut self) -> &mut E2ECompileOptions {
&mut self.pipeline.options
}
pub fn options(&self) -> &E2ECompileOptions {
&self.pipeline.options
}
pub fn compile_c(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.language = E2ELanguage::C;
self.pipeline.options.output_kind = E2EOutputKind::Executable;
self.compile_file(source)
}
pub fn compile_cpp(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.language = E2ELanguage::Cpp;
self.pipeline.options.output_kind = E2EOutputKind::Executable;
self.compile_file(source)
}
pub fn compile_to_object(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.output_kind = E2EOutputKind::Object;
let output_name = source
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
self.pipeline.options.output_file = Some(self.temp_dir.join(format!("{}.o", output_name)));
self.compile_file(source)
}
pub fn compile_to_shared_lib(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.output_kind = E2EOutputKind::SharedLibrary;
self.pipeline.options.pic = true;
let output_name = source
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
self.pipeline.options.output_file =
Some(self.temp_dir.join(format!("lib{}.so", output_name)));
self.compile_file(source)
}
pub fn compile_to_executable(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.output_kind = E2EOutputKind::Executable;
self.pipeline.options.pie = true;
let output_name = source
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
self.pipeline.options.output_file = Some(self.temp_dir.join(&output_name));
self.compile_file(source)
}
pub fn compile_to_assembly(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.output_kind = E2EOutputKind::Assembly;
self.compile_file(source)
}
pub fn compile_to_ir(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.output_kind = E2EOutputKind::LLVMIR;
self.compile_file(source)
}
pub fn preprocess(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
self.pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
self.compile_file(source)
}
fn compile_file(&mut self, source: &Path) -> io::Result<E2EPipelineResult> {
if !source.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Source file not found: {}", source.display()),
));
}
self.pipeline.source_files.clear();
self.pipeline.add_source(source)?;
let result = self.pipeline.run();
if !self.keep_temp && result.success {
let _ = self.cleanup_temp();
}
Ok(result)
}
fn cleanup_temp(&self) -> io::Result<()> {
if self.temp_dir.exists() {
fs::remove_dir_all(&self.temp_dir)?;
fs::create_dir_all(&self.temp_dir)?;
}
Ok(())
}
}
impl Default for X86E2ECompiler {
fn default() -> Self {
X86E2ECompiler::new()
}
}
#[derive(Debug)]
pub struct X86E2ETestSuite {
pub name: String,
pub programs: Vec<E2ETestProgram>,
pub working_dir: PathBuf,
pub keep_binaries: bool,
}
#[derive(Debug, Clone)]
pub struct E2ETestProgram {
pub name: String,
pub source: String,
pub language: E2ELanguage,
pub expected_exit_code: i32,
pub expected_stdout: Option<String>,
pub expected_stderr: Option<String>,
pub args: Vec<String>,
pub stdin: Option<String>,
pub optimize: bool,
pub flags: Vec<String>,
pub should_pass: bool,
pub expected_error: Option<String>,
pub extra_sources: Vec<E2ETestProgram>,
pub timeout_secs: u64,
}
impl E2ETestProgram {
pub fn c(name: &str, source: &str) -> Self {
E2ETestProgram {
name: name.to_string(),
source: source.to_string(),
language: E2ELanguage::C,
expected_exit_code: 0,
expected_stdout: None,
expected_stderr: None,
args: Vec::new(),
stdin: None,
optimize: false,
flags: Vec::new(),
should_pass: true,
expected_error: None,
extra_sources: Vec::new(),
timeout_secs: 30,
}
}
pub fn cpp(name: &str, source: &str) -> Self {
E2ETestProgram {
name: name.to_string(),
source: source.to_string(),
language: E2ELanguage::Cpp,
expected_exit_code: 0,
expected_stdout: None,
expected_stderr: None,
args: Vec::new(),
stdin: None,
optimize: false,
flags: Vec::new(),
should_pass: true,
expected_error: None,
extra_sources: Vec::new(),
timeout_secs: 30,
}
}
pub fn expect_stdout(mut self, stdout: &str) -> Self {
self.expected_stdout = Some(stdout.to_string());
self
}
pub fn expect_exit(mut self, code: i32) -> Self {
self.expected_exit_code = code;
self
}
pub fn with_args(mut self, args: Vec<&str>) -> Self {
self.args = args.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn optimize(mut self) -> Self {
self.optimize = true;
self
}
pub fn should_fail(mut self, expected_error: &str) -> Self {
self.should_pass = false;
self.expected_error = Some(expected_error.to_string());
self
}
}
impl X86E2ETestSuite {
pub fn new(name: &str) -> Self {
let working_dir = std::env::temp_dir().join(format!("e2e_test_{}", name));
let _ = fs::create_dir_all(&working_dir);
X86E2ETestSuite {
name: name.to_string(),
programs: Vec::new(),
working_dir,
keep_binaries: false,
}
}
pub fn add(&mut self, program: E2ETestProgram) {
self.programs.push(program);
}
pub fn add_standard_tests(&mut self) {
self.add_hello_world();
self.add_fibonacci();
self.add_quicksort();
self.add_linked_list();
self.add_binary_tree();
self.add_hash_table();
self.add_string_operations();
self.add_math_functions();
self.add_struct_operations();
self.add_pointer_arithmetic();
self.add_recursive_functions();
self.add_array_operations();
self.add_io_operations();
self.add_preprocessor_tests();
self.add_loop_tests();
self.add_switch_case_tests();
self.add_function_pointers();
self.add_variadic_functions();
self.add_bitwise_operations();
self.add_memory_management();
}
pub fn add_cpp_standard_tests(&mut self) {
self.add_template_usage();
self.add_class_inheritance();
self.add_operator_overloading();
self.add_exception_handling();
self.add_stl_usage();
self.add_lambda_expressions();
self.add_smart_pointers();
self.add_move_semantics();
self.add_virtual_functions();
self.add_constexpr_tests();
self.add_namespace_tests();
}
pub fn len(&self) -> usize {
self.programs.len()
}
pub fn is_empty(&self) -> bool {
self.programs.is_empty()
}
pub fn add_hello_world(&mut self) {
self.add(
E2ETestProgram::c(
"hello_world",
r#"#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
"#,
)
.expect_stdout("Hello, World!\n"),
);
}
pub fn add_fibonacci(&mut self) {
self.add(
E2ETestProgram::c(
"fibonacci",
r#"#include <stdio.h>
int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
int main() {
printf("%d\n", fib(10));
return 0;
}
"#,
)
.expect_stdout("55\n"),
);
}
pub fn add_quicksort(&mut self) {
self.add(
E2ETestProgram::c(
"quicksort",
r#"#include <stdio.h>
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return i + 1;
}
void quicksort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quicksort(arr, low, pi - 1);
quicksort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {10, 7, 8, 9, 1, 5, 3, 2, 4, 6};
int n = 10;
quicksort(arr, 0, n - 1);
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
"#,
)
.expect_stdout("1 2 3 4 5 6 7 8 9 10 \n"),
);
}
pub fn add_linked_list(&mut self) {
self.add(
E2ETestProgram::c(
"linked_list",
r#"#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
struct Node *create_node(int data) {
struct Node *n = malloc(sizeof(struct Node));
n->data = data;
n->next = NULL;
return n;
}
void push(struct Node **head, int data) {
struct Node *n = create_node(data);
n->next = *head;
*head = n;
}
int length(struct Node *head) {
int len = 0;
while (head) { len++; head = head->next; }
return len;
}
void free_list(struct Node *head) {
while (head) {
struct Node *tmp = head;
head = head->next;
free(tmp);
}
}
int main() {
struct Node *head = NULL;
push(&head, 10);
push(&head, 20);
push(&head, 30);
printf("%d\n", length(head));
free_list(head);
return 0;
}
"#,
)
.expect_stdout("3\n"),
);
}
pub fn add_binary_tree(&mut self) {
self.add(
E2ETestProgram::c(
"binary_tree",
r#"#include <stdio.h>
#include <stdlib.h>
struct TreeNode {
int data;
struct TreeNode *left, *right;
};
struct TreeNode *create_node(int data) {
struct TreeNode *n = malloc(sizeof(struct TreeNode));
n->data = data; n->left = n->right = NULL;
return n;
}
int count_nodes(struct TreeNode *root) {
if (!root) return 0;
return 1 + count_nodes(root->left) + count_nodes(root->right);
}
void free_tree(struct TreeNode *root) {
if (!root) return;
free_tree(root->left);
free_tree(root->right);
free(root);
}
int main() {
struct TreeNode *root = create_node(1);
root->left = create_node(2);
root->right = create_node(3);
root->left->left = create_node(4);
root->left->right = create_node(5);
printf("%d\n", count_nodes(root));
free_tree(root);
return 0;
}
"#,
)
.expect_stdout("5\n"),
);
}
pub fn add_hash_table(&mut self) {
self.add(
E2ETestProgram::c(
"hash_table",
r#"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TABLE_SIZE 128
struct Entry {
char *key;
int value;
struct Entry *next;
};
struct HashTable {
struct Entry *buckets[TABLE_SIZE];
};
unsigned int hash(const char *key) {
unsigned int h = 0;
while (*key) h = h * 31 + *key++;
return h % TABLE_SIZE;
}
void ht_set(struct HashTable *ht, const char *key, int value) {
unsigned int idx = hash(key);
struct Entry *e = malloc(sizeof(struct Entry));
e->key = strdup(key); e->value = value;
e->next = ht->buckets[idx];
ht->buckets[idx] = e;
}
int ht_get(struct HashTable *ht, const char *key, int *found) {
unsigned int idx = hash(key);
struct Entry *e = ht->buckets[idx];
while (e) {
if (strcmp(e->key, key) == 0) {
*found = 1; return e->value;
}
e = e->next;
}
*found = 0; return 0;
}
int main() {
struct HashTable ht = {0};
ht_set(&ht, "one", 1);
ht_set(&ht, "two", 2);
ht_set(&ht, "three", 3);
int found;
int v = ht_get(&ht, "two", &found);
printf("%d\n", found ? v : -1);
return 0;
}
"#,
)
.expect_stdout("2\n"),
);
}
pub fn add_string_operations(&mut self) {
self.add(
E2ETestProgram::c(
"string_ops",
r#"#include <stdio.h>
#include <string.h>
int main() {
char buf[256];
strcpy(buf, "Hello ");
strcat(buf, "World");
printf("%s %zu\n", buf, strlen(buf));
if (strcmp(buf, "Hello World") == 0)
printf("match\n");
return 0;
}
"#,
)
.expect_stdout("Hello World 11\nmatch\n"),
);
}
pub fn add_math_functions(&mut self) {
self.add(
E2ETestProgram::c(
"math_functions",
r#"#include <stdio.h>
#include <math.h>
int main() {
printf("%d\n", (int)sqrt(144));
printf("%d\n", (int)pow(2, 10));
printf("%d\n", abs(-42));
printf("%d\n", 5 > 3 ? 1 : 0);
return 0;
}
"#,
)
.expect_stdout("12\n1024\n42\n1\n"),
);
}
pub fn add_struct_operations(&mut self) {
self.add(
E2ETestProgram::c(
"struct_operations",
r#"#include <stdio.h>
struct Point { int x; int y; };
struct Rect { struct Point top_left; struct Point bottom_right; };
int area(struct Rect r) {
int w = r.bottom_right.x - r.top_left.x;
int h = r.bottom_right.y - r.top_left.y;
return w * h;
}
int main() {
struct Rect r = {{0, 0}, {10, 5}};
printf("%d\n", area(r));
return 0;
}
"#,
)
.expect_stdout("50\n"),
);
}
pub fn add_pointer_arithmetic(&mut self) {
self.add(
E2ETestProgram::c(
"pointer_arithmetic",
r#"#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *p);
p += 2;
printf("%d\n", *p);
printf("%ld\n", (p - arr));
return 0;
}
"#,
)
.expect_stdout("10\n30\n2\n"),
);
}
pub fn add_recursive_functions(&mut self) {
self.add(
E2ETestProgram::c(
"recursive_functions",
r#"#include <stdio.h>
int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); }
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
int main() {
printf("%d\n", factorial(5));
printf("%d\n", gcd(48, 18));
return 0;
}
"#,
)
.expect_stdout("120\n6\n"),
);
}
pub fn add_array_operations(&mut self) {
self.add(
E2ETestProgram::c(
"array_operations",
r#"#include <stdio.h>
int sum(int *arr, int n) {
int s = 0;
for (int i = 0; i < n; i++) s += arr[i];
return s;
}
int max(int *arr, int n) {
int m = arr[0];
for (int i = 1; i < n; i++)
if (arr[i] > m) m = arr[i];
return m;
}
int main() {
int arr[] = {3, 7, 2, 9, 1, 5, 8, 4, 6, 0};
printf("%d\n", sum(arr, 10));
printf("%d\n", max(arr, 10));
return 0;
}
"#,
)
.expect_stdout("45\n9\n"),
);
}
pub fn add_io_operations(&mut self) {
self.add(
E2ETestProgram::c(
"io_operations",
r#"#include <stdio.h>
int main() {
int val = 42;
printf("%d\n", val);
fprintf(stdout, "%s\n", "hello");
return 0;
}
"#,
)
.expect_stdout("42\nhello\n"),
);
}
pub fn add_preprocessor_tests(&mut self) {
self.add(
E2ETestProgram::c(
"preprocessor_tests",
r#"#include <stdio.h>
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define PI 3.14159
int main() {
printf("%d\n", SQUARE(5));
printf("%d\n", MAX(10, 20));
printf("%d\n", (int)(PI * 10));
return 0;
}
"#,
)
.expect_stdout("25\n20\n31\n"),
);
}
pub fn add_loop_tests(&mut self) {
self.add(
E2ETestProgram::c(
"loop_tests",
r#"#include <stdio.h>
int main() {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
printf("%d\n", sum);
int j = 10;
while (j > 0) { sum += j; j--; }
printf("%d\n", sum);
return 0;
}
"#,
)
.expect_stdout("5050\n5105\n"),
);
}
pub fn add_switch_case_tests(&mut self) {
self.add(
E2ETestProgram::c(
"switch_case_tests",
r#"#include <stdio.h>
const char *day_name(int d) {
switch (d) {
case 1: return "Mon";
case 2: return "Tue";
case 3: return "Wed";
case 4: return "Thu";
case 5: return "Fri";
case 6: return "Sat";
case 7: return "Sun";
default: return "???";
}
}
int main() {
printf("%s\n", day_name(3));
printf("%s\n", day_name(7));
printf("%s\n", day_name(0));
return 0;
}
"#,
)
.expect_stdout("Wed\nSun\n???\n"),
);
}
pub fn add_function_pointers(&mut self) {
self.add(
E2ETestProgram::c(
"function_pointers",
r#"#include <stdio.h>
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int apply(int (*f)(int, int), int x, int y) { return f(x, y); }
int main() {
printf("%d\n", apply(add, 3, 4));
printf("%d\n", apply(mul, 3, 4));
return 0;
}
"#,
)
.expect_stdout("7\n12\n"),
);
}
pub fn add_variadic_functions(&mut self) {
self.add(
E2ETestProgram::c(
"variadic_functions",
r#"#include <stdio.h>
#include <stdarg.h>
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++)
total += va_arg(args, int);
va_end(args);
return total;
}
int main() {
printf("%d\n", sum(5, 1, 2, 3, 4, 5));
return 0;
}
"#,
)
.expect_stdout("15\n"),
);
}
pub fn add_bitwise_operations(&mut self) {
self.add(
E2ETestProgram::c(
"bitwise_operations",
r#"#include <stdio.h>
int main() {
int a = 0x0F; // 00001111
int b = 0xF0; // 11110000
printf("%d\n", a & b); // 0
printf("%d\n", a | b); // 255
printf("%d\n", a ^ b); // 255
printf("%d\n", ~a & 0xFF); // 240
printf("%d\n", a << 4); // 240
printf("%d\n", b >> 4); // 15
return 0;
}
"#,
)
.expect_stdout("0\n255\n255\n240\n240\n15\n"),
);
}
pub fn add_memory_management(&mut self) {
self.add(
E2ETestProgram::c(
"memory_management",
r#"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *buf = malloc(100);
strcpy(buf, "dynamic memory");
printf("%s\n", buf);
free(buf);
int *arr = calloc(10, sizeof(int));
arr[0] = 1; arr[9] = 10;
printf("%d %d\n", arr[0], arr[9]);
free(arr);
return 0;
}
"#,
)
.expect_stdout("dynamic memory\n1 10\n"),
);
}
pub fn add_template_usage(&mut self) {
self.add(
E2ETestProgram::cpp(
"template_usage",
r#"#include <stdio.h>
template <typename T>
T max(T a, T b) { return a > b ? a : b; }
template <typename T>
class Pair {
T first, second;
public:
Pair(T a, T b) : first(a), second(b) {}
T sum() const { return first + second; }
};
int main() {
printf("%d\n", max(10, 20));
printf("%.1f\n", max(3.5, 2.5));
Pair<int> p(3, 4);
printf("%d\n", p.sum());
return 0;
}
"#,
)
.expect_stdout("20\n3.5\n7\n"),
);
}
pub fn add_class_inheritance(&mut self) {
self.add(
E2ETestProgram::cpp(
"class_inheritance",
r#"#include <stdio.h>
class Base {
public:
virtual int value() const { return 10; }
virtual ~Base() {}
};
class Derived : public Base {
public:
int value() const override { return 20; }
};
int main() {
Base *b = new Derived();
printf("%d\n", b->value());
delete b;
return 0;
}
"#,
)
.expect_stdout("20\n"),
);
}
pub fn add_operator_overloading(&mut self) {
self.add(
E2ETestProgram::cpp(
"operator_overloading",
r#"#include <stdio.h>
struct Vec {
int x, y;
Vec(int a, int b) : x(a), y(b) {}
Vec operator+(const Vec &o) const { return Vec(x + o.x, y + o.y); }
};
int main() {
Vec a(1, 2), b(3, 4);
Vec c = a + b;
printf("%d %d\n", c.x, c.y);
return 0;
}
"#,
)
.expect_stdout("4 6\n"),
);
}
pub fn add_exception_handling(&mut self) {
self.add(
E2ETestProgram::cpp(
"exception_handling",
r#"#include <stdio.h>
int divide(int a, int b) {
if (b == 0) throw "division by zero";
return a / b;
}
int main() {
try {
printf("%d\n", divide(10, 2));
printf("%d\n", divide(10, 0));
} catch (const char *msg) {
printf("error: %s\n", msg);
}
return 0;
}
"#,
)
.expect_stdout("5\nerror: division by zero\n"),
);
}
pub fn add_stl_usage(&mut self) {
self.add(
E2ETestProgram::cpp(
"stl_usage",
r#"#include <stdio.h>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
std::sort(v.begin(), v.end());
for (int x : v) printf("%d ", x);
printf("\n%zu\n", v.size());
return 0;
}
"#,
)
.expect_stdout("1 1 2 3 4 5 6 9 \n8\n"),
);
}
pub fn add_lambda_expressions(&mut self) {
self.add(
E2ETestProgram::cpp(
"lambda_expressions",
r#"#include <stdio.h>
int main() {
auto add = [](int a, int b) { return a + b; };
printf("%d\n", add(3, 4));
int factor = 10;
auto scale = [factor](int x) { return x * factor; };
printf("%d\n", scale(5));
return 0;
}
"#,
)
.expect_stdout("7\n50\n"),
);
}
pub fn add_smart_pointers(&mut self) {
self.add(
E2ETestProgram::cpp(
"smart_pointers",
r#"#include <stdio.h>
#include <memory>
struct Foo {
int val;
Foo(int v) : val(v) {}
~Foo() {}
};
int main() {
auto p = std::make_unique<Foo>(42);
printf("%d\n", p->val);
auto sp = std::make_shared<Foo>(99);
printf("%d\n", sp->val);
return 0;
}
"#,
)
.expect_stdout("42\n99\n"),
);
}
pub fn add_move_semantics(&mut self) {
self.add(
E2ETestProgram::cpp(
"move_semantics",
r#"#include <stdio.h>
#include <utility>
struct Buffer {
int *data;
size_t size;
Buffer(size_t s) : size(s) { data = new int[s](); }
Buffer(Buffer &&o) noexcept : data(o.data), size(o.size) {
o.data = nullptr; o.size = 0;
}
~Buffer() { delete[] data; }
};
int main() {
Buffer b1(10);
Buffer b2(std::move(b1));
printf("%zu\n", b2.size);
return 0;
}
"#,
)
.expect_stdout("10\n"),
);
}
pub fn add_virtual_functions(&mut self) {
self.add(
E2ETestProgram::cpp(
"virtual_functions",
r#"#include <stdio.h>
class Animal {
public:
virtual const char *speak() const = 0;
virtual ~Animal() {}
};
class Dog : public Animal {
public:
const char *speak() const override { return "woof"; }
};
class Cat : public Animal {
public:
const char *speak() const override { return "meow"; }
};
int main() {
Animal *a1 = new Dog();
Animal *a2 = new Cat();
printf("%s\n%s\n", a1->speak(), a2->speak());
delete a1; delete a2;
return 0;
}
"#,
)
.expect_stdout("woof\nmeow\n"),
);
}
pub fn add_constexpr_tests(&mut self) {
self.add(
E2ETestProgram::cpp(
"constexpr_tests",
r#"#include <stdio.h>
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int f5 = factorial(5);
printf("%d\n", f5);
return 0;
}
"#,
)
.expect_stdout("120\n"),
);
}
pub fn add_namespace_tests(&mut self) {
self.add(
E2ETestProgram::cpp(
"namespace_tests",
r#"#include <stdio.h>
namespace math {
int add(int a, int b) { return a + b; }
const int PI = 3;
}
namespace physics {
int add(int a, int b) { return a + b + 100; }
}
int main() {
printf("%d\n", math::add(1, 2));
printf("%d\n", physics::add(1, 2));
printf("%d\n", math::PI);
return 0;
}
"#,
)
.expect_stdout("3\n103\n3\n"),
);
}
pub fn write_sources(&self) -> io::Result<Vec<PathBuf>> {
let mut paths = Vec::new();
for program in &self.programs {
let ext = program.language.default_extension();
let path = self.working_dir.join(format!("{}{}", program.name, ext));
fs::write(&path, &program.source)?;
paths.push(path);
for (i, extra) in program.extra_sources.iter().enumerate() {
let ext = extra.language.default_extension();
let extra_path = self
.working_dir
.join(format!("{}_{}{}", program.name, i, ext));
fs::write(&extra_path, &extra.source)?;
}
}
Ok(paths)
}
pub fn cleanup(&self) -> io::Result<()> {
if self.working_dir.exists() && !self.keep_binaries {
fs::remove_dir_all(&self.working_dir)?;
}
Ok(())
}
}
impl Drop for X86E2ETestSuite {
fn drop(&mut self) {
if !self.keep_binaries {
let _ = self.cleanup();
}
}
}
#[derive(Debug)]
pub struct X86E2EVerifier {
pub linker: String,
pub use_system_oracle: bool,
pub system_cc: String,
pub timeout_secs: u64,
pub working_dir: PathBuf,
}
#[derive(Debug, Clone)]
pub struct E2EVerificationResult {
pub passed: bool,
pub test_name: String,
pub exit_code: Option<i32>,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub expected_stdout: Option<String>,
pub expected_exit_code: i32,
pub duration: Duration,
pub error: Option<String>,
pub compilation_success: bool,
}
impl E2EVerificationResult {
pub fn summary(&self) -> String {
let status = if self.passed { "✓ PASS" } else { "✗ FAIL" };
let mut s = format!("{} {}", status, self.test_name);
if let Some(ref err) = self.error {
s.push_str(&format!(": {}", err));
}
s
}
}
impl X86E2EVerifier {
pub fn new() -> Self {
X86E2EVerifier {
linker: "ld".to_string(),
use_system_oracle: true,
system_cc: "cc".to_string(),
timeout_secs: 30,
working_dir: std::env::temp_dir().join("e2e_verify"),
}
}
pub fn verify_binary(
&self,
binary_path: &Path,
test: &E2ETestProgram,
) -> E2EVerificationResult {
let start = Instant::now();
if !binary_path.exists() {
return E2EVerificationResult {
passed: false,
test_name: test.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: test.expected_stdout.clone(),
expected_exit_code: test.expected_exit_code,
duration: start.elapsed(),
error: Some(format!("Binary not found: {}", binary_path.display())),
compilation_success: false,
};
}
match self.run_binary(binary_path, &test.args, test.stdin.as_deref()) {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let exit_code = output.status.code();
let mut passed = true;
let mut error = None;
if exit_code != Some(test.expected_exit_code) {
passed = false;
error = Some(format!(
"Exit code mismatch: expected {}, got {:?}",
test.expected_exit_code, exit_code
));
}
if passed {
if let Some(ref expected) = test.expected_stdout {
if stdout != *expected {
passed = false;
error = Some(format!(
"Stdout mismatch.\nExpected:\n{}\nGot:\n{}",
expected, stdout
));
}
}
}
if passed {
if let Some(ref expected) = test.expected_stderr {
if stderr != *expected {
passed = false;
error = Some(format!(
"Stderr mismatch.\nExpected:\n{}\nGot:\n{}",
expected, stderr
));
}
}
}
E2EVerificationResult {
passed,
test_name: test.name.clone(),
exit_code,
stdout: Some(stdout),
stderr: Some(stderr),
expected_stdout: test.expected_stdout.clone(),
expected_exit_code: test.expected_exit_code,
duration: start.elapsed(),
error,
compilation_success: true,
}
}
Err(e) => E2EVerificationResult {
passed: false,
test_name: test.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: test.expected_stdout.clone(),
expected_exit_code: test.expected_exit_code,
duration: start.elapsed(),
error: Some(format!("Failed to run binary: {}", e)),
compilation_success: true,
},
}
}
pub fn verify_compilation(
&self,
result: &E2EPipelineResult,
test: &E2ETestProgram,
) -> E2EVerificationResult {
let start = Instant::now();
if test.should_pass && !result.success {
return E2EVerificationResult {
passed: false,
test_name: test.name.clone(),
exit_code: result.program_exit_code,
stdout: result.program_stdout.clone(),
stderr: result.program_stderr.clone(),
expected_stdout: test.expected_stdout.clone(),
expected_exit_code: test.expected_exit_code,
duration: start.elapsed(),
error: Some("Compilation failed unexpectedly".to_string()),
compilation_success: false,
};
}
if !test.should_pass && result.success {
return E2EVerificationResult {
passed: false,
test_name: test.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: start.elapsed(),
error: Some("Compilation succeeded but was expected to fail".to_string()),
compilation_success: true,
};
}
if !test.should_pass && !result.success {
if let Some(ref expected_error) = test.expected_error {
if let Some(ref text_output) = result.text_output {
if !text_output.contains(expected_error.as_str()) {
return E2EVerificationResult {
passed: false,
test_name: test.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: start.elapsed(),
error: Some(format!(
"Expected error '{}' not found in output",
expected_error
)),
compilation_success: false,
};
}
}
}
return E2EVerificationResult {
passed: true,
test_name: test.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: start.elapsed(),
error: None,
compilation_success: false,
};
}
if let Some(ref output_path) = result.output_path {
if output_path.exists() && result.output_kind == E2EOutputKind::Executable {
return self.verify_binary(output_path, test);
}
}
if let Some(ref text_output) = result.text_output {
if let Some(ref expected_stdout) = test.expected_stdout {
let passed = text_output.contains(expected_stdout.as_str());
return E2EVerificationResult {
passed,
test_name: test.name.clone(),
exit_code: result.program_exit_code,
stdout: Some(text_output.clone()),
stderr: result.program_stderr.clone(),
expected_stdout: test.expected_stdout.clone(),
expected_exit_code: test.expected_exit_code,
duration: start.elapsed(),
error: if passed {
None
} else {
Some("Text output mismatch".to_string())
},
compilation_success: result.success,
};
}
}
E2EVerificationResult {
passed: result.success,
test_name: test.name.clone(),
exit_code: result.program_exit_code,
stdout: result.program_stdout.clone(),
stderr: result.program_stderr.clone(),
expected_stdout: test.expected_stdout.clone(),
expected_exit_code: test.expected_exit_code,
duration: start.elapsed(),
error: None,
compilation_success: result.success,
}
}
fn run_binary(
&self,
binary_path: &Path,
args: &[String],
stdin: Option<&str>,
) -> io::Result<Output> {
let mut cmd = Command::new(binary_path);
cmd.args(args);
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
if stdin.is_some() {
cmd.stdin(Stdio::piped());
}
let mut child = cmd.spawn()?;
if let Some(input) = stdin {
if let Some(ref mut child_stdin) = child.stdin {
child_stdin.write_all(input.as_bytes())?;
}
}
let output = child.wait_with_output()?;
Ok(output)
}
pub fn compare_with_system(
&self,
source: &Path,
test: &E2ETestProgram,
) -> io::Result<E2EComparisonResult> {
if !self.use_system_oracle {
return Ok(E2EComparisonResult {
test_name: test.name.clone(),
matches: false,
our_output: None,
system_output: None,
error: Some("System oracle disabled".to_string()),
});
}
let system_binary = self.working_dir.join(format!("{}_system", test.name));
let compile_status = Command::new(&self.system_cc)
.arg(source)
.arg("-o")
.arg(&system_binary)
.arg("-O2")
.status()?;
if !compile_status.success() {
return Ok(E2EComparisonResult {
test_name: test.name.clone(),
matches: false,
our_output: None,
system_output: None,
error: Some("System compiler failed to compile".to_string()),
});
}
let system_output = self.run_binary(&system_binary, &test.args, test.stdin.as_deref())?;
let system_stdout = String::from_utf8_lossy(&system_output.stdout).to_string();
Ok(E2EComparisonResult {
test_name: test.name.clone(),
matches: false, our_output: None,
system_output: Some(system_stdout),
error: None,
})
}
pub fn run_suite(
&self,
compiler: &mut X86E2ECompiler,
suite: &X86E2ETestSuite,
) -> Vec<E2EVerificationResult> {
let mut results = Vec::new();
let source_paths = match suite.write_sources() {
Ok(paths) => paths,
Err(e) => {
for program in &suite.programs {
results.push(E2EVerificationResult {
passed: false,
test_name: program.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: program.expected_stdout.clone(),
expected_exit_code: program.expected_exit_code,
duration: Duration::default(),
error: Some(format!("Failed to write sources: {}", e)),
compilation_success: false,
});
}
return results;
}
};
for (i, program) in suite.programs.iter().enumerate() {
if i >= source_paths.len() {
break;
}
let source_path = &source_paths[i];
compiler.options_mut().language = program.language;
if program.optimize {
compiler.options_mut().opt_level = E2EOptLevel::O2;
}
let compile_result = match compiler.compile_to_executable(source_path) {
Ok(r) => r,
Err(e) => {
results.push(E2EVerificationResult {
passed: false,
test_name: program.name.clone(),
exit_code: None,
stdout: None,
stderr: None,
expected_stdout: program.expected_stdout.clone(),
expected_exit_code: program.expected_exit_code,
duration: Duration::default(),
error: Some(format!("Compilation error: {}", e)),
compilation_success: false,
});
continue;
}
};
let verify_result = self.verify_compilation(&compile_result, program);
results.push(verify_result);
}
results
}
}
impl Default for X86E2EVerifier {
fn default() -> Self {
X86E2EVerifier::new()
}
}
#[derive(Debug, Clone)]
pub struct E2EComparisonResult {
pub test_name: String,
pub matches: bool,
pub our_output: Option<String>,
pub system_output: Option<String>,
pub error: Option<String>,
}
impl E2EComparisonResult {
pub fn is_match(&self) -> bool {
self.matches && self.error.is_none()
}
}
#[derive(Debug)]
pub struct E2ETestRunner {
pub compiler: X86E2ECompiler,
pub verifier: X86E2EVerifier,
pub suite: X86E2ETestSuite,
pub results: Vec<E2EVerificationResult>,
pub stop_on_failure: bool,
}
impl E2ETestRunner {
pub fn new(suite: X86E2ETestSuite) -> Self {
E2ETestRunner {
compiler: X86E2ECompiler::new(),
verifier: X86E2EVerifier::new(),
suite,
results: Vec::new(),
stop_on_failure: false,
}
}
pub fn run_all(&mut self) -> &[E2EVerificationResult] {
self.results = self.verifier.run_suite(&mut self.compiler, &self.suite);
&self.results
}
pub fn pass_rate(&self) -> f64 {
if self.results.is_empty() {
return 0.0;
}
let passed = self.results.iter().filter(|r| r.passed).count();
(passed as f64) / (self.results.len() as f64) * 100.0
}
pub fn print_summary(&self) {
println!("╔══════════════════════════════════════════════════╗");
println!("║ E2E Test Runner Summary ║");
println!("╠══════════════════════════════════════════════════╣");
println!("║ Suite: {:<42}║", self.suite.name);
println!(
"║ Total: {:<3} Passed: {:<3} Failed: {:<3} Rate: {:>5.1}% ║",
self.results.len(),
self.results.iter().filter(|r| r.passed).count(),
self.results.iter().filter(|r| !r.passed).count(),
self.pass_rate(),
);
println!("╠══════════════════════════════════════════════════╣");
for result in &self.results {
println!("║ {}║", result.summary());
}
println!("╚══════════════════════════════════════════════════╝");
}
pub fn export_json(&self) -> String {
let mut json = String::from("[\n");
for (i, result) in self.results.iter().enumerate() {
if i > 0 {
json.push_str(",\n");
}
json.push_str(" {\n");
json.push_str(&format!(" \"test\": \"{}\",\n", result.test_name));
json.push_str(&format!(" \"passed\": {},\n", result.passed));
json.push_str(&format!(
" \"exit_code\": {},\n",
result
.exit_code
.map_or("null".to_string(), |c| c.to_string())
));
json.push_str(&format!(
" \"duration_ms\": {:.1},\n",
result.duration.as_secs_f64() * 1000.0
));
if let Some(ref err) = result.error {
json.push_str(&format!(
" \"error\": \"{}\"\n",
err.replace('\"', "\\\"")
));
} else {
json.push_str(" \"error\": null\n");
}
json.push_str(" }");
}
json.push_str("\n]");
json
}
}
#[derive(Debug, Default)]
pub struct E2EConfigBuilder {
options: E2ECompileOptions,
}
impl E2EConfigBuilder {
pub fn new() -> Self {
E2EConfigBuilder::default()
}
pub fn language(mut self, lang: E2ELanguage) -> Self {
self.options.language = lang;
self
}
pub fn standard(mut self, std: CLangStandard) -> Self {
self.options.standard = std;
self
}
pub fn opt_level(mut self, level: E2EOptLevel) -> Self {
self.options.opt_level = level;
self
}
pub fn target(mut self, triple: &str) -> Self {
self.options.target_triple = triple.to_string();
self
}
pub fn output_kind(mut self, kind: E2EOutputKind) -> Self {
self.options.output_kind = kind;
self
}
pub fn output_file(mut self, path: &Path) -> Self {
self.options.output_file = Some(path.to_path_buf());
self
}
pub fn include(mut self, dir: &Path) -> Self {
self.options.includes.push(dir.to_path_buf());
self
}
pub fn define(mut self, name: &str, value: Option<&str>) -> Self {
self.options
.defines
.push((name.to_string(), value.map(|v| v.to_string())));
self
}
pub fn debug(mut self) -> Self {
self.options.debug_info = true;
self.options.debug_level = 2;
self
}
pub fn incremental(mut self) -> Self {
self.options.incremental = true;
self
}
pub fn lto(mut self) -> Self {
self.options.lto = true;
self
}
pub fn jobs(mut self, n: u32) -> Self {
self.options.jobs = n;
self
}
pub fn build(self) -> E2ECompileOptions {
self.options
}
}
fn format_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_idx = 0;
while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
size /= 1024.0;
unit_idx += 1;
}
if unit_idx == 0 {
format!("{} {}", bytes, UNITS[unit_idx])
} else {
format!("{:.1} {}", size, UNITS[unit_idx])
}
}
fn hostname() -> String {
std::env::var("HOSTNAME")
.or_else(|_| std::env::var("HOST"))
.unwrap_or_else(|_| "unknown".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_e2e_stage_order() {
let stages = E2EStage::all();
assert_eq!(stages.len(), 13);
assert_eq!(stages[0], E2EStage::Source);
assert_eq!(stages[12], E2EStage::Link);
}
#[test]
fn test_e2e_stage_next() {
assert_eq!(E2EStage::Source.next(), Some(E2EStage::Preprocess));
assert_eq!(E2EStage::Preprocess.next(), Some(E2EStage::Lex));
assert_eq!(E2EStage::Emit.next(), Some(E2EStage::Link));
assert_eq!(E2EStage::Link.next(), None);
}
#[test]
fn test_e2e_stage_prev() {
assert_eq!(E2EStage::Preprocess.prev(), Some(E2EStage::Source));
assert_eq!(E2EStage::Lex.prev(), Some(E2EStage::Preprocess));
assert_eq!(E2EStage::Link.prev(), Some(E2EStage::Emit));
assert_eq!(E2EStage::Source.prev(), None);
}
#[test]
fn test_e2e_stage_names() {
assert_eq!(E2EStage::Source.name(), "Source");
assert_eq!(E2EStage::Preprocess.name(), "Preprocess");
assert_eq!(E2EStage::Lex.name(), "Lex");
assert_eq!(E2EStage::Parse.name(), "Parse");
assert_eq!(E2EStage::Sema.name(), "Sema");
assert_eq!(E2EStage::IRGen.name(), "IRGen");
assert_eq!(E2EStage::Optimize.name(), "Optimize");
assert_eq!(E2EStage::ISel.name(), "ISel");
assert_eq!(E2EStage::RegAlloc.name(), "RegAlloc");
assert_eq!(E2EStage::FrameLower.name(), "FrameLower");
assert_eq!(E2EStage::Encode.name(), "Encode");
assert_eq!(E2EStage::Emit.name(), "Emit");
assert_eq!(E2EStage::Link.name(), "Link");
}
#[test]
fn test_e2e_stage_is_frontend() {
assert!(E2EStage::Source.is_frontend());
assert!(E2EStage::Preprocess.is_frontend());
assert!(E2EStage::Lex.is_frontend());
assert!(E2EStage::Parse.is_frontend());
assert!(E2EStage::Sema.is_frontend());
assert!(E2EStage::IRGen.is_frontend());
assert!(!E2EStage::Optimize.is_frontend());
assert!(!E2EStage::ISel.is_frontend());
assert!(!E2EStage::Link.is_frontend());
}
#[test]
fn test_e2e_stage_is_backend() {
assert!(E2EStage::ISel.is_backend());
assert!(E2EStage::RegAlloc.is_backend());
assert!(E2EStage::FrameLower.is_backend());
assert!(E2EStage::Encode.is_backend());
assert!(E2EStage::Emit.is_backend());
assert!(E2EStage::Link.is_backend());
assert!(!E2EStage::Parse.is_backend());
assert!(!E2EStage::Sema.is_backend());
assert!(!E2EStage::IRGen.is_backend());
}
#[test]
fn test_e2e_stage_is_cacheable() {
assert!(E2EStage::Preprocess.is_cacheable());
assert!(E2EStage::Lex.is_cacheable());
assert!(E2EStage::Parse.is_cacheable());
assert!(E2EStage::Sema.is_cacheable());
assert!(E2EStage::IRGen.is_cacheable());
assert!(E2EStage::Optimize.is_cacheable());
assert!(!E2EStage::ISel.is_cacheable());
assert!(!E2EStage::Link.is_cacheable());
}
#[test]
fn test_e2e_stage_relative_cost() {
assert!(E2EStage::Source.relative_cost() <= E2EStage::Optimize.relative_cost());
assert!(E2EStage::Optimize.relative_cost() >= 5);
assert!(E2EStage::Link.relative_cost() >= 1);
}
#[test]
fn test_e2e_stage_display() {
assert_eq!(format!("{}", E2EStage::Parse), "Parse");
assert_eq!(format!("{}", E2EStage::IRGen), "IRGen");
}
#[test]
fn test_artifact_kind_extension() {
assert_eq!(E2EArtifactKind::SourceFile.extension(), ".c");
assert_eq!(E2EArtifactKind::ObjectFile.extension(), ".o");
assert_eq!(E2EArtifactKind::SharedLibrary.extension(), ".so");
assert_eq!(E2EArtifactKind::LLVMIR.extension(), ".ll");
}
#[test]
fn test_artifact_kind_is_text() {
assert!(E2EArtifactKind::SourceFile.is_text());
assert!(E2EArtifactKind::LLVMIR.is_text());
assert!(!E2EArtifactKind::ObjectFile.is_text());
assert!(!E2EArtifactKind::Executable.is_text());
}
#[test]
fn test_artifact_kind_is_binary() {
assert!(E2EArtifactKind::ObjectFile.is_binary());
assert!(E2EArtifactKind::SharedLibrary.is_binary());
assert!(!E2EArtifactKind::LLVMIR.is_binary());
}
#[test]
fn test_output_kind_extension() {
assert_eq!(E2EOutputKind::PreprocessedSource.extension(), ".i");
assert_eq!(E2EOutputKind::Assembly.extension(), ".s");
assert_eq!(E2EOutputKind::Object.extension(), ".o");
assert_eq!(E2EOutputKind::SharedLibrary.extension(), ".so");
assert_eq!(E2EOutputKind::LLVMIR.extension(), ".ll");
}
#[test]
fn test_output_kind_is_text() {
assert!(E2EOutputKind::PreprocessedSource.is_text());
assert!(E2EOutputKind::LLVMIR.is_text());
assert!(E2EOutputKind::Assembly.is_text());
assert!(!E2EOutputKind::Object.is_text());
assert!(!E2EOutputKind::Executable.is_text());
}
#[test]
fn test_output_kind_required_stages() {
let stages = E2EOutputKind::PreprocessedSource.required_stages();
assert_eq!(stages.len(), 2);
assert!(stages.contains(&E2EStage::Preprocess));
let stages = E2EOutputKind::Assembly.required_stages();
assert!(stages.contains(&E2EStage::Parse));
assert!(stages.contains(&E2EStage::ISel));
assert!(stages.contains(&E2EStage::Encode));
let stages = E2EOutputKind::Executable.required_stages();
assert!(stages.contains(&E2EStage::Link));
}
#[test]
fn test_output_kind_final_stage() {
assert_eq!(
E2EOutputKind::PreprocessedSource.final_stage(),
E2EStage::Preprocess
);
assert_eq!(E2EOutputKind::Assembly.final_stage(), E2EStage::Encode);
assert_eq!(E2EOutputKind::Object.final_stage(), E2EStage::Emit);
assert_eq!(E2EOutputKind::Executable.final_stage(), E2EStage::Link);
}
#[test]
fn test_opt_level_from_str() {
assert_eq!(E2EOptLevel::from_str("O0"), Some(E2EOptLevel::O0));
assert_eq!(E2EOptLevel::from_str("O2"), Some(E2EOptLevel::O2));
assert_eq!(E2EOptLevel::from_str("O3"), Some(E2EOptLevel::O3));
assert_eq!(E2EOptLevel::from_str("Os"), Some(E2EOptLevel::Os));
assert_eq!(E2EOptLevel::from_str("Oz"), Some(E2EOptLevel::Oz));
assert_eq!(E2EOptLevel::from_str("invalid"), None);
}
#[test]
fn test_opt_level_is_optimizing() {
assert!(!E2EOptLevel::O0.is_optimizing());
assert!(E2EOptLevel::O1.is_optimizing());
assert!(E2EOptLevel::O2.is_optimizing());
assert!(E2EOptLevel::O3.is_optimizing());
assert!(E2EOptLevel::Os.is_optimizing());
assert!(E2EOptLevel::Oz.is_optimizing());
}
#[test]
fn test_opt_level_is_size_priority() {
assert!(E2EOptLevel::Os.is_size_priority());
assert!(E2EOptLevel::Oz.is_size_priority());
assert!(!E2EOptLevel::O2.is_size_priority());
}
#[test]
fn test_language_from_extension() {
use std::path::Path;
assert_eq!(
E2ELanguage::from_extension(Path::new("test.c")),
Some(E2ELanguage::C)
);
assert_eq!(
E2ELanguage::from_extension(Path::new("test.cpp")),
Some(E2ELanguage::Cpp)
);
assert_eq!(
E2ELanguage::from_extension(Path::new("test.m")),
Some(E2ELanguage::ObjC)
);
assert_eq!(E2ELanguage::from_extension(Path::new("test.unknown")), None);
}
#[test]
fn test_language_is_cpp_family() {
assert!(E2ELanguage::Cpp.is_cpp_family());
assert!(E2ELanguage::ObjCpp.is_cpp_family());
assert!(!E2ELanguage::C.is_cpp_family());
}
#[test]
fn test_compile_options_default() {
let opts = E2ECompileOptions::default();
assert_eq!(opts.language, E2ELanguage::C);
assert_eq!(opts.standard, CLangStandard::C17);
assert_eq!(opts.opt_level, E2EOptLevel::O2);
assert_eq!(opts.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(opts.target_cpu, "generic");
assert!(opts.wall);
assert!(!opts.debug_info);
}
#[test]
fn test_compile_options_release() {
let opts = E2ECompileOptions::release();
assert_eq!(opts.opt_level, E2EOptLevel::O3);
assert!(!opts.debug_info);
assert!(opts.lto);
assert_eq!(opts.target_cpu, "haswell");
}
#[test]
fn test_compile_options_debug() {
let opts = E2ECompileOptions::debug();
assert_eq!(opts.opt_level, E2EOptLevel::O0);
assert!(opts.debug_info);
assert_eq!(opts.debug_level, 2);
}
#[test]
fn test_is_64_bit() {
let opts = E2ECompileOptions::x86_64_linux_c();
assert!(opts.is_64_bit());
assert!(!opts.is_32_bit());
}
#[test]
fn test_is_32_bit() {
let mut opts = E2ECompileOptions::default();
opts.target_triple = "i686-unknown-linux-gnu".into();
assert!(opts.is_32_bit());
assert!(!opts.is_64_bit());
}
#[test]
fn test_stage_result_success() {
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(10));
assert!(result.success);
assert_eq!(result.stage, E2EStage::Parse);
assert_eq!(result.error_count, 0);
assert_eq!(result.duration, Duration::from_millis(10));
}
#[test]
fn test_stage_result_failure() {
let result = E2EStageResult::failure(
E2EStage::Parse,
Duration::from_millis(5),
3,
1,
"syntax error",
);
assert!(!result.success);
assert_eq!(result.error_count, 3);
assert_eq!(result.warning_count, 1);
assert_eq!(result.summary, "syntax error");
}
#[test]
fn test_stage_result_one_line() {
let result = E2EStageResult::success(E2EStage::IRGen, Duration::from_millis(42))
.with_summary("5 functions");
let line = result.one_line();
assert!(line.contains("IRGen"));
assert!(line.contains("✓"));
assert!(line.contains("5 functions"));
}
#[test]
fn test_stage_result_with_metadata() {
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(1))
.with_metadata("tokens", "1500");
assert_eq!(result.metadata.get("tokens").unwrap(), "1500");
}
#[test]
fn test_stage_result_total_diagnostics() {
let result = E2EStageResult::failure(
E2EStage::Sema,
Duration::from_millis(1),
5,
2,
"type errors",
);
assert_eq!(result.total_diagnostics(), 7);
}
#[test]
fn test_pipeline_result_default() {
let start = SystemTime::now();
let result = E2EPipelineResult::failure(
E2EStage::Lex,
vec![],
Duration::from_millis(0),
"not started",
);
assert!(!result.success);
assert_eq!(result.failed_stage, Some(E2EStage::Lex));
assert!(result.text_output.is_some());
}
#[test]
fn test_pipeline_result_success_with() {
let stage_results = vec![
E2EStageResult::success(E2EStage::Lex, Duration::from_millis(1)),
E2EStageResult::success(E2EStage::Parse, Duration::from_millis(5)),
];
let result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(6),
None,
None,
None,
);
assert!(result.success);
assert_eq!(result.total_errors, 0);
assert_eq!(result.total_duration, Duration::from_millis(6));
}
#[test]
fn test_pipeline_result_format_report() {
let stage_results = vec![
E2EStageResult::success(E2EStage::Preprocess, Duration::from_millis(1))
.with_summary("100 bytes"),
E2EStageResult::success(E2EStage::Lex, Duration::from_millis(2))
.with_summary("50 tokens"),
];
let result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(3),
None,
None,
None,
);
let report = result.format_report();
assert!(report.contains("Clang→X86"));
assert!(report.contains("Preprocess"));
assert!(report.contains("Lex"));
assert!(report.contains("SUCCESS"));
}
#[test]
fn test_pipeline_result_compact_summary() {
let stage_results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(10),
)];
let result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(10),
None,
None,
None,
);
let summary = result.compact_summary();
assert!(summary.contains("OK"));
assert!(summary.contains("err=0"));
}
#[test]
fn test_cache_new() {
let dir = std::env::temp_dir().join("e2e_cache_test_new");
let _ = fs::remove_dir_all(&dir);
let cache = E2ECompilationCache::new(&dir);
assert!(cache.cache_dir().exists());
assert_eq!(cache.stats().hits, 0);
assert_eq!(cache.stats().misses, 0);
}
#[test]
fn test_cache_disabled() {
let mut cache = E2ECompilationCache::disabled();
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(1));
cache.store("test_key", vec![result]);
assert_eq!(cache.stats().entry_count, 0); }
#[test]
fn test_cache_store_and_lookup() {
let dir = std::env::temp_dir().join("e2e_cache_test_store");
let _ = fs::remove_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
let results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
cache.store("test_key", results);
let cached = cache.lookup("test_key");
assert!(cached.is_some());
assert_eq!(cache.stats().hits, 1);
}
#[test]
fn test_cache_lookup_miss() {
let dir = std::env::temp_dir().join("e2e_cache_test_miss");
let _ = fs::remove_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
let cached = cache.lookup("nonexistent_key");
assert!(cached.is_none());
assert_eq!(cache.stats().misses, 1);
}
#[test]
fn test_cache_stats_hit_rate() {
let dir = std::env::temp_dir().join("e2e_cache_test_rate");
let _ = fs::remove_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
let results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
cache.store("key1", results.clone());
cache.store("key2", results);
cache.lookup("key1"); cache.lookup("key3"); cache.lookup("key4");
assert!((cache.stats().hit_rate() - 33.33).abs() < 1.0);
}
#[test]
fn test_cache_clear() {
let dir = std::env::temp_dir().join("e2e_cache_test_clear");
let _ = fs::remove_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
let results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
cache.store("key1", results);
cache.clear();
assert_eq!(cache.stats().hits, 0);
assert_eq!(cache.stats().entry_count, 0);
}
#[test]
fn test_pipeline_new() {
let pipeline = X86E2EPipeline::new();
assert_eq!(pipeline.options.target_triple, "x86_64-unknown-linux-gnu");
assert!(pipeline.source_files.is_empty());
}
#[test]
fn test_pipeline_with_options() {
let opts = E2ECompileOptions::x86_64_linux_c();
let pipeline = X86E2EPipeline::with_options(opts);
assert_eq!(pipeline.options.language, E2ELanguage::C);
}
#[test]
fn test_pipeline_add_source() {
let mut pipeline = X86E2EPipeline::new();
let result = pipeline.add_source(Path::new("/nonexistent/file.c"));
assert!(result.is_err());
}
#[test]
fn test_pipeline_run_minimal() {
let dir = std::env::temp_dir().join("e2e_pipeline_test_run");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.options.cache_dir = Some(dir.join("cache"));
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
assert_eq!(result.total_errors, 0);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_run_with_progress() {
let dir = std::env::temp_dir().join("e2e_pipeline_test_progress");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.show_progress = true;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_stage_callback() {
let dir = std::env::temp_dir().join("e2e_pipeline_test_callback");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let callback_stages = Arc::new(Mutex::new(Vec::new()));
let callback_stages_clone = callback_stages.clone();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.on_stage_complete(move |result| {
callback_stages_clone.lock().unwrap().push(result.stage);
});
pipeline.add_source(&source_path).unwrap();
pipeline.run();
let stages = callback_stages.lock().unwrap();
assert!(!stages.is_empty());
assert_eq!(stages[0], E2EStage::Source);
assert_eq!(stages[1], E2EStage::Preprocess);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_cache_dir_default() {
let pipeline = X86E2EPipeline::new();
assert_eq!(pipeline.cache.cache_dir(), Path::new(E2E_DEFAULT_CACHE_DIR));
}
#[test]
fn test_compiler_new() {
let compiler = X86E2ECompiler::new();
assert_eq!(compiler.options().language, E2ELanguage::C);
}
#[test]
fn test_compiler_with_options() {
let opts = E2ECompileOptions::x86_64_linux_cpp();
let compiler = X86E2ECompiler::with_options(opts);
assert_eq!(compiler.options().language, E2ELanguage::Cpp);
}
#[test]
fn test_compiler_compile_c_nonexistent() {
let mut compiler = X86E2ECompiler::new();
let result = compiler.compile_c(Path::new("/nonexistent/file.c"));
assert!(result.is_err());
}
#[test]
fn test_compiler_compile_cpp_nonexistent() {
let mut compiler = X86E2ECompiler::new();
let result = compiler.compile_cpp(Path::new("/nonexistent/file.cpp"));
assert!(result.is_err());
}
#[test]
fn test_compiler_compile_to_object() {
let dir = std::env::temp_dir().join("e2e_compiler_test_obj");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut compiler = X86E2ECompiler::new();
compiler.options_mut().output_kind = E2EOutputKind::Object;
let result = compiler.compile_to_object(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_compile_to_executable() {
let dir = std::env::temp_dir().join("e2e_compiler_test_exe");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut compiler = X86E2ECompiler::new();
let result = compiler.compile_to_executable(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_compile_to_shared_lib() {
let dir = std::env::temp_dir().join("e2e_compiler_test_so");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int foo() { return 42; }\n").unwrap();
let mut compiler = X86E2ECompiler::new();
let result = compiler.compile_to_shared_lib(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_compile_to_assembly() {
let dir = std::env::temp_dir().join("e2e_compiler_test_asm");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut compiler = X86E2ECompiler::new();
let result = compiler.compile_to_assembly(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_default() {
let compiler = X86E2ECompiler::default();
assert!(!compiler.keep_temp);
}
#[test]
fn test_suite_new() {
let suite = X86E2ETestSuite::new("test_suite");
assert_eq!(suite.name, "test_suite");
assert!(suite.is_empty());
assert_eq!(suite.len(), 0);
}
#[test]
fn test_suite_add_standard_tests() {
let mut suite = X86E2ETestSuite::new("standard");
suite.add_standard_tests();
assert!(suite.len() >= 10);
let names: Vec<&str> = suite.programs.iter().map(|p| p.name.as_str()).collect();
assert!(names.contains(&"hello_world"));
assert!(names.contains(&"fibonacci"));
assert!(names.contains(&"quicksort"));
assert!(names.contains(&"linked_list"));
assert!(names.contains(&"binary_tree"));
}
#[test]
fn test_suite_add_cpp_tests() {
let mut suite = X86E2ETestSuite::new("cpp");
suite.add_cpp_standard_tests();
assert!(suite.len() >= 5);
let names: Vec<&str> = suite.programs.iter().map(|p| p.name.as_str()).collect();
assert!(names.contains(&"template_usage"));
assert!(names.contains(&"class_inheritance"));
assert!(names.contains(&"lambda_expressions"));
}
#[test]
fn test_suite_write_sources() {
let mut suite = X86E2ETestSuite::new("write_test");
suite.add_hello_world();
suite.add_fibonacci();
let paths = suite.write_sources().unwrap();
assert_eq!(paths.len(), 2);
for path in &paths {
assert!(path.exists());
let content = fs::read_to_string(path).unwrap();
assert!(!content.is_empty());
}
suite.cleanup().unwrap();
}
#[test]
fn test_program_c_creation() {
let program = E2ETestProgram::c("test", "int main() { return 0; }");
assert_eq!(program.name, "test");
assert_eq!(program.language, E2ELanguage::C);
assert_eq!(program.expected_exit_code, 0);
}
#[test]
fn test_program_cpp_creation() {
let program = E2ETestProgram::cpp("test", "int main() { return 0; }");
assert_eq!(program.name, "test");
assert_eq!(program.language, E2ELanguage::Cpp);
assert_eq!(program.expected_exit_code, 0);
}
#[test]
fn test_program_expect_stdout() {
let program = E2ETestProgram::c("test", "int main() { return 0; }").expect_stdout("hello");
assert_eq!(program.expected_stdout, Some("hello".to_string()));
}
#[test]
fn test_program_with_args() {
let program =
E2ETestProgram::c("test", "int main() { return 0; }").with_args(vec!["arg1", "arg2"]);
assert_eq!(program.args, vec!["arg1", "arg2"]);
}
#[test]
fn test_program_optimize() {
let program = E2ETestProgram::c("test", "int main() { return 0; }").optimize();
assert!(program.optimize);
}
#[test]
fn test_program_should_fail() {
let program = E2ETestProgram::c("test", "invalid code").should_fail("expected error");
assert!(!program.should_pass);
assert_eq!(program.expected_error, Some("expected error".to_string()));
}
#[test]
fn test_suite_hello_world_source() {
let mut suite = X86E2ETestSuite::new("hw");
suite.add_hello_world();
assert!(suite.programs[0].source.contains("Hello, World!"));
assert!(suite.programs[0].source.contains("int main()"));
}
#[test]
fn test_suite_fibonacci_source() {
let mut suite = X86E2ETestSuite::new("fib");
suite.add_fibonacci();
assert!(suite.programs[0].source.contains("fib"));
assert_eq!(suite.programs[0].expected_stdout, Some("55\n".to_string()));
}
#[test]
fn test_suite_quicksort_source() {
let mut suite = X86E2ETestSuite::new("qs");
suite.add_quicksort();
assert!(suite.programs[0].source.contains("partition"));
assert!(suite.programs[0]
.expected_stdout
.as_ref()
.unwrap()
.contains("1 2 3 4 5 6 7 8 9 10"));
}
#[test]
fn test_verifier_new() {
let verifier = X86E2EVerifier::new();
assert!(verifier.use_system_oracle);
assert_eq!(verifier.timeout_secs, 30);
}
#[test]
fn test_verifier_verify_binary_nonexistent() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("test", "int main() { return 0; }");
let result = verifier.verify_binary(Path::new("/nonexistent/binary"), &test);
assert!(!result.passed);
assert!(result.error.unwrap().contains("not found"));
}
#[test]
fn test_verifier_verify_compilation_success() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("test", "int main() { return 0; }");
let stage_results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
let result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(1),
Some("Hello, World!\n".to_string()),
None,
None,
);
let verify = verifier.verify_compilation(&result, &test);
assert!(verify.passed);
}
#[test]
fn test_verifier_verify_compilation_failure_expected() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("test", "bad code").should_fail("syntax error");
let result = E2EPipelineResult::failure(
E2EStage::Parse,
vec![],
Duration::from_millis(1),
"syntax error at line 1",
);
let verify = verifier.verify_compilation(&result, &test);
assert!(verify.passed);
}
#[test]
fn test_verifier_verify_compilation_unexpected_failure() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("test", "int main() { return 0; }");
let result = E2EPipelineResult::failure(
E2EStage::Parse,
vec![],
Duration::from_millis(1),
"unexpected error",
);
let verify = verifier.verify_compilation(&result, &test);
assert!(!verify.passed);
}
#[test]
fn test_verification_result_summary() {
let result = E2EVerificationResult {
passed: true,
test_name: "hello".to_string(),
exit_code: Some(0),
stdout: Some("Hello".to_string()),
stderr: None,
expected_stdout: Some("Hello".to_string()),
expected_exit_code: 0,
duration: Duration::from_millis(10),
error: None,
compilation_success: true,
};
let summary = result.summary();
assert!(summary.contains("✓ PASS"));
assert!(summary.contains("hello"));
}
#[test]
fn test_verification_result_summary_fail() {
let result = E2EVerificationResult {
passed: false,
test_name: "fail_test".to_string(),
exit_code: Some(1),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(10),
error: Some("expected failure".to_string()),
compilation_success: true,
};
let summary = result.summary();
assert!(summary.contains("✗ FAIL"));
assert!(summary.contains("expected failure"));
}
#[test]
fn test_runner_new() {
let suite = X86E2ETestSuite::new("runner_test");
let runner = E2ETestRunner::new(suite);
assert!(runner.results.is_empty());
}
#[test]
fn test_runner_pass_rate_empty() {
let suite = X86E2ETestSuite::new("empty");
let runner = E2ETestRunner::new(suite);
assert_eq!(runner.pass_rate(), 0.0);
}
#[test]
fn test_runner_pass_rate() {
let suite = X86E2ETestSuite::new("rate_test");
let mut runner = E2ETestRunner::new(suite);
runner.results = vec![
E2EVerificationResult {
passed: true,
test_name: "t1".to_string(),
exit_code: Some(0),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(1),
error: None,
compilation_success: true,
},
E2EVerificationResult {
passed: false,
test_name: "t2".to_string(),
exit_code: Some(1),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(1),
error: Some("fail".to_string()),
compilation_success: true,
},
];
assert_eq!(runner.pass_rate(), 50.0);
}
#[test]
fn test_runner_export_json() {
let suite = X86E2ETestSuite::new("json_test");
let mut runner = E2ETestRunner::new(suite);
runner.results = vec![E2EVerificationResult {
passed: true,
test_name: "hello".to_string(),
exit_code: Some(0),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(5),
error: None,
compilation_success: true,
}];
let json = runner.export_json();
assert!(json.contains("\"hello\""));
assert!(json.contains("\"passed\": true"));
assert!(json.contains("\"exit_code\": 0"));
}
#[test]
fn test_comparison_result_match() {
let result = E2EComparisonResult {
test_name: "test".to_string(),
matches: true,
our_output: Some("hello".to_string()),
system_output: Some("hello".to_string()),
error: None,
};
assert!(result.is_match());
}
#[test]
fn test_comparison_result_no_match() {
let result = E2EComparisonResult {
test_name: "test".to_string(),
matches: false,
our_output: Some("hello".to_string()),
system_output: Some("world".to_string()),
error: None,
};
assert!(!result.is_match());
}
#[test]
fn test_comparison_result_error() {
let result = E2EComparisonResult {
test_name: "test".to_string(),
matches: false,
our_output: None,
system_output: None,
error: Some("compilation failed".to_string()),
};
assert!(!result.is_match());
}
#[test]
fn test_config_builder_defaults() {
let opts = E2EConfigBuilder::new().build();
assert_eq!(opts.language, E2ELanguage::C);
assert_eq!(opts.opt_level, E2EOptLevel::O2);
}
#[test]
fn test_config_builder_chaining() {
let opts = E2EConfigBuilder::new()
.language(E2ELanguage::Cpp)
.opt_level(E2EOptLevel::O3)
.target("x86_64-unknown-linux-gnu")
.debug()
.lto()
.jobs(8)
.build();
assert_eq!(opts.language, E2ELanguage::Cpp);
assert_eq!(opts.opt_level, E2EOptLevel::O3);
assert_eq!(opts.target_triple, "x86_64-unknown-linux-gnu");
assert!(opts.debug_info);
assert!(opts.lto);
assert_eq!(opts.jobs, 8);
}
#[test]
fn test_config_builder_include() {
let opts = E2EConfigBuilder::new()
.include(Path::new("/usr/include"))
.include(Path::new("/usr/local/include"))
.build();
assert_eq!(opts.includes.len(), 2);
}
#[test]
fn test_config_builder_define() {
let opts = E2EConfigBuilder::new()
.define("DEBUG", None)
.define("VERSION", Some("1"))
.build();
assert_eq!(opts.defines.len(), 2);
assert_eq!(opts.defines[0].0, "DEBUG");
assert!(opts.defines[0].1.is_none());
assert_eq!(opts.defines[1].1, Some("1".to_string()));
}
#[test]
fn test_config_builder_output() {
let opts = E2EConfigBuilder::new()
.output_kind(E2EOutputKind::SharedLibrary)
.output_file(Path::new("/tmp/libtest.so"))
.build();
assert_eq!(opts.output_kind, E2EOutputKind::SharedLibrary);
assert_eq!(opts.output_file, Some(PathBuf::from("/tmp/libtest.so")));
}
#[test]
fn test_format_size_bytes() {
assert_eq!(format_size(0), "0 B");
assert_eq!(format_size(512), "512 B");
assert_eq!(format_size(1024), "1.0 KB");
assert_eq!(format_size(1048576), "1.0 MB");
assert_eq!(format_size(1073741824), "1.0 GB");
}
#[test]
fn test_hostname_not_empty() {
let host = hostname();
assert!(!host.is_empty());
}
#[test]
fn test_full_e2e_workflow() {
let mut suite = X86E2ETestSuite::new("e2e_workflow");
suite.add_hello_world();
suite.add_fibonacci();
let source_paths = suite.write_sources().unwrap();
assert_eq!(source_paths.len(), 2);
let mut compiler = X86E2ECompiler::new();
let verifier = X86E2EVerifier::new();
for (i, program) in suite.programs.iter().enumerate() {
if i >= source_paths.len() {
break;
}
let result = compiler.compile_to_executable(&source_paths[i]);
assert!(result.is_ok());
let compile_result = result.unwrap();
let verify_result = verifier.verify_compilation(&compile_result, program);
assert!(
verify_result.passed,
"Test '{}' failed: {:?}",
program.name, verify_result.error
);
}
suite.cleanup().unwrap();
}
#[test]
fn test_pipeline_save_temps() {
let dir = std::env::temp_dir().join("e2e_save_temps_test");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.options.save_temps = true;
pipeline.options.temp_dir = Some(dir.clone());
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_incremental_disabled() {
let dir = std::env::temp_dir().join("e2e_incremental_disabled");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.options.incremental = false;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
assert_eq!(result.cached_stages, 0);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_stop_after_error() {
let dir = std::env::temp_dir().join("e2e_stop_error_test");
let _ = fs::create_dir_all(&dir);
let mut pipeline = X86E2EPipeline::new();
pipeline.stop_after_error = true;
let result = pipeline.run();
assert!(!result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_all_e2e_stages_are_tested() {
let stages = E2EStage::all();
assert_eq!(stages.len(), 13);
for stage in &stages {
assert!(!stage.name().is_empty());
assert!(!stage.abbrev().is_empty());
assert_eq!(stage.name().len(), stage.name().chars().count());
}
}
#[test]
fn test_output_kind_all_variants() {
let kinds = [
E2EOutputKind::PreprocessedSource,
E2EOutputKind::LLVMIR,
E2EOutputKind::LLVMBitcode,
E2EOutputKind::Assembly,
E2EOutputKind::Object,
E2EOutputKind::SharedLibrary,
E2EOutputKind::Executable,
E2EOutputKind::StaticLibrary,
];
for kind in &kinds {
let stages = kind.required_stages();
assert!(!stages.is_empty());
assert!(stages.contains(&kind.final_stage()));
}
}
#[test]
fn test_e2e_artifact_kind_display() {
assert_eq!(format!("{}", E2EArtifactKind::ObjectFile), "object-file");
assert_eq!(format!("{}", E2EArtifactKind::LLVMIR), "llvm-ir");
}
#[test]
fn test_e2e_output_kind_display() {
assert_eq!(format!("{}", E2EOutputKind::Object), "object");
assert_eq!(format!("{}", E2EOutputKind::Assembly), "assembly");
}
#[test]
fn test_e2e_opt_level_display() {
assert_eq!(format!("{}", E2EOptLevel::O0), "O0");
assert_eq!(format!("{}", E2EOptLevel::O3), "O3");
}
#[test]
fn test_e2e_language_display() {
assert_eq!(format!("{}", E2ELanguage::C), "C");
assert_eq!(format!("{}", E2ELanguage::Cpp), "C++");
}
#[test]
fn test_pipeline_multiple_source_add() {
let dir = std::env::temp_dir().join("e2e_multi_source");
let _ = fs::create_dir_all(&dir);
let src1 = dir.join("a.c");
let src2 = dir.join("b.c");
fs::write(&src1, "int a() { return 1; }\n").unwrap();
fs::write(&src2, "int b() { return 2; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.add_sources(&[src1.clone(), src2.clone()]).unwrap();
assert_eq!(pipeline.source_files.len(), 2);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_run_to_assembly() {
let dir = std::env::temp_dir().join("e2e_run_to_asm");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::Assembly;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
assert!(result.completed_stage.is_some());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_run_to_object() {
let dir = std::env::temp_dir().join("e2e_run_to_obj");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::Object;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_run_to_shared_library() {
let dir = std::env::temp_dir().join("e2e_run_to_so");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int foo() { return 42; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::SharedLibrary;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_run_to_executable() {
let dir = std::env::temp_dir().join("e2e_run_to_exe");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::Executable;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_stage_result_from_cache() {
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(1))
.from_cache("cache_key_123");
assert!(result.from_cache);
assert_eq!(result.cache_key, Some("cache_key_123".to_string()));
}
#[test]
fn test_compile_options_cache_key() {
let opts1 = E2ECompileOptions::x86_64_linux_c();
let opts2 = E2ECompileOptions::x86_64_linux_c();
let source_hash = [0u8; 32];
let key1 = opts1.cache_key(&source_hash);
let key2 = opts2.cache_key(&source_hash);
assert_eq!(key1, key2);
}
#[test]
fn test_compile_options_cache_key_different() {
let mut opts1 = E2ECompileOptions::x86_64_linux_c();
let mut opts2 = E2ECompileOptions::x86_64_linux_c();
opts2.opt_level = E2EOptLevel::O0;
let source_hash = [0u8; 32];
let key1 = opts1.cache_key(&source_hash);
let key2 = opts2.cache_key(&source_hash);
assert_ne!(key1, key2);
}
#[test]
fn test_e2e_config_language_standard_mapping() {
let mut compiler = X86E2ECompiler::new();
compiler.options_mut().language = E2ELanguage::C;
compiler.options_mut().standard = CLangStandard::C11;
assert_eq!(compiler.options().standard, CLangStandard::C11);
compiler.options_mut().language = E2ELanguage::Cpp;
compiler.options_mut().standard = CLangStandard::Cpp17;
assert_eq!(compiler.options().standard, CLangStandard::Cpp17);
}
#[test]
fn test_e2e_pipeline_result_program_exit() {
let stage_results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
let mut result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(1),
Some("hello".to_string()),
None,
None,
);
result.program_exit_code = Some(0);
result.program_stdout = Some("hello".to_string());
assert_eq!(result.program_exit_code, Some(0));
assert_eq!(result.program_stdout, Some("hello".to_string()));
}
#[test]
fn test_cache_max_entries_eviction() {
let dir = std::env::temp_dir().join("e2e_cache_eviction");
let _ = fs::remove_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
for i in 0..300 {
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(1));
cache.store(&format!("key_{}", i), vec![result]);
}
assert!(cache.stats().evictions > 0);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_executes_all_frontend_stages() {
let dir = std::env::temp_dir().join("e2e_frontend_stages");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 42; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::LLVMIR;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
assert_eq!(result.stage_results.len(), 6);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_executes_all_backend_stages() {
let dir = std::env::temp_dir().join("e2e_backend_stages");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::Object;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let backend_stages: Vec<_> = result
.stage_results
.iter()
.filter(|r| r.stage.is_backend())
.collect();
assert!(backend_stages.len() >= 5);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_link_stage_included_for_executable() {
let dir = std::env::temp_dir().join("e2e_link_stage");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::Executable;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let last_stage = result.stage_results.last().unwrap();
assert_eq!(last_stage.stage, E2EStage::Link);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_cache_disabled_by_default() {
let dir = std::env::temp_dir().join("e2e_cache_default");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert_eq!(result.cached_stages, 0);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_compile_c_valid_source() {
let dir = std::env::temp_dir().join("e2e_compiler_c_valid");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(
&source_path,
"#include <stdio.h>\nint main() { printf(\"hello\\n\"); return 0; }\n",
)
.unwrap();
let mut compiler = X86E2ECompiler::new();
compiler.options_mut().output_kind = E2EOutputKind::PreprocessedSource;
let result = compiler.compile_c(&source_path);
assert!(result.is_ok());
assert!(result.unwrap().success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_compile_cpp_valid_source() {
let dir = std::env::temp_dir().join("e2e_compiler_cpp_valid");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.cpp");
fs::write(
&source_path,
"#include <cstdio>\nint main() { std::printf(\"hello\\n\"); return 0; }\n",
)
.unwrap();
let mut compiler = X86E2ECompiler::new();
compiler.options_mut().output_kind = E2EOutputKind::PreprocessedSource;
let result = compiler.compile_cpp(&source_path);
assert!(result.is_ok());
assert!(result.unwrap().success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_temp_dir_created() {
let compiler = X86E2ECompiler::new();
assert!(compiler.temp_dir.exists());
}
#[test]
fn test_compiler_keep_temp_files() {
let compiler = X86E2ECompiler::new().keep_temp_files();
assert!(compiler.keep_temp);
}
#[test]
fn test_compiler_options_mut() {
let mut compiler = X86E2ECompiler::new();
compiler.options_mut().wall = false;
assert!(!compiler.options().wall);
}
#[test]
fn test_compiler_preprocess() {
let dir = std::env::temp_dir().join("e2e_compiler_preprocess");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "#define X 42\nint main() { return X; }\n").unwrap();
let mut compiler = X86E2ECompiler::new();
let result = compiler.preprocess(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_compiler_compile_to_ir() {
let dir = std::env::temp_dir().join("e2e_compiler_ir");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut compiler = X86E2ECompiler::new();
let result = compiler.compile_to_ir(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_suite_program_default_values() {
let program = E2ETestProgram::c("default_test", "int main() { return 0; }");
assert_eq!(program.expected_exit_code, 0);
assert!(program.should_pass);
assert!(program.expected_stdout.is_none());
assert!(program.expected_stderr.is_none());
assert!(program.args.is_empty());
assert!(!program.optimize);
assert_eq!(program.timeout_secs, 30);
}
#[test]
fn test_suite_add_individual_tests() {
let mut suite = X86E2ETestSuite::new("individual");
suite.add_hello_world();
assert_eq!(suite.len(), 1);
suite.add_fibonacci();
assert_eq!(suite.len(), 2);
suite.add_linked_list();
assert_eq!(suite.len(), 3);
suite.add_binary_tree();
assert_eq!(suite.len(), 4);
suite.add_hash_table();
assert_eq!(suite.len(), 5);
suite.add_quicksort();
assert_eq!(suite.len(), 6);
}
#[test]
fn test_suite_string_ops_source() {
let mut suite = X86E2ETestSuite::new("str");
suite.add_string_operations();
let src = &suite.programs[0].source;
assert!(src.contains("strcpy"));
assert!(src.contains("strcat"));
assert!(src.contains("strlen"));
assert!(src.contains("strcmp"));
}
#[test]
fn test_suite_math_functions_source() {
let mut suite = X86E2ETestSuite::new("math");
suite.add_math_functions();
let src = &suite.programs[0].source;
assert!(src.contains("sqrt"));
assert!(src.contains("pow"));
assert!(src.contains("abs"));
}
#[test]
fn test_suite_struct_ops_source() {
let mut suite = X86E2ETestSuite::new("struct");
suite.add_struct_operations();
let src = &suite.programs[0].source;
assert!(src.contains("struct Point"));
assert!(src.contains("struct Rect"));
}
#[test]
fn test_suite_pointer_arith_source() {
let mut suite = X86E2ETestSuite::new("ptr");
suite.add_pointer_arithmetic();
let src = &suite.programs[0].source;
assert!(src.contains("int *p"));
assert!(src.contains("p += 2"));
}
#[test]
fn test_suite_recursive_source() {
let mut suite = X86E2ETestSuite::new("rec");
suite.add_recursive_functions();
let src = &suite.programs[0].source;
assert!(src.contains("factorial"));
assert!(src.contains("gcd"));
}
#[test]
fn test_suite_array_ops_source() {
let mut suite = X86E2ETestSuite::new("arr");
suite.add_array_operations();
let src = &suite.programs[0].source;
assert!(src.contains("int sum"));
assert!(src.contains("int max"));
}
#[test]
fn test_suite_loop_tests_source() {
let mut suite = X86E2ETestSuite::new("loop");
suite.add_loop_tests();
let src = &suite.programs[0].source;
assert!(src.contains("for"));
assert!(src.contains("while"));
}
#[test]
fn test_suite_switch_case_source() {
let mut suite = X86E2ETestSuite::new("switch");
suite.add_switch_case_tests();
let src = &suite.programs[0].source;
assert!(src.contains("switch"));
assert!(src.contains("case"));
assert!(src.contains("default"));
}
#[test]
fn test_suite_function_pointers_source() {
let mut suite = X86E2ETestSuite::new("fp");
suite.add_function_pointers();
let src = &suite.programs[0].source;
assert!(src.contains("int (*f)(int, int)"));
}
#[test]
fn test_suite_variadic_source() {
let mut suite = X86E2ETestSuite::new("va");
suite.add_variadic_functions();
let src = &suite.programs[0].source;
assert!(src.contains("va_list"));
assert!(src.contains("va_start"));
assert!(src.contains("va_arg"));
assert!(src.contains("va_end"));
}
#[test]
fn test_suite_bitwise_source() {
let mut suite = X86E2ETestSuite::new("bit");
suite.add_bitwise_operations();
let src = &suite.programs[0].source;
assert!(src.contains("0x0F"));
assert!(src.contains("&"));
assert!(src.contains("|"));
assert!(src.contains("^"));
assert!(src.contains("<<"));
assert!(src.contains(">>"));
}
#[test]
fn test_suite_memory_mgmt_source() {
let mut suite = X86E2ETestSuite::new("mem");
suite.add_memory_management();
let src = &suite.programs[0].source;
assert!(src.contains("malloc"));
assert!(src.contains("calloc"));
assert!(src.contains("free"));
}
#[test]
fn test_suite_template_source() {
let mut suite = X86E2ETestSuite::new("tpl");
suite.add_template_usage();
let src = &suite.programs[0].source;
assert!(src.contains("template"));
assert!(src.contains("typename"));
}
#[test]
fn test_suite_inheritance_source() {
let mut suite = X86E2ETestSuite::new("inh");
suite.add_class_inheritance();
let src = &suite.programs[0].source;
assert!(src.contains("class Base"));
assert!(src.contains("virtual"));
assert!(src.contains("override"));
}
#[test]
fn test_suite_operator_overload_source() {
let mut suite = X86E2ETestSuite::new("op");
suite.add_operator_overloading();
let src = &suite.programs[0].source;
assert!(src.contains("operator+"));
}
#[test]
fn test_suite_exception_source() {
let mut suite = X86E2ETestSuite::new("exc");
suite.add_exception_handling();
let src = &suite.programs[0].source;
assert!(src.contains("try"));
assert!(src.contains("catch"));
assert!(src.contains("throw"));
}
#[test]
fn test_suite_stl_source() {
let mut suite = X86E2ETestSuite::new("stl");
suite.add_stl_usage();
let src = &suite.programs[0].source;
assert!(src.contains("std::vector"));
assert!(src.contains("std::sort"));
}
#[test]
fn test_suite_lambda_source() {
let mut suite = X86E2ETestSuite::new("lam");
suite.add_lambda_expressions();
let src = &suite.programs[0].source;
assert!(src.contains("[]("));
}
#[test]
fn test_suite_smart_ptr_source() {
let mut suite = X86E2ETestSuite::new("sp");
suite.add_smart_pointers();
let src = &suite.programs[0].source;
assert!(src.contains("std::make_unique"));
assert!(src.contains("std::make_shared"));
}
#[test]
fn test_suite_move_semantics_source() {
let mut suite = X86E2ETestSuite::new("mv");
suite.add_move_semantics();
let src = &suite.programs[0].source;
assert!(src.contains("std::move"));
assert!(src.contains("noexcept"));
}
#[test]
fn test_suite_virtual_fn_source() {
let mut suite = X86E2ETestSuite::new("vf");
suite.add_virtual_functions();
let src = &suite.programs[0].source;
assert!(src.contains("= 0"));
assert!(src.contains("override"));
assert!(src.contains("virtual"));
}
#[test]
fn test_suite_constexpr_source() {
let mut suite = X86E2ETestSuite::new("ce");
suite.add_constexpr_tests();
let src = &suite.programs[0].source;
assert!(src.contains("constexpr"));
}
#[test]
fn test_suite_namespace_source() {
let mut suite = X86E2ETestSuite::new("ns");
suite.add_namespace_tests();
let src = &suite.programs[0].source;
assert!(src.contains("namespace math"));
assert!(src.contains("namespace physics"));
}
#[test]
fn test_program_expected_stdout_setting() {
let program = E2ETestProgram::c("t", "int main(){return 0;}")
.expect_stdout("42\n")
.expect_exit(0);
assert_eq!(program.expected_stdout, Some("42\n".to_string()));
assert_eq!(program.expected_exit_code, 0);
}
#[test]
fn test_program_with_stdin() {
let mut program = E2ETestProgram::c("t", "int main(){return 0;}");
program.stdin = Some("input data\n".to_string());
assert_eq!(program.stdin, Some("input data\n".to_string()));
}
#[test]
fn test_suite_io_ops_source() {
let mut suite = X86E2ETestSuite::new("io");
suite.add_io_operations();
let src = &suite.programs[0].source;
assert!(src.contains("fprintf"));
assert!(src.contains("stdout"));
}
#[test]
fn test_suite_preprocessor_test_source() {
let mut suite = X86E2ETestSuite::new("pp");
suite.add_preprocessor_tests();
let src = &suite.programs[0].source;
assert!(src.contains("#define SQUARE"));
assert!(src.contains("#define MAX"));
assert!(src.contains("#define PI"));
}
#[test]
fn test_verifier_default() {
let verifier = X86E2EVerifier::default();
assert!(verifier.use_system_oracle);
assert_eq!(verifier.linker, "ld");
}
#[test]
fn test_verifier_verify_compilation_unexpected_success() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("t", "bad code").should_fail("error");
let stage_results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
let result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(1),
None,
None,
None,
);
let verify = verifier.verify_compilation(&result, &test);
assert!(!verify.passed);
}
#[test]
fn test_verifier_verify_compilation_error_text() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("t", "code").should_fail("syntax error at line 5");
let result = E2EPipelineResult::failure(
E2EStage::Parse,
vec![],
Duration::from_millis(1),
"syntax error at line 5: unexpected token",
);
let verify = verifier.verify_compilation(&result, &test);
assert!(verify.passed);
}
#[test]
fn test_verifier_verify_binary_empty_args() {
let verifier = X86E2EVerifier::new();
let test = E2ETestProgram::c("t", "int main(){return 0;}");
let result = verifier.verify_binary(Path::new("/nonexistent"), &test);
assert!(!result.passed);
assert!(result.error.is_some());
}
#[test]
fn test_verifier_compare_system_disabled() {
let mut verifier = X86E2EVerifier::new();
verifier.use_system_oracle = false;
let dir = std::env::temp_dir().join("e2e_compare_disabled");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main(){return 0;}\n").unwrap();
let test = E2ETestProgram::c("t", "int main(){return 0;}");
let result = verifier.compare_with_system(&source_path, &test);
assert!(result.is_ok());
assert!(!result.unwrap().is_match());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_e2e_comparison_result_struct() {
let result = E2EComparisonResult {
test_name: "test".to_string(),
matches: true,
our_output: Some("hello".to_string()),
system_output: Some("hello".to_string()),
error: None,
};
assert!(result.is_match());
}
#[test]
fn test_runner_stop_on_failure() {
let suite = X86E2ETestSuite::new("stop_test");
let mut runner = E2ETestRunner::new(suite);
assert!(!runner.stop_on_failure);
runner.stop_on_failure = true;
assert!(runner.stop_on_failure);
}
#[test]
fn test_runner_pass_rate_full() {
let suite = X86E2ETestSuite::new("full_rate");
let mut runner = E2ETestRunner::new(suite);
runner.results = vec![
E2EVerificationResult {
passed: true,
test_name: "t1".to_string(),
exit_code: Some(0),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(1),
error: None,
compilation_success: true,
},
E2EVerificationResult {
passed: true,
test_name: "t2".to_string(),
exit_code: Some(0),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(1),
error: None,
compilation_success: true,
},
];
assert_eq!(runner.pass_rate(), 100.0);
}
#[test]
fn test_runner_export_json_multiple() {
let suite = X86E2ETestSuite::new("multi_json");
let mut runner = E2ETestRunner::new(suite);
runner.results = vec![
E2EVerificationResult {
passed: true,
test_name: "t1".to_string(),
exit_code: Some(0),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(5),
error: None,
compilation_success: true,
},
E2EVerificationResult {
passed: false,
test_name: "t2".to_string(),
exit_code: Some(1),
stdout: None,
stderr: None,
expected_stdout: None,
expected_exit_code: 0,
duration: Duration::from_millis(10),
error: Some("failed".to_string()),
compilation_success: false,
},
];
let json = runner.export_json();
assert!(json.contains("t1"));
assert!(json.contains("t2"));
assert!(json.contains("failed"));
}
#[test]
fn test_config_builder_chained_incremental() {
let opts = E2EConfigBuilder::new()
.language(E2ELanguage::C)
.opt_level(E2EOptLevel::Os)
.incremental()
.build();
assert!(opts.incremental);
assert_eq!(opts.opt_level, E2EOptLevel::Os);
}
#[test]
fn test_config_builder_full_featured() {
let tmp = std::env::temp_dir();
let opts = E2EConfigBuilder::new()
.language(E2ELanguage::Cpp)
.standard(CLangStandard::Cpp20)
.opt_level(E2EOptLevel::O3)
.target("x86_64-unknown-linux-gnu")
.output_kind(E2EOutputKind::SharedLibrary)
.output_file(&tmp.join("libout.so"))
.include(&tmp.join("include"))
.define("NDEBUG", None)
.define("VERSION", Some("2.0"))
.debug()
.incremental()
.lto()
.jobs(16)
.build();
assert_eq!(opts.language, E2ELanguage::Cpp);
assert_eq!(opts.standard, CLangStandard::Cpp20);
assert_eq!(opts.opt_level, E2EOptLevel::O3);
assert_eq!(opts.output_kind, E2EOutputKind::SharedLibrary);
assert!(opts.debug_info);
assert!(opts.incremental);
assert!(opts.lto);
assert_eq!(opts.jobs, 16);
assert_eq!(opts.defines.len(), 2);
assert_eq!(opts.includes.len(), 1);
}
#[test]
fn test_pipeline_multiple_runs() {
let dir = std::env::temp_dir().join("e2e_multi_runs");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
for i in 0..5 {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.options.cache_dir = Some(dir.join(format!("cache_{}", i)));
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success, "Run {} failed", i);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_stage_result_memory_tracking() {
let result = E2EStageResult::success(E2EStage::Optimize, Duration::from_millis(100))
.with_memory(1048576)
.with_metadata("cache_hits", "42");
assert_eq!(result.memory_usage, 1048576);
assert!(result.metadata.contains_key("cache_hits"));
}
#[test]
fn test_stage_result_input_output_sizes() {
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(50))
.with_input_size(8192)
.with_output_size(16384);
assert_eq!(result.input_size, 8192);
assert_eq!(result.output_size, 16384);
}
#[test]
fn test_stage_result_diagnostics_collection() {
let result = E2EStageResult::success(E2EStage::Sema, Duration::from_millis(10))
.with_diagnostic("note: candidate function not viable")
.with_diagnostic("warning: unused variable 'x'");
assert_eq!(result.diagnostics.len(), 2);
assert!(result.diagnostics[0].contains("candidate"));
assert!(result.diagnostics[1].contains("unused"));
}
#[test]
fn test_pipeline_result_output_kind_tracking() {
let stage_results = vec![E2EStageResult::success(
E2EStage::IRGen,
Duration::from_millis(10),
)];
let mut result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(10),
None,
None,
None,
);
result.output_kind = E2EOutputKind::SharedLibrary;
assert_eq!(result.output_kind, E2EOutputKind::SharedLibrary);
}
#[test]
fn test_artifact_kind_all_variants_have_extensions() {
let kinds = [
E2EArtifactKind::SourceFile,
E2EArtifactKind::PreprocessedSource,
E2EArtifactKind::TokenStream,
E2EArtifactKind::AST,
E2EArtifactKind::AnnotatedAST,
E2EArtifactKind::LLVMIR,
E2EArtifactKind::OptimizedIR,
E2EArtifactKind::MachineInstr,
E2EArtifactKind::MachineFunction,
E2EArtifactKind::EncodedBytes,
E2EArtifactKind::ObjectFile,
E2EArtifactKind::SharedLibrary,
E2EArtifactKind::Executable,
E2EArtifactKind::Binary,
];
for kind in &kinds {
assert!(
!kind.extension().is_empty()
|| *kind == E2EArtifactKind::Executable
|| *kind == E2EArtifactKind::Binary
);
}
}
#[test]
fn test_e2e_sanitizer_variants() {
let sanitizers = [
E2ESanitizer::Address,
E2ESanitizer::Memory,
E2ESanitizer::Thread,
E2ESanitizer::Undefined,
E2ESanitizer::Leak,
];
for i in 0..sanitizers.len() {
for j in (i + 1)..sanitizers.len() {
assert_ne!(sanitizers[i], sanitizers[j]);
}
}
}
#[test]
fn test_e2e_lto_type_variants() {
assert_ne!(E2ELTOType::Full, E2ELTOType::Thin);
assert_ne!(E2ELTOType::None, E2ELTOType::Full);
}
#[test]
fn test_e2e_code_model_variants() {
assert_ne!(E2ECodeModel::Small, E2ECodeModel::Large);
assert_ne!(E2ECodeModel::Kernel, E2ECodeModel::Medium);
}
#[test]
fn test_e2e_eh_model_variants() {
assert_ne!(E2EEHModel::DwarfCFI, E2EEHModel::SjLj);
assert_ne!(E2EEHModel::WinEH, E2EEHModel::None);
}
#[test]
fn test_e2e_frame_pointer_variants() {
assert_ne!(E2EFramePointer::None, E2EFramePointer::All);
assert_ne!(E2EFramePointer::NonLeaf, E2EFramePointer::Default);
}
#[test]
fn test_compile_options_sanitizers() {
let mut opts = E2ECompileOptions::default();
opts.sanitize.push(E2ESanitizer::Address);
opts.sanitize.push(E2ESanitizer::Undefined);
assert_eq!(opts.sanitize.len(), 2);
}
#[test]
fn test_compile_options_library_paths() {
let mut opts = E2ECompileOptions::default();
opts.library_paths.push(PathBuf::from("/usr/lib"));
opts.library_paths.push(PathBuf::from("/usr/local/lib"));
opts.libraries.push("m".to_string());
opts.libraries.push("pthread".to_string());
assert_eq!(opts.library_paths.len(), 2);
assert_eq!(opts.libraries.len(), 2);
}
#[test]
fn test_compile_options_linker_flags() {
let mut opts = E2ECompileOptions::default();
opts.linker_flags
.push("-Wl,-rpath,/usr/local/lib".to_string());
assert_eq!(opts.linker_flags.len(), 1);
}
#[test]
fn test_compile_options_sysroot() {
let mut opts = E2ECompileOptions::default();
opts.sysroot = Some(PathBuf::from("/sysroot"));
assert_eq!(opts.sysroot, Some(PathBuf::from("/sysroot")));
}
#[test]
fn test_pipeline_dry_run_does_not_affect_result() {
let opts = E2ECompileOptions::default();
let mut pipeline = X86E2EPipeline::with_options(opts);
pipeline.options.dry_run = true;
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
let dir = std::env::temp_dir().join("e2e_dry_run");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_pipeline_verbose_flag() {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.verbose = true;
assert!(pipeline.options.verbose);
}
#[test]
fn test_pipeline_time_flag() {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.time = true;
assert!(pipeline.options.time);
}
#[test]
fn test_integration_compile_and_verify() {
let dir = std::env::temp_dir().join("e2e_integration");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("hello.c");
fs::write(
&source_path,
"#include <stdio.h>\nint main() { printf(\"ok\\n\"); return 0; }\n",
)
.unwrap();
let mut suite = X86E2ETestSuite::new("integration");
let program = E2ETestProgram::c(
"hello",
"#include <stdio.h>\nint main() { printf(\"ok\\n\"); return 0; }\n",
)
.expect_stdout("ok\n")
.expect_exit(0);
suite.add(program);
let mut compiler = X86E2ECompiler::new();
compiler.options_mut().output_kind = E2EOutputKind::PreprocessedSource;
let result = compiler.compile_c(&source_path);
assert!(result.is_ok());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_multiple_sources() {
let dir = std::env::temp_dir().join("e2e_multi_integration");
let _ = fs::create_dir_all(&dir);
let src1 = dir.join("a.c");
let src2 = dir.join("b.c");
fs::write(&src1, "int add(int x, int y) { return x + y; }\n").unwrap();
fs::write(&src2, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = E2EOutputKind::Object;
pipeline.add_sources(&[src1, src2]).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_pipeline_with_all_output_kinds() {
let dir = std::env::temp_dir().join("e2e_all_kinds");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let kinds = [
E2EOutputKind::PreprocessedSource,
E2EOutputKind::LLVMIR,
E2EOutputKind::Assembly,
E2EOutputKind::Object,
E2EOutputKind::SharedLibrary,
E2EOutputKind::Executable,
];
for kind in &kinds {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.output_kind = *kind;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success, "Failed for output kind {:?}", kind);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_optimization_levels() {
let dir = std::env::temp_dir().join("e2e_opt_levels");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let opt_levels = [
E2EOptLevel::O0,
E2EOptLevel::O1,
E2EOptLevel::O2,
E2EOptLevel::O3,
E2EOptLevel::Os,
E2EOptLevel::Oz,
];
for opt in &opt_levels {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.opt_level = *opt;
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success, "Failed for {:?}", opt);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_language_standards() {
let dir = std::env::temp_dir().join("e2e_standards");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main(void) { return 0; }\n").unwrap();
let standards = [
CLangStandard::C89,
CLangStandard::C99,
CLangStandard::C11,
CLangStandard::C17,
CLangStandard::C23,
];
for std in &standards {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.standard = *std;
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success, "Failed for {:?}", std);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_target_cpus() {
let dir = std::env::temp_dir().join("e2e_cpus");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let cpus = ["generic", "haswell", "skylake", "znver3"];
for cpu in &cpus {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.target_cpu = cpu.to_string();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success, "Failed for CPU {}", cpu);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_sanitizers() {
let dir = std::env::temp_dir().join("e2e_sanitizers");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let sanitizers = [E2ESanitizer::Address, E2ESanitizer::Undefined];
for san in &sanitizers {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.sanitize = vec![*san];
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success, "Failed for sanitizer {:?}", san);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_code_models() {
let dir = std::env::temp_dir().join("e2e_code_models");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let models = [
E2ECodeModel::Small,
E2ECodeModel::Medium,
E2ECodeModel::Large,
E2ECodeModel::Kernel,
];
for model in &models {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.code_model = *model;
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_cpu_features() {
let dir = std::env::temp_dir().join("e2e_features");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let feature_sets = ["sse2", "avx2,fma", "avx512f,avx512bw,avx512dq"];
for features in &feature_sets {
let mut pipeline = X86E2EPipeline::new();
pipeline.options.target_features = features.to_string();
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
}
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_integration_combined_flags() {
let dir = std::env::temp_dir().join("e2e_combined");
let _ = fs::create_dir_all(&dir);
let source_path = dir.join("test.c");
fs::write(&source_path, "int main() { return 0; }\n").unwrap();
let mut pipeline = X86E2EPipeline::new();
pipeline.options.opt_level = E2EOptLevel::O3;
pipeline.options.lto = true;
pipeline.options.pic = true;
pipeline.options.pie = true;
pipeline.options.wall = true;
pipeline.options.debug_info = true;
pipeline.options.sanitize = vec![E2ESanitizer::Undefined];
pipeline.options.output_kind = E2EOutputKind::PreprocessedSource;
pipeline.add_source(&source_path).unwrap();
let result = pipeline.run();
assert!(result.success);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_stage_ordering_is_correct() {
let stages = E2EStage::all();
for i in 0..stages.len() - 1 {
assert_eq!(stages[i].next(), Some(stages[i + 1]));
assert_eq!(stages[i + 1].prev(), Some(stages[i]));
}
}
#[test]
fn test_cache_stats_hit_rate_perfect() {
let dir = std::env::temp_dir().join("e2e_cache_perfect");
let _ = fs::create_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
let results = vec![E2EStageResult::success(
E2EStage::Parse,
Duration::from_millis(1),
)];
cache.store("k", results);
cache.lookup("k");
assert_eq!(cache.stats().hit_rate(), 100.0);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_language_display_all() {
assert_eq!(format!("{}", E2ELanguage::C), "C");
assert_eq!(format!("{}", E2ELanguage::Cpp), "C++");
assert_eq!(format!("{}", E2ELanguage::ObjC), "Objective-C");
assert_eq!(format!("{}", E2ELanguage::OpenCL), "OpenCL");
assert_eq!(format!("{}", E2ELanguage::Cuda), "CUDA");
}
#[test]
fn test_opt_level_to_llvm_opt_level() {
assert_eq!(E2EOptLevel::O0.to_llvm_opt_level(), 0);
assert_eq!(E2EOptLevel::O1.to_llvm_opt_level(), 1);
assert_eq!(E2EOptLevel::O2.to_llvm_opt_level(), 2);
assert_eq!(E2EOptLevel::O3.to_llvm_opt_level(), 3);
}
#[test]
fn test_language_default_extension() {
assert_eq!(E2ELanguage::C.default_extension(), ".c");
assert_eq!(E2ELanguage::Cpp.default_extension(), ".cpp");
assert_eq!(E2ELanguage::ObjC.default_extension(), ".m");
assert_eq!(E2ELanguage::ObjCpp.default_extension(), ".mm");
assert_eq!(E2ELanguage::OpenCL.default_extension(), ".cl");
assert_eq!(E2ELanguage::Cuda.default_extension(), ".cu");
}
#[test]
fn test_stage_abbrev_all_unique() {
let stages = E2EStage::all();
let abbrevs: HashSet<&str> = stages.iter().map(|s| s.abbrev()).collect();
assert_eq!(abbrevs.len(), stages.len());
}
#[test]
fn test_suite_add_custom_program() {
let mut suite = X86E2ETestSuite::new("custom");
let program = E2ETestProgram::c(
"custom_test",
"#include <stdio.h>\nint main() { printf(\"custom\\n\"); return 0; }\n",
)
.expect_stdout("custom\n")
.expect_exit(0);
suite.add(program);
assert_eq!(suite.len(), 1);
}
#[test]
fn test_pipeline_result_cached_stages_count() {
let stage_results = vec![
E2EStageResult::success(E2EStage::Preprocess, Duration::from_millis(1))
.from_cache("key1"),
E2EStageResult::success(E2EStage::Lex, Duration::from_millis(2)),
E2EStageResult::success(E2EStage::Parse, Duration::from_millis(3)).from_cache("key3"),
];
let result = E2EPipelineResult::success_with(
stage_results,
Duration::from_millis(6),
None,
None,
None,
);
assert_eq!(result.cached_stages, 2);
}
#[test]
fn test_cache_saturation() {
let dir = std::env::temp_dir().join("e2e_cache_saturation");
let _ = fs::create_dir_all(&dir);
let mut cache = E2ECompilationCache::new(&dir);
for i in 0..500 {
let result = E2EStageResult::success(E2EStage::Parse, Duration::from_millis(1));
cache.store(&format!("saturate_{}", i), vec![result]);
}
assert!(cache.stats().entry_count > 0);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_artifact_kind_display_all() {
assert_eq!(format!("{}", E2EArtifactKind::SourceFile), "source-file");
assert_eq!(format!("{}", E2EArtifactKind::ObjectFile), "object-file");
assert_eq!(
format!("{}", E2EArtifactKind::SharedLibrary),
"shared-library"
);
assert_eq!(format!("{}", E2EArtifactKind::LLVMIR), "llvm-ir");
assert_eq!(format!("{}", E2EArtifactKind::Binary), "binary");
}
#[test]
fn test_output_kind_display_all() {
assert_eq!(
format!("{}", E2EOutputKind::PreprocessedSource),
"preprocessed-source"
);
assert_eq!(format!("{}", E2EOutputKind::LLVMIR), "llvm-ir");
assert_eq!(format!("{}", E2EOutputKind::Assembly), "assembly");
assert_eq!(format!("{}", E2EOutputKind::Object), "object");
assert_eq!(format!("{}", E2EOutputKind::Executable), "executable");
assert_eq!(
format!("{}", E2EOutputKind::SharedLibrary),
"shared-library"
);
}
#[test]
fn test_stage_result_complete_pipeline_trace() {
let mut results = Vec::new();
for stage in E2EStage::all() {
results.push(
E2EStageResult::success(stage, Duration::from_millis(1))
.with_summary(&format!("completed {}", stage.name())),
);
}
let result =
E2EPipelineResult::success_with(results, Duration::from_millis(13), None, None, None);
assert!(result.success);
assert_eq!(result.stage_results.len(), 13);
}
}