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.

### 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.

## Coverage

Install Devel::Cover in a local Perl library, then run the gate as one command:

```bash
cpanm --notest --local-lib-contained ./.perl5 Devel::Cover
export PERL5LIB="$PWD/.perl5/lib/perl5${PERL5LIB:+:$PERL5LIB}"
export PATH="$PWD/.perl5/bin:$PATH"
perl script/coverage-gate
```

`script/coverage-gate` is the canonical entrypoint and the one every CI
workflow runs, so the documented gate and the executed gate are the same thing.
It drops the coverage database, runs the instrumented suite, collects the
`lib/` report for statement, branch, condition and subroutine, and enforces
100.0 on all four through `script/check-all-metric-coverage`. Use
`perl script/coverage-gate --dry-run` to see the resolved interpreter, library
path, serializer module and the exact commands before spending a slot, and
`perl script/coverage-gate --database DIR t/some-file.t` to collect a focused
run into a scratch database.

Its exit status is the interface: `0` all four metrics at 100.0, `1` a genuine
shortfall with the failing metrics named, `2` the gate could not run or could
not read its report, `3` the coverage instrument could not read its own
database.

That last status is why the chain is one command rather than three.
`Devel::Cover::DB::IO` picks its on-disk serialization format at `BEGIN` from
whatever `@INC` makes visible — Sereal, then JSON, then Storable — and records
the choice nowhere. Typed as three shell lines, the library path has to be
repeated on every one of them; omit it from a single line on a host carrying



( run in 1.172 second using v1.01-cache-2.11-cpan-9789f410c06 )