@@ -26,6 +26,7 @@ struct SuiteSpec { |
| 26 | 26 | struct CaseSpec { |
| 27 | 27 | name: String, |
| 28 | 28 | source: PathBuf, |
| 29 | + graph_files: Vec<PathBuf>, |
| 29 | 30 | requested: BTreeSet<Stage>, |
| 30 | 31 | opt_levels: Vec<OptLevel>, |
| 31 | 32 | repeat_count: usize, |
@@ -35,6 +36,31 @@ struct CaseSpec { |
| 35 | 36 | status_rules: Vec<StatusRule>, |
| 36 | 37 | } |
| 37 | 38 | |
| 39 | +impl CaseSpec { |
| 40 | + fn is_graph(&self) -> bool { |
| 41 | + !self.graph_files.is_empty() |
| 42 | + } |
| 43 | + |
| 44 | + fn source_label(&self) -> String { |
| 45 | + if self.is_graph() { |
| 46 | + format!( |
| 47 | + "graph entry {} ({} files)", |
| 48 | + self.source.display(), |
| 49 | + self.graph_files.len() |
| 50 | + ) |
| 51 | + } else { |
| 52 | + self.source.display().to_string() |
| 53 | + } |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +#[derive(Debug, Clone)] |
| 58 | +struct PreparedInput { |
| 59 | + compiler_source: PathBuf, |
| 60 | + generated_source: Option<PathBuf>, |
| 61 | + temp_root: Option<PathBuf>, |
| 62 | +} |
| 63 | + |
| 38 | 64 | #[derive(Debug, Clone)] |
| 39 | 65 | struct StatusRule { |
| 40 | 66 | kind: StatusKind, |
@@ -520,9 +546,10 @@ fn parse_cli(args: &[String]) -> Result<CommandKind, String> { |
| 520 | 546 | "--include-future" => config.include_future = true, |
| 521 | 547 | "--all" => config.all_stages = true, |
| 522 | 548 | "--armfortas-bin" => { |
| 523 | | - let value = queue.pop_front().ok_or("--armfortas-bin requires a value")?; |
| 524 | | - config.tools.armfortas = |
| 525 | | - ArmfortasCliAdapter::External(value.clone()); |
| 549 | + let value = queue |
| 550 | + .pop_front() |
| 551 | + .ok_or("--armfortas-bin requires a value")?; |
| 552 | + config.tools.armfortas = ArmfortasCliAdapter::External(value.clone()); |
| 526 | 553 | } |
| 527 | 554 | "--gfortran-bin" => { |
| 528 | 555 | let value = queue.pop_front().ok_or("--gfortran-bin requires a value")?; |
@@ -666,12 +693,13 @@ fn parse_suite_file(path: &Path) -> Result<SuiteSpec, String> { |
| 666 | 693 | })?; |
| 667 | 694 | |
| 668 | 695 | if let Some(rest) = line.strip_prefix("source ") { |
| 669 | | - let relative = parse_quoted(rest, path, line_no)?; |
| 670 | | - let source = path |
| 671 | | - .parent() |
| 672 | | - .unwrap_or_else(|| Path::new(".")) |
| 673 | | - .join(relative); |
| 674 | | - builder.source = Some(source); |
| 696 | + builder.source = Some(resolve_suite_relative_path(rest, path, line_no)?); |
| 697 | + } else if let Some(rest) = line.strip_prefix("entry ") { |
| 698 | + builder.graph_entry = Some(resolve_suite_relative_path(rest, path, line_no)?); |
| 699 | + } else if let Some(rest) = line.strip_prefix("file ") { |
| 700 | + builder |
| 701 | + .graph_files |
| 702 | + .push(resolve_suite_relative_path(rest, path, line_no)?); |
| 675 | 703 | } else if let Some(rest) = line.strip_prefix("armfortas =>") { |
| 676 | 704 | builder.requested = parse_stage_list(rest, path, line_no)?; |
| 677 | 705 | } else if let Some(rest) = line.strip_prefix("repeat =>") { |
@@ -728,6 +756,8 @@ fn parse_suite_file(path: &Path) -> Result<SuiteSpec, String> { |
| 728 | 756 | struct CaseBuilder { |
| 729 | 757 | name: String, |
| 730 | 758 | source: Option<PathBuf>, |
| 759 | + graph_entry: Option<PathBuf>, |
| 760 | + graph_files: Vec<PathBuf>, |
| 731 | 761 | requested: BTreeSet<Stage>, |
| 732 | 762 | opt_levels: Vec<OptLevel>, |
| 733 | 763 | repeat_count: usize, |
@@ -742,6 +772,8 @@ impl CaseBuilder { |
| 742 | 772 | Self { |
| 743 | 773 | name, |
| 744 | 774 | source: None, |
| 775 | + graph_entry: None, |
| 776 | + graph_files: Vec::new(), |
| 745 | 777 | requested: BTreeSet::new(), |
| 746 | 778 | opt_levels: Vec::new(), |
| 747 | 779 | repeat_count: 2, |
@@ -753,13 +785,49 @@ impl CaseBuilder { |
| 753 | 785 | } |
| 754 | 786 | |
| 755 | 787 | fn build(self, suite_path: &Path) -> Result<CaseSpec, String> { |
| 756 | | - let source = self.source.ok_or_else(|| { |
| 757 | | - format!( |
| 758 | | - "{}: case '{}' is missing a source path", |
| 788 | + if self.source.is_some() && (self.graph_entry.is_some() || !self.graph_files.is_empty()) { |
| 789 | + return Err(format!( |
| 790 | + "{}: case '{}' mixes source with graph entry/file declarations", |
| 759 | 791 | suite_path.display(), |
| 760 | 792 | self.name |
| 761 | | - ) |
| 762 | | - })?; |
| 793 | + )); |
| 794 | + } |
| 795 | + |
| 796 | + if self.graph_entry.is_some() && self.graph_files.is_empty() { |
| 797 | + return Err(format!( |
| 798 | + "{}: case '{}' declares an entry without any file members", |
| 799 | + suite_path.display(), |
| 800 | + self.name |
| 801 | + )); |
| 802 | + } |
| 803 | + |
| 804 | + if self.graph_entry.is_none() && !self.graph_files.is_empty() { |
| 805 | + return Err(format!( |
| 806 | + "{}: case '{}' declares file members without an entry", |
| 807 | + suite_path.display(), |
| 808 | + self.name |
| 809 | + )); |
| 810 | + } |
| 811 | + |
| 812 | + let (source, graph_files) = if let Some(source) = self.source { |
| 813 | + (source, Vec::new()) |
| 814 | + } else if let Some(entry) = self.graph_entry { |
| 815 | + if !self.graph_files.iter().any(|file| file == &entry) { |
| 816 | + return Err(format!( |
| 817 | + "{}: case '{}' entry '{}' is not listed in file declarations", |
| 818 | + suite_path.display(), |
| 819 | + self.name, |
| 820 | + entry.display() |
| 821 | + )); |
| 822 | + } |
| 823 | + (entry, self.graph_files) |
| 824 | + } else { |
| 825 | + return Err(format!( |
| 826 | + "{}: case '{}' is missing a source path or graph entry", |
| 827 | + suite_path.display(), |
| 828 | + self.name |
| 829 | + )); |
| 830 | + }; |
| 763 | 831 | |
| 764 | 832 | let mut requested = self.requested; |
| 765 | 833 | if requested.is_empty() { |
@@ -775,6 +843,7 @@ impl CaseBuilder { |
| 775 | 843 | Ok(CaseSpec { |
| 776 | 844 | name: self.name, |
| 777 | 845 | source, |
| 846 | + graph_files, |
| 778 | 847 | requested, |
| 779 | 848 | opt_levels, |
| 780 | 849 | repeat_count: self.repeat_count, |
@@ -786,6 +855,14 @@ impl CaseBuilder { |
| 786 | 855 | } |
| 787 | 856 | } |
| 788 | 857 | |
| 858 | +fn resolve_suite_relative_path(rest: &str, path: &Path, line_no: usize) -> Result<PathBuf, String> { |
| 859 | + let relative = parse_quoted(rest, path, line_no)?; |
| 860 | + Ok(path |
| 861 | + .parent() |
| 862 | + .unwrap_or_else(|| Path::new(".")) |
| 863 | + .join(relative)) |
| 864 | +} |
| 865 | + |
| 789 | 866 | fn parse_stage_list(rest: &str, path: &Path, line_no: usize) -> Result<BTreeSet<Stage>, String> { |
| 790 | 867 | let mut stages = BTreeSet::new(); |
| 791 | 868 | for raw in rest.split(',') { |
@@ -1251,6 +1328,8 @@ fn execute_case_cell( |
| 1251 | 1328 | ensure_consistency_stage(*check, &mut requested); |
| 1252 | 1329 | } |
| 1253 | 1330 | |
| 1331 | + let prepared = prepare_case_input(case, suite, opt_level)?; |
| 1332 | + |
| 1254 | 1333 | if config.verbose { |
| 1255 | 1334 | let stage_list = requested |
| 1256 | 1335 | .iter() |
@@ -1266,7 +1345,13 @@ fn execute_case_cell( |
| 1266 | 1345 | .collect::<Vec<_>>() |
| 1267 | 1346 | .join(", ") |
| 1268 | 1347 | }; |
| 1269 | | - println!(" source: {}", case.source.display()); |
| 1348 | + println!(" source: {}", case.source_label()); |
| 1349 | + if case.is_graph() { |
| 1350 | + for file in &case.graph_files { |
| 1351 | + println!(" file: {}", file.display()); |
| 1352 | + } |
| 1353 | + println!(" compiled_as: {}", prepared.compiler_source.display()); |
| 1354 | + } |
| 1270 | 1355 | println!(" opt: {}", opt_level.as_str()); |
| 1271 | 1356 | println!(" stages: {}", stage_list); |
| 1272 | 1357 | println!(" refs: {}", refs); |
@@ -1276,12 +1361,12 @@ fn execute_case_cell( |
| 1276 | 1361 | } |
| 1277 | 1362 | |
| 1278 | 1363 | let request = CaptureRequest { |
| 1279 | | - input: case.source.clone(), |
| 1364 | + input: prepared.compiler_source.clone(), |
| 1280 | 1365 | requested: requested.clone(), |
| 1281 | 1366 | opt_level, |
| 1282 | 1367 | }; |
| 1283 | 1368 | |
| 1284 | | - let references = run_reference_compilers(case, opt_level, &config.tools); |
| 1369 | + let references = run_reference_compilers(&prepared, case, opt_level, &config.tools); |
| 1285 | 1370 | let mut artifacts = ExecutionArtifacts { |
| 1286 | 1371 | requested, |
| 1287 | 1372 | armfortas: None, |
@@ -1309,7 +1394,7 @@ fn execute_case_cell( |
| 1309 | 1394 | } |
| 1310 | 1395 | if execution.is_ok() && !case.consistency_checks.is_empty() { |
| 1311 | 1396 | artifacts.consistency_issues = |
| 1312 | | - run_consistency_checks(case, opt_level, result, &config.tools); |
| 1397 | + run_consistency_checks(case, &prepared, opt_level, result, &config.tools); |
| 1313 | 1398 | if !artifacts.consistency_issues.is_empty() { |
| 1314 | 1399 | execution = Err(format_consistency_issues(&artifacts.consistency_issues)); |
| 1315 | 1400 | } |
@@ -1404,7 +1489,7 @@ fn execute_case_cell( |
| 1404 | 1489 | || (matches!(outcome.kind, OutcomeKind::Xfail) && !artifacts.consistency_issues.is_empty()); |
| 1405 | 1490 | |
| 1406 | 1491 | if should_bundle { |
| 1407 | | - match write_failure_bundle(suite, case, &outcome, &artifacts) { |
| 1492 | + match write_failure_bundle(suite, case, &prepared, &outcome, &artifacts) { |
| 1408 | 1493 | Ok(bundle) => outcome.bundle = Some(bundle), |
| 1409 | 1494 | Err(err) => { |
| 1410 | 1495 | if outcome.detail.is_empty() { |
@@ -1419,11 +1504,85 @@ fn execute_case_cell( |
| 1419 | 1504 | } |
| 1420 | 1505 | } |
| 1421 | 1506 | |
| 1507 | + cleanup_prepared_input(&prepared); |
| 1422 | 1508 | cleanup_consistency_issues(&artifacts.consistency_issues); |
| 1423 | 1509 | |
| 1424 | 1510 | Ok(outcome) |
| 1425 | 1511 | } |
| 1426 | 1512 | |
| 1513 | +fn prepare_case_input( |
| 1514 | + case: &CaseSpec, |
| 1515 | + suite: &SuiteSpec, |
| 1516 | + opt_level: OptLevel, |
| 1517 | +) -> Result<PreparedInput, String> { |
| 1518 | + if case.graph_files.is_empty() { |
| 1519 | + return Ok(PreparedInput { |
| 1520 | + compiler_source: case.source.clone(), |
| 1521 | + generated_source: None, |
| 1522 | + temp_root: None, |
| 1523 | + }); |
| 1524 | + } |
| 1525 | + |
| 1526 | + let temp_root = default_report_root().join(".tmp").join(format!( |
| 1527 | + "graph_{}_{}_{}", |
| 1528 | + sanitize_component(&suite.name), |
| 1529 | + sanitize_component(&case.name), |
| 1530 | + next_report_suffix(opt_level) |
| 1531 | + )); |
| 1532 | + fs::create_dir_all(&temp_root).map_err(|e| { |
| 1533 | + format!( |
| 1534 | + "cannot create graph temp dir '{}': {}", |
| 1535 | + temp_root.display(), |
| 1536 | + e |
| 1537 | + ) |
| 1538 | + })?; |
| 1539 | + |
| 1540 | + let extension = case |
| 1541 | + .source |
| 1542 | + .extension() |
| 1543 | + .and_then(|ext| ext.to_str()) |
| 1544 | + .filter(|ext| !ext.is_empty()) |
| 1545 | + .unwrap_or("f90"); |
| 1546 | + let generated_source = temp_root.join(format!( |
| 1547 | + "{}_graph.{}", |
| 1548 | + sanitize_component(&case.name), |
| 1549 | + extension |
| 1550 | + )); |
| 1551 | + |
| 1552 | + let mut combined = String::new(); |
| 1553 | + for (index, file) in case.graph_files.iter().enumerate() { |
| 1554 | + let text = fs::read_to_string(file) |
| 1555 | + .map_err(|e| format!("cannot read graph file '{}': {}", file.display(), e))?; |
| 1556 | + if index > 0 { |
| 1557 | + combined.push('\n'); |
| 1558 | + } |
| 1559 | + combined.push_str(&text); |
| 1560 | + if !text.ends_with('\n') { |
| 1561 | + combined.push('\n'); |
| 1562 | + } |
| 1563 | + } |
| 1564 | + |
| 1565 | + fs::write(&generated_source, combined).map_err(|e| { |
| 1566 | + format!( |
| 1567 | + "cannot write generated graph input '{}': {}", |
| 1568 | + generated_source.display(), |
| 1569 | + e |
| 1570 | + ) |
| 1571 | + })?; |
| 1572 | + |
| 1573 | + Ok(PreparedInput { |
| 1574 | + compiler_source: generated_source.clone(), |
| 1575 | + generated_source: Some(generated_source), |
| 1576 | + temp_root: Some(temp_root), |
| 1577 | + }) |
| 1578 | +} |
| 1579 | + |
| 1580 | +fn cleanup_prepared_input(prepared: &PreparedInput) { |
| 1581 | + if let Some(temp_root) = &prepared.temp_root { |
| 1582 | + let _ = fs::remove_dir_all(temp_root); |
| 1583 | + } |
| 1584 | +} |
| 1585 | + |
| 1427 | 1586 | fn status_for_opt(case: &CaseSpec, opt_level: OptLevel) -> EffectiveStatus { |
| 1428 | 1587 | let mut status = EffectiveStatus::Normal; |
| 1429 | 1588 | for rule in &case.status_rules { |
@@ -1752,6 +1911,7 @@ fn compose_armfortas_failure_detail(artifacts: &ExecutionArtifacts) -> String { |
| 1752 | 1911 | |
| 1753 | 1912 | fn run_consistency_checks( |
| 1754 | 1913 | case: &CaseSpec, |
| 1914 | + prepared: &PreparedInput, |
| 1755 | 1915 | opt_level: OptLevel, |
| 1756 | 1916 | capture_result: &CaptureResult, |
| 1757 | 1917 | tools: &ToolchainConfig, |
@@ -1760,54 +1920,63 @@ fn run_consistency_checks( |
| 1760 | 1920 | for check in &case.consistency_checks { |
| 1761 | 1921 | let issue = match check { |
| 1762 | 1922 | ConsistencyCheck::CliObjVsSystemAs => { |
| 1763 | | - run_cli_obj_vs_system_as(&case.source, opt_level, tools) |
| 1764 | | - } |
| 1765 | | - ConsistencyCheck::CliAsmReproducible => { |
| 1766 | | - run_cli_asm_reproducible(&case.source, opt_level, case.repeat_count, tools) |
| 1767 | | - } |
| 1768 | | - ConsistencyCheck::CliObjReproducible => { |
| 1769 | | - run_cli_obj_reproducible(&case.source, opt_level, case.repeat_count, tools) |
| 1770 | | - } |
| 1771 | | - ConsistencyCheck::CliRunReproducible => { |
| 1772 | | - run_cli_run_reproducible(&case.source, opt_level, case.repeat_count, tools) |
| 1923 | + run_cli_obj_vs_system_as(&prepared.compiler_source, opt_level, tools) |
| 1773 | 1924 | } |
| 1925 | + ConsistencyCheck::CliAsmReproducible => run_cli_asm_reproducible( |
| 1926 | + &prepared.compiler_source, |
| 1927 | + opt_level, |
| 1928 | + case.repeat_count, |
| 1929 | + tools, |
| 1930 | + ), |
| 1931 | + ConsistencyCheck::CliObjReproducible => run_cli_obj_reproducible( |
| 1932 | + &prepared.compiler_source, |
| 1933 | + opt_level, |
| 1934 | + case.repeat_count, |
| 1935 | + tools, |
| 1936 | + ), |
| 1937 | + ConsistencyCheck::CliRunReproducible => run_cli_run_reproducible( |
| 1938 | + &prepared.compiler_source, |
| 1939 | + opt_level, |
| 1940 | + case.repeat_count, |
| 1941 | + tools, |
| 1942 | + ), |
| 1774 | 1943 | ConsistencyCheck::CaptureAsmVsCliAsm => run_capture_asm_vs_cli_asm( |
| 1775 | | - &case.source, |
| 1944 | + &prepared.compiler_source, |
| 1776 | 1945 | opt_level, |
| 1777 | 1946 | case.repeat_count, |
| 1778 | 1947 | capture_result, |
| 1779 | 1948 | tools, |
| 1780 | 1949 | ), |
| 1781 | 1950 | ConsistencyCheck::CaptureObjVsCliObj => run_capture_obj_vs_cli_obj( |
| 1782 | | - &case.source, |
| 1951 | + &prepared.compiler_source, |
| 1783 | 1952 | opt_level, |
| 1784 | 1953 | case.repeat_count, |
| 1785 | 1954 | capture_result, |
| 1786 | 1955 | tools, |
| 1787 | 1956 | ), |
| 1788 | 1957 | ConsistencyCheck::CaptureRunVsCliRun => run_capture_run_vs_cli_run( |
| 1789 | | - &case.source, |
| 1958 | + &prepared.compiler_source, |
| 1790 | 1959 | opt_level, |
| 1791 | 1960 | case.repeat_count, |
| 1792 | 1961 | capture_result, |
| 1793 | 1962 | tools, |
| 1794 | 1963 | ), |
| 1795 | 1964 | ConsistencyCheck::CaptureAsmReproducible => run_capture_asm_reproducible( |
| 1796 | | - &case.source, |
| 1965 | + &prepared.compiler_source, |
| 1797 | 1966 | opt_level, |
| 1798 | 1967 | case.repeat_count, |
| 1799 | 1968 | capture_result, |
| 1800 | 1969 | tools, |
| 1801 | 1970 | ), |
| 1802 | 1971 | ConsistencyCheck::CaptureObjReproducible => run_capture_obj_reproducible( |
| 1803 | | - &case.source, |
| 1972 | + &prepared.compiler_source, |
| 1804 | 1973 | opt_level, |
| 1805 | 1974 | case.repeat_count, |
| 1806 | 1975 | capture_result, |
| 1807 | 1976 | tools, |
| 1808 | 1977 | ), |
| 1809 | 1978 | ConsistencyCheck::CaptureRunReproducible => run_capture_run_reproducible( |
| 1810 | | - &case.source, |
| 1979 | + &prepared.compiler_source, |
| 1811 | 1980 | opt_level, |
| 1812 | 1981 | case.repeat_count, |
| 1813 | 1982 | capture_result, |
@@ -1870,20 +2039,20 @@ fn run_cli_obj_vs_system_as( |
| 1870 | 2039 | |
| 1871 | 2040 | let asm_command = |
| 1872 | 2041 | match compile_with_driver(source, opt_level, DriverEmitMode::Asm, &asm_path, tools) { |
| 1873 | | - Ok(command) => command, |
| 1874 | | - Err(detail) => { |
| 1875 | | - return Some(ConsistencyIssue { |
| 1876 | | - check: ConsistencyCheck::CliObjVsSystemAs, |
| 1877 | | - summary: "armfortas -S failed during consistency check".into(), |
| 1878 | | - repeat_count: None, |
| 1879 | | - unique_variant_count: None, |
| 1880 | | - varying_components: Vec::new(), |
| 1881 | | - stable_components: Vec::new(), |
| 1882 | | - detail, |
| 1883 | | - temp_root, |
| 1884 | | - }) |
| 1885 | | - } |
| 1886 | | - }; |
| 2042 | + Ok(command) => command, |
| 2043 | + Err(detail) => { |
| 2044 | + return Some(ConsistencyIssue { |
| 2045 | + check: ConsistencyCheck::CliObjVsSystemAs, |
| 2046 | + summary: "armfortas -S failed during consistency check".into(), |
| 2047 | + repeat_count: None, |
| 2048 | + unique_variant_count: None, |
| 2049 | + varying_components: Vec::new(), |
| 2050 | + stable_components: Vec::new(), |
| 2051 | + detail, |
| 2052 | + temp_root, |
| 2053 | + }) |
| 2054 | + } |
| 2055 | + }; |
| 1887 | 2056 | |
| 1888 | 2057 | let as_args = vec![ |
| 1889 | 2058 | "-o".to_string(), |
@@ -1929,20 +2098,20 @@ fn run_cli_obj_vs_system_as( |
| 1929 | 2098 | |
| 1930 | 2099 | let obj_command = |
| 1931 | 2100 | match compile_with_driver(source, opt_level, DriverEmitMode::Obj, &obj_path, tools) { |
| 1932 | | - Ok(command) => command, |
| 1933 | | - Err(detail) => { |
| 1934 | | - return Some(ConsistencyIssue { |
| 1935 | | - check: ConsistencyCheck::CliObjVsSystemAs, |
| 1936 | | - summary: "armfortas -c failed during consistency check".into(), |
| 1937 | | - repeat_count: None, |
| 1938 | | - unique_variant_count: None, |
| 1939 | | - varying_components: Vec::new(), |
| 1940 | | - stable_components: Vec::new(), |
| 1941 | | - detail, |
| 1942 | | - temp_root, |
| 1943 | | - }) |
| 1944 | | - } |
| 1945 | | - }; |
| 2101 | + Ok(command) => command, |
| 2102 | + Err(detail) => { |
| 2103 | + return Some(ConsistencyIssue { |
| 2104 | + check: ConsistencyCheck::CliObjVsSystemAs, |
| 2105 | + summary: "armfortas -c failed during consistency check".into(), |
| 2106 | + repeat_count: None, |
| 2107 | + unique_variant_count: None, |
| 2108 | + varying_components: Vec::new(), |
| 2109 | + stable_components: Vec::new(), |
| 2110 | + detail, |
| 2111 | + temp_root, |
| 2112 | + }) |
| 2113 | + } |
| 2114 | + }; |
| 1946 | 2115 | |
| 1947 | 2116 | let asm_snapshot = match object_snapshot(&asm_obj_path, tools) { |
| 1948 | 2117 | Ok(snapshot) => snapshot, |
@@ -2038,27 +2207,22 @@ fn run_cli_asm_reproducible( |
| 2038 | 2207 | let mut runs = Vec::new(); |
| 2039 | 2208 | for index in 0..repeat_count { |
| 2040 | 2209 | let asm_path = temp_root.join(format!("run_{:02}.s", index)); |
| 2041 | | - let command = match compile_with_driver( |
| 2042 | | - source, |
| 2043 | | - opt_level, |
| 2044 | | - DriverEmitMode::Asm, |
| 2045 | | - &asm_path, |
| 2046 | | - tools, |
| 2047 | | - ) { |
| 2048 | | - Ok(command) => command, |
| 2049 | | - Err(detail) => { |
| 2050 | | - return Some(ConsistencyIssue { |
| 2051 | | - check: ConsistencyCheck::CliAsmReproducible, |
| 2052 | | - summary: "armfortas -S failed during reproducibility check".into(), |
| 2053 | | - repeat_count: None, |
| 2054 | | - unique_variant_count: None, |
| 2055 | | - varying_components: Vec::new(), |
| 2056 | | - stable_components: Vec::new(), |
| 2057 | | - detail, |
| 2058 | | - temp_root, |
| 2059 | | - }) |
| 2060 | | - } |
| 2061 | | - }; |
| 2210 | + let command = |
| 2211 | + match compile_with_driver(source, opt_level, DriverEmitMode::Asm, &asm_path, tools) { |
| 2212 | + Ok(command) => command, |
| 2213 | + Err(detail) => { |
| 2214 | + return Some(ConsistencyIssue { |
| 2215 | + check: ConsistencyCheck::CliAsmReproducible, |
| 2216 | + summary: "armfortas -S failed during reproducibility check".into(), |
| 2217 | + repeat_count: None, |
| 2218 | + unique_variant_count: None, |
| 2219 | + varying_components: Vec::new(), |
| 2220 | + stable_components: Vec::new(), |
| 2221 | + detail, |
| 2222 | + temp_root, |
| 2223 | + }) |
| 2224 | + } |
| 2225 | + }; |
| 2062 | 2226 | let text = match read_text_artifact(&asm_path) { |
| 2063 | 2227 | Ok(text) => text, |
| 2064 | 2228 | Err(detail) => { |
@@ -2135,27 +2299,22 @@ fn run_cli_obj_reproducible( |
| 2135 | 2299 | let mut runs = Vec::new(); |
| 2136 | 2300 | for index in 0..repeat_count { |
| 2137 | 2301 | let obj_path = temp_root.join(format!("run_{:02}.o", index)); |
| 2138 | | - let command = match compile_with_driver( |
| 2139 | | - source, |
| 2140 | | - opt_level, |
| 2141 | | - DriverEmitMode::Obj, |
| 2142 | | - &obj_path, |
| 2143 | | - tools, |
| 2144 | | - ) { |
| 2145 | | - Ok(command) => command, |
| 2146 | | - Err(detail) => { |
| 2147 | | - return Some(ConsistencyIssue { |
| 2148 | | - check: ConsistencyCheck::CliObjReproducible, |
| 2149 | | - summary: "armfortas -c failed during reproducibility check".into(), |
| 2150 | | - repeat_count: None, |
| 2151 | | - unique_variant_count: None, |
| 2152 | | - varying_components: Vec::new(), |
| 2153 | | - stable_components: Vec::new(), |
| 2154 | | - detail, |
| 2155 | | - temp_root, |
| 2156 | | - }) |
| 2157 | | - } |
| 2158 | | - }; |
| 2302 | + let command = |
| 2303 | + match compile_with_driver(source, opt_level, DriverEmitMode::Obj, &obj_path, tools) { |
| 2304 | + Ok(command) => command, |
| 2305 | + Err(detail) => { |
| 2306 | + return Some(ConsistencyIssue { |
| 2307 | + check: ConsistencyCheck::CliObjReproducible, |
| 2308 | + summary: "armfortas -c failed during reproducibility check".into(), |
| 2309 | + repeat_count: None, |
| 2310 | + unique_variant_count: None, |
| 2311 | + varying_components: Vec::new(), |
| 2312 | + stable_components: Vec::new(), |
| 2313 | + detail, |
| 2314 | + temp_root, |
| 2315 | + }) |
| 2316 | + } |
| 2317 | + }; |
| 2159 | 2318 | let snapshot = match object_snapshot(&obj_path, tools) { |
| 2160 | 2319 | Ok(snapshot) => snapshot, |
| 2161 | 2320 | Err(detail) => { |
@@ -2259,22 +2418,21 @@ fn run_cli_run_reproducible( |
| 2259 | 2418 | &binary_path, |
| 2260 | 2419 | tools, |
| 2261 | 2420 | ) { |
| 2262 | | - Ok(command) => command, |
| 2263 | | - Err(detail) => { |
| 2264 | | - return Some(ConsistencyIssue { |
| 2265 | | - check: ConsistencyCheck::CliRunReproducible, |
| 2266 | | - summary: |
| 2267 | | - "armfortas binary build failed during runtime reproducibility check" |
| 2268 | | - .into(), |
| 2269 | | - repeat_count: None, |
| 2270 | | - unique_variant_count: None, |
| 2271 | | - varying_components: Vec::new(), |
| 2272 | | - stable_components: Vec::new(), |
| 2273 | | - detail, |
| 2274 | | - temp_root, |
| 2275 | | - }) |
| 2276 | | - } |
| 2277 | | - }; |
| 2421 | + Ok(command) => command, |
| 2422 | + Err(detail) => { |
| 2423 | + return Some(ConsistencyIssue { |
| 2424 | + check: ConsistencyCheck::CliRunReproducible, |
| 2425 | + summary: "armfortas binary build failed during runtime reproducibility check" |
| 2426 | + .into(), |
| 2427 | + repeat_count: None, |
| 2428 | + unique_variant_count: None, |
| 2429 | + varying_components: Vec::new(), |
| 2430 | + stable_components: Vec::new(), |
| 2431 | + detail, |
| 2432 | + temp_root, |
| 2433 | + }) |
| 2434 | + } |
| 2435 | + }; |
| 2278 | 2436 | let run_command = render_binary_run_command(&binary_path); |
| 2279 | 2437 | let run = match run_binary_capture(&binary_path, &temp_root, &run_command) { |
| 2280 | 2438 | Ok(run) => run, |
@@ -2415,27 +2573,23 @@ fn run_capture_asm_vs_cli_asm( |
| 2415 | 2573 | let mut mismatch_indices = Vec::new(); |
| 2416 | 2574 | for index in 0..repeat_count { |
| 2417 | 2575 | let asm_path = temp_root.join(format!("cli_run_{:02}.s", index)); |
| 2418 | | - let command = match compile_with_driver( |
| 2419 | | - source, |
| 2420 | | - opt_level, |
| 2421 | | - DriverEmitMode::Asm, |
| 2422 | | - &asm_path, |
| 2423 | | - tools, |
| 2424 | | - ) { |
| 2425 | | - Ok(command) => command, |
| 2426 | | - Err(detail) => { |
| 2427 | | - return Some(ConsistencyIssue { |
| 2428 | | - check: ConsistencyCheck::CaptureAsmVsCliAsm, |
| 2429 | | - summary: "armfortas -S failed during capture-vs-cli consistency check".into(), |
| 2430 | | - repeat_count: None, |
| 2431 | | - unique_variant_count: None, |
| 2432 | | - varying_components: Vec::new(), |
| 2433 | | - stable_components: Vec::new(), |
| 2434 | | - detail, |
| 2435 | | - temp_root, |
| 2436 | | - }) |
| 2437 | | - } |
| 2438 | | - }; |
| 2576 | + let command = |
| 2577 | + match compile_with_driver(source, opt_level, DriverEmitMode::Asm, &asm_path, tools) { |
| 2578 | + Ok(command) => command, |
| 2579 | + Err(detail) => { |
| 2580 | + return Some(ConsistencyIssue { |
| 2581 | + check: ConsistencyCheck::CaptureAsmVsCliAsm, |
| 2582 | + summary: "armfortas -S failed during capture-vs-cli consistency check" |
| 2583 | + .into(), |
| 2584 | + repeat_count: None, |
| 2585 | + unique_variant_count: None, |
| 2586 | + varying_components: Vec::new(), |
| 2587 | + stable_components: Vec::new(), |
| 2588 | + detail, |
| 2589 | + temp_root, |
| 2590 | + }) |
| 2591 | + } |
| 2592 | + }; |
| 2439 | 2593 | let text = match read_text_artifact(&asm_path) { |
| 2440 | 2594 | Ok(text) => text, |
| 2441 | 2595 | Err(detail) => { |
@@ -2576,27 +2730,23 @@ fn run_capture_obj_vs_cli_obj( |
| 2576 | 2730 | let mut mismatch_indices = Vec::new(); |
| 2577 | 2731 | for index in 0..repeat_count { |
| 2578 | 2732 | let obj_path = temp_root.join(format!("cli_run_{:02}.o", index)); |
| 2579 | | - let command = match compile_with_driver( |
| 2580 | | - source, |
| 2581 | | - opt_level, |
| 2582 | | - DriverEmitMode::Obj, |
| 2583 | | - &obj_path, |
| 2584 | | - tools, |
| 2585 | | - ) { |
| 2586 | | - Ok(command) => command, |
| 2587 | | - Err(detail) => { |
| 2588 | | - return Some(ConsistencyIssue { |
| 2589 | | - check: ConsistencyCheck::CaptureObjVsCliObj, |
| 2590 | | - summary: "armfortas -c failed during capture-vs-cli consistency check".into(), |
| 2591 | | - repeat_count: None, |
| 2592 | | - unique_variant_count: None, |
| 2593 | | - varying_components: Vec::new(), |
| 2594 | | - stable_components: Vec::new(), |
| 2595 | | - detail, |
| 2596 | | - temp_root, |
| 2597 | | - }) |
| 2598 | | - } |
| 2599 | | - }; |
| 2733 | + let command = |
| 2734 | + match compile_with_driver(source, opt_level, DriverEmitMode::Obj, &obj_path, tools) { |
| 2735 | + Ok(command) => command, |
| 2736 | + Err(detail) => { |
| 2737 | + return Some(ConsistencyIssue { |
| 2738 | + check: ConsistencyCheck::CaptureObjVsCliObj, |
| 2739 | + summary: "armfortas -c failed during capture-vs-cli consistency check" |
| 2740 | + .into(), |
| 2741 | + repeat_count: None, |
| 2742 | + unique_variant_count: None, |
| 2743 | + varying_components: Vec::new(), |
| 2744 | + stable_components: Vec::new(), |
| 2745 | + detail, |
| 2746 | + temp_root, |
| 2747 | + }) |
| 2748 | + } |
| 2749 | + }; |
| 2600 | 2750 | let snapshot = match object_snapshot(&obj_path, tools) { |
| 2601 | 2751 | Ok(snapshot) => snapshot, |
| 2602 | 2752 | Err(detail) => { |
@@ -2765,22 +2915,21 @@ fn run_capture_run_vs_cli_run( |
| 2765 | 2915 | &binary_path, |
| 2766 | 2916 | tools, |
| 2767 | 2917 | ) { |
| 2768 | | - Ok(command) => command, |
| 2769 | | - Err(detail) => { |
| 2770 | | - return Some(ConsistencyIssue { |
| 2771 | | - check: ConsistencyCheck::CaptureRunVsCliRun, |
| 2772 | | - summary: |
| 2773 | | - "armfortas binary build failed during capture-vs-cli runtime check" |
| 2774 | | - .into(), |
| 2775 | | - repeat_count: None, |
| 2776 | | - unique_variant_count: None, |
| 2777 | | - varying_components: Vec::new(), |
| 2778 | | - stable_components: Vec::new(), |
| 2779 | | - detail, |
| 2780 | | - temp_root, |
| 2781 | | - }) |
| 2782 | | - } |
| 2783 | | - }; |
| 2918 | + Ok(command) => command, |
| 2919 | + Err(detail) => { |
| 2920 | + return Some(ConsistencyIssue { |
| 2921 | + check: ConsistencyCheck::CaptureRunVsCliRun, |
| 2922 | + summary: "armfortas binary build failed during capture-vs-cli runtime check" |
| 2923 | + .into(), |
| 2924 | + repeat_count: None, |
| 2925 | + unique_variant_count: None, |
| 2926 | + varying_components: Vec::new(), |
| 2927 | + stable_components: Vec::new(), |
| 2928 | + detail, |
| 2929 | + temp_root, |
| 2930 | + }) |
| 2931 | + } |
| 2932 | + }; |
| 2784 | 2933 | let run_command = render_binary_run_command(&binary_path); |
| 2785 | 2934 | let run = match run_binary_capture(&binary_path, &temp_root, &run_command) { |
| 2786 | 2935 | Ok(run) => run, |
@@ -3322,6 +3471,7 @@ fn run_capture_run_reproducible( |
| 3322 | 3471 | } |
| 3323 | 3472 | |
| 3324 | 3473 | fn run_reference_compilers( |
| 3474 | + prepared: &PreparedInput, |
| 3325 | 3475 | case: &CaseSpec, |
| 3326 | 3476 | opt_level: OptLevel, |
| 3327 | 3477 | tools: &ToolchainConfig, |
@@ -3329,7 +3479,7 @@ fn run_reference_compilers( |
| 3329 | 3479 | case.reference_compilers |
| 3330 | 3480 | .iter() |
| 3331 | 3481 | .copied() |
| 3332 | | - .map(|compiler| run_reference_case(&case.source, opt_level, compiler, tools)) |
| 3482 | + .map(|compiler| run_reference_case(&prepared.compiler_source, opt_level, compiler, tools)) |
| 3333 | 3483 | .collect() |
| 3334 | 3484 | } |
| 3335 | 3485 | |
@@ -3715,12 +3865,22 @@ struct ObjectRun { |
| 3715 | 3865 | } |
| 3716 | 3866 | |
| 3717 | 3867 | fn object_snapshot(path: &Path, tools: &ToolchainConfig) -> Result<ObjectSnapshot, String> { |
| 3718 | | - let text = normalize_tool_output(&tool_output(tools.otool_bin(), &["-t", path.to_str().unwrap()])?); |
| 3719 | | - let load_commands = |
| 3720 | | - normalize_tool_output(&tool_output(tools.otool_bin(), &["-l", path.to_str().unwrap()])?); |
| 3721 | | - let relocations = |
| 3722 | | - normalize_tool_output(&tool_output(tools.otool_bin(), &["-rv", path.to_str().unwrap()])?); |
| 3723 | | - let symbols = normalize_tool_output(&tool_output(tools.nm_bin(), &["-m", path.to_str().unwrap()])?); |
| 3868 | + let text = normalize_tool_output(&tool_output( |
| 3869 | + tools.otool_bin(), |
| 3870 | + &["-t", path.to_str().unwrap()], |
| 3871 | + )?); |
| 3872 | + let load_commands = normalize_tool_output(&tool_output( |
| 3873 | + tools.otool_bin(), |
| 3874 | + &["-l", path.to_str().unwrap()], |
| 3875 | + )?); |
| 3876 | + let relocations = normalize_tool_output(&tool_output( |
| 3877 | + tools.otool_bin(), |
| 3878 | + &["-rv", path.to_str().unwrap()], |
| 3879 | + )?); |
| 3880 | + let symbols = normalize_tool_output(&tool_output( |
| 3881 | + tools.nm_bin(), |
| 3882 | + &["-m", path.to_str().unwrap()], |
| 3883 | + )?); |
| 3724 | 3884 | |
| 3725 | 3885 | Ok(ObjectSnapshot { |
| 3726 | 3886 | text, |
@@ -4188,6 +4348,7 @@ fn render_summary(summary: &Summary) -> String { |
| 4188 | 4348 | fn write_failure_bundle( |
| 4189 | 4349 | suite: &SuiteSpec, |
| 4190 | 4350 | case: &CaseSpec, |
| 4351 | + prepared: &PreparedInput, |
| 4191 | 4352 | outcome: &Outcome, |
| 4192 | 4353 | artifacts: &ExecutionArtifacts, |
| 4193 | 4354 | ) -> Result<PathBuf, String> { |
@@ -4233,7 +4394,7 @@ fn write_failure_bundle( |
| 4233 | 4394 | case.name, |
| 4234 | 4395 | outcome.kind, |
| 4235 | 4396 | outcome.opt_level.as_str(), |
| 4236 | | - case.source.display(), |
| 4397 | + case.source_label(), |
| 4237 | 4398 | stage_list, |
| 4238 | 4399 | case.repeat_count, |
| 4239 | 4400 | refs, |
@@ -4244,10 +4405,7 @@ fn write_failure_bundle( |
| 4244 | 4405 | fs::write(bundle_root.join("detail.txt"), &outcome.detail) |
| 4245 | 4406 | .map_err(|e| format!("cannot write bundle detail: {}", e))?; |
| 4246 | 4407 | |
| 4247 | | - let source_text = fs::read_to_string(&case.source) |
| 4248 | | - .map_err(|e| format!("cannot read case source '{}': {}", case.source.display(), e))?; |
| 4249 | | - fs::write(bundle_root.join("source.f90"), source_text) |
| 4250 | | - .map_err(|e| format!("cannot write bundle source copy: {}", e))?; |
| 4408 | + write_case_sources_bundle(&bundle_root, case, prepared)?; |
| 4251 | 4409 | |
| 4252 | 4410 | let armfortas_root = bundle_root.join("armfortas"); |
| 4253 | 4411 | fs::create_dir_all(&armfortas_root) |
@@ -4280,6 +4438,52 @@ fn write_failure_bundle( |
| 4280 | 4438 | Ok(bundle_root) |
| 4281 | 4439 | } |
| 4282 | 4440 | |
| 4441 | +fn write_case_sources_bundle( |
| 4442 | + bundle_root: &Path, |
| 4443 | + case: &CaseSpec, |
| 4444 | + prepared: &PreparedInput, |
| 4445 | +) -> Result<(), String> { |
| 4446 | + if case.graph_files.is_empty() { |
| 4447 | + let source_text = fs::read_to_string(&case.source) |
| 4448 | + .map_err(|e| format!("cannot read case source '{}': {}", case.source.display(), e))?; |
| 4449 | + fs::write(bundle_root.join("source.f90"), source_text) |
| 4450 | + .map_err(|e| format!("cannot write bundle source copy: {}", e))?; |
| 4451 | + return Ok(()); |
| 4452 | + } |
| 4453 | + |
| 4454 | + let generated_source = prepared.generated_source.as_ref().ok_or_else(|| { |
| 4455 | + format!( |
| 4456 | + "graph case '{}' was missing a generated compiler source", |
| 4457 | + case.name |
| 4458 | + ) |
| 4459 | + })?; |
| 4460 | + let generated_text = fs::read_to_string(generated_source).map_err(|e| { |
| 4461 | + format!( |
| 4462 | + "cannot read generated graph source '{}': {}", |
| 4463 | + generated_source.display(), |
| 4464 | + e |
| 4465 | + ) |
| 4466 | + })?; |
| 4467 | + fs::write(bundle_root.join("source.f90"), generated_text) |
| 4468 | + .map_err(|e| format!("cannot write generated bundle source copy: {}", e))?; |
| 4469 | + |
| 4470 | + let sources_root = bundle_root.join("sources"); |
| 4471 | + fs::create_dir_all(&sources_root) |
| 4472 | + .map_err(|e| format!("cannot create bundle sources dir: {}", e))?; |
| 4473 | + for (index, file) in case.graph_files.iter().enumerate() { |
| 4474 | + let text = fs::read_to_string(file) |
| 4475 | + .map_err(|e| format!("cannot read graph source '{}': {}", file.display(), e))?; |
| 4476 | + let file_name = file |
| 4477 | + .file_name() |
| 4478 | + .and_then(|name| name.to_str()) |
| 4479 | + .unwrap_or("source.f90"); |
| 4480 | + let target = sources_root.join(format!("{:02}_{}", index, file_name)); |
| 4481 | + fs::write(target, text).map_err(|e| format!("cannot write bundle graph source: {}", e))?; |
| 4482 | + } |
| 4483 | + |
| 4484 | + Ok(()) |
| 4485 | +} |
| 4486 | + |
| 4283 | 4487 | fn write_capture_result(root: &Path, result: &CaptureResult) -> Result<(), String> { |
| 4284 | 4488 | for (stage, captured) in &result.stages { |
| 4285 | 4489 | match captured { |
@@ -4656,8 +4860,8 @@ fn match_checks(checks: &[Check], output: &str, case_name: &str) -> Result<(), S |
| 4656 | 4860 | mod tests { |
| 4657 | 4861 | use super::*; |
| 4658 | 4862 | use crate::compiler::test_support::{ |
| 4659 | | - verify_module, BlockParam, FloatWidth, Function, Inst, InstKind, IntWidth, IrType, |
| 4660 | | - Module, Position, Span, Terminator, ValueId, |
| 4863 | + verify_module, BlockParam, FloatWidth, Function, Inst, InstKind, IntWidth, IrType, Module, |
| 4864 | + Position, Span, Terminator, ValueId, |
| 4661 | 4865 | }; |
| 4662 | 4866 | |
| 4663 | 4867 | fn dummy_span() -> Span { |
@@ -4781,6 +4985,43 @@ end |
| 4781 | 4985 | let _ = fs::remove_file(&root); |
| 4782 | 4986 | } |
| 4783 | 4987 | |
| 4988 | + #[test] |
| 4989 | + fn parses_graph_case() { |
| 4990 | + let root = std::env::temp_dir().join("afs_tests_graph_spec"); |
| 4991 | + let _ = fs::remove_dir_all(&root); |
| 4992 | + fs::create_dir_all(&root).unwrap(); |
| 4993 | + fs::write( |
| 4994 | + root.join("math_values.f90"), |
| 4995 | + "module math_values\nend module\n", |
| 4996 | + ) |
| 4997 | + .unwrap(); |
| 4998 | + fs::write(root.join("main.f90"), "program main\nend program\n").unwrap(); |
| 4999 | + fs::write( |
| 5000 | + root.join("graph.afs"), |
| 5001 | + r#"suite "modules/graph" |
| 5002 | + |
| 5003 | +case "basic_use" |
| 5004 | +entry "main.f90" |
| 5005 | +file "math_values.f90" |
| 5006 | +file "main.f90" |
| 5007 | +armfortas => run |
| 5008 | +expect run.exit_code equals 0 |
| 5009 | +end |
| 5010 | +"#, |
| 5011 | + ) |
| 5012 | + .unwrap(); |
| 5013 | + |
| 5014 | + let suite = parse_suite_file(&root.join("graph.afs")).unwrap(); |
| 5015 | + let case = &suite.cases[0]; |
| 5016 | + assert_eq!(case.source, root.join("main.f90")); |
| 5017 | + assert_eq!( |
| 5018 | + case.graph_files, |
| 5019 | + vec![root.join("math_values.f90"), root.join("main.f90")] |
| 5020 | + ); |
| 5021 | + |
| 5022 | + let _ = fs::remove_dir_all(&root); |
| 5023 | + } |
| 5024 | + |
| 4784 | 5025 | #[test] |
| 4785 | 5026 | fn parse_cli_collects_tool_overrides() { |
| 4786 | 5027 | let args = vec![ |
@@ -4804,7 +5045,10 @@ end |
| 4804 | 5045 | let command = parse_cli(&args).unwrap(); |
| 4805 | 5046 | let config = match command { |
| 4806 | 5047 | CommandKind::Run(config) => config, |
| 4807 | | - other => panic!("expected run command, got {:?}", std::mem::discriminant(&other)), |
| 5048 | + other => panic!( |
| 5049 | + "expected run command, got {:?}", |
| 5050 | + std::mem::discriminant(&other) |
| 5051 | + ), |
| 4808 | 5052 | }; |
| 4809 | 5053 | |
| 4810 | 5054 | assert_eq!(config.suite_filter.as_deref(), Some("consistency/runtime")); |
@@ -4899,6 +5143,7 @@ end |
| 4899 | 5143 | let case = CaseSpec { |
| 4900 | 5144 | name: "no_reserved_register".into(), |
| 4901 | 5145 | source: PathBuf::from("demo.f90"), |
| 5146 | + graph_files: Vec::new(), |
| 4902 | 5147 | requested: BTreeSet::from([Stage::Asm]), |
| 4903 | 5148 | opt_levels: vec![OptLevel::O0], |
| 4904 | 5149 | repeat_count: 2, |
@@ -4945,6 +5190,7 @@ end |
| 4945 | 5190 | let case = CaseSpec { |
| 4946 | 5191 | name: "hello_bundle".into(), |
| 4947 | 5192 | source: source.clone(), |
| 5193 | + graph_files: Vec::new(), |
| 4948 | 5194 | requested: BTreeSet::from([Stage::Ir, Stage::Run]), |
| 4949 | 5195 | opt_levels: vec![OptLevel::O0], |
| 4950 | 5196 | repeat_count: 3, |
@@ -5034,8 +5280,13 @@ end |
| 5034 | 5280 | bundle: None, |
| 5035 | 5281 | consistency_observations: Vec::new(), |
| 5036 | 5282 | }; |
| 5283 | + let prepared = PreparedInput { |
| 5284 | + compiler_source: source.clone(), |
| 5285 | + generated_source: None, |
| 5286 | + temp_root: None, |
| 5287 | + }; |
| 5037 | 5288 | |
| 5038 | | - let bundle = write_failure_bundle(&suite, &case, &outcome, &artifacts).unwrap(); |
| 5289 | + let bundle = write_failure_bundle(&suite, &case, &prepared, &outcome, &artifacts).unwrap(); |
| 5039 | 5290 | assert!(bundle.join("metadata.txt").exists()); |
| 5040 | 5291 | assert!(bundle.join("detail.txt").exists()); |
| 5041 | 5292 | assert!(bundle.join("source.f90").exists()); |
@@ -5089,6 +5340,114 @@ end |
| 5089 | 5340 | let _ = fs::remove_file(source); |
| 5090 | 5341 | } |
| 5091 | 5342 | |
| 5343 | + #[test] |
| 5344 | + fn materializes_graph_input_in_declared_file_order() { |
| 5345 | + let root = std::env::temp_dir().join("afs_tests_graph_materialize"); |
| 5346 | + let _ = fs::remove_dir_all(&root); |
| 5347 | + fs::create_dir_all(&root).unwrap(); |
| 5348 | + let module = root.join("math_values.f90"); |
| 5349 | + let main = root.join("main.f90"); |
| 5350 | + fs::write(&module, "module math_values\ncontains\nend module\n").unwrap(); |
| 5351 | + fs::write(&main, "program main\nuse math_values\nend program\n").unwrap(); |
| 5352 | + |
| 5353 | + let suite = SuiteSpec { |
| 5354 | + name: "modules/graph".into(), |
| 5355 | + path: root.join("graph.afs"), |
| 5356 | + cases: Vec::new(), |
| 5357 | + }; |
| 5358 | + let case = CaseSpec { |
| 5359 | + name: "basic_use".into(), |
| 5360 | + source: main.clone(), |
| 5361 | + graph_files: vec![module.clone(), main.clone()], |
| 5362 | + requested: BTreeSet::from([Stage::Run]), |
| 5363 | + opt_levels: vec![OptLevel::O0], |
| 5364 | + repeat_count: 2, |
| 5365 | + reference_compilers: Vec::new(), |
| 5366 | + consistency_checks: Vec::new(), |
| 5367 | + expectations: Vec::new(), |
| 5368 | + status_rules: Vec::new(), |
| 5369 | + }; |
| 5370 | + |
| 5371 | + let prepared = prepare_case_input(&case, &suite, OptLevel::O0).unwrap(); |
| 5372 | + let generated = fs::read_to_string(&prepared.compiler_source).unwrap(); |
| 5373 | + assert!(generated.contains("module math_values")); |
| 5374 | + assert!(generated.contains("program main")); |
| 5375 | + assert!( |
| 5376 | + generated.find("module math_values").unwrap() < generated.find("program main").unwrap() |
| 5377 | + ); |
| 5378 | + |
| 5379 | + cleanup_prepared_input(&prepared); |
| 5380 | + let _ = fs::remove_dir_all(&root); |
| 5381 | + } |
| 5382 | + |
| 5383 | + #[test] |
| 5384 | + fn graph_failure_bundle_writes_authored_sources() { |
| 5385 | + let root = std::env::temp_dir().join("afs_tests_graph_bundle"); |
| 5386 | + let _ = fs::remove_dir_all(&root); |
| 5387 | + fs::create_dir_all(&root).unwrap(); |
| 5388 | + let module = root.join("math_values.f90"); |
| 5389 | + let main = root.join("main.f90"); |
| 5390 | + let generated = root.join("generated.f90"); |
| 5391 | + fs::write( |
| 5392 | + &module, |
| 5393 | + "module math_values\n integer :: answer = 42\nend module\n", |
| 5394 | + ) |
| 5395 | + .unwrap(); |
| 5396 | + fs::write( |
| 5397 | + &main, |
| 5398 | + "program main\n use math_values\n print *, answer\nend program\n", |
| 5399 | + ) |
| 5400 | + .unwrap(); |
| 5401 | + fs::write(&generated, "module math_values\n integer :: answer = 42\nend module\n\nprogram main\n use math_values\n print *, answer\nend program\n").unwrap(); |
| 5402 | + |
| 5403 | + let suite = SuiteSpec { |
| 5404 | + name: "modules/bundles".into(), |
| 5405 | + path: root.join("bundle.afs"), |
| 5406 | + cases: Vec::new(), |
| 5407 | + }; |
| 5408 | + let case = CaseSpec { |
| 5409 | + name: "graph_bundle".into(), |
| 5410 | + source: main.clone(), |
| 5411 | + graph_files: vec![module.clone(), main.clone()], |
| 5412 | + requested: BTreeSet::from([Stage::Run]), |
| 5413 | + opt_levels: vec![OptLevel::O0], |
| 5414 | + repeat_count: 2, |
| 5415 | + reference_compilers: Vec::new(), |
| 5416 | + consistency_checks: Vec::new(), |
| 5417 | + expectations: Vec::new(), |
| 5418 | + status_rules: Vec::new(), |
| 5419 | + }; |
| 5420 | + let outcome = Outcome { |
| 5421 | + suite: suite.name.clone(), |
| 5422 | + case: case.name.clone(), |
| 5423 | + opt_level: OptLevel::O0, |
| 5424 | + kind: OutcomeKind::Fail, |
| 5425 | + detail: "boom".into(), |
| 5426 | + bundle: None, |
| 5427 | + consistency_observations: Vec::new(), |
| 5428 | + }; |
| 5429 | + let artifacts = ExecutionArtifacts { |
| 5430 | + requested: BTreeSet::from([Stage::Run]), |
| 5431 | + armfortas: Some(run_only_result("42\n", "", 0)), |
| 5432 | + armfortas_failure: None, |
| 5433 | + references: Vec::new(), |
| 5434 | + consistency_issues: Vec::new(), |
| 5435 | + }; |
| 5436 | + let prepared = PreparedInput { |
| 5437 | + compiler_source: generated.clone(), |
| 5438 | + generated_source: Some(generated.clone()), |
| 5439 | + temp_root: None, |
| 5440 | + }; |
| 5441 | + |
| 5442 | + let bundle = write_failure_bundle(&suite, &case, &prepared, &outcome, &artifacts).unwrap(); |
| 5443 | + assert!(bundle.join("source.f90").exists()); |
| 5444 | + assert!(bundle.join("sources").join("00_math_values.f90").exists()); |
| 5445 | + assert!(bundle.join("sources").join("01_main.f90").exists()); |
| 5446 | + |
| 5447 | + let _ = fs::remove_dir_all(bundle); |
| 5448 | + let _ = fs::remove_dir_all(&root); |
| 5449 | + } |
| 5450 | + |
| 5092 | 5451 | #[test] |
| 5093 | 5452 | fn render_summary_includes_consistency_rollups() { |
| 5094 | 5453 | let mut summary = Summary::default(); |