refactor(test): reduce cognitive complexity of the test runners - #1756
Open
marcossevilla wants to merge 9 commits into
Open
marcossevilla wants to merge 9 commits into
marcossevilla wants to merge 9 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member
Author
|
@vgvbot /review-pr |
Contributor
Contributor
There was a problem hiding this comment.
Approve. This is a clean, careful refactor that significantly reduces cognitive complexity across the test runners while preserving behavior, log output, exit codes, and public APIs.
Verified correctness
CoverageMetrics.fromLcovRecords— the_add+_uncoveredLinesWithdecomposition preserves per-file accumulation (via[...?uncoveredLines[file], ...newLines]) and still filters by exclude globs the same way._TestEventReporterswitch — I confirmedTestEventinvery_good_test_runner 0.4.0isabstract class TestEvent(not sealed), with subtypesStartTestEvent,AllSuitesTestEvent,DebugTestEvent,ExitTestEventnot matched in the newswitch. In Dart, a non-exhaustiveswitchstatement (not expression) over a non-sealed type simply falls through on no-match — matching the original if-chain's silent ignore of those types.ExitTestEventcontinues to be handled explicitly in_testCommand._checkCoverage— the simplifiedif (uncoveredLines != null)is sound becauseuncoveredLinesis assigned non-null only whenoptions.showUncovered && coverageMetrics.uncoveredLines.isNotEmpty._reportDone—success = event.success ?? false; if (success) return;is equivalent to the originalif (event.success != true)(continues on null/false, returns on true)._writeLcovordering change for Dart+all— assert runs before enhance now;_convertDartCoverageToLcovcreates the file beforehand, so the invariant holds. The PR description already calls this out._enhanceLcovWithUntestedFiles—.map((r) => r.file).nonNulls.toSet()is equivalent towhere((r) => r.file != null).map((r) => r.file!).toSet(); the_untestedFileRecordLF/LH/DA/SF format and the (pre-existing) prefix-based line filter ('part'still matchingpartition) are preserved verbatim._runTests/_validateTargetextractions in both commands are mechanical and preserve exit-code semantics.
Observations (no action required)
_runTestsinDartTestCommand/TestCommandtakesrecursiveeven though it's only forwarded toTestCLIRunner.test— fine, keeps the extraction honest.- The near-identical
_validateTargetduplication betweenTestCommandandDartTestCommandis already called out in the PR description as a follow-up.
No correctness, security, or lifecycle issues found. The reasoning that existing tests already reach every new branch is believable given the refactor is purely structural, but CI should confirm.
Contributor
|
Heads up — I'm auto-updating this PR by merging the latest |
Fix the grammar of the dart lcov conversion comment, document that excludeFromCoverage holds space-separated globs, drop the unused async from the test event listener, and skip computing uncovered lines for lcov records without a source file.
TestCommand and DartTestCommand carried the same _validateTarget, only differing in the project name of the message. Move it to TestCLIRunner.validateTarget and align the run helpers on _runTests. The rest-argument tests read argResults.rest inside verify(), which made mocktail verify that getter instead of the runner call. They now verify the runner with a local list, since validateTarget reads rest up front.
With collect-coverage-from all, the untested-files step parsed --exclude-coverage as a single glob, so the documented '**/*.g.dart **/*.freezed.dart' excluded nothing. It also matched the absolute file path, which package:glob resolves against the process working directory rather than the package root. Parse the value the same way CoverageMetrics does and match the path relative to the package, as the lcov SF entries are.
TestCLIRunner wrote, converted and checked coverage through a chain of static helpers that each took the package root, the lcov path, the test type and the coverage options, and branched on the test type to tell dart and flutter runs apart. Coverage now lives in its own library. CoverageOptions holds the settings of a run, and a sealed CoverageReport per package owns the lcov file: FlutterCoverageReport keeps what flutter test wrote, while DartCoverageReport converts the json coverage of dart test. The runner picks the report once and no longer branches on the test type for coverage, including the argument that turns coverage collection on. CoverageMetrics, CoverageCollectionMode, MinCoverageNotMet and formatUncoveredLines move along with it, so lib/src/cli depends on lib/src/coverage and not the other way around. The CoverageMetrics tests move to test/src/coverage, next to new CoverageReport tests.
_TestEventReporter is a self-contained unit that turns test events into terminal output. Give it its own part of the cli library so test_cli_runner.dart only orchestrates the run.
--exclude-coverage has always accepted several space-separated globs, and the untested-files step now honors them too. The test and dart test help, the MCP test tool, the option dartdocs and the configuration docs still described it as a single glob, unlike site/docs/commands/test.md.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status
READY
Description
Reduces the cognitive complexity of the test runners, and moves coverage handling into its own library along the way.
TestCLIRunner.testhands each package to_testPackage. Test events go to_TestEventReporter, which switches on the event type and keeps the tallies and the list of failures. It now lives in its owntest_event_reporter.dartpart, sotest_cli_runner.dartonly orchestrates the run (719 → 415 lines).lib/src/coverage/).CoverageOptionsholds the settings of a run. A sealedCoverageReportper package ownscoverage/lcov.info:FlutterCoverageReportkeeps the fileflutter testwrites.DartCoverageReportconverts the json coverage ofdart testinto lcov.--coverageargument.CoverageMetrics,CoverageCollectionMode,MinCoverageNotMetandformatUncoveredLinesmove there too, solib/src/clidepends onlib/src/coverageand not the other way around..gitignorere-includeslib/src/coverage/andtest/src/coverage/, which itscoverage/rule for reports also matched.TestCommandandDartTestCommandshareTestCLIRunner.validateTarget, and both run their tests through_runTests. The arguments they forward become private getters on their options classes.Bug fix
With
--collect-coverage-from all, the step that adds untested files tolcov.infodidn't honor--exclude-coverage:'**/*.g.dart **/*.freezed.dart'excluded nothing.package:globresolves against the process's working directory instead of the package root. Recursive runs never excluded anything.It now parses the value the same way
CoverageMetricsdoes and matches paths relative to the package, like the lcovSF:entries. The threshold check was already correct, so only the contents oflcov.infochange.Other changes
lcov.infoexistence assert for dart runs withcollectCoverageFrom: allnow runs before the untested-files step. The file already exists at that point, and asserts are off in release builds.argResults.restinsideverify(), which made mocktail verify that getter instead of the runner call. They now verify the runner.Validation
TestCLIRunner.test_testCommandTestCommand.runCoverageMetrics.fromLcovRecordsDartTestCommand.run_enhanceLcovWithUntestedFiles(now_addUntestedFiles)cognitive_complexity0.2.4: compared withmain, nothing increased and nothing breaks the 15 ceiling (net delta -118). The highest score in any touched file is 11.undead0.1.1 (closed-app): no new dead declarations or privatization candidates.dedupe0.1.0: duplicate lines betweenTestCommandandDartTestCommanddrop from 784 to 755. The rest of that duplication predates this PR, and a shared base for the two commands would be a follow-up.api_summary) is unchanged. Every changed line is covered by tests. New tests coverCoverageReportand the exclude-coverage fix, and theCoverageMetricstests moved totest/src/coverage.The commits are split so they can be stacked as separate PRs if needed.
Found by the full cognitive complexity scan in #1755, which tests VeryGoodOpenSource/very_good_workflows#520. This is part of a three-PR cleanup. Once all three merge, the full scan on #1755 passes:
Type of Change
🤖 Generated with Claude Code