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 |