| Index: pkg/front_end/test/fasta/shaker_test.dart
|
| diff --git a/pkg/front_end/test/fasta/shaker_test.dart b/pkg/front_end/test/fasta/shaker_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..85c29ad6b3f830ef6d9651dea1d752b9736768de
|
| --- /dev/null
|
| +++ b/pkg/front_end/test/fasta/shaker_test.dart
|
| @@ -0,0 +1,241 @@
|
| +// Copyright (c) 2017, 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.md file.
|
| +
|
| +/// Tests basic functionality of the API tree-shaker.
|
| +///
|
| +/// Each input file is built and tree-shaken, then we check that the set of
|
| +/// libraries, classes, and members that are retained match those declared in an
|
| +/// expectations file.
|
| +///
|
| +/// Input files may contain markers to turn on flags that configure this
|
| +/// runner. Currently only the following marker is recognized:
|
| +/// @@SHOW_CORE_LIBRARIES@@ - whether to check for retained information from
|
| +/// the core libraries. By default this runner only checks for members of
|
| +/// pkg/front_end/testcases/shaker/lib/lib.dart.
|
| +library fasta.test.shaker_test;
|
| +
|
| +import 'dart:async' show Future;
|
| +import 'dart:convert' show JSON;
|
| +import 'dart:io' show File;
|
| +
|
| +export 'package:testing/testing.dart' show Chain, runMe;
|
| +import 'package:front_end/physical_file_system.dart';
|
| +import 'package:front_end/src/fasta/dill/dill_target.dart' show DillTarget;
|
| +import 'package:front_end/src/fasta/errors.dart' show InputError;
|
| +import 'package:front_end/src/fasta/kernel/kernel_target.dart'
|
| + show KernelTarget;
|
| +import 'package:front_end/src/fasta/kernel/verifier.dart' show verifyProgram;
|
| +import 'package:front_end/src/fasta/testing/kernel_chain.dart' show runDiff;
|
| +import 'package:front_end/src/fasta/testing/patched_sdk_location.dart';
|
| +import 'package:front_end/src/fasta/ticker.dart' show Ticker;
|
| +import 'package:front_end/src/fasta/translate_uri.dart' show TranslateUri;
|
| +import 'package:front_end/src/fasta/util/relativize.dart' show relativizeUri;
|
| +import 'package:kernel/ast.dart' show Program;
|
| +import 'package:kernel/kernel.dart' show loadProgramFromBytes;
|
| +import 'package:testing/testing.dart'
|
| + show Chain, ChainContext, ExpectationSet, Result, Step, TestDescription;
|
| +import 'testing/suite.dart';
|
| +
|
| +main(List<String> arguments) => runMe(arguments, createContext, "testing.json");
|
| +
|
| +Future<TreeShakerContext> createContext(
|
| + Chain suite, Map<String, String> environment) {
|
| + return TreeShakerContext.create(environment);
|
| +}
|
| +
|
| +/// Context used to run the tree-shaking test suite.
|
| +class TreeShakerContext extends ChainContext {
|
| + final TranslateUri uriTranslator;
|
| + final Uri outlineUri;
|
| + final List<Step> steps;
|
| + final List<int> outlineBytes;
|
| +
|
| + final ExpectationSet expectationSet =
|
| + new ExpectationSet.fromJsonList(JSON.decode(EXPECTATIONS));
|
| +
|
| + TreeShakerContext(this.outlineUri, this.uriTranslator, this.outlineBytes,
|
| + bool updateExpectations)
|
| + : steps = <Step>[
|
| + const BuildProgram(),
|
| + new CheckShaker(updateExpectations: updateExpectations),
|
| + ];
|
| +
|
| + Program loadPlatformOutline() {
|
| + // Note: we rebuild the platform outline on every test because the compiler
|
| + // currently mutates the in-memory representation of the program without
|
| + // cloning it.
|
| + return loadProgramFromBytes(outlineBytes);
|
| + }
|
| +
|
| + static create(Map<String, String> environment) async {
|
| + environment[ENABLE_FULL_COMPILE] = "";
|
| + environment[AST_KIND_INDEX] = "${AstKind.Kernel.index}";
|
| + bool updateExpectations = environment["updateExpectations"] == "true";
|
| + Uri sdk = await computePatchedSdk();
|
| + Uri outlineUri = sdk.resolve('outline.dill');
|
| + Uri packages = Uri.base.resolve(".packages");
|
| + TranslateUri uriTranslator =
|
| + await TranslateUri.parse(PhysicalFileSystem.instance, packages);
|
| + List<int> outlineBytes = new File.fromUri(outlineUri).readAsBytesSync();
|
| + return new TreeShakerContext(
|
| + outlineUri, uriTranslator, outlineBytes, updateExpectations);
|
| + }
|
| +}
|
| +
|
| +/// Step that extracts the test-specific options and builds the program without
|
| +/// applying tree-shaking.
|
| +class BuildProgram
|
| + extends Step<TestDescription, _IntermediateData, TreeShakerContext> {
|
| + const BuildProgram();
|
| + String get name => "build program";
|
| + Future<Result<_IntermediateData>> run(
|
| + TestDescription description, TreeShakerContext context) async {
|
| + try {
|
| + var platformOutline = context.loadPlatformOutline();
|
| + platformOutline.unbindCanonicalNames();
|
| + var dillTarget =
|
| + new DillTarget(new Ticker(isVerbose: false), context.uriTranslator);
|
| + dillTarget.loader.appendLibraries(platformOutline);
|
| + var sourceTarget = new KernelTarget(PhysicalFileSystem.instance,
|
| + dillTarget, context.uriTranslator, false);
|
| + await dillTarget.buildOutlines();
|
| +
|
| + var inputUri = description.uri;
|
| +
|
| + /// We treat the lib.dart library as a special dependency that was
|
| + /// previously built. To do so, we build it and append it back to the
|
| + /// dillTarget before building the actual test.
|
| + var libUri = inputUri.resolve('lib/lib.dart');
|
| + sourceTarget.read(libUri);
|
| + dillTarget.loader.appendLibraries(
|
| + await sourceTarget.buildOutlines(), (uri) => uri == libUri);
|
| +
|
| + /// This new KernelTarget contains only sources from the test without
|
| + /// lib.dart.
|
| + sourceTarget = new KernelTarget(PhysicalFileSystem.instance, dillTarget,
|
| + context.uriTranslator, false);
|
| +
|
| + await dillTarget.buildOutlines();
|
| + sourceTarget.read(inputUri);
|
| + var contents = new File.fromUri(inputUri).readAsStringSync();
|
| + var showCoreLibraries = contents.contains("@@SHOW_CORE_LIBRARIES@@");
|
| +
|
| + await sourceTarget.buildOutlines();
|
| + // Note: We run the tree-shaker as a separate step on this suite to be
|
| + // able to specify what libraries to tree shake (by default only the code
|
| + // in the dillTarget gets tree-shaken). We could apply the tree-shaker
|
| + // twice, but that seems unnecessary.
|
| + var program = await sourceTarget.buildProgram(trimDependencies: true);
|
| + return pass(new _IntermediateData(inputUri, program, showCoreLibraries));
|
| + } on InputError catch (e, s) {
|
| + return fail(null, e.error, s);
|
| + }
|
| + }
|
| +}
|
| +
|
| +/// Intermediate result from the testing chain.
|
| +class _IntermediateData {
|
| + /// The input URI provided to the test.
|
| + final Uri uri;
|
| +
|
| + /// Program built by [BuildProgram].
|
| + final Program program;
|
| +
|
| + /// Whether the output should include tree-shaking information about the core
|
| + /// libraries. This is specified in a comment on individual test files where
|
| + /// we believe that information is relevant.
|
| + final bool showCoreLibraries;
|
| +
|
| + _IntermediateData(this.uri, this.program, this.showCoreLibraries);
|
| +}
|
| +
|
| +/// A step that runs the tree-shaker and checks againt an expectation file for
|
| +/// the list of members and classes that should be preserved by the tree-shaker.
|
| +class CheckShaker extends Step<_IntermediateData, String, ChainContext> {
|
| + final bool updateExpectations;
|
| + const CheckShaker({this.updateExpectations: false});
|
| +
|
| + String get name => "match shaker expectation";
|
| +
|
| + /// Tree-shake dart:* libraries and the library under [_specialLibraryPath].
|
| + bool _isTreeShaken(Uri uri) =>
|
| + uri.isScheme('dart') ||
|
| + Uri.base.resolveUri(uri).path.endsWith(_specialLibraryPath);
|
| +
|
| + Future<Result<String>> run(
|
| + _IntermediateData data, ChainContext context) async {
|
| + String actualResult;
|
| + var entryUri = data.program.libraries
|
| + .firstWhere((l) => !l.importUri.isScheme('dart'))
|
| + .importUri;
|
| +
|
| + var program = data.program;
|
| +
|
| + var errors = verifyProgram(program, isOutline: false);
|
| + if (!errors.isEmpty) {
|
| + return new Result<String>(
|
| + null, context.expectationSet["VerificationError"], errors, null);
|
| + }
|
| +
|
| + // Build a text representation of what we expect to be retained.
|
| + var buffer = new StringBuffer();
|
| + buffer.writeln('DO NOT EDIT -- this file is autogenerated ---');
|
| + buffer.writeln('Tree-shaker preserved the following:');
|
| + for (var library in program.libraries) {
|
| + var importUri = library.importUri;
|
| + if (!_isTreeShaken(importUri)) continue;
|
| + if (importUri.isScheme('dart') && !data.showCoreLibraries) continue;
|
| + String uri = relativizeUri(library.importUri);
|
| + buffer.writeln('\nlibrary $uri:');
|
| + for (var member in library.members) {
|
| + buffer.writeln(' - member ${member.name}');
|
| + }
|
| + for (var typedef_ in library.typedefs) {
|
| + buffer.writeln(' - typedef ${typedef_.name}');
|
| + }
|
| + for (var cls in library.classes) {
|
| + buffer.writeln(' - class ${cls.name}');
|
| + for (var member in cls.members) {
|
| + var name = '${member.name}';
|
| + if (name == "") {
|
| + buffer.writeln(' - (default constructor)');
|
| + } else {
|
| + buffer.writeln(' - $name');
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + actualResult = "$buffer";
|
| +
|
| + // Compare against expectations using the text representation.
|
| + File expectedFile = new File("${entryUri.toFilePath()}.shaker");
|
| + if (await expectedFile.exists()) {
|
| + String expected = await expectedFile.readAsString();
|
| + if (expected.trim() != actualResult.trim()) {
|
| + if (!updateExpectations) {
|
| + String diff = await runDiff(expectedFile.uri, actualResult);
|
| + return fail(
|
| + null, "$entryUri doesn't match ${expectedFile.uri}\n$diff");
|
| + }
|
| + } else {
|
| + return pass(actualResult);
|
| + }
|
| + }
|
| + if (updateExpectations) {
|
| + expectedFile.writeAsStringSync(actualResult);
|
| + return pass(actualResult);
|
| + } else {
|
| + return fail(
|
| + actualResult,
|
| + """
|
| +Please create file ${expectedFile.path} with this content:
|
| +$buffer""");
|
| + }
|
| + }
|
| +}
|
| +
|
| +/// A special library used only to test the shaker. The suite above will
|
| +/// tree-shake the contents of this library.
|
| +const _specialLibraryPath = 'pkg/front_end/testcases/shaker/lib/lib.dart';
|
|
|