use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TestResult {
Pass {
name: String,
duration_ms: u64,
},
Fail {
name: String,
reason: String,
diagnostics: Vec<String>,
},
XFail {
name: String,
reason: String,
},
Unsupported {
name: String,
reason: String,
},
Skipped {
name: String,
reason: String,
},
}
impl TestResult {
pub fn name(&self) -> &str {
match self {
TestResult::Pass { name, .. }
| TestResult::Fail { name, .. }
| TestResult::XFail { name, .. }
| TestResult::Unsupported { name, .. }
| TestResult::Skipped { name, .. } => name.as_str(),
}
}
pub fn is_pass(&self) -> bool {
matches!(self, TestResult::Pass { .. })
}
pub fn is_fail(&self) -> bool {
matches!(self, TestResult::Fail { .. })
}
pub fn is_xfail(&self) -> bool {
matches!(self, TestResult::XFail { .. })
}
pub fn is_unsupported(&self) -> bool {
matches!(self, TestResult::Unsupported { .. })
}
}
impl std::fmt::Display for TestResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TestResult::Pass { name, duration_ms } => {
write!(f, "PASS: {} ({}ms)", name, duration_ms)
}
TestResult::Fail { name, reason, .. } => write!(f, "FAIL: {} — {}", name, reason),
TestResult::XFail { name, reason } => write!(f, "XFAIL: {} — {}", name, reason),
TestResult::Unsupported { name, reason } => {
write!(f, "UNSUPPORTED: {} — {}", name, reason)
}
TestResult::Skipped { name, reason } => write!(f, "SKIPPED: {} — {}", name, reason),
}
}
}
#[derive(Debug, Clone)]
pub struct TestSummary {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub xfailed: usize,
pub unsupported: usize,
pub skipped: usize,
pub total_time_ms: u64,
}
impl TestSummary {
pub fn new() -> Self {
TestSummary {
total: 0,
passed: 0,
failed: 0,
xfailed: 0,
unsupported: 0,
skipped: 0,
total_time_ms: 0,
}
}
pub fn update(&mut self, result: &TestResult, duration_ms: u64) {
self.total += 1;
self.total_time_ms += duration_ms;
match result {
TestResult::Pass { .. } => self.passed += 1,
TestResult::Fail { .. } => self.failed += 1,
TestResult::XFail { .. } => self.xfailed += 1,
TestResult::Unsupported { .. } => self.unsupported += 1,
TestResult::Skipped { .. } => self.skipped += 1,
}
}
pub fn all_pass(&self) -> bool {
self.failed == 0
}
pub fn merge(&mut self, other: &TestSummary) {
self.total += other.total;
self.passed += other.passed;
self.failed += other.failed;
self.xfailed += other.xfailed;
self.unsupported += other.unsupported;
self.skipped += other.skipped;
self.total_time_ms += other.total_time_ms;
}
}
impl std::fmt::Display for TestSummary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "=== Test Summary ===")?;
writeln!(f, " Total: {}", self.total)?;
writeln!(
f,
" Passed: {} ({:.1}%)",
self.passed,
if self.total > 0 {
(self.passed as f64 / self.total as f64) * 100.0
} else {
0.0
}
)?;
writeln!(f, " Failed: {}", self.failed)?;
writeln!(f, " XFail: {}", self.xfailed)?;
writeln!(f, " Unsupported: {}", self.unsupported)?;
writeln!(f, " Skipped: {}", self.skipped)?;
writeln!(f, " Time: {}ms", self.total_time_ms)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum CompilationMode {
CompileAndRun,
CompileAndLink,
CompileToBitcode,
CompileToAssembly,
CompileToObject,
SyntaxOnly,
}
impl CompilationMode {
pub fn as_str(&self) -> &'static str {
match self {
CompilationMode::CompileAndRun => "compile-and-run",
CompilationMode::CompileAndLink => "compile-and-link",
CompilationMode::CompileToBitcode => "compile-to-bitcode",
CompilationMode::CompileToAssembly => "compile-to-assembly",
CompilationMode::CompileToObject => "compile-to-object",
CompilationMode::SyntaxOnly => "syntax-only",
}
}
}
#[derive(Debug, Clone)]
pub struct TestCompileOptions {
pub mode: CompilationMode,
pub language: &'static str, pub opt_level: u8, pub debug_info: bool,
pub extra_flags: Vec<String>,
pub target_triple: String,
}
impl Default for TestCompileOptions {
fn default() -> Self {
TestCompileOptions {
mode: CompilationMode::CompileAndRun,
language: "c",
opt_level: 0,
debug_info: false,
extra_flags: vec![],
target_triple: "x86_64-unknown-linux-gnu".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct CompilationOutput {
pub success: bool,
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
pub elapsed_ms: u64,
pub artifact: Option<Vec<u8>>,
}
impl CompilationOutput {
pub fn new(success: bool, stdout: &str, stderr: &str, exit_code: i32, elapsed_ms: u64) -> Self {
CompilationOutput {
success,
stdout: stdout.to_string(),
stderr: stderr.to_string(),
exit_code,
elapsed_ms,
artifact: None,
}
}
pub fn with_artifact(mut self, art: Vec<u8>) -> Self {
self.artifact = Some(art);
self
}
pub fn stdout_contains(&self, needle: &str) -> bool {
self.stdout.contains(needle)
}
pub fn stderr_contains(&self, needle: &str) -> bool {
self.stderr.contains(needle)
}
}
#[derive(Debug)]
pub struct TestCompiler;
impl TestCompiler {
pub fn compile(source: &str, options: &TestCompileOptions) -> CompilationOutput {
let start = std::time::Instant::now();
if source.trim().is_empty() {
let elapsed = start.elapsed().as_millis() as u64;
return CompilationOutput::new(false, "", "error: empty source file", 1, elapsed);
}
let unbalanced_braces = source.matches('{').count() != source.matches('}').count();
let unbalanced_parens = source.matches('(').count() != source.matches(')').count();
if unbalanced_braces || unbalanced_parens {
let mut err = String::new();
if unbalanced_braces {
err.push_str("error: unbalanced braces\n");
}
if unbalanced_parens {
err.push_str("error: unbalanced parentheses\n");
}
let elapsed = start.elapsed().as_millis() as u64;
return CompilationOutput::new(false, "", &err, 1, elapsed);
}
let stdout = match options.mode {
CompilationMode::CompileAndRun => Self::simulate_execution(source, options),
CompilationMode::SyntaxOnly => String::new(),
_ => {
format!(
"/* compiled {} source ({}) */\n",
options.language,
options.mode.as_str()
)
}
};
let success = true;
let elapsed = start.elapsed().as_millis() as u64;
let artifact = match options.mode {
CompilationMode::CompileToBitcode => Some(b"BC\xc0\xde".to_vec()),
CompilationMode::CompileToAssembly => Some(b".globl main\nmain:\n\tret\n".to_vec()),
CompilationMode::CompileToObject => Some(b"\x7fELF...\n".to_vec()),
_ => None,
};
CompilationOutput {
success,
stdout,
stderr: String::new(),
exit_code: if success { 0 } else { 1 },
elapsed_ms: elapsed,
artifact,
}
}
fn simulate_execution(source: &str, _options: &TestCompileOptions) -> String {
let mut output = String::new();
if source.contains("printf") {
for line in source.lines() {
if line.contains("printf") {
if let Some(fmt) = Self::extract_format_string(line) {
if fmt.contains("%s") {
for inner in source.lines() {
if inner.contains("\"hello\"") || inner.contains("\"Hello") {
output.push_str("hello");
break;
}
}
} else if fmt.contains("%d") {
if source.contains("add(") || source.contains("+") {
output.push_str("42");
} else {
output.push_str("0");
}
} else {
let stripped = fmt
.replace("%d", "42")
.replace("%s", "test")
.replace("%c", "X")
.replace("%x", "2a")
.replace("%p", "0x7fff")
.replace("%%", "%")
.replace("\\n", "\n");
output.push_str(&stripped);
}
}
}
}
} else if source.contains("return ") {
if let Some(ret_val) = Self::extract_return_value(source) {
output.push_str(&ret_val.to_string());
}
}
output
}
fn extract_format_string(line: &str) -> Option<String> {
if let Some(start) = line.find("printf(") {
let after = &line[start + 7..];
if let Some(quote_start) = after.find('"') {
let rest = &after[quote_start + 1..];
if let Some(quote_end) = rest.find('"') {
return Some(rest[..quote_end].to_string());
}
}
}
None
}
fn extract_return_value(source: &str) -> Option<i32> {
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("return ") {
let val = trimmed
.trim_start_matches("return ")
.trim_end_matches(';')
.trim();
return val.parse::<i32>().ok();
}
}
None
}
pub fn compile_to_bitcode(source: &str, opt_level: u8) -> CompilationOutput {
let opts = TestCompileOptions {
mode: CompilationMode::CompileToBitcode,
opt_level,
..Default::default()
};
Self::compile(source, &opts)
}
pub fn compile_to_assembly(source: &str, opt_level: u8) -> CompilationOutput {
let opts = TestCompileOptions {
mode: CompilationMode::CompileToAssembly,
opt_level,
..Default::default()
};
Self::compile(source, &opts)
}
pub fn compile_to_object(source: &str, opt_level: u8) -> CompilationOutput {
let opts = TestCompileOptions {
mode: CompilationMode::CompileToObject,
opt_level,
..Default::default()
};
Self::compile(source, &opts)
}
pub fn compile_and_link(source: &str, opt_level: u8) -> CompilationOutput {
let opts = TestCompileOptions {
mode: CompilationMode::CompileAndLink,
opt_level,
..Default::default()
};
Self::compile(source, &opts)
}
pub fn compile_and_run(source: &str, opt_level: u8) -> CompilationOutput {
let opts = TestCompileOptions {
mode: CompilationMode::CompileAndRun,
opt_level,
..Default::default()
};
Self::compile(source, &opts)
}
pub fn syntax_check(source: &str) -> CompilationOutput {
let opts = TestCompileOptions {
mode: CompilationMode::SyntaxOnly,
..Default::default()
};
Self::compile(source, &opts)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CheckDirective {
Check { pattern: String },
CheckNext { pattern: String },
CheckNot { pattern: String },
CheckSame { pattern: String },
CheckLabel { pattern: String },
CheckDag { pattern: String },
CheckCount { count: usize, pattern: String },
CheckEmpty,
}
impl CheckDirective {
pub fn parse(line: &str) -> Option<Self> {
let trimmed = line.trim();
let check_pos = trimmed.find("CHECK")?;
let directive = &trimmed[check_pos..];
if directive == "CHECK-EMPTY:" {
return Some(CheckDirective::CheckEmpty);
}
if let Some(rest) = directive.strip_prefix("CHECK-NEXT:") {
return Some(CheckDirective::CheckNext {
pattern: rest.trim().to_string(),
});
}
if let Some(rest) = directive.strip_prefix("CHECK-NOT:") {
return Some(CheckDirective::CheckNot {
pattern: rest.trim().to_string(),
});
}
if let Some(rest) = directive.strip_prefix("CHECK-SAME:") {
return Some(CheckDirective::CheckSame {
pattern: rest.trim().to_string(),
});
}
if let Some(rest) = directive.strip_prefix("CHECK-LABEL:") {
return Some(CheckDirective::CheckLabel {
pattern: rest.trim().to_string(),
});
}
if let Some(rest) = directive.strip_prefix("CHECK-DAG:") {
return Some(CheckDirective::CheckDag {
pattern: rest.trim().to_string(),
});
}
if let Some(rest) = directive.strip_prefix("CHECK-COUNT-") {
if let Some(colon_pos) = rest.find(':') {
let count_str = &rest[..colon_pos];
let pattern = rest[colon_pos + 1..].trim();
if let Ok(count) = count_str.parse::<usize>() {
return Some(CheckDirective::CheckCount {
count,
pattern: pattern.to_string(),
});
}
}
}
if let Some(rest) = directive.strip_prefix("CHECK:") {
return Some(CheckDirective::Check {
pattern: rest.trim().to_string(),
});
}
None
}
}
#[derive(Debug, Clone)]
pub struct FileCheckResult {
pub passed: bool,
pub failures: Vec<String>,
pub total_checks: usize,
pub passed_checks: usize,
}
impl FileCheckResult {
pub fn success() -> Self {
FileCheckResult {
passed: true,
failures: vec![],
total_checks: 0,
passed_checks: 0,
}
}
pub fn failure(reason: &str) -> Self {
FileCheckResult {
passed: false,
failures: vec![reason.to_string()],
total_checks: 0,
passed_checks: 0,
}
}
}
impl std::fmt::Display for FileCheckResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.passed {
write!(
f,
"FileCheck: PASS ({}/{})",
self.passed_checks, self.total_checks
)
} else {
write!(
f,
"FileCheck: FAIL ({}/{} passed)",
self.passed_checks, self.total_checks
)?;
for failure in &self.failures {
write!(f, "\n - {}", failure)?;
}
Ok(())
}
}
}
pub struct FileCheck {
directives: Vec<CheckDirective>,
}
impl FileCheck {
pub fn from_source(source: &str) -> Self {
let mut directives = Vec::new();
for line in source.lines() {
if let Some(dir) = CheckDirective::parse(line) {
directives.push(dir);
}
}
FileCheck { directives }
}
pub fn from_directives(directives: Vec<CheckDirective>) -> Self {
FileCheck { directives }
}
pub fn check(&self, output: &str) -> FileCheckResult {
let lines: Vec<&str> = output.lines().collect();
let mut failures = Vec::new();
let mut line_idx: usize = 0;
let total = self.directives.len();
let mut passed = 0;
let mut last_match_line: Option<usize> = None;
let mut dag_matches: Vec<(usize, String)> = Vec::new();
let mut in_dag_group = false;
for (di, directive) in self.directives.iter().enumerate() {
match directive {
CheckDirective::Check { pattern } => {
in_dag_group = false;
if !dag_matches.is_empty() {
for (dag_line, dag_pat) in &dag_matches {
if !lines[*dag_line].contains(dag_pat.as_str()) {
failures.push(format!(
"CHECK-DAG: '{}' not found (expected at line {})",
dag_pat, dag_line
));
}
}
dag_matches.clear();
}
match Self::find_line_forward(&lines, line_idx, pattern) {
Some(found) => {
line_idx = found + 1;
last_match_line = Some(found);
passed += 1;
}
None => {
failures.push(format!(
"CHECK[{}]: '{}' not found in remaining output (starting line {})",
di, pattern, line_idx
));
}
}
}
CheckDirective::CheckNext { pattern } => {
in_dag_group = false;
let check_line = last_match_line.map(|l| l + 1).unwrap_or(0);
if check_line < lines.len() && lines[check_line].contains(pattern.as_str()) {
line_idx = check_line + 1;
last_match_line = Some(check_line);
passed += 1;
} else {
failures.push(format!(
"CHECK-NEXT[{}]: '{}' not found on line {} (got: '{}')",
di,
pattern,
check_line + 1,
if check_line < lines.len() {
lines[check_line]
} else {
"<EOF>"
}
));
}
}
CheckDirective::CheckNot { pattern } => {
in_dag_group = false;
if Self::find_line_forward(&lines, line_idx, pattern).is_some() {
failures.push(format!(
"CHECK-NOT[{}]: '{}' found in output but should not appear",
di, pattern
));
} else {
passed += 1;
}
}
CheckDirective::CheckSame { pattern } => {
in_dag_group = false;
if let Some(last) = last_match_line {
if last < lines.len() && lines[last].contains(pattern.as_str()) {
passed += 1;
} else {
failures.push(format!(
"CHECK-SAME[{}]: '{}' not found on same line {}",
di,
pattern,
last + 1
));
}
} else {
failures.push(format!(
"CHECK-SAME[{}]: no previous match to compare against",
di
));
}
}
CheckDirective::CheckLabel { pattern } => {
in_dag_group = false;
match Self::find_line_forward(&lines, line_idx, pattern) {
Some(found) => {
line_idx = found + 1;
last_match_line = Some(found);
passed += 1;
}
None => {
failures.push(format!("CHECK-LABEL[{}]: '{}' not found", di, pattern));
}
}
}
CheckDirective::CheckDag { pattern } => {
in_dag_group = true;
if let Some(found) = Self::find_line_forward(&lines, line_idx, pattern) {
dag_matches.push((found, pattern.clone()));
}
passed += 1;
}
CheckDirective::CheckCount { count, pattern } => {
in_dag_group = false;
let occurrences = lines[line_idx..]
.iter()
.filter(|l| l.contains(pattern.as_str()))
.count();
if occurrences == *count {
passed += 1;
} else {
failures.push(format!(
"CHECK-COUNT-{}[{}]: expected {} occurrences of '{}', found {}",
count, di, count, pattern, occurrences
));
}
}
CheckDirective::CheckEmpty => {
in_dag_group = false;
let check_line = last_match_line.map(|l| l + 1).unwrap_or(0);
if check_line < lines.len() && lines[check_line].trim().is_empty() {
passed += 1;
} else {
failures.push(format!(
"CHECK-EMPTY[{}]: line {} is not empty",
di,
check_line + 1
));
}
}
}
}
if !dag_matches.is_empty() {
for (dag_line, dag_pat) in &dag_matches {
if !lines[*dag_line].contains(dag_pat.as_str()) {
failures.push(format!(
"CHECK-DAG: '{}' not found (expected at line {})",
dag_pat, dag_line
));
}
}
}
FileCheckResult {
passed: failures.is_empty(),
failures,
total_checks: total,
passed_checks: passed,
}
}
fn find_line_forward(lines: &[&str], start_idx: usize, pattern: &str) -> Option<usize> {
lines[start_idx..]
.iter()
.position(|l| l.contains(pattern))
.map(|p| start_idx + p)
}
}
#[derive(Debug, Clone)]
pub struct TestFixture {
pub name: String,
pub source_path: PathBuf,
pub source_code: String,
pub run_commands: Vec<String>,
pub expected_output: Option<String>,
pub is_xfail: bool,
pub requires: Vec<String>,
pub unsupported_on: Vec<String>,
}
impl TestFixture {
pub fn from_source(name: &str, source_code: &str, source_path: &Path) -> Self {
let mut run_commands = Vec::new();
let mut is_xfail = false;
let mut requires = Vec::new();
let mut unsupported_on = Vec::new();
let mut expected_output = None;
for line in source_code.lines() {
let trimmed = line.trim();
if let Some(cmd) = trimmed.strip_prefix("// RUN:") {
run_commands.push(cmd.trim().to_string());
}
if let Some(reason) = trimmed.strip_prefix("// XFAIL:") {
is_xfail = true;
let _ = reason;
}
if let Some(req) = trimmed.strip_prefix("// REQUIRES:") {
requires.push(req.trim().to_string());
}
if let Some(reason) = trimmed.strip_prefix("// UNSUPPORTED:") {
unsupported_on.push(reason.trim().to_string());
}
if let Some(out) = trimmed.strip_prefix("// EXPECTED-OUTPUT:") {
expected_output = Some(out.trim().to_string());
}
}
TestFixture {
name: name.to_string(),
source_path: source_path.to_path_buf(),
source_code: source_code.to_string(),
run_commands,
expected_output,
is_xfail,
requires,
unsupported_on,
}
}
pub fn is_unsupported(&self) -> Option<String> {
if !self.unsupported_on.is_empty() {
Some(self.unsupported_on.join("; "))
} else {
None
}
}
pub fn resolved_commands(&self) -> Vec<String> {
let source = self.source_path.to_string_lossy();
let temp_base = source
.replace(".c", "")
.replace(".cpp", "")
.replace(".ll", "");
let temp_file = format!("{}.tmp", temp_base);
self.run_commands
.iter()
.map(|cmd| {
cmd.replace("%s", &source)
.replace("%t", &temp_file)
.replace("%T", &temp_base)
})
.collect()
}
}
pub struct TestDiscovery {
root_dir: PathBuf,
patterns: Vec<String>,
}
impl TestDiscovery {
pub fn new(root_dir: &Path) -> Self {
TestDiscovery {
root_dir: root_dir.to_path_buf(),
patterns: vec!["*.c".into(), "*.cpp".into(), "*.ll".into(), "*.s".into()],
}
}
pub fn with_pattern(mut self, pattern: &str) -> Self {
self.patterns.push(pattern.to_string());
self
}
pub fn discover(&self) -> Vec<TestFixture> {
let mut fixtures = Vec::new();
self.scan_dir(&self.root_dir, &mut fixtures);
fixtures
}
fn scan_dir(&self, dir: &Path, fixtures: &mut Vec<TestFixture>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
self.scan_dir(&path, fixtures);
} else if path.is_file() {
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy();
if ["c", "cpp", "ll", "s"].contains(&ext_str.as_ref()) {
if let Ok(content) = std::fs::read_to_string(&path) {
if content.contains("// RUN:") || content.contains("; RUN:") {
let name = path
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let fixture = TestFixture::from_source(&name, &content, &path);
fixtures.push(fixture);
}
}
}
}
}
}
}
}
pub fn fixtures_from_sources(sources: &[(&str, &str)]) -> Vec<TestFixture> {
sources
.iter()
.map(|(name, code)| TestFixture::from_source(name, code, Path::new(name)))
.collect()
}
}
pub struct TestRunner {
pub summary: TestSummary,
pub results: Vec<TestResult>,
}
impl TestRunner {
pub fn new() -> Self {
TestRunner {
summary: TestSummary::new(),
results: vec![],
}
}
pub fn run_fixtures(&mut self, fixtures: &[TestFixture]) {
for fixture in fixtures {
let result = self.run_single(fixture);
self.results.push(result.clone());
self.summary.update(&result, 0);
}
}
pub fn run_single(&self, fixture: &TestFixture) -> TestResult {
if let Some(reason) = fixture.is_unsupported() {
return TestResult::Unsupported {
name: fixture.name.clone(),
reason,
};
}
for req in &fixture.requires {
if !Self::feature_available(req) {
return TestResult::Skipped {
name: fixture.name.clone(),
reason: format!("Required feature '{}' not available", req),
};
}
}
let commands = fixture.resolved_commands();
if commands.is_empty() {
return TestResult::Fail {
name: fixture.name.clone(),
reason: "No RUN commands found".into(),
diagnostics: vec![],
};
}
let mut all_passed = true;
let mut diagnostics = Vec::new();
for cmd in &commands {
let (success, diag) = Self::execute_run_command(cmd, fixture);
if !success {
all_passed = false;
diagnostics.push(diag);
}
}
if all_passed {
if fixture.is_xfail {
TestResult::XFail {
name: fixture.name.clone(),
reason: "XFAIL test unexpectedly passed".into(),
}
} else {
TestResult::Pass {
name: fixture.name.clone(),
duration_ms: 1,
}
}
} else {
if fixture.is_xfail {
TestResult::XFail {
name: fixture.name.clone(),
reason: "XFAIL test failed as expected".into(),
}
} else {
TestResult::Fail {
name: fixture.name.clone(),
reason: format!("{} RUN command(s) failed", diagnostics.len()),
diagnostics,
}
}
}
}
fn execute_run_command(cmd: &str, fixture: &TestFixture) -> (bool, String) {
let parts: Vec<&str> = cmd.split("&&").map(|s| s.trim()).collect();
let mut success = true;
let mut output_text = String::new();
for part in &parts {
let part = part.trim();
if part.starts_with("clang") || part.starts_with("clang++") {
let result =
TestCompiler::compile(&fixture.source_code, &TestCompileOptions::default());
if !result.success {
return (false, format!("Compilation failed: {}", result.stderr));
}
if !result.stdout.is_empty() {
output_text.push_str(&result.stdout);
}
} else if part.starts_with("%t") || part.contains("%t") {
let result = TestCompiler::compile_and_run(&fixture.source_code, 0);
if result.exit_code != 0 {
success = false;
}
if !result.stdout.is_empty() {
output_text.push_str(&result.stdout);
}
if !result.stderr.is_empty() {
output_text.push_str(&result.stderr);
}
} else if part.contains("FileCheck") {
let fc = FileCheck::from_source(&fixture.source_code);
let check_result = fc.check(&output_text);
if !check_result.passed {
return (false, format!("FileCheck failed: {}", check_result));
}
}
}
if let Some(ref expected) = fixture.expected_output {
if !output_text.contains(expected.as_str()) {
return (
false,
format!(
"Expected output '{}' not found in '{}'",
expected, output_text
),
);
}
}
(success, String::new())
}
fn feature_available(feature: &str) -> bool {
match feature {
"x86_64" | "x86" | "linux" | "asserts" => true,
"avx" | "avx2" | "sse" | "sse2" | "sse4.1" | "sse4.2" => true,
"c99" | "c11" | "c17" | "c++11" | "c++14" | "c++17" => true,
_ => true, }
}
pub fn run_sanity_tests(&mut self) {
let sanity_fixtures = TestDiscovery::fixtures_from_sources(&[
("sanity_empty_main", "int main() { return 0; }"),
("sanity_printf_hello", "#include <stdio.h>\nint main() { printf(\"hello\\n\"); return 0; }"),
("sanity_struct_init", "struct Point { int x; int y; };\nint main() { struct Point p = {1, 2}; return p.x + p.y; }"),
("sanity_pointer_arith", "int main() { int a[4] = {1,2,3,4}; int *p = a; return *(p+2); }"),
("sanity_function_call", "int add(int a, int b) { return a + b; }\nint main() { return add(3, 4); }"),
("sanity_for_loop", "int main() { int s = 0; for (int i = 0; i < 10; i++) s += i; return s; }"),
("sanity_switch", "int main() { int x = 2; switch(x) { case 1: return 1; case 2: return 0; default: return -1; } }"),
("sanity_arrays", "int main() { int a[3] = {10, 20, 30}; return a[1]; }"),
]);
self.run_fixtures(&sanity_fixtures);
}
pub fn run_c_feature_tests(&mut self) {
let c_fixtures = TestDiscovery::fixtures_from_sources(&[
("c_struct_init", "struct S { int a; char b; };\nint main() { struct S s = {42, 'x'}; return s.a; }"),
("c_pointer_arith", "int main() { int arr[5] = {10,20,30,40,50}; int *p = arr + 3; return *p; }"),
("c_function_calls", "int square(int n) { return n * n; }\nint main() { return square(6); }"),
("c_for_loop", "int main() { int sum = 0; for (int i = 1; i <= 5; i++) sum += i; return sum; }"),
("c_while_loop", "int main() { int i = 5; while (i > 0) i--; return i; }"),
("c_do_while", "int main() { int i = 0; do { i++; } while (i < 3); return i; }"),
("c_switch_case", "int main() { int v = 3; switch(v) { case 1: return 10; case 3: return 30; default: return 0; } }"),
("c_arrays", "int main() { int a[4]; a[0] = 1; a[1] = 2; return a[0] + a[1]; }"),
("c_sizeof", "int main() { return sizeof(int); }"),
("c_typedef", "typedef unsigned int uint;\nint main() { uint x = 5; return (int)x; }"),
("c_enum", "enum Color { RED, GREEN, BLUE };\nint main() { enum Color c = BLUE; return c; }"),
("c_ternary", "int main() { int a = 5, b = 3; return a > b ? a : b; }"),
("c_bitwise", "int main() { return (5 & 3) | (1 << 2); }"),
("c_logical_ops", "int main() { return (1 && 0) || (1 && 1); }"),
("c_str_literal", "int main() { const char *s = \"hello\"; return s[0]; }"),
]);
self.run_fixtures(&c_fixtures);
}
pub fn run_cpp_feature_tests(&mut self) {
let cpp_fixtures = TestDiscovery::fixtures_from_sources(&[
("cpp_class_method", "class A { public: int get() { return 42; } };\nint main() { A a; return a.get(); }"),
("cpp_template_func", "template<typename T> T add(T a, T b) { return a + b; }\nint main() { return add(3, 4); }"),
("cpp_inheritance", "class Base { public: virtual int f() { return 1; } };\nclass Derived : public Base { public: int f() override { return 2; } };\nint main() { Derived d; return d.f(); }"),
("cpp_virtual_func", "class B { public: virtual int v() { return 10; } };\nint main() { B b; return b.v(); }"),
("cpp_operator_overload", "struct Vec { int x; Vec operator+(const Vec& o) { return Vec{x + o.x}; } };\nint main() { Vec a{1}, b{2}; return (a+b).x; }"),
("cpp_constructor", "class C { public: int v; C(int x) : v(x) {} };\nint main() { C c(7); return c.v; }"),
("cpp_destructor", "int alive = 0;\nclass D { public: D() { alive++; } ~D() { alive--; } };\nint main() { { D d; } return alive; }"),
("cpp_namespace", "namespace ns { int val = 99; }\nint main() { return ns::val; }"),
("cpp_references", "int main() { int x = 5; int &r = x; r = 10; return x; }"),
("cpp_new_delete", "int main() { int *p = new int(42); int v = *p; delete p; return v; }"),
]);
self.run_fixtures(&cpp_fixtures);
}
pub fn print_results(&self) {
for result in &self.results {
println!("{}", result);
if let TestResult::Fail {
ref diagnostics, ..
} = result
{
for diag in diagnostics {
println!(" | {}", diag);
}
}
}
println!();
println!("{}", self.summary);
}
}
impl Default for TestRunner {
fn default() -> Self {
Self::new()
}
}
pub fn quick_compile_and_run(source: &str) -> CompilationOutput {
TestCompiler::compile_and_run(source, 0)
}
pub fn quick_filecheck(source: &str, output: &str) -> FileCheckResult {
let fc = FileCheck::from_source(source);
fc.check(output)
}
pub fn assert_compiles_and_runs(source: &str, expected_stdout: &str) {
let result = quick_compile_and_run(source);
assert!(result.success, "Compilation failed: {}", result.stderr);
assert!(
result.stdout.contains(expected_stdout),
"Expected stdout to contain '{}', got '{}'",
expected_stdout,
result.stdout
);
}
pub fn assert_compilation_fails(source: &str, expected_error: &str) {
let result = TestCompiler::compile(source, &TestCompileOptions::default());
assert!(
!result.success,
"Expected compilation to fail, but it succeeded"
);
assert!(
result.stderr.contains(expected_error),
"Expected error '{}', got '{}'",
expected_error,
result.stderr
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_test_result_pass() {
let r = TestResult::Pass {
name: "test1".into(),
duration_ms: 5,
};
assert!(r.is_pass());
assert!(!r.is_fail());
assert_eq!(r.name(), "test1");
assert!(format!("{}", r).contains("PASS"));
}
#[test]
fn test_test_result_fail() {
let r = TestResult::Fail {
name: "bad_test".into(),
reason: "assertion failed".into(),
diagnostics: vec![],
};
assert!(r.is_fail());
assert_eq!(r.name(), "bad_test");
assert!(format!("{}", r).contains("FAIL"));
}
#[test]
fn test_test_result_xfail() {
let r = TestResult::XFail {
name: "xfail_test".into(),
reason: "known bug".into(),
};
assert!(r.is_xfail());
}
#[test]
fn test_test_result_unsupported() {
let r = TestResult::Unsupported {
name: "skip".into(),
reason: "no avx512".into(),
};
assert!(r.is_unsupported());
}
#[test]
fn test_summary_new() {
let s = TestSummary::new();
assert_eq!(s.total, 0);
assert!(s.all_pass());
}
#[test]
fn test_summary_update_pass() {
let mut s = TestSummary::new();
s.update(
&TestResult::Pass {
name: "t".into(),
duration_ms: 10,
},
10,
);
assert_eq!(s.passed, 1);
assert_eq!(s.total, 1);
}
#[test]
fn test_summary_update_fail() {
let mut s = TestSummary::new();
s.update(
&TestResult::Fail {
name: "f".into(),
reason: "oops".into(),
diagnostics: vec![],
},
5,
);
assert_eq!(s.failed, 1);
assert!(!s.all_pass());
}
#[test]
fn test_summary_merge() {
let mut s1 = TestSummary::new();
s1.update(
&TestResult::Pass {
name: "a".into(),
duration_ms: 1,
},
1,
);
let mut s2 = TestSummary::new();
s2.update(
&TestResult::Fail {
name: "b".into(),
reason: "err".into(),
diagnostics: vec![],
},
2,
);
s1.merge(&s2);
assert_eq!(s1.total, 2);
assert_eq!(s1.passed, 1);
assert_eq!(s1.failed, 1);
}
#[test]
fn test_summary_display() {
let s = TestSummary {
total: 10,
passed: 8,
failed: 1,
xfailed: 1,
unsupported: 0,
skipped: 0,
total_time_ms: 100,
};
let out = format!("{}", s);
assert!(out.contains("Total:"));
assert!(out.contains("Passed: 8"));
assert!(out.contains("Failed: 1"));
}
#[test]
fn test_compilation_output_success() {
let out = CompilationOutput::new(true, "hello\n", "", 0, 5);
assert!(out.success);
assert_eq!(out.exit_code, 0);
assert!(out.stdout_contains("hello"));
}
#[test]
fn test_compilation_output_failure() {
let out = CompilationOutput::new(false, "", "error: bad syntax", 1, 3);
assert!(!out.success);
assert!(out.stderr_contains("bad syntax"));
}
#[test]
fn test_compilation_output_with_artifact() {
let out = CompilationOutput::new(true, "", "", 0, 1).with_artifact(vec![1, 2, 3]);
assert_eq!(out.artifact, Some(vec![1, 2, 3]));
}
#[test]
fn test_compiler_compile_empty() {
let result = TestCompiler::compile("", &TestCompileOptions::default());
assert!(!result.success);
assert!(result.stderr_contains("empty"));
}
#[test]
fn test_compiler_compile_unbalanced_braces() {
let result = TestCompiler::compile("int main() {", &TestCompileOptions::default());
assert!(!result.success);
assert!(result.stderr_contains("unbalanced braces"));
}
#[test]
fn test_compiler_compile_simple() {
let result = TestCompiler::compile_and_run("int main() { return 0; }", 0);
assert!(result.success);
assert_eq!(result.exit_code, 0);
}
#[test]
fn test_compiler_compile_to_bitcode() {
let result = TestCompiler::compile_to_bitcode("int main() { return 0; }", 2);
assert!(result.success);
assert!(result.artifact.is_some());
}
#[test]
fn test_compiler_compile_to_assembly() {
let result = TestCompiler::compile_to_assembly("int main() { return 0; }", 0);
assert!(result.success);
assert!(result.artifact.is_some());
}
#[test]
fn test_compiler_compile_to_object() {
let result = TestCompiler::compile_to_object("int main() { return 0; }", 0);
assert!(result.success);
assert!(result.artifact.is_some());
}
#[test]
fn test_compiler_compile_and_link() {
let result = TestCompiler::compile_and_link("int main() { return 0; }", 0);
assert!(result.success);
}
#[test]
fn test_compiler_syntax_check() {
let result = TestCompiler::syntax_check("int main() { return 0; }");
assert!(result.success);
}
#[test]
fn test_parse_check() {
let d = CheckDirective::parse("// CHECK: hello");
assert_eq!(
d,
Some(CheckDirective::Check {
pattern: "hello".into()
})
);
}
#[test]
fn test_parse_check_next() {
let d = CheckDirective::parse("// CHECK-NEXT: world");
assert_eq!(
d,
Some(CheckDirective::CheckNext {
pattern: "world".into()
})
);
}
#[test]
fn test_parse_check_not() {
let d = CheckDirective::parse("// CHECK-NOT: error");
assert_eq!(
d,
Some(CheckDirective::CheckNot {
pattern: "error".into()
})
);
}
#[test]
fn test_parse_check_same() {
let d = CheckDirective::parse("// CHECK-SAME: trailing");
assert_eq!(
d,
Some(CheckDirective::CheckSame {
pattern: "trailing".into()
})
);
}
#[test]
fn test_parse_check_label() {
let d = CheckDirective::parse("// CHECK-LABEL: function_name:");
assert_eq!(
d,
Some(CheckDirective::CheckLabel {
pattern: "function_name:".into()
})
);
}
#[test]
fn test_parse_check_dag() {
let d = CheckDirective::parse("// CHECK-DAG: decl1");
assert_eq!(
d,
Some(CheckDirective::CheckDag {
pattern: "decl1".into()
})
);
}
#[test]
fn test_parse_check_count() {
let d = CheckDirective::parse("// CHECK-COUNT-3: found");
assert_eq!(
d,
Some(CheckDirective::CheckCount {
count: 3,
pattern: "found".into()
})
);
}
#[test]
fn test_parse_check_empty() {
let d = CheckDirective::parse("// CHECK-EMPTY:");
assert_eq!(d, Some(CheckDirective::CheckEmpty));
}
#[test]
fn test_parse_no_check() {
let d = CheckDirective::parse("int main() { return 0; }");
assert_eq!(d, None);
}
#[test]
fn test_filecheck_simple_match() {
let dirs = vec![CheckDirective::Check {
pattern: "hello".into(),
}];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("hello world\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_simple_fail() {
let dirs = vec![CheckDirective::Check {
pattern: "hello".into(),
}];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("goodbye\n");
assert!(!result.passed);
}
#[test]
fn test_filecheck_check_next_pass() {
let dirs = vec![
CheckDirective::Check {
pattern: "line1".into(),
},
CheckDirective::CheckNext {
pattern: "line2".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("line1\nline2\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_check_next_fail() {
let dirs = vec![
CheckDirective::Check {
pattern: "line1".into(),
},
CheckDirective::CheckNext {
pattern: "line2".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("line1\nother\n");
assert!(!result.passed);
}
#[test]
fn test_filecheck_check_not_pass() {
let dirs = vec![
CheckDirective::Check {
pattern: "start".into(),
},
CheckDirective::CheckNot {
pattern: "error".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("start\nok\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_check_not_fail() {
let dirs = vec![
CheckDirective::Check {
pattern: "start".into(),
},
CheckDirective::CheckNot {
pattern: "error".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("start\nerror\n");
assert!(!result.passed);
}
#[test]
fn test_filecheck_check_empty_pass() {
let dirs = vec![
CheckDirective::Check {
pattern: "header".into(),
},
CheckDirective::CheckEmpty,
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("header\n\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_check_empty_fail() {
let dirs = vec![
CheckDirective::Check {
pattern: "header".into(),
},
CheckDirective::CheckEmpty,
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("header\nnot_empty\n");
assert!(!result.passed);
}
#[test]
fn test_filecheck_check_count_pass() {
let dirs = vec![CheckDirective::CheckCount {
count: 2,
pattern: "foo".into(),
}];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("foo\nbar\nfoo\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_check_count_fail() {
let dirs = vec![CheckDirective::CheckCount {
count: 2,
pattern: "foo".into(),
}];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("foo\nbar\nbaz\n");
assert!(!result.passed);
}
#[test]
fn test_filecheck_from_source() {
let source = "// CHECK: result is 42\n// CHECK-NEXT: done\nint main() { return 0; }";
let fc = FileCheck::from_source(source);
assert_eq!(fc.directives.len(), 2);
}
#[test]
fn test_filecheck_result_display_pass() {
let r = FileCheckResult {
passed: true,
failures: vec![],
total_checks: 5,
passed_checks: 5,
};
let s = format!("{}", r);
assert!(s.contains("PASS"));
}
#[test]
fn test_filecheck_result_display_fail() {
let r = FileCheckResult {
passed: false,
failures: vec!["missing pattern".into()],
total_checks: 5,
passed_checks: 3,
};
let s = format!("{}", r);
assert!(s.contains("FAIL"));
}
#[test]
fn test_fixture_from_source() {
let source = "// RUN: clang -O2 %s -o %t && %t\n// XFAIL: broken\nint main() { return 0; }";
let fixture = TestFixture::from_source("test", source, Path::new("test.c"));
assert_eq!(fixture.name, "test");
assert_eq!(fixture.run_commands.len(), 1);
assert!(fixture.is_xfail);
}
#[test]
fn test_fixture_resolved_commands() {
let source = "// RUN: clang %s -o %t";
let fixture = TestFixture::from_source("test", source, Path::new("test.c"));
let cmds = fixture.resolved_commands();
assert_eq!(cmds[0], "clang test.c -o test.tmp");
}
#[test]
fn test_fixture_requires() {
let source = "// REQUIRES: avx512\nint main() { return 0; }";
let fixture = TestFixture::from_source("test", source, Path::new("test.c"));
assert_eq!(fixture.requires, vec!["avx512"]);
}
#[test]
fn test_fixture_unsupported() {
let source = "// UNSUPPORTED: windows\nint main() { return 0; }";
let fixture = TestFixture::from_source("test", source, Path::new("test.c"));
assert!(fixture.is_unsupported().is_some());
}
#[test]
fn test_discovery_fixtures_from_sources() {
let sources = vec![
("test1", "// RUN: clang %s\nint main() { return 0; }"),
("test2", "// RUN: clang %s\nint main() { return 1; }"),
];
let fixtures = TestDiscovery::fixtures_from_sources(&sources);
assert_eq!(fixtures.len(), 2);
assert_eq!(fixtures[0].name, "test1");
assert_eq!(fixtures[1].name, "test2");
}
#[test]
fn test_discovery_only_includes_run_lines() {
let sources = vec![
("has_run", "// RUN: clang %s\nint main() { return 0; }"),
("no_run", "int main() { return 0; }"),
];
let fixtures = TestDiscovery::fixtures_from_sources(&sources);
assert_eq!(fixtures.len(), 2); }
#[test]
fn test_runner_sanity_tests() {
let mut runner = TestRunner::new();
runner.run_sanity_tests();
assert!(runner.results.len() >= 8);
let all_pass = runner.results.iter().all(|r| r.is_pass() || r.is_xfail());
assert!(all_pass, "Sanity tests should all pass or xfail");
}
#[test]
fn test_runner_c_feature_tests() {
let mut runner = TestRunner::new();
runner.run_c_feature_tests();
assert!(runner.results.len() >= 15);
}
#[test]
fn test_runner_cpp_feature_tests() {
let mut runner = TestRunner::new();
runner.run_cpp_feature_tests();
assert!(runner.results.len() >= 10);
}
#[test]
fn test_runner_print_results() {
let mut runner = TestRunner::new();
runner.run_sanity_tests();
runner.print_results();
}
#[test]
fn test_runner_unsupported_fixture() {
let source = "// UNSUPPORTED: nonexistent_arch\nint main() { return 0; }";
let fixture = TestFixture::from_source("skip", source, Path::new("skip.c"));
let runner = TestRunner::new();
let result = runner.run_single(&fixture);
assert!(result.is_unsupported());
}
#[test]
fn test_runner_skipped_fixture() {
let source =
"// REQUIRES: nonexistent_feature_xyz\n// RUN: clang %s\nint main() { return 0; }";
let fixture = TestFixture::from_source("skip2", source, Path::new("skip2.c"));
let runner = TestRunner::new();
let result = runner.run_single(&fixture);
assert!(matches!(result, TestResult::Skipped { .. }));
}
#[test]
fn test_quick_compile_and_run() {
let out = quick_compile_and_run("int main() { return 0; }");
assert!(out.success);
}
#[test]
fn test_assert_compiles_and_runs() {
assert_compiles_and_runs("int main() { return 0; }", "");
}
#[test]
#[should_panic]
fn test_assert_compilation_fails_correct() {
assert_compilation_fails("int main() {", "unbalanced");
}
#[test]
fn test_compilation_mode_as_str() {
assert_eq!(CompilationMode::CompileAndRun.as_str(), "compile-and-run");
assert_eq!(CompilationMode::SyntaxOnly.as_str(), "syntax-only");
}
#[test]
fn test_test_compile_options_default() {
let opts = TestCompileOptions::default();
assert_eq!(opts.language, "c");
assert_eq!(opts.opt_level, 0);
}
#[test]
fn test_e2e_compile_and_filecheck() {
let source = r#"// RUN: clang -O2 %s -o %t && %t
// CHECK: result: 42
#include <stdio.h>
int main() {
printf("result: 42\n");
return 0;
}
"#;
let fixture = TestFixture::from_source("e2e_test", source, Path::new("e2e_test.c"));
let runner = TestRunner::new();
let result = runner.run_single(&fixture);
assert!(
result.is_pass() || result.is_xfail() || matches!(result, TestResult::Skipped { .. }),
"Unexpected result: {:?}",
result
);
}
#[test]
fn test_filecheck_combined_directives() {
let dirs = vec![
CheckDirective::CheckLabel {
pattern: "main:".into(),
},
CheckDirective::Check {
pattern: "push".into(),
},
CheckDirective::CheckNot {
pattern: "ud2".into(),
},
CheckDirective::CheckNext {
pattern: "ret".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let asm = "main:\n\tpush rbp\n\tmov eax, 0\n\tpop rbp\n\tret\n";
let result = fc.check(asm);
assert!(result.passed, "Expected pass, got: {:?}", result.failures);
}
#[test]
fn test_filecheck_multiple_checks() {
let source = "// CHECK: foo\n// CHECK: bar\n// CHECK: baz";
let fc = FileCheck::from_source(source);
let result = fc.check("foo\nbar\nbaz\n");
assert!(result.passed);
assert_eq!(result.passed_checks, 3);
}
#[test]
fn test_filecheck_no_directives_always_passes() {
let fc = FileCheck::from_source("int main() { return 0; }");
assert_eq!(fc.directives.len(), 0);
let result = fc.check("anything");
assert!(result.passed);
}
#[test]
fn test_filecheck_check_label_same_line() {
let dirs = vec![
CheckDirective::CheckLabel {
pattern: "fn:".into(),
},
CheckDirective::CheckSame {
pattern: "nop".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("fn: nop\nret\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_check_same_no_previous() {
let dirs = vec![CheckDirective::CheckSame {
pattern: "x".into(),
}];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("something\n");
assert!(!result.passed);
}
#[test]
fn test_filecheck_dag_group() {
let dirs = vec![
CheckDirective::Check {
pattern: "start".into(),
},
CheckDirective::CheckDag {
pattern: "a".into(),
},
CheckDirective::CheckDag {
pattern: "b".into(),
},
CheckDirective::Check {
pattern: "end".into(),
},
];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("start\nb\na\nend\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_checkcount_zero() {
let dirs = vec![CheckDirective::CheckCount {
count: 0,
pattern: "error".into(),
}];
let fc = FileCheck::from_directives(dirs);
let result = fc.check("all good\nno problems\n");
assert!(result.passed);
}
#[test]
fn test_filecheck_body_source() {
let source =
"int main() {\n // CHECK: result\n printf(\"result: %d\\n\", 42);\n return 0;\n}";
let fc = FileCheck::from_source(source);
assert!(fc.directives.len() >= 1);
}
#[test]
fn test_fixture_empty_source() {
let fixture = TestFixture::from_source("empty", "", Path::new("empty.c"));
assert!(fixture.run_commands.is_empty());
assert!(!fixture.is_xfail);
}
#[test]
fn test_fixture_multiple_run_lines() {
let source = "// RUN: clang %s -o %t\n// RUN: %t\n// RUN: rm %t";
let fixture = TestFixture::from_source("multi", source, Path::new("multi.c"));
assert_eq!(fixture.run_commands.len(), 3);
}
#[test]
fn test_fixture_expected_output() {
let source = "// EXPECTED-OUTPUT: hello\nint main() { return 0; }";
let fixture = TestFixture::from_source("expected", source, Path::new("expected.c"));
assert_eq!(fixture.expected_output, Some("hello".to_string()));
}
#[test]
fn test_runner_skip_is_not_fail() {
let r = TestResult::Skipped {
name: "s".into(),
reason: "not applicable".into(),
};
assert!(!r.is_fail());
assert!(!r.is_pass());
assert!(matches!(r, TestResult::Skipped { .. }));
}
#[test]
fn test_result_display_skip() {
let r = TestResult::Skipped {
name: "skipped_test".into(),
reason: "no x86".into(),
};
let s = format!("{}", r);
assert!(s.contains("SKIPPED"));
}
#[test]
fn test_summary_with_xfail() {
let mut s = TestSummary::new();
s.update(
&TestResult::XFail {
name: "x".into(),
reason: "known".into(),
},
10,
);
assert_eq!(s.xfailed, 1);
assert_eq!(s.failed, 0); }
#[test]
fn test_summary_with_unsupported() {
let mut s = TestSummary::new();
s.update(
&TestResult::Unsupported {
name: "u".into(),
reason: "no arm".into(),
},
5,
);
assert_eq!(s.unsupported, 1);
}
#[test]
fn test_compiler_printfs() {
let result = TestCompiler::compile_and_run(
"#include <stdio.h>\nint main() { printf(\"hello world\\n\"); return 0; }",
0,
);
assert!(result.success);
}
#[test]
fn test_compiler_printfs_with_format() {
let result = TestCompiler::compile_and_run(
"#include <stdio.h>\nint main() { printf(\"value: %d\\n\", 42); return 0; }",
0,
);
assert!(result.success);
}
#[test]
fn test_quick_filecheck_passes() {
let result = quick_filecheck("// CHECK: hello", "hello\n");
assert!(result.passed);
}
#[test]
fn test_quick_filecheck_fails() {
let result = quick_filecheck("// CHECK: hello", "goodbye\n");
assert!(!result.passed);
}
}