Skip to content

[WIP] Feature/more detailed lcov#275

Draft
chrisdp wants to merge 34 commits into
masterfrom
feature/more-detailed-lcov
Draft

[WIP] Feature/more detailed lcov#275
chrisdp wants to merge 34 commits into
masterfrom
feature/more-detailed-lcov

Conversation

@chrisdp

@chrisdp chrisdp commented Feb 11, 2024

Copy link
Copy Markdown
Collaborator

Adding support for more detailed lcov file reporting to include things like function coverage.

Example coverage from test app in repo:
image

Comment thread bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.spec.ts Outdated
@chrisdp chrisdp added the enhancement New feature or request label Feb 13, 2024
@sooraj-prabhu sooraj-prabhu mentioned this pull request Jul 23, 2024
Comment thread bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts Outdated
Comment thread bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts Outdated
@TwitchBronBron

Copy link
Copy Markdown
Member

@chrisdp what needs done yet before this or can merge? I'm sure many people would appreciate having this current functionality soon. Is there anything that could be landed now and then other things implemented in future PRs?

@chrisdp

chrisdp commented Jan 24, 2025

Copy link
Copy Markdown
Collaborator Author

@TwitchBronBron i think it might be close enough to go after we merge the file structure update, mocha, @only bug fixes, and promises support.

Mostly because the code cov logic will also likely need fixes due to changes in brighterscript 0.68+. Most of the ast issues we had earlier were due to code cov. So we will want to at least fix that much before we merge.

chrisdp added 4 commits March 18, 2025 15:56
…ed-lcov

# Conflicts:
#	bsc-plugin/package-lock.json
#	bsc-plugin/package.json
#	framework/src/components/rooibos/CodeCoverage.xml
#	framework/src/source/rooibos/CodeCoverage.brs
#	framework/src/source/rooibos/Coverage.bs
#	framework/src/source/rooibos/TestRunner.bs
#	package-lock.json
#	package.json
#	src/lib/rooibos/CodeCoverageProcessor.spec.ts
#	src/lib/rooibos/CodeCoverageProcessor.ts
#	src/plugin.spec.ts
#	tests/bsconfig.json
#	tests/package-lock.json
#	tests/package.json
chrisdp and others added 9 commits May 3, 2026 17:31
…I capture, HTML viewer)

Framework instrumentation rewrite for proper LCOV output and an end-to-end
report pipeline that produces an nyc-style HTML viewer.

CodeCoverageProcessor:
- Emit 1-indexed line numbers (LCOV/Istanbul convention) instead of 0-indexed.
- Defer reportFunction insertion until after the AST walk completes; inserting
  during the walk shifted owner[0] mid-visit and caused the visitor to skip
  the first statement's children (returns inside if/else were silently lost).
- Pair an IfStatement's then/else blocks under one block.id with the
  if-statement's anchor line, so consumers can render them as a single
  decision with multiple outcomes.
- Anchor for/while/for-each body branches to the loop statement line, not
  the body's first line.
