dotfiles/claude/tests/statusline.isaacaudet.overflow_test.py
Jonny Barnes c88a291047
Wrap the status line rather than drop the per-model limit
The measured ladder tried every single-line variant before considering a
second line, so whenever line one plus the full group did not fit — from
around 92 usable columns upwards, depending on how long the branch, cwd and
model names are — it emitted rl_bare (5h and 7d only) and silently dropped
the per-model (Fable) bar that is the main reason the group is worth
rendering. On a 110-column laptop the limit was invisible.

Reorder so a second line beats losing that bar: one line rich/mid/lean, then
wrap, and rl_bare only when WRAP_NARROW is false. Reset times and
extra-usage credits are still given up rather than wrapped for, which makes
the rendered content non-monotone in width; the comment on the ladder spells
that out.

Also stop the tests writing fixtures to the caches the live status line
reads. ~/.claude/statusline.sh is a symlink to the script, so a test run was
visibly rendering fabricated usage — a Fable bar at 10%, extra-usage credits
of $1234.56/$2000.00 — in whatever session happened to be open, and a run
killed before its trap fired would have left that in place for an hour. The
cache directory is now $STATUSLINE_CACHE_DIR (default /tmp/claude) and each
suite points it at a temporary directory, which also removes the
backup/restore dance and the deletion of the live git-status cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 18:54:02 +01:00

106 lines
5.1 KiB
Python
Executable file

#!/usr/bin/env python3
"""Exhaustive overflow sweep for claude/statusline.isaacaudet.sh.
Covers the Isaac Audet-inspired status line (the variant symlinked from
~/.claude/statusline.sh); the sibling statusline.burnrate.sh and
statusline.original.sh are not covered.
Asserts the two hard invariants across a grid of terminal widths, branch-name
lengths and model-name lengths:
1. no rendered line exceeds the usable width
2. never more than two lines
Catches the case tier-based sizing missed: content whose width depends on
session state (branch, cwd, model display name) rather than on the terminal.
"""
import subprocess, re, unicodedata, sys, os, itertools, tempfile, shutil
SCRIPT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"statusline.isaacaudet.sh")
BRANCHES = ["main", "feature/some-longer-branch-name",
"feature/an-extremely-long-branch-name-that-keeps-going-and-going"]
MODELS = ["Opus 5", "Opus 5 (1M context)"]
WIDTHS = range(64, 245, 10)
# The cwd basename is rendered at the widest layouts and is capped by
# CWD_MAX_LEN; vary it, since a long project directory was one of the ways
# line one used to overflow.
CWDS = ["proj", "a-really-quite-long-project-directory-name-that-someone-might-have"]
# Extra-usage credits add ~30 columns and used to overflow line two on their own.
EXTRA = {
"off": '"extra_usage":{"is_enabled":false}',
"on": '"extra_usage":{"is_enabled":true,"monthly_limit":200000,'
'"used_credits":123456,"utilization":62.0}',
}
# Throwaway cache directory, inherited by every rendered subprocess: the live
# cache belongs to the running status line (~/.claude/statusline.sh symlinks to
# the script under test) and a fixture written there would be shown as real
# usage until it expired.
CACHE_DIR = tempfile.mkdtemp()
os.environ["STATUSLINE_CACHE_DIR"] = CACHE_DIR
CACHE = os.path.join(CACHE_DIR, "statusline-usage-cache.json")
FIXTURE = ('{"five_hour":{"utilization":5.0,"resets_at":"2026-08-27T15:40:00+00:00"},'
'"seven_day":{"utilization":7.0,"resets_at":"2026-08-28T02:00:00+00:00"},%s,'
'"limits":[{"kind":"weekly_scoped","percent":10,'
'"scope":{"model":{"display_name":"Fable"}},"is_active":true}]}')
def width_of(s):
return sum(2 if unicodedata.east_asian_width(c) in "WF" else 1
for c in re.sub(r"\033\[[0-9;]*m", "", s))
def main():
pad = int(subprocess.run(
["jq", "-r", ".statusLine.padding // 0", os.path.expanduser("~/.claude/settings.json")],
capture_output=True, text=True).stdout.strip() or 0)
root = tempfile.mkdtemp()
fails, checked = [], 0
try:
for cwd_name, extra_name in itertools.product(CWDS, EXTRA):
with open(CACHE, "w") as fh:
fh.write(FIXTURE % EXTRA[extra_name])
repo = os.path.join(root, cwd_name)
os.makedirs(repo, exist_ok=True)
subprocess.run(["git", "-C", repo, "init", "-q"], check=True)
subprocess.run(["git", "-C", repo, "-c", "user.email=t@t", "-c", "user.name=t",
"commit", "-q", "--allow-empty", "-m", "i"], check=True)
for br in BRANCHES:
subprocess.run(["git", "-C", repo, "checkout", "-q", "-B", br], capture_output=True)
for f in os.listdir(CACHE_DIR):
if f.startswith("git-"):
os.remove(os.path.join(CACHE_DIR, f))
for model, cols in itertools.product(MODELS, WIDTHS):
stdin = ('{"model":{"display_name":"%s"},"cwd":"%s",'
'"cost":{"total_cost_usd":8.31},"context_window":'
'{"context_window_size":1000000,"current_usage":'
'{"input_tokens":137000,"cache_read_input_tokens":34000}}}' % (model, repo))
out = subprocess.run(["bash", SCRIPT], input=stdin, capture_output=True,
text=True, env=dict(os.environ, TERM_WIDTH=str(cols))).stdout
usable = max(20, cols - 2 * pad - 1)
lines = out.rstrip("\n").split("\n")
checked += 1
tag = (br, model, cols, cwd_name, extra_name)
if len(lines) > 2:
fails.append(tag + ("more than two lines",))
continue
if not any(l.strip() for l in lines):
fails.append(tag + ("rendered nothing",))
continue
mx = max(width_of(l) for l in lines)
if mx > usable:
fails.append(tag + (f"max {mx} > usable {usable}",))
finally:
shutil.rmtree(root, ignore_errors=True)
shutil.rmtree(CACHE_DIR, ignore_errors=True)
print(f"checked {checked} combinations "
f"(widths {WIDTHS.start}-{WIDTHS.stop - 1}, {len(BRANCHES)} branches, "
f"{len(MODELS)} models, {len(CWDS)} cwds, extra-usage on and off)")
if fails:
print(f"FAIL: {len(fails)} overflow(s)")
for f in fails[:10]:
print(" ", f)
return 1
print("PASS: no line exceeded the usable width")
return 0
if __name__ == "__main__":
sys.exit(main())