@@ -19,11 +19,9 @@ order, on the ship tools/ship talks to: $JIBB_PIER, or /root/urbit/zod
19(tools/ship also reads $JIBB_CLICK and $JIBB_SHIP_LOG). When that ship
19(tools/ship also reads $JIBB_CLICK and $JIBB_SHIP_LOG). When that ship
20isn't running, tools/check starts it first and stops it again at the end.
20isn't running, tools/check starts it first and stops it again at the end.
22−Each check is one command, run from the repo root. It passes when it
22+Each check is one command, run from the repo root, and passes when it
23−exits 0 and, for the few that print their verdict rather than exit with
24−it, when its output matches what the list expects. Output is kept and
25−printed only for a check that fails (all of it with -v); --logs DIR
23+exits 0. Output is kept and printed only for a check that fails (all of
26−keeps every check's output in DIR/<name>.log.
24+it with -v); --logs DIR keeps every check's output in DIR/<name>.log.
28Exit status: 0 when every check ran and passed, 1 when a check failed,
26Exit status: 0 when every check ran and passed, 1 when a check failed,
292 when the checks couldn't run (an unknown name, a ship that won't start).
272 when the checks couldn't run (an unknown name, a ship that won't start).
@@ -47,11 +45,10 @@ SHIP = os.path.join('tools', 'ship')
49class Check:
47class Check:
50− def __init__(self, name, group, command, expect=None):
48+ def __init__(self, name, group, command):
51 self.name = name
49 self.name = name
52 self.group = group # 'eval' or 'ship'
50 self.group = group # 'eval' or 'ship'
53 self.command = command # argv, run from ROOT
51 self.command = command # argv, run from ROOT
54− self.expect = expect # a regex stdout must match in full
56 def shown(self):
53 def shown(self):
57 return ' '.join(c if re.match(r'^[\w./=-]+$', c) else "'%s'" % c
54 return ' '.join(c if re.match(r'^[\w./=-]+$', c) else "'%s'" % c
@@ -64,11 +61,6 @@ def tool(name, group='eval', *args):
64 return Check(base, group, [os.path.join('tools', name)] + list(args))
61 return Check(base, group, [os.path.join('tools', name)] + list(args))
67−def hoon(name, *args, expect):
68− """A check that runs tools/hoon and compares what it prints."""
69− return Check(name, 'eval', ['tools/hoon'] + list(args), expect=expect)
72# Every check, in the order they start: the eval group, slowest first,
64# Every check, in the order they start: the eval group, slowest first,
73# then the ship group, in the order they must run. Times are from one
65# then the ship group, in the order they must run. Times are from one
74# run on four CPUs (2026-11-03).
66# run on four CPUs (2026-11-03).
@@ -88,17 +80,6 @@ CHECKS = [
88 for i in range(1, ARM_SHARDS + 1)
80 for i in range(1, ARM_SHARDS + 1)
90 Check('my-app-copies', 'eval', ['tools/test-arms', '--copies']),
82 Check('my-app-copies', 'eval', ['tools/test-arms', '--copies']),
91− # These print their verdict and exit 0 either way.
92− hoon('check-spans-test', 'check-spans-test', expect=r'<<>>\n'),
93− hoon('zing-hoon-reference', '-f',
94− 'tools/testdata/zing-hoon-reference.hoon',
95− expect=r"'Hoon flattening reference: \d+ cases passed'\n"),
96− hoon('lead-late-hoon-reference', '-f',
97− 'tools/testdata/lead-late-hoon-reference.hoon',
98− expect=r"'Hoon lead/late reference: \d+ comparisons passed'\n"),
99− hoon('succ-hoon-reference', '-f',
100− 'tools/testdata/succ-hoon-reference.hoon',
101− expect=r"'Hoon successor reference: \d+ comparisons passed'\n"),
102 Check('doc-blocks', 'eval', ['tools/doc-blocks', 'docs/spec']),
83 Check('doc-blocks', 'eval', ['tools/doc-blocks', 'docs/spec']),
103 tool('testdata/clay-fixtures-selftest'),
84 tool('testdata/clay-fixtures-selftest'),
104 # The ship group. ship-selftest leaves its fixture files on the
85 # The ship group. ship-selftest leaves its fixture files on the
@@ -139,12 +120,11 @@ def run(check, timeout, env):
139 """Run one check; a Result."""
120 """Run one check; a Result."""
140 res = Result(check)
121 res = Result(check)
141 start = time.time()
122 start = time.time()
142− with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
123+ with tempfile.TemporaryFile() as out:
144 proc = subprocess.Popen(
125 proc = subprocess.Popen(
145 check.command, cwd=ROOT, env=env, stdin=subprocess.DEVNULL,
126 check.command, cwd=ROOT, env=env, stdin=subprocess.DEVNULL,
147− stderr=err if check.expect is not None else subprocess.STDOUT,
127+ stdout=out, stderr=subprocess.STDOUT,
148 start_new_session=True)
128 start_new_session=True)
149 except OSError as e:
129 except OSError as e:
150 res.why = 'could not run %s: %s' % (check.command[0], e)
130 res.why = 'could not run %s: %s' % (check.command[0], e)
@@ -162,18 +142,12 @@ def run(check, timeout, env):
162 _running.discard(proc.pid)
142 _running.discard(proc.pid)
163 kill(proc.pid) # anything it left running, such as urbit eval
143 kill(proc.pid) # anything it left running, such as urbit eval
164 out.seek(0)
144 out.seek(0)
166− stdout = out.read().decode('utf-8', 'replace')
145+ res.output = out.read().decode('utf-8', 'replace')
167− stderr = err.read().decode('utf-8', 'replace')
168 res.seconds = time.time() - start
146 res.seconds = time.time() - start
169− res.output = stdout + stderr
170 if res.why:
147 if res.why:
171 return res
148 return res
172 if res.status != 0:
149 if res.status != 0:
173 res.why = 'exit status %d' % res.status
150 res.why = 'exit status %d' % res.status
174− elif check.expect is not None and \
175− not re.fullmatch(check.expect, stdout):
176− res.why = "it didn't print the verdict that means a pass"
178 res.ok = True
152 res.ok = True
179 return res
153 return res