Add dart2js modular test suite under a unit test.
At this time this only compiles to .dill and then compiles from multiple modular
dill files.
Next steps: add the global split and add the modular data bits
Change-Id: I3399776dbd5187ddb0a3e2fdd307622a736570c9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102126
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/modular_test/lib/src/loader.dart b/pkg/modular_test/lib/src/loader.dart
index 8642fd8..ce00e49 100644
--- a/pkg/modular_test/lib/src/loader.dart
+++ b/pkg/modular_test/lib/src/loader.dart
@@ -111,7 +111,10 @@
Directory folder = Directory.fromUri(root);
int baseUriPrefixLength = folder.parent.uri.path.length;
await for (var file in folder.list(recursive: true)) {
- sources.add(Uri.parse(file.uri.path.substring(baseUriPrefixLength)));
+ var path = file.uri.path;
+ if (path.endsWith('.dart')) {
+ sources.add(Uri.parse(path.substring(baseUriPrefixLength)));
+ }
}
return sources..sort((a, b) => a.path.compareTo(b.path));
}
diff --git a/pkg/modular_test/lib/src/suite.dart b/pkg/modular_test/lib/src/suite.dart
index e5f17a4..a96e8c8 100644
--- a/pkg/modular_test/lib/src/suite.dart
+++ b/pkg/modular_test/lib/src/suite.dart
@@ -15,6 +15,8 @@
ModularTest(this.modules, this.mainModule)
: assert(mainModule != null && modules.length > 0);
+
+ String debugString() => modules.map((m) => m.debugString()).join('\n');
}
/// A single module in a modular test.
@@ -60,6 +62,17 @@
@override
String toString() => '[module $name]';
+
+ String debugString() {
+ var buffer = new StringBuffer();
+ buffer.write(' ');
+ buffer.write(name);
+ buffer.write(': ');
+ buffer.write(isPackage ? 'package' : '(not package)');
+ buffer.write(', deps: {${dependencies.map((d) => d.name).join(", ")}}');
+ buffer.write(', sources: {${sources.map((u) => "$u").join(', ')}}');
+ return '$buffer';
+ }
}
final RegExp _validModuleName = new RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$');
diff --git a/pkg/modular_test/test/loader/valid_packages/expectation.txt b/pkg/modular_test/test/loader/valid_packages/expectation.txt
index 3c0888d..dab4163 100644
--- a/pkg/modular_test/test/loader/valid_packages/expectation.txt
+++ b/pkg/modular_test/test/loader/valid_packages/expectation.txt
@@ -12,7 +12,6 @@
(no dependencies)
lib/js.dart
lib/js_util.dart
- lib/src/
lib/src/varargs.dart
main
diff --git a/tests/compiler/dart2js/dart2js.status b/tests/compiler/dart2js/dart2js.status
index 3df3488..84af435 100644
--- a/tests/compiler/dart2js/dart2js.status
+++ b/tests/compiler/dart2js/dart2js.status
@@ -30,6 +30,7 @@
model/native_test: Pass, Slow
model/no_such_method_enabled_test: Pass, Slow
model/subtype_test: Pass, Slow
+modular/*: Slow, Pass
packages/*: Skip # Skip packages folder
rti/rti_emission_test: Pass, Slow
rti/rti_need0_test: Pass, Slow
diff --git a/tests/compiler/dart2js/modular/data/int_js_number/def.dart b/tests/compiler/dart2js/modular/data/int_js_number/def.dart
new file mode 100644
index 0000000..0fd7377
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/int_js_number/def.dart
@@ -0,0 +1,4 @@
+// Copyright (c) 2019, 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.
+const int foo = 331;
diff --git a/tests/compiler/dart2js/modular/data/int_js_number/main.dart b/tests/compiler/dart2js/modular/data/int_js_number/main.dart
new file mode 100644
index 0000000..6352c3d
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/int_js_number/main.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, 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 'def.dart';
+
+main() {
+ print(const <int>[foo]);
+}
diff --git a/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml b/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml
new file mode 100644
index 0000000..a02b69a
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/int_js_number/modules.yaml
@@ -0,0 +1,8 @@
+# Copyright (c) 2019, 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: integral numbers should be treated as int and not double
+# after serialization across modules.
+dependencies:
+ main: def
diff --git a/tests/compiler/dart2js/modular/data/package_imports/.packages b/tests/compiler/dart2js/modular/data/package_imports/.packages
new file mode 100644
index 0000000..3da0bf2
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/.packages
@@ -0,0 +1,6 @@
+# Copyright (c) 2019, 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.
+f0:.
+f1:.
+a:a
diff --git a/tests/compiler/dart2js/modular/data/package_imports/a/a.dart b/tests/compiler/dart2js/modular/data/package_imports/a/a.dart
new file mode 100644
index 0000000..3a62c13
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/a/a.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2019, 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 'package:f1/f1.dart';
+
+int z = y + 100;
diff --git a/tests/compiler/dart2js/modular/data/package_imports/f0.dart b/tests/compiler/dart2js/modular/data/package_imports/f0.dart
new file mode 100644
index 0000000..27b7873
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/f0.dart
@@ -0,0 +1,4 @@
+// Copyright (c) 2019, 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.
+int x = 1;
diff --git a/tests/compiler/dart2js/modular/data/package_imports/f1.dart b/tests/compiler/dart2js/modular/data/package_imports/f1.dart
new file mode 100644
index 0000000..0eeb9f8
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/f1.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2019, 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 'package:f0/f0.dart';
+
+int y = x + 10;
diff --git a/tests/compiler/dart2js/modular/data/package_imports/f3.dart b/tests/compiler/dart2js/modular/data/package_imports/f3.dart
new file mode 100644
index 0000000..72965f8
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/f3.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2019, 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 'package:a/a.dart';
+
+int w = z + 1000;
diff --git a/tests/compiler/dart2js/modular/data/package_imports/main.dart b/tests/compiler/dart2js/modular/data/package_imports/main.dart
new file mode 100644
index 0000000..7dcee7a
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/main.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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 'f3.dart';
+import 'package:expect/expect.dart';
+
+main() {
+ print(w);
+ Expect.isTrue(w == 1111);
+}
diff --git a/tests/compiler/dart2js/modular/data/package_imports/modules.yaml b/tests/compiler/dart2js/modular/data/package_imports/modules.yaml
new file mode 100644
index 0000000..a8731e9
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/package_imports/modules.yaml
@@ -0,0 +1,13 @@
+# Copyright (c) 2019, 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.
+#
+# Test ensuring that the modular compiler works properly with `package:`
+# imports. This test also ensures that the dart2js implementation of the modular
+# test pipeline works as intended. The test is not designed to cover any
+# compiler or language feature explicitly.
+dependencies:
+ main: [f3, expect]
+ f3: a
+ a: f1
+ f1: f0
diff --git a/tests/compiler/dart2js/modular/data/subclass/a/a.dart b/tests/compiler/dart2js/modular/data/subclass/a/a.dart
new file mode 100644
index 0000000..e393550
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/subclass/a/a.dart
@@ -0,0 +1,4 @@
+// Copyright (c) 2019, 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.
+const x = 1;
diff --git a/tests/compiler/dart2js/modular/data/subclass/f0.dart b/tests/compiler/dart2js/modular/data/subclass/f0.dart
new file mode 100644
index 0000000..113a6ff
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/subclass/f0.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2019, 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.
+class A0 {
+ StringBuffer buffer = new StringBuffer()..write("hello ");
+}
diff --git a/tests/compiler/dart2js/modular/data/subclass/f1.dart b/tests/compiler/dart2js/modular/data/subclass/f1.dart
new file mode 100644
index 0000000..00f7709
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/subclass/f1.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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 'f0.dart';
+
+class B1 extends A0 {
+ A0 get foo => null;
+}
+
+A0 createA0() => new A0();
diff --git a/tests/compiler/dart2js/modular/data/subclass/main.dart b/tests/compiler/dart2js/modular/data/subclass/main.dart
new file mode 100644
index 0000000..090de94
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/subclass/main.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2019, 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 'f1.dart';
+import 'a/a.dart';
+
+class C2 extends B1 {
+ final foo = createA0();
+}
+
+main() {
+ var buffer = new C2().foo.buffer;
+
+ buffer.write('world! $x');
+ print(buffer.toString());
+}
diff --git a/tests/compiler/dart2js/modular/data/subclass/modules.yaml b/tests/compiler/dart2js/modular/data/subclass/modules.yaml
new file mode 100644
index 0000000..70606e9
--- /dev/null
+++ b/tests/compiler/dart2js/modular/data/subclass/modules.yaml
@@ -0,0 +1,9 @@
+# Copyright (c) 2019, 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.
+#
+# Test illustrating how the modular pipeline works. This test is not designed to
+# stress any specific feature of the compiler.
+dependencies:
+ f1: f0
+ main: [f1, a]
diff --git a/tests/compiler/dart2js/modular/modular_test.dart b/tests/compiler/dart2js/modular/modular_test.dart
new file mode 100644
index 0000000..b28edbf
--- /dev/null
+++ b/tests/compiler/dart2js/modular/modular_test.dart
@@ -0,0 +1,295 @@
+// Copyright (c) 2019, 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.
+
+/// Test the modular compilation pipeline of dart2js.
+///
+/// This is a shell that runs multiple tests, one per folder under `data/`.
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:async_helper/async_helper.dart';
+import 'package:expect/expect.dart';
+import 'package:front_end/src/compute_platform_binaries_location.dart'
+ show computePlatformBinariesLocation;
+import 'package:modular_test/src/io_pipeline.dart';
+import 'package:modular_test/src/loader.dart';
+import 'package:modular_test/src/pipeline.dart';
+import 'package:modular_test/src/suite.dart';
+
+_Options _options;
+main(List<String> args) {
+ _options = _Options.parse(args);
+ asyncTest(() async {
+ var baseUri = Platform.script.resolve('data/');
+ var baseDir = Directory.fromUri(baseUri);
+ await for (var entry in baseDir.list(recursive: false)) {
+ if (entry is Directory) {
+ await _runTest(entry.uri, baseUri);
+ }
+ }
+ });
+}
+
+Future<void> _runTest(Uri uri, Uri baseDir) async {
+ var dirName = uri.path.substring(baseDir.path.length);
+ if (_options.filter != null && !dirName.contains(_options.filter)) {
+ if (_options.showSkipped) print("skipped: $dirName");
+ return;
+ }
+
+ print("testing: $dirName");
+ ModularTest test = await loadTest(uri);
+ if (_options.verbose) print(test.debugString());
+ var pipeline = new IOPipeline([
+ SourceToDillStep(),
+ CompileFromDillStep(),
+ RunD8(),
+ ]);
+
+ await pipeline.run(test);
+}
+
+const dillId = const DataId("dill");
+const jsId = const DataId("js");
+
+// Step that compiles sources in a module to a .dill file.
+class SourceToDillStep implements IOModularStep {
+ @override
+ DataId get resultId => dillId;
+
+ @override
+ bool get needsSources => true;
+
+ @override
+ List<DataId> get dependencyDataNeeded => const [dillId];
+
+ @override
+ List<DataId> get moduleDataNeeded => const [];
+
+ @override
+ bool get onlyOnMain => false;
+
+ @override
+ Future<void> execute(
+ Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+ if (_options.verbose) print("step: source-to-dill on $module");
+ // We use non file-URI schemes for representeing source locations in a
+ // root-agnostic way. This allows us to refer to file across modules and
+ // across steps without exposing the underlying temporary folders that are
+ // created by the framework. In build systems like bazel this is especially
+ // important because each step may be run on a different machine.
+ //
+ // Files in packages are defined in terms of `package:` URIs, while
+ // non-package URIs are defined using the `dart-dev-app` scheme.
+ String rootScheme = 'dev-dart-app';
+ String sourceToImportUri(Uri relativeUri) {
+ if (module.isPackage) {
+ var basePath = module.packageBase.path;
+ var packageRelativePath = basePath == "./"
+ ? relativeUri.path
+ : relativeUri.path.substring(basePath.length);
+ return 'package:${module.name}/$packageRelativePath';
+ } else {
+ return '$rootScheme:/$relativeUri';
+ }
+ }
+
+ // We create a .packages file which defines the location of this module if
+ // it is a package. The CFE requires that if a `package:` URI of a
+ // dependency is used in an import, then we need that package entry in the
+ // .packages file. However, after it checks that the definition exists, the
+ // CFE will not actually use the resolved URI if a library for the import
+ // URI is already found in one of the provided .dill files of the
+ // dependencies. For that reason, and to ensure that a step only has access
+ // to the files provided in a module, we generate a .packages with invalid
+ // folders for other packages.
+ // TODO(sigmund): follow up with the CFE to see if we can remove the need
+ // for the .packages entry altogether if they won't need to read the
+ // sources.
+ var packagesContents = new StringBuffer();
+ if (module.isPackage) {
+ packagesContents.write('${module.name}:${module.packageBase}\n');
+ }
+ Set<Module> transitiveDependencies = _computeTransitiveDependencies(module);
+ for (Module dependency in transitiveDependencies) {
+ if (dependency.isPackage) {
+ packagesContents.write('${dependency.name}:unused\n');
+ }
+ }
+
+ await File.fromUri(root.resolve('.packages'))
+ .writeAsString('$packagesContents');
+
+ var sdkRoot = Platform.script.resolve("../../../../");
+ var platform =
+ computePlatformBinariesLocation().resolve("dart2js_platform.dill");
+
+ List<String> workerArgs = [
+ sdkRoot.resolve("utils/bazel/kernel_worker.dart").toFilePath(),
+ '--no-summary-only',
+ '--target',
+ 'dart2js',
+ '--multi-root',
+ '$root',
+ '--multi-root-scheme',
+ rootScheme,
+ '--dart-sdk-summary',
+ '${platform}',
+ '--output',
+ '${toUri(module, dillId)}',
+ '--packages-file',
+ '$rootScheme:/.packages',
+ ...(transitiveDependencies
+ .expand((m) => ['--input-linked', '${toUri(m, dillId)}'])),
+ ...(module.sources.expand((uri) => ['--source', sourceToImportUri(uri)])),
+ ];
+
+ var result = await _runProcess(
+ Platform.resolvedExecutable, workerArgs, root.toFilePath());
+ _checkExitCode(result, this, module);
+ }
+}
+
+// Step that invokes dart2js on the main module by providing the .dill files of
+// all transitive modules as inputs.
+class CompileFromDillStep implements IOModularStep {
+ @override
+ DataId get resultId => jsId;
+
+ @override
+ bool get needsSources => false;
+
+ @override
+ List<DataId> get dependencyDataNeeded => const [dillId];
+
+ @override
+ List<DataId> get moduleDataNeeded => const [dillId];
+
+ @override
+ bool get onlyOnMain => true;
+
+ @override
+ Future<void> execute(
+ Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+ if (_options.verbose) print("step: dart2js on $module");
+ Set<Module> transitiveDependencies = _computeTransitiveDependencies(module);
+ Iterable<String> dillDependencies =
+ transitiveDependencies.map((m) => '${toUri(m, dillId)}');
+ var sdkRoot = Platform.script.resolve("../../../../");
+ List<String> args = [
+ '--packages=${sdkRoot.toFilePath()}/.packages',
+ 'package:compiler/src/dart2js.dart',
+ '${toUri(module, dillId)}',
+ '--dill-dependencies=${dillDependencies.join(',')}',
+ '--out=${toUri(module, resultId)}',
+ ];
+ var result =
+ await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
+
+ _checkExitCode(result, this, module);
+ }
+}
+
+/// Step that runs the output of dart2js in d8 and saves the output.
+class RunD8 implements IOModularStep {
+ @override
+ DataId get resultId => const DataId("txt");
+
+ @override
+ bool get needsSources => false;
+
+ @override
+ List<DataId> get dependencyDataNeeded => const [];
+
+ @override
+ List<DataId> get moduleDataNeeded => const [jsId];
+
+ @override
+ bool get onlyOnMain => true;
+
+ @override
+ Future<void> execute(
+ Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+ if (_options.verbose) print("step: d8 on $module");
+ var sdkRoot = Platform.script.resolve("../../../../");
+ List<String> d8Args = [
+ sdkRoot
+ .resolve('sdk/lib/_internal/js_runtime/lib/preambles/d8.js')
+ .toFilePath(),
+ root.resolveUri(toUri(module, jsId)).toFilePath(),
+ ];
+ var result = await _runProcess(
+ sdkRoot.resolve(_d8executable).toFilePath(), d8Args, root.toFilePath());
+
+ _checkExitCode(result, this, module);
+
+ await File.fromUri(root.resolveUri(toUri(module, resultId)))
+ .writeAsString(result.stdout);
+ }
+}
+
+/// Helper to compute trnsitive dependencies from [module].
+Set<Module> _computeTransitiveDependencies(Module module) {
+ Set<Module> deps = {};
+ helper(Module m) {
+ if (deps.add(m)) m.dependencies.forEach(helper);
+ }
+
+ module.dependencies.forEach(helper);
+ return deps;
+}
+
+void _checkExitCode(ProcessResult result, IOModularStep step, Module module) {
+ if (result.exitCode != 0 || _options.verbose) {
+ stdout.write(result.stdout);
+ stderr.write(result.stderr);
+ }
+ if (result.exitCode != 0) {
+ exitCode = result.exitCode;
+ Expect.fail("${step.runtimeType} failed on $module");
+ }
+}
+
+Future<ProcessResult> _runProcess(
+ String command, List<String> arguments, String workingDirectory) {
+ if (_options.verbose) {
+ print('command:\n$command ${arguments.join(' ')} from $workingDirectory');
+ }
+ return Process.run(command, arguments, workingDirectory: workingDirectory);
+}
+
+String get _d8executable {
+ if (Platform.isWindows) {
+ return 'third_party/d8/windows/d8.exe';
+ } else if (Platform.isLinux) {
+ return 'third_party/d8/linux/d8';
+ } else if (Platform.isMacOS) {
+ return 'third_party/d8/macos/d8';
+ }
+ throw new UnsupportedError('Unsupported platform.');
+}
+
+class _Options {
+ bool showSkipped = false;
+ bool verbose = false;
+ String filter = null;
+
+ static _Options parse(List<String> args) {
+ var parser = new ArgParser()
+ ..addFlag('verbose',
+ abbr: 'v',
+ defaultsTo: false,
+ help: "print detailed information about the test and modular steps")
+ ..addFlag('show-skipped',
+ defaultsTo: false,
+ help: "print the name of the tests skipped by the filtering option")
+ ..addOption('filter',
+ help: "only run tests containing this filter as a substring");
+ ArgResults argResults = parser.parse(args);
+ return _Options()
+ ..showSkipped = argResults['show-skipped']
+ ..verbose = argResults['verbose']
+ ..filter = argResults['filter'];
+ }
+}