| // Copyright (c) 2016, 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. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:bazel_worker/bazel_worker.dart'; |
| // TODO(jakemac): Remove once this is a part of the testing library. |
| import 'package:bazel_worker/src/async_message_grouper.dart'; |
| import 'package:bazel_worker/testing.dart'; |
| import 'package:path/path.dart' show dirname, join, joinAll; |
| import 'package:test/test.dart'; |
| |
| Directory tmp = Directory.systemTemp.createTempSync('ddc_worker_test'); |
| File file(String path) => File(join(tmp.path, joinAll(path.split('/')))); |
| |
| void main() { |
| runTests(false); |
| runTests(true); |
| } |
| |
| void runTests(bool isKernel) { |
| String mode = isKernel ? 'DDK' : 'DDC'; |
| List<String> baseArgs = isKernel ? ['-k'] : []; |
| String ext = isKernel ? 'dill' : 'sum'; |
| final binDir = dirname(Platform.resolvedExecutable); |
| // Note, the bots use the dart binary in the top-level build directory. |
| // On windows, this is a .bat file. |
| final dartdevc = 'dartdevc${Platform.isWindows ? ".bat" : ""}'; |
| final executable = binDir.endsWith('bin') |
| ? join(binDir, dartdevc) |
| : join(binDir, 'dart-sdk', 'bin', dartdevc); |
| final executableArgs = <String>[]; |
| group('$mode: Hello World', () { |
| final argsFile = file('hello_world.args'); |
| final inputDartFile = file('hello_world.dart'); |
| final outputJsFile = file('out/hello_world.js'); |
| final compilerArgs = baseArgs + |
| [ |
| '--no-source-map', |
| '--no-summarize', |
| '-o', |
| outputJsFile.path, |
| inputDartFile.path, |
| ]; |
| |
| setUp(() { |
| inputDartFile.createSync(); |
| inputDartFile.writeAsStringSync('main() => print("hello world");'); |
| }); |
| |
| tearDown(() { |
| if (inputDartFile.existsSync()) inputDartFile.deleteSync(); |
| if (outputJsFile.parent.existsSync()) { |
| outputJsFile.parent.deleteSync(recursive: true); |
| } |
| if (argsFile.existsSync()) argsFile.deleteSync(); |
| }); |
| |
| test('can compile in worker mode', () async { |
| var args = executableArgs.toList()..add('--persistent_worker'); |
| var process = await Process.start(executable, args); |
| var messageGrouper = AsyncMessageGrouper(process.stdout); |
| |
| var request = WorkRequest(); |
| request.arguments.addAll(compilerArgs); |
| process.stdin.add(protoToDelimitedBuffer(request)); |
| |
| var response = await _readResponse(messageGrouper); |
| expect(response.exitCode, EXIT_CODE_OK, reason: response.output); |
| expect(response.output, isEmpty); |
| |
| expect(outputJsFile.existsSync(), isTrue); |
| expect(outputJsFile.readAsStringSync(), contains('hello world')); |
| |
| /// Now update hello_world.dart and send another [WorkRequest]. |
| inputDartFile.writeAsStringSync('main() => print("goodbye world");'); |
| process.stdin.add(protoToDelimitedBuffer(request)); |
| |
| response = await _readResponse(messageGrouper); |
| expect(response.exitCode, EXIT_CODE_OK, reason: response.output); |
| expect(response.output, isEmpty); |
| expect(outputJsFile.readAsStringSync(), contains('goodbye world')); |
| |
| // Try to compile an error. |
| inputDartFile.writeAsStringSync('main() { int x = "hello"; }'); |
| process.stdin.add(protoToDelimitedBuffer(request)); |
| |
| response = await _readResponse(messageGrouper); |
| expect(response.exitCode, 1, reason: response.output); |
| expect( |
| response.output, |
| contains( |
| 'A value of type \'String\' can\'t be assigned to a variable of type \'int\'.')); |
| |
| process.kill(); |
| |
| // TODO(jakemac): This shouldn't be necessary, but it is for the process |
| // to exit properly. |
| expect(await messageGrouper.next, isNull); |
| }); |
| |
| test('can compile in basic mode', () { |
| var args = executableArgs.toList()..addAll(compilerArgs); |
| var result = Process.runSync(executable, args); |
| |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(outputJsFile.existsSync(), isTrue); |
| }); |
| |
| test('unknown options', () { |
| var args = List<String>.from(executableArgs) |
| ..add('--does-not-exist') |
| ..addAll(compilerArgs); |
| var result = Process.runSync(executable, args); |
| |
| expect(result.exitCode, 64); |
| expect(result.stdout, |
| contains('Could not find an option named "does-not-exist"')); |
| expect(result.stderr, isEmpty); |
| expect(outputJsFile.existsSync(), isFalse); |
| }); |
| |
| test('unknown options ignored', () { |
| var args = List<String>.from(executableArgs) |
| ..add('--does-not-exist') |
| ..add('--ignore-unrecognized-flags') |
| ..addAll(compilerArgs); |
| var result = Process.runSync(executable, args); |
| |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(outputJsFile.existsSync(), isTrue); |
| }); |
| |
| test('can compile in basic mode with args in a file', () async { |
| argsFile.createSync(); |
| argsFile.writeAsStringSync(compilerArgs.join('\n')); |
| var args = executableArgs.toList()..add('@${argsFile.path}'); |
| var process = await Process.start(executable, args); |
| await stderr.addStream(process.stderr); |
| var futureProcessOutput = process.stdout.map(utf8.decode).toList(); |
| |
| expect(await process.exitCode, EXIT_CODE_OK); |
| expect((await futureProcessOutput).join(), isEmpty); |
| expect(outputJsFile.existsSync(), isTrue); |
| }); |
| |
| test('can compile in basic mode with "legacy" modules', () async { |
| var args = List<String>.from(executableArgs) |
| ..add('--modules') |
| ..add('legacy') |
| ..addAll(compilerArgs); |
| var result = Process.runSync(executable, args); |
| |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(outputJsFile.existsSync(), isTrue); |
| }); |
| }); |
| |
| group('$mode: Hello World with Summaries', () { |
| final greetingDart = file('greeting.dart'); |
| final helloDart = file('hello.dart'); |
| |
| final greetingJS = file('greeting.js'); |
| final greetingSummary = file('greeting.$ext'); |
| final helloJS = file('hello_world.js'); |
| |
| final greeting2JS = file('greeting2.js'); |
| final greeting2Summary = file('greeting2.$ext'); |
| |
| setUp(() { |
| greetingDart.writeAsStringSync('String greeting = "hello";'); |
| helloDart.writeAsStringSync('import "greeting.dart";' |
| 'main() => print(greeting);'); |
| }); |
| |
| tearDown(() { |
| if (greetingDart.existsSync()) greetingDart.deleteSync(); |
| if (helloDart.existsSync()) helloDart.deleteSync(); |
| if (greetingJS.existsSync()) greetingJS.deleteSync(); |
| if (greetingSummary.existsSync()) greetingSummary.deleteSync(); |
| if (greeting2JS.existsSync()) greeting2JS.deleteSync(); |
| if (greeting2Summary.existsSync()) greeting2Summary.deleteSync(); |
| if (helloJS.existsSync()) helloJS.deleteSync(); |
| }); |
| |
| test('can compile in basic mode', () { |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-source-map', |
| '-o', |
| greetingJS.path, |
| greetingDart.path, |
| ]); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(greetingJS.existsSync(), isTrue); |
| expect(greetingSummary.existsSync(), isTrue); |
| |
| result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-source-map', |
| '--no-summarize', |
| '-s', |
| greetingSummary.path, |
| '-o', |
| helloJS.path, |
| helloDart.path, |
| ]); |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(helloJS.existsSync(), isTrue); |
| }); |
| |
| test('reports error on overlapping summaries', () { |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-source-map', |
| '-o', |
| greetingJS.path, |
| greetingDart.path, |
| ]); |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(greetingJS.existsSync(), isTrue); |
| expect(greetingSummary.existsSync(), isTrue); |
| |
| result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-source-map', |
| '-o', |
| greeting2JS.path, |
| greetingDart.path, |
| ]); |
| expect(result.exitCode, EXIT_CODE_OK); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(greeting2JS.existsSync(), isTrue); |
| expect(greeting2Summary.existsSync(), isTrue); |
| |
| result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-source-map', |
| '--no-summarize', |
| '-s', |
| greetingSummary.path, |
| '-s', |
| greeting2Summary.path, |
| '-o', |
| helloJS.path, |
| helloDart.path, |
| ]); |
| // TODO(vsm): Re-enable when we turn this check back on. |
| expect(result.exitCode, 0); |
| // expect(result.exitCode, 65); |
| // expect(result.stdout, contains("conflict")); |
| // expect(result.stdout, contains('${toUri(greetingDart.path)}')); |
| // expect(helloJS.existsSync(), isFalse); |
| }); |
| }); |
| |
| group('$mode: Error handling', () { |
| final badFileDart = file('bad.dart'); |
| final badFileJs = file('bad.js'); |
| |
| tearDown(() { |
| if (badFileDart.existsSync()) badFileDart.deleteSync(); |
| if (badFileJs.existsSync()) badFileJs.deleteSync(); |
| }); |
| |
| test('incorrect usage', () { |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| 'oops', |
| ]); |
| expect(result.exitCode, 64); |
| expect( |
| result.stdout, contains('Please specify the output file location.')); |
| expect(result.stdout, isNot(contains('#0'))); |
| }); |
| |
| test('compile errors', () { |
| badFileDart.writeAsStringSync('main() => "hello world"'); |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-source-map', |
| '-o', |
| badFileJs.path, |
| badFileDart.path, |
| ]); |
| expect(result.exitCode, 1); |
| expect(result.stdout, contains(RegExp(r"Expected (to find )?\';\'"))); |
| }); |
| }); |
| |
| group('$mode: Parts', () { |
| final partFile = file('greeting.dart'); |
| final libraryFile = file('hello.dart'); |
| |
| final outJS = file('output.js'); |
| |
| setUp(() { |
| partFile.writeAsStringSync('part of hello;\n' |
| 'String greeting = "hello";'); |
| libraryFile.writeAsStringSync('library hello;\n' |
| 'part "greeting.dart";\n' |
| 'main() => print(greeting);\n'); |
| }); |
| |
| tearDown(() { |
| if (partFile.existsSync()) partFile.deleteSync(); |
| if (libraryFile.existsSync()) libraryFile.deleteSync(); |
| if (outJS.existsSync()) outJS.deleteSync(); |
| }); |
| |
| test('works if part and library supplied', () { |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-summarize', |
| '--no-source-map', |
| '-o', |
| outJS.path, |
| partFile.path, |
| libraryFile.path, |
| ]); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(outJS.existsSync(), isTrue); |
| }); |
| |
| test('works if part is not supplied', () { |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-summarize', |
| '--no-source-map', |
| '-o', |
| outJS.path, |
| libraryFile.path, |
| ]); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(outJS.existsSync(), isTrue); |
| }); |
| |
| if (!isKernel) { |
| // TODO(vsm): Consider dropping this test. Kernel behavior is better. |
| test('part without library is silently ignored', () { |
| var result = Process.runSync( |
| executable, |
| executableArgs + |
| baseArgs + |
| [ |
| '--no-summarize', |
| '--no-source-map', |
| '-o', |
| outJS.path, |
| partFile.path, |
| ]); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(outJS.existsSync(), isTrue); |
| }); |
| } |
| }); |
| } |
| |
| Future<WorkResponse> _readResponse(MessageGrouper messageGrouper) async { |
| var buffer = (await messageGrouper.next) as List<int>; |
| try { |
| return WorkResponse.fromBuffer(buffer); |
| } catch (_) { |
| var bufferAsString = |
| buffer == null ? '' : 'String: ${utf8.decode(buffer)}\n'; |
| throw 'Failed to parse response:\nbytes: $buffer\n$bufferAsString'; |
| } |
| } |