| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // Regression test for running commands whose executable path or arguments | |
| // contain a space (e.g. the default pub cache under a Windows user name with | |
| // a space, `C:\Users\First Last\AppData\Local\Pub\Cache\...`). | |
| // | |
| // `runProcess` never runs through a shell. Absolute paths (with a file | |
| // extension on Windows) are required; relative paths and PATHEXT are not | |
| // supported. Spaces in the executable path and arguments are handled by | |
| // passing them as separate CreateProcess / exec arguments. | |
| import 'dart:io'; | |
| import 'package:test/test.dart'; | |
| import '../helpers.dart'; | |
| void main() { | |
| late Uri scriptUri; | |
| setUpAll(() async { | |
| final scriptDir = await tempDirForTest(); | |
| scriptUri = scriptDir.resolve('echo_args.dart'); | |
| await File.fromUri(scriptUri).writeAsString(''' | |
| void main(List<String> args) { | |
| print('ARGC:\${args.length}'); | |
| print('ARGV:\${args.join('|')}'); | |
| } | |
| '''); | |
| }); | |
| test( | |
| 'runProcess handles an executable path containing a space', | |
| timeout: const Timeout.factor(5), | |
| () async { | |
| final outDir = await tempDirForTest(); | |
| final exeUri = outDir.resolve( | |
| 'echo args${Platform.isWindows ? '.exe' : ''}', | |
| ); | |
| final compileResult = await Process.run(Platform.resolvedExecutable, [ | |
| 'compile', | |
| 'exe', | |
| scriptUri.toFilePath(), | |
| '-o', | |
| exeUri.toFilePath(), | |
| ]); | |
| expect(compileResult.exitCode, 0, reason: '${compileResult.stderr}'); | |
| final workingDir = await tempDirForTest(); | |
| final result = await runProcess( | |
| executable: exeUri, | |
| arguments: ['an argument with spaces'], | |
| workingDirectory: workingDir, | |
| logger: logger, | |
| ); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout, contains('ARGC:1')); | |
| expect(result.stdout, contains('ARGV:an argument with spaces')); | |
| }, | |
| ); | |
| test('runProcess handles arguments containing a space', () async { | |
| final workingDir = await tempDirForTest(); | |
| final result = await runProcess( | |
| executable: Uri.file(Platform.resolvedExecutable), | |
| arguments: [scriptUri.toFilePath(), 'first arg', 'second arg'], | |
| workingDirectory: workingDir, | |
| logger: logger, | |
| ); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout, contains('ARGC:2')); | |
| expect(result.stdout, contains('ARGV:first arg|second arg')); | |
| }); | |
| test('runProcess handles arguments containing a space and quotes', () async { | |
| final workingDir = await tempDirForTest(); | |
| final result = await runProcess( | |
| executable: Uri.file(Platform.resolvedExecutable), | |
| arguments: [scriptUri.toFilePath(), 'fir"st arg', 'sec\'ond arg'], | |
| workingDirectory: workingDir, | |
| logger: logger, | |
| ); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout, contains('ARGC:2')); | |
| expect(result.stdout, contains('ARGV:fir"st arg|sec\'ond arg')); | |
| }); | |
| test('runProcess rejects a relative executable path', () async { | |
| await expectLater( | |
| runProcess( | |
| executable: Uri(path: 'dart'), | |
| logger: logger, | |
| ), | |
| throwsA( | |
| isA<ArgumentError>().having( | |
| (e) => e.message, | |
| 'message', | |
| contains('absolute'), | |
| ), | |
| ), | |
| ); | |
| }); | |
| test( | |
| 'runProcess rejects a Windows executable without a file extension', | |
| () async { | |
| if (!Platform.isWindows) return; | |
| await expectLater( | |
| runProcess( | |
| executable: Uri.file(r'C:\path\to\dart'), | |
| logger: logger, | |
| ), | |
| throwsA( | |
| isA<ArgumentError>().having( | |
| (e) => e.message, | |
| 'message', | |
| contains('PATHEXT'), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ); | |
| } |