| 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 |