Developer-Dashboard
view release on metacpan or search on metacpan
doc/testing.md view on Meta::CPAN
# Testing
## Test Suite
Run the full test suite with:
```bash
prove -lr t
```
The dotted installed-skill command regressions cover both `cli/<command>.py`
and `cli/<command>.js`. The JavaScript execution assertions require `node` on
`PATH`, so the release tarball gate keeps the path-resolution assertions
everywhere and skips only the `.js` execution step on minimal hosts that do not
ship Node.js.
Collector lifecycle regressions also cover two macOS-specific runtime rules:
Dashboard-owned Perl child processes must keep the current interpreter ahead of
the system Perl in `PATH`, and collector shell commands must avoid login-shell
startup chatter that can prepend text ahead of collector JSON output.
Those path regressions now also lock in the shell side of that contract:
dashboard-managed child commands must keep the active shell directory in
`PATH`, not just the Perl interpreter bin, because `_run_command` executes
shell collectors through `sh -c` and blank-environment install verification
will fail if `perl` is found but `sh` is not.
Run the fast saved-bookmark browser smoke check with:
```bash
integration/browser/run-bookmark-browser-smoke.pl
```
That host-side smoke runner creates an isolated temporary runtime, starts the
checkout-local dashboard, loads one saved bookmark page through headless
Chromium, and can assert page-source fragments, saved `/ajax/...` output, and
the final browser DOM. With no arguments it runs the built-in Ajax
`foo.bar` bookmark case.
For a real bookmark file, point it at the saved file and add the specific
browser assertions you care about:
```bash
integration/browser/run-bookmark-browser-smoke.pl \
--bookmark-file ~/.developer-dashboard/dashboards/test \
--expect-page-fragment "set_chain_value(foo,'bar','/ajax/foobar?type=text')" \
--expect-ajax-path /ajax/foobar?type=text \
--expect-ajax-body 123 \
--expect-dom-fragment '<span class="display">123</span>'
```
For a skill page that declares `config/routes.json`, assert the canonical
custom ajax path rather than the default smart `/ajax/<repo-name>/...` path:
```bash
integration/browser/run-bookmark-browser-smoke.pl \
--bookmark-file ~/.developer-dashboard/skills/example-skill/dashboards/index \
--expect-page-fragment "set_chain_value(endpoints,'status','/v1/status')" \
--expect-ajax-path /v1/status \
--expect-ajax-body '{"status":"ok"}'
```
For long-running saved bookmark Ajax handlers that would otherwise survive a
browser refresh, prefer `Ajax(..., singleton => 'NAME', ...)`. The runtime will
rename the Perl worker to `dashboard ajax: NAME`, terminate the older matching
Perl stream before it starts the refreshed one, and also tear down matching
singleton workers during `dashboard stop`, `dashboard restart`, and browser
`pagehide` cleanup beacons. For browser streaming checks, use `stream_data()`
or `stream_value()` against a finite saved Ajax handler and assert the final
DOM after incremental chunks land.
The Ajax helper regression also checks transient code templating:
`data => { args => 123 }` must encode `my $foobar = 123;` rather than leaving
`[% args %]` in the token payload. Run the focused regression test when
changing the `Ajax` helper.
### Source-tree gates and git worktrees
Some test files only make sense against a checkout rather than an installed
distribution, and they gate themselves on the presence of `.git`. Detect that
by existence, never by directory-ness: in the primary checkout `.git` is a
directory, but in a linked git worktree it is a regular file holding a
`gitdir:` pointer. A `-d` gate is therefore false in every worktree, and a file
that opens with `plan skip_all` on it deletes itself there entirely.
This matters because per-ticket work happens in worktrees, so a directory-only
gate switches the affected guardrails off at exactly the moment a change is
authored and first verified, and they only wake up later on a run from the
primary checkout. `t/139-worktree-source-tree-detection.t` proves the guardrail
file really executes inside a real linked worktree and sweeps `t/` so the
directory-only form cannot come back.
### Running the suite in a container
The project image runs as `uid 0`, and Docker's default capability set includes
`CAP_DAC_OVERRIDE`. A privileged process bypasses discretionary access control
entirely, so every test that asserts an operation is *refused* â an unreadable
file, an unwritable directory â is asserting something that cannot happen. Those
tests skip themselves rather than fail, because each one probes whether a denial
is observable before asserting on it (see
`docs/assertions-that-cannot-fail.md`).
Skipping is honest, but it is coverage you did not get. **Drop the two DAC
capabilities and the assertions run instead:**
```bash
docker run --rm \
--cap-drop=DAC_OVERRIDE --cap-drop=DAC_READ_SEARCH \
-v "$PWD:/src:ro" developer-dashboard:latest bash -c '
mkdir -p /work && tar -C /src --exclude=./.git -cf - . | tar -C /work -xf -
cd /work && prove -lr t'
```
The process is still `uid 0` â it can install packages, which the blank-host
bootstrap gate legitimately needs â but it can no longer bypass the mode bits, so
a permission denial is observable and every such assertion executes.
**Verifying it worked is a skip count, not a test count.** A skipped test still
( run in 0.962 second using v1.01-cache-2.11-cpan-16aabcbfbcb )