From 8504f3ae13d081ee78409e80777a4c71b04ffff0 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 10 Sep 2019 17:27:30 -0700 Subject: [PATCH] Update the supported library set for Flutter for web (#39983) --- .../flutter_goldens/lib/flutter_goldens.dart | 2 +- packages/flutter_test/lib/flutter_test.dart | 2 + .../flutter_test/lib/src/_binding_io.dart | 524 ++++++++++++++++++ .../flutter_test/lib/src/_binding_web.dart | 22 + .../flutter_test/lib/src/_goldens_io.dart | 208 +++++++ .../flutter_test/lib/src/_goldens_web.dart | 28 + packages/flutter_test/lib/src/binding.dart | 520 +---------------- packages/flutter_test/lib/src/goldens.dart | 239 +------- .../lib/src/build_runner/build_script.dart | 25 +- .../lib/src/build_runner/web_fs.dart | 6 +- 10 files changed, 841 insertions(+), 735 deletions(-) create mode 100644 packages/flutter_test/lib/src/_binding_io.dart create mode 100644 packages/flutter_test/lib/src/_binding_web.dart create mode 100644 packages/flutter_test/lib/src/_goldens_io.dart create mode 100644 packages/flutter_test/lib/src/_goldens_web.dart diff --git a/packages/flutter_goldens/lib/flutter_goldens.dart b/packages/flutter_goldens/lib/flutter_goldens.dart index 87933e29579..0804836750d 100644 --- a/packages/flutter_goldens/lib/flutter_goldens.dart +++ b/packages/flutter_goldens/lib/flutter_goldens.dart @@ -153,7 +153,7 @@ class FlutterGoldensRepositoryFileComparator extends FlutterGoldenFileComparator throw TestFailure('Could not be compared against non-existent file: "$golden"'); } final List goldenBytes = await goldenFile.readAsBytes(); - final ComparisonResult result = GoldenFileComparator.compareLists(imageBytes, goldenBytes); + final ComparisonResult result = GoldenFileComparator.compareLists(imageBytes, goldenBytes); return result.passed; } diff --git a/packages/flutter_test/lib/flutter_test.dart b/packages/flutter_test/lib/flutter_test.dart index d2ade297275..5ecd00bdfa6 100644 --- a/packages/flutter_test/lib/flutter_test.dart +++ b/packages/flutter_test/lib/flutter_test.dart @@ -46,6 +46,8 @@ library flutter_test; export 'dart:async' show Future; +export 'src/_goldens_io.dart' + if (dart.library.html) 'src/_goldens_web.dart'; export 'src/accessibility.dart'; export 'src/all_elements.dart'; export 'src/binding.dart'; diff --git a/packages/flutter_test/lib/src/_binding_io.dart b/packages/flutter_test/lib/src/_binding_io.dart new file mode 100644 index 00000000000..7b6d0e6bd7e --- /dev/null +++ b/packages/flutter_test/lib/src/_binding_io.dart @@ -0,0 +1,524 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; + +import 'binding.dart'; + +/// Ensure the [WidgetsBinding] is initialized. +WidgetsBinding ensureInitialized([@visibleForTesting Map environment]) { + environment ??= Platform.environment; + if (WidgetsBinding.instance == null) { + if (environment.containsKey('FLUTTER_TEST') && environment['FLUTTER_TEST'] != 'false') { + AutomatedTestWidgetsFlutterBinding(); + } else { + LiveTestWidgetsFlutterBinding(); + } + } + assert(WidgetsBinding.instance is TestWidgetsFlutterBinding); + return WidgetsBinding.instance; +} + +/// Setup mocking of the global [HttpClient]. +void setupHttpOverrides() { + HttpOverrides.global = _MockHttpOverrides(); +} + +/// Setup mocking of platform assets if `UNIT_TEST_ASSETS` is defined. +void mockFlutterAssets() { + if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) { + return; + } + final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS']; + final String prefix = 'packages/${Platform.environment['APP_NAME']}/'; + + /// Navigation related actions (pop, push, replace) broadcasts these actions via + /// platform messages. + SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {}); + + ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData message) { + String key = utf8.decode(message.buffer.asUint8List()); + File asset = File(path.join(assetFolderPath, key)); + + if (!asset.existsSync()) { + // For tests in package, it will load assets with its own package prefix. + // In this case, we do a best-effort look up. + if (!key.startsWith(prefix)) { + return null; + } + + key = key.replaceFirst(prefix, ''); + asset = File(path.join(assetFolderPath, key)); + if (!asset.existsSync()) { + return null; + } + } + + final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync()); + return Future.value(encoded.buffer.asByteData()); + }); +} + +/// Provides a default [HttpClient] which always returns empty 400 responses. +/// +/// If another [HttpClient] is provided using [HttpOverrides.runZoned], that will +/// take precedence over this provider. +class _MockHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext _) { + return _MockHttpClient(); + } +} + +/// A mocked [HttpClient] which always returns a [_MockHttpRequest]. +class _MockHttpClient implements HttpClient { + @override + bool autoUncompress; + + @override + Duration connectionTimeout; + + @override + Duration idleTimeout; + + @override + int maxConnectionsPerHost; + + @override + String userAgent; + + @override + void addCredentials(Uri url, String realm, HttpClientCredentials credentials) { } + + @override + void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) { } + + @override + set authenticate(Future Function(Uri url, String scheme, String realm) f) { } + + @override + set authenticateProxy(Future Function(String host, int port, String scheme, String realm) f) { } + + @override + set badCertificateCallback(bool Function(X509Certificate cert, String host, int port) callback) { } + + @override + void close({ bool force = false }) { } + + @override + Future delete(String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future deleteUrl(Uri url) { + return Future.value(_MockHttpRequest()); + } + + @override + set findProxy(String Function(Uri url) f) { } + + @override + Future get(String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future getUrl(Uri url) { + return Future.value(_MockHttpRequest()); + } + + @override + Future head(String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future headUrl(Uri url) { + return Future.value(_MockHttpRequest()); + } + + @override + Future open(String method, String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future openUrl(String method, Uri url) { + return Future.value(_MockHttpRequest()); + } + + @override + Future patch(String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future patchUrl(Uri url) { + return Future.value(_MockHttpRequest()); + } + + @override + Future post(String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future postUrl(Uri url) { + return Future.value(_MockHttpRequest()); + } + + @override + Future put(String host, int port, String path) { + return Future.value(_MockHttpRequest()); + } + + @override + Future putUrl(Uri url) { + return Future.value(_MockHttpRequest()); + } +} + +/// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse]. +class _MockHttpRequest extends HttpClientRequest { + @override + Encoding encoding; + + @override + final HttpHeaders headers = _MockHttpHeaders(); + + @override + void add(List data) { } + + @override + void addError(Object error, [ StackTrace stackTrace ]) { } + + @override + Future addStream(Stream> stream) { + return Future.value(); + } + + @override + Future close() { + return Future.value(_MockHttpResponse()); + } + + @override + HttpConnectionInfo get connectionInfo => null; + + @override + List get cookies => null; + + @override + Future get done async => null; + + @override + Future flush() { + return Future.value(); + } + + @override + String get method => null; + + @override + Uri get uri => null; + + @override + void write(Object obj) { } + + @override + void writeAll(Iterable objects, [ String separator = '' ]) { } + + @override + void writeCharCode(int charCode) { } + + @override + void writeln([ Object obj = '' ]) { } +} + +/// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400. +// TODO(tvolkert): Change to `extends Stream` once +// https://dart-review.googlesource.com/c/sdk/+/104525 is rolled into the framework. +class _MockHttpResponse implements HttpClientResponse { + final Stream _delegate = Stream.fromIterable(const Iterable.empty()); + + @override + final HttpHeaders headers = _MockHttpHeaders(); + + @override + X509Certificate get certificate => null; + + @override + HttpConnectionInfo get connectionInfo => null; + + @override + int get contentLength => -1; + + @override + HttpClientResponseCompressionState get compressionState { + return HttpClientResponseCompressionState.decompressed; + } + + @override + List get cookies => null; + + @override + Future detachSocket() { + return Future.error(UnsupportedError('Mocked response')); + } + + @override + bool get isRedirect => false; + + @override + StreamSubscription listen(void Function(Uint8List event) onData, { Function onError, void Function() onDone, bool cancelOnError }) { + return const Stream.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); + } + + @override + bool get persistentConnection => null; + + @override + String get reasonPhrase => null; + + @override + Future redirect([ String method, Uri url, bool followLoops ]) { + return Future.error(UnsupportedError('Mocked response')); + } + + @override + List get redirects => []; + + @override + int get statusCode => 400; + + @override + Future any(bool Function(Uint8List element) test) { + return _delegate.any(test); + } + + @override + Stream asBroadcastStream({ + void Function(StreamSubscription subscription) onListen, + void Function(StreamSubscription subscription) onCancel, + }) { + return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel); + } + + @override + Stream asyncExpand(Stream Function(Uint8List event) convert) { + return _delegate.asyncExpand(convert); + } + + @override + Stream asyncMap(FutureOr Function(Uint8List event) convert) { + return _delegate.asyncMap(convert); + } + + @override + Stream cast() { + return _delegate.cast(); + } + + @override + Future contains(Object needle) { + return _delegate.contains(needle); + } + + @override + Stream distinct([bool Function(Uint8List previous, Uint8List next) equals]) { + return _delegate.distinct(equals); + } + + @override + Future drain([E futureValue]) { + return _delegate.drain(futureValue); + } + + @override + Future elementAt(int index) { + return _delegate.elementAt(index); + } + + @override + Future every(bool Function(Uint8List element) test) { + return _delegate.every(test); + } + + @override + Stream expand(Iterable Function(Uint8List element) convert) { + return _delegate.expand(convert); + } + + @override + Future get first => _delegate.first; + + @override + Future firstWhere( + bool Function(Uint8List element) test, { + List Function() orElse, + }) { + return _delegate.firstWhere(test, orElse: () { + return Uint8List.fromList(orElse()); + }); + } + + @override + Future fold(S initialValue, S Function(S previous, Uint8List element) combine) { + return _delegate.fold(initialValue, combine); + } + + @override + Future forEach(void Function(Uint8List element) action) { + return _delegate.forEach(action); + } + + @override + Stream handleError( + Function onError, { + bool Function(dynamic error) test, + }) { + return _delegate.handleError(onError, test: test); + } + + @override + bool get isBroadcast => _delegate.isBroadcast; + + @override + Future get isEmpty => _delegate.isEmpty; + + @override + Future join([String separator = '']) { + return _delegate.join(separator); + } + + @override + Future get last => _delegate.last; + + @override + Future lastWhere( + bool Function(Uint8List element) test, { + List Function() orElse, + }) { + return _delegate.lastWhere(test, orElse: () { + return Uint8List.fromList(orElse()); + }); + } + + @override + Future get length => _delegate.length; + + @override + Stream map(S Function(Uint8List event) convert) { + return _delegate.map(convert); + } + + @override + Future pipe(StreamConsumer> streamConsumer) { + return _delegate.cast>().pipe(streamConsumer); + } + + @override + Future reduce(List Function(Uint8List previous, Uint8List element) combine) { + return _delegate.reduce((Uint8List previous, Uint8List element) { + return Uint8List.fromList(combine(previous, element)); + }); + } + + @override + Future get single => _delegate.single; + + @override + Future singleWhere(bool Function(Uint8List element) test, {List Function() orElse}) { + return _delegate.singleWhere(test, orElse: () { + return Uint8List.fromList(orElse()); + }); + } + + @override + Stream skip(int count) { + return _delegate.skip(count); + } + + @override + Stream skipWhile(bool Function(Uint8List element) test) { + return _delegate.skipWhile(test); + } + + @override + Stream take(int count) { + return _delegate.take(count); + } + + @override + Stream takeWhile(bool Function(Uint8List element) test) { + return _delegate.takeWhile(test); + } + + @override + Stream timeout( + Duration timeLimit, { + void Function(EventSink sink) onTimeout, + }) { + return _delegate.timeout(timeLimit, onTimeout: onTimeout); + } + + @override + Future> toList() { + return _delegate.toList(); + } + + @override + Future> toSet() { + return _delegate.toSet(); + } + + @override + Stream transform(StreamTransformer, S> streamTransformer) { + return _delegate.cast>().transform(streamTransformer); + } + + @override + Stream where(bool Function(Uint8List event) test) { + return _delegate.where(test); + } +} + +/// A mocked [HttpHeaders] that ignores all writes. +class _MockHttpHeaders extends HttpHeaders { + @override + List operator [](String name) => []; + + @override + void add(String name, Object value) { } + + @override + void clear() { } + + @override + void forEach(void Function(String name, List values) f) { } + + @override + void noFolding(String name) { } + + @override + void remove(String name, Object value) { } + + @override + void removeAll(String name) { } + + @override + void set(String name, Object value) { } + + @override + String value(String name) => null; +} diff --git a/packages/flutter_test/lib/src/_binding_web.dart b/packages/flutter_test/lib/src/_binding_web.dart new file mode 100644 index 00000000000..45cfe1408ae --- /dev/null +++ b/packages/flutter_test/lib/src/_binding_web.dart @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + import 'package:flutter/widgets.dart'; + + import 'binding.dart'; + + /// Ensure the [WidgetsBinding] is initialized. +WidgetsBinding ensureInitialized([@visibleForTesting Map environment]) { + if (WidgetsBinding.instance == null) { + AutomatedTestWidgetsFlutterBinding(); + } + assert(WidgetsBinding.instance is TestWidgetsFlutterBinding); + return WidgetsBinding.instance; +} + + /// This method is a noop on the web. +void setupHttpOverrides() { } + + /// This method is a noop on the web. +void mockFlutterAssets() { } diff --git a/packages/flutter_test/lib/src/_goldens_io.dart b/packages/flutter_test/lib/src/_goldens_io.dart new file mode 100644 index 00000000000..ce1516c69dd --- /dev/null +++ b/packages/flutter_test/lib/src/_goldens_io.dart @@ -0,0 +1,208 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:math' as math; +import 'dart:typed_data'; + +import 'package:image/image.dart'; +import 'package:path/path.dart' as path; +import 'package:test_api/test_api.dart' as test_package show TestFailure; + +import 'goldens.dart'; + +/// The default [GoldenFileComparator] implementation for `flutter test`. +/// +/// The term __golden file__ refers to a master image that is considered the true +/// rendering of a given widget, state, application, or other visual +/// representation you have chosen to capture. This comparator loads golden +/// files from the local file system, treating the golden key as a relative +/// path from the test file's directory. +/// +/// This comparator performs a pixel-for-pixel comparison of the decoded PNGs, +/// returning true only if there's an exact match. In cases where the captured +/// test image does not match the golden file, this comparator will provide +/// output to illustrate the difference, described in further detail below. +/// +/// When using `flutter test --update-goldens`, [LocalFileComparator] +/// updates the golden files on disk to match the rendering. +/// +/// ## Local Output from Golden File Testing +/// +/// The [LocalFileComparator] will output test feedback when a golden file test +/// fails. This output takes the form of differential images contained within a +/// `failures` directory that will be generated in the same location specified +/// by the golden key. The differential images include the master and test +/// images that were compared, as well as an isolated diff of detected pixels, +/// and a masked diff that overlays these detected pixels over the master image. +/// +/// The following images are examples of a test failure output: +/// +/// | File Name | Image Output | +/// |----------------------------|---------------| +/// | testName_masterImage.png | ![A golden master image](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_masterImage.png) | +/// | testName_testImage.png | ![Test image](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_testImage.png) | +/// | testName_isolatedDiff.png | ![An isolated pixel difference.](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_isolatedDiff.png) | +/// | testName_maskedDiff.png | ![A masked pixel difference](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_maskedDiff.png) | +/// +/// See also: +/// +/// * [GoldenFileComparator], the abstract class that [LocalFileComparator] +/// implements. +/// * [matchesGoldenFile], the function from [flutter_test] that invokes the +/// comparator. +class LocalFileComparator extends GoldenFileComparator { + /// Creates a new [LocalFileComparator] for the specified [testFile]. + /// + /// Golden file keys will be interpreted as file paths relative to the + /// directory in which [testFile] resides. + /// + /// The [testFile] URL must represent a file. + LocalFileComparator(Uri testFile, {path.Style pathStyle}) + : basedir = _getBasedir(testFile, pathStyle), + _path = _getPath(pathStyle); + + static path.Context _getPath(path.Style style) { + return path.Context(style: style ?? path.Style.platform); + } + + static Uri _getBasedir(Uri testFile, path.Style pathStyle) { + final path.Context context = _getPath(pathStyle); + final String testFilePath = context.fromUri(testFile); + final String testDirectoryPath = context.dirname(testFilePath); + return context.toUri(testDirectoryPath + context.separator); + } + + /// The directory in which the test was loaded. + /// + /// Golden file keys will be interpreted as file paths relative to this + /// directory. + final Uri basedir; + + /// Path context exists as an instance variable rather than just using the + /// system path context in order to support testing, where we can spoof the + /// platform to test behaviors with arbitrary path styles. + final path.Context _path; + + @override + Future compare(Uint8List imageBytes, Uri golden) async { + final File goldenFile = _getGoldenFile(golden); + if (!goldenFile.existsSync()) { + throw test_package.TestFailure('Could not be compared against non-existent file: "$golden"'); + } + final List goldenBytes = await goldenFile.readAsBytes(); + final ComparisonResult result = GoldenFileComparator.compareLists(imageBytes, goldenBytes); + + if (!result.passed) { + String additionalFeedback = ''; + if (result.diffs != null) { + additionalFeedback = '\nFailure feedback can be found at ${path.join(basedir.path, 'failures')}'; + final Map diffs = result.diffs; + diffs.forEach((String name, Object untypedImage) { + final Image image = untypedImage; + final File output = _getFailureFile(name, golden); + output.parent.createSync(recursive: true); + output.writeAsBytesSync(encodePng(image)); + }); + } + throw test_package.TestFailure('Golden "$golden": ${result.error}$additionalFeedback'); + } + return result.passed; + } + + @override + Future update(Uri golden, Uint8List imageBytes) async { + final File goldenFile = _getGoldenFile(golden); + await goldenFile.parent.create(recursive: true); + await goldenFile.writeAsBytes(imageBytes, flush: true); + } + + File _getGoldenFile(Uri golden) { + return File(_path.join(_path.fromUri(basedir), _path.fromUri(golden.path))); + } + + File _getFailureFile(String failure, Uri golden) { + final String fileName = golden.pathSegments[0]; + final String testName = fileName.split(path.extension(fileName))[0] + + '_' + + failure + + '.png'; + return File(_path.join('failures', testName)); + } +} + +/// Returns a [ComparisonResult] to describe the pixel differential of the +/// [test] and [master] image bytes provided. +ComparisonResult compareLists(List test, List master) { + if (identical(test, master)) + return ComparisonResult(passed: true); + + if (test == null || master == null || test.isEmpty || master.isEmpty) { + return ComparisonResult( + passed: false, + error: 'Pixel test failed, null image provided.', + ); + } + + final Image testImage = decodePng(test); + final Image masterImage = decodePng(master); + + assert(testImage != null); + assert(masterImage != null); + + final int width = testImage.width; + final int height = testImage.height; + + if (width != masterImage.width || height != masterImage.height) { + return ComparisonResult( + passed: false, + error: 'Pixel test failed, image sizes do not match.\n' + 'Master Image: ${masterImage.width} X ${masterImage.height}\n' + 'Test Image: ${testImage.width} X ${testImage.height}', + ); + } + + int pixelDiffCount = 0; + final int totalPixels = width * height; + final Image invertedMaster = invert(Image.from(masterImage)); + final Image invertedTest = invert(Image.from(testImage)); + + final Map diffs = { + 'masterImage' : masterImage, + 'testImage' : testImage, + 'maskedDiff' : Image.from(testImage), + 'isolatedDiff' : Image(width, height), + }; + + for (int x = 0; x < width; x++) { + for (int y =0; y < height; y++) { + final int testPixel = testImage.getPixel(x, y); + final int masterPixel = masterImage.getPixel(x, y); + + final int diffPixel = (getRed(testPixel) - getRed(masterPixel)).abs() + + (getGreen(testPixel) - getGreen(masterPixel)).abs() + + (getBlue(testPixel) - getBlue(masterPixel)).abs() + + (getAlpha(testPixel) - getAlpha(masterPixel)).abs(); + + if (diffPixel != 0 ) { + final int invertedMasterPixel = invertedMaster.getPixel(x, y); + final int invertedTestPixel = invertedTest.getPixel(x, y); + final int maskPixel = math.max(invertedMasterPixel, invertedTestPixel); + diffs['maskedDiff'].setPixel(x, y, maskPixel); + diffs['isolatedDiff'].setPixel(x, y, maskPixel); + pixelDiffCount++; + } + } + } + + if (pixelDiffCount > 0) { + return ComparisonResult( + passed: false, + error: 'Pixel test failed, ${((pixelDiffCount/totalPixels) * 100).toStringAsFixed(2)}% diff detected.', + diffs: diffs, + ); + } + return ComparisonResult(passed: true); +} diff --git a/packages/flutter_test/lib/src/_goldens_web.dart b/packages/flutter_test/lib/src/_goldens_web.dart new file mode 100644 index 00000000000..ce86fe5ddfa --- /dev/null +++ b/packages/flutter_test/lib/src/_goldens_web.dart @@ -0,0 +1,28 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + import 'dart:typed_data'; + + import 'goldens.dart'; + + /// An unsupported [GoldenFileComparator] that exists for API compatibility. +class LocalFileComparator extends GoldenFileComparator { + @override + Future compare(Uint8List imageBytes, Uri golden) { + throw UnsupportedError('LocalFileComparator is not supported on the web.'); + } + + @override + Future update(Uri golden, Uint8List imageBytes) { + throw UnsupportedError('LocalFileComparator is not supported on the web.'); + } +} + + /// Returns whether [test] and [master] are pixel by pixel identical. +/// +/// This method is not supported on the web and throws an [UnsupportedError] +/// when called. +ComparisonResult compareLists(List test, List master) { + throw UnsupportedError('Golden testing is not supported on the web.'); +} diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 71b7650d53f..c3ac3909156 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -3,8 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; @@ -17,11 +15,12 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart' show TestWindow; import 'package:quiver/testing/async.dart'; import 'package:quiver/time.dart'; -import 'package:path/path.dart' as path; import 'package:test_api/test_api.dart' as test_package; import 'package:stack_trace/stack_trace.dart' as stack_trace; import 'package:vector_math/vector_math_64.dart'; +import '_binding_io.dart' + if (dart.library.html) '_binding_web.dart' as binding; import 'goldens.dart'; import 'platform.dart'; import 'stack_manipulation.dart'; @@ -247,30 +246,13 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// /// The parameter `environment` is exposed to test different environment /// variable values, and should not be used. - static WidgetsBinding ensureInitialized([@visibleForTesting Map environment]) { - if (!isBrowser) { - // Accessing Platform may throw from a browser, so we guard this. - environment ??= Platform.environment; - } - if (WidgetsBinding.instance == null) { - if (isBrowser) { - // Browser environments do not support the LiveTestWidgetsFlutterBinding. - AutomatedTestWidgetsFlutterBinding(); - } else if (environment.containsKey('FLUTTER_TEST') && environment['FLUTTER_TEST'] != 'false') { - AutomatedTestWidgetsFlutterBinding(); - } else { - LiveTestWidgetsFlutterBinding(); - } - } - assert(WidgetsBinding.instance is TestWidgetsFlutterBinding); - return WidgetsBinding.instance; - } + static WidgetsBinding ensureInitialized([@visibleForTesting Map environment]) => binding.ensureInitialized(environment); @override void initInstances() { super.initInstances(); timeDilation = 1.0; // just in case the developer has artificially changed it for development - HttpOverrides.global = _MockHttpOverrides(); + binding.setupHttpOverrides(); _testTextInput = TestTextInput(onCleared: _resetFocusedEditable)..register(); } @@ -834,7 +816,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { @override void initInstances() { super.initInstances(); - _mockFlutterAssets(); + binding.mockFlutterAssets(); } FakeAsync _currentFakeAsync; // set in runTest; cleared in postTest @@ -862,43 +844,6 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { @override int get microtaskCount => _currentFakeAsync.microtaskCount; - void _mockFlutterAssets() { - if (isBrowser) { - return; - } - if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) { - return; - } - final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS']; - final String prefix = 'packages/${Platform.environment['APP_NAME']}/'; - - /// Navigation related actions (pop, push, replace) broadcasts these actions via - /// platform messages. - SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {}); - - ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData message) { - String key = utf8.decode(message.buffer.asUint8List()); - File asset = File(path.join(assetFolderPath, key)); - - if (!asset.existsSync()) { - // For tests in package, it will load assets with its own package prefix. - // In this case, we do a best-effort look up. - if (!key.startsWith(prefix)) { - return null; - } - - key = key.replaceFirst(prefix, ''); - asset = File(path.join(assetFolderPath, key)); - if (!asset.existsSync()) { - return null; - } - } - - final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync()); - return Future.value(encoded.buffer.asByteData()); - }); - } - @override Future pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) { return TestAsyncUtils.guard(() { @@ -1700,458 +1645,3 @@ StackTrace _unmangle(StackTrace stack) { return stack.toTrace().vmTrace; return stack; } - -/// Provides a default [HttpClient] which always returns empty 400 responses. -/// -/// If another [HttpClient] is provided using [HttpOverrides.runZoned], that will -/// take precedence over this provider. -class _MockHttpOverrides extends HttpOverrides { - @override - HttpClient createHttpClient(SecurityContext _) { - return _MockHttpClient(); - } -} - -/// A mocked [HttpClient] which always returns a [_MockHttpRequest]. -class _MockHttpClient implements HttpClient { - @override - bool autoUncompress; - - @override - Duration connectionTimeout; - - @override - Duration idleTimeout; - - @override - int maxConnectionsPerHost; - - @override - String userAgent; - - @override - void addCredentials(Uri url, String realm, HttpClientCredentials credentials) { } - - @override - void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) { } - - @override - set authenticate(Future Function(Uri url, String scheme, String realm) f) { } - - @override - set authenticateProxy(Future Function(String host, int port, String scheme, String realm) f) { } - - @override - set badCertificateCallback(bool Function(X509Certificate cert, String host, int port) callback) { } - - @override - void close({ bool force = false }) { } - - @override - Future delete(String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future deleteUrl(Uri url) { - return Future.value(_MockHttpRequest()); - } - - @override - set findProxy(String Function(Uri url) f) { } - - @override - Future get(String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future getUrl(Uri url) { - return Future.value(_MockHttpRequest()); - } - - @override - Future head(String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future headUrl(Uri url) { - return Future.value(_MockHttpRequest()); - } - - @override - Future open(String method, String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future openUrl(String method, Uri url) { - return Future.value(_MockHttpRequest()); - } - - @override - Future patch(String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future patchUrl(Uri url) { - return Future.value(_MockHttpRequest()); - } - - @override - Future post(String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future postUrl(Uri url) { - return Future.value(_MockHttpRequest()); - } - - @override - Future put(String host, int port, String path) { - return Future.value(_MockHttpRequest()); - } - - @override - Future putUrl(Uri url) { - return Future.value(_MockHttpRequest()); - } -} - -/// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse]. -class _MockHttpRequest extends HttpClientRequest { - @override - Encoding encoding; - - @override - final HttpHeaders headers = _MockHttpHeaders(); - - @override - void add(List data) { } - - @override - void addError(Object error, [ StackTrace stackTrace ]) { } - - @override - Future addStream(Stream> stream) { - return Future.value(); - } - - @override - Future close() { - return Future.value(_MockHttpResponse()); - } - - @override - HttpConnectionInfo get connectionInfo => null; - - @override - List get cookies => null; - - @override - Future get done async => null; - - @override - Future flush() { - return Future.value(); - } - - @override - String get method => null; - - @override - Uri get uri => null; - - @override - void write(Object obj) { } - - @override - void writeAll(Iterable objects, [ String separator = '' ]) { } - - @override - void writeCharCode(int charCode) { } - - @override - void writeln([ Object obj = '' ]) { } -} - -/// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400. -// TODO(tvolkert): Change to `extends Stream` once -// https://dart-review.googlesource.com/c/sdk/+/104525 is rolled into the framework. -class _MockHttpResponse implements HttpClientResponse { - final Stream _delegate = Stream.fromIterable(const Iterable.empty()); - - @override - final HttpHeaders headers = _MockHttpHeaders(); - - @override - X509Certificate get certificate => null; - - @override - HttpConnectionInfo get connectionInfo => null; - - @override - int get contentLength => -1; - - @override - HttpClientResponseCompressionState get compressionState { - return HttpClientResponseCompressionState.decompressed; - } - - @override - List get cookies => null; - - @override - Future detachSocket() { - return Future.error(UnsupportedError('Mocked response')); - } - - @override - bool get isRedirect => false; - - @override - StreamSubscription listen(void Function(Uint8List event) onData, { Function onError, void Function() onDone, bool cancelOnError }) { - return const Stream.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - - @override - bool get persistentConnection => null; - - @override - String get reasonPhrase => null; - - @override - Future redirect([ String method, Uri url, bool followLoops ]) { - return Future.error(UnsupportedError('Mocked response')); - } - - @override - List get redirects => []; - - @override - int get statusCode => 400; - - @override - Future any(bool Function(Uint8List element) test) { - return _delegate.any(test); - } - - @override - Stream asBroadcastStream({ - void Function(StreamSubscription subscription) onListen, - void Function(StreamSubscription subscription) onCancel, - }) { - return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel); - } - - @override - Stream asyncExpand(Stream Function(Uint8List event) convert) { - return _delegate.asyncExpand(convert); - } - - @override - Stream asyncMap(FutureOr Function(Uint8List event) convert) { - return _delegate.asyncMap(convert); - } - - @override - Stream cast() { - return _delegate.cast(); - } - - @override - Future contains(Object needle) { - return _delegate.contains(needle); - } - - @override - Stream distinct([bool Function(Uint8List previous, Uint8List next) equals]) { - return _delegate.distinct(equals); - } - - @override - Future drain([E futureValue]) { - return _delegate.drain(futureValue); - } - - @override - Future elementAt(int index) { - return _delegate.elementAt(index); - } - - @override - Future every(bool Function(Uint8List element) test) { - return _delegate.every(test); - } - - @override - Stream expand(Iterable Function(Uint8List element) convert) { - return _delegate.expand(convert); - } - - @override - Future get first => _delegate.first; - - @override - Future firstWhere( - bool Function(Uint8List element) test, { - List Function() orElse, - }) { - return _delegate.firstWhere(test, orElse: () { - return Uint8List.fromList(orElse()); - }); - } - - @override - Future fold(S initialValue, S Function(S previous, Uint8List element) combine) { - return _delegate.fold(initialValue, combine); - } - - @override - Future forEach(void Function(Uint8List element) action) { - return _delegate.forEach(action); - } - - @override - Stream handleError( - Function onError, { - bool Function(dynamic error) test, - }) { - return _delegate.handleError(onError, test: test); - } - - @override - bool get isBroadcast => _delegate.isBroadcast; - - @override - Future get isEmpty => _delegate.isEmpty; - - @override - Future join([String separator = '']) { - return _delegate.join(separator); - } - - @override - Future get last => _delegate.last; - - @override - Future lastWhere( - bool Function(Uint8List element) test, { - List Function() orElse, - }) { - return _delegate.lastWhere(test, orElse: () { - return Uint8List.fromList(orElse()); - }); - } - - @override - Future get length => _delegate.length; - - @override - Stream map(S Function(Uint8List event) convert) { - return _delegate.map(convert); - } - - @override - Future pipe(StreamConsumer> streamConsumer) { - return _delegate.cast>().pipe(streamConsumer); - } - - @override - Future reduce(List Function(Uint8List previous, Uint8List element) combine) { - return _delegate.reduce((Uint8List previous, Uint8List element) { - return Uint8List.fromList(combine(previous, element)); - }); - } - - @override - Future get single => _delegate.single; - - @override - Future singleWhere(bool Function(Uint8List element) test, {List Function() orElse}) { - return _delegate.singleWhere(test, orElse: () { - return Uint8List.fromList(orElse()); - }); - } - - @override - Stream skip(int count) { - return _delegate.skip(count); - } - - @override - Stream skipWhile(bool Function(Uint8List element) test) { - return _delegate.skipWhile(test); - } - - @override - Stream take(int count) { - return _delegate.take(count); - } - - @override - Stream takeWhile(bool Function(Uint8List element) test) { - return _delegate.takeWhile(test); - } - - @override - Stream timeout( - Duration timeLimit, { - void Function(EventSink sink) onTimeout, - }) { - return _delegate.timeout(timeLimit, onTimeout: onTimeout); - } - - @override - Future> toList() { - return _delegate.toList(); - } - - @override - Future> toSet() { - return _delegate.toSet(); - } - - @override - Stream transform(StreamTransformer, S> streamTransformer) { - return _delegate.cast>().transform(streamTransformer); - } - - @override - Stream where(bool Function(Uint8List event) test) { - return _delegate.where(test); - } -} - -/// A mocked [HttpHeaders] that ignores all writes. -class _MockHttpHeaders extends HttpHeaders { - @override - List operator [](String name) => []; - - @override - void add(String name, Object value) { } - - @override - void clear() { } - - @override - void forEach(void Function(String name, List values) f) { } - - @override - void noFolding(String name) { } - - @override - void remove(String name, Object value) { } - - @override - void removeAll(String name) { } - - @override - void set(String name, Object value) { } - - @override - String value(String name) => null; -} diff --git a/packages/flutter_test/lib/src/goldens.dart b/packages/flutter_test/lib/src/goldens.dart index 39a666b2dee..6e3d3e163df 100644 --- a/packages/flutter_test/lib/src/goldens.dart +++ b/packages/flutter_test/lib/src/goldens.dart @@ -3,15 +3,12 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; -import 'dart:math' as math; import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/foundation.dart'; -import 'package:image/image.dart'; import 'package:path/path.dart' as path; -import 'package:test_api/test_api.dart' as test_package show TestFailure; +import '_goldens_io.dart' + if (dart.library.html) '_goldens_web.dart' as _goldens; /// Compares image pixels against a golden image file. /// @@ -100,76 +97,8 @@ abstract class GoldenFileComparator { /// Returns a [ComparisonResult] to describe the pixel differential of the /// [test] and [master] image bytes provided. - static ComparisonResult compareLists(List test, List master) { - if (identical(test, master)) - return ComparisonResult(passed: true); - - if (test == null || master == null || test.isEmpty || master.isEmpty) { - return ComparisonResult( - passed: false, - error: 'Pixel test failed, null image provided.', - ); - } - - final Image testImage = decodePng(test); - final Image masterImage = decodePng(master); - - assert(testImage != null); - assert(masterImage != null); - - final int width = testImage.width; - final int height = testImage.height; - - if (width != masterImage.width || height != masterImage.height) { - return ComparisonResult( - passed: false, - error: 'Pixel test failed, image sizes do not match.\n' - 'Master Image: ${masterImage.width} X ${masterImage.height}\n' - 'Test Image: ${testImage.width} X ${testImage.height}', - ); - } - - int pixelDiffCount = 0; - final int totalPixels = width * height; - final Image invertedMaster = invert(Image.from(masterImage)); - final Image invertedTest = invert(Image.from(testImage)); - - final Map diffs = { - 'masterImage' : masterImage, - 'testImage' : testImage, - 'maskedDiff' : Image.from(testImage), - 'isolatedDiff' : Image(width, height), - }; - - for (int x = 0; x < width; x++) { - for (int y =0; y < height; y++) { - final int testPixel = testImage.getPixel(x, y); - final int masterPixel = masterImage.getPixel(x, y); - - final int diffPixel = (getRed(testPixel) - getRed(masterPixel)).abs() - + (getGreen(testPixel) - getGreen(masterPixel)).abs() - + (getBlue(testPixel) - getBlue(masterPixel)).abs() - + (getAlpha(testPixel) - getAlpha(masterPixel)).abs(); - - if (diffPixel != 0 ) { - final int invertedMasterPixel = invertedMaster.getPixel(x, y); - final int invertedTestPixel = invertedTest.getPixel(x, y); - final int maskPixel = math.max(invertedMasterPixel, invertedTestPixel); - diffs['maskedDiff'].setPixel(x, y, maskPixel); - diffs['isolatedDiff'].setPixel(x, y, maskPixel); - pixelDiffCount++; - } - } - } - - if (pixelDiffCount > 0) { - return ComparisonResult( - passed: false, - error: 'Pixel test failed, ${((pixelDiffCount/totalPixels) * 100).toStringAsFixed(2)}% diff detected.', - diffs: diffs, - ); - } - return ComparisonResult(passed: true); + static ComparisonResult compareLists(List test, List master) { + return _goldens.compareLists(test, master); } } @@ -221,32 +150,6 @@ set goldenFileComparator(GoldenFileComparator value) { /// * [goldenFileComparator] bool autoUpdateGoldenFiles = false; -/// The result of a pixel comparison test. -/// -/// The [ComparisonResult] will always indicate if a test has [passed]. The -/// optional [error] and [diffs] parameters provide further information about -/// the result of a failing test. -class ComparisonResult { - /// Creates a new [ComparisonResult] for the current test. - ComparisonResult({ - @required this.passed, - this.error, - this.diffs, - }) : assert(passed != null); - - /// Indicates whether or not a pixel comparison test has failed. - /// - /// This value cannot be null. - final bool passed; - - /// Error message used to describe the cause of the pixel comparison failure. - final String error; - - /// Map containing differential images to illustrate found variants in pixel - /// values in the execution of the pixel test. - final Map diffs; -} - /// Placeholder comparator that is set as the value of [goldenFileComparator] /// when the initialization that happens in the test bootstrap either has not /// yet happened or has been bypassed. @@ -283,121 +186,29 @@ class TrivialComparator implements GoldenFileComparator { } } -/// The default [GoldenFileComparator] implementation for `flutter test`. +/// The result of a pixel comparison test. /// -/// The term __golden file__ refers to a master image that is considered the true -/// rendering of a given widget, state, application, or other visual -/// representation you have chosen to capture. This comparator loads golden -/// files from the local file system, treating the golden key as a relative -/// path from the test file's directory. -/// -/// This comparator performs a pixel-for-pixel comparison of the decoded PNGs, -/// returning true only if there's an exact match. In cases where the captured -/// test image does not match the golden file, this comparator will provide -/// output to illustrate the difference, described in further detail below. -/// -/// When using `flutter test --update-goldens`, [LocalFileComparator] -/// updates the golden files on disk to match the rendering. -/// -/// ## Local Output from Golden File Testing -/// -/// The [LocalFileComparator] will output test feedback when a golden file test -/// fails. This output takes the form of differential images contained within a -/// `failures` directory that will be generated in the same location specified -/// by the golden key. The differential images include the master and test -/// images that were compared, as well as an isolated diff of detected pixels, -/// and a masked diff that overlays these detected pixels over the master image. -/// -/// The following images are examples of a test failure output: -/// -/// | File Name | Image Output | -/// |----------------------------|---------------| -/// | testName_masterImage.png | ![A golden master image](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_masterImage.png) | -/// | testName_testImage.png | ![Test image](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_testImage.png) | -/// | testName_isolatedDiff.png | ![An isolated pixel difference.](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_isolatedDiff.png) | -/// | testName_maskedDiff.png | ![A masked pixel difference](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_maskedDiff.png) | -/// -/// See also: -/// -/// * [GoldenFileComparator], the abstract class that [LocalFileComparator] -/// implements. -/// * [matchesGoldenFile], the function from [flutter_test] that invokes the -/// comparator. -class LocalFileComparator extends GoldenFileComparator { - /// Creates a new [LocalFileComparator] for the specified [testFile]. +/// The [ComparisonResult] will always indicate if a test has [passed]. The +/// optional [error] and [diffs] parameters provide further information about +/// the result of a failing test. +class ComparisonResult { + /// Creates a new [ComparisonResult] for the current test. + ComparisonResult({ + @required this.passed, + this.error, + this.diffs, + }) : assert(passed != null); + + /// Indicates whether or not a pixel comparison test has failed. /// - /// Golden file keys will be interpreted as file paths relative to the - /// directory in which [testFile] resides. - /// - /// The [testFile] URL must represent a file. - LocalFileComparator(Uri testFile, {path.Style pathStyle}) - : basedir = _getBasedir(testFile, pathStyle), - _path = _getPath(pathStyle); + /// This value cannot be null. + final bool passed; - static path.Context _getPath(path.Style style) { - return path.Context(style: style ?? path.Style.platform); - } + /// Error message used to describe the cause of the pixel comparison failure. + final String error; - static Uri _getBasedir(Uri testFile, path.Style pathStyle) { - final path.Context context = _getPath(pathStyle); - final String testFilePath = context.fromUri(testFile); - final String testDirectoryPath = context.dirname(testFilePath); - return context.toUri(testDirectoryPath + context.separator); - } - - /// The directory in which the test was loaded. - /// - /// Golden file keys will be interpreted as file paths relative to this - /// directory. - final Uri basedir; - - /// Path context exists as an instance variable rather than just using the - /// system path context in order to support testing, where we can spoof the - /// platform to test behaviors with arbitrary path styles. - final path.Context _path; - - @override - Future compare(Uint8List imageBytes, Uri golden) async { - final File goldenFile = _getGoldenFile(golden); - if (!goldenFile.existsSync()) { - throw test_package.TestFailure('Could not be compared against non-existent file: "$golden"'); - } - final List goldenBytes = await goldenFile.readAsBytes(); - final ComparisonResult result = GoldenFileComparator.compareLists(imageBytes, goldenBytes); - - if (!result.passed) { - String additionalFeedback = ''; - if (result.diffs != null) { - additionalFeedback = '\nFailure feedback can be found at ${path.join(basedir.path, 'failures')}'; - final Map diffs = result.diffs; - diffs.forEach((String name, Image image) { - final File output = _getFailureFile(name, golden); - output.parent.createSync(recursive: true); - output.writeAsBytesSync(encodePng(image)); - }); - } - throw test_package.TestFailure('Golden "$golden": ${result.error}$additionalFeedback'); - } - return result.passed; - } - - @override - Future update(Uri golden, Uint8List imageBytes) async { - final File goldenFile = _getGoldenFile(golden); - await goldenFile.parent.create(recursive: true); - await goldenFile.writeAsBytes(imageBytes, flush: true); - } - - File _getGoldenFile(Uri golden) { - return File(_path.join(_path.fromUri(basedir), _path.fromUri(golden.path))); - } - - File _getFailureFile(String failure, Uri golden) { - final String fileName = golden.pathSegments[0]; - final String testName = fileName.split(path.extension(fileName))[0] - + '_' - + failure - + '.png'; - return File(_path.join('failures', testName)); - } + /// Map containing differential images to illustrate found variants in pixel + /// values in the execution of the pixel test. + // TODO(jonahwilliams): fix type signature when image is updated to support web. + final Map diffs; } diff --git a/packages/flutter_tools/lib/src/build_runner/build_script.dart b/packages/flutter_tools/lib/src/build_runner/build_script.dart index 433abd816b6..cd3cfad652f 100644 --- a/packages/flutter_tools/lib/src/build_runner/build_script.dart +++ b/packages/flutter_tools/lib/src/build_runner/build_script.dart @@ -41,6 +41,21 @@ const String jsSourceMapExtension = '.ddc.js.map'; const String kReleaseFlag = 'release'; const String kProfileFlag = 'profile'; +// A minimum set of libraries to skip checks for to keep the examples compiling +// until we make a decision on whether to support dart:io on the web. +// See https://github.com/dart-lang/sdk/issues/35969 +// See https://github.com/flutter/flutter/issues/39998 +const Set skipPlatformCheckPackages = { + 'flutter', + 'flutter_test', + 'flutter_driver', + 'flutter_goldens', + 'flutter_goldens_client', + 'flutter_gallery', + 'connectivity', + 'video_player', +}; + final DartPlatform flutterWebPlatform = DartPlatform.register('flutter_web', [ 'async', @@ -127,7 +142,7 @@ final List builders = [ sdkKernelPath: path.join('kernel', 'flutter_ddc_sdk.dill'), outputExtension: ddcKernelExtension, platform: flutterWebPlatform, - librariesPath: 'libraries.json', + librariesPath: path.absolute(path.join(builderOptions.config['flutterWebSdk'], 'libraries.json')), kernelTargetName: 'ddc', ), (BuilderOptions builderOptions) => DevCompilerBuilder( @@ -135,7 +150,7 @@ final List builders = [ platform: flutterWebPlatform, platformSdk: builderOptions.config['flutterWebSdk'], sdkKernelPath: path.url.join('kernel', 'flutter_ddc_sdk.dill'), - librariesPath: 'libraries.json', + librariesPath: path.absolute(path.join(builderOptions.config['flutterWebSdk'], 'libraries.json')), ), ], core.toAllPackages(), @@ -201,7 +216,8 @@ class FlutterWebTestEntrypointBuilder implements Builder { @override Future build(BuildStep buildStep) async { log.info('building for target ${buildStep.inputId.path}'); - await bootstrapDdc(buildStep, platform: flutterWebPlatform); + await bootstrapDdc(buildStep, platform: flutterWebPlatform, + skipPlatformCheckPackages: skipPlatformCheckPackages); } } @@ -229,7 +245,8 @@ class FlutterWebEntrypointBuilder implements Builder { if (release || profile) { await bootstrapDart2Js(buildStep, flutterWebSdk, profile); } else { - await bootstrapDdc(buildStep, platform: flutterWebPlatform); + await bootstrapDdc(buildStep, platform: flutterWebPlatform, + skipPlatformCheckPackages: skipPlatformCheckPackages); } } } diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart index 36c3611bbaa..f2db89c360d 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_fs.dart @@ -422,7 +422,11 @@ class BuildDaemonCreator { } break; default: - printTrace(serverLog.message); + if (serverLog.message.contains('Skipping compiling')) { + printError(serverLog.message); + } else { + printTrace(serverLog.message); + } } }, buildMode: daemon.BuildMode.Manual,