diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb3c326..26142f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,51 +1,59 @@ name: CLI Tools CI on: - push: - branches: - - main - pull_request: - branches: - - main + push: + branches: + - main + pull_request: + branches: + - main jobs: - dart_format: - name: Dart format check - strategy: - fail-fast: false - matrix: - dart: [3.3, 3.9] - package: [cli_tools, config] - runs-on: ubuntu-latest - defaults: - run: - working-directory: packages/${{ matrix.package }} - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1.7.1 - with: - sdk: ${{ matrix.dart }} + dart_format: + name: Dart format check + strategy: + fail-fast: false + matrix: + dart: [3.3, 3.9] + package: [cli_tools, config] + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/${{ matrix.package }} + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1.7.1 + with: + sdk: ${{ matrix.dart }} - - run: dart pub get - - run: dart format --output=none --set-exit-if-changed . + - run: dart pub get + - run: dart format --output=none --set-exit-if-changed . - dart_analyze_test: - name: Dart Analyze and Test - strategy: - fail-fast: false - matrix: - dart: [3.3, 3.9] - package: [cli_tools, config] - runs-on: ubuntu-latest - defaults: - run: - working-directory: packages/${{ matrix.package }} - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1.7.1 - with: - sdk: ${{ matrix.dart }} + dart_analyze_test: + name: Dart Analyze and Test + strategy: + fail-fast: false + matrix: + dart: [3.3, 3.9] + package: [cli_tools, config] + platform: [ubuntu-latest] + include: + - package: cli_tools + platform: windows-latest + dart: 3.3 + - package: cli_tools + platform: macos-latest + dart: 3.3 + runs-on: ${{ matrix.platform }} + defaults: + run: + working-directory: packages/${{ matrix.package }} + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1.7.1 + with: + sdk: ${{ matrix.dart }} - - run: dart pub get - - run: dart analyze --fatal-infos - - run: dart test + - run: dart pub get + - run: dart analyze --fatal-infos + - run: dart test diff --git a/packages/cli_tools/test/execute_test.dart b/packages/cli_tools/test/execute_test.dart index eec1ce9..cdd522b 100644 --- a/packages/cli_tools/test/execute_test.dart +++ b/packages/cli_tools/test/execute_test.dart @@ -1,12 +1,11 @@ -// These test depends on bash and unix specific tools (trap, exit, echo) -@TestOn('!windows') -library; - import 'dart:io'; import 'package:cli_tools/execute.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +import 'test_utils/mock_stdout.dart'; Future compileDriver() async { final driverExe = p.join(Directory.systemTemp.path, 'execute_driver.exe'); @@ -29,7 +28,7 @@ void main() { test( 'when running a command that succeeds, then the effect is expected', () async { - final result = await runDriver('echo "Hello world!"'); + final result = await runDriver('echo Hello world!'); expect(result.exitCode, 0); expect(result.stdout, contains('Hello world!')); }, @@ -47,32 +46,85 @@ void main() { () async { final result = await runDriver('fhasjkhfs'); expect(result.exitCode, isNot(0)); - expect(result.stderr, contains('not found')); + expect(result.stderr, + contains(Platform.isWindows ? 'not recognized' : 'not found')); }, ); - test('when sending SIGINT, then it is forwarded to the child process', - () async { - // Use trap to catch signal in child - final process = await startDriver( - 'trap "echo SIGINT; exit 0" INT; echo "Running"; while :; do sleep 0.1; done'); - - // Collect stdout incrementally - final stdoutBuffer = StringBuffer(); - process.stdout.transform(systemEncoding.decoder).listen((final data) { - stdoutBuffer.write(data); - }); - - // Wait for the script to start (look for "Running" message) - while (!stdoutBuffer.toString().contains('Running')) { - await Future.delayed(const Duration(milliseconds: 100)); - } - - // Send SIGINT to driver - process.kill(ProcessSignal.sigint); - - expect(await process.exitCode, 0); - expect(stdoutBuffer.toString(), contains('SIGINT')); - }); + test( + 'when sending SIGINT, then it is forwarded to the child process', + () async { + // Use trap to catch signal in child + final process = await startDriver( + 'trap "echo SIGINT; exit 0" INT; echo "Running"; while :; do sleep 0.1; done'); + + // Collect stdout incrementally + final stdoutBuffer = StringBuffer(); + process.stdout.transform(systemEncoding.decoder).listen((final data) { + stdoutBuffer.write(data); + }); + + // Wait for the script to start (look for "Running" message) + while (!stdoutBuffer.toString().contains('Running')) { + await Future.delayed(const Duration(milliseconds: 100)); + } + + // Send SIGINT to driver + process.kill(ProcessSignal.sigint); + + expect(await process.exitCode, 0); + expect(stdoutBuffer.toString(), contains('SIGINT')); + }, + skip: Platform.isWindows ? 'No trap equivalent on Windows' : null, + ); + + test( + 'when specifying a working directory, ' + 'then the command runs in that directory', + () async { + await d.dir('test_workdir', []).create(); + final testDir = Directory(p.join(d.sandbox, 'test_workdir')); + + final stdout = MockStdout(); + final exitCode = await execute( + Platform.isWindows ? 'cd' : 'pwd', + workingDirectory: testDir, + stdout: stdout, + ); + + expect(exitCode, 0); + expect(stdout.output.trim(), testDir.path); + }, + ); + + test( + 'when the command outputs to stderr, then stderr is streamed', + () async { + final stderr = MockStdout(); + final exitCode = await execute( + 'echo error output 1>&2', + stderr: stderr, + ); + + expect(exitCode, 0); + expect(stderr.output, contains('error output')); + }, + ); + + test( + 'when running a complex command with shell features, ' + 'then it handles them correctly', + () async { + final stdout = MockStdout(); + final exitCode = await execute( + 'echo hello && echo world', + stdout: stdout, + ); + + expect(exitCode, 0); + expect(stdout.output, contains('hello')); + expect(stdout.output, contains('world')); + }, + ); }); } diff --git a/packages/cli_tools/test/test_utils/mock_stdout.dart b/packages/cli_tools/test/test_utils/mock_stdout.dart index 6bb4a94..326901b 100644 --- a/packages/cli_tools/test/test_utils/mock_stdout.dart +++ b/packages/cli_tools/test/test_utils/mock_stdout.dart @@ -26,7 +26,7 @@ class MockStdout implements Stdout { @override Future addStream(final Stream> stream) { - throw UnimplementedError(); + return stream.forEach(add); } @override