Skip to content

Commit

Permalink
Merge pull request #19 from emanuel-braz/develop
Browse files Browse the repository at this point in the history
Add filter by pattern feature
  • Loading branch information
emanuel-braz authored Mar 22, 2022
2 parents 65fbd6c + 89c3311 commit b78e0dd
Show file tree
Hide file tree
Showing 23 changed files with 348 additions and 57 deletions.
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@
"program": "bin/dlcov.dart",
"flutterMode": "debug",
"args": ["gen-refs"]
},
{
"name": "content patterns",
"request": "launch",
"type": "dart",
"program": "bin/dlcov.dart",
"flutterMode": "debug",
"args": ["gen-refs", "--exclude-files=*/usecases/*,*config_m*", "--exclude-contents=\"coverageParam\""]
},
{
"name": "content patterns path",
"request": "launch",
"type": "dart",
"program": "bin/dlcov.dart",
"flutterMode": "debug",
"args": ["gen-refs", "--exclude-files=*/usecases/*,*config_m*", "--exclude-contents-path=patterns.txt" ]
}
]
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# [4.2.0] 2022-03-21
#### Added
- add --exclude-files parameter
- add --exclude-contents parameter
- add --exclude-contents-path parameter

# [4.0.1] 2022-03-20
#### Chore
- refactoring code
Expand Down
52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
## DLCOV - CLI to verify code coverage threshold (CI/CD, git hooks, etc.)

### Main features:

#### - It can be used in shell scripts, github actions, git hooks, etc, to block the app build if the code coverage is less than the value you enter as the minimum threshold.

#### - Include files in code coverage that Flutter does not natively include, improving the accuracy of code coverage threshold values.
> ℹ️ Flutter (test --coverage) doesn't include files that weren't referenced in tests, which can give a false impression of greater code coverage than it actually is, ONLY if you don't take this implicit detail into account.
#### - Remove unwanted files from code coverage reports using the following filters:
- By file name suffix
- By regular expression in the filename
- By the content/code inside the file

#### - It keeps a log of the tests in a file, being able to observe if the code coverage increased or decreased with each verification.

#### - Allows the generation of the `lcov.info` file by external tools, without direct dependencies to the Flutter SDK or other technologies.

----
### Usage Example
`dlcov --coverage=80 --include-untested-files=true --lcov-gen="flutter test --coverage" --log=true`

Expand All @@ -9,38 +26,57 @@
- `--lcov-gen="flutter test --coverage"` Generate `lcov.info` through the command "flutter test --coverage", it can be used with "test_cov" for dart only for example, or others if you prefer.
- `--log=true` Log every test coverage info in dlcov.log - Limit up to 1000 lines


### Install
`pub global activate dlcov`
`pub global activate dlcov` and later `dlcov -c 80`, `pub global run dlcov -c 80`
or add as dev dependency to `pubspec.yaml`

```yaml
dev_dependencies:
dlcov: 4.0.1
```
dlcov: 4.2.0

