@@ -1403,15 +1403,7 @@ fn execute_case_cell( |
| 1403 | 1403 | } |
| 1404 | 1404 | } |
| 1405 | 1405 | (None, Some(failure)) => { |
| 1406 | | - let partial = failure.partial_result(); |
| 1407 | | - let mut execution = evaluate_positive_expectations(case, &partial); |
| 1408 | | - if execution.is_ok() { |
| 1409 | | - if has_failure_expectation(case) { |
| 1410 | | - execution = evaluate_failure_expectations(case, failure); |
| 1411 | | - } else { |
| 1412 | | - execution = Err(compose_armfortas_failure_detail(&artifacts)); |
| 1413 | | - } |
| 1414 | | - } |
| 1406 | + let mut execution = evaluate_failed_armfortas(case, &artifacts, failure); |
| 1415 | 1407 | if execution.is_ok() && !artifacts.references.is_empty() { |
| 1416 | 1408 | execution = |
| 1417 | 1409 | Err("differential comparison requires a successful armfortas run".to_string()); |
@@ -1748,6 +1740,25 @@ fn evaluate_failure_expectations(case: &CaseSpec, failure: &CaptureFailure) -> R |
| 1748 | 1740 | Ok(()) |
| 1749 | 1741 | } |
| 1750 | 1742 | |
| 1743 | +fn evaluate_failed_armfortas( |
| 1744 | + case: &CaseSpec, |
| 1745 | + artifacts: &ExecutionArtifacts, |
| 1746 | + failure: &CaptureFailure, |
| 1747 | +) -> Result<(), String> { |
| 1748 | + if has_failure_expectation(case) { |
| 1749 | + evaluate_failure_expectations(case, failure) |
| 1750 | + } else { |
| 1751 | + let partial = failure.partial_result(); |
| 1752 | + match evaluate_positive_expectations(case, &partial) { |
| 1753 | + Ok(()) => Err(compose_armfortas_failure_detail(artifacts)), |
| 1754 | + Err(detail) if is_missing_stage_detail(&detail) => { |
| 1755 | + Err(compose_armfortas_failure_detail(artifacts)) |
| 1756 | + } |
| 1757 | + Err(detail) => Err(detail), |
| 1758 | + } |
| 1759 | + } |
| 1760 | +} |
| 1761 | + |
| 1751 | 1762 | fn has_failure_expectation(case: &CaseSpec) -> bool { |
| 1752 | 1763 | case.expectations.iter().any(|expectation| { |
| 1753 | 1764 | matches!( |
@@ -1777,6 +1788,10 @@ fn expected_failure_description(case: &CaseSpec) -> String { |
| 1777 | 1788 | } |
| 1778 | 1789 | } |
| 1779 | 1790 | |
| 1791 | +fn is_missing_stage_detail(detail: &str) -> bool { |
| 1792 | + detail.starts_with("missing captured stage '") || detail == "missing captured run stage" |
| 1793 | +} |
| 1794 | + |
| 1780 | 1795 | fn target_text<'a>(result: &'a CaptureResult, target: &Target) -> Result<&'a str, String> { |
| 1781 | 1796 | match target { |
| 1782 | 1797 | Target::Stage(stage) => match result.get(*stage) { |
@@ -5721,4 +5736,126 @@ end |
| 5721 | 5736 | errors |
| 5722 | 5737 | ); |
| 5723 | 5738 | } |
| 5739 | + |
| 5740 | + #[test] |
| 5741 | + fn failure_expectation_precedes_partial_stage_checks() { |
| 5742 | + let case = CaseSpec { |
| 5743 | + name: "missing_then".into(), |
| 5744 | + source: PathBuf::from("demo.f90"), |
| 5745 | + graph_files: Vec::new(), |
| 5746 | + requested: BTreeSet::from([Stage::Tokens, Stage::Run]), |
| 5747 | + opt_levels: vec![OptLevel::O0], |
| 5748 | + repeat_count: 3, |
| 5749 | + reference_compilers: Vec::new(), |
| 5750 | + consistency_checks: Vec::new(), |
| 5751 | + expectations: vec![ |
| 5752 | + Expectation::Contains { |
| 5753 | + target: Target::RunStdout, |
| 5754 | + needle: "42".into(), |
| 5755 | + }, |
| 5756 | + Expectation::FailContains { |
| 5757 | + stage: FailureStage::Parser, |
| 5758 | + needle: "expected 'then'".into(), |
| 5759 | + }, |
| 5760 | + ], |
| 5761 | + status_rules: Vec::new(), |
| 5762 | + }; |
| 5763 | + let artifacts = ExecutionArtifacts { |
| 5764 | + requested: BTreeSet::from([Stage::Tokens, Stage::Run]), |
| 5765 | + armfortas: None, |
| 5766 | + armfortas_failure: None, |
| 5767 | + references: Vec::new(), |
| 5768 | + consistency_issues: Vec::new(), |
| 5769 | + }; |
| 5770 | + let failure = CaptureFailure { |
| 5771 | + input: PathBuf::from("demo.f90"), |
| 5772 | + opt_level: OptLevel::O0, |
| 5773 | + stage: FailureStage::Parser, |
| 5774 | + detail: "expected 'then'".into(), |
| 5775 | + stages: BTreeMap::from([( |
| 5776 | + Stage::Tokens, |
| 5777 | + CapturedStage::Text("if\n".into()), |
| 5778 | + )]), |
| 5779 | + }; |
| 5780 | + |
| 5781 | + assert!(evaluate_failed_armfortas(&case, &artifacts, &failure).is_ok()); |
| 5782 | + } |
| 5783 | + |
| 5784 | + #[test] |
| 5785 | + fn unexpected_capture_failure_reports_compiler_failure_detail() { |
| 5786 | + let case = CaseSpec { |
| 5787 | + name: "module_procedure_runtime".into(), |
| 5788 | + source: PathBuf::from("graph.f90"), |
| 5789 | + graph_files: Vec::new(), |
| 5790 | + requested: BTreeSet::from([Stage::Run]), |
| 5791 | + opt_levels: vec![OptLevel::O0], |
| 5792 | + repeat_count: 3, |
| 5793 | + reference_compilers: Vec::new(), |
| 5794 | + consistency_checks: Vec::new(), |
| 5795 | + expectations: vec![Expectation::Contains { |
| 5796 | + target: Target::RunStdout, |
| 5797 | + needle: "42".into(), |
| 5798 | + }], |
| 5799 | + status_rules: Vec::new(), |
| 5800 | + }; |
| 5801 | + let failure = CaptureFailure { |
| 5802 | + input: PathBuf::from("graph.f90"), |
| 5803 | + opt_level: OptLevel::O0, |
| 5804 | + stage: FailureStage::Run, |
| 5805 | + detail: "Undefined symbols for architecture arm64:\n \"_add_one\"".into(), |
| 5806 | + stages: BTreeMap::new(), |
| 5807 | + }; |
| 5808 | + let artifacts = ExecutionArtifacts { |
| 5809 | + requested: BTreeSet::from([Stage::Run]), |
| 5810 | + armfortas: None, |
| 5811 | + armfortas_failure: Some(failure.clone()), |
| 5812 | + references: Vec::new(), |
| 5813 | + consistency_issues: Vec::new(), |
| 5814 | + }; |
| 5815 | + |
| 5816 | + let err = evaluate_failed_armfortas(&case, &artifacts, &failure).unwrap_err(); |
| 5817 | + assert!(err.contains("armfortas failed in run")); |
| 5818 | + assert!(err.contains("_add_one")); |
| 5819 | + assert!(!err.contains("missing captured run stage")); |
| 5820 | + } |
| 5821 | + |
| 5822 | + #[test] |
| 5823 | + fn partial_stage_expectation_failure_is_preserved_on_capture_failure() { |
| 5824 | + let case = CaseSpec { |
| 5825 | + name: "module_procedure_backend".into(), |
| 5826 | + source: PathBuf::from("graph.f90"), |
| 5827 | + graph_files: Vec::new(), |
| 5828 | + requested: BTreeSet::from([Stage::Asm, Stage::Obj, Stage::Run]), |
| 5829 | + opt_levels: vec![OptLevel::O0], |
| 5830 | + repeat_count: 3, |
| 5831 | + reference_compilers: Vec::new(), |
| 5832 | + consistency_checks: Vec::new(), |
| 5833 | + expectations: vec![Expectation::Contains { |
| 5834 | + target: Target::Stage(Stage::Asm), |
| 5835 | + needle: ".globl _add_one".into(), |
| 5836 | + }], |
| 5837 | + status_rules: Vec::new(), |
| 5838 | + }; |
| 5839 | + let failure = CaptureFailure { |
| 5840 | + input: PathBuf::from("graph.f90"), |
| 5841 | + opt_level: OptLevel::O0, |
| 5842 | + stage: FailureStage::Run, |
| 5843 | + detail: "Undefined symbols for architecture arm64:\n \"_add_one\"".into(), |
| 5844 | + stages: BTreeMap::from([( |
| 5845 | + Stage::Asm, |
| 5846 | + CapturedStage::Text(".globl _main\n".into()), |
| 5847 | + )]), |
| 5848 | + }; |
| 5849 | + let artifacts = ExecutionArtifacts { |
| 5850 | + requested: BTreeSet::from([Stage::Asm, Stage::Obj, Stage::Run]), |
| 5851 | + armfortas: None, |
| 5852 | + armfortas_failure: Some(failure.clone()), |
| 5853 | + references: Vec::new(), |
| 5854 | + consistency_issues: Vec::new(), |
| 5855 | + }; |
| 5856 | + |
| 5857 | + let err = evaluate_failed_armfortas(&case, &artifacts, &failure).unwrap_err(); |
| 5858 | + assert!(err.contains("expected asm to contain")); |
| 5859 | + assert!(!err.contains("armfortas failed in run")); |
| 5860 | + } |
| 5724 | 5861 | } |