Comprehensive test coverage for all Zert functionality before public launch.
Progress key: [ ] todo · [~] in progress · [x] done
- 1.1 Fresh install — run bootstrap on clean system with no
$ZERT_PLUGINS_DIR, verifyzert/zert.zshandzert/zert.plugin.zshcreated - 1.2 Idempotency — run bootstrap twice, second run should
exit 0immediately - 1.3 Custom
ZERT_PLUGINS_DIR— set to custom path, verify clone lands there - 1.4 Custom
ZERT_DIR— setZERT_DIR(noZERT_PLUGINS_DIR), verify resolves to$ZERT_DIR/plugins - 1.5 XDG fallback — unset both
ZERT_DIRandZERT_PLUGINS_DIR, verify falls back to$XDG_DATA_HOME/zert/pluginsor~/.local/share/zert/plugins - 1.6 Atomic clone — verify temp directory used during clone and removed after
mv - 1.7 Cleanup on failure — interrupt bootstrap mid-clone, verify temp directory cleaned via trap
- 1.8 No Zsh required — verify bootstrap runs under plain
shwithout Zsh in PATH - 1.9 Network failure — test with unreachable remote, verify error message and non-zero exit
- 1.10 Existing partial install — create
zert.zshbut notzert.plugin.zsh, verify clone runs
- 2.1 All deps present — verify returns 0 when
git,curl,stdbuf,find, Zsh >= 5.0 exist - 2.2 Missing
git— hide from PATH, verify error and return 1 - 2.3 Missing
curl— hide from PATH, verify error and return 1 - 2.4 Old Zsh version — mock
ZSH_VERSION=4.9, verify error about Zsh >= 5.0 - 2.5 Multiple missing deps — hide both
gitandcurl, verify both reported - 2.6 Output goes to stderr — verify errors print to fd 2, not stdout
- 3.1 GitHub shorthand —
zsh-users/zsh-autosuggestions→ type=git, value=https://github.com/zsh-users/zsh-autosuggestions.git - 3.2 Full HTTPS URL —
https://github.com/user/repo.git→ type=git, value=URL - 3.3 GitLab URL —
https://gitlab.com/user/repo→ type=git, value=URL - 3.4 SSH URL —
git@github.com:user/repo.git→ type=git, value=URL - 3.5 Absolute local path —
/home/user/plugin→ type=local, value=path (if exists) - 3.6 Relative path rejected —
./my-plugin→ error + return 1 - 3.7 Nonexistent local path —
/nonexistent/path→ error + return 1 - 3.8 Empty input —
""→ error + return 1 - 3.9
ohmyzshsource —ohmyzsh→ type=ohmyzsh, value=ohmyzsh - 3.10
preztosource —prezto→ type=prezto, value=prezto - 3.11 GitHub URL without
.git—https://github.com/user/repo→ type=git - 3.12 Trailing slash handling —
https://github.com/user/repo/→ handled correctly
- 4.1 Boolean flag —
--no-alias→"true" - 4.2 Value flag —
--branch main→"main" - 4.3 Missing flag — query for
--branchwhen not present →"" - 4.4 Pin flag —
--pin abc123→"abc123" - 4.5 Multiple flags —
--no-alias --branch dev --pin xyz→ each parses correctly - 4.6 Underscore normalization — query
no_alias→ matches--no-alias - 4.7 Flag at end of args —
--no-aliasas last arg (no value after) →"true"
- 5.1 GitHub shorthand —
git+https://github.com/zsh-users/zsh-autosuggestions.git→github.com--zsh-users--zsh-autosuggestions - 5.2 GitLab URL —
git+https://gitlab.com/user/repo→gitlab.com--user--repo - 5.3 SSH format —
git+git@github.com:user/repo.git→ correct sanitized ID - 5.4 Local path —
local+/home/user/plugin→local--home--user--plugin - 5.5
.gitsuffix stripped — URL ending in.git→ suffix removed - 5.6 Trailing slashes — URL with trailing
/→ handled correctly - 5.7 Special chars in path — paths with dots, hyphens, underscores → sanitized correctly
- 6.1 Fresh clone — clone a known public repo, verify directory exists with
.git - 6.2 Treeless clone — verify
--filter=tree:0is used - 6.3 Atomic clone (temp dir) — verify clone uses temp dir +
mvpattern - 6.4 Cleanup on failure — simulate clone failure, verify temp dir removed
- 6.5 Branch flag —
--branch develop→ verify correct branch cloned - 6.6 Oh-My-Zsh URL mapping —
ohmyzshsource type → maps to correct GitHub URL - 6.7 Prezto URL mapping —
preztosource type → maps to correct GitHub URL - 6.8 Zert self-clone —
zertsource type → maps tohttps://github.com/oxcl/zert.git - 6.9 Custom GitLab URL — clone from GitLab URL, verify works
- 6.10 Already cloned — clone when directory exists → skip or handle gracefully
- 7.1 Compile
.zshfiles — create plugin with.zshfiles, verify.zwccreated - 7.2 No
.zshfiles — plugin with no.zshfiles → silent skip, return 0 - 7.3 Nested
.zshfiles — plugin with subdirs containing.zsh→ all compiled via**/*.zsh(N) - 7.4 Nonexistent directory — pass invalid path → return 1
- 7.5 Recompilation — compile already-compiled files →
.zwcupdated without error
- 8.1
.plugin.zshdetection — plugin withname.plugin.zsh→ sourced correctly - 8.2
init.zshdetection — plugin withinit.zsh→ sourced correctly - 8.3
.zshfallback — plugin with onlyname.zsh→ sourced - 8.4
.shfallback — plugin with onlyname.sh→ sourced - 8.5 Single
.zshfile — directory with one.zshfile → sourced directly - 8.6 No sourceable file — empty plugin directory → return 1
- 8.7
completions/fpath — plugin withcompletions/dir → added tofpath - 8.8
functions/fpath — plugin withfunctions/dir → added tofpath - 8.9
--no-completion— verifycompletions/NOT added tofpath - 8.10
--only-completion— verify nothing sourced, onlyfpathupdated - 8.11
--no-alias— plugin defines aliases → verify NOT loaded - 8.12 Theme detection —
.zsh-themefile → sourced, single-theme rule enforced - 8.13
ohmyzsh/preztotype — returns 0 immediately (handled by respective commands)
- 9.1 Write entry — write new entry, verify file created with correct format
- 9.2 Read entry — write then read, verify all fields match
- 9.3 Replace entry — write same
plugin_idtwice, verify replaced not duplicated - 9.4 Remove entry — write then remove, verify entry gone, others preserved
- 9.5 Atomic write — verify temp file +
mvpattern (no partial writes) - 9.6 Header preservation — after write, verify comments and
version::1present - 9.7 Options serialization — write with
--branch main --no-alias, verify options field format - 9.8
::escaping — plugin ID or URL containing::→ escaped as\:: - 9.9 Read nonexistent — read for plugin not in lockfile → return 1
- 9.10 Read missing lockfile — read when
$ZERT_LOCKFILEdoesn't exist → return 1 - 9.11 Remove from missing lockfile — remove when file doesn't exist → return 1
- 9.12 Multiple entries — write 5+ entries, verify all readable and removable independently
- 9.13 Comments/blank lines — lockfile with comments and blanks → parser skips correctly
- 9.14 Pin option persistence —
--pin abc123→ lockfile showspin=abc123in options - 9.15 Local plugin entry —
source=local, empty url and commit_sha fields
- 10.1 Fresh plugin (no lockfile) — returns
"register" - 10.2 Already synced — plugin exists, lockfile matches →
"synced" - 10.3 Branch mismatch —
--branch devbut lockfile saysmain→"checkout"or"pull" - 10.4 Pin mismatch —
--pin abcbut lockfile saysdef→"checkout"or"pull" - 10.5 SHA mismatch (remote updated) — lockfile SHA doesn't match local HEAD →
"pull"if SHA not local - 10.6 SHA exists locally — lockfile SHA exists in local git history →
"checkout" - 10.7 Local plugin — always returns
"synced"
- 11.1 Empty list — no plugins loaded → "No plugins loaded" message
- 11.2 Single plugin — one plugin → correct columns (PLUGIN, TYPE, SHA, FLAGS)
- 11.3 Multiple plugins — 5+ plugins → table formatted correctly
- 11.4 Plugin with lockfile entry — shows truncated 7-char SHA
- 11.5 Plugin without lockfile — shows em-dash for missing fields
- 11.6 Local plugin display — shows path with
--converted to/ - 11.7 Plugin with flags — shows flags like
pin=abc,branch=dev - 11.8 Terminal width adaptation — narrow terminal (< 80 cols) → columns truncated
- 11.9 Very long plugin names — names truncated with
... - 11.10 Color output — verify ANSI colors applied when
$NO_COLORunset
- 12.1 Update all plugins — multiple unlocked plugins → all updated, lockfile regenerated
- 12.2 Skip local plugins — local source type → skipped, not updated
- 12.3 Skip pinned plugins — plugin with
pin=*option → skipped - 12.4 Missing lockfile — no lockfile → error message
- 12.5 Missing plugin directory — lockfile entry but no cloned dir → skip with log
- 12.6 Pull failure — simulate git pull failure → error logged, failed counter incremented
- 12.7 Compile after update — verify
.zwcfiles regenerated after pull - 12.8 Lockfile updated — after update, lockfile has new commit SHAs
- 12.9 Interrupt handling — Ctrl-C during update →
__ZERT_INTERRUPTED=1, partial results preserved - 12.10 Update count output — verify updated/skipped/failed counts reported
- 12.11
--ff-onlyfallback — ifgit pull --ff-onlyfails, falls back togit fetch origin
- 13.1 Nothing to prune — all dirs in loaded plugins → "Nothing to prune"
- 13.2 Prune stale dirs — extra dirs not in
__ZERT_LOADED_PLUGINS→ deleted - 13.3 Skip
zertdirectory —zertdir never pruned (self-management) - 13.4 Lockfile cleanup — pruned plugin's lockfile entry removed
- 13.5 Missing plugins dir —
$ZERT_PLUGINS_DIRdoesn't exist → graceful message - 13.6 Partial failure —
rm -rffails on one dir → error logged, others still pruned - 13.7 Count output — verify "Pruned N plugin(s)" message
- 14.1
zert use ohmyzsh— initializes OMZ: clones repo, sets$ZSH,$ZSH_CUSTOM,$ZSH_CACHE_DIR - 14.2
zert use prezto— initializes Prezto: clones repo, sets$ZPREZTODIR, sources helper init - 14.3 Idempotent
use— callzert use ohmyzshtwice, second call is no-op - 14.4
zert ohmyzsh plugins/git— loads OMZ git plugin from cloned repo - 14.5
zert ohmyzsh lib/clipboard— loads OMZ clipboard lib - 14.6
zert ohmyzsh themes/robbyrussell— loads OMZ theme - 14.7
zert prezto modules/utility— loads Prezto utility module, autoloads functions - 14.8 OMZ not initialized —
zert ohmyzsh Xwithoutzert use ohmyzsh→ error - 14.9 Prezto not initialized —
zert prezto Xwithoutzert use prezto→ error - 14.10 Invalid OMZ component —
zert ohmyzsh invalid/name→ error with valid options - 14.11 Nonexistent OMZ plugin —
zert ohmyzsh plugins/nonexistent→ error - 14.12 Nonexistent Prezto module —
zert prezto nonexistent→ error - 14.13 OMZ lockfile entry — after
use ohmyzsh, lockfile hasohmyzsh::ohmyzsh::...entry - 14.14 Prezto lockfile entry — after
use prezto, lockfile hasprezto::prezto::...entry - 14.15 OMZ flags passthrough —
zert ohmyzsh plugins/git --no-alias→ alias suppression works
- 15.1 Trigger fast-track — lockfile with non-local entries, no cloned dirs → fast-track runs
- 15.2 Skip fast-track — no lockfile → falls back to sequential
- 15.3 Skip if already cloned — all lockfile plugins already cloned → skip
- 15.4 Skip if only local — lockfile has only
localentries → skip - 15.5 Parallel clone — 3+ plugins → verify parallel execution (background PIDs)
- 15.6 Failure collection — one clone fails → others still complete, failure counted
- 15.7 Options from lockfile — lockfile has
--branch,--pin,--no-alias→ applied during fast-track - 15.8 Compile after clone — all cloned plugins compiled after parallel phase
- 15.9 Stale tmpdir cleanup — pre-existing
.zert-clone-*dirs → cleaned before starting - 15.10 Interrupt handling — Ctrl-C during fast-track → all background jobs killed
- 16.1 Color variables defined — all
__ZERT_CLR_*variables set when$NO_COLORunset - 16.2
NO_COLORsupport — set$NO_COLOR→ all color variables empty - 16.3
_zert_ui_ok— output contains green text, goes to stderr - 16.4
_zert_ui_error— output contains red text, goes to stderr - 16.5
_zert_ui_log— output contains blue text, goes to stderr - 16.6
_zert_ui_truncate— text shorter than max → unchanged, text longer → truncated with... - 16.7
_zert_ui_truncateedge cases —max_width=0→ empty,max_width=3→ truncated without... - 16.8
_zert_ui_emphasize—**text**→ yellow+italic ANSI, no markers → unchanged - 16.9
_zert_ui_pick_spinner— frame 0-9 → correct braille characters, cycles at 10 - 16.10 Task start/end —
_zert_ui_task_startcreates FIFO, spawns renderer,_zert_ui_task_endcleans up - 16.11 Subtask lifecycle — start → update → end sequence works correctly
- 16.12 Background renderer — FIFO communication, spinner animation, log ring buffer
- 16.13 Ctrl-C handling —
TRAPINTkills background PIDs, ends task with fail - 16.14 FIFO cleanup — after task end,
/tmp/zert_ui_comm_$$removed - 16.15 80-column constraint — all output fits within 80 columns
- 17.1 No arguments — shows usage error, returns 1
- 17.2 Known subcommand —
zert list→ dispatches to_zert_cmd_list - 17.3 Unknown subcommand —
zert foobar→ tries to parse as source, fails with error - 17.4 Plugin shorthand —
zert user/repo→ parsed as plugin, added - 17.5 Plugin with flags —
zert user/repo --branch dev→ flags passed through - 17.6
zert use X— dispatches to_zert_cmd_use - 17.7
zert ohmyzsh X— dispatches to_zert_cmd_ohmyzsh
- 18.1 Env var
ZERT_DIR— set → overrides default - 18.2 Env var
ZERT_PLUGINS_DIR— set → overridesZERT_DIR/plugins - 18.3 Env var
ZERT_LOCKFILE— set → overrides$ZDOTDIR/zert.lock - 18.4 XDG fallback — unset all → uses
$XDG_DATA_HOME/zertor~/.local/share/zert - 18.5
$ZDOTDIRfallback —ZERT_LOCKFILEunset → uses$ZDOTDIR/zert.lockor$HOME/zert.lock - 18.6 Env vars export — all
ZERT_*variables are exported to child processes
- 19.1 No
exitin sourced files — grep all.zshfiles forexit, verify none outsidebootstrap.sh - 19.2 All functions use
local— verify no unscoped variable assignments in functions - 19.3 Return codes — functions return 1 on user error, 2 on internal error
- 19.4 stderr for errors — all error output goes to fd 2
- 19.5 Sourcing failure — plugin main file has syntax error → error reported, loading stops
- 19.6 Network timeout — clone with slow/unreachable network → appropriate error
- 19.7 Disk full — simulate disk full during clone/lockfile write → graceful error
- 19.8 Concurrent sourcing — source
zert.zshfrom two shells simultaneously → no corruption - 19.9 Signal handling — SIGINT during clone → cleanup temp dirs
- 19.10 Empty plugin declaration —
zertwith empty string → error
- 20.1 Fresh
.zshrcto running shell — full bootstrap → declare 3 plugins → shell starts with all loaded - 20.2 Lockfile reproducibility — clone on machine A → copy lockfile to machine B → identical plugin versions
- 20.3 Update workflow —
zert update→ lockfile SHA changes → next startup uses new SHA - 20.4 Prune workflow — remove plugin from
.zshrc→zert prune→ dir deleted, lockfile cleaned - 20.5 Mixed source types —
git+local+ohmyzsh+preztoplugins all in one.zshrc - 20.6 Pin workflow —
zert user/repo --pin abc123→ lockfile has pin → update skips it - 20.7 Branch workflow —
zert user/repo --branch dev→ cloned from dev → lockfile records branch - 20.8 Re-bootstrap after delete — delete
$ZERT_PLUGINS_DIR/zert→ re-run bootstrap → works - 20.9 Large plugin count — 10+ plugins → parallel install performance acceptable
- 20.10 Self-update —
zert updatewhen zert itself is listed → updates self
- 21.1 No user file modification — verify Zert never writes to
.zshrc,.zprofile, etc. - 21.2 Lockfile atomicity — kill process during lockfile write → no corruption
- 21.3 No eval usage — grep for
evalin all source files, verify none or justified with comment - 21.4 Relative path rejection —
zert ../etc/passwd→ rejected - 21.5 Path traversal in plugin ID — sanitized IDs prevent
../in filesystem paths - 21.6 Temp file permissions — temp dirs/files don't expose sensitive data
Total: 180 test cases across 21 categories.