we (web engine): Experimental web browser project to understand the limits of Claude
at css-loading 304 lines 9.7 kB view raw
1//! Test262 test harness. 2//! 3//! Walks the Test262 test suite and runs each test case against our JavaScript 4//! engine. Reports pass/fail/skip counts. 5//! 6//! Run with: `cargo test -p we-js --test test262` 7 8/// Workspace root relative to the crate directory. 9const WORKSPACE_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../"); 10 11/// Metadata extracted from a Test262 test file's YAML frontmatter. 12struct TestMeta { 13 /// If true, the test expects a parse/early error. 14 negative_phase_parse: bool, 15 /// If true, the test expects a runtime error. 16 negative_phase_runtime: bool, 17 /// The expected error type for negative tests (e.g. "SyntaxError"). 18 negative_type: Option<String>, 19 /// If true, this is an async test. 20 is_async: bool, 21 /// If true, this test should be run as a module. 22 is_module: bool, 23 /// If true, skip the harness preamble. 24 is_raw: bool, 25 /// Required features. 26 features: Vec<String>, 27 /// Required harness includes. 28 includes: Vec<String>, 29} 30 31impl TestMeta { 32 fn should_skip(&self) -> bool { 33 // Skip async tests and module tests for now. 34 self.is_async || self.is_module 35 } 36} 37 38/// Parse the YAML-ish frontmatter from a Test262 test file. 39/// 40/// The frontmatter is between `/*---` and `---*/`. 41fn parse_frontmatter(source: &str) -> TestMeta { 42 let mut meta = TestMeta { 43 negative_phase_parse: false, 44 negative_phase_runtime: false, 45 negative_type: None, 46 is_async: false, 47 is_module: false, 48 is_raw: false, 49 features: Vec::new(), 50 includes: Vec::new(), 51 }; 52 53 let start = match source.find("/*---") { 54 Some(i) => i + 5, 55 None => return meta, 56 }; 57 let end = match source[start..].find("---*/") { 58 Some(i) => start + i, 59 None => return meta, 60 }; 61 let yaml = &source[start..end]; 62 63 // Very simple line-by-line YAML extraction. 64 let mut in_negative = false; 65 let mut in_features = false; 66 let mut in_includes = false; 67 let mut in_flags = false; 68 69 for line in yaml.lines() { 70 let trimmed = line.trim(); 71 72 // Detect top-level keys (not indented or with specific indent). 73 if !trimmed.is_empty() && !trimmed.starts_with('-') && !line.starts_with(' ') { 74 in_negative = false; 75 in_features = false; 76 in_includes = false; 77 in_flags = false; 78 } 79 80 if trimmed.starts_with("negative:") { 81 in_negative = true; 82 continue; 83 } 84 if trimmed.starts_with("features:") { 85 in_features = true; 86 // Check for inline list: features: [a, b] 87 if let Some(rest) = trimmed.strip_prefix("features:") { 88 let rest = rest.trim(); 89 if rest.starts_with('[') && rest.ends_with(']') { 90 let inner = &rest[1..rest.len() - 1]; 91 for item in inner.split(',') { 92 let item = item.trim(); 93 if !item.is_empty() { 94 meta.features.push(item.to_string()); 95 } 96 } 97 in_features = false; 98 } 99 } 100 continue; 101 } 102 if trimmed.starts_with("includes:") { 103 in_includes = true; 104 if let Some(rest) = trimmed.strip_prefix("includes:") { 105 let rest = rest.trim(); 106 if rest.starts_with('[') && rest.ends_with(']') { 107 let inner = &rest[1..rest.len() - 1]; 108 for item in inner.split(',') { 109 let item = item.trim(); 110 if !item.is_empty() { 111 meta.includes.push(item.to_string()); 112 } 113 } 114 in_includes = false; 115 } 116 } 117 continue; 118 } 119 if trimmed.starts_with("flags:") { 120 in_flags = true; 121 if let Some(rest) = trimmed.strip_prefix("flags:") { 122 let rest = rest.trim(); 123 if rest.starts_with('[') && rest.ends_with(']') { 124 let inner = &rest[1..rest.len() - 1]; 125 for item in inner.split(',') { 126 let flag = item.trim(); 127 match flag { 128 "async" => meta.is_async = true, 129 "module" => meta.is_module = true, 130 "raw" => meta.is_raw = true, 131 _ => {} 132 } 133 } 134 in_flags = false; 135 } 136 } 137 continue; 138 } 139 140 // Handle list items under current key. 141 if let Some(item) = trimmed.strip_prefix("- ") { 142 if in_features { 143 meta.features.push(item.to_string()); 144 } else if in_includes { 145 meta.includes.push(item.to_string()); 146 } else if in_flags { 147 match item { 148 "async" => meta.is_async = true, 149 "module" => meta.is_module = true, 150 "raw" => meta.is_raw = true, 151 _ => {} 152 } 153 } 154 continue; 155 } 156 157 // Handle sub-keys under negative. 158 if in_negative { 159 if let Some(rest) = trimmed.strip_prefix("phase:") { 160 let phase = rest.trim(); 161 match phase { 162 "parse" | "early" => meta.negative_phase_parse = true, 163 "runtime" | "resolution" => meta.negative_phase_runtime = true, 164 _ => {} 165 } 166 } 167 if let Some(rest) = trimmed.strip_prefix("type:") { 168 meta.negative_type = Some(rest.trim().to_string()); 169 } 170 } 171 } 172 173 meta 174} 175 176/// Recursively collect all `.js` test files under a directory. 177fn collect_test_files(dir: &std::path::Path, files: &mut Vec<std::path::PathBuf>) { 178 let entries = match std::fs::read_dir(dir) { 179 Ok(e) => e, 180 Err(_) => return, 181 }; 182 let mut entries: Vec<_> = entries.filter_map(|e| e.ok()).collect(); 183 entries.sort_by_key(|e| e.file_name()); 184 185 for entry in entries { 186 let path = entry.path(); 187 if path.is_dir() { 188 collect_test_files(&path, files); 189 } else if path.extension().map_or(false, |e| e == "js") { 190 // Skip _FIXTURE files (test helpers, not tests themselves). 191 let name = path.file_name().unwrap().to_string_lossy(); 192 if !name.contains("_FIXTURE") { 193 files.push(path); 194 } 195 } 196 } 197} 198 199/// Run a single Test262 test file. Returns (pass, fail, skip). 200fn run_test(path: &std::path::Path) -> (usize, usize, usize) { 201 let source = match std::fs::read_to_string(path) { 202 Ok(s) => s, 203 Err(_) => return (0, 0, 1), 204 }; 205 206 let meta = parse_frontmatter(&source); 207 208 if meta.should_skip() { 209 return (0, 0, 1); 210 } 211 212 // For negative parse tests, if our evaluate returns an error, that's a pass. 213 // For positive tests, evaluate should succeed (return Ok). 214 let result = we_js::evaluate(&source); 215 216 if meta.negative_phase_parse { 217 // We expect a parse error. If our engine returns any error, count as pass. 218 match result { 219 Err(_) => (1, 0, 0), 220 Ok(()) => (0, 1, 0), 221 } 222 } else { 223 // We expect success. 224 match result { 225 Ok(()) => (1, 0, 0), 226 Err(_) => (0, 1, 0), 227 } 228 } 229} 230 231#[test] 232fn test262_language_tests() { 233 let test_dir = std::path::PathBuf::from(WORKSPACE_ROOT).join("tests/test262/test/language"); 234 235 if !test_dir.exists() { 236 eprintln!( 237 "test262 submodule not checked out at {}", 238 test_dir.display() 239 ); 240 eprintln!("Run: git submodule update --init tests/test262"); 241 return; 242 } 243 244 let mut files = Vec::new(); 245 collect_test_files(&test_dir, &mut files); 246 247 let mut total_pass = 0; 248 let mut total_fail = 0; 249 let mut total_skip = 0; 250 251 // Group results by top-level subdirectory for reporting. 252 let mut current_group = String::new(); 253 let mut group_pass = 0; 254 let mut group_fail = 0; 255 let mut group_skip = 0; 256 257 for path in &files { 258 // Determine the top-level group (e.g. "expressions", "literals"). 259 let rel = path.strip_prefix(&test_dir).unwrap_or(path); 260 let group = rel 261 .components() 262 .next() 263 .map(|c| c.as_os_str().to_string_lossy().to_string()) 264 .unwrap_or_default(); 265 266 if group != current_group { 267 if !current_group.is_empty() { 268 eprintln!( 269 " {}: {} pass, {} fail, {} skip", 270 current_group, group_pass, group_fail, group_skip 271 ); 272 } 273 current_group = group; 274 group_pass = 0; 275 group_fail = 0; 276 group_skip = 0; 277 } 278 279 let (p, f, s) = run_test(path); 280 group_pass += p; 281 group_fail += f; 282 group_skip += s; 283 total_pass += p; 284 total_fail += f; 285 total_skip += s; 286 } 287 288 // Print last group. 289 if !current_group.is_empty() { 290 eprintln!( 291 " {}: {} pass, {} fail, {} skip", 292 current_group, group_pass, group_fail, group_skip 293 ); 294 } 295 296 eprintln!(); 297 eprintln!( 298 "Test262 language totals: {} pass, {} fail, {} skip ({} total)", 299 total_pass, 300 total_fail, 301 total_skip, 302 total_pass + total_fail + total_skip 303 ); 304}