diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-23 22:35:41 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-23 22:35:41 +0300 |
| commit | 71ead678c05d2165fc0b6a291ca6d1e25c2a11e0 (patch) | |
| tree | fbe72fe613d530eb121e9b30ab5644f462fe930c | |
| parent | f15ea95bf296056a7cea26a29509d9a3ef185eb7 (diff) | |
| download | fparkan-71ead678c05d2165fc0b6a291ca6d1e25c2a11e0.tar.xz fparkan-71ead678c05d2165fc0b6a291ca6d1e25c2a11e0.zip | |
feat: add Vulkan instance probe to smoke runner
| -rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
| -rw-r--r-- | apps/fparkan-vulkan-smoke/src/main.rs | 172 | ||||
| -rw-r--r-- | fixtures/acceptance/coverage.tsv | 1 | ||||
| -rw-r--r-- | fixtures/acceptance/stage_0_2_roadmap.md | 1 |
4 files changed, 158 insertions, 18 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3aa187..fd41ff0 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-loader + --probe-instance --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 bd7efe7..7e47581 100644 --- a/apps/fparkan-vulkan-smoke/src/main.rs +++ b/apps/fparkan-vulkan-smoke/src/main.rs @@ -12,7 +12,8 @@ //! Native Vulkan smoke runner entrypoint. use fparkan_render_vulkan::{ - probe_vulkan_loader, triangle_shader_manifest, validate_shader_manifest, + create_vulkan_instance_probe, probe_vulkan_loader, triangle_shader_manifest, + validate_shader_manifest, VulkanInstanceConfig, }; use std::path::PathBuf; use std::process::Command; @@ -57,6 +58,7 @@ struct SmokeOptions { resize_count: u32, validation_error_count: Option<u32>, probe_loader: bool, + probe_instance: bool, reason: Option<String>, } @@ -69,6 +71,7 @@ impl SmokeOptions { let mut resize_count = 0; let mut validation_error_count = None; let mut probe_loader = false; + let mut probe_instance = false; let mut reason = None; let mut iter = args.iter(); while let Some(arg) = iter.next() { @@ -112,6 +115,10 @@ impl SmokeOptions { "--probe-loader" => { probe_loader = true; } + "--probe-instance" => { + probe_loader = true; + probe_instance = true; + } "--reason" => { let value = iter .next() @@ -129,6 +136,7 @@ impl SmokeOptions { resize_count, validation_error_count, probe_loader, + probe_instance, reason, }) } @@ -144,7 +152,10 @@ fn parse_u32(name: &str, value: &str) -> Result<u32, String> { struct VulkanBootstrapProbe { loader_status: VulkanLoaderStatus, instance_api: Option<String>, - error: Option<String>, + loader_error: Option<String>, + instance_status: VulkanInstanceStatus, + instance_error: Option<String>, + portability_enumeration: bool, } impl VulkanBootstrapProbe { @@ -153,22 +164,47 @@ impl VulkanBootstrapProbe { return Self { loader_status: VulkanLoaderStatus::Skipped, instance_api: None, - error: None, + loader_error: None, + instance_status: VulkanInstanceStatus::Skipped, + instance_error: None, + portability_enumeration: false, }; } - match probe_vulkan_loader() { + let mut probe = match probe_vulkan_loader() { Ok(report) => Self { loader_status: VulkanLoaderStatus::Available, instance_api: Some(format_api_version(report.instance_api_version)), - error: None, + loader_error: None, + instance_status: VulkanInstanceStatus::Skipped, + instance_error: None, + portability_enumeration: false, }, Err(err) => Self { loader_status: VulkanLoaderStatus::Unavailable, instance_api: None, - error: Some(err.to_string()), + loader_error: Some(err.to_string()), + instance_status: VulkanInstanceStatus::Skipped, + instance_error: None, + portability_enumeration: false, }, + }; + + if options.probe_instance && probe.loader_status == VulkanLoaderStatus::Available { + let config = VulkanInstanceConfig::smoke("fparkan-vulkan-smoke"); + probe.portability_enumeration = config.enable_portability_enumeration; + match create_vulkan_instance_probe(&config) { + Ok(instance) => { + probe.instance_status = VulkanInstanceStatus::Created; + probe.portability_enumeration = instance.report.create_flags != 0; + } + Err(err) => { + probe.instance_status = VulkanInstanceStatus::Failed; + probe.instance_error = Some(err.to_string()); + } + } } + probe } } @@ -190,6 +226,23 @@ impl VulkanLoaderStatus { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum VulkanInstanceStatus { + Skipped, + Created, + Failed, +} + +impl VulkanInstanceStatus { + const fn as_str(self) -> &'static str { + match self { + Self::Skipped => "skipped", + Self::Created => "created", + Self::Failed => "failed", + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] enum SmokePlatform { Windows, Linux, @@ -271,6 +324,11 @@ fn validate_smoke_options( "passed native smoke report requires successful --probe-loader".to_string(), ); } + if bootstrap.instance_status != VulkanInstanceStatus::Created { + return Err( + "passed native smoke report requires successful --probe-instance".to_string(), + ); + } } } Ok(()) @@ -293,8 +351,12 @@ fn render_smoke_report_json( .instance_api .as_ref() .map_or_else(|| "null".to_string(), |value| json_string(value)); - let bootstrap_error = bootstrap - .error + let loader_error = bootstrap + .loader_error + .as_ref() + .map_or_else(|| "null".to_string(), |value| json_string(value)); + let instance_error = bootstrap + .instance_error .as_ref() .map_or_else(|| "null".to_string(), |value| json_string(value)); Ok(format!( @@ -311,7 +373,10 @@ fn render_smoke_report_json( " \"shader_manifest_hash\": \"{}\",\n", " \"vulkan_loader_status\": \"{}\",\n", " \"vulkan_instance_api\": {},\n", - " \"vulkan_bootstrap_error\": {},\n", + " \"vulkan_loader_error\": {},\n", + " \"vulkan_instance_status\": \"{}\",\n", + " \"vulkan_instance_error\": {},\n", + " \"vulkan_portability_enumeration\": {},\n", " \"reason\": {}\n", "}}\n" ), @@ -326,7 +391,14 @@ fn render_smoke_report_json( json_escape(&shader_manifest.manifest_hash), bootstrap.loader_status.as_str(), instance_api, - bootstrap_error, + loader_error, + bootstrap.instance_status.as_str(), + instance_error, + if bootstrap.portability_enumeration { + "true" + } else { + "false" + }, reason )) } @@ -404,7 +476,10 @@ mod tests { &VulkanBootstrapProbe { loader_status: VulkanLoaderStatus::Unavailable, instance_api: None, - error: Some("Vulkan loader is unavailable".to_string()), + loader_error: Some("Vulkan loader is unavailable".to_string()), + instance_status: VulkanInstanceStatus::Skipped, + instance_error: None, + portability_enumeration: false, }, ) } @@ -433,7 +508,10 @@ mod tests { &VulkanBootstrapProbe { loader_status: VulkanLoaderStatus::Available, instance_api: Some("1.3.0".to_string()), - error: None, + loader_error: None, + instance_status: VulkanInstanceStatus::Created, + instance_error: None, + portability_enumeration: false, }, ), Err("passed native smoke report requires --frames >= 300".to_string()) @@ -464,7 +542,10 @@ mod tests { &VulkanBootstrapProbe { loader_status: VulkanLoaderStatus::Skipped, instance_api: None, - error: None, + loader_error: None, + instance_status: VulkanInstanceStatus::Skipped, + instance_error: None, + portability_enumeration: false, }, ), Err("passed native smoke report requires successful --probe-loader".to_string()) @@ -472,6 +553,41 @@ mod tests { } #[test] + fn rejects_passed_without_instance_probe() { + let options = SmokeOptions::parse(&strings(&[ + "--platform", + "linux", + "--out", + "target/native.json", + "--status", + "passed", + "--frames", + "300", + "--resize-count", + "1", + "--validation-error-count", + "0", + "--probe-loader", + ])) + .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::Skipped, + instance_error: None, + portability_enumeration: false, + }, + ), + Err("passed native smoke report requires successful --probe-instance".to_string()) + ); + } + + #[test] fn blocked_report_includes_shader_manifest_and_bootstrap_status() -> Result<(), String> { let options = SmokeOptions::parse(&strings(&[ "--platform", @@ -489,7 +605,10 @@ mod tests { &VulkanBootstrapProbe { loader_status: VulkanLoaderStatus::Unavailable, instance_api: None, - error: Some("Vulkan loader is unavailable: dlopen failed".to_string()), + loader_error: Some("Vulkan loader is unavailable: dlopen failed".to_string()), + instance_status: VulkanInstanceStatus::Skipped, + instance_error: None, + portability_enumeration: true, }, )?; @@ -499,14 +618,33 @@ mod tests { assert!(json.contains("\"shader_manifest_hash\": \"")); assert!(json.contains("\"vulkan_loader_status\": \"unavailable\"")); assert!(json.contains("\"vulkan_instance_api\": null")); - assert!(json.contains( - "\"vulkan_bootstrap_error\": \"Vulkan loader is unavailable: dlopen failed\"" - )); + assert!(json + .contains("\"vulkan_loader_error\": \"Vulkan loader is unavailable: dlopen failed\"")); + assert!(json.contains("\"vulkan_instance_status\": \"skipped\"")); + assert!(json.contains("\"vulkan_instance_error\": null")); + assert!(json.contains("\"vulkan_portability_enumeration\": true")); assert!(json.contains("\"reason\": \"runner unavailable\"")); Ok(()) } #[test] + fn parses_instance_probe_as_loader_probe() -> Result<(), String> { + let options = SmokeOptions::parse(&strings(&[ + "--platform", + "linux", + "--out", + "target/native.json", + "--probe-instance", + "--reason", + "runner unavailable", + ]))?; + + assert!(options.probe_loader); + assert!(options.probe_instance); + Ok(()) + } + + #[test] fn formats_vulkan_api_version() { assert_eq!(format_api_version((1 << 22) | (3 << 12) | 280), "1.3.280"); } diff --git a/fixtures/acceptance/coverage.tsv b/fixtures/acceptance/coverage.tsv index 45cdf67..233572a 100644 --- a/fixtures/acceptance/coverage.tsv +++ b/fixtures/acceptance/coverage.tsv @@ -52,6 +52,7 @@ S0-VK-021 covered cargo test -p fparkan-render-vulkan --offline frame_submission S0-VK-022 covered cargo test -p fparkan-render-vulkan --offline backend_tracks_render_request_and_presents 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-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 619104d..6ccfd72 100644 --- a/fixtures/acceptance/stage_0_2_roadmap.md +++ b/fixtures/acceptance/stage_0_2_roadmap.md @@ -52,6 +52,7 @@ `S0-VK-022` `S0-VK-023` `S0-VK-024` +`S0-VK-025` `S0-LIMIT-001` `S0-LIMIT-002` `L1-P1-NRES-001` |
