diff options
Diffstat (limited to 'apps/fparkan-vulkan-smoke')
| -rw-r--r-- | apps/fparkan-vulkan-smoke/src/main.rs | 172 |
1 files changed, 163 insertions, 9 deletions
diff --git a/apps/fparkan-vulkan-smoke/src/main.rs b/apps/fparkan-vulkan-smoke/src/main.rs index dded309..bd7efe7 100644 --- a/apps/fparkan-vulkan-smoke/src/main.rs +++ b/apps/fparkan-vulkan-smoke/src/main.rs @@ -11,7 +11,9 @@ #![allow(clippy::print_stderr, clippy::print_stdout)] //! Native Vulkan smoke runner entrypoint. -use fparkan_render_vulkan::{triangle_shader_manifest, validate_shader_manifest}; +use fparkan_render_vulkan::{ + probe_vulkan_loader, triangle_shader_manifest, validate_shader_manifest, +}; use std::path::PathBuf; use std::process::Command; @@ -35,8 +37,9 @@ fn main() { fn run(args: &[String]) -> Result<String, String> { let options = SmokeOptions::parse(args)?; - validate_smoke_options(&options)?; - let report = render_smoke_report_json(&options)?; + let bootstrap = VulkanBootstrapProbe::run(&options); + validate_smoke_options(&options, &bootstrap)?; + let report = render_smoke_report_json(&options, &bootstrap)?; if let Some(parent) = options.out.parent() { std::fs::create_dir_all(parent).map_err(|err| format!("{}: {err}", parent.display()))?; } @@ -53,6 +56,7 @@ struct SmokeOptions { frames: u32, resize_count: u32, validation_error_count: Option<u32>, + probe_loader: bool, reason: Option<String>, } @@ -64,6 +68,7 @@ impl SmokeOptions { let mut frames = 0; let mut resize_count = 0; let mut validation_error_count = None; + let mut probe_loader = false; let mut reason = None; let mut iter = args.iter(); while let Some(arg) = iter.next() { @@ -104,6 +109,9 @@ impl SmokeOptions { .ok_or_else(|| "--validation-error-count requires a value".to_string())?; validation_error_count = Some(parse_u32("--validation-error-count", value)?); } + "--probe-loader" => { + probe_loader = true; + } "--reason" => { let value = iter .next() @@ -120,6 +128,7 @@ impl SmokeOptions { frames, resize_count, validation_error_count, + probe_loader, reason, }) } @@ -131,6 +140,55 @@ fn parse_u32(name: &str, value: &str) -> Result<u32, String> { .map_err(|_| format!("invalid {name} value: {value}")) } +#[derive(Clone, Debug, Eq, PartialEq)] +struct VulkanBootstrapProbe { + loader_status: VulkanLoaderStatus, + instance_api: Option<String>, + error: Option<String>, +} + +impl VulkanBootstrapProbe { + fn run(options: &SmokeOptions) -> Self { + if !options.probe_loader { + return Self { + loader_status: VulkanLoaderStatus::Skipped, + instance_api: None, + error: None, + }; + } + + match probe_vulkan_loader() { + Ok(report) => Self { + loader_status: VulkanLoaderStatus::Available, + instance_api: Some(format_api_version(report.instance_api_version)), + error: None, + }, + Err(err) => Self { + loader_status: VulkanLoaderStatus::Unavailable, + instance_api: None, + error: Some(err.to_string()), + }, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum VulkanLoaderStatus { + Skipped, + Available, + Unavailable, +} + +impl VulkanLoaderStatus { + const fn as_str(self) -> &'static str { + match self { + Self::Skipped => "skipped", + Self::Available => "available", + Self::Unavailable => "unavailable", + } + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum SmokePlatform { Windows, @@ -180,7 +238,10 @@ impl SmokeStatus { } } -fn validate_smoke_options(options: &SmokeOptions) -> Result<(), String> { +fn validate_smoke_options( + options: &SmokeOptions, + bootstrap: &VulkanBootstrapProbe, +) -> Result<(), String> { match options.status { SmokeStatus::Blocked => { if options @@ -205,12 +266,20 @@ fn validate_smoke_options(options: &SmokeOptions) -> Result<(), String> { "passed native smoke report requires --validation-error-count 0".to_string(), ); } + if bootstrap.loader_status != VulkanLoaderStatus::Available { + return Err( + "passed native smoke report requires successful --probe-loader".to_string(), + ); + } } } Ok(()) } -fn render_smoke_report_json(options: &SmokeOptions) -> Result<String, String> { +fn render_smoke_report_json( + options: &SmokeOptions, + bootstrap: &VulkanBootstrapProbe, +) -> Result<String, String> { let shader_manifest = validate_shader_manifest(&triangle_shader_manifest()) .map_err(|err| format!("shader manifest: {err}"))?; let validation_error_count = options @@ -220,6 +289,14 @@ fn render_smoke_report_json(options: &SmokeOptions) -> Result<String, String> { .reason .as_ref() .map_or_else(|| "null".to_string(), |value| json_string(value)); + let instance_api = bootstrap + .instance_api + .as_ref() + .map_or_else(|| "null".to_string(), |value| json_string(value)); + let bootstrap_error = bootstrap + .error + .as_ref() + .map_or_else(|| "null".to_string(), |value| json_string(value)); Ok(format!( concat!( "{{\n", @@ -232,6 +309,9 @@ fn render_smoke_report_json(options: &SmokeOptions) -> Result<String, String> { " \"resize_count\": {},\n", " \"validation_error_count\": {},\n", " \"shader_manifest_hash\": \"{}\",\n", + " \"vulkan_loader_status\": \"{}\",\n", + " \"vulkan_instance_api\": {},\n", + " \"vulkan_bootstrap_error\": {},\n", " \"reason\": {}\n", "}}\n" ), @@ -244,10 +324,20 @@ fn render_smoke_report_json(options: &SmokeOptions) -> Result<String, String> { options.resize_count, validation_error_count, json_escape(&shader_manifest.manifest_hash), + bootstrap.loader_status.as_str(), + instance_api, + bootstrap_error, reason )) } +fn format_api_version(version: u32) -> String { + let major = version >> 22; + let minor = (version >> 12) & 0x03ff; + let patch = version & 0x0fff; + format!("{major}.{minor}.{patch}") +} + fn current_git_commit_sha() -> String { Command::new("git") .args(["rev-parse", "HEAD"]) @@ -300,14 +390,23 @@ mod tests { "target/native.json", "--status", "blocked", + "--probe-loader", "--reason", "runner unavailable", ]))?; assert_eq!(options.platform, SmokePlatform::Linux); assert_eq!(options.status, SmokeStatus::Blocked); + assert!(options.probe_loader); assert_eq!(options.reason.as_deref(), Some("runner unavailable")); - validate_smoke_options(&options) + validate_smoke_options( + &options, + &VulkanBootstrapProbe { + loader_status: VulkanLoaderStatus::Unavailable, + instance_api: None, + error: Some("Vulkan loader is unavailable".to_string()), + }, + ) } #[test] @@ -329,13 +428,51 @@ mod tests { .expect("options"); assert_eq!( - validate_smoke_options(&options), + validate_smoke_options( + &options, + &VulkanBootstrapProbe { + loader_status: VulkanLoaderStatus::Available, + instance_api: Some("1.3.0".to_string()), + error: None, + }, + ), Err("passed native smoke report requires --frames >= 300".to_string()) ); } #[test] - fn blocked_report_includes_shader_manifest_hash() -> Result<(), String> { + fn rejects_passed_without_loader_probe() { + let options = SmokeOptions::parse(&strings(&[ + "--platform", + "linux", + "--out", + "target/native.json", + "--status", + "passed", + "--frames", + "300", + "--resize-count", + "1", + "--validation-error-count", + "0", + ])) + .expect("options"); + + assert_eq!( + validate_smoke_options( + &options, + &VulkanBootstrapProbe { + loader_status: VulkanLoaderStatus::Skipped, + instance_api: None, + error: None, + }, + ), + Err("passed native smoke report requires successful --probe-loader".to_string()) + ); + } + + #[test] + fn blocked_report_includes_shader_manifest_and_bootstrap_status() -> Result<(), String> { let options = SmokeOptions::parse(&strings(&[ "--platform", "macos", @@ -347,13 +484,30 @@ mod tests { "runner unavailable", ]))?; - let json = render_smoke_report_json(&options)?; + let json = render_smoke_report_json( + &options, + &VulkanBootstrapProbe { + loader_status: VulkanLoaderStatus::Unavailable, + instance_api: None, + error: Some("Vulkan loader is unavailable: dlopen failed".to_string()), + }, + )?; assert!(json.contains("\"schema_version\": \"fparkan-native-smoke-v1\"")); assert!(json.contains("\"platform\": \"macos\"")); assert!(json.contains("\"status\": \"blocked\"")); 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("\"reason\": \"runner unavailable\"")); Ok(()) } + + #[test] + fn formats_vulkan_api_version() { + assert_eq!(format_api_version((1 << 22) | (3 << 12) | 280), "1.3.280"); + } } |
