Index: packages/polymer/test/build/common.dart |
diff --git a/packages/polymer/test/build/common.dart b/packages/polymer/test/build/common.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8a1b620bd57309157eaf159d3f58d93ab8617db4 |
--- /dev/null |
+++ b/packages/polymer/test/build/common.dart |
@@ -0,0 +1,258 @@ |
+// Copyright (c) 2013, 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. |
+ |
+library polymer.test.build.common; |
+ |
+import 'dart:async'; |
+ |
+import 'package:barback/barback.dart'; |
+import 'package:code_transformers/messages/build_logger.dart' |
+ show LOG_EXTENSION; |
+import 'package:polymer/src/build/common.dart'; |
+import 'package:stack_trace/stack_trace.dart'; |
+import 'package:unittest/unittest.dart'; |
+ |
+String idToString(AssetId id) => '${id.package}|${id.path}'; |
+AssetId idFromString(String s) { |
+ int index = s.indexOf('|'); |
+ return new AssetId(s.substring(0, index), s.substring(index + 1)); |
+} |
+ |
+String _removeTrailingWhitespace(String str) => str.splitMapJoin('\n', |
+ onNonMatch: (s) => s.replaceAll(new RegExp(r'\s+$'), '')); |
+ |
+/// A helper package provider that has files stored in memory, also wraps |
+/// [Barback] to simply our tests. |
+class TestHelper implements PackageProvider { |
+ /// Maps from an asset string identifier of the form 'package|path' to the |
+ /// file contents. |
+ final Map<String, String> files; |
+ final Iterable<String> packages; |
+ final List<String> messages; |
+ int messagesSeen = 0; |
+ bool errorSeen = false; |
+ |
+ Barback barback; |
+ var errorSubscription; |
+ var resultSubscription; |
+ var logSubscription; |
+ |
+ Future<Asset> getAsset(AssetId id) { |
+ var content = files[idToString(id)]; |
+ if (content == null) fail('error: requested $id, but $id is not available'); |
+ return new Future.value(new Asset.fromString(id, content)); |
+ } |
+ |
+ TestHelper(List<List<Transformer>> transformers, Map<String, String> files, |
+ this.messages) |
+ : files = files, |
+ packages = files.keys.map((s) => idFromString(s).package) { |
+ barback = new Barback(this); |
+ for (var p in packages) { |
+ barback.updateTransformers(p, transformers); |
+ } |
+ |
+ errorSubscription = barback.errors.listen((e) { |
+ var trace = null; |
+ if (e is Error) trace = e.stackTrace; |
+ if (trace != null) { |
+ print(Trace.format(trace)); |
+ } |
+ fail('error running barback: $e'); |
+ }); |
+ |
+ resultSubscription = barback.results.listen((result) { |
+ expect(result.succeeded, !errorSeen, reason: "${result.errors}"); |
+ }); |
+ |
+ logSubscription = barback.log.listen((entry) { |
+ // Ignore info messages. |
+ if (entry.level == LogLevel.INFO || entry.level == LogLevel.FINE) return; |
+ if (entry.level == LogLevel.ERROR) errorSeen = true; |
+ // We only check messages when an expectation is provided. |
+ if (messages == null) return; |
+ |
+ var errorLink = |
+ new RegExp(' See http://goo.gl/5HPeuP#polymer_[0-9]* for details.'); |
+ var text = entry.message; |
+ var newText = text.replaceFirst(errorLink, ''); |
+ expect(text != newText, isTrue); |
+ var msg = '${entry.level.name.toLowerCase()}: ${newText}'; |
+ var span = entry.span; |
+ var spanInfo = span == null |
+ ? '' |
+ : ' (${span.sourceUrl} ${span.start.line} ${span.start.column})'; |
+ var index = messagesSeen++; |
+ expect(messagesSeen, lessThanOrEqualTo(messages.length), |
+ reason: 'more messages than expected.\nMessage seen: $msg$spanInfo'); |
+ expect('$msg$spanInfo', messages[index]); |
+ }); |
+ } |
+ |
+ void tearDown() { |
+ errorSubscription.cancel(); |
+ resultSubscription.cancel(); |
+ logSubscription.cancel(); |
+ } |
+ |
+ /// Tells barback which files have changed, and thus anything that depends on |
+ /// it on should be computed. By default mark all the input files. |
+ void run([Iterable<String> paths]) { |
+ if (paths == null) paths = files.keys; |
+ barback.updateSources(paths.map(idFromString)); |
+ } |
+ |
+ Future<String> operator [](String assetString) { |
+ return barback |
+ .getAssetById(idFromString(assetString)) |
+ .then((asset) => asset.readAsString()); |
+ } |
+ |
+ Future check(String assetIdString, String content) { |
+ return this[assetIdString].then((value) { |
+ value = _removeTrailingWhitespace(value); |
+ content = _removeTrailingWhitespace(content); |
+ expect(value, content, reason: 'Final output of $assetIdString differs.'); |
+ }); |
+ } |
+ |
+ Future checkAll(Map<String, String> files) { |
+ return barback.results.first.then((_) { |
+ if (files == null) return null; |
+ var futures = []; |
+ files.forEach((k, v) { |
+ futures.add(check(k, v)); |
+ }); |
+ return Future.wait(futures); |
+ }).then((_) { |
+ // We only check messages when an expectation is provided. |
+ if (messages == null) return; |
+ expect(messagesSeen, messages.length, |
+ reason: 'less messages than expected'); |
+ }); |
+ } |
+} |
+ |
+testPhases(String testName, List<List<Transformer>> phases, |
+ Map<String, String> inputFiles, Map<String, String> expectedFiles, |
+ [List<String> expectedMessages, bool solo = false]) { |
+ // Include mock versions of the polymer library that can be used to test |
+ // resolver-based code generation. |
+ POLYMER_MOCKS.forEach((file, contents) { |
+ inputFiles[file] = contents; |
+ }); |
+ (solo ? solo_test : test)(testName, () { |
+ var helper = new TestHelper(phases, inputFiles, expectedMessages)..run(); |
+ return helper.checkAll(expectedFiles).whenComplete(() => helper.tearDown()); |
+ }); |
+} |
+ |
+solo_testPhases(String testName, List<List<Transformer>> phases, |
+ Map<String, String> inputFiles, Map<String, String> expectedFiles, |
+ [List<String> expectedMessages]) => testPhases( |
+ testName, phases, inputFiles, expectedFiles, expectedMessages, true); |
+ |
+// Similar to testPhases, but tests all the cases around log behaviour in |
+// different modes. Any expectedFiles with [LOG_EXTENSION] will be removed from |
+// the expectation as appropriate, and any error logs will be changed to expect |
+// warning logs as appropriate. |
+testLogOutput(Function buildPhase, String testName, |
+ Map<String, String> inputFiles, Map<String, String> expectedFiles, |
+ [List<String> expectedMessages, bool solo = false]) { |
+ final transformOptions = [ |
+ new TransformOptions(injectBuildLogsInOutput: false, releaseMode: false), |
+ new TransformOptions(injectBuildLogsInOutput: false, releaseMode: true), |
+ new TransformOptions(injectBuildLogsInOutput: true, releaseMode: false), |
+ new TransformOptions(injectBuildLogsInOutput: true, releaseMode: true), |
+ ]; |
+ |
+ for (var options in transformOptions) { |
+ var phase = buildPhase(options); |
+ var actualExpectedFiles = {}; |
+ expectedFiles.forEach((file, content) { |
+ if (file.contains(LOG_EXTENSION) && |
+ (!options.injectBuildLogsInOutput || options.releaseMode)) { |
+ return; |
+ } |
+ actualExpectedFiles[file] = content; |
+ }); |
+ var fullTestName = '$testName: ' |
+ 'injectLogs=${options.injectBuildLogsInOutput} ' |
+ 'releaseMode=${options.releaseMode}'; |
+ testPhases(fullTestName, [[phase]], inputFiles, actualExpectedFiles, |
+ expectedMessages |
+ .map((m) => |
+ options.releaseMode ? m : m.replaceFirst('error:', 'warning:')) |
+ .toList(), solo); |
+ } |
+} |
+ |
+/// Generate an expected ._data file, where all files are assumed to be in the |
+/// same [package]. |
+String expectedData(List<String> urls, {package: 'a', experimental: false}) { |
+ var ids = urls.map((e) => '["$package","$e"]').join(','); |
+ return '{"experimental_bootstrap":$experimental,"script_ids":[$ids]}'; |
+} |
+ |
+const EMPTY_DATA = '{"experimental_bootstrap":false,"script_ids":[]}'; |
+ |
+const DART_SUPPORT_TAG = |
+ '<script src="packages/web_components/dart_support.js"></script>'; |
+const WEB_COMPONENTS_JS_TAG = |
+ '<script src="packages/web_components/webcomponents.min.js"></script>'; |
+const COMPATIBILITY_JS_TAGS = '$WEB_COMPONENTS_JS_TAG$DART_SUPPORT_TAG'; |
+const PLATFORM_JS_TAG = |
+ '<script src="packages/web_components/platform.js"></script>'; |
+ |
+const INTEROP_TAG = '<script src="packages/browser/interop.js"></script>'; |
+const DART_JS_TAG = '<script src="packages/browser/dart.js"></script>'; |
+ |
+const POLYMER_MOCKS = const { |
+ 'initialize|lib/initialize.dart': ''' |
+ library initialize; |
+ |
+ abstract class Initializer<T> {} |
+ |
+ class _InitMethod implements Initializer<Function> { |
+ const _InitMethod(); |
+ } |
+ const _InitMethod initMethod = const _InitMethod();''', |
+ 'polymer|lib/polymer.html': '<!DOCTYPE html><html>' |
+ '<link rel="import" href="../../packages/polymer_interop/polymer.html">', |
+ 'polymer|lib/polymer_experimental.html': '<!DOCTYPE html><html>' |
+ '<link rel="import" href="polymer.html">', |
+ 'polymer_interop|lib/polymer.html': '<!DOCTYPE html><html>' |
+ '<link rel="import" href="src/js/polymer.html">', |
+ 'polymer_interop|lib/src/js/polymer.html': '<!DOCTYPE html>', |
+ 'polymer|lib/polymer.dart': 'library polymer;\n' |
+ 'import "dart:html";\n' |
+ 'import "package:initialize/initialize.dart";\n' |
+ 'export "package:observe/observe.dart";\n' // for @observable |
+ 'part "src/loader.dart";\n' // for @CustomTag and @initMethod |
+ 'part "src/instance.dart";\n', // for @published and @ObserveProperty |
+ |
+ 'polymer|lib/src/loader.dart': 'part of polymer;\n' |
+ 'class CustomTag implements Initializer<Type> {\n' |
+ ' final String tagName;\n' |
+ ' const CustomTag(this.tagName);' |
+ '}\n', |
+ 'polymer|lib/src/instance.dart': 'part of polymer;\n' |
+ 'class PublishedProperty { const PublishedProperty(); }\n' |
+ 'const published = const PublishedProperty();\n' |
+ 'class ComputedProperty {' |
+ ' final String expression;\n' |
+ ' const ComputedProperty();' |
+ '}\n' |
+ 'class ObserveProperty { const ObserveProperty(); }\n' |
+ 'abstract class Polymer {}\n' |
+ 'class PolymerElement extends HtmlElement with Polymer {}\n', |
+ 'polymer|lib/init.dart': 'library polymer.init;\n' |
+ 'import "package:polymer/polymer.dart";\n' |
+ 'main() {};\n', |
+ 'observe|lib/observe.dart': 'library observe;\n' |
+ 'export "src/metadata.dart";', |
+ 'observe|lib/src/metadata.dart': 'library observe.src.metadata;\n' |
+ 'class ObservableProperty { const ObservableProperty(); }\n' |
+ 'const observable = const ObservableProperty();\n', |
+}; |