Skip to content

fix(ego-browser): make mutation-check mutate the operators it targets - #68

Open
zhuran24 wants to merge 1 commit into
opencue:mainfrom
zhuran24:fix/mutation-check-collector
Open

zhuran24 wants to merge 1 commit into
opencue:mainfrom
zhuran24:fix/mutation-check-collector

Conversation

@zhuran24

Copy link
Copy Markdown

Summary

scripts/mutation-check.mjs mutated code outside the functions it was asked to focus on, never flipped && or ||, and could flip the wrong operator. This PR fixes the mutant collector and adds tests for it.

Related issue

None: issues are disabled on this repository.

Changes

collectMutations() had three defects:

  1. Logical operators were never mutated. && and || are listed in BINARY_FLIPS, but acorn parses them as LogicalExpression, which had no visitor.
  2. The wrong operator could be flipped. The mutant was built with original.replace(op, …), which changes the first occurrence of the operator text in the expression's source: inside a string literal ("===" === value became "!==" === value), inside a comment, or the inner + of (a + b) + c.
  3. Focus ranges were guessed from text. inFocus() looked for a focus function's declaration in the 2000 characters before each node. Code after a focus function was mutated as if it were inside it, and the end of a focus function longer than 2000 characters was skipped.

The operator position now comes from acorn's token stream, and LogicalExpression gets the same visitor as BinaryExpression. Focus ranges are the source ranges of the named function declarations, function expressions and arrow functions in the AST, and an operator mutation is kept only when the operator itself lies in one of them. collectMutations() is exported, and main() runs only when the script is executed, directly or through a symlink, so test/mutation-check.test.js can import the collector.

Effect on the configured modules (dist/src built from main):

Module Mutants on main Of which outside the focus functions Mutants with this PR Of which on && / ||
cdp-eval 41 14 39 10
element-resolver 21 13 9 1
browser-runtime 19 0 39 19
helpers 19 10 13 4

The "outside" column counts mutants on main that fall in other functions, such as jsExceptionLocation and jsSnippet for cdp-eval, axNameMatches and isTextMatcher for element-resolver, and newTaskSpace and switchTaskSpace for helpers. With this PR every mutant falls inside a focus function. node scripts/mutation-check.mjs cdp-eval element-resolver runs end to end with the new collector: 39 of 48 mutants killed.

Verification

Run from package/ego-browser.

Check main This branch
npm test (build, typecheck, tests) 403 passed 411 passed
The 7 new collector tests against main's collector (exported, otherwise unchanged) 5 fail, 2 pass¹ 7 pass
Starting the script through a symlink (new test) n/a² passes
prettier --check on the changed files n/a clean

¹ a missing focus function does not mutate unrelated code and function focus excludes an operator after a selected function expression already hold on main; they guard the new range check.
² On main the script calls main() unconditionally when loaded, so it cannot be imported by a test at all.

Impact

Development tooling only; npm run mutation-check reports on a different, intended set of mutants. No runtime, helper or CI change.

🤖 Generated with Claude Code

collectMutations() had three defects that decided which mutants ran:

- && and || are listed in BINARY_FLIPS, but acorn parses them as
  LogicalExpression, which had no visitor, so no logical operator was
  ever flipped.
- The mutant was built with original.replace(op, ...), which changes the
  first occurrence of the operator text in the expression: one inside a
  string literal or comment, or the inner + of (a + b) + c.
- inFocus() searched the 2000 characters before an offset for a focus
  function's declaration. Code after a focus function was mutated as if it
  were inside it, and the end of a focus function longer than 2000
  characters was skipped.

Take the operator position from the token stream, visit
LogicalExpression too, compute the focus ranges from the AST, and check
the operator's own position against them. collectMutations() is exported
and main() runs only when the script is executed, directly or through a
symlink, so the collector can be tested directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant