diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-23 22:38:09 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-23 22:38:09 +0300 |
| commit | aa2133d82b2a9d92fdbdce2b60eec103536fe484 (patch) | |
| tree | ddb395163d457ef2cafcb51fd752eacb42cb1fb8 | |
| parent | 71ead678c05d2165fc0b6a291ca6d1e25c2a11e0 (diff) | |
| download | fparkan-aa2133d82b2a9d92fdbdce2b60eec103536fe484.tar.xz fparkan-aa2133d82b2a9d92fdbdce2b60eec103536fe484.zip | |
feat: add Vulkan surface preflight to smoke runner
| -rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
| -rw-r--r-- | apps/fparkan-vulkan-smoke/src/main.rs | 134 | ||||
| -rw-r--r-- | fixtures/acceptance/coverage.tsv | 1 | ||||
| -rw-r--r-- | fixtures/acceptance/stage_0_2_roadmap.md | 1 |
4 files changed, 135 insertions, 3 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd41ff0..f0c90e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: --platform "${{ matrix.smoke_platform }}" --out "target/fparkan/native-smoke/${{ runner.os }}.json" --status blocked - --probe-instance + --probe-surface --reason "native Vulkan smoke runner is not enabled on this CI lane yet" - name: Upload acceptance evidence if: always() diff --git a/apps/fparkan-vulkan-smoke/src/main.rs b/apps/fparkan-vulkan-smoke/src/main.rs index 7e47581..e6fc800 100644 --- a/apps/fparkan-vulkan-smoke/src/main.rs +++ b/apps/fparkan-vulkan-smoke/src/main.rs @@ -12,8 +12,8 @@ //! Native Vulkan smoke runner entrypoint. use fparkan_render_vulkan::{ - create_vulkan_instance_probe, probe_vulkan_loader, triangle_shader_manifest, - validate_shader_manifest, VulkanInstanceConfig, + create_vulkan_instance_probe, plan_vulkan_surface, probe_vulkan_loader, + triangle_shader_manifest, validate_shader_manifest, VulkanInstanceConfig, }; use std::path::PathBuf; use std::process::Command; @@ -59,6 +59,7 @@ struct SmokeOptions { validation_error_count: Option<u32>, probe_loader: bool, probe_instance: bool, + probe_surface: bool, reason: Option<String>, } @@ -72,6 +73,7 @@ impl SmokeOptions { let mut validation_error_count = None; let mut probe_loader = false; let mut probe_instance = false; + let mut probe_surface = false; let mut reason = None; let mut iter = args.iter(); while let Some(arg) = iter.next() { @@ -119,6 +121,11 @@ impl SmokeOptions { probe_loader = true; probe_instance = true; } + "--probe-surface" => { + probe_loader = true; + probe_instance = true; + probe_surface = true; + } "--reason" => { let value = iter .next() @@ -137,6 +144,7 @@ impl SmokeOptions { validation_error_count, probe_loader, probe_instance, + probe_surface, reason, }) } @@ -156,6 +164,8 @@ struct VulkanBootstrapProbe { instance_status: VulkanInstanceStatus, instance_error: Option<String>, portability_enumeration: bool, + surface_status: VulkanSurfaceStatus, + surface_error: Option<String>, } impl VulkanBootstrapProbe { @@ -168,6 +178,8 @@ impl VulkanBootstrapProbe { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, }; } @@ -179,6 +191,8 @@ impl VulkanBootstrapProbe { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, }, Err(err) => Self { loader_status: VulkanLoaderStatus::Unavailable, @@ -187,6 +201,8 @@ impl VulkanBootstrapProbe { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, }, }; @@ -204,6 +220,17 @@ impl VulkanBootstrapProbe { } } } + if options.probe_surface && probe.instance_status == VulkanInstanceStatus::Created { + match plan_vulkan_surface(None) { + Ok(_) => { + probe.surface_status = VulkanSurfaceStatus::Planned; + } + Err(err) => { + probe.surface_status = VulkanSurfaceStatus::MissingWindowHandles; + probe.surface_error = Some(err.to_string()); + } + } + } probe } } @@ -243,6 +270,23 @@ impl VulkanInstanceStatus { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum VulkanSurfaceStatus { + Skipped, + Planned, + MissingWindowHandles, +} + +impl VulkanSurfaceStatus { + const fn as_str(self) -> &'static str { + match self { + Self::Skipped => "skipped", + Self::Planned => "planned", + Self::MissingWindowHandles => "missing_window_handles", + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] enum SmokePlatform { Windows, Linux, @@ -329,6 +373,11 @@ fn validate_smoke_options( "passed native smoke report requires successful --probe-instance".to_string(), ); } + if bootstrap.surface_status != VulkanSurfaceStatus::Planned { + return Err( + "passed native smoke report requires successful --probe-surface".to_string(), + ); + } } } Ok(()) @@ -359,6 +408,10 @@ fn render_smoke_report_json( .instance_error .as_ref() .map_or_else(|| "null".to_string(), |value| json_string(value)); + let surface_error = bootstrap + .surface_error + .as_ref() + .map_or_else(|| "null".to_string(), |value| json_string(value)); Ok(format!( concat!( "{{\n", @@ -377,6 +430,8 @@ fn render_smoke_report_json( " \"vulkan_instance_status\": \"{}\",\n", " \"vulkan_instance_error\": {},\n", " \"vulkan_portability_enumeration\": {},\n", + " \"vulkan_surface_status\": \"{}\",\n", + " \"vulkan_surface_error\": {},\n", " \"reason\": {}\n", "}}\n" ), @@ -399,6 +454,8 @@ fn render_smoke_report_json( } else { "false" }, + bootstrap.surface_status.as_str(), + surface_error, reason )) } @@ -480,6 +537,8 @@ mod tests { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, }, ) } @@ -512,6 +571,8 @@ mod tests { instance_status: VulkanInstanceStatus::Created, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Planned, + surface_error: None, }, ), Err("passed native smoke report requires --frames >= 300".to_string()) @@ -546,6 +607,8 @@ mod tests { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, }, ), Err("passed native smoke report requires successful --probe-loader".to_string()) @@ -581,6 +644,8 @@ mod tests { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, }, ), Err("passed native smoke report requires successful --probe-instance".to_string()) @@ -588,6 +653,43 @@ mod tests { } #[test] + fn rejects_passed_without_surface_probe() { + let options = SmokeOptions::parse(&strings(&[ + "--platform", + "linux", + "--out", + "target/native.json", + "--status", + "passed", + "--frames", + "300", + "--resize-count", + "1", + "--validation-error-count", + "0", + "--probe-instance", + ])) + .expect("options"); + + assert_eq!( + validate_smoke_options( + &options, + &VulkanBootstrapProbe { + loader_status: VulkanLoaderStatus::Available, + instance_api: Some("1.3.0".to_string()), + loader_error: None, + instance_status: VulkanInstanceStatus::Created, + instance_error: None, + portability_enumeration: false, + surface_status: VulkanSurfaceStatus::Skipped, + surface_error: None, + }, + ), + Err("passed native smoke report requires successful --probe-surface".to_string()) + ); + } + + #[test] fn blocked_report_includes_shader_manifest_and_bootstrap_status() -> Result<(), String> { let options = SmokeOptions::parse(&strings(&[ "--platform", @@ -609,6 +711,11 @@ mod tests { instance_status: VulkanInstanceStatus::Skipped, instance_error: None, portability_enumeration: true, + surface_status: VulkanSurfaceStatus::MissingWindowHandles, + surface_error: Some( + "native window/display handles are required for Vulkan surface creation" + .to_string(), + ), }, )?; @@ -623,6 +730,10 @@ mod tests { assert!(json.contains("\"vulkan_instance_status\": \"skipped\"")); assert!(json.contains("\"vulkan_instance_error\": null")); assert!(json.contains("\"vulkan_portability_enumeration\": true")); + assert!(json.contains("\"vulkan_surface_status\": \"missing_window_handles\"")); + assert!(json.contains( + "\"vulkan_surface_error\": \"native window/display handles are required for Vulkan surface creation\"" + )); assert!(json.contains("\"reason\": \"runner unavailable\"")); Ok(()) } @@ -641,6 +752,25 @@ mod tests { assert!(options.probe_loader); assert!(options.probe_instance); + assert!(!options.probe_surface); + Ok(()) + } + + #[test] + fn parses_surface_probe_as_instance_probe() -> Result<(), String> { + let options = SmokeOptions::parse(&strings(&[ + "--platform", + "linux", + "--out", + "target/native.json", + "--probe-surface", + "--reason", + "runner unavailable", + ]))?; + + assert!(options.probe_loader); + assert!(options.probe_instance); + assert!(options.probe_surface); Ok(()) } diff --git a/fixtures/acceptance/coverage.tsv b/fixtures/acceptance/coverage.tsv index 233572a..7bbcb3d 100644 --- a/fixtures/acceptance/coverage.tsv +++ b/fixtures/acceptance/coverage.tsv @@ -53,6 +53,7 @@ S0-VK-022 covered cargo test -p fparkan-render-vulkan --offline backend_tracks_r S0-VK-023 covered cargo test -p fparkan-vulkan-smoke --offline rejects_false_pass_without_full_evidence blocked_report_includes_shader_manifest_and_bootstrap_status S0-VK-024 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_loader_probe formats_vulkan_api_version S0-VK-025 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_instance_probe parses_instance_probe_as_loader_probe +S0-VK-026 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_surface_probe parses_surface_probe_as_instance_probe S0-LIMIT-001 covered cargo test -p fparkan-binary --offline rejects_count_stride_overflow S0-LIMIT-002 covered cargo test -p fparkan-binary --offline rejects_oversized_declared_allocation_before_read L1-P1-NRES-001 covered cargo test -p fparkan-nres --offline licensed_corpora_nres_roundtrip_gates diff --git a/fixtures/acceptance/stage_0_2_roadmap.md b/fixtures/acceptance/stage_0_2_roadmap.md index 6ccfd72..0977b2c 100644 --- a/fixtures/acceptance/stage_0_2_roadmap.md +++ b/fixtures/acceptance/stage_0_2_roadmap.md @@ -53,6 +53,7 @@ `S0-VK-023` `S0-VK-024` `S0-VK-025` +`S0-VK-026` `S0-LIMIT-001` `S0-LIMIT-002` `L1-P1-NRES-001` |
