| // Copyright (c) 2020, 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' show Directory, File, Platform, Process, stderr, stdout; |
| |
| import 'package:build_integration/file_system/multi_root.dart'; |
| import 'package:front_end/src/api_prototype/standard_file_system.dart'; |
| import 'package:front_end/src/compute_platform_binaries_location.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:pedantic/pedantic.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'package:dev_compiler/src/kernel/expression_compiler_worker.dart'; |
| |
| /// Verbose mode for debugging |
| bool get verbose => false; |
| |
| class ModuleConfiguration { |
| final Uri root; |
| final String outputDir; |
| final String libraryUri; |
| final String moduleName; |
| final String jsFileName; |
| final String fullDillFileName; |
| |
| ModuleConfiguration( |
| {this.root, |
| this.outputDir, |
| this.moduleName, |
| this.libraryUri, |
| this.jsFileName, |
| this.fullDillFileName}); |
| |
| Uri get jsPath => root.resolve('$outputDir/$jsFileName'); |
| Uri get fullDillPath => root.resolve('$outputDir/$fullDillFileName'); |
| } |
| |
| class TestProjectConfiguration { |
| final Directory rootDirectory; |
| final String outputDir = 'out'; |
| |
| TestProjectConfiguration(this.rootDirectory); |
| |
| ModuleConfiguration get mainModule => ModuleConfiguration( |
| root: root, |
| outputDir: outputDir, |
| moduleName: 'packages/_testPackage/main', |
| libraryUri: 'org-dartlang-app:/lib/main.dart', |
| jsFileName: 'main.js', |
| fullDillFileName: 'main.full.dill'); |
| |
| ModuleConfiguration get testModule => ModuleConfiguration( |
| root: root, |
| outputDir: outputDir, |
| moduleName: 'packages/_testPackage/test_library', |
| libraryUri: 'package:_testPackage/test_library.dart', |
| jsFileName: 'test_library.js', |
| fullDillFileName: 'test_library.full.dill'); |
| |
| ModuleConfiguration get testModule2 => ModuleConfiguration( |
| root: root, |
| outputDir: outputDir, |
| moduleName: 'packages/_testPackage/test_library2', |
| libraryUri: 'package:_testPackage/test_library2.dart', |
| jsFileName: 'test_library2.js', |
| fullDillFileName: 'test_library2.full.dill'); |
| |
| String get rootPath => rootDirectory.path; |
| Uri get root => rootDirectory.uri; |
| Uri get outputPath => root.resolve(outputDir); |
| Uri get packagesPath => root.resolve('.packages'); |
| |
| Uri get sdkRoot => computePlatformBinariesLocation(); |
| Uri get sdkSummaryPath => sdkRoot.resolve('ddc_sdk.dill'); |
| Uri get librariesPath => sdkRoot.resolve('lib/libraries.json'); |
| |
| void createTestProject() { |
| var pubspec = root.resolve('pubspec.yaml'); |
| File.fromUri(pubspec) |
| ..createSync() |
| ..writeAsStringSync(''' |
| name: _testPackage |
| version: 1.0.0 |
| |
| environment: |
| sdk: '>=2.8.0 <3.0.0' |
| '''); |
| |
| var packages = root.resolve('.packages'); |
| File.fromUri(packages) |
| ..createSync() |
| ..writeAsStringSync(''' |
| _testPackage:lib/ |
| '''); |
| |
| var main = root.resolve('lib/main.dart'); |
| File.fromUri(main) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| import 'package:_testPackage/test_library.dart'; |
| |
| var global = 0; |
| |
| void main() { |
| var count = 0; |
| // line 7 |
| print('Global is: \${++global}'); |
| print('Count is: \${++count}'); |
| |
| B b = new B(); |
| } |
| '''); |
| |
| var testLibrary = root.resolve('lib/test_library.dart'); |
| File.fromUri(testLibrary) |
| ..createSync() |
| ..writeAsStringSync(''' |
| import 'package:_testPackage/test_library2.dart'; |
| |
| int testLibraryFunction(int formal) { |
| return formal; // line 4 |
| } |
| |
| int callLibraryFunction2(int formal) { |
| return testLibraryFunction2(formal); // line 8 |
| } |
| |
| class B { |
| C c() => new C(); |
| } |
| '''); |
| |
| var testLibrary2 = root.resolve('lib/test_library2.dart'); |
| File.fromUri(testLibrary2) |
| ..createSync() |
| ..writeAsStringSync(''' |
| int testLibraryFunction2(int formal) { |
| return formal; // line 2 |
| } |
| |
| class C { |
| int getNumber() => 42; |
| } |
| '''); |
| } |
| } |
| |
| void main() async { |
| group('Expression compiler worker (webdev simulation) - ', () { |
| ExpressionCompilerWorker worker; |
| Future workerDone; |
| StreamController<Map<String, dynamic>> requestController; |
| StreamController<Map<String, dynamic>> responseController; |
| Directory tempDir; |
| TestProjectConfiguration config; |
| List inputs; |
| |
| setUpAll(() async { |
| tempDir = Directory.systemTemp.createTempSync('foo bar'); |
| config = TestProjectConfiguration(tempDir); |
| |
| // simulate webdev |
| config.createTestProject(); |
| var kernelGenerator = DDCKernelGenerator(config); |
| await kernelGenerator.generate(); |
| |
| inputs = [ |
| { |
| 'path': config.mainModule.fullDillPath.path, |
| 'moduleName': config.mainModule.moduleName |
| }, |
| { |
| 'path': config.testModule.fullDillPath.path, |
| 'moduleName': config.testModule.moduleName |
| }, |
| { |
| 'path': config.testModule2.fullDillPath.path, |
| 'moduleName': config.testModule2.moduleName |
| }, |
| ]; |
| }); |
| |
| tearDownAll(() async { |
| tempDir.deleteSync(recursive: true); |
| }); |
| |
| setUp(() async { |
| var fileSystem = MultiRootFileSystem( |
| 'org-dartlang-app', [tempDir.uri], StandardFileSystem.instance); |
| |
| requestController = StreamController<Map<String, dynamic>>(); |
| responseController = StreamController<Map<String, dynamic>>(); |
| worker = await ExpressionCompilerWorker.create( |
| librariesSpecificationUri: config.librariesPath, |
| // We should be able to load everything from dill and not require |
| // source parsing. Webdev and google3 integration currently rely on |
| // that. Make the test fail on source reading by not providing a |
| // packages file. |
| packagesFile: null, |
| sdkSummary: config.sdkSummaryPath, |
| fileSystem: fileSystem, |
| requestStream: requestController.stream, |
| sendResponse: responseController.add, |
| verbose: verbose, |
| ); |
| workerDone = worker.start(); |
| }); |
| |
| tearDown(() async { |
| unawaited(requestController.close()); |
| await workerDone; |
| unawaited(responseController.close()); |
| }); |
| |
| test('can load dependencies and compile expressions in sdk', () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'other', |
| 'line': 107, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {'other': 'other'}, |
| 'libraryUri': 'dart:collection', |
| 'moduleName': 'dart_sdk', |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': contains('return other;'), |
| }) |
| ])); |
| }, skip: 'Evaluating expressions in SDK is not supported yet'); |
| |
| test('can load dependencies and compile expressions in a library', |
| () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'formal', |
| 'line': 4, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {'formal': 'formal'}, |
| 'libraryUri': config.testModule.libraryUri, |
| 'moduleName': config.testModule.moduleName, |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': contains('return formal;'), |
| }) |
| ])); |
| }); |
| |
| test('can load dependencies and compile expressions in main', () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'count', |
| 'line': 7, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {'count': 'count'}, |
| 'libraryUri': config.mainModule.libraryUri, |
| 'moduleName': config.mainModule.moduleName, |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': contains('return count;'), |
| }) |
| ])); |
| }); |
| |
| test('can load dependencies and compile transitive expressions in main', |
| () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'B().c().getNumber()', |
| 'line': 7, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {}, |
| 'libraryUri': config.mainModule.libraryUri, |
| 'moduleName': config.mainModule.moduleName, |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': |
| contains('return new test_library.B.new().c().getNumber()'), |
| }) |
| ])); |
| }); |
| }); |
| |
| group('Expression compiler worker (google3 simulation) - ', () { |
| ExpressionCompilerWorker worker; |
| Future workerDone; |
| StreamController<Map<String, dynamic>> requestController; |
| StreamController<Map<String, dynamic>> responseController; |
| Directory tempDir; |
| TestProjectConfiguration config; |
| List inputs; |
| |
| setUpAll(() async { |
| tempDir = Directory.systemTemp.createTempSync('foo bar'); |
| config = TestProjectConfiguration(tempDir); |
| |
| // simulate google3 |
| config.createTestProject(); |
| var kernelGenerator = BazelKernelWorkerGenerator(config); |
| await kernelGenerator.generate(); |
| |
| inputs = [ |
| { |
| 'path': config.mainModule.fullDillPath.path, |
| 'moduleName': config.mainModule.moduleName |
| }, |
| ]; |
| }); |
| |
| tearDownAll(() async { |
| tempDir.deleteSync(recursive: true); |
| }); |
| |
| setUp(() async { |
| var fileSystem = MultiRootFileSystem( |
| 'org-dartlang-app', [tempDir.uri], StandardFileSystem.instance); |
| |
| requestController = StreamController<Map<String, dynamic>>(); |
| responseController = StreamController<Map<String, dynamic>>(); |
| worker = await ExpressionCompilerWorker.create( |
| librariesSpecificationUri: config.librariesPath, |
| packagesFile: null, |
| sdkSummary: config.sdkSummaryPath, |
| fileSystem: fileSystem, |
| requestStream: requestController.stream, |
| sendResponse: responseController.add, |
| verbose: verbose, |
| ); |
| workerDone = worker.start(); |
| }); |
| |
| tearDown(() async { |
| unawaited(requestController.close()); |
| await workerDone; |
| unawaited(responseController.close()); |
| }); |
| |
| test('can load dependencies and compile expressions in sdk', () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'other', |
| 'line': 107, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {'other': 'other'}, |
| 'libraryUri': 'dart:collection', |
| 'moduleName': 'dart_sdk', |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': contains('return other;'), |
| }) |
| ])); |
| }, skip: 'Evaluating expressions in SDK is not supported yet'); |
| |
| test('can load dependencies and compile expressions in a library', |
| () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'formal', |
| 'line': 4, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {'formal': 'formal'}, |
| 'libraryUri': config.testModule.libraryUri, |
| 'moduleName': config.mainModule.moduleName, |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': contains('return formal;'), |
| }) |
| ])); |
| }); |
| |
| test('can load dependencies and compile expressions in main', () async { |
| requestController.add({ |
| 'command': 'UpdateDeps', |
| 'inputs': inputs, |
| }); |
| |
| requestController.add({ |
| 'command': 'CompileExpression', |
| 'expression': 'count', |
| 'line': 7, |
| 'column': 1, |
| 'jsModules': {}, |
| 'jsScope': {'count': 'count'}, |
| 'libraryUri': config.mainModule.libraryUri, |
| 'moduleName': config.mainModule.moduleName, |
| }); |
| |
| expect( |
| responseController.stream, |
| emitsInOrder([ |
| equals({ |
| 'succeeded': true, |
| }), |
| equals({ |
| 'succeeded': true, |
| 'errors': isEmpty, |
| 'warnings': isEmpty, |
| 'compiledProcedure': contains('return count;'), |
| }) |
| ])); |
| }); |
| }, skip: 'bazel kernel worker does not support full kernel generation yet'); |
| } |
| |
| /// Uses DDC to generate kernel from the test code |
| /// in order to simulate webdev environment |
| class DDCKernelGenerator { |
| final TestProjectConfiguration config; |
| |
| DDCKernelGenerator(this.config); |
| |
| Future<int> generate() async { |
| var dart = Platform.resolvedExecutable; |
| var dartdevc = |
| p.join(p.dirname(dart), 'snapshots', 'dartdevc.dart.snapshot'); |
| |
| Directory.fromUri(config.outputPath)..createSync(); |
| |
| // generate test_library2.full.dill |
| var args = [ |
| dartdevc, |
| config.testModule2.libraryUri, |
| '--no-summarize', |
| '-o', |
| config.testModule2.jsPath.toFilePath(), |
| '--source-map', |
| '--experimental-emit-debug-metadata', |
| '--experimental-output-compiled-kernel', |
| '--dart-sdk-summary', |
| config.sdkSummaryPath.path, |
| '--multi-root', |
| '${config.root}', |
| '--multi-root-scheme', |
| 'org-dartlang-app', |
| '--packages', |
| config.packagesPath.path, |
| ]; |
| |
| var exitCode = await runProcess(dart, args, config.rootPath); |
| if (exitCode != 0) { |
| return exitCode; |
| } |
| |
| // generate test_library.full.dill |
| args = [ |
| dartdevc, |
| config.testModule.libraryUri, |
| '--no-summarize', |
| '--summary', |
| '${config.testModule2.fullDillPath}=${config.testModule2.moduleName}', |
| '-o', |
| config.testModule.jsPath.toFilePath(), |
| '--source-map', |
| '--experimental-emit-debug-metadata', |
| '--experimental-output-compiled-kernel', |
| '--dart-sdk-summary', |
| config.sdkSummaryPath.path, |
| '--multi-root', |
| '${config.root}', |
| '--multi-root-scheme', |
| 'org-dartlang-app', |
| '--packages', |
| config.packagesPath.path, |
| ]; |
| |
| exitCode = await runProcess(dart, args, config.rootPath); |
| if (exitCode != 0) { |
| return exitCode; |
| } |
| |
| // generate main.full.dill |
| args = [ |
| dartdevc, |
| config.mainModule.libraryUri, |
| '--no-summarize', |
| '--summary', |
| '${config.testModule2.fullDillPath}=${config.testModule2.moduleName}', |
| '--summary', |
| '${config.testModule.fullDillPath}=${config.testModule.moduleName}', |
| '-o', |
| config.mainModule.jsPath.toFilePath(), |
| '--source-map', |
| '--experimental-emit-debug-metadata', |
| '--experimental-output-compiled-kernel', |
| '--dart-sdk-summary', |
| config.sdkSummaryPath.path, |
| '--multi-root', |
| '${config.root}', |
| '--multi-root-scheme', |
| 'org-dartlang-app', |
| '--packages', |
| config.packagesPath.path, |
| ]; |
| |
| return await runProcess(dart, args, config.rootPath); |
| } |
| } |
| |
| /// Uses bazel kernel worker to generate kernel from test code |
| /// in order to simulate google3 environment |
| /// TODO: split into 3 modules |
| class BazelKernelWorkerGenerator { |
| TestProjectConfiguration config; |
| |
| BazelKernelWorkerGenerator(this.config); |
| |
| Future<void> generate() async { |
| var dart = Platform.resolvedExecutable; |
| var kernelWorker = |
| p.join(p.dirname(dart), 'snapshots', 'kernel_worker.dart.snapshot'); |
| |
| var args = [ |
| kernelWorker, |
| '--target', |
| 'ddc', |
| '--output', |
| config.mainModule.fullDillPath.path, |
| '--dart-sdk-summary', |
| config.sdkSummaryPath.path, |
| '--exclude-non-sources', |
| '--source', |
| config.mainModule.libraryUri, |
| '--source', |
| config.testModule.libraryUri, |
| '--source', |
| config.testModule2.libraryUri, |
| '--multi-root', |
| '${config.root}', |
| '--multi-root-scheme', |
| 'org-dartlang-app', |
| '--packages-file', |
| '.packages', |
| '--verbose' |
| ]; |
| |
| return await runProcess(dart, args, config.rootPath); |
| } |
| } |
| |
| Future<int> runProcess( |
| String command, List<String> args, String workingDirectory) async { |
| if (verbose) { |
| print('Running command in $workingDirectory:' |
| '\n\t $command ${args.join(' ')}, '); |
| } |
| var process = |
| await Process.start(command, args, workingDirectory: workingDirectory) |
| .then((Process process) { |
| process |
| ..stdout.transform(utf8.decoder).listen(stdout.write) |
| ..stderr.transform(utf8.decoder).listen(stderr.write); |
| return process; |
| }); |
| |
| return await process.exitCode; |
| } |