blob: 0c5d421b17df48276fffe9ee9e91d73dc0c00d17 [file] [log] [blame]
// 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: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) return;
if (entry.level == LogLevel.ERROR) errorSeen = true;
// We only check messages when an expectation is provided.
if (messages == null) return;
var msg = '${entry.level.name.toLowerCase()}: ${entry.message}';
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);
/// 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 WEB_COMPONENTS_TAG =
'<script src="packages/web_components/platform.js"></script>\n'
'<script src="packages/web_components/dart_support.js"></script>\n';
const INTEROP_TAG = '<script src="packages/browser/interop.js"></script>\n';
const DART_JS_TAG = '<script src="packages/browser/dart.js"></script>';
const POLYMER_MOCKS = const {
'polymer|lib/src/js/polymer/polymer.html': '<!DOCTYPE html><html>',
'polymer|lib/polymer.html': '<!DOCTYPE html><html>'
'<link rel="import" href="src/js/polymer/polymer.html">',
'polymer|lib/polymer_experimental.html':
'<!DOCTYPE html><html>'
'<link rel="import" href="polymer.html">',
'polymer|lib/polymer.dart':
'library polymer;\n'
'import "dart:html";\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 {\n'
' final String tagName;\n'
' const CustomTag(this.tagName);'
'}\n'
'class InitMethodAnnotation { const InitMethodAnnotation(); }\n'
'const initMethod = const InitMethodAnnotation();\n',
'polymer|lib/src/instance.dart':
'part of polymer;\n'
'class PublishedProperty { const PublishedProperty(); }\n'
'const published = const PublishedProperty();\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',
};