From 0f7055e330c9a9f5456540a491a1896b02d3436a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:47:30 +0000 Subject: [PATCH 1/8] Initial plan From 51c9d8637d50b2fb200e6cf4396bbd339f0f9056 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:50:35 +0000 Subject: [PATCH 2/8] fix: guard EPOCHREALTIME availability in perf.zsh to avoid silent 0ms timing Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- configs/core/perf.zsh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/configs/core/perf.zsh b/configs/core/perf.zsh index bf2c384..6427605 100644 --- a/configs/core/perf.zsh +++ b/configs/core/perf.zsh @@ -4,6 +4,13 @@ zmodload zsh/datetime 2>/dev/null +if (( ! ${+EPOCHREALTIME} )); then + # zsh/datetime unavailable; define no-op stubs so _stage/_zsh_report calls still succeed + _stage() { :; } + _zsh_report() { :; } + return +fi + typeset -ga _zsh_stage_names typeset -ga _zsh_stage_durations typeset -g _zsh_report_called From 34eff0d4aeae3cd3126ec8e84c0b1f024747202b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:00:31 +0000 Subject: [PATCH 3/8] test: add Vitest and shell tests for perf.zsh EPOCHREALTIME guard Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- package.json | 3 +- tests/configs.test.js | 56 +++++++++++++++- tests/perf.zsh | 153 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 tests/perf.zsh diff --git a/package.json b/package.json index 4961d06..1533c8c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "verify": "node src/cli.js verify", "clean": "node src/cli.js clean", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "test:shell": "zsh tests/perf.zsh" }, "keywords": ["dotfiles", "macos", "setup", "zsh", "cli"], "author": "ChangeHow", diff --git a/tests/configs.test.js b/tests/configs.test.js index 0506e76..c40111c 100644 --- a/tests/configs.test.js +++ b/tests/configs.test.js @@ -1,4 +1,4 @@ -import { describe, test, expect } from "vitest"; +import { describe, test, expect, beforeEach } from "vitest"; import { readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; import { execSync } from "node:child_process"; @@ -157,3 +157,57 @@ describe("Static config templates", () => { } }); }); + +describe("perf.zsh EPOCHREALTIME guard", () => { + let perf; + + beforeEach(() => { + perf = readFileSync(join(CONFIGS_DIR, "core", "perf.zsh"), "utf-8"); + }); + + test("checks EPOCHREALTIME availability after zmodload attempt", () => { + expect(perf).toContain("${+EPOCHREALTIME}"); + }); + + test("guard is placed after zmodload and before typeset declarations", () => { + const zmodloadIdx = perf.indexOf("zmodload zsh/datetime"); + const guardIdx = perf.indexOf("${+EPOCHREALTIME}"); + const typesetIdx = perf.indexOf("typeset -ga _zsh_stage_names"); + + expect(zmodloadIdx).toBeGreaterThan(-1); + expect(guardIdx).toBeGreaterThan(zmodloadIdx); + expect(guardIdx).toBeLessThan(typesetIdx); + }); + + test("defines no-op _stage stub in the fallback block", () => { + expect(perf).toMatch(/_stage\(\) \{ :; \}/); + }); + + test("defines no-op _zsh_report stub in the fallback block", () => { + expect(perf).toMatch(/_zsh_report\(\) \{ :; \}/); + }); + + test("uses return to skip the normal-path setup in the fallback block", () => { + // 'return' must appear inside the guard block (after the guard, before typeset declarations) + const guardIdx = perf.indexOf("${+EPOCHREALTIME}"); + const typesetIdx = perf.indexOf("typeset -ga _zsh_stage_names"); + + // Use a regex search from the guard position to allow varying indentation + const afterGuard = perf.slice(guardIdx, typesetIdx); + expect(afterGuard).toMatch(/^\s*return\s*$/m); + }); + + test("normal path defines full _record_stage_duration function", () => { + expect(perf).toMatch(/_record_stage_duration\(\) \{/); + }); + + test("normal path _stage records stage names, not just a stub", () => { + // The full _stage definition assigns to _zsh_current_stage + expect(perf).toContain('_zsh_current_stage="$1"'); + }); + + test("normal path _zsh_report outputs a timing table", () => { + expect(perf).toContain("┌──────────────────────────┐"); + expect(perf).toContain("total"); + }); +}); diff --git a/tests/perf.zsh b/tests/perf.zsh new file mode 100644 index 0000000..1f456a6 --- /dev/null +++ b/tests/perf.zsh @@ -0,0 +1,153 @@ +#!/usr/bin/env zsh +# ============================================================================= +# Runtime behavioral tests for configs/core/perf.zsh +# +# Tests the EPOCHREALTIME guard: verifies that sourcing perf.zsh produces +# functional no-op stubs when zsh/datetime is unavailable, and full timing +# instrumentation when it is available. +# +# Usage: +# zsh tests/perf.zsh +# +# Requires zsh. The "normal path" tests additionally require the zsh/datetime +# module (present on macOS and most Linux distributions with a full zsh build). +# ============================================================================= + +typeset -i _pass=0 _fail=0 + +PERF_ZSH="${${(%):-%x}:A:h}/../configs/core/perf.zsh" +# ${(%):-%x} – expands to the path of the current script +# :A – resolves symlinks to an absolute path +# :h – strips the last component (filename), leaving the directory + +_ok() { print " ✓ $1"; (( _pass++ )); } +_fail() { print " ✗ $1\n expected: '$2'\n actual: '$3'"; (( _fail++ )); } + +_assert_eq() { + local desc="$1" expected="$2" actual="$3" + [[ "$expected" == "$actual" ]] && _ok "$desc" || _fail "$desc" "$expected" "$actual" +} + +_assert_ne() { + local desc="$1" unexpected="$2" actual="$3" + [[ "$unexpected" != "$actual" ]] && _ok "$desc" || _fail "$desc" "(not $unexpected)" "$actual" +} + +_assert_contains() { + local desc="$1" needle="$2" haystack="$3" + [[ "$haystack" == *"$needle"* ]] && _ok "$desc" || _fail "$desc" "*$needle*" "$haystack" +} + +# Each test case runs in a dedicated zsh subprocess for complete state isolation. +# Results (PASS/FAIL lines) are printed to stdout and the subprocess exits +# non-zero on assertion failure so the parent can tally counts. + +_run() { + local label="$1" + local script="$2" + local out rc + out=$(zsh -c "$script" 2>&1) + rc=$? + if (( rc == 0 )); then + _ok "$label" + else + _fail "$label" "exit 0" "exit $rc ($out)" + fi +} + +# ============================================================================= +# 1. Fallback path: EPOCHREALTIME unavailable (zsh/datetime not loaded) +# ============================================================================= + +# Common preamble embedded into every fallback subprocess: override zmodload so +# the zsh/datetime module is never loaded and EPOCHREALTIME stays unset. +_FALLBACK_SETUP=" + function zmodload { :; } + unset EPOCHREALTIME 2>/dev/null + source '$PERF_ZSH' +" + +print "\nperf.zsh – fallback path (EPOCHREALTIME unavailable):" + +_run "_stage is a no-op (does not set _zsh_current_stage)" " + $_FALLBACK_SETUP + _stage 'test-stage' + [[ -z \"\${_zsh_current_stage:-}\" ]] +" + +_run "_zsh_report is a no-op (produces no output)" " + $_FALLBACK_SETUP + out=\$(_zsh_report 2>&1) + [[ -z \"\$out\" ]] +" + +_run "_zsh_stage_names array is not populated after _stage call" " + $_FALLBACK_SETUP + _stage 'x' + _stage 'y' + [[ -z \"\${_zsh_stage_names[*]:-}\" ]] +" + +_run "repeated _stage calls do not raise errors" " + $_FALLBACK_SETUP + _stage 'a'; _stage 'b'; _stage 'c' + true +" + +_run "repeated _zsh_report calls do not raise errors" " + $_FALLBACK_SETUP + _zsh_report; _zsh_report + true +" + +# ============================================================================= +# 2. Normal path: EPOCHREALTIME available (zsh/datetime loaded successfully) +# ============================================================================= +print "\nperf.zsh – normal path (EPOCHREALTIME available):" + +if ! zsh -c "zmodload zsh/datetime 2>/dev/null && (( \${+EPOCHREALTIME} ))" >/dev/null 2>&1; then + print " ⚠ zsh/datetime not available in this environment; skipping normal-path tests" +else + _run "_stage sets _zsh_current_stage" " + source '$PERF_ZSH' + _stage 'env' + [[ \"\$_zsh_current_stage\" == 'env' ]] + " + + _run "_stage records the previous stage name when transitioning" " + source '$PERF_ZSH' + _stage 'env' + _stage 'tools' + [[ \"\${_zsh_stage_names[1]}\" == 'env' ]] + " + + _run "_stage records a non-zero duration for the previous stage" " + source '$PERF_ZSH' + _stage 'env' + sleep 0.01 + _stage 'tools' + (( \${_zsh_stage_durations[1]:-0} > 0 )) + " + + _run "_zsh_report outputs a timing table containing 'total'" " + source '$PERF_ZSH' + _stage 'env' + _stage 'tools' + out=\$(_zsh_report 2>&1) + [[ \"\$out\" == *'total'* ]] + " + + _run "_zsh_report is idempotent (second call produces no extra output)" " + source '$PERF_ZSH' + _stage 'env' + first=\$(_zsh_report 2>&1) + second=\$(_zsh_report 2>&1) + [[ -z \"\$second\" ]] + " +fi + +# ============================================================================= +# Summary +# ============================================================================= +print "\nResults: $_pass passed, $_fail failed." +(( _fail == 0 )) From 1bc4cf62122b610e4edbe4e288fe298f83396485 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:24:28 +0000 Subject: [PATCH 4/8] chore: sync with dev - bring in CI workflow (test.yml) Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- .github/workflows/test.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ea541d9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,21 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: npm + - name: Install zsh + run: sudo apt-get update && sudo apt-get install -y zsh + - run: npm ci + - run: npm test From bcfcf12d28e0e7aab67991a68133ced0100fb57e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:28:01 +0000 Subject: [PATCH 5/8] chore: combine test:shell and test:watch into single test:watch script Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 1533c8c..54b0b4a 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "verify": "node src/cli.js verify", "clean": "node src/cli.js clean", "test": "vitest run", - "test:watch": "vitest", - "test:shell": "zsh tests/perf.zsh" + "test:watch": "zsh tests/perf.zsh && vitest" }, "keywords": ["dotfiles", "macos", "setup", "zsh", "cli"], "author": "ChangeHow", From 7a89c4747fd92bfac45a46c2220769751c2ae57a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:30:29 +0000 Subject: [PATCH 6/8] ci: add shell test step to GitHub Actions workflow Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea541d9..6668622 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,4 +18,5 @@ jobs: - name: Install zsh run: sudo apt-get update && sudo apt-get install -y zsh - run: npm ci + - run: zsh tests/perf.zsh - run: npm test From ae6a25646c9b8f520ab2a3636524b8554ec3b136 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:43:19 +0000 Subject: [PATCH 7/8] fix: npm test runs shell+JS tests, test:watch is vitest-only Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- .github/workflows/test.yml | 1 - package.json | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6668622..ea541d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,5 +18,4 @@ jobs: - name: Install zsh run: sudo apt-get update && sudo apt-get install -y zsh - run: npm ci - - run: zsh tests/perf.zsh - run: npm test diff --git a/package.json b/package.json index 54b0b4a..c3f7dc1 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "append": "node src/cli.js append", "verify": "node src/cli.js verify", "clean": "node src/cli.js clean", - "test": "vitest run", - "test:watch": "zsh tests/perf.zsh && vitest" + "test": "zsh tests/perf.zsh && vitest run", + "test:watch": "vitest" }, "keywords": ["dotfiles", "macos", "setup", "zsh", "cli"], "author": "ChangeHow", From 150ac1f7d7798782c6506b383ae6ac127736054e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:46:50 +0000 Subject: [PATCH 8/8] fix: correct idempotency test to avoid subshell flag isolation Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> --- tests/perf.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf.zsh b/tests/perf.zsh index 1f456a6..5ac1db9 100644 --- a/tests/perf.zsh +++ b/tests/perf.zsh @@ -140,7 +140,7 @@ else _run "_zsh_report is idempotent (second call produces no extra output)" " source '$PERF_ZSH' _stage 'env' - first=\$(_zsh_report 2>&1) + _zsh_report >/dev/null 2>&1 second=\$(_zsh_report 2>&1) [[ -z \"\$second\" ]] "