1111
1212REPO_ROOT = Path (__file__ ).resolve ().parent .parent
1313
14+ # The dependency stamps carry the GMP the artifact was built against, so these
15+ # have to match what _samba4x.sh computes. A mismatch makes the build scripts
16+ # treat every dependency as unbuilt and try to download it.
17+ NETTLE_SYSTEM_GMP_STAMP = ".stamp-nettle-3.10.1-system-6.1.0"
18+ NETTLE_BUNDLED_GMP_STAMP = ".stamp-nettle-3.10.1-bundled-6.3.0"
19+ GNUTLS_SYSTEM_GMP_STAMP = (
20+ ".stamp-gnutls-3.8.5-system-nettle-oaep-no-thread-local-system-6.1.0"
21+ )
22+
1423
1524class Samba4XBuildScriptTests (unittest .TestCase ):
1625 def make_executable (self , path : Path , text : str ) -> None :
@@ -94,6 +103,26 @@ def prepare_fake_toolchain(self, out: Path, triple: str) -> None:
94103 """
95104 ),
96105 )
106+ # nm drives the __gmpn_zero_p probe that decides whether the GMP in the
107+ # NetBSD tree is usable. It cannot join the loop below: those exit 0
108+ # without printing, which would read as a GMP missing every symbol.
109+ self .make_executable (
110+ tools / f"{ triple } -nm" ,
111+ textwrap .dedent (
112+ """\
113+ #!/bin/sh
114+ if [ "${TEST_NM_RC:-0}" != "0" ]; then
115+ exit "$TEST_NM_RC"
116+ fi
117+ printf '0000 T __gmpn_add_n\\ n'
118+ case "${TEST_NM_ZERO_P:-1}" in
119+ 1) printf '0000 T __gmpn_zero_p\\ n' ;;
120+ undefined) printf ' U __gmpn_zero_p\\ n' ;;
121+ esac
122+ exit 0
123+ """
124+ ),
125+ )
97126 for name in ("ar" , "ranlib" , "readelf" , "strip" ):
98127 self .make_executable (tools / f"{ triple } -{ name } " , "#!/bin/sh\n exit 0\n " )
99128
@@ -222,12 +251,12 @@ def prepare_fake_netbsd_inputs(self, root: Path, *, lane: str) -> dict[str, Path
222251 self .make_file (build_src / "external" / "lgpl3" / "gmp" / "lib" / "libgmp" / "arch" / gmp_arch / "gmp.h" )
223252
224253 deps = samba_build / "deps"
225- self .make_file (deps / ".stamp-nettle-3.10.1-system-gmp" )
254+ self .make_file (deps / NETTLE_SYSTEM_GMP_STAMP )
226255 self .make_file (deps / "lib" / "libnettle.a" )
227256 self .make_file (deps / "lib" / "libhogweed.a" )
228257 self .make_file (deps / ".stamp-libtasn1-4.20.0" )
229258 self .make_file (deps / "lib" / "libtasn1.a" )
230- self .make_file (deps / ".stamp-gnutls-3.8.5-system-nettle-oaep-no-thread-local" )
259+ self .make_file (deps / GNUTLS_SYSTEM_GMP_STAMP )
231260 self .make_file (deps / "lib" / "libgnutls.a" )
232261 self .make_file (deps / "lib" / "pkgconfig" / "gnutls.pc" , "Libs: -L${libdir} -lgnutls\n " )
233262
@@ -239,9 +268,25 @@ def prepare_fake_netbsd_inputs(self, root: Path, *, lane: str) -> dict[str, Path
239268 "samba_stage" : samba_stage ,
240269 }
241270
271+ def offline_dependency_urls (self , root : Path ) -> dict [str , str ]:
272+ """Point every dependency download at a file that does not exist.
273+
274+ The fixture pre-creates the dependency stamps so nothing is downloaded,
275+ but a stamp name that stops matching what the build scripts compute
276+ would silently turn these tests into real fetches from ftp.gnu.org. A
277+ missing file:// URL fails curl at once instead.
278+ """
279+ return {
280+ "SAMBA4X_GMP_URL" : f"file://{ root / 'no-such-gmp.tar.xz' } " ,
281+ "SAMBA4X_NETTLE_URL" : f"file://{ root / 'no-such-nettle.tar.gz' } " ,
282+ "SAMBA4X_LIBTASN1_URL" : f"file://{ root / 'no-such-libtasn1.tar.gz' } " ,
283+ "SAMBA4X_GNUTLS_URL" : f"file://{ root / 'no-such-gnutls.tar.xz' } " ,
284+ }
285+
242286 def env_for_lane (self , root : Path , lane : str , capture : Path ) -> dict [str , str ]:
243287 paths = self .prepare_fake_netbsd_inputs (root , lane = lane )
244288 env = os .environ .copy ()
289+ env .update (self .offline_dependency_urls (root ))
245290 env .update (
246291 {
247292 "TC_ENV_FILE" : "/dev/null" ,
@@ -724,6 +769,139 @@ def test_generation_fails_when_independent_probe_disagrees(self) -> None:
724769 )
725770 self .assertFalse ((output_dir / "samba4x-4.24.3-netbsd4be.answers" ).exists ())
726771
772+ def test_usable_target_gmp_is_adopted_and_stamped_as_system (self ) -> None :
773+ with tempfile .TemporaryDirectory () as tmp :
774+ root = Path (tmp )
775+ capture = root / "configure-args.txt"
776+ env = self .env_for_lane (root , "netbsd7" , capture )
777+
778+ result = self .run_wrapper ("samba4x.sh" , env )
779+
780+ self .assertEqual (result .returncode , 0 , result .stdout + result .stderr )
781+ log = Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text ()
782+ self .assertIn ("Using NetBSD target GMP" , log )
783+ self .assertIn ("nettle 3.10.1 already built." , log )
784+ self .assertIn ("GnuTLS 3.8.5 already built." , log )
785+ deps = Path (env ["SAMBA4X_NETBSD7_BUILD" ]) / "deps"
786+ self .assertTrue ((deps / NETTLE_SYSTEM_GMP_STAMP ).exists ())
787+ self .assertTrue ((deps / GNUTLS_SYSTEM_GMP_STAMP ).exists ())
788+ pc = (deps / "lib" / "pkgconfig" / "gmp.pc" ).read_text ()
789+ self .assertIn ("Version: 6.1.0" , pc )
790+ self .assertNotIn ("Version: 6.3.0" , pc )
791+
792+ def test_target_gmp_without_zero_p_falls_back_to_bundled_gmp (self ) -> None :
793+ with tempfile .TemporaryDirectory () as tmp :
794+ root = Path (tmp )
795+ capture = root / "configure-args.txt"
796+ env = self .env_for_lane (root , "netbsd7" , capture )
797+ env ["TEST_NM_ZERO_P" ] = "0"
798+
799+ result = self .run_wrapper ("samba4x.sh" , env )
800+
801+ self .assertNotEqual (result .returncode , 0 )
802+ log = Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text ()
803+ self .assertIn ("lacks __gmpn_zero_p" , log )
804+ self .assertIn ("NetBSD target GMP is unavailable; building GMP 6.3.0." , log )
805+ self .assertNotIn ("Using NetBSD target GMP" , log )
806+ self .assertFalse (capture .exists ())
807+
808+ def test_unusable_nm_leaves_target_gmp_adoption_untouched (self ) -> None :
809+ cases = ("nm exits nonzero" , "nm missing" )
810+ for label in cases :
811+ with self .subTest (label = label ):
812+ with tempfile .TemporaryDirectory () as tmp :
813+ root = Path (tmp )
814+ capture = root / "configure-args.txt"
815+ env = self .env_for_lane (root , "netbsd7" , capture )
816+ if label == "nm exits nonzero" :
817+ env ["TEST_NM_RC" ] = "1"
818+ else :
819+ tools = Path (env ["BUILD_OUT" ]) / "tools" / "bin"
820+ (tools / "arm--netbsdelf-nm" ).unlink ()
821+
822+ result = self .run_wrapper ("samba4x.sh" , env )
823+
824+ self .assertEqual (
825+ result .returncode , 0 , result .stdout + result .stderr
826+ )
827+ log = Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text ()
828+ self .assertIn ("Using NetBSD target GMP" , log )
829+ self .assertNotIn ("lacks __gmpn_zero_p" , log )
830+ self .assertIn ("nettle 3.10.1 already built." , log )
831+
832+ def test_switching_gmp_provider_invalidates_nettle_and_gnutls (self ) -> None :
833+ with tempfile .TemporaryDirectory () as tmp :
834+ root = Path (tmp )
835+ capture = root / "configure-args.txt"
836+ env = self .env_for_lane (root , "netbsd7" , capture )
837+ deps = Path (env ["SAMBA4X_NETBSD7_BUILD" ]) / "deps"
838+ # The fixture is stamped for the adopted target GMP. Reject that GMP
839+ # and present the bundled one as already built, so the run reaches
840+ # the nettle decision with the other provider selected.
841+ env ["TEST_NM_ZERO_P" ] = "0"
842+ self .make_file (deps / ".stamp-gmp-6.3.0" )
843+ self .make_file (deps / "lib" / "libgmp.a" )
844+
845+ result = self .run_wrapper ("samba4x.sh" , env )
846+
847+ self .assertNotEqual (result .returncode , 0 )
848+ log = Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text ()
849+ self .assertIn ("GMP 6.3.0 already built." , log )
850+ self .assertNotIn ("nettle 3.10.1 already built." , log )
851+ self .assertNotIn ("GnuTLS 3.8.5 already built." , log )
852+ self .assertTrue ((deps / NETTLE_SYSTEM_GMP_STAMP ).exists ())
853+ self .assertFalse ((deps / NETTLE_BUNDLED_GMP_STAMP ).exists ())
854+ self .assertFalse (capture .exists ())
855+
856+ def test_undefined_zero_p_reference_does_not_count_as_a_definition (self ) -> None :
857+ with tempfile .TemporaryDirectory () as tmp :
858+ root = Path (tmp )
859+ capture = root / "configure-args.txt"
860+ env = self .env_for_lane (root , "netbsd7" , capture )
861+ # nm lists "U __gmpn_zero_p" for a member that only references the
862+ # symbol. Matching the bare name would read that as a definition.
863+ env ["TEST_NM_ZERO_P" ] = "undefined"
864+
865+ result = self .run_wrapper ("samba4x.sh" , env )
866+
867+ self .assertNotEqual (result .returncode , 0 )
868+ log = Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text ()
869+ self .assertIn ("lacks __gmpn_zero_p" , log )
870+ self .assertNotIn ("Using NetBSD target GMP" , log )
871+
872+ def test_adopting_target_gmp_drops_the_bundled_gmp_stamp (self ) -> None :
873+ with tempfile .TemporaryDirectory () as tmp :
874+ root = Path (tmp )
875+ capture = root / "configure-args.txt"
876+ env = self .env_for_lane (root , "netbsd7" , capture )
877+ deps = Path (env ["SAMBA4X_NETBSD7_BUILD" ]) / "deps"
878+ # An earlier run built the bundled GMP. Both providers install to
879+ # the same libgmp.a, so adopting the target one has to retire that
880+ # stamp; otherwise a later rejection reuses the adopted archive
881+ # while calling it the bundled build.
882+ self .make_file (deps / ".stamp-gmp-6.3.0" )
883+ self .make_file (deps / "lib" / "libgmp.a" , "bundled gmp\n " )
884+
885+ adopt = self .run_wrapper ("samba4x.sh" , env )
886+
887+ self .assertEqual (adopt .returncode , 0 , adopt .stdout + adopt .stderr )
888+ self .assertIn (
889+ "Using NetBSD target GMP" ,
890+ Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text (),
891+ )
892+ self .assertFalse ((deps / ".stamp-gmp-6.3.0" ).exists ())
893+
894+ # The target GMP is unusable from here on, so the bundled build has
895+ # to run again rather than claim it is already done.
896+ env ["TEST_NM_ZERO_P" ] = "0"
897+
898+ reject = self .run_wrapper ("samba4x.sh" , env )
899+
900+ self .assertNotEqual (reject .returncode , 0 )
901+ log = Path (env ["SAMBA4X_NETBSD7_LOG" ]).read_text ()
902+ self .assertIn ("NetBSD target GMP is unavailable" , log )
903+ self .assertNotIn ("GMP 6.3.0 already built." , log )
904+
727905
728906if __name__ == "__main__" :
729907 unittest .main ()
0 commit comments