Coverde
A CLI for optimizing test execution and manipulating coverage trace files. Optimize tests, validate coverage, transform trace files, and generate HTML reports.
Index
- Installing
- Features
coverde.yamlconfiguration file- Usage with
melos - CI integration for coverage checks
Installing
You can make coverde globally available by executing the following command:
$ dart pub global activate coverde
NOTE: To run coverde directly from the terminal, add the system cache bin directory to your PATH environment variable.
Features
- Optimize tests by gathering them.
- Check the coverage value (%) computed from a trace file.
- Filter a coverage trace file.
- Transform a coverage trace file.
- Generate the coverage report from a trace file.
- Remove a set of files and folders.
- Compute the coverage value (%) of an info file.
coverde optimize-tests
Optimize tests by gathering them.
Note
Why use coverde optimize-tests?
The optimize-tests command gathers all your Dart test files into a single "optimized" test entry point. This can lead to much faster test execution, especially in CI/CD pipelines or large test suites. By reducing the Dart VM spawn overhead and centralizing test discovery, it enables more efficient use of resources.
For more information, see the flutter/flutter#90225.
Arguments
Single-options
-
--includeThe glob pattern for the tests files to include.
Default value:test/**_test.dart -
--excludeThe glob pattern for the tests files to exclude.
-
--outputThe path to the optimized tests file.
Default value:test/optimized_test.dart -
--total-shardsThe total number of shards to split tests across. Requires
--shard-indexto also be specified. -
--shard-indexThe index of the current shard (0-based). Requires
--total-shardsto also be specified.
Flags
-
--flutter-goldensWhether to use golden tests in case of a Flutter package.
Default value: Enabled
Examples
Basic usage
Given the following test files:
test/user_test.dart:
import 'package:test/test.dart';
void main() {
test('user test', () {
// test implementation
});
}
test/product_test.dart:
import 'package:test/test.dart';
void main() {
test('product test', () {
// test implementation
});
}
Running:
$ coverde optimize-tests
Output: test/optimized_test.dart
// ignore_for_file: deprecated_member_use, type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:test_api/test_api.dart';
import 'product_test.dart' as _i1;
import 'user_test.dart' as _i2;
void main() {
group(
'product_test.dart',
() {
_i1.main();
},
);
group(
'user_test.dart',
() {
_i2.main();
},
);
}
Preserving Test Annotations
The command preserves test annotations from individual test files. Given the following test files with annotations:
test/slow_test.dart:
@Timeout(Duration(seconds: 45))
import 'package:test/test.dart';
void main() {
test('slow test', () {
// long-running test
});
}
test/skipped_test.dart:
@Skip('Temporarily disabled')
import 'package:test/test.dart';
void main() {
test('skipped test', () {
// test implementation
});
}
test/tagged_test.dart:
@Tags(['integration', 'slow'])
import 'package:test/test.dart';
void main() {
test('tagged test', () {
// test implementation
});
}
test/vm_only_test.dart:
@TestOn('vm')
import 'package:test/test.dart';
void main() {
test('VM-only test', () {
// test implementation
});
}
Running:
$ coverde optimize-tests --no-flutter-goldens
Output: test/optimized_test.dart
// ignore_for_file: deprecated_member_use, type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:test_api/test_api.dart';
import 'skipped_test.dart' as _i1;
import 'slow_test.dart' as _i2;
import 'tagged_test.dart' as _i3;
import 'vm_only_test.dart' as _i4;
void main() {
group(
'skipped_test.dart',
() {
_i1.main();
},
skip: 'Temporarily disabled',
);
group(
'slow_test.dart',
() {
_i2.main();
},
timeout: Timeout(Duration(seconds: 45)),
);
group(
'tagged_test.dart',
() {
_i3.main();
},
tags: ['integration', 'slow'],
);
group(
'vm_only_test.dart',
() {
_i4.main();
},
testOn: 'vm',
);
}
The following annotations are supported and preserved:
@Skip()or@Skip('reason')→skip: trueorskip: 'reason'@Timeout(...)→timeout: Timeout(...)@Tags([...])→tags: [...]@TestOn('...')→testOn: '...'@OnPlatform({...})→onPlatform: {...}
Excluding Test Files
To exclude certain test files using a glob pattern:
$ coverde optimize-tests --exclude "**/*_integration_test.dart"
This will gather all test files matching the default include pattern (test/**_test.dart) except those matching the exclude pattern.
Disabling Flutter Golden Tests
For non-Flutter packages or when golden tests are not needed:
$ coverde optimize-tests --no-flutter-goldens
This prevents the command from adding golden test setup code, which is only relevant for Flutter packages.
Sharding Tests
For large test suites, you can split tests across multiple shards to run them in parallel in different processes or CI jobs. Sharding requires both --total-shards and --shard-index options to be specified together.
Example: Splitting 4 test files across 2 shards
Given the following test files (sorted alphabetically):
test/auth_test.dart(index 0)test/order_test.dart(index 1)test/product_test.dart(index 2)test/user_test.dart(index 3)
Tests are distributed using round-robin assignment by index: index % <total shards> == <shard index>.
To run shard 0 (gets indices 0 and 2):
$ coverde optimize-tests --total-shards=2 --shard-index=0 --output=test/optimized_test_shard_0.dart
Output: test/optimized_test_shard_0.dart
// ignore_for_file: deprecated_member_use, type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:test_api/test_api.dart';
import 'auth_test.dart' as _i1;
import 'product_test.dart' as _i2;
void main() {
group(
'auth_test.dart',
() {
_i1.main();
},
);
group(
'product_test.dart',
() {
_i2.main();
},
);
}
To run shard 1 (gets indices 1 and 3):
$ coverde optimize-tests --total-shards=2 --shard-index=1 --output=test/optimized_test_shard_1.dart
Output: test/optimized_test_shard_1.dart
// ignore_for_file: deprecated_member_use, type=lint
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:test_api/test_api.dart';
import 'order_test.dart' as _i1;
import 'user_test.dart' as _i2;
void main() {
group(
'order_test.dart',
() {
_i1.main();
},
);
group(
'user_test.dart',
() {
_i2.main();
},
);
}
In a CI/CD pipeline, you could run both shards in parallel:
# Job 1
$ dart test test/optimized_test_shard_0.dart
# Job 2 (running concurrently)
$ dart test test/optimized_test_shard_1.dart
Sharding with 3 shards:
# Shard 0
$ coverde optimize-tests --total-shards=3 --shard-index=0 --output=test/optimized_test_0.dart
# Shard 1
$ coverde optimize-tests --total-shards=3 --shard-index=1 --output=test/optimized_test_1.dart
# Shard 2
$ coverde optimize-tests --total-shards=3 --shard-index=2 --output=test/optimized_test_2.dart
Notes:
- Shard indices are 0-based (start from 0)
- The
--shard-indexmust be less than--total-shards - Tests are distributed as evenly as possible across shards
- Both
--total-shardsand--shard-indexmust be provided together; specifying only one will result in an error
coverde check
Check the coverage value (%) computed from a trace file.
The unique argument should be an integer between 0 and 100.
This parameter indicates the minimum value for the coverage to be accepted.
Arguments
Single-options
-
--inputTrace file used for the coverage check.
Default value:coverage/lcov.info -
--file-coverage-log-levelThe log level for the coverage value for each source file listed in the
inputinfo file.
Default value:line-content
Allowed values:none: Log nothing.overview: Log the overview of the coverage value for the file.line-numbers: Log only the uncovered line numbers.line-content: Log the uncovered line numbers and their content.
Parameters
-
min-coverageThe minimum coverage value to be accepted. It should be an integer between 0 and 100.
Examples

coverde filter
Caution
The filter command will be removed in the next major update. Use coverde transform instead.
Filter a coverage trace file.
Filter the coverage info by ignoring data related to files with paths that matches the given FILTERS.
The coverage data is taken from the INPUT_LCOV_FILE file and the result is appended to the OUTPUT_LCOV_FILE file.
All the relative paths in the resulting coverage trace file will be resolved relative to the
Arguments
Single-options
-
--inputOrigin coverage info file to pick coverage data from.
Default value:coverage/lcov.info -
--outputDestination coverage info file to dump the resulting coverage data into.
Default value:coverage/filtered.lcov.info -
--base-directoryBase directory relative to which trace file source paths are resolved.
-
--modeThe mode in which the OUTPUT_LCOV_FILE can be generated.
Default value:a
Allowed values:a: Append filtered content to the OUTPUT_LCOV_FILE content, if any.w: Override the OUTPUT_LCOV_FILE content, if any, with the filtered content.
Multi-options
-
--filtersSet of comma-separated path patterns of the files to be ignored.
Each pattern must be a valid regex expression. Invalid patterns will cause the command to fail.
Default value: None
Migration to transform
Use coverde transform with equivalent options:
| Filter option | Transform equivalent |
|---|---|
--filters pattern1,pattern2 |
--transformations skip-by-regex=pattern1 --transformations skip-by-regex=pattern2 |
--base-directory B |
--transformations relative=B |
--input, --output, --mode |
Same options |
Example:
# Before (filter)
coverde filter --input coverage/lcov.info --output coverage/filtered.lcov.info \
--filters '\.g\.dart$' --base-directory /project --mode w
# After (transform)
coverde transform --input coverage/lcov.info --output coverage/filtered.lcov.info \
--transformations skip-by-regex='\.g\.dart$' --transformations relative=/project --mode w
coverde transform
Transform a coverage trace file.
Apply a sequence of transformations to the coverage data.
The coverage data is taken from the INPUT_LCOV_FILE file and written to the OUTPUT_LCOV_FILE file.
Presets can be defined in coverde.yaml under the transformations key.
Arguments
Single-options
-
--inputOrigin coverage info file to transform.
Default value:coverage/lcov.info -
--outputDestination coverage info file to dump the transformed coverage data into.
Default value:coverage/transformed.lcov.info -
--modeThe mode in which the OUTPUT_LCOV_FILE can be generated.
Default value:a
Allowed values:a: Append transformed content to the OUTPUT_LCOV_FILE content, if any.w: Override the OUTPUT_LCOV_FILE content, if any, with the transformed content.
Multi-options
-
--transformationsTransformation steps to apply in order.
Default value: None
Allowed values:keep-by-regex=<regex>: Keep files that match the<regex>.skip-by-regex=<regex>: Skip files that match the<regex>.keep-by-glob=<glob>: Keep files that match the<glob>.skip-by-glob=<glob>: Skip files that match the<glob>.keep-by-coverage=<comparison>: Keep files that match the<comparison>(with reference values between 0 and 100).skip-by-coverage=<comparison>: Skip files that match the<comparison>(with reference values between 0 and 100).relative=<base-path>: Rewrite file paths to be relative to the<base-path>.preset=<name>: Expand a preset fromcoverde.yaml.
Flags
-
--explainPrint the resolved transformation list and exit without modifying files.
Default value: Disabled
Examples
Inline Transformations
$ coverde transform \
--transformations relative="/packages/my_package/" \
--transformations keep-by-glob="lib/**" \
--transformations skip-by-glob="**/*.g.dart" \
--transformations keep-by-coverage="lte|80"
This transformation chain performs the following steps:
- Rewrite file paths to be relative to the
/packages/my_package/directory (useful for monorepos). - Keep files that match the
lib/**glob pattern, i.e. implementation files. - Skip files that match the
**/*.g.dartglob pattern, i.e. generated files. - Keep files that have a coverage value less than or equal to 80%.
Preset Usage
Given the following coverde.yaml configuration:
# coverde.yaml
transformations:
implementation-without-generated:
- type: keep-by-glob
glob: "lib/**"
- type: skip-by-glob
glob: "**/*.g.dart"
Running:
$ coverde transform \
--transformations relative="/packages/my_package/" \
--transformations preset=implementation-without-generated \
--transformations keep-by-coverage="lte|80"
Is equivalent to the Inline Transformations example.
coverde report
Generate the coverage report from a trace file.
Generate the coverage report inside REPORT_DIR from the TRACE_FILE trace file.
Arguments
Single-options
-
--inputCoverage trace file to be used for the coverage report generation.
Default value:coverage/lcov.info -
--outputDestination directory where the generated coverage report will be stored.
Default value:coverage/html/ -
--mediumMedium threshold.
Must be a number between 0 and 100, and must be less than the high threshold.
Default value:75 -
--highHigh threshold.
Must be a number between 0 and 100, and must be greater than the medium threshold.
Default value:90
Flags
-
--launchLaunch the generated report in the default browser. This option is only supported on desktop platforms. (defaults to off)
Default value: Disabled
Examples

coverde remove
Remove a set of files and folders.
Arguments
Flags
-
--dry-runPreview what would be deleted without actually deleting. When enabled (default), the command will list what would be deleted but not perform the deletion. When disabled, the command will actually delete the specified files and folders.
Default value: Enabled -
--accept-absenceAccept absence of a file or folder. When an element is not present:
- If enabled, the command will continue.
- If disabled, the command will fail.
Default value: Enabled
Parameters
-
pathsSet of file and/or directory paths to be removed.
coverde value
Compute the coverage value (%) of an info file.
Compute the coverage value of the LCOV_FILE info file.
Arguments
Single-options
-
--inputCoverage info file to be used for the coverage value computation.
Default value:coverage/lcov.info -
--file-coverage-log-levelThe log level for the coverage value for each source file listed in the LCOV_FILE info file.
Default value:line-content
Allowed values:none: Log nothing.overview: Log the overview of the coverage value for the file.line-numbers: Log only the uncovered line numbers.line-content: Log the uncovered line numbers and their content.
Examples

coverde.yaml configuration file
The coverde.yaml file allows you to define reusable transformation presets for the coverde transform command.
The file is optional and is read from the current working directory when you run coverde transform (typically your project root).
File Format
The configuration file uses YAML format with a transformations key at the root level. Each preset is defined as a named list of transformation steps.
# coverde.yaml
transformations:
preset-name:
- type: <transformation-type>
<parameter-1-name>: <value-1>
<parameter-2-name>: <value-2>
- type: <transformation-type>
<parameter-1-name>: <value-1>
Transformation Types
Each transformation step requires a type field and the corresponding parameters.
keep-by-regex
Keep files whose paths match the provided regular expression pattern.
type: keep-by-regex
regex: <regex-pattern> # Example: "^lib/.*\\.dart$"
skip-by-regex
Skip (exclude) files whose paths match the provided regular expression pattern.
type: skip-by-regex
regex: <regex-pattern> # Example: "^test/.*_integration\\.dart$"
keep-by-glob
Keep files whose paths match the provided glob pattern.
type: keep-by-glob
glob: <glob-pattern> # Example: "**/lib/**"
skip-by-glob
Skip (exclude) files whose paths match the provided glob pattern.
type: skip-by-glob
glob: <glob-pattern> # Example: "**/*.g.dart"
keep-by-coverage
Keep files whose coverage meets the specified comparison.
type: keep-by-coverage
comparison: <comparison> # Example: "lte|80" (less than or equal to 80%)
skip-by-coverage
Skip (exclude) files whose coverage meets the specified comparison.
type: skip-by-coverage
comparison: <comparison> # Example: "gt|50" (greater than 50%)
See Comparison Operators for the allowed comparison operators.
relative
Rewrite file paths so that they are relative to the given base path.
type: relative
base-path: <base-path> # Example: "/path/to/project"
preset
Include transformations defined in another preset by name.
Caution
Circular references between presets are detected and will result in an error.
type: preset
name: <other-preset-name> # Example: "production-only"
Comparison Operators
eq
eq|<value>
Checks if the value is equal to <value>.
neq
neq|<value>
Checks if the value is not equal to <value>.
gt
gt|<value>
Checks if the value is greater than <value>.
gte
gte|<value>
Checks if the value is greater than or equal to <value>.
lt
lt|<value>
Checks if the value is less than <value>.
lte
lte|<value>
Checks if the value is less than or equal to <value>.
in
in|<range>
Checks if the value is within the specified <range>.
The <range> can be one of the following:
[lowerValue,upperValue](lowerValue,upperValue][lowerValue,upperValue)(lowerValue,upperValue)
The [ and ] brackets indicate that the lower and upper bounds are inclusive, while the ( and ) parentheses indicate that the lower and upper bounds are exclusive.
Both lower and upper bounds should be between 0 and 100 inclusive, as they are coverage percentages.
Example Configuration
# coverde.yaml
transformations:
# Exclude generated code files
exclude-generated:
- type: skip-by-glob
glob: "**/*.g.dart"
- type: skip-by-glob
glob: "**/*.freezed.dart"
- type: skip-by-glob
glob: "**/*.gen.dart"
# Keep only production code (lib folder), excluding generated files
production-only:
- type: keep-by-glob
glob: "**/lib/**"
- type: preset
name: exclude-generated
# Filter to files with high coverage
high-coverage-only:
- type: keep-by-coverage
comparison: "gte|80"
# Common CI workflow preset
ci-workflow:
- type: preset
name: production-only
- type: relative
base-path: "/path/to/project"
Using Presets
Once defined, presets can be used with the coverde transform command.
# Use a single preset
$ coverde transform --transformations preset=exclude-generated
# Combine presets with inline transformations
$ coverde transform \
--transformations preset=production-only \
--transformations skip-by-coverage="gt|50"
# Preview transformations without applying them
$ coverde transform --transformations preset=ci-workflow --explain
Update checks
If coverde is installed via dart pub global activate --source=hosted, i.e. as a global package from the Pub.dev, it can prompt the user to update the package if a new compatible version is available.
This process verifies that the new release has a higher SemVer version than the current one, and that its environment constraints are met by the current Dart SDK.
--update-check
The update check mode to use.
Default value: enabled
Allowed values:
disabled: Disable the update check.enabled: Enable the update check with silent output, only prompting the user if an update is available, and ignoring any warnings and errors.enabled-verbose: Enable the update check with verbose output.
Usage with melos
If your project uses melos to manage its multi-package structure, coverde can help optimize test execution and collect test coverage data in a unified trace file.
Here are some examples of how to use coverde with melos to manage your monorepo. Adapt them to match your project structure and needs.
Test Optimization
Optimize test execution by gathering all test files into a single optimized test file before running tests:
test:
description: Run tests and generate coverage trace file for a package
run: >
dart run coverde optimize-tests
--output=test/optimized_test.dart
&&
dart test
--coverage-path=coverage/lcov.info
--test-randomize-ordering-seed random
test/optimized_test.dart
packageFilters:
dependsOn:
- test
dirExists:
- test
This script:
- Optimizes tests by gathering them into a single file
- Runs the optimized test file with coverage collection, directly outputting an LCOV trace file
Coverage Data Merging
Merge coverage trace files from all packages into a unified trace file:
coverage.merge:
description: Merge all packages coverage trace files
run: >
dart run coverde remove
--no-dry-run
MELOS_ROOT_PATH/coverage/filtered.lcov.info
&&
melos exec
--file-exists coverage/lcov.info
--
"
dart run coverde transform
--input coverage/lcov.info
--output MELOS_ROOT_PATH/coverage/filtered.lcov.info
--transformations relative=MELOS_ROOT_PATH
--transformations skip-by-glob='**/*.g.dart'
"
This script:
- Removes any existing merged coverage file
- Executes
coverde transformfor each package that contains acoverage/lcov.infofile:- Rewrites file paths to be relative to the monorepo root (using Melos’s
MELOS_ROOT_PATHenvironment variable) - Skips files that match the
**/*.g.dartglob pattern, i.e. generated files.
- Rewrites file paths to be relative to the monorepo root (using Melos’s
The resulting merged trace file can be used with coverde report to generate a unified HTML coverage report for the entire monorepo, or with coverde check to validate the coverage threshold for the overall project.
Coverage Check
Validate minimum coverage threshold across all packages:
coverage.check:
description: Check test coverage
run: >
dart run coverde check
--input MELOS_ROOT_PATH/coverage/filtered.lcov.info
100
This script checks that the merged coverage trace file meets the minimum coverage threshold (100% in this example). The command will fail if coverage is below the threshold, making it suitable for CI/CD pipelines.
CI integration for coverage checks
If your project uses GitHub Actions for CI, you might already know very_good_coverage, which offers a simple but effective method for coverage validation.
However, adding coverage checks to CI workflows hosted by other alternatives is not always that straightforward.
To solve this, after enabling Dart or Flutter in your CI workflow, according to your project requirements, you can use coverde and its check tool by adding the following commands to your workflow steps:
dart pub global activate coverdecoverde check <min_coverage>
Bugs or Requests
If you encounter any problems or you believe the CLI is missing a feature, feel free to open an issue on GitHub.
Pull requests are also welcome. See CONTRIBUTING.md.
Libraries
- coverde
- Coverde CLI package.