@@ -164,7 +164,7 @@ pub struct EnumGroup {
164164 pub values : Vec < FlatEnum > ,
165165}
166166
167- #[ derive( Debug , Serialize , Clone , Copy ) ]
167+ #[ derive( Debug , Serialize , Clone , Copy , PartialEq , Eq ) ]
168168pub struct PfnRange {
169169 /// Index into featArray or extArray.
170170 pub extension : u16 ,
@@ -2045,4 +2045,304 @@ mod tests {
20452045 // glX — capital X matters for generated member names.
20462046 assert_eq ! ( api_name_prefix( "glx" ) , "glX" ) ;
20472047 }
2048+
2049+ // ---- Protection lattice ----
2050+
2051+ #[ test]
2052+ fn protection_guarded_absorbs_unconditional ( ) {
2053+ // A guarded state that encounters an unprotected extension becomes
2054+ // unconditional (absorbing element of the lattice).
2055+ let mut p = Protection :: Guarded ( vec ! [ "VK_USE_PLATFORM_WIN32_KHR" . to_string( ) ] ) ;
2056+ p. add_extension ( & [ ] ) ; // unprotected extension
2057+ assert ! ( p. is_unconditional( ) ) ;
2058+ }
2059+
2060+ #[ test]
2061+ fn protection_unconditional_stays_unconditional ( ) {
2062+ // Once unconditional, adding a guarded extension can't re-guard it.
2063+ let mut p = Protection :: Unconditional ;
2064+ p. add_extension ( & [ "VK_USE_PLATFORM_XLIB_KHR" . to_string ( ) ] ) ;
2065+ assert ! ( p. is_unconditional( ) ) ;
2066+ }
2067+
2068+ #[ test]
2069+ fn protection_guarded_unions_guards ( ) {
2070+ let mut p = Protection :: new_guarded ( ) ;
2071+ p. add_extension ( & [ "VK_USE_PLATFORM_WIN32_KHR" . to_string ( ) ] ) ;
2072+ p. add_extension ( & [ "VK_USE_PLATFORM_XLIB_KHR" . to_string ( ) ] ) ;
2073+ let v = p. into_vec ( ) ;
2074+ assert_eq ! ( v. len( ) , 2 ) ;
2075+ // into_vec sorts
2076+ assert_eq ! ( v[ 0 ] , "VK_USE_PLATFORM_WIN32_KHR" ) ;
2077+ assert_eq ! ( v[ 1 ] , "VK_USE_PLATFORM_XLIB_KHR" ) ;
2078+ }
2079+
2080+ #[ test]
2081+ fn protection_guarded_deduplicates ( ) {
2082+ let mut p = Protection :: new_guarded ( ) ;
2083+ p. add_extension ( & [ "VK_USE_PLATFORM_WIN32_KHR" . to_string ( ) ] ) ;
2084+ p. add_extension ( & [ "VK_USE_PLATFORM_WIN32_KHR" . to_string ( ) ] ) ;
2085+ let v = p. into_vec ( ) ;
2086+ assert_eq ! ( v. len( ) , 1 ) ;
2087+ }
2088+
2089+ #[ test]
2090+ fn protection_empty_guarded_into_vec_is_empty ( ) {
2091+ // A Guarded with no guards is treated as unconditional (empty Vec).
2092+ let p = Protection :: new_guarded ( ) ;
2093+ assert ! ( p. into_vec( ) . is_empty( ) ) ;
2094+ }
2095+
2096+ #[ test]
2097+ fn protection_unconditional_into_vec_is_empty ( ) {
2098+ let p = Protection :: Unconditional ;
2099+ assert ! ( p. into_vec( ) . is_empty( ) ) ;
2100+ }
2101+
2102+ // ---- indices_to_ranges ----
2103+
2104+ #[ test]
2105+ fn indices_to_ranges_empty ( ) {
2106+ assert ! ( indices_to_ranges( 0 , & [ ] ) . is_empty( ) ) ;
2107+ }
2108+
2109+ #[ test]
2110+ fn indices_to_ranges_single_element ( ) {
2111+ let r = indices_to_ranges ( 5 , & [ 42 ] ) ;
2112+ assert_eq ! (
2113+ r,
2114+ vec![ PfnRange {
2115+ extension: 5 ,
2116+ start: 42 ,
2117+ count: 1
2118+ } ]
2119+ ) ;
2120+ }
2121+
2122+ #[ test]
2123+ fn indices_to_ranges_fully_contiguous ( ) {
2124+ // A contiguous run should produce a single range.
2125+ let r = indices_to_ranges ( 0 , & [ 10 , 11 , 12 , 13 , 14 ] ) ;
2126+ assert_eq ! (
2127+ r,
2128+ vec![ PfnRange {
2129+ extension: 0 ,
2130+ start: 10 ,
2131+ count: 5
2132+ } ]
2133+ ) ;
2134+ }
2135+
2136+ #[ test]
2137+ fn indices_to_ranges_single_gap ( ) {
2138+ // [3, 4, 5, 10, 11] → two ranges.
2139+ let r = indices_to_ranges ( 1 , & [ 3 , 4 , 5 , 10 , 11 ] ) ;
2140+ assert_eq ! (
2141+ r,
2142+ vec![
2143+ PfnRange {
2144+ extension: 1 ,
2145+ start: 3 ,
2146+ count: 3
2147+ } ,
2148+ PfnRange {
2149+ extension: 1 ,
2150+ start: 10 ,
2151+ count: 2
2152+ } ,
2153+ ]
2154+ ) ;
2155+ }
2156+
2157+ #[ test]
2158+ fn indices_to_ranges_all_disjoint ( ) {
2159+ // No contiguous pairs → one range per element.
2160+ let r = indices_to_ranges ( 2 , & [ 0 , 5 , 10 ] ) ;
2161+ assert_eq ! (
2162+ r,
2163+ vec![
2164+ PfnRange {
2165+ extension: 2 ,
2166+ start: 0 ,
2167+ count: 1
2168+ } ,
2169+ PfnRange {
2170+ extension: 2 ,
2171+ start: 5 ,
2172+ count: 1
2173+ } ,
2174+ PfnRange {
2175+ extension: 2 ,
2176+ start: 10 ,
2177+ count: 1
2178+ } ,
2179+ ]
2180+ ) ;
2181+ }
2182+
2183+ #[ test]
2184+ fn indices_to_ranges_multiple_gaps ( ) {
2185+ let r = indices_to_ranges ( 0 , & [ 1 , 2 , 5 , 6 , 7 , 20 ] ) ;
2186+ assert_eq ! (
2187+ r,
2188+ vec![
2189+ PfnRange {
2190+ extension: 0 ,
2191+ start: 1 ,
2192+ count: 2
2193+ } ,
2194+ PfnRange {
2195+ extension: 0 ,
2196+ start: 5 ,
2197+ count: 3
2198+ } ,
2199+ PfnRange {
2200+ extension: 0 ,
2201+ start: 20 ,
2202+ count: 1
2203+ } ,
2204+ ]
2205+ ) ;
2206+ }
2207+
2208+ // ---- ext_short_name / version_short_name ----
2209+
2210+ #[ test]
2211+ fn ext_short_name_strips_gl_prefix ( ) {
2212+ assert_eq ! ( ext_short_name( "GL_ARB_sync" ) , "ARB_sync" ) ;
2213+ }
2214+
2215+ #[ test]
2216+ fn ext_short_name_strips_vk_prefix ( ) {
2217+ assert_eq ! ( ext_short_name( "VK_KHR_swapchain" ) , "KHR_swapchain" ) ;
2218+ }
2219+
2220+ #[ test]
2221+ fn ext_short_name_strips_egl_prefix ( ) {
2222+ assert_eq ! (
2223+ ext_short_name( "EGL_KHR_platform_wayland" ) ,
2224+ "KHR_platform_wayland"
2225+ ) ;
2226+ }
2227+
2228+ #[ test]
2229+ fn ext_short_name_unknown_prefix_unchanged ( ) {
2230+ assert_eq ! ( ext_short_name( "UNKNOWN_foo_bar" ) , "UNKNOWN_foo_bar" ) ;
2231+ }
2232+
2233+ #[ test]
2234+ fn version_short_name_gl ( ) {
2235+ assert_eq ! ( version_short_name( "GL_VERSION_3_3" , "gl" ) , "VERSION_3_3" ) ;
2236+ }
2237+
2238+ #[ test]
2239+ fn version_short_name_gles2 ( ) {
2240+ // GLES uses "GL_" prefix in the XML feature name.
2241+ assert_eq ! (
2242+ version_short_name( "GL_ES_VERSION_3_0" , "gles2" ) ,
2243+ "ES_VERSION_3_0"
2244+ ) ;
2245+ }
2246+
2247+ #[ test]
2248+ fn version_short_name_vk ( ) {
2249+ assert_eq ! ( version_short_name( "VK_VERSION_1_3" , "vk" ) , "VERSION_1_3" ) ;
2250+ }
2251+
2252+ #[ test]
2253+ fn version_short_name_unknown_api_no_strip ( ) {
2254+ assert_eq ! (
2255+ version_short_name( "CUSTOM_VERSION_1_0" , "custom" ) ,
2256+ "CUSTOM_VERSION_1_0"
2257+ ) ;
2258+ }
2259+
2260+ // ---- is_gl_auto_excluded ----
2261+
2262+ #[ test]
2263+ fn gl_auto_excluded_known_names ( ) {
2264+ assert ! ( is_gl_auto_excluded( "stddef" ) ) ;
2265+ assert ! ( is_gl_auto_excluded( "khrplatform" ) ) ;
2266+ assert ! ( is_gl_auto_excluded( "inttypes" ) ) ;
2267+ }
2268+
2269+ #[ test]
2270+ fn gl_auto_excluded_other_names ( ) {
2271+ assert ! ( !is_gl_auto_excluded( "GLuint" ) ) ;
2272+ assert ! ( !is_gl_auto_excluded( "GLenum" ) ) ;
2273+ }
2274+
2275+ // ---- topo_sort_typedefs: cycle fallback ----
2276+
2277+ #[ test]
2278+ fn topo_sort_typedefs_simple_dependency_order ( ) {
2279+ // B depends on A (A appears in B's raw_c), so A should come first.
2280+ let types = vec ! [
2281+ TypeDef {
2282+ name: "B" . to_string( ) ,
2283+ raw_c: "typedef struct { A member; } B;" . to_string( ) ,
2284+ category: "struct" . to_string( ) ,
2285+ protect: vec![ ] ,
2286+ } ,
2287+ TypeDef {
2288+ name: "A" . to_string( ) ,
2289+ raw_c: "typedef struct { int x; } A;" . to_string( ) ,
2290+ category: "struct" . to_string( ) ,
2291+ protect: vec![ ] ,
2292+ } ,
2293+ ] ;
2294+ let sorted = topo_sort_typedefs ( types) ;
2295+ let a_pos = sorted. iter ( ) . position ( |t| t. name == "A" ) . unwrap ( ) ;
2296+ let b_pos = sorted. iter ( ) . position ( |t| t. name == "B" ) . unwrap ( ) ;
2297+ assert ! ( a_pos < b_pos, "A must precede B" ) ;
2298+ }
2299+
2300+ #[ test]
2301+ fn topo_sort_typedefs_cycle_does_not_panic ( ) {
2302+ // A references B, B references A — a cycle. The fallback path
2303+ // must produce *some* valid output without panicking.
2304+ let types = vec ! [
2305+ TypeDef {
2306+ name: "A" . to_string( ) ,
2307+ raw_c: "typedef struct { B* ptr; } A;" . to_string( ) ,
2308+ category: "struct" . to_string( ) ,
2309+ protect: vec![ ] ,
2310+ } ,
2311+ TypeDef {
2312+ name: "B" . to_string( ) ,
2313+ raw_c: "typedef struct { A* ptr; } B;" . to_string( ) ,
2314+ category: "struct" . to_string( ) ,
2315+ protect: vec![ ] ,
2316+ } ,
2317+ ] ;
2318+ let sorted = topo_sort_typedefs ( types) ;
2319+ // Both must appear exactly once.
2320+ assert_eq ! ( sorted. len( ) , 2 ) ;
2321+ assert ! ( sorted. iter( ) . any( |t| t. name == "A" ) ) ;
2322+ assert ! ( sorted. iter( ) . any( |t| t. name == "B" ) ) ;
2323+ }
2324+
2325+ #[ test]
2326+ fn topo_sort_typedefs_non_scannable_categories_ignored ( ) {
2327+ // "define" category bodies are not scanned for deps, so even though
2328+ // D's raw_c mentions C, no edge is created and insertion order is kept.
2329+ let types = vec ! [
2330+ TypeDef {
2331+ name: "D" . to_string( ) ,
2332+ raw_c: "#define D C" . to_string( ) ,
2333+ category: "define" . to_string( ) ,
2334+ protect: vec![ ] ,
2335+ } ,
2336+ TypeDef {
2337+ name: "C" . to_string( ) ,
2338+ raw_c: "typedef int C;" . to_string( ) ,
2339+ category: "basetype" . to_string( ) ,
2340+ protect: vec![ ] ,
2341+ } ,
2342+ ] ;
2343+ let sorted = topo_sort_typedefs ( types) ;
2344+ // No dep edge was created, so original order is preserved.
2345+ assert_eq ! ( sorted[ 0 ] . name, "D" ) ;
2346+ assert_eq ! ( sorted[ 1 ] . name, "C" ) ;
2347+ }
20482348}
0 commit comments