OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library polymer.test.build.common; |
| 6 |
| 7 import 'dart:async'; |
| 8 |
| 9 import 'package:barback/barback.dart'; |
| 10 import 'package:code_transformers/messages/build_logger.dart' |
| 11 show LOG_EXTENSION; |
| 12 import 'package:polymer/src/build/common.dart'; |
| 13 import 'package:stack_trace/stack_trace.dart'; |
| 14 import 'package:unittest/unittest.dart'; |
| 15 |
| 16 String idToString(AssetId id) => '${id.package}|${id.path}'; |
| 17 AssetId idFromString(String s) { |
| 18 int index = s.indexOf('|'); |
| 19 return new AssetId(s.substring(0, index), s.substring(index + 1)); |
| 20 } |
| 21 |
| 22 String _removeTrailingWhitespace(String str) => str.splitMapJoin('\n', |
| 23 onNonMatch: (s) => s.replaceAll(new RegExp(r'\s+$'), '')); |
| 24 |
| 25 /// A helper package provider that has files stored in memory, also wraps |
| 26 /// [Barback] to simply our tests. |
| 27 class TestHelper implements PackageProvider { |
| 28 /// Maps from an asset string identifier of the form 'package|path' to the |
| 29 /// file contents. |
| 30 final Map<String, String> files; |
| 31 final Iterable<String> packages; |
| 32 final List<String> messages; |
| 33 int messagesSeen = 0; |
| 34 bool errorSeen = false; |
| 35 |
| 36 Barback barback; |
| 37 var errorSubscription; |
| 38 var resultSubscription; |
| 39 var logSubscription; |
| 40 |
| 41 Future<Asset> getAsset(AssetId id) { |
| 42 var content = files[idToString(id)]; |
| 43 if (content == null) fail('error: requested $id, but $id is not available'); |
| 44 return new Future.value(new Asset.fromString(id, content)); |
| 45 } |
| 46 |
| 47 TestHelper(List<List<Transformer>> transformers, Map<String, String> files, |
| 48 this.messages) |
| 49 : files = files, |
| 50 packages = files.keys.map((s) => idFromString(s).package) { |
| 51 barback = new Barback(this); |
| 52 for (var p in packages) { |
| 53 barback.updateTransformers(p, transformers); |
| 54 } |
| 55 |
| 56 errorSubscription = barback.errors.listen((e) { |
| 57 var trace = null; |
| 58 if (e is Error) trace = e.stackTrace; |
| 59 if (trace != null) { |
| 60 print(Trace.format(trace)); |
| 61 } |
| 62 fail('error running barback: $e'); |
| 63 }); |
| 64 |
| 65 resultSubscription = barback.results.listen((result) { |
| 66 expect(result.succeeded, !errorSeen, reason: "${result.errors}"); |
| 67 }); |
| 68 |
| 69 logSubscription = barback.log.listen((entry) { |
| 70 // Ignore info messages. |
| 71 if (entry.level == LogLevel.INFO || entry.level == LogLevel.FINE) return; |
| 72 if (entry.level == LogLevel.ERROR) errorSeen = true; |
| 73 // We only check messages when an expectation is provided. |
| 74 if (messages == null) return; |
| 75 |
| 76 var errorLink = |
| 77 new RegExp(' See http://goo.gl/5HPeuP#polymer_[0-9]* for details.'); |
| 78 var text = entry.message; |
| 79 var newText = text.replaceFirst(errorLink, ''); |
| 80 expect(text != newText, isTrue); |
| 81 var msg = '${entry.level.name.toLowerCase()}: ${newText}'; |
| 82 var span = entry.span; |
| 83 var spanInfo = span == null |
| 84 ? '' |
| 85 : ' (${span.sourceUrl} ${span.start.line} ${span.start.column})'; |
| 86 var index = messagesSeen++; |
| 87 expect(messagesSeen, lessThanOrEqualTo(messages.length), |
| 88 reason: 'more messages than expected.\nMessage seen: $msg$spanInfo'); |
| 89 expect('$msg$spanInfo', messages[index]); |
| 90 }); |
| 91 } |
| 92 |
| 93 void tearDown() { |
| 94 errorSubscription.cancel(); |
| 95 resultSubscription.cancel(); |
| 96 logSubscription.cancel(); |
| 97 } |
| 98 |
| 99 /// Tells barback which files have changed, and thus anything that depends on |
| 100 /// it on should be computed. By default mark all the input files. |
| 101 void run([Iterable<String> paths]) { |
| 102 if (paths == null) paths = files.keys; |
| 103 barback.updateSources(paths.map(idFromString)); |
| 104 } |
| 105 |
| 106 Future<String> operator [](String assetString) { |
| 107 return barback |
| 108 .getAssetById(idFromString(assetString)) |
| 109 .then((asset) => asset.readAsString()); |
| 110 } |
| 111 |
| 112 Future check(String assetIdString, String content) { |
| 113 return this[assetIdString].then((value) { |
| 114 value = _removeTrailingWhitespace(value); |
| 115 content = _removeTrailingWhitespace(content); |
| 116 expect(value, content, reason: 'Final output of $assetIdString differs.'); |
| 117 }); |
| 118 } |
| 119 |
| 120 Future checkAll(Map<String, String> files) { |
| 121 return barback.results.first.then((_) { |
| 122 if (files == null) return null; |
| 123 var futures = []; |
| 124 files.forEach((k, v) { |
| 125 futures.add(check(k, v)); |
| 126 }); |
| 127 return Future.wait(futures); |
| 128 }).then((_) { |
| 129 // We only check messages when an expectation is provided. |
| 130 if (messages == null) return; |
| 131 expect(messagesSeen, messages.length, |
| 132 reason: 'less messages than expected'); |
| 133 }); |
| 134 } |
| 135 } |
| 136 |
| 137 testPhases(String testName, List<List<Transformer>> phases, |
| 138 Map<String, String> inputFiles, Map<String, String> expectedFiles, |
| 139 [List<String> expectedMessages, bool solo = false]) { |
| 140 // Include mock versions of the polymer library that can be used to test |
| 141 // resolver-based code generation. |
| 142 POLYMER_MOCKS.forEach((file, contents) { |
| 143 inputFiles[file] = contents; |
| 144 }); |
| 145 (solo ? solo_test : test)(testName, () { |
| 146 var helper = new TestHelper(phases, inputFiles, expectedMessages)..run(); |
| 147 return helper.checkAll(expectedFiles).whenComplete(() => helper.tearDown()); |
| 148 }); |
| 149 } |
| 150 |
| 151 solo_testPhases(String testName, List<List<Transformer>> phases, |
| 152 Map<String, String> inputFiles, Map<String, String> expectedFiles, |
| 153 [List<String> expectedMessages]) => testPhases( |
| 154 testName, phases, inputFiles, expectedFiles, expectedMessages, true); |
| 155 |
| 156 // Similar to testPhases, but tests all the cases around log behaviour in |
| 157 // different modes. Any expectedFiles with [LOG_EXTENSION] will be removed from |
| 158 // the expectation as appropriate, and any error logs will be changed to expect |
| 159 // warning logs as appropriate. |
| 160 testLogOutput(Function buildPhase, String testName, |
| 161 Map<String, String> inputFiles, Map<String, String> expectedFiles, |
| 162 [List<String> expectedMessages, bool solo = false]) { |
| 163 final transformOptions = [ |
| 164 new TransformOptions(injectBuildLogsInOutput: false, releaseMode: false), |
| 165 new TransformOptions(injectBuildLogsInOutput: false, releaseMode: true), |
| 166 new TransformOptions(injectBuildLogsInOutput: true, releaseMode: false), |
| 167 new TransformOptions(injectBuildLogsInOutput: true, releaseMode: true), |
| 168 ]; |
| 169 |
| 170 for (var options in transformOptions) { |
| 171 var phase = buildPhase(options); |
| 172 var actualExpectedFiles = {}; |
| 173 expectedFiles.forEach((file, content) { |
| 174 if (file.contains(LOG_EXTENSION) && |
| 175 (!options.injectBuildLogsInOutput || options.releaseMode)) { |
| 176 return; |
| 177 } |
| 178 actualExpectedFiles[file] = content; |
| 179 }); |
| 180 var fullTestName = '$testName: ' |
| 181 'injectLogs=${options.injectBuildLogsInOutput} ' |
| 182 'releaseMode=${options.releaseMode}'; |
| 183 testPhases(fullTestName, [[phase]], inputFiles, actualExpectedFiles, |
| 184 expectedMessages |
| 185 .map((m) => |
| 186 options.releaseMode ? m : m.replaceFirst('error:', 'warning:')) |
| 187 .toList(), solo); |
| 188 } |
| 189 } |
| 190 |
| 191 /// Generate an expected ._data file, where all files are assumed to be in the |
| 192 /// same [package]. |
| 193 String expectedData(List<String> urls, {package: 'a', experimental: false}) { |
| 194 var ids = urls.map((e) => '["$package","$e"]').join(','); |
| 195 return '{"experimental_bootstrap":$experimental,"script_ids":[$ids]}'; |
| 196 } |
| 197 |
| 198 const EMPTY_DATA = '{"experimental_bootstrap":false,"script_ids":[]}'; |
| 199 |
| 200 const DART_SUPPORT_TAG = |
| 201 '<script src="packages/web_components/dart_support.js"></script>'; |
| 202 const WEB_COMPONENTS_JS_TAG = |
| 203 '<script src="packages/web_components/webcomponents.min.js"></script>'; |
| 204 const COMPATIBILITY_JS_TAGS = '$WEB_COMPONENTS_JS_TAG$DART_SUPPORT_TAG'; |
| 205 const PLATFORM_JS_TAG = |
| 206 '<script src="packages/web_components/platform.js"></script>'; |
| 207 |
| 208 const INTEROP_TAG = '<script src="packages/browser/interop.js"></script>'; |
| 209 const DART_JS_TAG = '<script src="packages/browser/dart.js"></script>'; |
| 210 |
| 211 const POLYMER_MOCKS = const { |
| 212 'initialize|lib/initialize.dart': ''' |
| 213 library initialize; |
| 214 |
| 215 abstract class Initializer<T> {} |
| 216 |
| 217 class _InitMethod implements Initializer<Function> { |
| 218 const _InitMethod(); |
| 219 } |
| 220 const _InitMethod initMethod = const _InitMethod();''', |
| 221 'polymer|lib/polymer.html': '<!DOCTYPE html><html>' |
| 222 '<link rel="import" href="../../packages/polymer_interop/polymer.html">', |
| 223 'polymer|lib/polymer_experimental.html': '<!DOCTYPE html><html>' |
| 224 '<link rel="import" href="polymer.html">', |
| 225 'polymer_interop|lib/polymer.html': '<!DOCTYPE html><html>' |
| 226 '<link rel="import" href="src/js/polymer.html">', |
| 227 'polymer_interop|lib/src/js/polymer.html': '<!DOCTYPE html>', |
| 228 'polymer|lib/polymer.dart': 'library polymer;\n' |
| 229 'import "dart:html";\n' |
| 230 'import "package:initialize/initialize.dart";\n' |
| 231 'export "package:observe/observe.dart";\n' // for @observable |
| 232 'part "src/loader.dart";\n' // for @CustomTag and @initMethod |
| 233 'part "src/instance.dart";\n', // for @published and @ObserveProperty |
| 234 |
| 235 'polymer|lib/src/loader.dart': 'part of polymer;\n' |
| 236 'class CustomTag implements Initializer<Type> {\n' |
| 237 ' final String tagName;\n' |
| 238 ' const CustomTag(this.tagName);' |
| 239 '}\n', |
| 240 'polymer|lib/src/instance.dart': 'part of polymer;\n' |
| 241 'class PublishedProperty { const PublishedProperty(); }\n' |
| 242 'const published = const PublishedProperty();\n' |
| 243 'class ComputedProperty {' |
| 244 ' final String expression;\n' |
| 245 ' const ComputedProperty();' |
| 246 '}\n' |
| 247 'class ObserveProperty { const ObserveProperty(); }\n' |
| 248 'abstract class Polymer {}\n' |
| 249 'class PolymerElement extends HtmlElement with Polymer {}\n', |
| 250 'polymer|lib/init.dart': 'library polymer.init;\n' |
| 251 'import "package:polymer/polymer.dart";\n' |
| 252 'main() {};\n', |
| 253 'observe|lib/observe.dart': 'library observe;\n' |
| 254 'export "src/metadata.dart";', |
| 255 'observe|lib/src/metadata.dart': 'library observe.src.metadata;\n' |
| 256 'class ObservableProperty { const ObservableProperty(); }\n' |
| 257 'const observable = const ObservableProperty();\n', |
| 258 }; |
OLD | NEW |