1313use std:: os:: unix:: net:: UnixStream ;
1414use std:: os:: unix:: process:: CommandExt ;
1515use std:: process:: Command ;
16- use std:: sync:: Arc ;
16+ use std:: sync:: { Arc , OnceLock } ;
1717
1818use cap_std_ext:: cmdext:: CapStdExtCommandExt ;
1919use color_eyre:: Result ;
@@ -521,8 +521,139 @@ integration_test!(test_varlink_todisk_creates_disk);
521521// Tests: cross-interface / varlinkctl
522522// ===========================================================================
523523
524+ /// Check whether `varlinkctl` can successfully talk to a zlink-based server.
525+ ///
526+ /// Older versions of systemd's `varlinkctl` (or versions affected by
527+ /// <https://github.com/z-galaxy/zlink/issues/233>) send an introspection
528+ /// request that zlink cannot deserialize, causing the connection to be
529+ /// immediately dropped. Rather than failing hard on such systems, we
530+ /// detect the incompatibility here and let callers skip or adapt.
531+ fn varlinkctl_is_compatible ( ) -> bool {
532+ static RESULT : OnceLock < bool > = OnceLock :: new ( ) ;
533+ * RESULT . get_or_init ( || {
534+ let bck = match get_bck_command ( ) {
535+ Ok ( b) => b,
536+ Err ( e) => {
537+ eprintln ! ( "note: varlinkctl probe: get_bck_command() failed: {e}" ) ;
538+ return false ;
539+ }
540+ } ;
541+ let sh = match shell ( ) {
542+ Ok ( s) => s,
543+ Err ( e) => {
544+ eprintln ! ( "note: varlinkctl probe: shell() failed: {e}" ) ;
545+ return false ;
546+ }
547+ } ;
548+ // Try a varlinkctl call; if it fails, the tool is either missing
549+ // or incompatible with our server. The most common incompatibility
550+ // is that varlinkctl sends org.varlink.service.GetInfo during its
551+ // initial handshake, which zlink cannot deserialize (zlink#233).
552+ let ok = xshell:: cmd!( sh, "varlinkctl call exec:{bck} io.bootc.vk.images.List" )
553+ . ignore_status ( )
554+ . read ( )
555+ . map ( |output| {
556+ serde_json:: from_str :: < serde_json:: Value > ( & output)
557+ . ok ( )
558+ . and_then ( |v| v. get ( "images" ) . cloned ( ) )
559+ . is_some ( )
560+ } )
561+ . unwrap_or ( false ) ;
562+ if !ok {
563+ eprintln ! (
564+ "note: varlinkctl probe failed; varlinkctl-dependent tests will be skipped \
565+ (see https://github.com/z-galaxy/zlink/issues/233)"
566+ ) ;
567+ }
568+ ok
569+ } )
570+ }
571+
572+ /// Cross-check the images `List` API using the Rust varlink client, and
573+ /// optionally verify that `varlinkctl` returns the same result when it is
574+ /// available and compatible.
575+ ///
576+ /// The Rust client path is the primary assertion — it always runs. The
577+ /// `varlinkctl` cross-check is best-effort: if the installed systemd is
578+ /// too old or suffers from the zlink introspection deserialization bug
579+ /// (<https://github.com/z-galaxy/zlink/issues/233>), the cross-check is
580+ /// skipped with a log message.
581+ fn test_varlink_images_list_crosscheck ( ) -> Result < ( ) > {
582+ let image = get_test_image ( ) ;
583+
584+ // Ensure the test image is pulled so we have at least one image to compare
585+ let sh = shell ( ) ?;
586+ xshell:: cmd!( sh, "podman pull -q {image}" ) . run ( ) ?;
587+
588+ // Primary path: Rust varlink client
589+ let mut bcvk = activated_connection ( ) ?;
590+ let reply = bcvk. rt . block_on ( async { bcvk. conn . list ( ) . await } ) ??;
591+ assert ! (
592+ !reply. images. is_empty( ) ,
593+ "Rust client: expected at least one image"
594+ ) ;
595+ assert ! (
596+ reply. images. iter( ) . any( |name| name. contains( & image) ) ,
597+ "Rust client: expected test image {image} in list, got: {:?}" ,
598+ reply. images
599+ ) ;
600+
601+ // Cross-check: varlinkctl (best-effort)
602+ if varlinkctl_is_compatible ( ) {
603+ let bck = get_bck_command ( ) ?;
604+ let output =
605+ xshell:: cmd!( sh, "varlinkctl call exec:{bck} io.bootc.vk.images.List" ) . read ( ) ?;
606+ let parsed: serde_json:: Value = serde_json:: from_str ( & output) ?;
607+ let varlinkctl_images = parsed
608+ . get ( "images" )
609+ . and_then ( |v| v. as_array ( ) )
610+ . expect ( "varlinkctl response missing 'images' array" ) ;
611+ let varlinkctl_names: Vec < & str > = varlinkctl_images
612+ . iter ( )
613+ . filter_map ( |v| v. as_str ( ) )
614+ . collect ( ) ;
615+
616+ // Both should see the same set of images
617+ assert_eq ! (
618+ reply. images. len( ) ,
619+ varlinkctl_names. len( ) ,
620+ "image count mismatch: Rust client={:?}, varlinkctl={:?}" ,
621+ reply. images,
622+ varlinkctl_names
623+ ) ;
624+ for img in & reply. images {
625+ assert ! (
626+ varlinkctl_names. contains( & img. as_str( ) ) ,
627+ "varlinkctl missing image {img} that Rust client returned"
628+ ) ;
629+ }
630+ eprintln ! (
631+ "varlinkctl cross-check passed ({} images)" ,
632+ reply. images. len( )
633+ ) ;
634+ } else {
635+ eprintln ! (
636+ "note: skipping varlinkctl cross-check (varlinkctl missing or incompatible \
637+ with this zlink server, see https://github.com/z-galaxy/zlink/issues/233)"
638+ ) ;
639+ }
640+
641+ Ok ( ( ) )
642+ }
643+ integration_test ! ( test_varlink_images_list_crosscheck) ;
644+
524645/// Verify that `varlinkctl call` against the images List method works.
646+ ///
647+ /// Skipped when `varlinkctl` is not compatible with the zlink server
648+ /// (e.g. systemd < 258 due to <https://github.com/z-galaxy/zlink/issues/233>).
525649fn test_varlink_exec_varlinkctl ( ) -> Result < ( ) > {
650+ if !varlinkctl_is_compatible ( ) {
651+ eprintln ! (
652+ "note: skipping test_varlink_exec_varlinkctl (varlinkctl missing or incompatible, \
653+ see https://github.com/z-galaxy/zlink/issues/233)"
654+ ) ;
655+ return Ok ( ( ) ) ;
656+ }
526657 let sh = shell ( ) ?;
527658 let bck = get_bck_command ( ) ?;
528659 let output = xshell:: cmd!( sh, "varlinkctl call exec:{bck} io.bootc.vk.images.List" ) . read ( ) ?;
@@ -536,7 +667,16 @@ fn test_varlink_exec_varlinkctl() -> Result<()> {
536667integration_test ! ( test_varlink_exec_varlinkctl) ;
537668
538669/// Test that `varlinkctl introspect` shows all three interface names.
670+ ///
671+ /// Skipped when `varlinkctl` is not compatible with the zlink server.
539672fn test_varlink_introspect_varlinkctl ( ) -> Result < ( ) > {
673+ if !varlinkctl_is_compatible ( ) {
674+ eprintln ! (
675+ "note: skipping test_varlink_introspect_varlinkctl (varlinkctl missing or incompatible, \
676+ see https://github.com/z-galaxy/zlink/issues/233)"
677+ ) ;
678+ return Ok ( ( ) ) ;
679+ }
540680 let sh = shell ( ) ?;
541681 let bck = get_bck_command ( ) ?;
542682 let output = xshell:: cmd!( sh, "varlinkctl introspect exec:{bck} io.bootc.vk.images" ) . read ( ) ?;
0 commit comments