@@ -41,6 +41,7 @@ struct CaseSpec { |
| 41 | 41 | consistency_checks: Vec<ConsistencyCheck>, |
| 42 | 42 | expectations: Vec<Expectation>, |
| 43 | 43 | status_rules: Vec<StatusRule>, |
| 44 | + capability_policy: Option<CapabilityPolicy>, |
| 44 | 45 | } |
| 45 | 46 | |
| 46 | 47 | impl CaseSpec { |
@@ -96,6 +97,12 @@ struct StatusRule { |
| 96 | 97 | reason: String, |
| 97 | 98 | } |
| 98 | 99 | |
| 100 | +#[derive(Debug, Clone)] |
| 101 | +struct CapabilityPolicy { |
| 102 | + kind: StatusKind, |
| 103 | + reason: String, |
| 104 | +} |
| 105 | + |
| 99 | 106 | #[derive(Debug, Clone)] |
| 100 | 107 | enum PendingStatusRule { |
| 101 | 108 | Explicit(StatusRule), |
@@ -273,6 +280,10 @@ struct ToolchainConfig { |
| 273 | 280 | armfortas: ArmfortasCliAdapter, |
| 274 | 281 | gfortran: String, |
| 275 | 282 | flang_new: String, |
| 283 | + lfortran: String, |
| 284 | + ifort: String, |
| 285 | + ifx: String, |
| 286 | + nvfortran: String, |
| 276 | 287 | system_as: String, |
| 277 | 288 | otool: String, |
| 278 | 289 | nm: String, |
@@ -288,6 +299,10 @@ impl ToolchainConfig { |
| 288 | 299 | }, |
| 289 | 300 | gfortran: tool_override("BENCCH_GFORTRAN_BIN", "gfortran"), |
| 290 | 301 | flang_new: tool_override("BENCCH_FLANG_BIN", "flang-new"), |
| 302 | + lfortran: tool_override("BENCCH_LFORTRAN_BIN", "lfortran"), |
| 303 | + ifort: tool_override("BENCCH_IFORT_BIN", "ifort"), |
| 304 | + ifx: tool_override("BENCCH_IFX_BIN", "ifx"), |
| 305 | + nvfortran: tool_override("BENCCH_NVFORTRAN_BIN", "nvfortran"), |
| 291 | 306 | system_as: tool_override("BENCCH_AS_BIN", "as"), |
| 292 | 307 | otool: tool_override("BENCCH_OTOOL_BIN", "otool"), |
| 293 | 308 | nm: tool_override("BENCCH_NM_BIN", "nm"), |
@@ -334,6 +349,10 @@ impl ToolchainConfig { |
| 334 | 349 | }, |
| 335 | 350 | NamedCompiler::Gfortran => Some(self.gfortran.clone()), |
| 336 | 351 | NamedCompiler::FlangNew => Some(self.flang_new.clone()), |
| 352 | + NamedCompiler::LFortran => Some(self.lfortran.clone()), |
| 353 | + NamedCompiler::Ifort => Some(self.ifort.clone()), |
| 354 | + NamedCompiler::Ifx => Some(self.ifx.clone()), |
| 355 | + NamedCompiler::Nvfortran => Some(self.nvfortran.clone()), |
| 337 | 356 | } |
| 338 | 357 | } |
| 339 | 358 | } |
@@ -419,6 +438,75 @@ fn capability_unavailable_summary(capabilities: &CompilerCapabilities) -> String |
| 419 | 438 | .join(", ") |
| 420 | 439 | } |
| 421 | 440 | |
| 441 | +fn named_compiler_status_value( |
| 442 | + named: NamedCompiler, |
| 443 | + tools: &ToolchainConfig, |
| 444 | + capture_root: Option<&PathBuf>, |
| 445 | +) -> String { |
| 446 | + match named { |
| 447 | + NamedCompiler::Armfortas => match &tools.armfortas { |
| 448 | + ArmfortasCliAdapter::Linked => capture_root |
| 449 | + .map(|root| format!("linked via Cargo to {}", display_path(root))) |
| 450 | + .unwrap_or_else(|| { |
| 451 | + "linked adapter requested but unavailable in this build".to_string() |
| 452 | + }), |
| 453 | + ArmfortasCliAdapter::External(binary) => tool_probe_status(binary, false), |
| 454 | + }, |
| 455 | + _ => tool_probe_status( |
| 456 | + &tools |
| 457 | + .named_compiler_binary(named) |
| 458 | + .unwrap_or_else(|| named.as_str().to_string()), |
| 459 | + false, |
| 460 | + ), |
| 461 | + } |
| 462 | +} |
| 463 | + |
| 464 | +fn append_named_compiler_fields( |
| 465 | + fields: &mut Vec<(String, String)>, |
| 466 | + named: NamedCompiler, |
| 467 | + tools: &ToolchainConfig, |
| 468 | + capture_root: Option<&PathBuf>, |
| 469 | +) { |
| 470 | + let prefix = format!("named_compiler.{}", named.as_str()); |
| 471 | + let capabilities = compiler_capabilities(&CompilerSpec::Named(named), tools); |
| 472 | + if named == NamedCompiler::Armfortas { |
| 473 | + let armfortas = tools.armfortas_adapters(); |
| 474 | + fields.push(( |
| 475 | + prefix.clone(), |
| 476 | + format!( |
| 477 | + "cli={} capture={}", |
| 478 | + armfortas.cli_mode_name(), |
| 479 | + armfortas.capture_mode_name() |
| 480 | + ), |
| 481 | + )); |
| 482 | + } else { |
| 483 | + fields.push(( |
| 484 | + prefix.clone(), |
| 485 | + named_compiler_status_value(named, tools, capture_root), |
| 486 | + )); |
| 487 | + } |
| 488 | + fields.push(( |
| 489 | + format!("{}.accepted_names", prefix), |
| 490 | + named.accepted_names().join(", "), |
| 491 | + )); |
| 492 | + fields.push(( |
| 493 | + format!("{}.candidate_binaries", prefix), |
| 494 | + named.candidate_binaries().join(", "), |
| 495 | + )); |
| 496 | + fields.push(( |
| 497 | + format!("{}.generic_artifacts", prefix), |
| 498 | + format_artifact_name_list(&capabilities.generic_artifacts()), |
| 499 | + )); |
| 500 | + fields.push(( |
| 501 | + format!("{}.adapter_extras", prefix), |
| 502 | + capability_extra_summary(&capabilities.adapter_extras()), |
| 503 | + )); |
| 504 | + fields.push(( |
| 505 | + format!("{}.unavailable_artifacts", prefix), |
| 506 | + capability_unavailable_summary(&capabilities), |
| 507 | + )); |
| 508 | +} |
| 509 | + |
| 422 | 510 | fn compiler_capability_backend(spec: &CompilerSpec, tools: &ToolchainConfig) -> (String, String) { |
| 423 | 511 | match spec { |
| 424 | 512 | CompilerSpec::Named(NamedCompiler::Armfortas) => { |
@@ -919,6 +1007,28 @@ fn parse_tool_override_arg( |
| 919 | 1007 | tools.flang_new = value.clone(); |
| 920 | 1008 | Ok(true) |
| 921 | 1009 | } |
| 1010 | + "--lfortran-bin" => { |
| 1011 | + let value = queue.pop_front().ok_or("--lfortran-bin requires a value")?; |
| 1012 | + tools.lfortran = value.clone(); |
| 1013 | + Ok(true) |
| 1014 | + } |
| 1015 | + "--ifort-bin" => { |
| 1016 | + let value = queue.pop_front().ok_or("--ifort-bin requires a value")?; |
| 1017 | + tools.ifort = value.clone(); |
| 1018 | + Ok(true) |
| 1019 | + } |
| 1020 | + "--ifx-bin" => { |
| 1021 | + let value = queue.pop_front().ok_or("--ifx-bin requires a value")?; |
| 1022 | + tools.ifx = value.clone(); |
| 1023 | + Ok(true) |
| 1024 | + } |
| 1025 | + "--nvfortran-bin" => { |
| 1026 | + let value = queue |
| 1027 | + .pop_front() |
| 1028 | + .ok_or("--nvfortran-bin requires a value")?; |
| 1029 | + tools.nvfortran = value.clone(); |
| 1030 | + Ok(true) |
| 1031 | + } |
| 922 | 1032 | "--as-bin" => { |
| 923 | 1033 | let value = queue.pop_front().ok_or("--as-bin requires a value")?; |
| 924 | 1034 | tools.system_as = value.clone(); |
@@ -1207,12 +1317,13 @@ fn print_usage(program_name: &str) { |
| 1207 | 1317 | program_name |
| 1208 | 1318 | ); |
| 1209 | 1319 | eprintln!( |
| 1210 | | - " {} doctor [--json-report <path>] [--markdown-report <path>] [--armfortas-bin <path>] [--gfortran-bin <path>] [--flang-bin <path>] [--as-bin <path>] [--otool-bin <path>] [--nm-bin <path>]", |
| 1320 | + " {} doctor [--json-report <path>] [--markdown-report <path>] [--armfortas-bin <path>] [--gfortran-bin <path>] [--flang-bin <path>] [--lfortran-bin <path>] [--ifort-bin <path>] [--ifx-bin <path>] [--nvfortran-bin <path>] [--as-bin <path>] [--otool-bin <path>] [--nm-bin <path>]", |
| 1211 | 1321 | program_name |
| 1212 | 1322 | ); |
| 1213 | 1323 | eprintln!(); |
| 1214 | 1324 | eprintln!("env overrides:"); |
| 1215 | | - eprintln!(" BENCCH_ARMFORTAS_BIN, BENCCH_GFORTRAN_BIN, BENCCH_FLANG_BIN"); |
| 1325 | + eprintln!(" BENCCH_ARMFORTAS_BIN, BENCCH_GFORTRAN_BIN, BENCCH_FLANG_BIN, BENCCH_LFORTRAN_BIN"); |
| 1326 | + eprintln!(" BENCCH_IFORT_BIN, BENCCH_IFX_BIN, BENCCH_NVFORTRAN_BIN"); |
| 1216 | 1327 | eprintln!(" BENCCH_AS_BIN, BENCCH_OTOOL_BIN, BENCCH_NM_BIN"); |
| 1217 | 1328 | eprintln!(); |
| 1218 | 1329 | if linked_capture_available() { |
@@ -1298,41 +1409,62 @@ fn capability_request_issue( |
| 1298 | 1409 | return None; |
| 1299 | 1410 | } |
| 1300 | 1411 | |
| 1301 | | - let mut lines = vec![format!("{}:", spec.display_name())]; |
| 1302 | | - for (artifact, reason) in unavailable { |
| 1303 | | - lines.push(format!(" unavailable {}: {}", artifact, reason)); |
| 1412 | + let mut sections = Vec::new(); |
| 1413 | + if !unavailable.is_empty() { |
| 1414 | + let detail = unavailable |
| 1415 | + .into_iter() |
| 1416 | + .map(|(artifact, reason)| format!("requested {}: {}", artifact, reason)) |
| 1417 | + .collect::<Vec<_>>() |
| 1418 | + .join("\n"); |
| 1419 | + sections.push(format!( |
| 1420 | + "{} unavailable for requested artifacts in this build\n{}", |
| 1421 | + spec.display_name(), |
| 1422 | + detail |
| 1423 | + )); |
| 1304 | 1424 | } |
| 1305 | 1425 | if !unsupported.is_empty() { |
| 1306 | | - lines.push(format!( |
| 1307 | | - " unsupported in this adapter: {}", |
| 1426 | + sections.push(format!( |
| 1427 | + "{} does not support requested artifacts in this adapter: {}", |
| 1428 | + spec.display_name(), |
| 1308 | 1429 | unsupported.join(", ") |
| 1309 | 1430 | )); |
| 1310 | 1431 | } |
| 1311 | | - Some(lines.join("\n")) |
| 1432 | + Some(sections.join("\n")) |
| 1312 | 1433 | } |
| 1313 | 1434 | |
| 1314 | | -fn preflight_compare_request( |
| 1315 | | - config: &CompareConfig, |
| 1435 | +fn compare_capability_issue( |
| 1436 | + left: &CompilerSpec, |
| 1437 | + right: &CompilerSpec, |
| 1316 | 1438 | requested: &BTreeSet<ArtifactKey>, |
| 1317 | | -) -> Result<(), String> { |
| 1439 | + tools: &ToolchainConfig, |
| 1440 | +) -> Option<String> { |
| 1318 | 1441 | let mut issues = Vec::new(); |
| 1319 | | - if let Some(issue) = capability_request_issue(&config.left, requested, &config.tools) { |
| 1320 | | - issues.push(format!("left {}\n{}", config.left.display_name(), issue)); |
| 1442 | + if let Some(issue) = capability_request_issue(left, requested, tools) { |
| 1443 | + issues.push(format!("left:\n{}", issue)); |
| 1321 | 1444 | } |
| 1322 | | - if let Some(issue) = capability_request_issue(&config.right, requested, &config.tools) { |
| 1323 | | - issues.push(format!("right {}\n{}", config.right.display_name(), issue)); |
| 1445 | + if let Some(issue) = capability_request_issue(right, requested, tools) { |
| 1446 | + issues.push(format!("right:\n{}", issue)); |
| 1324 | 1447 | } |
| 1325 | | - |
| 1326 | 1448 | if issues.is_empty() { |
| 1327 | | - Ok(()) |
| 1449 | + None |
| 1328 | 1450 | } else { |
| 1329 | | - Err(format!( |
| 1451 | + Some(format!( |
| 1330 | 1452 | "compare request is not supported for the selected compiler surfaces\n{}", |
| 1331 | 1453 | issues.join("\n") |
| 1332 | 1454 | )) |
| 1333 | 1455 | } |
| 1334 | 1456 | } |
| 1335 | 1457 | |
| 1458 | +fn preflight_compare_request( |
| 1459 | + config: &CompareConfig, |
| 1460 | + requested: &BTreeSet<ArtifactKey>, |
| 1461 | +) -> Result<(), String> { |
| 1462 | + match compare_capability_issue(&config.left, &config.right, requested, &config.tools) { |
| 1463 | + Some(issue) => Err(issue), |
| 1464 | + None => Ok(()), |
| 1465 | + } |
| 1466 | +} |
| 1467 | + |
| 1336 | 1468 | fn run_introspect(config: &IntrospectConfig) -> Result<ObservedProgram, String> { |
| 1337 | 1469 | let requested = if config.artifacts.is_empty() { |
| 1338 | 1470 | default_introspection_artifacts(&config.compiler, config.all_artifacts) |
@@ -3191,58 +3323,9 @@ fn doctor_report_fields(config: &DoctorConfig) -> Vec<(String, String)> { |
| 3191 | 3323 | "primary_backend_selection".to_string(), |
| 3192 | 3324 | "observable backend is selected for asm/obj/run-only cells when the armfortas CLI is external and the case does not require expect-fail or capture-consistency semantics; otherwise full backend".to_string(), |
| 3193 | 3325 | )); |
| 3194 | | - fields.push(( |
| 3195 | | - "named_compiler.armfortas".to_string(), |
| 3196 | | - format!( |
| 3197 | | - "cli={} capture={}", |
| 3198 | | - armfortas.cli_mode_name(), |
| 3199 | | - armfortas.capture_mode_name() |
| 3200 | | - ), |
| 3201 | | - )); |
| 3202 | | - let armfortas_capabilities = compiler_capabilities( |
| 3203 | | - &CompilerSpec::Named(NamedCompiler::Armfortas), |
| 3204 | | - &config.tools, |
| 3205 | | - ); |
| 3206 | | - fields.push(( |
| 3207 | | - "named_compiler.armfortas.generic_artifacts".to_string(), |
| 3208 | | - format_artifact_name_list(&armfortas_capabilities.generic_artifacts()), |
| 3209 | | - )); |
| 3210 | | - fields.push(( |
| 3211 | | - "named_compiler.armfortas.adapter_extras".to_string(), |
| 3212 | | - capability_extra_summary(&armfortas_capabilities.adapter_extras()), |
| 3213 | | - )); |
| 3214 | | - fields.push(( |
| 3215 | | - "named_compiler.armfortas.unavailable_artifacts".to_string(), |
| 3216 | | - capability_unavailable_summary(&armfortas_capabilities), |
| 3217 | | - )); |
| 3218 | | - fields.push(( |
| 3219 | | - "named_compiler.gfortran".to_string(), |
| 3220 | | - tool_probe_status(&config.tools.gfortran, false), |
| 3221 | | - )); |
| 3222 | | - let gfortran_capabilities = |
| 3223 | | - compiler_capabilities(&CompilerSpec::Named(NamedCompiler::Gfortran), &config.tools); |
| 3224 | | - fields.push(( |
| 3225 | | - "named_compiler.gfortran.generic_artifacts".to_string(), |
| 3226 | | - format_artifact_name_list(&gfortran_capabilities.generic_artifacts()), |
| 3227 | | - )); |
| 3228 | | - fields.push(( |
| 3229 | | - "named_compiler.gfortran.adapter_extras".to_string(), |
| 3230 | | - capability_extra_summary(&gfortran_capabilities.adapter_extras()), |
| 3231 | | - )); |
| 3232 | | - fields.push(( |
| 3233 | | - "named_compiler.flang-new".to_string(), |
| 3234 | | - tool_probe_status(&config.tools.flang_new, false), |
| 3235 | | - )); |
| 3236 | | - let flang_capabilities = |
| 3237 | | - compiler_capabilities(&CompilerSpec::Named(NamedCompiler::FlangNew), &config.tools); |
| 3238 | | - fields.push(( |
| 3239 | | - "named_compiler.flang-new.generic_artifacts".to_string(), |
| 3240 | | - format_artifact_name_list(&flang_capabilities.generic_artifacts()), |
| 3241 | | - )); |
| 3242 | | - fields.push(( |
| 3243 | | - "named_compiler.flang-new.adapter_extras".to_string(), |
| 3244 | | - capability_extra_summary(&flang_capabilities.adapter_extras()), |
| 3245 | | - )); |
| 3326 | + for named in NamedCompiler::ALL { |
| 3327 | + append_named_compiler_fields(&mut fields, named, &config.tools, capture_root.as_ref()); |
| 3328 | + } |
| 3246 | 3329 | fields.push(( |
| 3247 | 3330 | "explicit_compiler_path".to_string(), |
| 3248 | 3331 | "any filesystem path passed to compare/introspect uses the generic external-driver adapter" |
@@ -3268,6 +3351,22 @@ fn doctor_report_fields(config: &DoctorConfig) -> Vec<(String, String)> { |
| 3268 | 3351 | "flang-new".to_string(), |
| 3269 | 3352 | tool_probe_status(&config.tools.flang_new, false), |
| 3270 | 3353 | )); |
| 3354 | + fields.push(( |
| 3355 | + "lfortran".to_string(), |
| 3356 | + tool_probe_status(&config.tools.lfortran, false), |
| 3357 | + )); |
| 3358 | + fields.push(( |
| 3359 | + "ifort".to_string(), |
| 3360 | + tool_probe_status(&config.tools.ifort, false), |
| 3361 | + )); |
| 3362 | + fields.push(( |
| 3363 | + "ifx".to_string(), |
| 3364 | + tool_probe_status(&config.tools.ifx, false), |
| 3365 | + )); |
| 3366 | + fields.push(( |
| 3367 | + "nvfortran".to_string(), |
| 3368 | + tool_probe_status(&config.tools.nvfortran, false), |
| 3369 | + )); |
| 3271 | 3370 | fields.push(( |
| 3272 | 3371 | "as".to_string(), |
| 3273 | 3372 | tool_probe_status(&config.tools.system_as, false), |
@@ -3362,6 +3461,51 @@ fn json_string_vec_map(map: &BTreeMap<String, Vec<String>>) -> String { |
| 3362 | 3461 | rendered |
| 3363 | 3462 | } |
| 3364 | 3463 | |
| 3464 | +fn render_named_compiler_entry_json( |
| 3465 | + named: NamedCompiler, |
| 3466 | + tools: &ToolchainConfig, |
| 3467 | + capture_root: Option<&PathBuf>, |
| 3468 | +) -> String { |
| 3469 | + let capabilities = compiler_capabilities(&CompilerSpec::Named(named), tools); |
| 3470 | + let mut fields = vec![ |
| 3471 | + format!( |
| 3472 | + "\"accepted_names\": {}", |
| 3473 | + json_string_iter(named.accepted_names().iter().copied()) |
| 3474 | + ), |
| 3475 | + format!( |
| 3476 | + "\"candidate_binaries\": {}", |
| 3477 | + json_string_iter(named.candidate_binaries().iter().copied()) |
| 3478 | + ), |
| 3479 | + format!( |
| 3480 | + "\"capabilities\": {}", |
| 3481 | + render_doctor_capabilities_json(&capabilities) |
| 3482 | + ), |
| 3483 | + ]; |
| 3484 | + if named == NamedCompiler::Armfortas { |
| 3485 | + let armfortas = tools.armfortas_adapters(); |
| 3486 | + fields.insert( |
| 3487 | + 0, |
| 3488 | + format!( |
| 3489 | + "\"surface\": \"{}\"", |
| 3490 | + json_escape(&format!( |
| 3491 | + "cli={} capture={}", |
| 3492 | + armfortas.cli_mode_name(), |
| 3493 | + armfortas.capture_mode_name() |
| 3494 | + )) |
| 3495 | + ), |
| 3496 | + ); |
| 3497 | + } else { |
| 3498 | + fields.insert( |
| 3499 | + 0, |
| 3500 | + format!( |
| 3501 | + "\"status\": \"{}\"", |
| 3502 | + json_escape(&named_compiler_status_value(named, tools, capture_root)) |
| 3503 | + ), |
| 3504 | + ); |
| 3505 | + } |
| 3506 | + format!("{{{}}}", fields.join(", ")) |
| 3507 | +} |
| 3508 | + |
| 3365 | 3509 | fn render_doctor_json(config: &DoctorConfig) -> String { |
| 3366 | 3510 | let fields = doctor_report_fields(config); |
| 3367 | 3511 | let workspace_root = workspace_root(); |
@@ -3373,18 +3517,26 @@ fn render_doctor_json(config: &DoctorConfig) -> String { |
| 3373 | 3517 | .cli_observable_capture_backend(report_root.join(".tmp").join("doctor")); |
| 3374 | 3518 | let capture_root = armfortas.capture_root(); |
| 3375 | 3519 | let capture_manifest = capture_root.as_ref().map(|root| root.join("Cargo.toml")); |
| 3376 | | - let armfortas_capabilities = compiler_capabilities( |
| 3377 | | - &CompilerSpec::Named(NamedCompiler::Armfortas), |
| 3378 | | - &config.tools, |
| 3379 | | - ); |
| 3380 | | - let gfortran_capabilities = |
| 3381 | | - compiler_capabilities(&CompilerSpec::Named(NamedCompiler::Gfortran), &config.tools); |
| 3382 | | - let flang_capabilities = |
| 3383 | | - compiler_capabilities(&CompilerSpec::Named(NamedCompiler::FlangNew), &config.tools); |
| 3384 | 3520 | let explicit_capabilities = compiler_capabilities( |
| 3385 | 3521 | &CompilerSpec::Binary(PathBuf::from("/path/to/compiler")), |
| 3386 | 3522 | &config.tools, |
| 3387 | 3523 | ); |
| 3524 | + let named_entries = NamedCompiler::ALL |
| 3525 | + .iter() |
| 3526 | + .enumerate() |
| 3527 | + .map(|(index, named)| { |
| 3528 | + format!( |
| 3529 | + " \"{}\": {}{}", |
| 3530 | + named.as_str(), |
| 3531 | + render_named_compiler_entry_json(*named, &config.tools, capture_root.as_ref()), |
| 3532 | + if index + 1 == NamedCompiler::ALL.len() { |
| 3533 | + "" |
| 3534 | + } else { |
| 3535 | + "," |
| 3536 | + } |
| 3537 | + ) |
| 3538 | + }) |
| 3539 | + .collect::<Vec<_>>(); |
| 3388 | 3540 | let mut lines = vec![ |
| 3389 | 3541 | "{".to_string(), |
| 3390 | 3542 | " \"command\": \"doctor\",".to_string(), |
@@ -3483,25 +3635,9 @@ fn render_doctor_json(config: &DoctorConfig) -> String { |
| 3483 | 3635 | ), |
| 3484 | 3636 | " },".to_string(), |
| 3485 | 3637 | " \"named_compilers\": {".to_string(), |
| 3486 | | - format!( |
| 3487 | | - " \"armfortas\": {{\"surface\":\"{}\",\"capabilities\":{}}},", |
| 3488 | | - json_escape(&format!( |
| 3489 | | - "cli={} capture={}", |
| 3490 | | - armfortas.cli_mode_name(), |
| 3491 | | - armfortas.capture_mode_name() |
| 3492 | | - )), |
| 3493 | | - render_doctor_capabilities_json(&armfortas_capabilities) |
| 3494 | | - ), |
| 3495 | | - format!( |
| 3496 | | - " \"gfortran\": {{\"status\":\"{}\",\"capabilities\":{}}},", |
| 3497 | | - json_escape(&tool_probe_status(&config.tools.gfortran, false)), |
| 3498 | | - render_doctor_capabilities_json(&gfortran_capabilities) |
| 3499 | | - ), |
| 3500 | | - format!( |
| 3501 | | - " \"flang-new\": {{\"status\":\"{}\",\"capabilities\":{}}}", |
| 3502 | | - json_escape(&tool_probe_status(&config.tools.flang_new, false)), |
| 3503 | | - render_doctor_capabilities_json(&flang_capabilities) |
| 3504 | | - ), |
| 3638 | + ]; |
| 3639 | + lines.extend(named_entries); |
| 3640 | + lines.extend([ |
| 3505 | 3641 | " },".to_string(), |
| 3506 | 3642 | format!( |
| 3507 | 3643 | " \"explicit_compiler_path\": {{\"description\":\"{}\",\"capabilities\":{}}},", |
@@ -3519,6 +3655,22 @@ fn render_doctor_json(config: &DoctorConfig) -> String { |
| 3519 | 3655 | " \"flang-new\": \"{}\",", |
| 3520 | 3656 | json_escape(&tool_probe_status(&config.tools.flang_new, false)) |
| 3521 | 3657 | ), |
| 3658 | + format!( |
| 3659 | + " \"lfortran\": \"{}\",", |
| 3660 | + json_escape(&tool_probe_status(&config.tools.lfortran, false)) |
| 3661 | + ), |
| 3662 | + format!( |
| 3663 | + " \"ifort\": \"{}\",", |
| 3664 | + json_escape(&tool_probe_status(&config.tools.ifort, false)) |
| 3665 | + ), |
| 3666 | + format!( |
| 3667 | + " \"ifx\": \"{}\",", |
| 3668 | + json_escape(&tool_probe_status(&config.tools.ifx, false)) |
| 3669 | + ), |
| 3670 | + format!( |
| 3671 | + " \"nvfortran\": \"{}\",", |
| 3672 | + json_escape(&tool_probe_status(&config.tools.nvfortran, false)) |
| 3673 | + ), |
| 3522 | 3674 | format!( |
| 3523 | 3675 | " \"as\": \"{}\",", |
| 3524 | 3676 | json_escape(&tool_probe_status(&config.tools.system_as, false)) |
@@ -3581,7 +3733,7 @@ fn render_doctor_json(config: &DoctorConfig) -> String { |
| 3581 | 3733 | ), |
| 3582 | 3734 | " },".to_string(), |
| 3583 | 3735 | " \"fields\": {".to_string(), |
| 3584 | | - ]; |
| 3736 | + ]); |
| 3585 | 3737 | for (index, (field, value)) in fields.iter().enumerate() { |
| 3586 | 3738 | lines.push(format!( |
| 3587 | 3739 | " \"{}\": \"{}\"{}", |
@@ -3789,6 +3941,34 @@ fn parse_suite_file(path: &Path) -> Result<SuiteSpec, String> { |
| 3789 | 3941 | builder |
| 3790 | 3942 | .expectations |
| 3791 | 3943 | .push(parse_expectation(rest, path, line_no)?); |
| 3944 | + } else if let Some(rest) = line.strip_prefix("xfail capability ") { |
| 3945 | + if builder.capability_policy.is_some() { |
| 3946 | + return Err(format!( |
| 3947 | + "{}:{}: duplicate capability policy", |
| 3948 | + path.display(), |
| 3949 | + line_no |
| 3950 | + )); |
| 3951 | + } |
| 3952 | + builder.capability_policy = Some(parse_capability_policy( |
| 3953 | + StatusKind::Xfail, |
| 3954 | + rest, |
| 3955 | + path, |
| 3956 | + line_no, |
| 3957 | + )?); |
| 3958 | + } else if let Some(rest) = line.strip_prefix("future capability ") { |
| 3959 | + if builder.capability_policy.is_some() { |
| 3960 | + return Err(format!( |
| 3961 | + "{}:{}: duplicate capability policy", |
| 3962 | + path.display(), |
| 3963 | + line_no |
| 3964 | + )); |
| 3965 | + } |
| 3966 | + builder.capability_policy = Some(parse_capability_policy( |
| 3967 | + StatusKind::Future, |
| 3968 | + rest, |
| 3969 | + path, |
| 3970 | + line_no, |
| 3971 | + )?); |
| 3792 | 3972 | } else if let Some(rest) = line.strip_prefix("xfail ") { |
| 3793 | 3973 | builder |
| 3794 | 3974 | .status_rules |
@@ -3840,6 +4020,7 @@ struct CaseBuilder { |
| 3840 | 4020 | consistency_checks: Vec<ConsistencyCheck>, |
| 3841 | 4021 | expectations: Vec<Expectation>, |
| 3842 | 4022 | status_rules: Vec<PendingStatusRule>, |
| 4023 | + capability_policy: Option<CapabilityPolicy>, |
| 3843 | 4024 | } |
| 3844 | 4025 | |
| 3845 | 4026 | impl CaseBuilder { |
@@ -3860,6 +4041,7 @@ impl CaseBuilder { |
| 3860 | 4041 | consistency_checks: Vec::new(), |
| 3861 | 4042 | expectations: Vec::new(), |
| 3862 | 4043 | status_rules: Vec::new(), |
| 4044 | + capability_policy: None, |
| 3863 | 4045 | } |
| 3864 | 4046 | } |
| 3865 | 4047 | |
@@ -4052,6 +4234,7 @@ impl CaseBuilder { |
| 4052 | 4234 | consistency_checks: self.consistency_checks, |
| 4053 | 4235 | expectations, |
| 4054 | 4236 | status_rules, |
| 4237 | + capability_policy: self.capability_policy, |
| 4055 | 4238 | }) |
| 4056 | 4239 | } |
| 4057 | 4240 | } |
@@ -4435,6 +4618,18 @@ fn parse_status_rule( |
| 4435 | 4618 | })) |
| 4436 | 4619 | } |
| 4437 | 4620 | |
| 4621 | +fn parse_capability_policy( |
| 4622 | + kind: StatusKind, |
| 4623 | + rest: &str, |
| 4624 | + path: &Path, |
| 4625 | + line_no: usize, |
| 4626 | +) -> Result<CapabilityPolicy, String> { |
| 4627 | + Ok(CapabilityPolicy { |
| 4628 | + kind, |
| 4629 | + reason: parse_quoted(rest.trim(), path, line_no)?, |
| 4630 | + }) |
| 4631 | +} |
| 4632 | + |
| 4438 | 4633 | fn resolve_source_comment_expectations( |
| 4439 | 4634 | expectations: Vec<Expectation>, |
| 4440 | 4635 | source_text: Option<&str>, |
@@ -4678,6 +4873,16 @@ fn case_discovery_lines(case: &CaseSpec, tools: &ToolchainConfig) -> Vec<String> |
| 4678 | 4873 | format!("source: {}", case.source_label()), |
| 4679 | 4874 | format!("opts: {}", format_opt_level_list(&case.opt_levels)), |
| 4680 | 4875 | ]; |
| 4876 | + if let Some(policy) = &case.capability_policy { |
| 4877 | + lines.push(format!( |
| 4878 | + "capability_policy: {} when blocked ({})", |
| 4879 | + match policy.kind { |
| 4880 | + StatusKind::Xfail => "xfail", |
| 4881 | + StatusKind::Future => "future", |
| 4882 | + }, |
| 4883 | + policy.reason |
| 4884 | + )); |
| 4885 | + } |
| 4681 | 4886 | |
| 4682 | 4887 | if let Some(generic) = &case.generic_introspect { |
| 4683 | 4888 | lines.push(format!("compiler: {}", generic.compiler.display_name())); |
@@ -4693,7 +4898,11 @@ fn case_discovery_lines(case: &CaseSpec, tools: &ToolchainConfig) -> Vec<String> |
| 4693 | 4898 | )); |
| 4694 | 4899 | match capability_request_issue(&generic.compiler, &generic.artifacts, tools) { |
| 4695 | 4900 | Some(issue) => { |
| 4696 | | - lines.push("capability_status: blocked".to_string()); |
| 4901 | + lines.push(if case.capability_policy.is_some() { |
| 4902 | + "capability_status: deferred".to_string() |
| 4903 | + } else { |
| 4904 | + "capability_status: blocked".to_string() |
| 4905 | + }); |
| 4697 | 4906 | lines.extend( |
| 4698 | 4907 | issue |
| 4699 | 4908 | .lines() |
@@ -4723,15 +4932,19 @@ fn case_discovery_lines(case: &CaseSpec, tools: &ToolchainConfig) -> Vec<String> |
| 4723 | 4932 | )); |
| 4724 | 4933 | let mut issues = Vec::new(); |
| 4725 | 4934 | if let Some(issue) = capability_request_issue(&generic.left, &generic.artifacts, tools) { |
| 4726 | | - issues.push(format!("left {}", issue)); |
| 4935 | + issues.push(format!("left:\n{}", issue)); |
| 4727 | 4936 | } |
| 4728 | 4937 | if let Some(issue) = capability_request_issue(&generic.right, &generic.artifacts, tools) { |
| 4729 | | - issues.push(format!("right {}", issue)); |
| 4938 | + issues.push(format!("right:\n{}", issue)); |
| 4730 | 4939 | } |
| 4731 | 4940 | if issues.is_empty() { |
| 4732 | 4941 | lines.push("capability_status: ready".to_string()); |
| 4733 | 4942 | } else { |
| 4734 | | - lines.push("capability_status: blocked".to_string()); |
| 4943 | + lines.push(if case.capability_policy.is_some() { |
| 4944 | + "capability_status: deferred".to_string() |
| 4945 | + } else { |
| 4946 | + "capability_status: blocked".to_string() |
| 4947 | + }); |
| 4735 | 4948 | lines.extend(issues.into_iter().flat_map(|issue| { |
| 4736 | 4949 | issue |
| 4737 | 4950 | .lines() |
@@ -4780,7 +4993,11 @@ fn case_discovery_lines(case: &CaseSpec, tools: &ToolchainConfig) -> Vec<String> |
| 4780 | 4993 | if linked_capture_available() { |
| 4781 | 4994 | lines.push("capability_status: ready".to_string()); |
| 4782 | 4995 | } else { |
| 4783 | | - lines.push("capability_status: blocked".to_string()); |
| 4996 | + lines.push(if case.capability_policy.is_some() { |
| 4997 | + "capability_status: deferred".to_string() |
| 4998 | + } else { |
| 4999 | + "capability_status: blocked".to_string() |
| 5000 | + }); |
| 4784 | 5001 | lines.push( |
| 4785 | 5002 | "capability_detail: linked armfortas capture is unavailable in this build" |
| 4786 | 5003 | .to_string(), |
@@ -4907,7 +5124,7 @@ fn execute_case_cell( |
| 4907 | 5124 | suite, |
| 4908 | 5125 | case, |
| 4909 | 5126 | opt_level, |
| 4910 | | - effective_status, |
| 5127 | + capability_effective_status(&effective_status, case), |
| 4911 | 5128 | Err(detail), |
| 4912 | 5129 | Some(PrimaryBackendReport::from_selected(&selected_backend)), |
| 4913 | 5130 | Vec::new(), |
@@ -5168,6 +5385,26 @@ fn execute_generic_compare_case_cell( |
| 5168 | 5385 | .ok_or_else(|| "missing generic compare case configuration".to_string())?; |
| 5169 | 5386 | let prepared = prepare_case_input(case, suite, opt_level)?; |
| 5170 | 5387 | |
| 5388 | + if let Some(detail) = compare_capability_issue( |
| 5389 | + &generic.left, |
| 5390 | + &generic.right, |
| 5391 | + &generic.artifacts, |
| 5392 | + &config.tools, |
| 5393 | + ) { |
| 5394 | + let mut outcome = outcome_from_status_and_execution( |
| 5395 | + suite, |
| 5396 | + case, |
| 5397 | + opt_level, |
| 5398 | + capability_effective_status(&effective_status, case), |
| 5399 | + Err(detail), |
| 5400 | + None, |
| 5401 | + Vec::new(), |
| 5402 | + ); |
| 5403 | + outcome.detail = outcome.detail.trim().to_string(); |
| 5404 | + cleanup_prepared_input(&prepared); |
| 5405 | + return Ok(outcome); |
| 5406 | + } |
| 5407 | + |
| 5171 | 5408 | if config.verbose { |
| 5172 | 5409 | let artifacts = generic |
| 5173 | 5410 | .artifacts |
@@ -5274,6 +5511,7 @@ fn execute_generic_compare_case_cell( |
| 5274 | 5511 | }; |
| 5275 | 5512 | |
| 5276 | 5513 | outcome.detail = outcome.detail.trim().to_string(); |
| 5514 | + cleanup_prepared_input(&prepared); |
| 5277 | 5515 | Ok(outcome) |
| 5278 | 5516 | } |
| 5279 | 5517 | |
@@ -5305,6 +5543,23 @@ fn execute_generic_introspect_case_cell( |
| 5305 | 5543 | .ok_or_else(|| "missing generic introspection case configuration".to_string())?; |
| 5306 | 5544 | let prepared = prepare_case_input(case, suite, opt_level)?; |
| 5307 | 5545 | |
| 5546 | + if let Some(detail) = |
| 5547 | + capability_request_issue(&generic.compiler, &generic.artifacts, &config.tools) |
| 5548 | + { |
| 5549 | + let mut outcome = outcome_from_status_and_execution( |
| 5550 | + suite, |
| 5551 | + case, |
| 5552 | + opt_level, |
| 5553 | + capability_effective_status(&effective_status, case), |
| 5554 | + Err(detail), |
| 5555 | + None, |
| 5556 | + Vec::new(), |
| 5557 | + ); |
| 5558 | + outcome.detail = outcome.detail.trim().to_string(); |
| 5559 | + cleanup_prepared_input(&prepared); |
| 5560 | + return Ok(outcome); |
| 5561 | + } |
| 5562 | + |
| 5308 | 5563 | if config.verbose { |
| 5309 | 5564 | let artifacts = generic |
| 5310 | 5565 | .artifacts |
@@ -5467,6 +5722,7 @@ fn execute_generic_introspect_case_cell( |
| 5467 | 5722 | }; |
| 5468 | 5723 | |
| 5469 | 5724 | outcome.detail = outcome.detail.trim().to_string(); |
| 5725 | + cleanup_prepared_input(&prepared); |
| 5470 | 5726 | cleanup_consistency_issues(&consistency_issues); |
| 5471 | 5727 | Ok(outcome) |
| 5472 | 5728 | } |
@@ -5616,6 +5872,19 @@ fn status_for_opt(case: &CaseSpec, opt_level: OptLevel) -> EffectiveStatus { |
| 5616 | 5872 | status |
| 5617 | 5873 | } |
| 5618 | 5874 | |
| 5875 | +fn capability_effective_status(base: &EffectiveStatus, case: &CaseSpec) -> EffectiveStatus { |
| 5876 | + match base { |
| 5877 | + EffectiveStatus::Normal => match &case.capability_policy { |
| 5878 | + Some(policy) => match policy.kind { |
| 5879 | + StatusKind::Xfail => EffectiveStatus::Xfail(policy.reason.clone()), |
| 5880 | + StatusKind::Future => EffectiveStatus::Future(policy.reason.clone()), |
| 5881 | + }, |
| 5882 | + None => EffectiveStatus::Normal, |
| 5883 | + }, |
| 5884 | + other => other.clone(), |
| 5885 | + } |
| 5886 | +} |
| 5887 | + |
| 5619 | 5888 | fn ensure_target_stage(expectation: &Expectation, requested: &mut BTreeSet<Stage>) { |
| 5620 | 5889 | match expectation { |
| 5621 | 5890 | Expectation::CheckComments(target) |
@@ -6030,7 +6299,7 @@ fn outcome_from_status_and_execution( |
| 6030 | 6299 | suite: suite.name.clone(), |
| 6031 | 6300 | case: case.name.clone(), |
| 6032 | 6301 | opt_level, |
| 6033 | | - kind: OutcomeKind::Pass, |
| 6302 | + kind: OutcomeKind::Xpass, |
| 6034 | 6303 | detail: reason, |
| 6035 | 6304 | bundle: None, |
| 6036 | 6305 | primary_backend, |
@@ -6040,7 +6309,7 @@ fn outcome_from_status_and_execution( |
| 6040 | 6309 | suite: suite.name.clone(), |
| 6041 | 6310 | case: case.name.clone(), |
| 6042 | 6311 | opt_level, |
| 6043 | | - kind: OutcomeKind::Fail, |
| 6312 | + kind: OutcomeKind::Future, |
| 6044 | 6313 | detail: format!("{}\n{}", reason, detail), |
| 6045 | 6314 | bundle: None, |
| 6046 | 6315 | primary_backend, |
@@ -10810,12 +11079,17 @@ mod tests { |
| 10810 | 11079 | needle: "42".into(), |
| 10811 | 11080 | }], |
| 10812 | 11081 | status_rules: Vec::new(), |
| 11082 | + capability_policy: None, |
| 10813 | 11083 | }; |
| 10814 | 11084 | let requested = BTreeSet::from([Stage::Run]); |
| 10815 | 11085 | let external_tools = ToolchainConfig { |
| 10816 | 11086 | armfortas: ArmfortasCliAdapter::External("/tmp/armfortas".into()), |
| 10817 | 11087 | gfortran: "gfortran".into(), |
| 10818 | 11088 | flang_new: "flang-new".into(), |
| 11089 | + lfortran: "lfortran".into(), |
| 11090 | + ifort: "ifort".into(), |
| 11091 | + ifx: "ifx".into(), |
| 11092 | + nvfortran: "nvfortran".into(), |
| 10819 | 11093 | system_as: "as".into(), |
| 10820 | 11094 | otool: "otool".into(), |
| 10821 | 11095 | nm: "nm".into(), |
@@ -10886,6 +11160,7 @@ mod tests { |
| 10886 | 11160 | needle: "program".into(), |
| 10887 | 11161 | }], |
| 10888 | 11162 | status_rules: Vec::new(), |
| 11163 | + capability_policy: None, |
| 10889 | 11164 | }; |
| 10890 | 11165 | let backend = SelectedPrimaryBackend { |
| 10891 | 11166 | kind: PrimaryCaptureBackendKind::Full, |
@@ -10921,6 +11196,7 @@ mod tests { |
| 10921 | 11196 | needle: "42".into(), |
| 10922 | 11197 | }], |
| 10923 | 11198 | status_rules: Vec::new(), |
| 11199 | + capability_policy: None, |
| 10924 | 11200 | }; |
| 10925 | 11201 | assert!(legacy_case_uses_generic_consistency_checks(&cli_only_case)); |
| 10926 | 11202 | |
@@ -10952,6 +11228,7 @@ mod tests { |
| 10952 | 11228 | needle: "42".into(), |
| 10953 | 11229 | }], |
| 10954 | 11230 | status_rules: Vec::new(), |
| 11231 | + capability_policy: None, |
| 10955 | 11232 | }; |
| 10956 | 11233 | assert!(legacy_case_uses_generic_observation_execution( |
| 10957 | 11234 | &observable_case, |
@@ -11022,6 +11299,7 @@ mod tests { |
| 11022 | 11299 | }, |
| 11023 | 11300 | ], |
| 11024 | 11301 | status_rules: Vec::new(), |
| 11302 | + capability_policy: None, |
| 11025 | 11303 | }; |
| 11026 | 11304 | let prepared = PreparedInput { |
| 11027 | 11305 | compiler_source: source.clone(), |
@@ -11032,6 +11310,10 @@ mod tests { |
| 11032 | 11310 | armfortas: ArmfortasCliAdapter::External(compiler.display().to_string()), |
| 11033 | 11311 | gfortran: "gfortran".into(), |
| 11034 | 11312 | flang_new: "flang-new".into(), |
| 11313 | + lfortran: "lfortran".into(), |
| 11314 | + ifort: "ifort".into(), |
| 11315 | + ifx: "ifx".into(), |
| 11316 | + nvfortran: "nvfortran".into(), |
| 11035 | 11317 | system_as: "as".into(), |
| 11036 | 11318 | otool: "otool".into(), |
| 11037 | 11319 | nm: "nm".into(), |
@@ -11143,7 +11425,8 @@ mod tests { |
| 11143 | 11425 | |
| 11144 | 11426 | let err = run_compare(&config).unwrap_err(); |
| 11145 | 11427 | assert!(err.contains("compare request is not supported")); |
| 11146 | | - assert!(err.contains("right gfortran")); |
| 11428 | + assert!(err.contains("right:")); |
| 11429 | + assert!(err.contains("gfortran does not support requested artifacts")); |
| 11147 | 11430 | assert!(err.contains("armfortas.ir")); |
| 11148 | 11431 | } |
| 11149 | 11432 | |
@@ -11728,6 +12011,7 @@ mod tests { |
| 11728 | 12011 | needle: "func".into(), |
| 11729 | 12012 | }], |
| 11730 | 12013 | status_rules: Vec::new(), |
| 12014 | + capability_policy: None, |
| 11731 | 12015 | }; |
| 11732 | 12016 | let config = RunConfig { |
| 11733 | 12017 | suite_filter: None, |
@@ -11750,6 +12034,55 @@ mod tests { |
| 11750 | 12034 | assert!(!outcome.detail.contains("gfortran failed")); |
| 11751 | 12035 | } |
| 11752 | 12036 | |
| 12037 | + #[test] |
| 12038 | + fn execute_generic_introspect_case_applies_future_capability_policy() { |
| 12039 | + let suite = SuiteSpec { |
| 12040 | + name: "v2/capability-policy".into(), |
| 12041 | + path: PathBuf::from("suite.afs"), |
| 12042 | + cases: Vec::new(), |
| 12043 | + }; |
| 12044 | + let case = CaseSpec { |
| 12045 | + name: "gfortran-armfortas-ir".into(), |
| 12046 | + source: runtime_fixture("if_else.f90"), |
| 12047 | + graph_files: Vec::new(), |
| 12048 | + requested: BTreeSet::new(), |
| 12049 | + generic_introspect: Some(GenericIntrospectCase { |
| 12050 | + compiler: CompilerSpec::Named(NamedCompiler::Gfortran), |
| 12051 | + artifacts: BTreeSet::from([ArtifactKey::Extra("armfortas.ir".into())]), |
| 12052 | + }), |
| 12053 | + generic_compare: None, |
| 12054 | + opt_levels: vec![OptLevel::O0], |
| 12055 | + repeat_count: 2, |
| 12056 | + reference_compilers: Vec::new(), |
| 12057 | + consistency_checks: Vec::new(), |
| 12058 | + expectations: Vec::new(), |
| 12059 | + status_rules: Vec::new(), |
| 12060 | + capability_policy: Some(CapabilityPolicy { |
| 12061 | + kind: StatusKind::Future, |
| 12062 | + reason: "generic gfortran surface has no armfortas extras".into(), |
| 12063 | + }), |
| 12064 | + }; |
| 12065 | + let config = RunConfig { |
| 12066 | + suite_filter: None, |
| 12067 | + case_filter: None, |
| 12068 | + opt_filter: None, |
| 12069 | + verbose: false, |
| 12070 | + fail_fast: false, |
| 12071 | + include_future: false, |
| 12072 | + all_stages: false, |
| 12073 | + json_report: None, |
| 12074 | + markdown_report: None, |
| 12075 | + tools: ToolchainConfig::from_env(), |
| 12076 | + }; |
| 12077 | + |
| 12078 | + let outcome = execute_case_cell(&suite, &case, OptLevel::O0, &config).unwrap(); |
| 12079 | + assert_eq!(outcome.kind, OutcomeKind::Future); |
| 12080 | + assert!(outcome |
| 12081 | + .detail |
| 12082 | + .contains("generic gfortran surface has no armfortas extras")); |
| 12083 | + assert!(outcome.detail.contains("armfortas.ir")); |
| 12084 | + } |
| 12085 | + |
| 11753 | 12086 | #[cfg(unix)] |
| 11754 | 12087 | #[test] |
| 11755 | 12088 | fn execute_generic_suite_case_uses_introspect_engine() { |
@@ -11790,6 +12123,7 @@ mod tests { |
| 11790 | 12123 | }, |
| 11791 | 12124 | ], |
| 11792 | 12125 | status_rules: Vec::new(), |
| 12126 | + capability_policy: None, |
| 11793 | 12127 | }; |
| 11794 | 12128 | let config = RunConfig { |
| 11795 | 12129 | suite_filter: None, |
@@ -11849,6 +12183,7 @@ mod tests { |
| 11849 | 12183 | }, |
| 11850 | 12184 | ], |
| 11851 | 12185 | status_rules: Vec::new(), |
| 12186 | + capability_policy: None, |
| 11852 | 12187 | }; |
| 11853 | 12188 | let config = RunConfig { |
| 11854 | 12189 | suite_filter: None, |
@@ -11906,6 +12241,7 @@ mod tests { |
| 11906 | 12241 | }, |
| 11907 | 12242 | ], |
| 11908 | 12243 | status_rules: Vec::new(), |
| 12244 | + capability_policy: None, |
| 11909 | 12245 | }; |
| 11910 | 12246 | let config = RunConfig { |
| 11911 | 12247 | suite_filter: None, |
@@ -11980,6 +12316,7 @@ mod tests { |
| 11980 | 12316 | }, |
| 11981 | 12317 | ], |
| 11982 | 12318 | status_rules: Vec::new(), |
| 12319 | + capability_policy: None, |
| 11983 | 12320 | }; |
| 11984 | 12321 | let config = RunConfig { |
| 11985 | 12322 | suite_filter: None, |
@@ -12029,6 +12366,7 @@ mod tests { |
| 12029 | 12366 | value: "match".into(), |
| 12030 | 12367 | }], |
| 12031 | 12368 | status_rules: Vec::new(), |
| 12369 | + capability_policy: None, |
| 12032 | 12370 | }; |
| 12033 | 12371 | let config = RunConfig { |
| 12034 | 12372 | suite_filter: None, |
@@ -12049,6 +12387,56 @@ mod tests { |
| 12049 | 12387 | assert!(outcome.detail.contains("armfortas.ir")); |
| 12050 | 12388 | } |
| 12051 | 12389 | |
| 12390 | + #[test] |
| 12391 | + fn execute_generic_compare_suite_case_applies_xfail_capability_policy() { |
| 12392 | + let suite = SuiteSpec { |
| 12393 | + name: "v2/capability-policy".into(), |
| 12394 | + path: PathBuf::from("suite.afs"), |
| 12395 | + cases: Vec::new(), |
| 12396 | + }; |
| 12397 | + let case = CaseSpec { |
| 12398 | + name: "armfortas-ir-vs-gfortran".into(), |
| 12399 | + source: runtime_fixture("if_else.f90"), |
| 12400 | + graph_files: Vec::new(), |
| 12401 | + requested: BTreeSet::new(), |
| 12402 | + generic_introspect: None, |
| 12403 | + generic_compare: Some(GenericCompareCase { |
| 12404 | + left: CompilerSpec::Named(NamedCompiler::Armfortas), |
| 12405 | + right: CompilerSpec::Named(NamedCompiler::Gfortran), |
| 12406 | + artifacts: BTreeSet::from([ArtifactKey::Extra("armfortas.ir".into())]), |
| 12407 | + }), |
| 12408 | + opt_levels: vec![OptLevel::O0], |
| 12409 | + repeat_count: 2, |
| 12410 | + reference_compilers: Vec::new(), |
| 12411 | + consistency_checks: Vec::new(), |
| 12412 | + expectations: Vec::new(), |
| 12413 | + status_rules: Vec::new(), |
| 12414 | + capability_policy: Some(CapabilityPolicy { |
| 12415 | + kind: StatusKind::Xfail, |
| 12416 | + reason: "mixed-surface namespaced compare stays soft for now".into(), |
| 12417 | + }), |
| 12418 | + }; |
| 12419 | + let config = RunConfig { |
| 12420 | + suite_filter: None, |
| 12421 | + case_filter: None, |
| 12422 | + opt_filter: None, |
| 12423 | + verbose: false, |
| 12424 | + fail_fast: false, |
| 12425 | + include_future: false, |
| 12426 | + all_stages: false, |
| 12427 | + json_report: None, |
| 12428 | + markdown_report: None, |
| 12429 | + tools: ToolchainConfig::from_env(), |
| 12430 | + }; |
| 12431 | + |
| 12432 | + let outcome = execute_case_cell(&suite, &case, OptLevel::O0, &config).unwrap(); |
| 12433 | + assert_eq!(outcome.kind, OutcomeKind::Xfail); |
| 12434 | + assert!(outcome |
| 12435 | + .detail |
| 12436 | + .contains("mixed-surface namespaced compare stays soft for now")); |
| 12437 | + assert!(outcome.detail.contains("armfortas.ir")); |
| 12438 | + } |
| 12439 | + |
| 12052 | 12440 | #[test] |
| 12053 | 12441 | fn parses_suite_and_case() { |
| 12054 | 12442 | let root = std::env::temp_dir().join("afs_tests_parser_spec.afs"); |
@@ -12242,6 +12630,33 @@ end |
| 12242 | 12630 | let _ = fs::remove_file(&root); |
| 12243 | 12631 | } |
| 12244 | 12632 | |
| 12633 | + #[test] |
| 12634 | + fn parses_capability_policy_for_generic_case() { |
| 12635 | + let root = std::env::temp_dir().join("bencch_generic_capability_policy_spec.afs"); |
| 12636 | + fs::write( |
| 12637 | + &root, |
| 12638 | + r#"suite "v2/capability-policy" |
| 12639 | + |
| 12640 | +case "gfortran_armfortas_ir" |
| 12641 | +source "../../fixtures/runtime/if_else.f90" |
| 12642 | +compiler gfortran => armfortas.ir |
| 12643 | +future capability "generic gfortran surface has no armfortas extras" |
| 12644 | +end |
| 12645 | +"#, |
| 12646 | + ) |
| 12647 | + .unwrap(); |
| 12648 | + |
| 12649 | + let suite = parse_suite_file(&root).unwrap(); |
| 12650 | + let case = &suite.cases[0]; |
| 12651 | + let policy = case.capability_policy.as_ref().unwrap(); |
| 12652 | + assert!(matches!(policy.kind, StatusKind::Future)); |
| 12653 | + assert_eq!( |
| 12654 | + policy.reason, |
| 12655 | + "generic gfortran surface has no armfortas extras" |
| 12656 | + ); |
| 12657 | + let _ = fs::remove_file(&root); |
| 12658 | + } |
| 12659 | + |
| 12245 | 12660 | #[test] |
| 12246 | 12661 | fn parses_matrix_status_and_differential() { |
| 12247 | 12662 | let root = std::env::temp_dir().join("afs_tests_matrix_spec.afs"); |
@@ -12374,6 +12789,14 @@ end |
| 12374 | 12789 | "/tmp/gfortran".to_string(), |
| 12375 | 12790 | "--flang-bin".to_string(), |
| 12376 | 12791 | "/tmp/flang-new".to_string(), |
| 12792 | + "--lfortran-bin".to_string(), |
| 12793 | + "/tmp/lfortran".to_string(), |
| 12794 | + "--ifort-bin".to_string(), |
| 12795 | + "/tmp/ifort".to_string(), |
| 12796 | + "--ifx-bin".to_string(), |
| 12797 | + "/tmp/ifx".to_string(), |
| 12798 | + "--nvfortran-bin".to_string(), |
| 12799 | + "/tmp/nvfortran".to_string(), |
| 12377 | 12800 | "--as-bin".to_string(), |
| 12378 | 12801 | "/tmp/as".to_string(), |
| 12379 | 12802 | "--otool-bin".to_string(), |
@@ -12406,6 +12829,10 @@ end |
| 12406 | 12829 | ); |
| 12407 | 12830 | assert_eq!(config.tools.gfortran, "/tmp/gfortran"); |
| 12408 | 12831 | assert_eq!(config.tools.flang_new, "/tmp/flang-new"); |
| 12832 | + assert_eq!(config.tools.lfortran, "/tmp/lfortran"); |
| 12833 | + assert_eq!(config.tools.ifort, "/tmp/ifort"); |
| 12834 | + assert_eq!(config.tools.ifx, "/tmp/ifx"); |
| 12835 | + assert_eq!(config.tools.nvfortran, "/tmp/nvfortran"); |
| 12409 | 12836 | assert_eq!(config.tools.system_as, "/tmp/as"); |
| 12410 | 12837 | assert_eq!(config.tools.otool, "/tmp/otool"); |
| 12411 | 12838 | assert_eq!(config.tools.nm, "/tmp/nm"); |
@@ -12453,6 +12880,14 @@ end |
| 12453 | 12880 | "/tmp/gfortran".to_string(), |
| 12454 | 12881 | "--flang-bin".to_string(), |
| 12455 | 12882 | "/tmp/flang-new".to_string(), |
| 12883 | + "--lfortran-bin".to_string(), |
| 12884 | + "/tmp/lfortran".to_string(), |
| 12885 | + "--ifort-bin".to_string(), |
| 12886 | + "/tmp/ifort".to_string(), |
| 12887 | + "--ifx-bin".to_string(), |
| 12888 | + "/tmp/ifx".to_string(), |
| 12889 | + "--nvfortran-bin".to_string(), |
| 12890 | + "/tmp/nvfortran".to_string(), |
| 12456 | 12891 | "--as-bin".to_string(), |
| 12457 | 12892 | "/tmp/as".to_string(), |
| 12458 | 12893 | "--otool-bin".to_string(), |
@@ -12476,6 +12911,10 @@ end |
| 12476 | 12911 | ); |
| 12477 | 12912 | assert_eq!(config.tools.gfortran, "/tmp/gfortran"); |
| 12478 | 12913 | assert_eq!(config.tools.flang_new, "/tmp/flang-new"); |
| 12914 | + assert_eq!(config.tools.lfortran, "/tmp/lfortran"); |
| 12915 | + assert_eq!(config.tools.ifort, "/tmp/ifort"); |
| 12916 | + assert_eq!(config.tools.ifx, "/tmp/ifx"); |
| 12917 | + assert_eq!(config.tools.nvfortran, "/tmp/nvfortran"); |
| 12479 | 12918 | assert_eq!(config.tools.system_as, "/tmp/as"); |
| 12480 | 12919 | assert_eq!(config.tools.otool, "/tmp/otool"); |
| 12481 | 12920 | assert_eq!(config.tools.nm, "/tmp/nm"); |
@@ -12810,6 +13249,7 @@ end |
| 12810 | 13249 | needle: "x18".into(), |
| 12811 | 13250 | }], |
| 12812 | 13251 | status_rules: Vec::new(), |
| 13252 | + capability_policy: None, |
| 12813 | 13253 | }; |
| 12814 | 13254 | let result = CaptureResult { |
| 12815 | 13255 | input: PathBuf::from("demo.f90"), |
@@ -12870,6 +13310,7 @@ end |
| 12870 | 13310 | consistency_checks: vec![ConsistencyCheck::CliObjVsSystemAs], |
| 12871 | 13311 | expectations: Vec::new(), |
| 12872 | 13312 | status_rules: Vec::new(), |
| 13313 | + capability_policy: None, |
| 12873 | 13314 | }; |
| 12874 | 13315 | let mut stages = std::collections::BTreeMap::new(); |
| 12875 | 13316 | stages.insert(Stage::Ir, CapturedStage::Text("module main".into())); |
@@ -13163,6 +13604,7 @@ end |
| 13163 | 13604 | consistency_checks: Vec::new(), |
| 13164 | 13605 | expectations: Vec::new(), |
| 13165 | 13606 | status_rules: Vec::new(), |
| 13607 | + capability_policy: None, |
| 13166 | 13608 | }; |
| 13167 | 13609 | |
| 13168 | 13610 | let prepared = prepare_case_input(&case, &suite, OptLevel::O0).unwrap(); |
@@ -13215,6 +13657,7 @@ end |
| 13215 | 13657 | consistency_checks: Vec::new(), |
| 13216 | 13658 | expectations: Vec::new(), |
| 13217 | 13659 | status_rules: Vec::new(), |
| 13660 | + capability_policy: None, |
| 13218 | 13661 | }; |
| 13219 | 13662 | let outcome = Outcome { |
| 13220 | 13663 | suite: suite.name.clone(), |
@@ -13698,6 +14141,10 @@ end |
| 13698 | 14141 | armfortas: ArmfortasCliAdapter::External(armfortas_bin.display().to_string()), |
| 13699 | 14142 | gfortran: gfortran_bin.display().to_string(), |
| 13700 | 14143 | flang_new: "/tmp/does-not-exist-flang".into(), |
| 14144 | + lfortran: "/tmp/does-not-exist-lfortran".into(), |
| 14145 | + ifort: "/tmp/does-not-exist-ifort".into(), |
| 14146 | + ifx: "/tmp/does-not-exist-ifx".into(), |
| 14147 | + nvfortran: "/tmp/does-not-exist-nvfortran".into(), |
| 13701 | 14148 | system_as: "/tmp/does-not-exist-as".into(), |
| 13702 | 14149 | otool: "/tmp/does-not-exist-otool".into(), |
| 13703 | 14150 | nm: "/tmp/does-not-exist-nm".into(), |
@@ -13737,6 +14184,11 @@ end |
| 13737 | 14184 | "named_compiler.gfortran.generic_artifacts: diagnostics, exit-code, stdout, stderr, asm, obj, executable, runtime" |
| 13738 | 14185 | )); |
| 13739 | 14186 | assert!(rendered.contains("named_compiler.gfortran.adapter_extras: none")); |
| 14187 | + assert!(rendered.contains("named_compiler.lfortran:")); |
| 14188 | + assert!(rendered.contains("named_compiler.lfortran.accepted_names: lfortran")); |
| 14189 | + assert!(rendered.contains("named_compiler.lfortran.candidate_binaries: lfortran")); |
| 14190 | + assert!(rendered.contains("named_compiler.ifx.accepted_names: ifx")); |
| 14191 | + assert!(rendered.contains("named_compiler.nvfortran.accepted_names: nvfortran, pgfortran")); |
| 13740 | 14192 | assert!(rendered.contains( |
| 13741 | 14193 | "explicit_compiler_path: any filesystem path passed to compare/introspect uses the generic external-driver adapter" |
| 13742 | 14194 | )); |
@@ -13760,6 +14212,7 @@ end |
| 13760 | 14212 | assert!(rendered_json.contains("\"workspace\": {")); |
| 13761 | 14213 | assert!(rendered_json.contains("\"named_compilers\": {")); |
| 13762 | 14214 | assert!(rendered_json.contains("\"tools\": {")); |
| 14215 | + assert!(rendered_json.contains("\"lfortran\": {")); |
| 13763 | 14216 | assert!(rendered_json.contains("\"named_compiler.armfortas.adapter_extras\"")); |
| 13764 | 14217 | assert!(rendered_markdown.contains("# bencch doctor report")); |
| 13765 | 14218 | assert!(rendered_markdown.contains("| `named_compiler.armfortas` |")); |
@@ -13785,13 +14238,46 @@ end |
| 13785 | 14238 | consistency_checks: Vec::new(), |
| 13786 | 14239 | expectations: Vec::new(), |
| 13787 | 14240 | status_rules: Vec::new(), |
| 14241 | + capability_policy: None, |
| 13788 | 14242 | }; |
| 13789 | 14243 | |
| 13790 | 14244 | let lines = case_discovery_lines(&case, &ToolchainConfig::from_env()); |
| 13791 | 14245 | assert!(lines.contains(&"capability_status: blocked".to_string())); |
| 13792 | | - assert!(lines |
| 13793 | | - .iter() |
| 13794 | | - .any(|line| line.contains("unsupported in this adapter: armfortas.ir"))); |
| 14246 | + assert!(lines.iter().any(|line| line.contains( |
| 14247 | + "gfortran does not support requested artifacts in this adapter: armfortas.ir" |
| 14248 | + ))); |
| 14249 | + } |
| 14250 | + |
| 14251 | + #[test] |
| 14252 | + fn case_discovery_lines_report_capability_policy_as_deferred() { |
| 14253 | + let case = CaseSpec { |
| 14254 | + name: "unsupported_extra".into(), |
| 14255 | + source: PathBuf::from("demo.f90"), |
| 14256 | + graph_files: Vec::new(), |
| 14257 | + requested: BTreeSet::new(), |
| 14258 | + generic_introspect: Some(GenericIntrospectCase { |
| 14259 | + compiler: CompilerSpec::Named(NamedCompiler::Gfortran), |
| 14260 | + artifacts: BTreeSet::from([ArtifactKey::Extra("armfortas.ir".into())]), |
| 14261 | + }), |
| 14262 | + generic_compare: None, |
| 14263 | + opt_levels: vec![OptLevel::O0], |
| 14264 | + repeat_count: 2, |
| 14265 | + reference_compilers: Vec::new(), |
| 14266 | + consistency_checks: Vec::new(), |
| 14267 | + expectations: Vec::new(), |
| 14268 | + status_rules: Vec::new(), |
| 14269 | + capability_policy: Some(CapabilityPolicy { |
| 14270 | + kind: StatusKind::Future, |
| 14271 | + reason: "generic gfortran surface has no armfortas extras".into(), |
| 14272 | + }), |
| 14273 | + }; |
| 14274 | + |
| 14275 | + let lines = case_discovery_lines(&case, &ToolchainConfig::from_env()); |
| 14276 | + assert!(lines.contains(&"capability_status: deferred".to_string())); |
| 14277 | + assert!(lines.contains( |
| 14278 | + &"capability_policy: future when blocked (generic gfortran surface has no armfortas extras)" |
| 14279 | + .to_string() |
| 14280 | + )); |
| 13795 | 14281 | } |
| 13796 | 14282 | |
| 13797 | 14283 | #[test] |
@@ -13809,6 +14295,7 @@ end |
| 13809 | 14295 | consistency_checks: Vec::new(), |
| 13810 | 14296 | expectations: Vec::new(), |
| 13811 | 14297 | status_rules: Vec::new(), |
| 14298 | + capability_policy: None, |
| 13812 | 14299 | }; |
| 13813 | 14300 | let linked_case = CaseSpec { |
| 13814 | 14301 | name: "linked".into(), |
@@ -13823,6 +14310,7 @@ end |
| 13823 | 14310 | consistency_checks: vec![ConsistencyCheck::CaptureAsmReproducible], |
| 13824 | 14311 | expectations: Vec::new(), |
| 13825 | 14312 | status_rules: Vec::new(), |
| 14313 | + capability_policy: None, |
| 13826 | 14314 | }; |
| 13827 | 14315 | |
| 13828 | 14316 | let tools = ToolchainConfig { |
@@ -14140,6 +14628,7 @@ end |
| 14140 | 14628 | }, |
| 14141 | 14629 | ], |
| 14142 | 14630 | status_rules: Vec::new(), |
| 14631 | + capability_policy: None, |
| 14143 | 14632 | }; |
| 14144 | 14633 | let artifacts = ExecutionArtifacts { |
| 14145 | 14634 | requested: BTreeSet::from([Stage::Tokens, Stage::Run]), |
@@ -14176,6 +14665,7 @@ end |
| 14176 | 14665 | consistency_checks: Vec::new(), |
| 14177 | 14666 | expectations: vec![Expectation::FailCommentPatterns(vec!["hidden".into()])], |
| 14178 | 14667 | status_rules: Vec::new(), |
| 14668 | + capability_policy: None, |
| 14179 | 14669 | }; |
| 14180 | 14670 | let artifacts = ExecutionArtifacts { |
| 14181 | 14671 | requested: BTreeSet::from([Stage::Run]), |
@@ -14215,6 +14705,7 @@ end |
| 14215 | 14705 | needle: "expected 'then'".into(), |
| 14216 | 14706 | }], |
| 14217 | 14707 | status_rules: Vec::new(), |
| 14708 | + capability_policy: None, |
| 14218 | 14709 | }; |
| 14219 | 14710 | let failure = CaptureFailure { |
| 14220 | 14711 | input: PathBuf::from("generated.f90"), |
@@ -14259,6 +14750,7 @@ end |
| 14259 | 14750 | needle: "42".into(), |
| 14260 | 14751 | }], |
| 14261 | 14752 | status_rules: Vec::new(), |
| 14753 | + capability_policy: None, |
| 14262 | 14754 | }; |
| 14263 | 14755 | let failure = CaptureFailure { |
| 14264 | 14756 | input: PathBuf::from("graph.f90"), |
@@ -14301,6 +14793,7 @@ end |
| 14301 | 14793 | needle: ".globl _add_one".into(), |
| 14302 | 14794 | }], |
| 14303 | 14795 | status_rules: Vec::new(), |
| 14796 | + capability_policy: None, |
| 14304 | 14797 | }; |
| 14305 | 14798 | let failure = CaptureFailure { |
| 14306 | 14799 | input: PathBuf::from("graph.f90"), |