LCOV - code coverage report
Current view: top level - src/cli - cli.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 46 46 100.0 %
Date: 2024-03-25 10:36:11 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'package:collection/collection.dart';
       3             : import 'package:glob/glob.dart';
       4             : import 'package:lcov_parser/lcov_parser.dart';
       5             : import 'package:mason/mason.dart';
       6             : import 'package:meta/meta.dart';
       7             : import 'package:path/path.dart' as p;
       8             : import 'package:pubspec_parse/pubspec_parse.dart';
       9             : import 'package:universal_io/io.dart';
      10             : import 'package:very_good_cli/src/commands/test/templates/test_optimizer_bundle.dart';
      11             : import 'package:very_good_test_runner/very_good_test_runner.dart';
      12             : 
      13             : part 'dart_cli.dart';
      14             : 
      15             : part 'flutter_cli.dart';
      16             : 
      17             : part 'git_cli.dart';
      18             : 
      19             : const _asyncRunZoned = runZoned;
      20             : 
      21             : /// Type definition for [Process.run].
      22             : typedef RunProcess = Future<ProcessResult> Function(
      23             :   String executable,
      24             :   List<String> arguments, {
      25             :   String? workingDirectory,
      26             :   bool runInShell,
      27             : });
      28             : 
      29             : /// This class facilitates overriding [Process.run].
      30             : /// It should be extended by another class in client code with overrides
      31             : /// that construct a custom implementation.
      32             : @visibleForTesting
      33             : abstract class ProcessOverrides {
      34           3 :   static final _token = Object();
      35             : 
      36             :   /// Returns the current [ProcessOverrides] instance.
      37             :   ///
      38             :   /// This will return `null` if the current [Zone] does not contain
      39             :   /// any [ProcessOverrides].
      40             :   ///
      41             :   /// See also:
      42             :   /// * [ProcessOverrides.runZoned] to provide [ProcessOverrides]
      43             :   /// in a fresh [Zone].
      44             :   ///
      45           1 :   static ProcessOverrides? get current {
      46           3 :     return Zone.current[_token] as ProcessOverrides?;
      47             :   }
      48             : 
      49             :   /// Runs [body] in a fresh [Zone] using the provided overrides.
      50           1 :   static R runZoned<R>(
      51             :     R Function() body, {
      52             :     RunProcess? runProcess,
      53             :   }) {
      54           1 :     final overrides = _ProcessOverridesScope(runProcess);
      55           3 :     return _asyncRunZoned(body, zoneValues: {_token: overrides});
      56             :   }
      57             : 
      58             :   /// The method used to run a [Process].
      59           1 :   RunProcess get runProcess => Process.run;
      60             : }
      61             : 
      62             : class _ProcessOverridesScope extends ProcessOverrides {
      63           1 :   _ProcessOverridesScope(this._runProcess);
      64             : 
      65             :   final ProcessOverrides? _previous = ProcessOverrides.current;
      66             :   final RunProcess? _runProcess;
      67             : 
      68           1 :   @override
      69             :   RunProcess get runProcess {
      70           4 :     return _runProcess ?? _previous?.runProcess ?? super.runProcess;
      71             :   }
      72             : }
      73             : 
      74             : /// Abstraction for running commands via command-line.
      75             : class _Cmd {
      76             :   /// Runs the specified [cmd] with the provided [args].
      77           1 :   static Future<ProcessResult> run(
      78             :     String cmd,
      79             :     List<String> args, {
      80             :     required Logger logger,
      81             :     bool throwOnError = true,
      82             :     String? workingDirectory,
      83             :   }) async {
      84           2 :     logger.detail('Running: $cmd with $args');
      85           2 :     final runProcess = ProcessOverrides.current?.runProcess ?? Process.run;
      86           1 :     final result = await runProcess(
      87             :       cmd,
      88             :       args,
      89             :       workingDirectory: workingDirectory,
      90             :       runInShell: true,
      91             :     );
      92             :     logger
      93           3 :       ..detail('stdout:\n${result.stdout}')
      94           3 :       ..detail('stderr:\n${result.stderr}');
      95             : 
      96             :     if (throwOnError) {
      97           1 :       _throwIfProcessFailed(result, cmd, args);
      98             :     }
      99             :     return result;
     100             :   }
     101             : 
     102           1 :   static Iterable<Future<T>> runWhere<T>({
     103             :     required Future<T> Function(FileSystemEntity) run,
     104             :     required bool Function(FileSystemEntity) where,
     105             :     String cwd = '.',
     106             :   }) {
     107             :     final directories =
     108           4 :         Directory(cwd).listSync(recursive: true).where(where).toList()
     109           2 :           ..sort((a, b) {
     110             :             /// Linux and macOS have different sorting behaviors
     111             :             /// regarding the order that the list of folders/files are returned.
     112             :             /// To ensure consistency across platforms, we apply a
     113             :             /// uniform sorting logic.
     114           2 :             final aSplit = p.split(a.path);
     115           2 :             final bSplit = p.split(b.path);
     116           1 :             final aLevel = aSplit.length;
     117           1 :             final bLevel = bSplit.length;
     118             : 
     119           1 :             if (aLevel == bLevel) {
     120           3 :               return aSplit.last.compareTo(bSplit.last);
     121             :             } else {
     122           1 :               return aLevel.compareTo(bLevel);
     123             :             }
     124             :           });
     125             : 
     126           1 :     return directories.map(run);
     127             :   }
     128             : 
     129           1 :   static void _throwIfProcessFailed(
     130             :     ProcessResult pr,
     131             :     String process,
     132             :     List<String> args,
     133             :   ) {
     134           2 :     if (pr.exitCode != 0) {
     135           1 :       final values = {
     136           3 :         'Standard out': pr.stdout.toString().trim(),
     137           3 :         'Standard error': pr.stderr.toString().trim(),
     138           3 :       }..removeWhere((k, v) => v.isEmpty);
     139             : 
     140             :       var message = 'Unknown error';
     141           1 :       if (values.isNotEmpty) {
     142           7 :         message = values.entries.map((e) => '${e.key}\n${e.value}').join('\n');
     143             :       }
     144             : 
     145           2 :       throw ProcessException(process, args, message, pr.exitCode);
     146             :     }
     147             :   }
     148             : }
     149             : 
     150             : const _ignoredDirectories = {
     151             :   'ios',
     152             :   'android',
     153             :   'windows',
     154             :   'linux',
     155             :   'macos',
     156             :   '.symlinks',
     157             :   '.plugin_symlinks',
     158             :   '.dart_tool',
     159             :   'build',
     160             :   '.fvm',
     161             : };
     162             : 
     163           1 : bool _isPubspec(FileSystemEntity entity) {
     164           1 :   if (entity is! File) return false;
     165           3 :   return p.basename(entity.path) == 'pubspec.yaml';
     166             : }
     167             : 
     168             : // The extension is intended to be unnamed, but it's not possible due to
     169             : // an issue with Dart SDK 2.18.0.
     170             : //
     171             : // Once the min Dart SDK is bumped, this extension can be unnamed again.
     172             : extension _Set on Set<String> {
     173           1 :   bool excludes(FileSystemEntity entity) {
     174           3 :     final segments = p.split(entity.path).toSet();
     175           2 :     if (segments.intersection(_ignoredDirectories).isNotEmpty) return true;
     176           2 :     if (segments.intersection(this).isNotEmpty) return true;
     177             : 
     178           2 :     for (final value in this) {
     179           4 :       if (value.isNotEmpty && Glob(value).matches(entity.path)) {
     180             :         return true;
     181             :       }
     182             :     }
     183             : 
     184             :     return false;
     185             :   }
     186             : }

Generated by: LCOV version 1.15