test_launcher_build.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. #!/usr/bin/env python3
  2. """Offline tests for the build step (`cmd_build`) of the launcher.
  3. No Docker, SSH or network: the only thing driven here is the exclude list of the
  4. rsync that stages the tree on the workers, because that one runs with `--delete`
  5. against a copy of the repository and a wrong pattern removes files from it.
  6. """
  7. import os
  8. import re
  9. import shutil
  10. import subprocess
  11. import tempfile
  12. import unittest
  13. from pathlib import Path
  14. ROOT = Path(__file__).resolve().parents[1]
  15. HAVE_RSYNC = shutil.which("rsync") is not None
  16. def build_source():
  17. """The body of cmd_build() from start.sh."""
  18. source = (ROOT / "start.sh").read_text()
  19. build = source[source.index("cmd_build()"):]
  20. return build[:build.index("\n}\n")]
  21. def rsync_excludes(which="tp4"):
  22. """Excludes of one rsync arm. `tp4` is start-tp4.sh; `start` is ./start.sh."""
  23. build = build_source()
  24. begin, end = {
  25. "tp4": ("# rsync-tp4", "# end-rsync-tp4"),
  26. "start": ("# rsync-start", "# end-rsync-start"),
  27. }[which]
  28. arm = build.split(begin, 1)[1].split(end, 1)[0]
  29. return re.findall(r"--exclude '([^']+)'", arm)
  30. def launcher_source():
  31. """start.sh from the variable defaults through the helper functions, no dispatch."""
  32. source = (ROOT / "start.sh").read_text()
  33. start = source.index("HEAD_IP=")
  34. end = source.index('case "$CMD" in')
  35. return "set -euo pipefail\n" + source[start:end]
  36. def shell(code, directory, settings=None):
  37. env = {"PATH": os.environ["PATH"], "HOME": os.environ["HOME"], "ROOT": str(ROOT)}
  38. for name in ("MODEL_DIR", "COMMON_MODEL", "SSH_IDENTITY", "NCCL_HOST_DIR",
  39. "STATE_DIR", "LOG_DIR", "WORKER_DIR", "WORKER_ENGRAM_DIR", "ENGRAM_DIR"):
  40. env[name] = str(directory / name)
  41. env.update(settings or {})
  42. result = subprocess.run(["bash", "-c", launcher_source() + "\n" + code],
  43. env=env, cwd=ROOT, text=True, capture_output=True, timeout=30)
  44. assert result.returncode == 0, result.stdout + result.stderr
  45. return result.stdout
  46. def check(result):
  47. assert result.returncode == 0, result.stdout + result.stderr
  48. return result.stdout
  49. def rsync(source, destination):
  50. args = ["rsync", "-aH", "--delete"]
  51. for pattern in rsync_excludes():
  52. args += ["--exclude", pattern]
  53. args += [f"{source}/", f"{destination}/"]
  54. result = subprocess.run(args, text=True, capture_output=True, timeout=60)
  55. assert result.returncode == 0, result.stdout + result.stderr
  56. return result
  57. class BuildTargetTests(unittest.TestCase):
  58. """`IMAGE` is whatever the profile names, so `build` must compile that recipe."""
  59. def test_build_compiles_the_configured_dockerfile(self):
  60. with tempfile.TemporaryDirectory() as tmp:
  61. Path(tmp, "SSH_IDENTITY.pub").write_text("ssh-ed25519 AAAA test\n")
  62. log = Path(tmp, "docker.log")
  63. shell(r"""
  64. BUILD_DOCKERFILE=Dockerfile.canary
  65. BUILD_ARGS="--build-arg PIP_INDEX=https://mirror.invalid/simple"
  66. WORKER_HOSTS=()
  67. docker() { printf '%s\n' "$*" >> "$LOG"
  68. case "$1 $2" in "image inspect") echo arm64;; esac; }
  69. cmd_build
  70. """, Path(tmp), {"LOG": str(log), "DSV41_LAUNCHER": "tp4"})
  71. calls = [line for line in log.read_text().splitlines() if line.startswith("build ")]
  72. self.assertEqual(len(calls), 1, log.read_text())
  73. self.assertIn(f"-f {ROOT}/Dockerfile.canary", calls[0])
  74. self.assertIn("--build-arg PIP_INDEX=https://mirror.invalid/simple", calls[0])
  75. def test_worker_builds_the_same_dockerfile(self):
  76. build = build_source()
  77. self.assertIn('docker build -f "$ROOT/$dockerfile" -t "$IMAGE"', build)
  78. workers = build[build.index("for h in \"${WORKER_HOSTS[@]}\""):]
  79. self.assertIn('docker build -f $(printf \'%q\' "$dockerfile")', workers)
  80. def test_start_sh_builds_the_default_dockerfile(self):
  81. """Without DSV41_LAUNCHER, BUILD_DOCKERFILE and BUILD_ARGS are ignored."""
  82. with tempfile.TemporaryDirectory() as tmp:
  83. Path(tmp, "SSH_IDENTITY.pub").write_text("ssh-ed25519 AAAA test\n")
  84. log = Path(tmp, "docker.log")
  85. shell(r"""
  86. BUILD_DOCKERFILE=Dockerfile.canary
  87. BUILD_ARGS="--build-arg PIP_INDEX=https://mirror.invalid/simple"
  88. WORKER_HOSTS=()
  89. docker() { printf '%s\n' "$*" >> "$LOG"
  90. case "$1 $2" in "image inspect") echo arm64;; esac; }
  91. cmd_build
  92. """, Path(tmp), {"LOG": str(log)})
  93. calls = [line for line in log.read_text().splitlines() if line.startswith("build ")]
  94. self.assertEqual(calls, [f"build -t dsv41-3x-spark:local {ROOT}"])
  95. def test_a_dockerfile_outside_the_repository_is_rejected(self):
  96. """The workers only see what the rsync staged, so an absolute path cannot work."""
  97. build = build_source()
  98. self.assertIn("BUILD_DOCKERFILE must be inside the repository", build)
  99. @unittest.skipUnless(HAVE_RSYNC, "rsync is not installed")
  100. class RsyncExcludesTests(unittest.TestCase):
  101. """cmd_build must not exclude nested directories that share a name with a root artifact."""
  102. def test_a_nested_models_directory_reaches_the_worker(self):
  103. with tempfile.TemporaryDirectory() as tmp:
  104. src, dst = Path(tmp, "src"), Path(tmp, "dst")
  105. staged = "vendor/sglang/python/sglang/srt/models"
  106. (src / staged).mkdir(parents=True)
  107. (src / staged / "deepseek_v4.py").write_text("branch")
  108. (dst / staged).mkdir(parents=True)
  109. (dst / staged / "deepseek_v4.py").write_text("stale")
  110. (src / "models").mkdir()
  111. (src / "models" / "shard.safetensors").write_text("weights")
  112. rsync(src, dst)
  113. self.assertEqual((dst / staged / "deepseek_v4.py").read_text(), "branch",
  114. "a nested models/ directory never reached the worker")
  115. self.assertFalse((dst / "models").exists(),
  116. "the checkpoint directory at the repository root was copied")
  117. def test_every_exclude_is_anchored(self):
  118. """A bare name matches any path component; the TP4 arm anchors root-level artifacts."""
  119. for pattern in rsync_excludes("tp4"):
  120. if pattern in (".env", ".env.tp4"):
  121. continue # exact filenames, meant to be protected everywhere
  122. self.assertTrue(pattern.startswith("/"), f"unanchored exclude: {pattern}")
  123. def test_start_sh_keeps_its_excludes(self):
  124. self.assertEqual(
  125. rsync_excludes("start"),
  126. [".env", ".env.tp4", "state", "state-tp4", "logs", "logs-tp4", "models", "engram"],
  127. )
  128. class ExtraContainerEnvTests(unittest.TestCase):
  129. """EXTRA_CONTAINER_ENV reaches the head and every worker, and a key the launcher already
  130. passes is replaced in place instead of repeated."""
  131. def args(self, settings):
  132. with tempfile.TemporaryDirectory() as tmp:
  133. d = Path(tmp)
  134. Path(d, "MODEL_DIR").mkdir()
  135. out = shell(r"""
  136. head=()
  137. docker_common_args head "$HEAD_IP" 3
  138. printf '%s\0' "${head[@]}"
  139. printf 'SPLIT\0'
  140. capture() { printf '%s\0' "$@"; }
  141. eval "capture $(worker_env_lines 10.0.0.2 3 1)
  142. end-of-command"
  143. """, d, settings).split("\0")
  144. split = out.index("SPLIT")
  145. pairs = lambda a: [a[i + 1] for i, x in enumerate(a[:-1]) if x == "-e"]
  146. return pairs(out[:split]), pairs(out[split + 1:])
  147. def test_start_sh_ignores_the_tp4_env_channel(self):
  148. head, worker = self.args({"EXTRA_CONTAINER_ENV": "DSV41_MOE_B12X_NEXT=1"})
  149. for env in (head, worker):
  150. self.assertNotIn("DSV41_LAUNCHER=tp4", env)
  151. self.assertFalse(any(e.startswith("DSV41_SHARED_PAD_K=") for e in env))
  152. self.assertNotIn("DSV41_MOE_B12X_NEXT=1", env)
  153. def test_unset_changes_nothing(self):
  154. head, worker = self.args({"DSV41_LAUNCHER": "tp4"})
  155. self.assertIn("DSV41_WO_A_W8=0", head)
  156. self.assertIn("DSV41_WO_A_W8=0", worker)
  157. self.assertIn("DSV41_SHARED_PAD_K=0", head)
  158. self.assertIn("DSV41_SHARED_PAD_K=0", worker)
  159. self.assertIn("DSV41_LAUNCHER=tp4", head)
  160. self.assertIn("DSV41_LAUNCHER=tp4", worker)
  161. def test_set_keys_override_once_and_new_keys_are_added(self):
  162. extra = "DSV41_WO_A_W8=1 DSV41_SHARED_PAD_K=1 DSV41_MOE_B12X_NEXT=1 SGLANG_DSPARK_FOLDED_SAMPLING=2"
  163. head, worker = self.args({"DSV41_LAUNCHER": "tp4", "EXTRA_CONTAINER_ENV": extra})
  164. for env in (head, worker):
  165. for kv in extra.split():
  166. self.assertEqual(sum(e.split("=", 1)[0] == kv.split("=", 1)[0] for e in env), 1, kv)
  167. self.assertIn(kv, env)
  168. def test_a_malformed_entry_is_refused(self):
  169. with tempfile.TemporaryDirectory() as tmp:
  170. d = Path(tmp)
  171. Path(d, "MODEL_DIR").mkdir()
  172. with self.assertRaises(AssertionError):
  173. shell('head=(); docker_common_args head "$HEAD_IP" 3', d,
  174. {"DSV41_LAUNCHER": "tp4", "EXTRA_CONTAINER_ENV": "NOTAPAIR"})
  175. class CanaryDockerfileTests(unittest.TestCase):
  176. """Dockerfile.canary installs two wheels from PyPI; an offline site needs a mirror."""
  177. def test_the_pip_index_is_overridable(self):
  178. text = (ROOT / "Dockerfile.canary").read_text()
  179. self.assertIn("ARG PIP_INDEX=https://pypi.org/simple", text)
  180. self.assertIn('-i "$PIP_INDEX"', text)
  181. class DockerfilePathTests(unittest.TestCase):
  182. """Every COPY source exists in the repository and is not excluded from the build context;
  183. every COPY --from=fetch source is a directory scripts/fetch_runtime.sh writes."""
  184. FETCHED = {"/fetched/sglang-canary/python", "/fetched/b12x/b12x", "/fetched/b12x/LICENSE",
  185. "/fetched/b12x_next/b12x_next", "/fetched/b12x_next/LICENSE"}
  186. def copies(self, name):
  187. text = (ROOT / name).read_text().replace("\\\n", " ")
  188. for line in text.splitlines():
  189. words = line.split()
  190. if words[:1] == ["COPY"]:
  191. yield words[1:-1]
  192. def test_sources_exist(self):
  193. ignored = [p.strip().rstrip("/") for p in (ROOT / ".dockerignore").read_text().splitlines()
  194. if p.strip() and not p.startswith("#")]
  195. fetch = (ROOT / "scripts/fetch_runtime.sh").read_text()
  196. for name in ("Dockerfile", "Dockerfile.canary", "Dockerfile.canary-roce"):
  197. for srcs in self.copies(name):
  198. if srcs and srcs[0] == "--from=fetch":
  199. for src in srcs[1:]:
  200. self.assertIn(src, self.FETCHED, f"{name}: {src}")
  201. self.assertIn(src.split("/")[2], fetch, f"{name}: {src}")
  202. continue
  203. for src in srcs:
  204. self.assertTrue((ROOT / src).exists(), f"{name}: COPY {src} does not exist")
  205. self.assertNotIn(src.split("/")[0], ignored, f"{name}: {src} is in .dockerignore")
  206. def test_base_images_are_pinned_alike(self):
  207. froms = set()
  208. for name in ("Dockerfile", "Dockerfile.canary", "Dockerfile.canary-roce"):
  209. for line in (ROOT / name).read_text().splitlines():
  210. if line.startswith("FROM "):
  211. froms.add(line.split()[1])
  212. self.assertEqual(len(froms), 1, froms)
  213. self.assertRegex(froms.pop(), r"@sha256:[0-9a-f]{64}$")
  214. if __name__ == "__main__":
  215. unittest.main(verbosity=2)