add tool to check that deferred libraries contain the expected code
BUG=
R=sigmund@google.com
Review URL: https://codereview.chromium.org//1425953002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6f4f16..f8e71fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,13 @@
# Changelog
+## 0.2.2
+- Added `deferred_libary_check` tool
+
## 0.2.1
- Merged `verify_deps` tool into `debug_info` tool
## 0.2.0
-- Added AllInfoJsonCodec
+- Added `AllInfoJsonCodec`
- Added `verify_deps` tool
## 0.1.0
@@ -16,8 +19,7 @@
- Added executable names
## 0.0.2
-- Add support for ConstantInfo
+- Add support for `ConstantInfo`
## 0.0.1
-
- Initial version
diff --git a/README.md b/README.md
index 811877e..50a2692 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,11 @@
in many ways (e.g. to tally together all libraries that belong to a package,
or all libraries that match certain name pattern).
+ * [`deferred_library_check`][deferred_lib]: a tool that verifies that code
+ was split into deferred parts as expected. This tool takes a specification
+ of the expected layout of code into deferred parts, and checks that the
+ output from `dart2js` meets the specification.
+
* [`function_size_analysis`][function_analysis]: a tool that shows how much
code was attributed to each function. This tool also uses dependency
information to compute dominance and reachability data. This information can
@@ -188,6 +193,47 @@
size. Currently dart2js's `--dump-info` is not complete, so numbers for
bootstrapping code and lazy static initializers are missing.
+### Deferred library verification
+
+This tool checks that the output from dart2js meets a given specification,
+given in a YAML file. It can be run as follows:
+
+```bash
+pub global activate dart2js_info # only needed once
+dart2js_info_deferred_library_check out.js.info.json manifest.yaml
+```
+
+The format of the YAML file is:
+
+```yaml
+main:
+ packages:
+ - some_package
+ - other_package
+
+foo:
+ packages:
+ - foo
+ - bar
+
+baz:
+ packages:
+ - baz
+ - quux
+```
+
+The YAML file consists of a list of declarations, one for each deferred
+part expected in the output. At least one of these parts must be named
+"main"; this is the main part that contains the program entrypoint. Each
+top-level part contains a list of package names that are expected to be
+contained in that part. Any package that is not explicitly listed is
+expected to be in the main part. For instance, in the example YAML above
+the part named "baz" is expected to contain the packages "baz" and "quux".
+
+The names for parts given in the specification YAML file (besides "main")
+are arbitrary and just used for reporting when the output does not meet the
+specification.
+
### Function size analysis tool
This command-line tool presents how much each function contributes to the total
@@ -268,6 +314,7 @@
[tracker]: https://github.com/dart-lang/dart2js_info/issues
[code_deps]: https://github.com/dart-lang/dart2js_info/blob/master/bin/code_deps.dart
[lib_split]: https://github.com/dart-lang/dart2js_info/blob/master/bin/library_size_split.dart
+[lib_split]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_check.dart
[coverage]: https://github.com/dart-lang/dart2js_info/blob/master/bin/coverage_log_server.dart
[live]: https://github.com/dart-lang/dart2js_info/blob/master/bin/live_code_size_analysis.dart
[function_analysis]: https://github.com/dart-lang/dart2js_info/blob/master/bin/function_size_analysis.dart
diff --git a/bin/deferred_library_check.dart b/bin/deferred_library_check.dart
new file mode 100644
index 0000000..0c092c5
--- /dev/null
+++ b/bin/deferred_library_check.dart
@@ -0,0 +1,144 @@
+// Copyright (c) 2015, 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.
+
+/// A command-line tool that verifies that deferred libraries split the code as
+/// expected.
+/// This tool checks that the output from dart2js meets a given specification,
+/// given in a YAML file. The format of the YAML file is:
+///
+/// main:
+/// packages:
+/// - some_package
+/// - other_package
+///
+/// foo:
+/// packages:
+/// - foo
+/// - bar
+///
+/// baz:
+/// packages:
+/// - baz
+/// - quux
+///
+/// The YAML file consists of a list of declarations, one for each deferred
+/// part expected in the output. At least one of these parts must be named
+/// "main"; this is the main part that contains the program entrypoint. Each
+/// top-level part contains a list of package names that are expected to be
+/// contained in that part. Any package that is not explicitly listed is
+/// expected to be in the main part. For instance, in the example YAML above
+/// the part named "baz" is expected to contain the packages "baz" and "quux".
+///
+/// The names for parts given in the specification YAML file (besides "main")
+/// are arbitrary and just used for reporting when the output does not meet the
+/// specification.
+library dart2js_info.bin.deferred_library_check;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:dart2js_info/info.dart';
+import 'package:quiver/collection.dart';
+import 'package:yaml/yaml.dart';
+
+Future main(List<String> args) async {
+ if (args.length < 2) {
+ usage();
+ exit(1);
+ }
+ var info = await infoFromFile(args[0]);
+ var manifest = await manifestFromFile(args[1]);
+
+ // For each part in the manifest, record the expected "packages" for that
+ // part.
+ var packages = <String, String>{};
+ for (var part in manifest.keys) {
+ for (var package in manifest[part]['packages']) {
+ if (packages.containsKey(package)) {
+ print('You cannot specify that package "$package" maps to both parts '
+ '"$part" and "${packages[package]}".');
+ exit(1);
+ }
+ packages[package] = part;
+ }
+ }
+
+ var guessedPartMapping = new BiMap<String, String>();
+ guessedPartMapping['main'] = 'main';
+
+ bool anyFailed = false;
+
+ checkInfo(BasicInfo info) {
+ var lib = getLibraryOf(info);
+ if (lib != null && isPackageUri(lib.uri)) {
+ var packageName = getPackageName(lib.uri);
+ var outputUnitName = info.outputUnit.name;
+ var expectedPart;
+ if (packages.containsKey(packageName)) {
+ expectedPart = packages[packageName];
+ } else {
+ expectedPart = 'main';
+ }
+ var expectedOutputUnit = guessedPartMapping[expectedPart];
+ if (expectedOutputUnit == null) {
+ guessedPartMapping[expectedPart] = outputUnitName;
+ } else {
+ if (expectedOutputUnit != outputUnitName) {
+ // TODO(het): add options for how to treat unspecified packages
+ if (!packages.containsKey(packageName)) {
+ print('"${info.name}" from package "$packageName" was not declared '
+ 'to be in an explicit part but was not in the main part');
+ } else {
+ var actualPart = guessedPartMapping.inverse[outputUnitName];
+ print('"${info.name}" from package "$packageName" was specified to '
+ 'be in part $expectedPart but is in part $actualPart');
+ }
+ anyFailed = true;
+ }
+ }
+ }
+ }
+
+ info.functions.forEach(checkInfo);
+ info.fields.forEach(checkInfo);
+ if (anyFailed) {
+ print('The dart2js output did not meet the specification.');
+ } else {
+ print('The dart2js output meets the specification');
+ }
+}
+
+LibraryInfo getLibraryOf(Info info) {
+ var current = info;
+ while (current is! LibraryInfo) {
+ if (current == null) {
+ return null;
+ }
+ current = current.parent;
+ }
+ return current;
+}
+
+bool isPackageUri(Uri uri) => uri.scheme == 'package';
+
+String getPackageName(Uri uri) {
+ assert(isPackageUri(uri));
+ return uri.pathSegments.first;
+}
+
+Future<AllInfo> infoFromFile(String fileName) async {
+ var file = await new File(fileName).readAsString();
+ return new AllInfoJsonCodec().decode(JSON.decode(file));
+}
+
+Future manifestFromFile(String fileName) async {
+ var file = await new File(fileName).readAsString();
+ return loadYaml(file);
+}
+
+void usage() {
+ print('''
+usage: dart2js_info_deferred_library_check dump.info.json manifest.yaml''');
+}
diff --git a/lib/json_info_codec.dart b/lib/json_info_codec.dart
index 1293d30..6ee7634 100644
--- a/lib/json_info_codec.dart
+++ b/lib/json_info_codec.dart
@@ -48,11 +48,21 @@
result.dependencies[idMap[k]] = deps.map((d) => idMap[d]).toList();
});
+ result.outputUnits.addAll(json['outputUnits'].map(parseOutputUnit));
+
result.program = parseProgram(json['program']);
// todo: version, etc
return result;
}
+ OutputUnitInfo parseOutputUnit(Map json) {
+ OutputUnitInfo result = parseId(json['id']);
+ result
+ ..name = json['name']
+ ..size = json['size'];
+ return result;
+ }
+
LibraryInfo parseLibrary(Map json) {
LibraryInfo result = parseId(json['id']);
result
diff --git a/pubspec.yaml b/pubspec.yaml
index 81b8c2d..0b4ae5c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: dart2js_info
-version: 0.2.1
+version: 0.2.2
description: >
Libraries and tools to process data produced when running dart2js with
--dump-info.
@@ -12,6 +12,7 @@
shelf: ^0.6.1+2
yaml: ^2.1.0
charcode: ^1.1.0
+ quiver: ^0.21.0
environment:
sdk: '>=1.11.0 <2.0.0'
@@ -22,6 +23,7 @@
dart2js_info_code_deps: code_deps
dart2js_info_coverage_log_server: coverage_log_server
dart2js_info_debug_info: debug_info
+ dart2js_info_deferred_library_check: deferred_library_check
dart2js_info_function_size_analysis: function_size_analysis
dart2js_info_library_size_split: library_size_split
dart2js_info_live_code_size_analysis: live_code_size_analysis