- coverageExcludedFiles matcher is now case-insensitive (`Main.bs` matches
  `**/source/main.bs`), and `**/components/rooibos/**/*` is excluded by
  default (the framework's own coverage Task moved to that path).

Coverage.bs runtime:
- Always emit FNDA/BRDA rows including unhit ones (LCOV spec) so missed
  functions and branches render in HTML.
- Synthesize an implicit-else BRDA for single-arm ifs from
  condition_evaluations - then_hits, computed at lcov-write time. Lets the
  HTML viewer flag never-taken falsy paths without instrumenting both arms.

CLI:
- New --coverage-output flag; the console-output listener captures everything
  between `+-=-coverage:start`/`+-=-coverage:end` and writes lcov.info with
  SF: paths rewritten to absolute against bsConfig.rootDir.

scripts/lcov-to-html.js:
- Pure-JS lcov -> Istanbul HTML renderer using lcov-parse +
  istanbul-lib-coverage + istanbul-lib-report + istanbul-reports (all
  already pulled in transitively by nyc, no new deps).
- Groups BRDA rows by block to produce multi-location branch entries with
  type:'if', so the annotator emits inline I/E badges.
- Reads source files to compute the indent column for each branch line so
  badges anchor next to the statement (matching nyc's TS reports).
- Strips the hardcoded `prettyprint lang-js` class from generated pages
  (BS isn't JS; prettify mis-tokenized the source and overrode the badge
  color).

coverage-sample/:
- New benchmark project separate from test-project. Calculator.bs is
  designed to be 100%-coverable; PartialCoverage.bs has deliberate hits and
  misses across paired if/else, single-arm if, loop bodies, and a
  never-called function.

package.json:
- npm scripts: build-coverage-sample, coverage:run, coverage:html,
  coverage:report.

Tests: 120 -> 125 passing, 1 failing -> 0 failing. Three new regression
tests cover return instrumentation in top-level/namespaced/class-method
contexts. Existing snapshot tests updated for the new line numbering and
paired-branch IDs.
Pair try and catch blocks under a single block.id (same model used for
if/else), anchor both arms to the `try` keyword line, and instrument the
try line itself by prefixing the `try` token with a reportLine call -
matching the token-text-mutation pattern already used for for/while/
forEach (avoids the walker re-read bug that breaks arraySplice insertions
mid-visit).

Sample updates:
- Calculator.safeDivide: both try and catch arms exercised by spec
- PartialCoverage.parseSafe: only try succeeds, catch arm missed (E badge)
Adds expression-level branch coverage for ternary expressions, with the
missed arm rendered exactly like nyc/Istanbul TS reports - yellow
cbranch-no wrap on the unhit arm.

Framework:
- Defer convertStatementToCoverageStatement (the reportLine inserts) until
  after the walk completes. The pre-existing inline arraySplice was
  breaking the walker's descent into statement children, so a ternary
  inside a return statement was never visited. Same kind of issue we hit
  earlier with reportFunction inserts.
- New TernaryExpression visitor that wraps each arm in a
  RBS_CC_<id>_branchValue(blockId, branchId, value) helper call, parsed
  from a placeholder template and grafted with the original arm as a
  sub-expression so the walker can still descend into nested ternaries.
- Added matching branchValue helper to coverageBrsTemplate (pass-through
  that records the hit and returns the value unchanged).
- BranchCoverage.branches now has optional column / endColumn for
  expression-level branches; ternary visitor records both arms' ranges.

Coverage.bs runtime emits a custom RBSCOL:<block>,<branch>,<startCol>,<endCol>
line per branch when columns are present. Non-LCOV but lcov-parse
ignores unknown lines.

Renderer:
- Pre-parses RBSCOL lines (handles CRLF from device console capture)
  into a sidecar map; strips them before handing to lcov-parse.
- When a branch has column data, builds the branchMap entry with
  type:'cond-expr' and exact start/end columns so Istanbul's annotator
  fires the wrap path with cbranch-no (yellow !important) - exactly how
  TS reports render missed ternary arms. Block-level branches without
  column data continue to use type:'if' for the badge approach (the
  wrap path's snap-to-whitespace logic mis-handles full-line wraps).

Sample updates:
- Calculator.abs: ternary with both arms exercised - no badge
- PartialCoverage.asLabel: ternary truthy arm only - "off" wrapped yellow

Tests: 5 snapshot expectations updated to include the new branchValue
helper definition. 125 passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirrors the ternary visitor: NullCoalescingExpression's consequent and
alternate are each wrapped with RBS_CC_<id>_branchValue(...) so the
renderer can highlight a missed alternate in yellow exactly the way it
does for ternary arms.

Note on semantics: BrightScript always evaluates the consequent of `??`
to null-check it, so branch 0 (consequent) fires on every reach rather
than only when the consequent is "kept". The visually meaningful case -
"alternate never tested" - is still captured directly via branch 1.

Sample updates:
- Calculator.withDefault: both arms exercised
- PartialCoverage.nameOrDefault: only consequent exercised, alternate
  rendered in yellow
Adds a BinaryExpression visitor that wraps each operand of a logical
`and`/`or` with RBS_CC_<id>_branchValue. Short-circuit semantics are
preserved naturally: when BS short-circuits the right operand, BS never
evaluates the right wrap, so branch 1 stays unhit and the renderer
flags it in yellow. Bitwise integer use (e.g. `5 and 3`) still works -
both wraps fire because BS evaluates both sides for integers, so both
arms show as hit and there are no false-positive missed-branch reports.

The synthetic AND that the IfStatement handler injects to wrap an if's
condition with a reportLine call is added to processedExpressions so
the new BinaryExpression visitor doesn't try to instrument it again.

Also unskips `correctly transpiles some statements` (anonymous function
expressions) - that test now passes incidentally because the same fixes
that made expression-level branches reachable (deferring
convertStatementToCoverageStatement and reportFunction inserts so the
walker can descend into statement children) also let the walker reach
nested function expressions and instrument their bodies.

Sample updates:
- Calculator.isInRange: logical AND, both short-circuit and full-eval
  paths exercised
- PartialCoverage.isTrue: logical OR right side never evaluated, renders
  yellow on `fallback`

Tests: 126 passing, 1 pending (unrelated pre-existing skip).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Anon functions already work (the deferred-inserts fix in the earlier
commit unblocked walking into FunctionExpression bodies); this just
adds samples that exercise the rendering paths and tightens the
fstat-no wrap to match TS reports.

Sample updates:
- Calculator.classifySign: assigned anon function with both arms hit
- PartialCoverage.describeFlag: anon function called once with the
  else arm missed - inner `if f then` shows the E badge
- PartialCoverage.unusedHandler: anon returned but never invoked -
  FNDA:0,bench.unusedHandler$anon0 with body lines as cstat-no

Renderer:
- New getKeywordColumn helper finds the `function`/`sub` keyword on the
  declaration line; fnMap.decl now starts at that column instead of
  col 0. The fstat-no highlight wraps just `function(args) as ret`
  rather than the whole assignment line `handler = function(...)`,
  matching nyc/Istanbul TS reports.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI cleanup in CodeCoverageProcessor.ts:
- Remove the dead transpileState field (and BrsTranspileState import) - it
  was leftover from an earlier wrap-via-transpile approach; the current
  template-parsing path doesn't need it.
- Drop unused funcId locals in the report-text helpers; the call to
  ensureFunctionTracked is kept for the side effect of registering the
  function.
- Strip ~240 lines of dead code at the file tail (unused createCovMap
  function and a Perl reference dump that was commented out).

Naming cleanup:
- Rename getFunctionIdInFile -> ensureFunctionTracked since callers
  consume the side effect (registration + queued reportFunction insert),
  not the return value. Drop the now-dead return value and the unused
  owner/key parameters that were forwarded but never consumed.

New tests for coverage + global mocking interaction:
- Both helper sets and the mock prologue end up in the output.
- Returns and ternary arms inside a mocked function still get
  instrumented end-to-end.
- The mock prologue's anonymous lookup function isn't accidentally
  registered as a user-defined function for tracking.

Tests: 126 -> 129 passing, 1 pending.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Merge the two `from 'brighterscript'` imports into one type-import +
  one runtime-import (eslint flagged the same module being imported on
  multiple lines).
- Remove the vestigial `try {} catch (e) { console.log('Error:', e.stack) }`
  in the constructor - empty try body, so the catch could never fire,
  and `e.stack` ran into noImplicitAny on `unknown` errors.
- Spell out the longform property syntax in the pendingLineReports.push
  call to satisfy `object-shorthand: never`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@chrisdp chrisdp linked an issue May 3, 2026 that may be closed by this pull request
chrisdp and others added 3 commits May 3, 2026 19:05
Apps with their own build pipeline can run rooibos coverage without
asking the CLI to also build. Skips the builder.run() step when --package
is set; deploys the supplied artifact and watches the device console.

Accepts either:
- A .zip - deployed as-is via rokuDeploy.publish
- A directory - zipped to out/rooibos-prebuilt.zip via rokuDeploy.zipFolder
  first (most build systems write a folder of files, not a zip)

The user is responsible for having built the package with the rooibos
plugin enabled - otherwise the device runs unmodified code and the
coverage capture flow has nothing to scrape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Context for picking up the coverage work in a new session: where the
feature branch is (TS-Istanbul parity for line/function/branch coverage
including ternary, ??, and logical short-circuit), the unblocked
production-app integration blocker (Roku's per-function bytecode cap
trips on big functions because the per-line/per-branch report calls
double or triple function size), and a proposed direction (auto-split
function bodies into helper chunks at instrumentation time, with a
skip-large-functions escape hatch as a fallback).
rokuDeploy.zipFolder defaults to including every file in the source dir,
which dragged .brs.map and .xml.map files into the deployed channel. The
maps are harmless functionally but bloat channel size (saw real apps
trip Roku's package-size warnings just from this), and Roku has no use
for them anyway. Pass an explicit filter that keeps everything except
*.map files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coverage Report

2 participants