#### Uses
`dlcov`, `pub run dlcov` or `pub global run dlcov`
<br/>
# pub run dlcov -c 80
```


#### Verify if code coverage threshold is equal or greater than 80.0%
`dlcov gen-refs && flutter test --coverage && dlcov -c 80`
`dlcov -c 80 --lcov-gen="flutter test --coverage" --include-untested-files=true`
`dlcov -c 80 --lcov-gen="test_cov" --include-untested-files=true`

----
## Parameters availables
| Long | Short | Mandatory | Default | Sample | Description |
|---|---|---|---|---|---|
| --coverage | -c | false | 0 | 80.0 | min coverage threshold |
| --log | -l | false | false | true | Log every test coverage info in dlcov.log - Limit up to 1000 lines |
| --exclude-suffix | -e | false | .g.dart,.freezed.dart | .g.dart | Remove generated or other files from test coverage results, separated by commas |
| --include-untested-files | | false | false | true | Get reports more coherent with reality, and do not ignore untested files during the analysis |
| --lcov-gen | | false | | "flutter test --coverage" | Generate `lcov.info` through the command "flutter test --coverage", it can be used with "test_cov" for dart only for example, or others if you prefer |
| --exclude-suffix | -e | false | .g.dart,.freezed.dart | .g.dart | Exclude files from test coverage results, using suffixes, separated by commas |
| --exclude-files | | false | | "\*widget\\.dart,\*\_part\_\*" | Exclude all files from test coverage results, with path names that contains "\_part\_" and path names that ends with "widget.dart" |
| --exclude-contents | | false | | "class\*extends StatefulWidget\*" | Exclude all files from test coverage results if those files contains this patterns. In the example it exclude all files that contains classes that extends StatefulWidget |
| --exclude-contents-path | | false | | "./patterns_list.txt" | Path to file that contains all patterns that will be used to exclude all files from test coverage results (This overrides `--exclude-contents`) |

Examples how to removing/ignoring files from code coverage reports:
`dlcov --exclude-contents-path=example/patterns.txt -c 80 --lcov-gen="flutter test --coverage" --include-untested-files=true`

`dlcov --exclude-contents="class*extends StatefulWidget*" -c 80 --lcov-gen="flutter test --coverage" --include-untested-files=true`

`dlcov --exclude-files="*\.g\.dart" -c 80 --lcov-gen="flutter test --coverage" --include-untested-files=true`

`dlcov --exclude-suffix=".g.dart" -c 80 --lcov-gen="flutter test --coverage" --include-untested-files=true`

---


| Command | Description |
|---|---|
| gen-refs | Generate tested and untested file references, it should be used before `lcov.info` file generation step.|
| gen-refs | It generate test file references for all dart files under lib folder, it should be used before lcov.info file generation step. And it can be filtered, using additional parameters.|
Examples:
`dlcov gen-refs --exclude-contents-path=example/patterns.txt`
`dlcov gen-refs --exclude-files="lib/usecases/*,*/models/*,*\.g\.dart"` _all files under "lib/usecases/" folder, all files with "models" in the path, all files that contains ".g.dart"_

----
### Github actions

if the test coverage is less than 80, it stop the pipeline here, and abort the actions
Expand Down
11 changes: 5 additions & 6 deletions bin/dlcov.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:args/args.dart';
import 'package:dlcov/core/app_constants.dart';
import 'package:dlcov/core/commands/app_command.dart';
import 'package:dlcov/entities/config.dart';
import 'package:dlcov/repositories/config_repository.dart';
import 'package:dlcov/repositories/record_repository.dart';
Expand All @@ -10,7 +11,6 @@ import 'package:dlcov/usecases/get_lcov.dart';
import 'package:dlcov/usecases/get_records.dart';
import 'package:dlcov/usecases/parse_arguments.dart';
import 'package:dlcov/usecases/verify_coverage.dart';
import 'package:dlcov/utils/commands/app_command.dart';
import 'package:dlcov/utils/file_system/file_system_util.dart';
import 'package:dlcov/utils/process_util.dart';

Expand All @@ -30,11 +30,10 @@ void main(List<String> arguments) async {
if (config.includeUntestedFiles) {
// Create references for all dart files
await CreateFileReferences(
CreateFileReferencesHelper(FileSystemUtil()),
AppConstants.sourceDirectory,
config.excludeSuffixes,
config.packageName)
.call();
CreateFileReferencesHelper(FileSystemUtil()),
AppConstants.sourceDirectory,
config,
).call();
} else {
// Delete references for all dart files
await DeleteFileReferences(FileSystemUtil()).call();
Expand Down
3 changes: 3 additions & 0 deletions example/patterns.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class*extends StatefulWidget*
class*extends StatelessWidget*
class GetConfig*
12 changes: 12 additions & 0 deletions lib/core/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ class AppConstants {
static String argLongPackageName = 'package-name';
static String argIncludeUntestedFiles = 'include-untested-files';
static String argLcovGen = 'lcov-gen';
static String argLongExcludeFiles = 'exclude-files';
static String argLongExcludeFilesHelp =
'the patterns used to exclude files from test coverage';
static String argLongExcludeContents = 'exclude-contents';
static String argLongExcludeContentsHelp =
'the patterns used to exclude files from test coverage by it\'s content(code) e.g: "*extends StatefulWidget*" remove all files that have statefulwidget implementations.';

static String argLongExcludeContentsPath = 'exclude-contents-path';
static String argLongExcludeContentsPathHelp =
'Path to file that contains all patterns separated by break line';

static String argHelpDescription = 'Show help informations';

Expand All @@ -31,4 +41,6 @@ class AppConstants {
static String dlcovFileReferences = 'test/dlcov_references_test.dart';

static String sourceDirectory = 'lib';

static String fileNotFound = 'Error: File not found!';
}
7 changes: 7 additions & 0 deletions lib/core/app_error_codes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AppErrorCodes {
static int noError = 0;
static int generalError = 1;
static int commandCannotExecute = 126;
static int commandNotFound = 127;
static int fatalError = 128;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:args/command_runner.dart';
import '../../core/app_constants.dart';
import '../../entities/config.dart';
import '../../usecases/create_file_references.dart';
import '../file_system/file_system_util.dart';
import '../../utils/file_system/file_system_util.dart';
import 'gen_refs_command.dart';

/// AppCommand
Expand All @@ -19,7 +19,6 @@ class AppCommand {
createFileReferences: CreateFileReferences(
CreateFileReferencesHelper(FileSystemUtil()),
AppConstants.sourceDirectory,
config.excludeSuffixes,
config.packageName)));
config)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:args/command_runner.dart';
import '../../core/app_constants.dart';
import '../../entities/config.dart';
import '../../usecases/create_file_references.dart';
import '../app_error_codes.dart';

class GenRefsCommand extends Command {
final Config config;
Expand All @@ -23,12 +24,26 @@ class GenRefsCommand extends Command {
abbr: AppConstants.argShortPackageName,
defaultsTo: null,
mandatory: false);

argParser.addOption(AppConstants.argLongExcludeSuffix,
abbr: AppConstants.argShortExcludeSuffix,
defaultsTo: AppConstants.excludeSuffixDefaultValue,
mandatory: false);

argParser.addMultiOption(AppConstants.argLongExcludeFiles,
splitCommas: true, help: AppConstants.argLongExcludeFilesHelp);

argParser.addMultiOption(AppConstants.argLongExcludeContents,
splitCommas: true, help: AppConstants.argLongExcludeContentsHelp);

argParser.addOption(AppConstants.argLongExcludeContentsPath,
help: AppConstants.argLongExcludeContentsPathHelp, mandatory: false);
}

/// excute run createFileReferences
@override
void run() async {
await createFileReferences();
exit(0);
exit(AppErrorCodes.noError);
}
}
5 changes: 5 additions & 0 deletions lib/core/extensions/list_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extension ListExtension on List {
Iterable<RegExp> mapRegex() =>
map((pattern) => '^${pattern.replaceAll('*', r'(.*)?')}\$')
.map((pattern) => RegExp(pattern));
}
37 changes: 37 additions & 0 deletions lib/entities/config.a.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:args/args.dart';

/// Config entity
class Config {
final double percentage;
final List<String> excludeSuffixes;
final bool log;
final String? packageName;
final ArgResults? command;

/// file name patterns
List<RegExp> excludeFiles;

/// file content patterns
List<RegExp> excludeContents;

// If should include untested files to report
final bool includeUntestedFiles;

/// [lcovGen]
final String? lcovGen;

/// [excludeContentsPath]
final String? excludeContentsPath;

Config(
{required this.percentage,
required this.excludeSuffixes,
required this.includeUntestedFiles,
required this.excludeFiles,
required this.excludeContents,
required this.log,
this.packageName,
this.command,
this.lcovGen,
this.excludeContentsPath});
}
15 changes: 14 additions & 1 deletion lib/entities/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,30 @@ class Config {
final String? packageName;
final ArgResults? command;

/// file name patterns
List<RegExp> excludeFiles;

/// file content patterns
List<RegExp> excludeContents;

// If should include untested files to report
final bool includeUntestedFiles;

/// [lcovGen]
final String? lcovGen;

/// [excludeContentsPath]
final String? excludeContentsPath;

Config(
{required this.percentage,
required this.excludeSuffixes,
required this.includeUntestedFiles,
required this.excludeFiles,
required this.excludeContents,
required this.log,
this.packageName,
this.command,
this.lcovGen});
this.lcovGen,
this.excludeContentsPath});
}
45 changes: 36 additions & 9 deletions lib/entities/lcov.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'dart:io';

import 'package:dlcov/core/extensions/list_extension.dart';
import 'package:dlcov/utils/file_matcher_util.dart';
import 'package:lcov_parser/lcov_parser.dart';

import '../core/app_constants.dart';
import '../utils/file_system/file_system_util.dart';
import 'config.dart';
import 'coverage.dart';

Expand All @@ -27,9 +30,40 @@ class Lcov {
Lcov({required this.config, required this.records}) {
try {
coverage = Coverage(config.percentage);
final fileMatcher = FileMatcherUtil();
final fileSystemUtil = FileSystemUtil();

records =
records.where((element) => !hasSuffix(element.file ?? '')).toList();
records = records
.where((record) => !fileMatcher.hasSuffix(
file: record.file ?? '', excludeSuffixes: config.excludeSuffixes))
.where((record) => !fileMatcher.hasPattern(
value: record.file ?? '', patterns: config.excludeFiles))
.toList();

if (config.excludeContentsPath != null) {
final excludeContentsByPathList = fileSystemUtil
.readAsLinesSync(config.excludeContentsPath!)
.mapRegex()
.toList(growable: false);

records = records.where((record) {
if (record.file == null) return true;
final file = File(record.file!);
final loc = file.readAsLinesSync();
final hasPatterns = fileMatcher.hasPatterns(
values: loc, patterns: excludeContentsByPathList);
return !hasPatterns;
}).toList();
} else if (config.excludeContents.isNotEmpty) {
records = records.where((record) {
if (record.file == null) return true;
final file = File(record.file!);
final loc = file.readAsLinesSync();
final hasPatterns = fileMatcher.hasPatterns(
values: loc, patterns: config.excludeContents);
return !hasPatterns;
}).toList();
}

records.forEach(updateTotals);

Expand All @@ -45,13 +79,6 @@ class Lcov {
}
}

/// Check if has suffix
bool hasSuffix(String value) {
final matchList =
config.excludeSuffixes.where((element) => value.endsWith(element));
return matchList.isNotEmpty;
}

/// Update totals
void updateTotals(Record rec) {
totalFinds += rec.lines?.found ?? 0;
Expand Down
Loading

0 comments on commit b78e0dd

Please sign in to comment.