diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 3fee76600af93..c1503fb23d7e5 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -ae311ca4da5af9a8b30d4f361ad0473a9528b033 +feb94f6c9774e748628d22d4754fd3a34eccf6d9 diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version index c4e06f7b5a865..f4bec3699e39c 100644 --- a/bin/internal/fuchsia-linux.version +++ b/bin/internal/fuchsia-linux.version @@ -1 +1 @@ -1UtoyM5xYhavd9YdlWErYMoR0-f45dTUqn7hhx1Iao0C +kEtiuXXESsLg2BNT2-rMBtE7amfMpABtpvV9f2br-kAC diff --git a/bin/internal/fuchsia-mac.version b/bin/internal/fuchsia-mac.version index 6d3847f69d330..17ccf68ce9f5b 100644 --- a/bin/internal/fuchsia-mac.version +++ b/bin/internal/fuchsia-mac.version @@ -1 +1 @@ -akM_dAPd5mFOGO5IPovHhZYnbfJsPYkz31AE2wvj3PcC +ornVJ7V0rQ6OtxRu-IPB3yoRLQ6R_qfgKaBvmqUseVUC diff --git a/dev/automated_tests/flutter_test/filtering_tag_test.dart b/dev/automated_tests/flutter_test/filtering_tag_test.dart new file mode 100644 index 0000000000000..a550a304ea70e --- /dev/null +++ b/dev/automated_tests/flutter_test/filtering_tag_test.dart @@ -0,0 +1,14 @@ +// Copyright 2014 The Flutter 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:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + test('included', () { + expect(2 + 2, 4); + }, tags: ['include-tag']); + test('excluded', () { + throw 'this test should have been filtered out'; + }, tags: ['exclude-tag']); +} diff --git a/dev/automated_tests/pubspec.yaml b/dev/automated_tests/pubspec.yaml index 84350b7a2036a..caeeb5e7d9225 100644 --- a/dev/automated_tests/pubspec.yaml +++ b/dev/automated_tests/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,7 +60,7 @@ dependencies: typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,4 +70,4 @@ flutter: assets: - icon/ -# PUBSPEC CHECKSUM: d484 +# PUBSPEC CHECKSUM: ad87 diff --git a/dev/benchmarks/complex_layout/pubspec.yaml b/dev/benchmarks/complex_layout/pubspec.yaml index 06e5429fea259..e71f5aea05f89 100644 --- a/dev/benchmarks/complex_layout/pubspec.yaml +++ b/dev/benchmarks/complex_layout/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +67,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,7 +80,7 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -90,4 +90,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: b552 +# PUBSPEC CHECKSUM: e956 diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart index 1e45f9a1f0d49..2f0092eabac24 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart @@ -67,9 +67,9 @@ class BenchPictureRecording extends RawRecorder { } canvas.restore(); } - }); + }, reported: true); profile.record('estimatePaintBounds', () { recorder.endRecording(); - }); + }, reported: true); } } diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart index 7aa31881fc1b4..f0de05855578a 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart @@ -58,11 +58,6 @@ void _useCanvasText(bool useCanvasText) { ); } -typedef OnBenchmark = void Function(String name, num value); -void _onBenchmark(OnBenchmark listener) { - js_util.setProperty(html.window, '_flutter_internal_on_benchmark', listener); -} - /// Repeatedly lays out a paragraph using the DOM measurement approach. /// /// Creates a different paragraph each time in order to avoid hitting the cache. @@ -132,21 +127,21 @@ class BenchTextLayout extends RawRecorder { }) { profile.record('$keyPrefix.layout', () { paragraph.layout(ui.ParagraphConstraints(width: maxWidth)); - }); + }, reported: true); profile.record('$keyPrefix.getBoxesForRange', () { for (int start = 0; start < text.length; start += 3) { for (int end = start + 1; end < text.length; end *= 2) { paragraph.getBoxesForRange(start, end); } } - }); + }, reported: true); profile.record('$keyPrefix.getPositionForOffset', () { for (double dx = 0.0; dx < paragraph.width; dx += 10.0) { for (double dy = 0.0; dy < paragraph.height; dy += 10.0) { paragraph.getPositionForOffset(Offset(dx, dy)); } } - }); + }, reported: true); } } @@ -179,7 +174,7 @@ class BenchTextCachedLayout extends RawRecorder { final ui.Paragraph paragraph = builder.build(); profile.record('layout', () { paragraph.layout(const ui.ParagraphConstraints(width: double.infinity)); - }); + }, reported: true); _useCanvasText(null); } } @@ -242,7 +237,7 @@ class BenchBuildColorsGrid extends WidgetBuildRecorder { if (mode == _TestMode.useDomTextLayout) { _useCanvasText(false); } - _onBenchmark((String name, num value) { + registerEngineBenchmarkValueListener('text_layout', (num value) { _textLayoutMicros += value; }); } @@ -250,7 +245,7 @@ class BenchBuildColorsGrid extends WidgetBuildRecorder { @override Future tearDownAll() async { _useCanvasText(null); - _onBenchmark(null); + stopListeningToEngineBenchmarkValues('text_layout'); } @override @@ -268,6 +263,7 @@ class BenchBuildColorsGrid extends WidgetBuildRecorder { profile.addDataPoint( 'text_layout', Duration(microseconds: _textLayoutMicros.toInt()), + reported: true, ); } super.frameDidDraw(); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart index 26305a4304de8..4dc2d1b13ae64 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:html' as html; +import 'dart:js_util' as js_util; import 'dart:math' as math; import 'dart:ui'; @@ -27,6 +28,16 @@ const int _kMeasuredSampleCount = 100; /// The total number of samples collected by a benchmark. const int kTotalSampleCount = _kWarmUpSampleCount + _kMeasuredSampleCount; +/// A benchmark metric that includes frame-related computations prior to +/// submitting layer and picture operations to the underlying renderer, such as +/// HTML and CanvasKit. During this phase we compute transforms, clips, and +/// other information needed for rendering. +const String kProfilePrerollFrame = 'preroll_frame'; + +/// A benchmark metric that includes submitting layer and picture information +/// to the renderer. +const String kProfileApplyFrame = 'apply_frame'; + /// Measures the amount of time [action] takes. Duration timeAction(VoidCallback action) { final Stopwatch stopwatch = Stopwatch()..start(); @@ -221,9 +232,9 @@ abstract class SceneBuilderRecorder extends Recorder { final Scene scene = sceneBuilder.build(); profile.record('windowRenderDuration', () { window.render(scene); - }); - }); - }); + }, reported: false); + }, reported: false); + }, reported: true); endMeasureFrame(); if (profile.shouldContinue()) { @@ -331,7 +342,7 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder { @mustCallSuper void frameDidDraw() { endMeasureFrame(); - profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed); + profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed, reported: true); if (profile.shouldContinue()) { window.scheduleFrame(); @@ -353,12 +364,30 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder { final _RecordingWidgetsBinding binding = _RecordingWidgetsBinding.ensureInitialized(); final Widget widget = createWidget(); + + registerEngineBenchmarkValueListener(kProfilePrerollFrame, (num value) { + localProfile.addDataPoint( + kProfilePrerollFrame, + Duration(microseconds: value.toInt()), + reported: false, + ); + }); + registerEngineBenchmarkValueListener(kProfileApplyFrame, (num value) { + localProfile.addDataPoint( + kProfileApplyFrame, + Duration(microseconds: value.toInt()), + reported: false, + ); + }); + binding._beginRecording(this, widget); try { await _runCompleter.future; return localProfile; } finally { + stopListeningToEngineBenchmarkValues(kProfilePrerollFrame); + stopListeningToEngineBenchmarkValues(kProfileApplyFrame); _runCompleter = null; profile = null; } @@ -421,7 +450,7 @@ abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder { // Only record frames that show the widget. if (showWidget) { endMeasureFrame(); - profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed); + profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed, reported: true); } if (profile.shouldContinue()) { @@ -488,11 +517,21 @@ class _WidgetBuildRecorderHostState extends State<_WidgetBuildRecorderHost> { /// calculations will only apply to the latest [_kMeasuredSampleCount] data /// points. class Timeseries { - Timeseries(this.name); + Timeseries(this.name, this.isReported); /// The label of this timeseries used for debugging and result inspection. final String name; + /// Whether this timeseries is reported to the benchmark dashboard. + /// + /// If `true` a new benchmark card is created for the timeseries and is + /// visible on the dashboard. + /// + /// If `false` the data is stored but it does not show up on the dashboard. + /// Use unreported metrics for metrics that are useful for manual inspection + /// but that are too fine-grained to be useful for tracking on the dashboard. + final bool isReported; + /// List of all the values that have been recorded. /// /// This list has no limit. @@ -529,10 +568,16 @@ class Timeseries { // Final statistics. final double cleanAverage = _computeAverage(name, cleanValues); - final double outlierAverage = _computeAverage(name, outliers); final double standardDeviation = _computeStandardDeviationForPopulation(name, cleanValues); final double noise = cleanAverage > 0.0 ? standardDeviation / cleanAverage : 0.0; + // Compute outlier average. If there are no outliers the outlier average is + // the same as clean value average. In other words, in a perfect benchmark + // with no noise the difference between average and outlier average is zero, + // which the best possible outcome. Noise produces a positive difference + // between the two. + final double outlierAverage = outliers.isNotEmpty ? _computeAverage(name, outliers) : cleanAverage; + final List annotatedValues = [ for (final double warmUpValue in warmUpValues) AnnotatedSample( @@ -631,6 +676,15 @@ class TimeseriesStats { /// See [AnnotatedSample] for more details. final List samples; + /// Outlier average divided by clean average. + /// + /// This is a measure of performance consistency. The higher this number the + /// worse is jank when it happens. Smaller is better, with 1.0 being the + /// perfect score. If [average] is zero, this value defaults to 1.0. + double get outlierRatio => average > 0.0 + ? outlierAverage / average + : 1.0; // this can only happen in perfect benchmark that reports only zeros + @override String toString() { final StringBuffer buffer = StringBuffer(); @@ -640,6 +694,7 @@ class TimeseriesStats { '${samples.length} total)'); buffer.writeln(' | average: $average μs'); buffer.writeln(' | outlier average: $outlierAverage μs'); + buffer.writeln(' | outlier/clean ratio: ${outlierRatio}x'); buffer.writeln(' | noise: ${_ratioToPercent(noise)}'); return buffer.toString(); } @@ -684,14 +739,20 @@ class Profile { final Map extraData = {}; /// Invokes [callback] and records the duration of its execution under [key]. - Duration record(String key, VoidCallback callback) { + Duration record(String key, VoidCallback callback, { @required bool reported }) { final Duration duration = timeAction(callback); - addDataPoint(key, duration); + addDataPoint(key, duration, reported: reported); return duration; } - void addDataPoint(String key, Duration duration) { - scoreData.putIfAbsent(key, () => Timeseries(key)).add(duration.inMicroseconds.toDouble()); + /// Adds a timed sample to the timeseries corresponding to [key]. + /// + /// Set [reported] to `true` to report the timeseries to the dashboard UI. + /// + /// Set [reported] to `false` to store the data, but not show it on the + /// dashboard UI. + void addDataPoint(String key, Duration duration, { @required bool reported }) { + scoreData.putIfAbsent(key, () => Timeseries(key, reported)).add(duration.inMicroseconds.toDouble()); } /// Decides whether the data collected so far is sufficient to stop, or @@ -724,12 +785,20 @@ class Profile { }; for (final String key in scoreData.keys) { - scoreKeys.add('$key.average'); - scoreKeys.add('$key.outlierAverage'); final Timeseries timeseries = scoreData[key]; + + if (timeseries.isReported) { + scoreKeys.add('$key.average'); + // Report `outlierRatio` rather than `outlierAverage`, because + // the absolute value of outliers is less interesting than the + // ratio. + scoreKeys.add('$key.outlierRatio'); + } + final TimeseriesStats stats = timeseries.computeStats(); json['$key.average'] = stats.average; json['$key.outlierAverage'] = stats.outlierAverage; + json['$key.outlierRatio'] = stats.outlierRatio; json['$key.noise'] = stats.noise; } @@ -941,3 +1010,55 @@ void endMeasureFrame() { ); _currentFrameNumber += 1; } + +/// A function that receives a benchmark value from the framework. +typedef EngineBenchmarkValueListener = void Function(num value); + +// Maps from a value label name to a listener. +final Map _engineBenchmarkListeners = {}; + +/// Registers a [listener] for engine benchmark values labeled by [name]. +/// +/// If another listener is already registered, overrides it. +void registerEngineBenchmarkValueListener(String name, EngineBenchmarkValueListener listener) { + if (listener == null) { + throw ArgumentError( + 'Listener must not be null. To stop listening to engine benchmark values ' + 'under label "$name", call stopListeningToEngineBenchmarkValues(\'$name\').', + ); + } + + if (_engineBenchmarkListeners.containsKey(name)) { + throw StateError( + 'A listener for "$name" is already registered.\n' + 'Call `stopListeningToEngineBenchmarkValues` to unregister the previous ' + 'listener before registering a new one.' + ); + } + + if (_engineBenchmarkListeners.isEmpty) { + // The first listener is being registered. Register the global listener. + js_util.setProperty(html.window, '_flutter_internal_on_benchmark', _dispatchEngineBenchmarkValue); + } + + _engineBenchmarkListeners[name] = listener; +} + +/// Stops listening to engine benchmark values under labeled by [name]. +void stopListeningToEngineBenchmarkValues(String name) { + _engineBenchmarkListeners.remove(name); + if (_engineBenchmarkListeners.isEmpty) { + // The last listener unregistered. Remove the global listener. + js_util.setProperty(html.window, '_flutter_internal_on_benchmark', null); + } +} + +// Dispatches a benchmark value reported by the engine to the relevant listener. +// +// If there are no listeners registered for [name], ignores the value. +void _dispatchEngineBenchmarkValue(String name, double value) { + final EngineBenchmarkValueListener listener = _engineBenchmarkListeners[name]; + if (listener != null) { + listener(value); + } +} diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index f5502c7c5702c..858fedc08f622 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +67,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,7 +80,7 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -91,4 +91,4 @@ flutter: - packages/flutter_gallery_assets/food/cherry_pie.png - assets/999x1000.png -# PUBSPEC CHECKSUM: b552 +# PUBSPEC CHECKSUM: e956 diff --git a/dev/benchmarks/microbenchmarks/pubspec.yaml b/dev/benchmarks/microbenchmarks/pubspec.yaml index 1a44456cc7e45..6b55c51492526 100644 --- a/dev/benchmarks/microbenchmarks/pubspec.yaml +++ b/dev/benchmarks/microbenchmarks/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: coverage: 0.13.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - dart_style: 1.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + dart_style: 1.3.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,7 +46,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,7 +68,7 @@ dependencies: typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -76,4 +76,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 0c0c +# PUBSPEC CHECKSUM: 8210 diff --git a/dev/benchmarks/platform_views_layout/pubspec.yaml b/dev/benchmarks/platform_views_layout/pubspec.yaml index e0defa9deed3d..7d412d8c2cbe7 100644 --- a/dev/benchmarks/platform_views_layout/pubspec.yaml +++ b/dev/benchmarks/platform_views_layout/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +67,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,7 +80,7 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -90,4 +90,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: b552 +# PUBSPEC CHECKSUM: e956 diff --git a/dev/benchmarks/test_apps/stocks/pubspec.yaml b/dev/benchmarks/test_apps/stocks/pubspec.yaml index bb9b37d7eb9b4..b09b2cf2f84bd 100644 --- a/dev/benchmarks/test_apps/stocks/pubspec.yaml +++ b/dev/benchmarks/test_apps/stocks/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - dart_style: 1.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + dart_style: 1.3.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dependencies: meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -41,7 +41,7 @@ dependencies: term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: @@ -59,7 +59,7 @@ dev_dependencies: file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -86,4 +86,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: f3a2 +# PUBSPEC CHECKSUM: 96a7 diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index e987a6445b25a..4376bc7c6c7c5 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -605,6 +605,7 @@ Future verifyNoTrailingSpaces(String workingDirectory, { int minimumMatche .where((File file) => path.extension(file.path) != '.jpg') .where((File file) => path.extension(file.path) != '.ico') .where((File file) => path.extension(file.path) != '.jar') + .where((File file) => path.extension(file.path) != '.swp') .toList(); final List problems = []; for (final File file in files) { @@ -1299,5 +1300,6 @@ bool _isGeneratedPluginRegistrant(File file) { return !file.path.contains('.pub-cache') && (filename == 'GeneratedPluginRegistrant.java' || filename == 'GeneratedPluginRegistrant.h' || - filename == 'GeneratedPluginRegistrant.m'); + filename == 'GeneratedPluginRegistrant.m' || + filename == 'generated_plugin_registrant.dart'); } diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh index b6499b2577a34..b18032017ddec 100755 --- a/dev/bots/docs.sh +++ b/dev/bots/docs.sh @@ -111,7 +111,7 @@ if [[ -d "$FLUTTER_PUB_CACHE" ]]; then fi # Install and activate dartdoc. -"$PUB" global activate dartdoc 0.30.4 +"$PUB" global activate dartdoc 0.31.0 # This script generates a unified doc set, and creates # a custom index.html, placing everything into dev/docs/doc. diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml index 9837f2fea2de9..e97e158c71041 100644 --- a/dev/bots/pubspec.yaml +++ b/dev/bots/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -47,7 +47,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +67,7 @@ dependencies: typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xml: 3.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -77,4 +77,4 @@ dev_dependencies: mockito: 4.1.1 test_api: 0.2.15 -# PUBSPEC CHECKSUM: 4a6b +# PUBSPEC CHECKSUM: be6f diff --git a/dev/bots/run_fuchsia_tests.sh b/dev/bots/run_fuchsia_tests.sh index b2c94297a57d7..c706c50a16c0a 100755 --- a/dev/bots/run_fuchsia_tests.sh +++ b/dev/bots/run_fuchsia_tests.sh @@ -15,11 +15,17 @@ # # This script expects `pm`, `device-finder`, and `fuchsia_ctl` to all be in the # same directory as the script. +# +# This script also expects a private key available at: +# "/etc/botanist/keys/id_rsa_infra". set -Eex script_dir=$(dirname "$(readlink -f "$0")") +# Bot key to pave and ssh the device. +pkey="/etc/botanist/keys/id_rsa_infra" + # The nodes are named blah-blah--four-word-fuchsia-id device_name=${SWARMING_BOT_ID#*--} @@ -33,16 +39,22 @@ fi reboot() { # note: this will set an exit code of 255, which we can ignore. - $script_dir/fuchsia_ctl -d $device_name --dev-finder-path $script_dir/dev_finder ssh --identity-file $script_dir/.ssh/pkey -c "dm reboot-recovery" || true + echo "$(date) START:REBOOT ------------------------------------------" + $script_dir/fuchsia_ctl -d $device_name --dev-finder-path $script_dir/dev_finder ssh --identity-file $pkey -c "dm reboot-recovery" || true + echo "$(date) END:REBOOT --------------------------------------------" } trap reboot EXIT -$script_dir/fuchsia_ctl -d $device_name pave -i $1 -$script_dir/fuchsia_ctl push-packages -d $device_name --repoArchive generic-x64.tar.gz -p tiles -p tiles_ctl +echo "$(date) START:PAVING ------------------------------------------" +ssh-keygen -y -f $pkey > key.pub +$script_dir/fuchsia_ctl -d $device_name pave -i $1 --public-key "key.pub" +echo "$(date) END:PAVING --------------------------------------------" + + +$script_dir/fuchsia_ctl push-packages -d $device_name --identity-file $pkey --repoArchive generic-x64.tar.gz -p tiles -p tiles_ctl # set fuchsia ssh config -export FUCHSIA_SSH_PKEY=$script_dir/.ssh/pkey cat > $script_dir/fuchsia_ssh_config << EOF Host * CheckHostIP no @@ -53,7 +65,7 @@ Host * UserKnownHostsFile /dev/null User fuchsia IdentitiesOnly yes - IdentityFile $FUCHSIA_SSH_PKEY + IdentityFile $pkey ControlPersist yes ControlMaster auto ControlPath /tmp/fuchsia--%r@%h:%p @@ -66,13 +78,13 @@ EOF export FUCHSIA_SSH_CONFIG=$script_dir/fuchsia_ssh_config # Run the driver test +echo "$(date) START:DRIVER_TEST -------------------------------------" flutter_dir=$script_dir/flutter flutter_bin=$flutter_dir/bin/flutter # remove all out dated .packages references find $flutter_dir -name ".packages" | xargs rm - cd $flutter_dir/dev/benchmarks/test_apps/stocks/ - $flutter_bin pub get $flutter_bin drive -v -d $device_name --target=test_driver/stock_view.dart +echo "$(date) END:DRIVER_TEST ---------------------------------------" diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 4ab96eadafda3..a640536f15b74 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -9,6 +9,7 @@ import 'dart:math' as math; import 'package:googleapis/bigquery/v2.dart' as bq; import 'package:googleapis_auth/auth_io.dart' as auth; import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'browser.dart'; @@ -287,21 +288,27 @@ Future _runToolTests() async { /// we can build when there are spaces in the path name for the Flutter SDK and /// target app. Future _runBuildTests() async { - final Stream exampleDirectories = Directory(path.join(flutterRoot, 'examples')).list(); - await for (final FileSystemEntity fileEntity in exampleDirectories) { + final List exampleDirectories = Directory(path.join(flutterRoot, 'examples')).listSync() + ..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'))); + for (final FileSystemEntity fileEntity in exampleDirectories) { if (fileEntity is! Directory) { continue; } final String examplePath = fileEntity.path; + final bool hasNullSafety = File(path.join(examplePath, 'null_safety')).existsSync(); + final List additionalArgs = hasNullSafety + ? ['--enable-experiment', 'non-nullable'] + : []; if (Directory(path.join(examplePath, 'android')).existsSync()) { - await _flutterBuildAot(examplePath); - await _flutterBuildApk(examplePath); + await _flutterBuildApk(examplePath, release: false, additionalArgs: additionalArgs); + await _flutterBuildApk(examplePath, release: true, additionalArgs: additionalArgs); } else { - print('Example project ${path.basename(examplePath)} has no android directory, skipping aot and apk'); + print('Example project ${path.basename(examplePath)} has no android directory, skipping apk'); } if (Platform.isMacOS) { if (Directory(path.join(examplePath, 'ios')).existsSync()) { - await _flutterBuildIpa(examplePath); + await _flutterBuildIpa(examplePath, release: false, additionalArgs: additionalArgs); + await _flutterBuildIpa(examplePath, release: true, additionalArgs: additionalArgs); } else { print('Example project ${path.basename(examplePath)} has no ios directory, skipping ipa'); } @@ -323,23 +330,30 @@ Future _runBuildTests() async { } } -Future _flutterBuildAot(String relativePathToApplication) async { - print('${green}Testing AOT build$reset for $cyan$relativePathToApplication$reset...'); - await runCommand(flutter, - ['build', 'aot', '-v'], - workingDirectory: path.join(flutterRoot, relativePathToApplication), - ); -} - -Future _flutterBuildApk(String relativePathToApplication) async { +Future _flutterBuildApk(String relativePathToApplication, { + @required bool release, + List additionalArgs = const [], +}) async { print('${green}Testing APK --debug build$reset for $cyan$relativePathToApplication$reset...'); await runCommand(flutter, - ['build', 'apk', '--debug', '-v'], + [ + 'build', + 'apk', + ...additionalArgs, + if (release) + '--release' + else + '--debug', + '-v', + ], workingDirectory: path.join(flutterRoot, relativePathToApplication), ); } -Future _flutterBuildIpa(String relativePathToApplication) async { +Future _flutterBuildIpa(String relativePathToApplication, { + @required bool release, + List additionalArgs = const [], +}) async { assert(Platform.isMacOS); print('${green}Testing IPA build$reset for $cyan$relativePathToApplication$reset...'); // Install Cocoapods. We don't have these checked in for the examples, @@ -355,7 +369,17 @@ Future _flutterBuildIpa(String relativePathToApplication) async { ); } await runCommand(flutter, - ['build', 'ios', '--no-codesign', '--debug', '-v'], + [ + 'build', + 'ios', + ...additionalArgs, + '--no-codesign', + if (release) + '--release' + else + '--debug', + '-v', + ], workingDirectory: path.join(flutterRoot, relativePathToApplication), ); } @@ -590,6 +614,7 @@ Future _runWebIntegrationTests() async { await _runWebDebugTest('lib/stack_trace.dart'); await _runWebDebugTest('lib/web_directory_loading.dart'); await _runWebDebugTest('test/test.dart'); + await _runWebDebugTest('lib/null_safe_main.dart', enableNullSafety: true); await _runWebDebugTest('lib/web_define_loading.dart', additionalArguments: [ '--dart-define=test.valueA=Example', @@ -692,6 +717,7 @@ Future _runWebReleaseTest(String target, { /// /// Instead, we use `flutter run --debug` and sniff out the standard output. Future _runWebDebugTest(String target, { + bool enableNullSafety = false, List additionalArguments = const[], }) async { final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); @@ -702,6 +728,11 @@ Future _runWebDebugTest(String target, { [ 'run', '--debug', + if (enableNullSafety) + ...[ + '--enable-experiment', + 'non-nullable', + ], '-d', 'chrome', '--web-run-headless', diff --git a/dev/devicelab/bin/tasks/flutter_gallery_v2_chrome_run_test.dart b/dev/devicelab/bin/tasks/flutter_gallery_v2_chrome_run_test.dart new file mode 100644 index 0000000000000..ad38fd39d8788 --- /dev/null +++ b/dev/devicelab/bin/tasks/flutter_gallery_v2_chrome_run_test.dart @@ -0,0 +1,112 @@ +// Copyright 2014 The Flutter 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 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; + +Future main() async { + await task(const NewGalleryChromeRunTest().run); +} + +/// URI for the New Flutter Gallery repository. +const String galleryRepo = 'https://github.com/flutter/gallery.git'; + +/// After the gallery loads, a duration of [durationToWaitForError] +/// is waited, allowing any possible exceptions to be thrown. +const Duration durationToWaitForError = Duration(seconds: 5); + +/// Flutter prints this string when an app is successfully loaded. +/// Used to check when the app is successfully loaded. +const String successfullyLoadedString = 'To hot restart'; + +/// Flutter prints this string when an exception is caught. +/// Used to check if there are any exceptions. +const String exceptionString = 'EXCEPTION CAUGHT'; + +/// Checks that the New Flutter Gallery runs successfully on Chrome. +class NewGalleryChromeRunTest { + const NewGalleryChromeRunTest(); + + /// Runs the test. + Future run() async { + await gitClone(path: 'temp', repo: galleryRepo); + + final TaskResult result = await inDirectory('temp/gallery', () async { + await flutter('doctor'); + await flutter('packages', options: ['get']); + + await flutter('build', options: [ + 'web', + '-v', + '--release', + '--no-pub', + ], environment: { + 'FLUTTER_WEB': 'true', + }); + + final List options = ['-d', 'chrome', '--verbose', '--resident']; + final Process process = await startProcess( + 'flutter', + flutterCommandArgs('run', options), + environment: { + 'FLUTTER_WEB': 'true', + }, + ); + + final Completer stdoutDone = Completer(); + final Completer stderrDone = Completer(); + + bool success = true; + + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + if (line.contains(successfullyLoadedString)) { + // Successfully started. + Future.delayed( + durationToWaitForError, + () {process.stdin.write('q');} + ); + } + if (line.contains(exceptionString)) { + success = false; + } + print('stdout: $line'); + }, onDone: () { + stdoutDone.complete(); + }); + + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + print('stderr: $line'); + }, onDone: () { + stderrDone.complete(); + }); + + await Future.wait(>[ + stdoutDone.future, + stderrDone.future, + ]); + + await process.exitCode; + + if (success) { + return TaskResult.success({}); + } else { + return TaskResult.failure('An exception was thrown.'); + } + }); + + rmTree(Directory('temp')); + + return result; + } +} diff --git a/dev/devicelab/bin/tasks/flutter_gallery_v2_web_compile_test.dart b/dev/devicelab/bin/tasks/flutter_gallery_v2_web_compile_test.dart new file mode 100644 index 0000000000000..c3b2479882946 --- /dev/null +++ b/dev/devicelab/bin/tasks/flutter_gallery_v2_web_compile_test.dart @@ -0,0 +1,44 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; + +import 'package:flutter_devicelab/tasks/perf_tests.dart' show WebCompileTest; + +Future main() async { + await task(const NewGalleryWebCompileTest().run); +} + +/// Measures the time to compile the New Flutter Gallery to JavaScript +/// and the size of the compiled code. +class NewGalleryWebCompileTest { + const NewGalleryWebCompileTest(); + + String get metricKeyPrefix => 'new_gallery'; + + /// Runs the test. + Future run() async { + await gitClone(path: 'temp', repo: 'https://github.com/flutter/gallery.git'); + + final Map metrics = await inDirectory>( + 'temp/gallery', + () async { + await flutter('doctor'); + + return await WebCompileTest.runSingleBuildTest( + directory: 'temp/gallery', + metric: metricKeyPrefix, + measureBuildTime: true, + ); + }, + ); + + rmTree(Directory('temp')); + + return TaskResult.success(metrics, benchmarkScoreKeys: metrics.keys.toList()); + } +} diff --git a/dev/devicelab/bin/tasks/new_gallery__transition_perf.dart b/dev/devicelab/bin/tasks/new_gallery__transition_perf.dart new file mode 100644 index 0000000000000..01d064bd64144 --- /dev/null +++ b/dev/devicelab/bin/tasks/new_gallery__transition_perf.dart @@ -0,0 +1,25 @@ +// Copyright 2014 The Flutter 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 'package:flutter_devicelab/framework/utils.dart'; +import 'package:flutter_devicelab/tasks/new_gallery.dart'; +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:path/path.dart' as path; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + + final Directory galleryParentDir = Directory.systemTemp.createTempSync('new_gallery_test'); + final Directory galleryDir = Directory(path.join(galleryParentDir.path, 'gallery')); + + try { + await task(NewGalleryPerfTest(galleryDir).run); + } finally { + rmTree(galleryParentDir); + } +} diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index f6fce13d2a6a7..b331b7a1903ad 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -501,6 +501,22 @@ String jsonEncode(dynamic data) { return const JsonEncoder.withIndent(' ').convert(data) + '\n'; } +Future getNewGallery(String revision, Directory galleryDir) async { + section('Get New Flutter Gallery!'); + + if (exists(galleryDir)) { + galleryDir.deleteSync(recursive: true); + } + + await inDirectory(galleryDir.parent, () async { + await exec('git', ['clone', 'https://github.com/flutter/gallery.git']); + }); + + await inDirectory(galleryDir, () async { + await exec('git', ['checkout', revision]); + }); +} + void checkNotNull(Object o1, [Object o2 = 1, Object o3 = 1, @@ -676,3 +692,18 @@ void checkFileContains(List patterns, String filePath) { } } } + +/// Clones a git repository. +/// +/// Removes the directory [path], then clones the git repository +/// specified by [repo] to the directory [path]. +Future gitClone({String path, String repo}) async { + rmTree(Directory(path)); + + await Directory(path).create(recursive: true); + + return await inDirectory( + path, + () => exec('git', ['clone', repo]), + ); +} diff --git a/dev/devicelab/lib/tasks/new_gallery.dart b/dev/devicelab/lib/tasks/new_gallery.dart new file mode 100644 index 0000000000000..011fbb2d802d7 --- /dev/null +++ b/dev/devicelab/lib/tasks/new_gallery.dart @@ -0,0 +1,27 @@ +// Copyright 2014 The Flutter 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 'package:flutter_devicelab/tasks/perf_tests.dart'; + + +import '../framework/framework.dart'; +import '../framework/utils.dart'; + +class NewGalleryPerfTest extends PerfTest { + NewGalleryPerfTest(this.galleryDir) : super(galleryDir.path, 'test_driver/transitions_perf.dart', 'transitions'); + + @override + Future run() async { + // Manually roll the new gallery version for now. If the new gallery repo + // turns out to be updated frequently in the future, we can set up an auto + // roller to update this version. + await getNewGallery('59489e5571ddf554fc62ef52e9b16f1fed291026', galleryDir); + return await super.run(); + } + + final Directory galleryDir; +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index ac9c73f1c936d..7b266fe274cfe 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -305,22 +305,47 @@ class WebCompileTest { Future run() async { final Map metrics = {}; - await inDirectory('${flutterDirectory.path}/examples/hello_world', () async { - await flutter('packages', options: ['get']); - await evalFlutter('build', options: [ - 'web', - '-v', - '--release', - '--no-pub', - ], environment: { + + metrics.addAll(await runSingleBuildTest( + directory: '${flutterDirectory.path}/examples/hello_world', + metric: 'hello_world', + )); + + metrics.addAll(await runSingleBuildTest( + directory: '${flutterDirectory.path}/dev/integration_tests/flutter_gallery', + metric: 'flutter_gallery', + )); + + const String sampleAppName = 'sample_flutter_app'; + final Directory sampleDir = dir('${Directory.systemTemp.path}/$sampleAppName'); + + rmTree(sampleDir); + + await inDirectory(Directory.systemTemp, () async { + await flutter('create', options: ['--template=app', sampleAppName], environment: { 'FLUTTER_WEB': 'true', }); - final String output = '${flutterDirectory.path}/examples/hello_world/build/web/main.dart.js'; - await _measureSize('hello_world', output, metrics); - return null; }); - await inDirectory('${flutterDirectory.path}/dev/integration_tests/flutter_gallery', () async { + + metrics.addAll(await runSingleBuildTest( + directory: sampleDir.path, + metric: 'basic_material_app', + )); + + return TaskResult.success(metrics, benchmarkScoreKeys: metrics.keys.toList()); + } + + /// Run a single web compile test and return its metrics. + /// + /// Run a single web compile test for the app under [directory], and store + /// its metrics with prefix [metric]. + static Future> runSingleBuildTest({String directory, String metric, bool measureBuildTime = false}) { + return inDirectory>(directory, () async { + final Map metrics = {}; + await flutter('packages', options: ['get']); + final Stopwatch watch = measureBuildTime ? Stopwatch() : null; + watch?.start(); await evalFlutter('build', options: [ 'web', '-v', @@ -329,41 +354,30 @@ class WebCompileTest { ], environment: { 'FLUTTER_WEB': 'true', }); - final String output = '${flutterDirectory.path}/dev/integration_tests/flutter_gallery/build/web/main.dart.js'; - await _measureSize('flutter_gallery', output, metrics); - return null; - }); - const String sampleAppName = 'sample_flutter_app'; - final Directory sampleDir = dir('${Directory.systemTemp.path}/$sampleAppName'); + watch?.stop(); + final String outputFileName = path.join(directory, 'build/web/main.dart.js'); + metrics.addAll(await getSize(outputFileName, metric: metric)); - rmTree(sampleDir); + if (measureBuildTime) { + metrics['${metric}_dart2js_millis'] = watch.elapsedMilliseconds; + } - await inDirectory(Directory.systemTemp, () async { - await flutter('create', options: ['--template=app', sampleAppName], environment: { - 'FLUTTER_WEB': 'true', - }); - await inDirectory(sampleDir, () async { - await flutter('packages', options: ['get']); - await evalFlutter('build', options: [ - 'web', - '-v', - '--release', - '--no-pub', - ], environment: { - 'FLUTTER_WEB': 'true', - }); - await _measureSize('basic_material_app', path.join(sampleDir.path, 'build/web/main.dart.js'), metrics); - }); + return metrics; }); - return TaskResult.success(metrics, benchmarkScoreKeys: metrics.keys.toList()); } - static Future _measureSize(String metric, String output, Map metrics) async { - final ProcessResult result = await Process.run('du', ['-k', output]); - await Process.run('gzip',['-k', '9', output]); - final ProcessResult resultGzip = await Process.run('du', ['-k', output + '.gz']); - metrics['${metric}_dart2js_size'] = _parseDu(result.stdout as String); - metrics['${metric}_dart2js_size_gzip'] = _parseDu(resultGzip.stdout as String); + /// Obtains the size and gzipped size of a file given by [fileName]. + static Future> getSize(String fileName, {String metric}) async { + final Map sizeMetrics = {}; + + final ProcessResult result = await Process.run('du', ['-k', fileName]); + sizeMetrics['${metric}_dart2js_size'] = _parseDu(result.stdout as String); + + await Process.run('gzip',['-k', '9', fileName]); + final ProcessResult resultGzip = await Process.run('du', ['-k', fileName + '.gz']); + sizeMetrics['${metric}_dart2js_size_gzip'] = _parseDu(resultGzip.stdout as String); + + return sizeMetrics; } static int _parseDu(String source) { diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 51d8728718fca..dcbc2f3764dc5 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -749,6 +749,12 @@ tasks: stage: devicelab required_agent_capabilities: ["linux/android"] + new_gallery__transition_perf: + description: > + Measures the performance of screen transitions in the new Flutter Gallery on Android. + stage: devicelab + required_agent_capabilities: ["mac/android"] + fast_scroll_large_images__memory: description: > Measures memory usage for scrolling through a list of large images. @@ -811,3 +817,18 @@ tasks: # stage: devicelab_ios # required_agent_capabilities: ["mac/ios", "ios/gl-render-image"] # flaky: true + + flutter_gallery_v2_chrome_run_test: + description: > + Checks that the New Flutter Gallery runs successfully on Chrome. + stage: devicelab + required_agent_capabilities: ["linux/android"] + flaky: true + + flutter_gallery_v2_web_compile_test: + description: > + Measures the time to compile the New Flutter Gallery to JavaScript + and the size of the compiled code. + stage: devicelab + required_agent_capabilities: ["linux/android"] + flaky: true diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 55990f403b88c..89a166a17b3c1 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -59,7 +59,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,8 +72,8 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 78c3 +# PUBSPEC CHECKSUM: d0c7 diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml index 3571c7855e789..73df5d6b80d0f 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml +++ b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter # This plugin is using Android Embedding 1 - battery: 0.3.1+9 + battery: 0.3.1+10 # TODO(egarciad): Add a plugin that uses Android Embedding 2 # The following adds the Cupertino Icons font to your application. @@ -93,4 +93,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: 2f15 +# PUBSPEC CHECKSUM: ca3d diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml index 642c56cf3c408..d64ec75ad19b9 100644 --- a/dev/integration_tests/android_semantics_testing/pubspec.yaml +++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -38,7 +38,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -61,7 +61,7 @@ dependencies: vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,4 +70,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fc9a +# PUBSPEC CHECKSUM: 319e diff --git a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml index 54465afbae58b..3c51b596ffcca 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml +++ b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml @@ -64,7 +64,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,7 +72,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -93,7 +93,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -137,4 +137,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: 79f8 +# PUBSPEC CHECKSUM: adfc diff --git a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml index d488b875ab1ed..23e301c7e945d 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml +++ b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml @@ -64,7 +64,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,7 +72,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -93,7 +93,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -137,4 +137,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: 79f8 +# PUBSPEC CHECKSUM: adfc diff --git a/dev/integration_tests/android_views/pubspec.yaml b/dev/integration_tests/android_views/pubspec.yaml index cc28689f55c54..73b9c26e2bd09 100644 --- a/dev/integration_tests/android_views/pubspec.yaml +++ b/dev/integration_tests/android_views/pubspec.yaml @@ -7,7 +7,7 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - path_provider: 1.6.5 + path_provider: 1.6.7 collection: 1.14.12 assets_for_android_views: git: @@ -23,11 +23,11 @@ dependencies: crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_macos: 0.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_macos: 0.0.4+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_platform_interface: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" plugin_platform_interface: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,7 +68,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -81,11 +81,11 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: c56d +# PUBSPEC CHECKSUM: a9cf diff --git a/dev/integration_tests/channels/pubspec.yaml b/dev/integration_tests/channels/pubspec.yaml index a6cadad0e4814..3381e2a1d0cff 100644 --- a/dev/integration_tests/channels/pubspec.yaml +++ b/dev/integration_tests/channels/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -42,7 +42,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +65,7 @@ dependencies: vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fc9a +# PUBSPEC CHECKSUM: 319e diff --git a/dev/integration_tests/codegen/pubspec.yaml b/dev/integration_tests/codegen/pubspec.yaml index 6b120f5099b3d..16d0cec0066ae 100644 --- a/dev/integration_tests/codegen/pubspec.yaml +++ b/dev/integration_tests/codegen/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,7 +57,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,7 +70,7 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -81,4 +81,4 @@ builders: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fc9a +# PUBSPEC CHECKSUM: 319e diff --git a/dev/integration_tests/external_ui/pubspec.yaml b/dev/integration_tests/external_ui/pubspec.yaml index 99e6e44574ef8..1e1407be91b38 100644 --- a/dev/integration_tests/external_ui/pubspec.yaml +++ b/dev/integration_tests/external_ui/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -42,7 +42,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +65,7 @@ dependencies: vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fc9a +# PUBSPEC CHECKSUM: 319e diff --git a/dev/integration_tests/flavors/pubspec.yaml b/dev/integration_tests/flavors/pubspec.yaml index 5b4ae4bc35b1b..670d094507d9f 100644 --- a/dev/integration_tests/flavors/pubspec.yaml +++ b/dev/integration_tests/flavors/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -42,7 +42,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +65,7 @@ dependencies: vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fc9a +# PUBSPEC CHECKSUM: 319e diff --git a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml index 9911aade65964..fe2638f844937 100644 --- a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml +++ b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter_driver: sdk: flutter cupertino_icons: 0.1.3 - device_info: 0.4.2+1 + device_info: 0.4.2+2 archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -21,7 +21,7 @@ dependencies: crypto: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -58,7 +58,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,7 +71,7 @@ dev_dependencies: test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +80,4 @@ flutter: assets: - assets/ -# PUBSPEC CHECKSUM: ed69 +# PUBSPEC CHECKSUM: 8f6e diff --git a/dev/integration_tests/flutter_gallery/pubspec.yaml b/dev/integration_tests/flutter_gallery/pubspec.yaml index e1bcb5d1440d4..e30b5cde11a89 100644 --- a/dev/integration_tests/flutter_gallery/pubspec.yaml +++ b/dev/integration_tests/flutter_gallery/pubspec.yaml @@ -8,11 +8,11 @@ dependencies: flutter: sdk: flutter collection: 1.14.12 - device_info: 0.4.2+1 + device_info: 0.4.2+2 intl: 0.16.1 connectivity: 0.4.8+2 string_scanner: 1.0.5 - url_launcher: 5.4.2 + url_launcher: 5.4.5 cupertino_icons: 0.1.3 video_player: 0.10.6 scoped_model: 1.0.1 @@ -31,7 +31,7 @@ dependencies: source_span: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - url_launcher_macos: 0.0.1+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_macos: 0.0.1+5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_platform_interface: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_web: 0.1.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +67,7 @@ dev_dependencies: http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -75,7 +75,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -94,7 +94,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -271,4 +271,4 @@ flutter: - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf -# PUBSPEC CHECKSUM: 555c +# PUBSPEC CHECKSUM: f065 diff --git a/dev/integration_tests/image_loading/pubspec.yaml b/dev/integration_tests/image_loading/pubspec.yaml index fd3ec252ef054..e23dd15ca6181 100644 --- a/dev/integration_tests/image_loading/pubspec.yaml +++ b/dev/integration_tests/image_loading/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -54,7 +54,7 @@ dependencies: typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,4 +62,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 3512 +# PUBSPEC CHECKSUM: 0e15 diff --git a/dev/integration_tests/ios_platform_view_tests/pubspec.yaml b/dev/integration_tests/ios_platform_view_tests/pubspec.yaml index 11fe61dcbc2c9..c2af7a7074a10 100644 --- a/dev/integration_tests/ios_platform_view_tests/pubspec.yaml +++ b/dev/integration_tests/ios_platform_view_tests/pubspec.yaml @@ -43,7 +43,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -51,7 +51,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,7 +72,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -85,4 +85,4 @@ flutter: # the material Icons class. uses-material-design: true -# PUBSPEC CHECKSUM: 8853 +# PUBSPEC CHECKSUM: bc57 diff --git a/dev/integration_tests/non_nullable/.gitignore b/dev/integration_tests/non_nullable/.gitignore new file mode 100644 index 0000000000000..f3c205341e7db --- /dev/null +++ b/dev/integration_tests/non_nullable/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/dev/integration_tests/non_nullable/analysis_options.yaml b/dev/integration_tests/non_nullable/analysis_options.yaml new file mode 100644 index 0000000000000..2388a77af4127 --- /dev/null +++ b/dev/integration_tests/non_nullable/analysis_options.yaml @@ -0,0 +1,5 @@ +# Exclude lib/main.dart to avoid warnings about the non_nullable +# experiment before it is enabled. +analyzer: + exclude: + - lib/main.dart diff --git a/dev/integration_tests/non_nullable/android/.gitignore b/dev/integration_tests/non_nullable/android/.gitignore new file mode 100644 index 0000000000000..bc2100d8f75e6 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/dev/integration_tests/non_nullable/android/app/build.gradle b/dev/integration_tests/non_nullable/android/app/build.gradle new file mode 100644 index 0000000000000..51ab145d4cba9 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/build.gradle @@ -0,0 +1,67 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.non_nullable" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/dev/integration_tests/non_nullable/android/app/src/debug/AndroidManifest.xml b/dev/integration_tests/non_nullable/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000000..4ef3b6d9d2eb6 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000..f47cf1641f987 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/non_nullable/android/app/src/main/kotlin/com/example/non_nullable/MainActivity.kt b/dev/integration_tests/non_nullable/android/app/src/main/kotlin/com/example/non_nullable/MainActivity.kt new file mode 100644 index 0000000000000..60bdc72f0ad4d --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/src/main/kotlin/com/example/non_nullable/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.non_nullable + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/drawable/launch_background.xml b/dev/integration_tests/non_nullable/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000000000..32d7798786856 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000..db77bb4b7b090 Binary files /dev/null and b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000..17987b79bb8a3 Binary files /dev/null and b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000..09d4391482be6 Binary files /dev/null and b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000..d5f1c8d34e7a8 Binary files /dev/null and b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000..4d6372eebdb28 Binary files /dev/null and b/dev/integration_tests/non_nullable/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/dev/integration_tests/non_nullable/android/app/src/main/res/values/styles.xml b/dev/integration_tests/non_nullable/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000..aac0d5ba20bf7 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/dev/integration_tests/non_nullable/android/app/src/profile/AndroidManifest.xml b/dev/integration_tests/non_nullable/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000000..4ef3b6d9d2eb6 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/dev/integration_tests/non_nullable/android/build.gradle b/dev/integration_tests/non_nullable/android/build.gradle new file mode 100644 index 0000000000000..1c2d2db3e04d6 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/build.gradle @@ -0,0 +1,35 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/dev/integration_tests/non_nullable/android/gradle.properties b/dev/integration_tests/non_nullable/android/gradle.properties new file mode 100644 index 0000000000000..38c8d4544ff1c --- /dev/null +++ b/dev/integration_tests/non_nullable/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000..296b146b7318d --- /dev/null +++ b/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/dev/integration_tests/non_nullable/android/settings.gradle b/dev/integration_tests/non_nullable/android/settings.gradle new file mode 100644 index 0000000000000..d3b6a4013d714 --- /dev/null +++ b/dev/integration_tests/non_nullable/android/settings.gradle @@ -0,0 +1,15 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/dev/integration_tests/non_nullable/ios/.gitignore b/dev/integration_tests/non_nullable/ios/.gitignore new file mode 100644 index 0000000000000..e96ef602b8d17 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/dev/integration_tests/non_nullable/ios/Flutter/AppFrameworkInfo.plist b/dev/integration_tests/non_nullable/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000000..6b4c0f78a7850 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/dev/integration_tests/non_nullable/ios/Flutter/Debug.xcconfig b/dev/integration_tests/non_nullable/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000000..592ceee85b89b --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/dev/integration_tests/non_nullable/ios/Flutter/Release.xcconfig b/dev/integration_tests/non_nullable/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000000..592ceee85b89b --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..e053237ae954f --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,503 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nonNullable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nonNullable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nonNullable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..1d526a16ed0f1 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000..f9b0d7c5ea15f --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000..a28140cfdb3ff --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..1d526a16ed0f1 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000..f9b0d7c5ea15f --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift b/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000000000..d815fed684a8a --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// Copyright 2014 The Flutter 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 UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000..d36b1fab2d9de --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000..dc9ada4725e9b Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000..28c6bf03016f6 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000..2ccbfd967d969 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000..f091b6b0bca85 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000..4cde12118dda4 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000..d0ef06e7edb86 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000000..dcdc2306c2850 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000..2ccbfd967d969 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000..c8f9ed8f5cee1 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000..a6d6b8609df07 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000..a6d6b8609df07 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000..75b2d164a5a98 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000..c4df70d39da79 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000000..6a84f41e14e27 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000..d0e1f58536026 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000000000..0bedcf2fd4678 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000..9da19eacad3b0 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000..9da19eacad3b0 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000..9da19eacad3b0 Binary files /dev/null and b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000000..89c2725b70f18 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/dev/integration_tests/non_nullable/ios/Runner/Base.lproj/LaunchScreen.storyboard b/dev/integration_tests/non_nullable/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000000..f2e259c7c9390 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner/Base.lproj/Main.storyboard b/dev/integration_tests/non_nullable/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000000..f3c28516fb38e --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner/Info.plist b/dev/integration_tests/non_nullable/ios/Runner/Info.plist new file mode 100644 index 0000000000000..3d1faf81fde0b --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + non_nullable + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/dev/integration_tests/non_nullable/ios/Runner/Runner-Bridging-Header.h b/dev/integration_tests/non_nullable/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000000000..95b7baf386d06 --- /dev/null +++ b/dev/integration_tests/non_nullable/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter 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 "GeneratedPluginRegistrant.h" diff --git a/dev/integration_tests/non_nullable/lib/main.dart b/dev/integration_tests/non_nullable/lib/main.dart new file mode 100644 index 0000000000000..32ce3b72be857 --- /dev/null +++ b/dev/integration_tests/non_nullable/lib/main.dart @@ -0,0 +1,26 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart=2.9 +import 'package:flutter/material.dart'; + +String? unused; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: const Center(child: Text('hello, world')) + ); + } +} diff --git a/dev/integration_tests/non_nullable/null_safety b/dev/integration_tests/non_nullable/null_safety new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/dev/integration_tests/non_nullable/pubspec.yaml b/dev/integration_tests/non_nullable/pubspec.yaml new file mode 100644 index 0000000000000..d479b2d1d979a --- /dev/null +++ b/dev/integration_tests/non_nullable/pubspec.yaml @@ -0,0 +1,41 @@ +name: non_nullable +description: A new Flutter project. + +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: 0.1.3 + + collection: 1.14.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + flutter_test: + sdk: flutter + + async: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + charcode: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fake_async: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + +# PUBSPEC CHECKSUM: 7c67 diff --git a/dev/integration_tests/non_nullable/web/favicon.png b/dev/integration_tests/non_nullable/web/favicon.png new file mode 100644 index 0000000000000..8aaa46ac1ae21 Binary files /dev/null and b/dev/integration_tests/non_nullable/web/favicon.png differ diff --git a/dev/integration_tests/non_nullable/web/icons/Icon-192.png b/dev/integration_tests/non_nullable/web/icons/Icon-192.png new file mode 100644 index 0000000000000..b749bfef07473 Binary files /dev/null and b/dev/integration_tests/non_nullable/web/icons/Icon-192.png differ diff --git a/dev/integration_tests/non_nullable/web/icons/Icon-512.png b/dev/integration_tests/non_nullable/web/icons/Icon-512.png new file mode 100644 index 0000000000000..88cfd48dff116 Binary files /dev/null and b/dev/integration_tests/non_nullable/web/icons/Icon-512.png differ diff --git a/dev/integration_tests/non_nullable/web/index.html b/dev/integration_tests/non_nullable/web/index.html new file mode 100644 index 0000000000000..aae9e6efad60d --- /dev/null +++ b/dev/integration_tests/non_nullable/web/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + non_nullable + + + + + + + + diff --git a/dev/integration_tests/non_nullable/web/manifest.json b/dev/integration_tests/non_nullable/web/manifest.json new file mode 100644 index 0000000000000..0887f55ce1b15 --- /dev/null +++ b/dev/integration_tests/non_nullable/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "non_nullable", + "short_name": "non_nullable", + "start_url": ".", + "display": "minimal-ui", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/dev/integration_tests/platform_interaction/pubspec.yaml b/dev/integration_tests/platform_interaction/pubspec.yaml index c8e42543755a9..bd4e6ac94ed0b 100644 --- a/dev/integration_tests/platform_interaction/pubspec.yaml +++ b/dev/integration_tests/platform_interaction/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -42,7 +42,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +65,7 @@ dependencies: vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fc9a +# PUBSPEC CHECKSUM: 319e diff --git a/dev/integration_tests/release_smoke_test/pubspec.yaml b/dev/integration_tests/release_smoke_test/pubspec.yaml index ae21681a9e3b7..c25db9a4b8927 100644 --- a/dev/integration_tests/release_smoke_test/pubspec.yaml +++ b/dev/integration_tests/release_smoke_test/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: flutter_test: sdk: flutter - e2e: 0.4.0 + e2e: 0.4.2 archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -30,7 +30,7 @@ dev_dependencies: fake_async: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -45,4 +45,4 @@ dev_dependencies: web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 5152 +# PUBSPEC CHECKSUM: e155 diff --git a/dev/integration_tests/simple_codegen/pubspec.yaml b/dev/integration_tests/simple_codegen/pubspec.yaml index 6d0e13e009f57..33b3542383306 100644 --- a/dev/integration_tests/simple_codegen/pubspec.yaml +++ b/dev/integration_tests/simple_codegen/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -28,11 +28,11 @@ dependencies: string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" environment: # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. sdk: ">=2.0.0-dev.68.0 <3.0.0" -# PUBSPEC CHECKSUM: a0e7 +# PUBSPEC CHECKSUM: fbea diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml index 73836b90d9ecc..9658d2686fc00 100644 --- a/dev/integration_tests/ui/pubspec.yaml +++ b/dev/integration_tests/ui/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -43,7 +43,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -66,7 +66,7 @@ dependencies: vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -84,4 +84,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: db7d +# PUBSPEC CHECKSUM: cc81 diff --git a/dev/integration_tests/web/analysis_options.yaml b/dev/integration_tests/web/analysis_options.yaml new file mode 100644 index 0000000000000..8c534b5588a6a --- /dev/null +++ b/dev/integration_tests/web/analysis_options.yaml @@ -0,0 +1,5 @@ +# Exclude lib/main.dart to avoid warnings about the non_nullable +# experiment before it is enabled. +analyzer: + exclude: + - lib/null_safe_main.dart diff --git a/dev/integration_tests/web/lib/null_safe_main.dart b/dev/integration_tests/web/lib/null_safe_main.dart new file mode 100644 index 0000000000000..7d548d4f80a41 --- /dev/null +++ b/dev/integration_tests/web/lib/null_safe_main.dart @@ -0,0 +1,11 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart=2.9 + +String? x; + +void main() { + print('--- TEST SUCCEEDED ---'); +} diff --git a/dev/integration_tests/web/pubspec.yaml b/dev/integration_tests/web/pubspec.yaml index 22d1a5c44ca1e..eae11e08216f0 100644 --- a/dev/integration_tests/web/pubspec.yaml +++ b/dev/integration_tests/web/pubspec.yaml @@ -3,7 +3,7 @@ description: Integration test for web compilation. environment: # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: ">=2.6.0 <3.0.0" dependencies: flutter: diff --git a/dev/manual_tests/lib/actions.dart b/dev/manual_tests/lib/actions.dart index cf638db1c9e0a..847cf23e203ad 100644 --- a/dev/manual_tests/lib/actions.dart +++ b/dev/manual_tests/lib/actions.dart @@ -193,7 +193,7 @@ class UndoIntent extends Intent { class UndoAction extends Action { @override - bool get enabled { + bool isEnabled(UndoIntent intent) { final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext, nullOk: true) as UndoableActionDispatcher; return manager.canUndo; } @@ -211,7 +211,7 @@ class RedoIntent extends Intent { class RedoAction extends Action { @override - bool get enabled { + bool isEnabled(RedoIntent intent) { final UndoableActionDispatcher manager = Actions.of(primaryFocus.context, nullOk: true) as UndoableActionDispatcher; return manager.canRedo; } diff --git a/dev/snippets/pubspec.yaml b/dev/snippets/pubspec.yaml index 4df05e41e848e..588f6c9b2f270 100644 --- a/dev/snippets/pubspec.yaml +++ b/dev/snippets/pubspec.yaml @@ -14,7 +14,7 @@ dartdoc: dependencies: args: 1.6.0 - dart_style: 1.3.4 + dart_style: 1.3.5 meta: 1.1.8 platform: 2.2.1 @@ -31,7 +31,7 @@ dependencies: js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -39,7 +39,7 @@ dependencies: string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: @@ -100,4 +100,4 @@ executables: vm_service_client: 0.2.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 981d +# PUBSPEC CHECKSUM: 0821 diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart index de2f59e8ac98b..7c1099c429d5b 100644 --- a/dev/tools/dartdoc.dart +++ b/dev/tools/dartdoc.dart @@ -50,11 +50,7 @@ Future main(List arguments) async { final StringBuffer buf = StringBuffer(); buf.writeln('name: Flutter'); buf.writeln('homepage: https://flutter.dev'); - // TODO(dnfield): We should make DartDoc able to avoid emitting this. If we - // use the real value here, every file will get marked as new instead of only - // files that have otherwise changed. Instead, we replace it dynamically using - // JavaScript so that fewer files get marked as changed. - // https://github.com/dart-lang/dartdoc/issues/1982 + // TODO(dnfield): Re-factor for proper versioning, https://github.com/flutter/flutter/issues/55409 buf.writeln('version: 0.0.0'); buf.writeln('dependencies:'); for (final String package in findPackageNames()) { diff --git a/dev/tools/pubspec.yaml b/dev/tools/pubspec.yaml index 9191bf6f2d0a1..362c13f2c7df5 100644 --- a/dev/tools/pubspec.yaml +++ b/dev/tools/pubspec.yaml @@ -50,7 +50,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -63,9 +63,9 @@ dev_dependencies: stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 59dd +# PUBSPEC CHECKSUM: 62e0 diff --git a/dev/tracing_tests/pubspec.yaml b/dev/tracing_tests/pubspec.yaml index ac905b3e721ae..c2791298f91aa 100644 --- a/dev/tracing_tests/pubspec.yaml +++ b/dev/tracing_tests/pubspec.yaml @@ -9,6 +9,8 @@ dependencies: flutter: sdk: flutter + vm_service: 4.0.0 + collection: 1.14.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,4 +34,4 @@ dev_dependencies: term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_api: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 09c1 +# PUBSPEC CHECKSUM: a444 diff --git a/dev/tracing_tests/test/image_cache_tracing_test.dart b/dev/tracing_tests/test/image_cache_tracing_test.dart index 6eac37689e593..dde8c8373ddab 100644 --- a/dev/tracing_tests/test/image_cache_tracing_test.dart +++ b/dev/tracing_tests/test/image_cache_tracing_test.dart @@ -2,37 +2,33 @@ // 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:developer' as developer; -import 'dart:io'; import 'dart:isolate' as isolate; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service/vm_service_io.dart'; void main() { + VmService vmService; String isolateId; - final TimelineObtainer timelineObtainer = TimelineObtainer(); - setUpAll(() async { - isolateId = developer.Service.getIsolateID(isolate.Isolate.current); final developer.ServiceProtocolInfo info = await developer.Service.getInfo(); if (info.serverUri == null) { fail('This test _must_ be run with --enable-vmservice.'); } - await timelineObtainer.connect(info.serverUri); - await timelineObtainer.setDartFlags(); + + vmService = await vmServiceConnectUri('ws://localhost:${info.serverUri.port}${info.serverUri.path}ws'); + await vmService.setVMTimelineFlags(['Dart']); + isolateId = developer.Service.getIsolateID(isolate.Isolate.current); // Initialize the image cache. TestWidgetsFlutterBinding.ensureInitialized(); }); - tearDownAll(() async { - await timelineObtainer?.close(); - }); - test('Image cache tracing', () async { final TestImageStreamCompleter completer1 = TestImageStreamCompleter(); PaintingBinding.instance.imageCache.putIfAbsent( @@ -41,10 +37,9 @@ void main() { ); PaintingBinding.instance.imageCache.clear(); - final List> timelineEvents = await timelineObtainer.getTimelineData(); - + final Timeline timeline = await vmService.getVMTimeline(); _expectTimelineEvents( - timelineEvents, + timeline.traceEvents, >[ { 'name': 'ImageCache.putIfAbsent', @@ -69,15 +64,12 @@ void main() { }, skip: isBrowser); // uses dart:isolate and io } -void _expectTimelineEvents( - List> events, - List> expected, -) { - for (final Map event in events) { +void _expectTimelineEvents(List events, List> expected) { + for (final TimelineEvent event in events) { for (int index = 0; index < expected.length; index += 1) { - if (expected[index]['name'] == event['name']) { + if (expected[index]['name'] == event.json['name']) { final Map expectedArgs = expected[index]['args'] as Map; - final Map args = event['args'] as Map; + final Map args = event.json['args'] as Map; if (_mapsEqual(expectedArgs, args)) { expected.removeAt(index); } @@ -99,59 +91,4 @@ bool _mapsEqual(Map expectedArgs, Map args) { return true; } -// TODO(dnfield): we can drop this in favor of vm_service when https://github.com/dart-lang/webdev/issues/899 is resolved. -class TimelineObtainer { - WebSocket _observatorySocket; - int _lastCallId = 0; - - final Map> _completers = >{}; - - - Future connect(Uri uri) async { - _observatorySocket = await WebSocket.connect('ws://localhost:${uri.port}${uri.path}ws'); - _observatorySocket.listen((dynamic data) => _processResponse(data as String)); - } - - void _processResponse(String data) { - final Map json = jsonDecode(data) as Map; - final int id = json['id'] as int; - _completers.remove(id).complete(json['result']); - } - - Future setDartFlags() async { - _lastCallId += 1; - final Completer> completer = Completer>(); - _completers[_lastCallId] = completer; - _observatorySocket.add(jsonEncode({ - 'id': _lastCallId, - 'method': 'setVMTimelineFlags', - 'params': { - 'recordedStreams': ['Dart'], - }, - })); - - final Map result = await completer.future; - return result['type'] == 'Success'; - } - - Future>> getTimelineData() async { - _lastCallId += 1; - final Completer> completer = Completer>(); - _completers[_lastCallId] = completer; - _observatorySocket.add(jsonEncode({ - 'id': _lastCallId, - 'method': 'getVMTimeline', - })); - - final Map result = await completer.future; - final List list = result['traceEvents'] as List; - return list.cast>(); - } - - Future close() async { - expect(_completers, isEmpty); - await _observatorySocket?.close(); - } -} - class TestImageStreamCompleter extends ImageStreamCompleter {} diff --git a/examples/catalog/pubspec.yaml b/examples/catalog/pubspec.yaml index c543b977f24c9..6c5f8bfb231f2 100644 --- a/examples/catalog/pubspec.yaml +++ b/examples/catalog/pubspec.yaml @@ -44,7 +44,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -52,7 +52,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,7 +72,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -81,4 +81,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 8853 +# PUBSPEC CHECKSUM: bc57 diff --git a/examples/catalog/test/custom_semantics_test.dart b/examples/catalog/test/custom_semantics_test.dart index 329117b860950..dd8217679d6ed 100644 --- a/examples/catalog/test/custom_semantics_test.dart +++ b/examples/catalog/test/custom_semantics_test.dart @@ -18,10 +18,7 @@ void main() { await tester.pump(); // Verify it correctly exposes its semantics. - // TODO(goderbauer): Use `SemanticsTester` after https://github.com/flutter/flutter/issues/12286. - final SemanticsNode semantics = tester - .renderObject(find.byType(AdjustableDropdownListTile)) - .debugSemantics; + final SemanticsNode semantics = tester.getSemantics(find.byType(AdjustableDropdownListTile)); expectAdjustable(semantics, hasIncreaseAction: true, diff --git a/examples/hello_world/pubspec.yaml b/examples/hello_world/pubspec.yaml index b6dba53549374..a539cd03378a7 100644 --- a/examples/hello_world/pubspec.yaml +++ b/examples/hello_world/pubspec.yaml @@ -42,7 +42,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,7 +50,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,10 +71,10 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 8853 +# PUBSPEC CHECKSUM: bc57 diff --git a/examples/platform_channel/pubspec.yaml b/examples/platform_channel/pubspec.yaml index 8f08aeefe2d19..e44e9c2d9a305 100644 --- a/examples/platform_channel/pubspec.yaml +++ b/examples/platform_channel/pubspec.yaml @@ -42,7 +42,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,7 +50,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,7 +71,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +80,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 8853 +# PUBSPEC CHECKSUM: bc57 diff --git a/examples/platform_channel_swift/pubspec.yaml b/examples/platform_channel_swift/pubspec.yaml index 355a3dd7fe047..b9c7a0f1c47e7 100644 --- a/examples/platform_channel_swift/pubspec.yaml +++ b/examples/platform_channel_swift/pubspec.yaml @@ -42,7 +42,7 @@ dev_dependencies: intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,7 +50,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,7 +71,7 @@ dev_dependencies: test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +80,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 8853 +# PUBSPEC CHECKSUM: bc57 diff --git a/packages/_flutter_web_build_script/pubspec.yaml b/packages/_flutter_web_build_script/pubspec.yaml index 91fdb6fc44a73..66f457457581b 100644 --- a/packages/_flutter_web_build_script/pubspec.yaml +++ b/packages/_flutter_web_build_script/pubspec.yaml @@ -14,10 +14,10 @@ dependencies: test_api: 0.2.15 test_core: 0.3.3 - build_runner: 1.8.1 + build_runner: 1.9.0 build_test: 1.0.0 - build_runner_core: 5.0.0 - dart_style: 1.3.4 + build_runner_core: 5.1.0 + dart_style: 1.3.5 code_builder: 3.2.1 build: 1.2.2 build_modules: 2.9.0 @@ -32,7 +32,7 @@ dependencies: bazel_worker: 0.1.23+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" build_config: 0.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - build_resolvers: 1.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + build_resolvers: 1.3.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_collection: 4.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_value: 7.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,7 +60,7 @@ dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -85,7 +85,7 @@ dependencies: timing: 0.1.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -94,4 +94,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 1f44 +# PUBSPEC CHECKSUM: 744c diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index d52c58c18624e..8199457f4f375 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -11,6 +11,7 @@ library services; export 'src/services/asset_bundle.dart'; +export 'src/services/autofill.dart'; export 'src/services/binary_messenger.dart'; export 'src/services/binding.dart'; export 'src/services/clipboard.dart'; diff --git a/packages/flutter/lib/src/cupertino/action_sheet.dart b/packages/flutter/lib/src/cupertino/action_sheet.dart index bb57b25ed8741..c0258122363e9 100644 --- a/packages/flutter/lib/src/cupertino/action_sheet.dart +++ b/packages/flutter/lib/src/cupertino/action_sheet.dart @@ -937,13 +937,15 @@ class _PressableActionButtonState extends State<_PressableActionButton> { Widget build(BuildContext context) { return _ActionButtonParentDataWidget( isPressed: _isPressed, - // TODO(mattcarroll): Button press dynamics need overhaul for iOS: https://github.com/flutter/flutter/issues/19786 + // TODO(mattcarroll): Button press dynamics need overhaul for iOS: + // https://github.com/flutter/flutter/issues/19786 child: GestureDetector( excludeFromSemantics: true, behavior: HitTestBehavior.opaque, onTapDown: (TapDownDetails details) => setState(() => _isPressed = true), onTapUp: (TapUpDetails details) => setState(() => _isPressed = false), - // TODO(mattcarroll): Cancel is currently triggered when user moves past slop instead of off button: https://github.com/flutter/flutter/issues/19783 + // TODO(mattcarroll): Cancel is currently triggered when user moves past + // slop instead of off button: https://github.com/flutter/flutter/issues/19783 onTapCancel: () => setState(() => _isPressed = false), child: widget.child, ), diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart index 68d462e3d70a2..68f3089d1ebe4 100644 --- a/packages/flutter/lib/src/cupertino/context_menu.dart +++ b/packages/flutter/lib/src/cupertino/context_menu.dart @@ -378,8 +378,8 @@ class _CupertinoContextMenuState extends State with Ticker // decoy will pop on top of the AppBar if the child is partially behind it, // such as a top item in a partially scrolled view. However, if we don't use // an overlay, then the decoy will appear behind its neighboring widget when - // it expands. This may be solveable by adding a widget to Scaffold that's - // undernearth the AppBar. + // it expands. This may be solvable by adding a widget to Scaffold that's + // underneath the AppBar. _lastOverlayEntry = OverlayEntry( opaque: false, builder: (BuildContext context) { diff --git a/packages/flutter/lib/src/cupertino/dialog.dart b/packages/flutter/lib/src/cupertino/dialog.dart index 3623fc0cab13a..15854603ff988 100644 --- a/packages/flutter/lib/src/cupertino/dialog.dart +++ b/packages/flutter/lib/src/cupertino/dialog.dart @@ -995,7 +995,8 @@ class _PressableActionButtonState extends State<_PressableActionButton> { return _ActionButtonParentDataWidget( isPressed: _isPressed, child: MergeSemantics( - // TODO(mattcarroll): Button press dynamics need overhaul for iOS: https://github.com/flutter/flutter/issues/19786 + // TODO(mattcarroll): Button press dynamics need overhaul for iOS: + // https://github.com/flutter/flutter/issues/19786 child: GestureDetector( excludeFromSemantics: true, behavior: HitTestBehavior.opaque, @@ -1005,7 +1006,8 @@ class _PressableActionButtonState extends State<_PressableActionButton> { onTapUp: (TapUpDetails details) => setState(() { _isPressed = false; }), - // TODO(mattcarroll): Cancel is currently triggered when user moves past slop instead of off button: https://github.com/flutter/flutter/issues/19783 + // TODO(mattcarroll): Cancel is currently triggered when user moves + // past slop instead of off button: https://github.com/flutter/flutter/issues/19783 onTapCancel: () => setState(() => _isPressed = false), child: widget.child, ), diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index c021b72e2b887..a00410fd8fa27 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -268,6 +268,7 @@ class CupertinoTextField extends StatefulWidget { this.onTap, this.scrollController, this.scrollPhysics, + this.autofillHints, }) : assert(textAlign != null), assert(readOnly != null), assert(autofocus != null), @@ -579,6 +580,9 @@ class CupertinoTextField extends StatefulWidget { /// {@macro flutter.material.textfield.onTap} final GestureTapCallback onTap; + /// {@macro flutter.widgets.editableText.autofillHints} + final Iterable autofillHints; + @override _CupertinoTextFieldState createState() => _CupertinoTextFieldState(); @@ -950,6 +954,7 @@ class _CupertinoTextFieldState extends State with AutomaticK scrollController: widget.scrollController, scrollPhysics: widget.scrollPhysics, enableInteractiveSelection: widget.enableInteractiveSelection, + autofillHints: widget.autofillHints, ), ), ); diff --git a/packages/flutter/lib/src/foundation/assertions.dart b/packages/flutter/lib/src/foundation/assertions.dart index cf7b070723437..ab2d13e4d72fc 100644 --- a/packages/flutter/lib/src/foundation/assertions.dart +++ b/packages/flutter/lib/src/foundation/assertions.dart @@ -715,7 +715,7 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti /// Called whenever the Flutter framework catches an error. /// - /// The default behavior is to call [dumpErrorToConsole]. + /// The default behavior is to call [presentError]. /// /// You can set this to your own function to override this default behavior. /// For example, you could report all errors to your server. @@ -725,7 +725,18 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti /// /// Set this to null to silently catch and ignore errors. This is not /// recommended. - static FlutterExceptionHandler onError = dumpErrorToConsole; + static FlutterExceptionHandler onError = (FlutterErrorDetails details) => presentError(details); + + /// Called whenever the Flutter framework wants to present an error to the + /// users. + /// + /// The default behavior is to call [dumpErrorToConsole]. + /// + /// Plugins can override how an error is to be presented to the user. For + /// example, the structured errors service extension sets its own method when + /// the extension is enabled. If you want to change how Flutter responds to an + /// error, use [onError] instead. + static FlutterExceptionHandler presentError = dumpErrorToConsole; static int _errorCount = 0; diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index c4e74faf8ad9f..2ca700a80e1aa 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -799,6 +799,8 @@ class DropdownButton extends StatefulWidget { /// defaults, so do not need to be specified). The boolean [isDense] and /// [isExpanded] arguments must not be null. /// + /// The [autofocus] argument must not be null. + /// /// The [dropdownColor] argument specifies the background color of the /// dropdown when it is open. If it is null, the current theme's /// [ThemeData.canvasColor] will be used instead. @@ -825,6 +827,8 @@ class DropdownButton extends StatefulWidget { this.focusNode, this.autofocus = false, this.dropdownColor, + // When adding new arguments, consider adding similar arguments to + // DropdownButtonFormField. }) : assert(items == null || items.isEmpty || value == null || items.where((DropdownMenuItem item) { return item.value == value; @@ -1426,25 +1430,26 @@ class _DropdownButtonState extends State> with WidgetsBindi } } -/// A convenience widget that wraps a [DropdownButton] in a [FormField]. +/// A convenience widget that makes a [DropdownButton] into a [FormField]. class DropdownButtonFormField extends FormField { - /// Creates a [DropdownButton] widget wrapped in an [InputDecorator] and - /// [FormField]. + /// Creates a [DropdownButton] widget that is a [FormField], wrapped in an + /// [InputDecorator]. + /// + /// For a description of the `onSaved`, `validator`, or `autovalidate` + /// parameters, see [FormField]. For the rest (other than [decoration]), see + /// [DropdownButton]. /// - /// The [DropdownButton] [items] parameters must not be null. + /// The `items`, `elevation`, `iconSize`, `isDense`, `isExpanded`, + /// `autofocus`, and `decoration` parameters must not be null. DropdownButtonFormField({ Key key, - T value, @required List> items, DropdownButtonBuilder selectedItemBuilder, + T value, Widget hint, + Widget disabledHint, @required this.onChanged, VoidCallback onTap, - this.decoration = const InputDecoration(), - FormFieldSetter onSaved, - FormFieldValidator validator, - bool autovalidate = false, - Widget disabledHint, int elevation = 8, TextStyle style, Widget icon, @@ -1454,6 +1459,14 @@ class DropdownButtonFormField extends FormField { bool isDense = true, bool isExpanded = false, double itemHeight, + Color focusColor, + FocusNode focusNode, + bool autofocus = false, + Color dropdownColor, + InputDecoration decoration, + FormFieldSetter onSaved, + FormFieldValidator validator, + bool autovalidate = false, }) : assert(items == null || items.isEmpty || value == null || items.where((DropdownMenuItem item) { return item.value == value; @@ -1463,12 +1476,13 @@ class DropdownButtonFormField extends FormField { 'Either zero or 2 or more [DropdownMenuItem]s were detected ' 'with the same value', ), - assert(decoration != null), assert(elevation != null), assert(iconSize != null), assert(isDense != null), assert(isExpanded != null), - assert(itemHeight == null || itemHeight > 0), + assert(itemHeight == null || itemHeight >= kMinInteractiveDimension), + assert(autofocus != null), + decoration = decoration ?? InputDecoration(focusColor: focusColor), super( key: key, onSaved: onSaved, @@ -1477,32 +1491,46 @@ class DropdownButtonFormField extends FormField { autovalidate: autovalidate, builder: (FormFieldState field) { final _DropdownButtonFormFieldState state = field as _DropdownButtonFormFieldState; - final InputDecoration effectiveDecoration = decoration.applyDefaults( + final InputDecoration decorationArg = decoration ?? InputDecoration(focusColor: focusColor); + final InputDecoration effectiveDecoration = decorationArg.applyDefaults( Theme.of(field.context).inputDecorationTheme, ); - return InputDecorator( - decoration: effectiveDecoration.copyWith(errorText: field.errorText), - isEmpty: state.value == null, - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: state.value, - items: items, - selectedItemBuilder: selectedItemBuilder, - hint: hint, - onChanged: onChanged == null ? null : state.didChange, - onTap: onTap, - disabledHint: disabledHint, - elevation: elevation, - style: style, - icon: icon, - iconDisabledColor: iconDisabledColor, - iconEnabledColor: iconEnabledColor, - iconSize: iconSize, - isDense: isDense, - isExpanded: isExpanded, - itemHeight: itemHeight, - ), - ), + // An unfocusable Focus widget so that this widget can detect if its + // descendants have focus or not. + return Focus( + canRequestFocus: false, + skipTraversal: true, + child: Builder(builder: (BuildContext context) { + return InputDecorator( + decoration: effectiveDecoration.copyWith(errorText: field.errorText), + isEmpty: state.value == null, + isFocused: Focus.of(context).hasFocus, + child: DropdownButtonHideUnderline( + child: DropdownButton( + items: items, + selectedItemBuilder: selectedItemBuilder, + value: state.value, + hint: hint, + disabledHint: disabledHint, + onChanged: onChanged == null ? null : state.didChange, + onTap: onTap, + elevation: elevation, + style: style, + icon: icon, + iconDisabledColor: iconDisabledColor, + iconEnabledColor: iconEnabledColor, + iconSize: iconSize, + isDense: isDense, + isExpanded: isExpanded, + itemHeight: itemHeight, + focusColor: focusColor, + focusNode: focusNode, + autofocus: autofocus, + dropdownColor: dropdownColor, + ), + ), + ); + }), ); }, ); @@ -1512,11 +1540,11 @@ class DropdownButtonFormField extends FormField { /// The decoration to show around the dropdown button form field. /// - /// By default, draws a horizontal line under the dropdown button field but can be - /// configured to show an icon, label, hint text, and error text. + /// By default, draws a horizontal line under the dropdown button field but + /// can be configured to show an icon, label, hint text, and error text. /// - /// Specify null to remove the decoration entirely (including the - /// extra padding introduced by the decoration to save space for the labels). + /// If not specified, an [InputDecorator] with the `focusColor` set to the + /// supplied `focusColor` (if any) will be used. final InputDecoration decoration; @override diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 5e3c97861672b..ffed8ff9359fb 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -41,6 +41,7 @@ class ExpansionTile extends StatefulWidget { this.children = const [], this.trailing, this.initiallyExpanded = false, + this.tilePadding, }) : assert(initiallyExpanded != null), super(key: key); @@ -80,6 +81,15 @@ class ExpansionTile extends StatefulWidget { /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). final bool initiallyExpanded; + /// Specifies padding for the [ListTile]. + /// + /// Analogous to [ListTile.contentPadding], this property defines the insets for + /// the [leading], [title], [subtitle] and [trailing] widgets. It does not inset + /// the expanded [children] widgets. + /// + /// When the value is null, the tile's padding is `EdgeInsets.symmetric(horizontal: 16.0)`. + final EdgeInsetsGeometry tilePadding; + @override _ExpansionTileState createState() => _ExpansionTileState(); } @@ -165,6 +175,7 @@ class _ExpansionTileState extends State with SingleTickerProvider textColor: _headerColor.value, child: ListTile( onTap: _handleTap, + contentPadding: widget.tilePadding, leading: widget.leading, title: widget.title, subtitle: widget.subtitle, diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 40f6ad6268e65..26cf16ff7926a 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -514,10 +514,12 @@ class _Decoration { this.helperError, this.counter, this.container, + this.fixTextFieldOutlineLabel = false, }) : assert(contentPadding != null), assert(isCollapsed != null), assert(floatingLabelHeight != null), - assert(floatingLabelProgress != null); + assert(floatingLabelProgress != null), + assert(fixTextFieldOutlineLabel != null); final EdgeInsetsGeometry contentPadding; final bool isCollapsed; @@ -539,6 +541,7 @@ class _Decoration { final Widget helperError; final Widget counter; final Widget container; + final bool fixTextFieldOutlineLabel; @override bool operator ==(Object other) { @@ -566,7 +569,8 @@ class _Decoration { && other.suffixIcon == suffixIcon && other.helperError == helperError && other.counter == counter - && other.container == container; + && other.container == container + && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel; } @override @@ -591,6 +595,7 @@ class _Decoration { helperError, counter, container, + fixTextFieldOutlineLabel, ); } } @@ -1238,11 +1243,16 @@ class _RenderDecoration extends RenderBox { double subtextHeight = _lineHeight(width, [helperError, counter]); if (subtextHeight > 0.0) subtextHeight += subtextGap; - return contentPadding.top + final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment; + final double containerHeight = contentPadding.top + (label == null ? 0.0 : decoration.floatingLabelHeight) + _lineHeight(width, [prefix, input, suffix]) + subtextHeight + contentPadding.bottom; + final double minContainerHeight = decoration.isDense || expands + ? 0.0 + : kMinInteractiveDimension + densityOffset.dy; + return math.max(containerHeight, minContainerHeight); } @override @@ -1433,12 +1443,18 @@ class _RenderDecoration extends RenderBox { if (label != null) { final Offset labelOffset = _boxParentData(label).offset; final double labelHeight = label.size.height; + final double borderWeight = decoration.border.borderSide.width; final double t = decoration.floatingLabelProgress; // The center of the outline border label ends up a little below the // center of the top border line. final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline; - final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top; - final double scale = lerpDouble(1.0, 0.75, t); + // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028 + // Center the scaled label relative to the border. + const double finalLabelScale = 0.75; + final double floatingY = decoration.fixTextFieldOutlineLabel + ? isOutlineBorder ? (-labelHeight * finalLabelScale) / 2.0 + borderWeight / 2.0 : contentPadding.top + : isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top; + final double scale = lerpDouble(1.0, finalLabelScale, t); double dx; switch (textDirection) { case TextDirection.rtl: @@ -2073,9 +2089,17 @@ class _InputDecoratorState extends State with TickerProviderStat ? decoration.errorStyle?.color ?? themeData.errorColor : _getActiveColor(themeData); final TextStyle style = themeData.textTheme.subtitle1.merge(widget.baseStyle); - return style - .copyWith(color: decoration.enabled ? color : themeData.disabledColor) - .merge(decoration.labelStyle); + // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028 + // Setting TextStyle.height to 1 ensures that the label's height will equal + // its font size. + return themeData.fixTextFieldOutlineLabel + ? style + .copyWith(height: 1, color: decoration.enabled ? color : themeData.disabledColor) + .merge(decoration.labelStyle) + : style + .copyWith(color: decoration.enabled ? color : themeData.disabledColor) + .merge(decoration.labelStyle); + } TextStyle _getHelperStyle(ThemeData themeData) { @@ -2154,7 +2178,12 @@ class _InputDecoratorState extends State with TickerProviderStat isHovering: isHovering, ); - final TextStyle inlineLabelStyle = inlineStyle.merge(decoration.labelStyle); + // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028 + // Setting TextStyle.height to 1 ensures that the label's height will equal + // its font size. + final TextStyle inlineLabelStyle = themeData.fixTextFieldOutlineLabel + ? inlineStyle.merge(decoration.labelStyle).copyWith(height: 1) + : inlineStyle.merge(decoration.labelStyle); final Widget label = decoration.labelText == null ? null : _Shaker( animation: _shakingLabelController.view, child: AnimatedOpacity( @@ -2331,6 +2360,7 @@ class _InputDecoratorState extends State with TickerProviderStat helperError: helperError, counter: counter, container: container, + fixTextFieldOutlineLabel: themeData.fixTextFieldOutlineLabel, ), textDirection: textDirection, textBaseline: textBaseline, diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index cb72f58373362..62a6fe3d0c933 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -346,6 +346,7 @@ class TextField extends StatefulWidget { this.buildCounter, this.scrollController, this.scrollPhysics, + this.autofillHints, }) : assert(textAlign != null), assert(readOnly != null), assert(autofocus != null), @@ -710,6 +711,9 @@ class TextField extends StatefulWidget { /// {@macro flutter.widgets.editableText.scrollController} final ScrollController scrollController; + /// {@macro flutter.widgets.editableText.autofillHints} + final Iterable autofillHints; + @override _TextFieldState createState() => _TextFieldState(); @@ -1049,6 +1053,7 @@ class _TextFieldState extends State implements TextSelectionGestureDe dragStartBehavior: widget.dragStartBehavior, scrollController: widget.scrollController, scrollPhysics: widget.scrollPhysics, + autofillHints: widget.autofillHints, autocorrectionTextRectColor: autocorrectionTextRectColor, ), ); diff --git a/packages/flutter/lib/src/material/text_theme.dart b/packages/flutter/lib/src/material/text_theme.dart index cd5a07b52c521..98588c72d984f 100644 --- a/packages/flutter/lib/src/material/text_theme.dart +++ b/packages/flutter/lib/src/material/text_theme.dart @@ -32,7 +32,7 @@ import 'typography.dart'; /// /// The Material Design typography scheme was significantly changed in the /// current (2018) version of the specification -/// (https://material.io/design/typography). +/// ([https://material.io/design/typography](https://material.io/design/typography)). /// /// The 2018 spec has thirteen text styles: /// ``` diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index a92ccef81ffd3..2f8675733ab4b 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -268,6 +268,7 @@ class ThemeData with Diagnosticable { DividerThemeData dividerTheme, ButtonBarThemeData buttonBarTheme, BottomNavigationBarThemeData bottomNavigationBarTheme, + bool fixTextFieldOutlineLabel, }) { brightness ??= Brightness.light; final bool isDark = brightness == Brightness.dark; @@ -378,6 +379,8 @@ class ThemeData with Diagnosticable { buttonBarTheme ??= const ButtonBarThemeData(); bottomNavigationBarTheme ??= const BottomNavigationBarThemeData(); + fixTextFieldOutlineLabel ??= false; + return ThemeData.raw( brightness: brightness, visualDensity: visualDensity, @@ -444,6 +447,7 @@ class ThemeData with Diagnosticable { dividerTheme: dividerTheme, buttonBarTheme: buttonBarTheme, bottomNavigationBarTheme: bottomNavigationBarTheme, + fixTextFieldOutlineLabel: fixTextFieldOutlineLabel, ); } @@ -523,6 +527,7 @@ class ThemeData with Diagnosticable { @required this.dividerTheme, @required this.buttonBarTheme, @required this.bottomNavigationBarTheme, + @required this.fixTextFieldOutlineLabel, }) : assert(brightness != null), assert(visualDensity != null), assert(primaryColor != null), @@ -584,7 +589,8 @@ class ThemeData with Diagnosticable { assert(bannerTheme != null), assert(dividerTheme != null), assert(buttonBarTheme != null), - assert(bottomNavigationBarTheme != null); + assert(bottomNavigationBarTheme != null), + assert(fixTextFieldOutlineLabel != null); /// Create a [ThemeData] based on the colors in the given [colorScheme] and /// text styles of the optional [textTheme]. @@ -1035,6 +1041,18 @@ class ThemeData with Diagnosticable { /// widgets. final BottomNavigationBarThemeData bottomNavigationBarTheme; + /// A temporary flag to allow apps to opt-in to a + /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y + /// coordinate of the floating label in a [TextField] [OutlineInputBorder]. + /// + /// Setting this flag to true causes the floating label to be more precisely + /// vertically centered relative to the border's outline. + /// + /// The flag is currently false by default. It will be default true and + /// deprecated before the next beta release (1.18), and removed before the next + /// stable release (1.19). + final bool fixTextFieldOutlineLabel; + /// Creates a copy of this theme but with the given fields replaced with the new values. ThemeData copyWith({ Brightness brightness, @@ -1102,6 +1120,7 @@ class ThemeData with Diagnosticable { DividerThemeData dividerTheme, ButtonBarThemeData buttonBarTheme, BottomNavigationBarThemeData bottomNavigationBarTheme, + bool fixTextFieldOutlineLabel, }) { cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault(); return ThemeData.raw( @@ -1170,6 +1189,7 @@ class ThemeData with Diagnosticable { dividerTheme: dividerTheme ?? this.dividerTheme, buttonBarTheme: buttonBarTheme ?? this.buttonBarTheme, bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme, + fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel, ); } @@ -1316,6 +1336,7 @@ class ThemeData with Diagnosticable { dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t), buttonBarTheme: ButtonBarThemeData.lerp(a.buttonBarTheme, b.buttonBarTheme, t), bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t), + fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel, ); } @@ -1389,7 +1410,8 @@ class ThemeData with Diagnosticable { && other.bannerTheme == bannerTheme && other.dividerTheme == dividerTheme && other.buttonBarTheme == buttonBarTheme - && other.bottomNavigationBarTheme == bottomNavigationBarTheme; + && other.bottomNavigationBarTheme == bottomNavigationBarTheme + && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel; } @override @@ -1463,6 +1485,7 @@ class ThemeData with Diagnosticable { dividerTheme, buttonBarTheme, bottomNavigationBarTheme, + fixTextFieldOutlineLabel, ]; return hashList(values); } diff --git a/packages/flutter/lib/src/painting/box_decoration.dart b/packages/flutter/lib/src/painting/box_decoration.dart index eac6b2011ed6e..7a5dc25da4158 100644 --- a/packages/flutter/lib/src/painting/box_decoration.dart +++ b/packages/flutter/lib/src/painting/box_decoration.dart @@ -45,7 +45,7 @@ import 'image_provider.dart'; /// decoration: BoxDecoration( /// color: const Color(0xff7c94b6), /// image: const DecorationImage( -/// image: NetworkImage('https:///flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'), +/// image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'), /// fit: BoxFit.cover, /// ), /// border: Border.all( diff --git a/packages/flutter/lib/src/painting/image_stream.dart b/packages/flutter/lib/src/painting/image_stream.dart index 4a06e1fa751b7..be34ac4d3f59d 100644 --- a/packages/flutter/lib/src/painting/image_stream.dart +++ b/packages/flutter/lib/src/painting/image_stream.dart @@ -516,6 +516,23 @@ abstract class ImageStreamCompleter with Diagnosticable { } } + /// Calls all the registered [ImageChunkListener]s (listeners with an + /// [ImageStreamListener.onChunk] specified) to notify them of a new + /// [ImageChunkEvent]. + @protected + void reportImageChunkEvent(ImageChunkEvent event){ + if (hasListeners) { + // Make a copy to allow for concurrent modification. + final List localListeners = _listeners + .map((ImageStreamListener listener) => listener.onChunk) + .where((ImageChunkListener chunkListener) => chunkListener != null) + .toList(); + for (final ImageChunkListener listener in localListeners) { + listener(event); + } + } + } + /// Accumulates a list of strings describing the object's state. Subclasses /// should override this to have their information included in [toString]. @override @@ -626,19 +643,8 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter { ); }); if (chunkEvents != null) { - chunkEvents.listen( - (ImageChunkEvent event) { - if (hasListeners) { - // Make a copy to allow for concurrent modification. - final List localListeners = _listeners - .map((ImageStreamListener listener) => listener.onChunk) - .where((ImageChunkListener chunkListener) => chunkListener != null) - .toList(); - for (final ImageChunkListener listener in localListeners) { - listener(event); - } - } - }, onError: (dynamic error, StackTrace stack) { + chunkEvents.listen(reportImageChunkEvent, + onError: (dynamic error, StackTrace stack) { reportError( context: ErrorDescription('loading an image'), exception: error, diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index b9c52e46e50f4..df5a7c5c5a8d2 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -138,6 +138,18 @@ bool _isWhitespace(int codeUnit) { return true; } +/// Returns true if [codeUnit] is a leading (high) surrogate for a surrogate +/// pair. +bool _isLeadingSurrogate(int codeUnit) { + return codeUnit & 0xFC00 == 0xD800; +} + +/// Returns true if [codeUnit] is a trailing (low) surrogate for a surrogate +/// pair. +bool _isTrailingSurrogate(int codeUnit) { + return codeUnit & 0xFC00 == 0xDC00; +} + /// Displays some text in a scrollable container with a potentially blinking /// cursor and with gesture recognizers. /// @@ -597,12 +609,14 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } } else { if (rightArrow && newSelection.extentOffset < _plainText.length) { - newSelection = newSelection.copyWith(extentOffset: newSelection.extentOffset + 1); + final int delta = _isLeadingSurrogate(text.codeUnitAt(newSelection.extentOffset)) ? 2 : 1; + newSelection = newSelection.copyWith(extentOffset: newSelection.extentOffset + delta); if (shift) { _cursorResetLocation += 1; } } else if (leftArrow && newSelection.extentOffset > 0) { - newSelection = newSelection.copyWith(extentOffset: newSelection.extentOffset - 1); + final int delta = _isTrailingSurrogate(text.codeUnitAt(newSelection.extentOffset - 1)) ? 2 : 1; + newSelection = newSelection.copyWith(extentOffset: newSelection.extentOffset - delta); if (shift) { _cursorResetLocation -= 1; } @@ -720,10 +734,12 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } void _handleDelete() { - if (selection.textAfter(_plainText).isNotEmpty) { + final String textAfter = selection.textAfter(_plainText); + if (textAfter.isNotEmpty) { + final int deleteCount = _isLeadingSurrogate(textAfter.codeUnitAt(0)) ? 2 : 1; textSelectionDelegate.textEditingValue = TextEditingValue( text: selection.textBefore(_plainText) - + selection.textAfter(_plainText).substring(1), + + selection.textAfter(_plainText).substring(deleteCount), selection: TextSelection.collapsed(offset: selection.start), ); } else { diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 25f8c83576b54..676bfbf74055e 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -4914,7 +4914,7 @@ class RenderFollowerLayer extends RenderProxyBox { /// /// When the render object is not linked, then: if [showWhenUnlinked] is true, /// the child is visible and not repositioned; if it is false, then child is - /// hidden. + /// hidden, and its hit testing is also disabled. bool get showWhenUnlinked => _showWhenUnlinked; bool _showWhenUnlinked; set showWhenUnlinked(bool value) { @@ -4962,6 +4962,9 @@ class RenderFollowerLayer extends RenderProxyBox { @override bool hitTest(BoxHitTestResult result, { Offset position }) { + // Disables the hit testing if this render object is hidden. + if (link.leader == null && !showWhenUnlinked) + return false; // RenderFollowerLayer objects don't check if they are // themselves hit, because it's confusing to think about // how the untransformed size and the child's transformed diff --git a/packages/flutter/lib/src/services/autofill.dart b/packages/flutter/lib/src/services/autofill.dart new file mode 100644 index 0000000000000..bffa7e8711761 --- /dev/null +++ b/packages/flutter/lib/src/services/autofill.dart @@ -0,0 +1,811 @@ +// Copyright 2014 The Flutter 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/foundation.dart'; +import 'text_input.dart'; + +/// A collection of commonly used autofill hint strings on different platforms. +/// +/// Each hint may not be supported on every platform, and may get translated to +/// different strings on different platforms. Please refer to their documentation +/// for what each value corresponds to on different platforms. +class AutofillHints { + AutofillHints._(); + + /// The input field expects an address locality (city/town). + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY). + /// * iOS: [addressCity](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * Otherwise, the hint string will be used as-is. + static const String addressCity = 'addressCity'; + + /// The input field expects a city name combined with a state name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [addressCityAndState](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * Otherwise, the hint string will be used as-is. + static const String addressCityAndState = 'addressCityAndState'; + + /// The input field expects a region/state. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_REGION](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_REGION). + /// * iOS: [addressState](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * Otherwise, the hint string will be used as-is. + static const String addressState = 'addressState'; + + /// The input field expects a person's full birth date. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_BIRTH_DATE_FULL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_FULL). + /// * web: ["bday"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String birthday = 'birthday'; + + /// The input field expects a person's birth day(of the month). + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_BIRTH_DATE_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_DAY). + /// * web: ["bday-day"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String birthdayDay = 'birthdayDay'; + + /// The input field expects a person's birth month. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_BIRTH_DATE_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_MONTH). + /// * web: ["bday-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String birthdayMonth = 'birthdayMonth'; + + /// The input field expects a person's birth year. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_BIRTH_DATE_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_YEAR). + /// * web: ["bday-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String birthdayYear = 'birthdayYear'; + + /// The input field expects an + /// [ISO 3166-1-alpha-2](https://www.iso.org/standard/63545.html) country code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["country"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String countryCode = 'countryCode'; + + /// The input field expects a country name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY). + /// * iOS: [countryName](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["country-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String countryName = 'countryName'; + + /// The input field expects a credit card expiration date. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER). + /// * web: ["cc-exp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardExpirationDate = 'creditCardExpirationDate'; + + /// The input field expects a credit card expiration day. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardExpirationDay = 'creditCardExpirationDay'; + + /// The input field expects a credit card expiration month. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH). + /// * web: ["cc-exp-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardExpirationMonth = 'creditCardExpirationMonth'; + + /// The input field expects a credit card expiration year. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR). + /// * web: ["cc-exp-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardExpirationYear = 'creditCardExpirationYear'; + + /// The input field expects the holder's last/family name as given on a credit + /// card. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["cc-family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardFamilyName = 'creditCardFamilyName'; + + /// The input field expects the holder's first/given name as given on a credit + /// card. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["cc-given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardGivenName = 'creditCardGivenName'; + + /// The input field expects the holder's middle name as given on a credit + /// card. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["cc-additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardMiddleName = 'creditCardMiddleName'; + + /// The input field expects the holder's full name as given on a credit card. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["cc-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardName = 'creditCardName'; + + /// The input field expects a credit card number. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER). + /// * iOS: [creditCardNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["cc-number"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardNumber = 'creditCardNumber'; + + /// The input field expects a credit card security code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE). + /// * web: ["cc-csc"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardSecurityCode = 'creditCardSecurityCode'; + + /// The input field expects the type of a credit card, for example "Visa". + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["cc-type"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String creditCardType = 'creditCardType'; + + /// The input field expects an email address. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_EMAIL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_EMAIL_ADDRESS). + /// * iOS: [emailAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["email"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String email = 'email'; + + /// The input field expects a person's last/family name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME_FAMILY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_FAMILY). + /// * iOS: [familyName](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String familyName = 'familyName'; + + /// The input field expects a street address that fully identifies a location. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS). + /// * iOS: [fullStreetAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["street-address"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String fullStreetAddress = 'fullStreetAddress'; + + /// The input field expects a gender. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_GENDER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_GENDER). + /// * web: ["sex"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String gender = 'gender'; + + /// The input field expects a person's first/given name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME_GIVEN](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_GIVEN). + /// * iOS: [givenName](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String givenName = 'givenName'; + + /// The input field expects a URL representing an instant messaging protocol + /// endpoint. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["impp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String impp = 'impp'; + + /// The input field expects a job title. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [jobTitle](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["organization-title"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String jobTitle = 'jobTitle'; + + /// The input field expects the preferred language of the user. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["language"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String language = 'language'; + + /// The input field expects a location, such as a point of interest, an + /// address,or another way to identify a location. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [location](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * Otherwise, the hint string will be used as-is. + static const String location = 'location'; + + /// The input field expects a person's middle initial. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL). + /// * Otherwise, the hint string will be used as-is. + static const String middleInitial = 'middleInitial'; + + /// The input field expects a person's middle name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE). + /// * iOS: [middleName](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String middleName = 'middleName'; + + /// The input field expects a person's full name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME). + /// * iOS: [name](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String name = 'name'; + + /// The input field expects a person's name prefix or title, such as "Dr.". + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME_PREFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_PREFIX). + /// * iOS: [namePrefix](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["honorific-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String namePrefix = 'namePrefix'; + + /// The input field expects a person's name suffix, such as "Jr.". + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PERSON_NAME_SUFFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_SUFFIX). + /// * iOS: [nameSuffix](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["honorific-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String nameSuffix = 'nameSuffix'; + + /// The input field expects a newly created password for save/update. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_NEW_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_PASSWORD). + /// * iOS: [newPassword](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["new-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String newPassword = 'newPassword'; + + /// The input field expects a newly created username for save/update. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME). + /// * Otherwise, the hint string will be used as-is. + static const String newUsername = 'newUsername'; + + /// The input field expects a nickname. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [nickname](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["nickname"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String nickname = 'nickname'; + + /// The input field expects a single-factor SMS login code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_SMS_OTP](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_SMS_OTP). + /// * iOS: [oneTimeCode](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["one-time-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String oneTimeCode = 'oneTimeCode'; + + /// The input field expects an organization name corresponding to the person, + /// address, or contact information in the other fields associated with this + /// field. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [organizationName](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["organization"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String organizationName = 'organizationName'; + + /// The input field expects a password. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PASSWORD). + /// * iOS: [password](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["current-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String password = 'password'; + + /// The input field expects a photograph, icon, or other image corresponding + /// to the company, person, address, or contact information in the other + /// fields associated with this field. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["photo"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String photo = 'photo'; + + /// The input field expects a postal address. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS). + /// * Otherwise, the hint string will be used as-is. + static const String postalAddress = 'postalAddress'; + + /// The input field expects an auxiliary address details. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS). + /// * Otherwise, the hint string will be used as-is. + static const String postalAddressExtended = 'postalAddressExtended'; + + /// The input field expects an extended ZIP/POSTAL code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE). + /// * Otherwise, the hint string will be used as-is. + static const String postalAddressExtendedPostalCode = 'postalAddressExtendedPostalCode'; + + /// The input field expects a postal code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_CODE). + /// * iOS: [postalCode](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["postal-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String postalCode = 'postalCode'; + + /// The first administrative level in the address. This is typically the + /// province in which the address is located. In the United States, this would + /// be the state. In Switzerland, the canton. In the United Kingdom, the post + /// town. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["address-level1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLevel1 = 'streetAddressLevel1'; + + /// The second administrative level, in addresses with at least two of them. + /// In countries with two administrative levels, this would typically be the + /// city, town, village, or other locality in which the address is located. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["address-level2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLevel2 = 'streetAddressLevel2'; + + /// The third administrative level, in addresses with at least three + /// administrative levels. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["address-level3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLevel3 = 'streetAddressLevel3'; + + /// The finest-grained administrative level, in addresses which have four + /// levels. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["address-level4"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLevel4 = 'streetAddressLevel4'; + + /// The input field expects the first line of a street address. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [streetAddressLine1](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["address-line1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLine1 = 'streetAddressLine1'; + + /// The input field expects the second line of a street address. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [streetAddressLine2](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["address-line2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLine2 = 'streetAddressLine2'; + + /// The input field expects the third line of a street address. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["address-line3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String streetAddressLine3 = 'streetAddressLine3'; + + /// The input field expects a sublocality. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [sublocality](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * Otherwise, the hint string will be used as-is. + static const String sublocality = 'sublocality'; + + /// The input field expects a telephone number. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PHONE_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER). + /// * iOS: [telephoneNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["tel"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumber = 'telephoneNumber'; + + /// The input field expects a phone number's area code, with a country + /// -internal prefix applied if applicable. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["tel-area-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberAreaCode = 'telephoneNumberAreaCode'; + + /// The input field expects a phone number's country code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PHONE_COUNTRY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_COUNTRY_CODE). + /// * web: ["tel-country-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberCountryCode = 'telephoneNumberCountryCode'; + + /// The input field expects the current device's phone number, usually for + /// Sign Up / OTP flows. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PHONE_NUMBER_DEVICE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER_DEVICE). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberDevice = 'telephoneNumberDevice'; + + /// The input field expects a phone number's internal extension code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["tel-extension"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberExtension = 'telephoneNumberExtension'; + + /// The input field expects a phone number without the country code and area + /// code components. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["tel-local"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberLocal = 'telephoneNumberLocal'; + + /// The input field expects the first part of the component of the telephone + /// number that follows the area code, when that component is split into two + /// components. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["tel-local-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberLocalPrefix = 'telephoneNumberLocalPrefix'; + + /// The input field expects the second part of the component of the telephone + /// number that follows the area code, when that component is split into two + /// components. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["tel-local-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberLocalSuffix = 'telephoneNumberLocalSuffix'; + + /// The input field expects a phone number without country code. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_PHONE_NATIONAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NATIONAL). + /// * web: ["tel-national"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String telephoneNumberNational = 'telephoneNumberNational'; + + /// The amount that the user would like for the transaction (e.g. when + /// entering a bid or sale price). + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["transaction-amount"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String transactionAmount = 'transactionAmount'; + + /// The currency that the user would prefer the transaction to use, in [ISO + /// 4217 currency code](https://www.iso.org/iso-4217-currency-codes.html). + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * web: ["transaction-currency"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String transactionCurrency = 'transactionCurrency'; + + /// The input field expects a URL. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * iOS: [URL](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["url"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String url = 'url'; + + /// The input field expects a username or an account name. + /// + /// This hint will be translated to the below values on different platforms: + /// + /// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME). + /// * iOS: [username](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// * web: ["username"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + /// * Otherwise, the hint string will be used as-is. + static const String username = 'username'; +} + +/// A collection of autofill related information that represents an [AutofillClient]. +/// +/// Typically used in [TextInputConfiguration.autofillConfiguration]. +@immutable +class AutofillConfiguration { + /// Creates autofill related configuration information that can be sent to the + /// platform. + const AutofillConfiguration({ + @required this.uniqueIdentifier, + @required this.autofillHints, + this.currentEditingValue, + }) : assert(uniqueIdentifier != null), + assert(autofillHints != null); + + /// A string that uniquely identifies the current [AutofillClient]. + /// + /// The identifier needs to be unique within the [AutofillScope] for the + /// [AutofillClient] to receive the correct autofill value. + /// + /// Must not be null. + final String uniqueIdentifier; + + /// A list of strings that helps the autofill service identify the type of the + /// [AutofillClient]. + /// + /// Must not be null or empty. + /// + /// {@template flutter.services.autofill.autofillHints} + /// For the best results, hint strings need to be understood by the platform's + /// autofill service. The common values of hint strings can be found in + /// [AutofillHints], as well as the platforms that understand each of them. + /// + /// If an autofillable input field needs to use a custom hint that translate to + /// different strings on different platforms, the easiest way to achieve that + /// is to return different hint strings based on the value of + /// [defaultTargetPlatform]. + /// + /// Each hint in the list, if not ignored, will be translated to the platform's + /// autofill hint type understood by its autofill services: + /// + /// * On iOS, only the first hint in the list is accounted for. The hint will + /// be translated to a + /// [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype). + /// + /// * On Android, all hints in the list are translated to Android hint strings. + /// + /// * On web, only the first hint is accounted for and will be translated to + /// an "autocomplete" string. + /// + /// See also: + /// + /// * [AutofillHints], a list of autofill hint strings that is predefined on at + /// least one platform. + /// + /// * [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype), + /// the iOS equivalent. + /// + /// * Android [autofillHints](https://developer.android.com/reference/android/view/View#setAutofillHints(java.lang.String...)), + /// the Android equivalent. + /// + /// * The [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute, + /// the web equivalent. + /// {@endtemplate} + final List autofillHints; + + /// The current [TextEditingValue] of the [AutofillClient]. + final TextEditingValue currentEditingValue; + + /// Returns a representation of this object as a JSON object. + Map toJson() { + assert(autofillHints.isNotEmpty); + return { + 'uniqueIdentifier': uniqueIdentifier, + 'hints': autofillHints, + 'editingValue': currentEditingValue.toJSON(), + }; + } +} + +/// An object that represents an autofillable input field in the autofill workflow. +/// +/// An [AutofillClient] provides autofill-related information of the input field +/// it represents to the platform, and consumes autofill inputs from the platform. +abstract class AutofillClient { + /// The unique identifier of this [AutofillClient]. + /// + /// Must not be null; + String get autofillId; + + /// The [TextInputConfiguration] that describes this [AutofillClient]. + /// + /// In order to participate in autofill, its + /// [TextInputConfiguration.autofillConfiguration] must not be null. + TextInputConfiguration get textInputConfiguration; + + /// Requests this [AutofillClient] update its [TextEditingState] to the given + /// state. + void updateEditingValue(TextEditingValue newEditingValue); +} + +/// An ordered group within which [AutofillClient]s are logically connected. +/// +/// {@template flutter.services.autofill.AutofillScope} +/// [AutofillClient]s within the same [AutofillScope] are isolated from other +/// input fields during autofill. That is, when an autofillable [TextInputClient] +/// gains focus, only the [AutofillClient]s within the same [AutofillScope] will +/// be visible to the autofill service, in the same order as they appear in +/// [autofillClients]. +/// +/// [AutofillScope] also allows [TextInput] to redirect autofill values from the +/// platform to the [AutofillClient] with the given identifier, by calling +/// [getAutofillClient]. +/// +/// An [AutofillClient] that's not tied to any [AutofillScope] will only +/// participate in autofill if the autofill is directly triggered by its own +/// [TextInputClient]. +/// {@endtemplate} +abstract class AutofillScope { + /// Gets the [AutofillScope] associated with the given [autofillId], in + /// this [AutofillScope]. + /// + /// Returns null if there's no matching [AutofillClient]. + AutofillClient getAutofillClient(String autofillId); + + /// The collection of [AutofillClient]s currently tied to this [AutofillScope]. + /// + /// Every [AutofillClient] in this list must have autofill enabled (i.e. its + /// [AutofillClient.textInputConfiguration] must have a non-null + /// [AutofillConfiguration].) + Iterable get autofillClients; + + /// Allows a [TextInputClient] to attach to this scope. This method should be + /// called in lieu of [TextInput.attach], when the [TextInputClient] wishes to + /// participate in autofill. + TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration); +} + +@immutable +class _AutofillScopeTextInputConfiguration extends TextInputConfiguration { + _AutofillScopeTextInputConfiguration({ + @required this.allConfigurations, + @required TextInputConfiguration currentClientConfiguration, + }) : assert(allConfigurations != null), + assert(currentClientConfiguration != null), + super(inputType: currentClientConfiguration.inputType, + obscureText: currentClientConfiguration.obscureText, + autocorrect: currentClientConfiguration.autocorrect, + smartDashesType: currentClientConfiguration.smartDashesType, + smartQuotesType: currentClientConfiguration.smartQuotesType, + enableSuggestions: currentClientConfiguration.enableSuggestions, + inputAction: currentClientConfiguration.inputAction, + textCapitalization: currentClientConfiguration.textCapitalization, + keyboardAppearance: currentClientConfiguration.keyboardAppearance, + actionLabel: currentClientConfiguration.actionLabel, + autofillConfiguration: currentClientConfiguration.autofillConfiguration, + ); + + final Iterable allConfigurations; + + @override + Map toJson() { + final Map result = super.toJson(); + result['fields'] = allConfigurations + .map((TextInputConfiguration configuration) => configuration.toJson()) + .toList(growable: false); + return result; + } +} + +/// A partial implementation of [AutofillScope]. +/// +/// The mixin provides a default implementation for [AutofillScope.attach]. +mixin AutofillScopeMixin implements AutofillScope { + @override + TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration) { + assert(trigger != null); + assert( + !autofillClients.any((AutofillClient client) => client.textInputConfiguration.autofillConfiguration == null), + 'Every client in AutofillScope.autofillClients must enable autofill', + ); + return TextInput.attach( + trigger, + _AutofillScopeTextInputConfiguration( + allConfigurations: autofillClients + .map((AutofillClient client) => client.textInputConfiguration), + currentClientConfiguration: configuration, + ), + ); + } +} diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 35bd2f400f48c..6eb96b47beae4 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -16,6 +16,7 @@ import 'dart:ui' show import 'package:flutter/foundation.dart'; import 'package:vector_math/vector_math_64.dart' show Matrix4; +import 'autofill.dart'; import 'message_codec.dart'; import 'platform_channel.dart'; import 'system_channels.dart'; @@ -24,9 +25,6 @@ import 'text_editing.dart'; export 'dart:ui' show TextAffinity; -// Whether we're compiled to JavaScript in a web browser. -const bool _kIsBrowser = identical(0, 0.0); - /// Indicates how to handle the intelligent replacement of dashes in text input. /// /// See also: @@ -441,6 +439,7 @@ class TextInputConfiguration { this.inputAction = TextInputAction.done, this.keyboardAppearance = Brightness.light, this.textCapitalization = TextCapitalization.none, + this.autofillConfiguration, }) : assert(inputType != null), assert(obscureText != null), smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), @@ -464,6 +463,14 @@ class TextInputConfiguration { /// Defaults to true. final bool autocorrect; + /// The configuration to use for autofill. + /// + /// Defaults to null, in which case no autofill information will be provided + /// to the platform. This will prevent the corresponding input field from + /// participating in autofills triggered by other fields. Additionally, on + /// Android and web, setting [autofillConfiguration] to null disables autofill. + final AutofillConfiguration autofillConfiguration; + /// {@template flutter.services.textInput.smartDashesType} /// Whether to allow the platform to automatically format dashes. /// @@ -565,6 +572,7 @@ class TextInputConfiguration { 'inputAction': inputAction.toString(), 'textCapitalization': textCapitalization.toString(), 'keyboardAppearance': keyboardAppearance.toString(), + if (autofillConfiguration != null) 'autofill': autofillConfiguration.toJson(), }; } } @@ -745,6 +753,21 @@ abstract class TextInputClient { /// const constructors so that they can be used in const expressions. const TextInputClient(); + /// The current state of the [TextEditingValue] held by this client. + TextEditingValue get currentTextEditingValue; + + /// The [AutofillScope] this [TextInputClient] belongs to, if any. + /// + /// It should return null if this [TextInputClient] does not need autofill + /// support. For a [TextInputClient] that supports autofill, returning null + /// causes it to participate in autofill alone. + /// + /// See also: + /// + /// * [AutofillGroup], a widget that creates an [AutofillScope] for its + /// descendent autofillable [TextInputClient]s. + AutofillScope get currentAutofillScope; + /// Requests that this client update its editing state to the given value. void updateEditingValue(TextEditingValue value); @@ -754,9 +777,6 @@ abstract class TextInputClient { /// Updates the floating cursor position and state. void updateFloatingCursor(RawFloatingCursorPoint point); - /// The current state of the [TextEditingValue] held by this client. - TextEditingValue get currentTextEditingValue; - /// Requests that this client display a prompt rectangle for the given text range, /// to indicate the range of text that will be changed by a pending autocorrection. /// @@ -809,6 +829,17 @@ class TextInputConnection { TextInput._instance._show(); } + /// Requests the platform autofill UI to appear. + /// + /// The call has no effect unless the currently attached client supports + /// autofill, and the platform has a standalone autofill UI (for example, this + /// call has no effect on iOS since its autofill UI is part of the software + /// keyboard). + void requestAutofill() { + assert(attached); + TextInput._instance._requestAutofill(); + } + /// Requests that the text input control change its internal state to match the given state. void setEditingState(TextEditingValue value) { assert(attached); @@ -1022,7 +1053,7 @@ class TextInput { static bool _debugEnsureInputActionWorksOnPlatform(TextInputAction inputAction) { assert(() { - if (_kIsBrowser) { + if (kIsWeb) { // TODO(flutterweb): what makes sense here? return true; } @@ -1065,6 +1096,22 @@ class TextInput { } final List args = methodCall.arguments as List; + + if (method == 'TextInputClient.updateEditingStateWithTag') { + final TextInputClient client = _currentConnection._client; + assert(client != null); + final AutofillScope scope = client.currentAutofillScope; + final Map editingValue = args[1] as Map; + for (final String tag in editingValue.keys) { + final TextEditingValue textEditingValue = TextEditingValue.fromJSON( + editingValue[tag] as Map, + ); + scope?.getAutofillClient(tag)?.updateEditingValue(textEditingValue); + } + + return; + } + final int client = args[0] as int; // The incoming message was for a different client. if (client != _currentConnection._id) @@ -1128,6 +1175,10 @@ class TextInput { _channel.invokeMethod('TextInput.show'); } + void _requestAutofill() { + _channel.invokeMethod('TextInput.requestAutofill'); + } + void _setEditableSizeAndTransform(Map args) { _channel.invokeMethod( 'TextInput.setEditableSizeAndTransform', diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart index 096f61a9373d1..87e0ac296eee1 100644 --- a/packages/flutter/lib/src/widgets/actions.dart +++ b/packages/flutter/lib/src/widgets/actions.dart @@ -82,7 +82,7 @@ abstract class Action with Diagnosticable { /// /// If the enabled state changes, overriding subclasses must call /// [notifyActionListeners] to notify any listeners of the change. - bool get enabled => true; + bool isEnabled(covariant T intent) => true; /// Called when the action is to be performed. /// @@ -379,7 +379,7 @@ class ActionDispatcher with Diagnosticable { assert(action != null); assert(intent != null); context ??= primaryFocus?.context; - if (action.enabled) { + if (action.isEnabled(intent)) { if (action is ContextAction) { return action.invoke(intent, context); } else { @@ -486,7 +486,7 @@ class Actions extends StatefulWidget { /// updated action. static VoidCallback handler(BuildContext context, T intent, {bool nullOk = false}) { final Action action = Actions.find(context, nullOk: nullOk); - if (action != null && action.enabled) { + if (action != null && action.isEnabled(intent)) { return () { Actions.of(context).invokeAction(action, intent, context); }; @@ -635,12 +635,10 @@ class Actions extends StatefulWidget { } class _ActionsState extends State { - // Keeps the last-known enabled state of each action in the action map in - // order to be able to appropriately notify dependents that the state has - // changed. - Map, bool> enabledState = , bool>{}; - // Used to tell the marker to rebuild when the enabled state of an action in - // the map changes. + // The set of actions that this Actions widget is current listening to. + Set> listenedActions = >{}; + // Used to tell the marker to rebuild its dependencies when the state of an + // action in the map changes. Object rebuildKey = Object(); @override @@ -650,42 +648,24 @@ class _ActionsState extends State { } void _handleActionChanged(Action action) { - assert(enabledState.containsKey(action)); - final bool actionEnabled = action.enabled; - if (enabledState[action] == null || enabledState[action] != actionEnabled) { - setState(() { - enabledState[action] = actionEnabled; - // Generate a new key so that the marker notifies dependents. - rebuildKey = Object(); - }); - } + // Generate a new key so that the marker notifies dependents. + setState(() { + rebuildKey = Object(); + }); } void _updateActionListeners() { - final Map, bool> newState = , bool>{}; - final Set> foundActions = >{}; - for (final Action action in widget.actions.values) { - if (enabledState.containsKey(action)) { - // Already subscribed to this action, just copy over the current enabled state. - newState[action] = enabledState[action]; - foundActions.add(action); - } else { - // New action to subscribe to. - // Don't set the new state to action.enabled, since that can cause - // problems when the enabled accessor looks up other widgets (which may - // have already been removed from the tree). - newState[action] = null; - action.addActionListener(_handleActionChanged); - } + final Set> widgetActions = widget.actions.values.toSet(); + final Set> removedActions = listenedActions.difference(widgetActions); + final Set> addedActions = widgetActions.difference(listenedActions); + + for (final Action action in removedActions) { + action.removeActionListener(_handleActionChanged); } - // Unregister from any actions in the previous enabledState map that aren't - // going to be transferred to the new one. - for (final Action action in enabledState.keys) { - if (!foundActions.contains(action)) { - action.removeActionListener(_handleActionChanged); - } + for (final Action action in addedActions) { + action.addActionListener(_handleActionChanged); } - enabledState = newState; + listenedActions = widgetActions; } @override @@ -697,10 +677,10 @@ class _ActionsState extends State { @override void dispose() { super.dispose(); - for (final Action action in enabledState.keys) { + for (final Action action in listenedActions) { action.removeActionListener(_handleActionChanged); } - enabledState = null; + listenedActions = null; } @override diff --git a/packages/flutter/lib/src/widgets/autofill.dart b/packages/flutter/lib/src/widgets/autofill.dart new file mode 100644 index 0000000000000..e86198eb0e0e8 --- /dev/null +++ b/packages/flutter/lib/src/widgets/autofill.dart @@ -0,0 +1,227 @@ +// Copyright 2014 The Flutter 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/services.dart'; +import 'framework.dart'; + +export 'package:flutter/services.dart' show AutofillHints; + +/// An [AutofillScope] widget that groups [AutofillClient]s together. +/// +/// [AutofillClient]s within the same [AutofillScope] must be built together, and +/// they be will be autofilled together. +/// +/// {@macro flutter.services.autofill.AutofillScope} +/// +/// The [AutofillGroup] widget only knows about [AutofillClient]s registered to +/// it using the [AutofillGroupState.register] API. Typically, [AutofillGroup] +/// will not pick up [AutofillClient]s that are not mounted, for example, an +/// [AutofillClient] within a [Scrollable] that has never been scrolled into the +/// viewport. To workaround this problem, ensure clients in the same [AutofillGroup] +/// are built together: +/// +/// {@tool dartpad --template=stateful_widget_material} +/// +/// An example form with autofillable fields grouped into different `AutofillGroup`s. +/// +/// ```dart +/// bool isSameAddress = true; +/// final TextEditingController shippingAddress1 = TextEditingController(); +/// final TextEditingController shippingAddress2 = TextEditingController(); +/// final TextEditingController billingAddress1 = TextEditingController(); +/// final TextEditingController billingAddress2 = TextEditingController(); +/// +/// final TextEditingController creditCardNumber = TextEditingController(); +/// final TextEditingController creditCardSecurityCode = TextEditingController(); +/// +/// final TextEditingController phoneNumber = TextEditingController(); +/// +/// @override +/// Widget build(BuildContext context) { +/// return ListView( +/// children: [ +/// const Text('Shipping address'), +/// // The address fields are grouped together as some platforms are capable +/// // of autofilling all these fields in one go. +/// AutofillGroup( +/// child: Column( +/// children: [ +/// TextField( +/// controller: shippingAddress1, +/// autofillHints: [AutofillHints.streetAddressLine1], +/// ), +/// TextField( +/// controller: shippingAddress2, +/// autofillHints: [AutofillHints.streetAddressLine2], +/// ), +/// ], +/// ), +/// ), +/// const Text('Billing address'), +/// Checkbox( +/// value: isSameAddress, +/// onChanged: (bool newValue) { +/// setState(() { isSameAddress = newValue; }); +/// }, +/// ), +/// // Again the address fields are grouped together for the same reason. +/// if (!isSameAddress) AutofillGroup( +/// child: Column( +/// children: [ +/// TextField( +/// controller: billingAddress1, +/// autofillHints: [AutofillHints.streetAddressLine1], +/// ), +/// TextField( +/// controller: billingAddress2, +/// autofillHints: [AutofillHints.streetAddressLine2], +/// ), +/// ], +/// ), +/// ), +/// const Text('Credit Card Information'), +/// // The credit card number and the security code are grouped together as +/// // some platforms are capable of autofilling both fields. +/// AutofillGroup( +/// child: Column( +/// children: [ +/// TextField( +/// controller: creditCardNumber, +/// autofillHints: [AutofillHints.creditCardNumber], +/// ), +/// TextField( +/// controller: creditCardSecurityCode, +/// autofillHints: [AutofillHints.creditCardSecurityCode], +/// ), +/// ], +/// ), +/// ), +/// const Text('Contact Phone Number'), +/// // The phone number field can still be autofilled despite lacking an +/// // `AutofillScope`. +/// TextField( +/// controller: phoneNumber, +/// autofillHints: [AutofillHints.telephoneNumber], +/// ), +/// ], +/// ); +/// } +/// ``` +/// {@end-tool} +class AutofillGroup extends StatefulWidget { + /// Creates a scope for autofillable input fields. + /// + /// The [child] argument must not be null. + const AutofillGroup({ + Key key, + @required this.child, + }) : assert(child != null), + super(key: key); + + /// Returns the closest [AutofillGroupState] which encloses the given context. + /// + /// {@macro flutter.widgets.autofill.AutofillGroupState} + /// + /// See also: + /// + /// * [EditableTextState], where this method is used to retrive the closest + /// [AutofillGroupState]. + static AutofillGroupState of(BuildContext context) { + final _AutofillScope scope = context.dependOnInheritedWidgetOfExactType<_AutofillScope>(); + return scope?._scope; + } + + /// {@macro flutter.widgets.child} + final Widget child; + + @override + AutofillGroupState createState() => AutofillGroupState(); +} + +/// State associated with an [AutofillGroup] widget. +/// +/// {@template flutter.widgets.autofill.AutofillGroupState} +/// An [AutofillGroupState] can be used to register an [AutofillClient] when it +/// enters this [AutofillGroup] (for example, when an [EditableText] is mounted or +/// reparented onto the [AutofillGroup]'s subtree), and unregister an +/// [AutofillClient] when it exits (for example, when an [EditableText] gets +/// unmounted or reparented out of the [AutofillGroup]'s subtree). +/// +/// The [AutofillGroupState] class also provides an [attach] method that can be +/// called by [TextInputClient]s that support autofill, instead of +/// [TextInputClient.attach], to create a [TextInputConnection] to interact with +/// the platform's text input system. +/// {@endtemplate} +/// +/// Typically obtained using [AutofillGroup.of]. +class AutofillGroupState extends State with AutofillScopeMixin { + final Map _clients = {}; + + @override + AutofillClient getAutofillClient(String tag) => _clients[tag]; + + @override + Iterable get autofillClients { + return _clients.values + .where((AutofillClient client) => client?.textInputConfiguration?.autofillConfiguration != null); + } + + /// Adds the [AutofillClient] to this [AutofillGroup]. + /// + /// Typically, this is called by [TextInputClient]s that support autofill (for + /// example, [EditableTextState]) in [State.didChangeDependencies], when the + /// input field should be registered to a new [AutofillGroup]. + /// + /// See also: + /// + /// * [EditableTextState.didChangeDependencies], where this method is called + /// to update the current [AutofillScope] when needed. + void register(AutofillClient client) { + assert(client != null); + _clients.putIfAbsent(client.autofillId, () => client); + } + + /// Removes an [AutofillClient] with the given [autofillId] from this + /// [AutofillGroup]. + /// + /// Typically, this should be called by autofillable [TextInputClient]s in + /// [State.dispose] and [State.didChangeDependencies], when the input field + /// needs to be removed from the [AutofillGroup] it is currently registered to. + /// + /// See also: + /// + /// * [EditableTextState.didChangeDependencies], where this method is called + /// to unregister from the previous [AutofillScope]. + /// * [EditableTextState.dispose], where this method is called to unregister + /// from the current [AutofillScope] when the widget is about to be removed + /// from the tree. + void unregister(String autofillId) { + assert(autofillId != null && _clients.containsKey(autofillId)); + _clients.remove(autofillId); + } + + @override + Widget build(BuildContext context) { + return _AutofillScope( + autofillScopeState: this, + child: widget.child, + ); + } +} + +class _AutofillScope extends InheritedWidget { + const _AutofillScope({ + Key key, + Widget child, + AutofillGroupState autofillScopeState, + }) : _scope = autofillScopeState, + super(key: key, child: child); + + final AutofillGroupState _scope; + + AutofillGroup get client => _scope.widget; + + @override + bool updateShouldNotify(_AutofillScope old) => _scope != old._scope; +} diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index e1ebab433b445..a1792bb436e79 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -7,12 +7,13 @@ import 'dart:math' as math; import 'dart:ui' as ui hide TextStyle; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/painting.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/gestures.dart' show DragStartBehavior; +import 'autofill.dart'; import 'automatic_keep_alive.dart'; import 'basic.dart'; import 'binding.dart'; @@ -29,8 +30,8 @@ import 'scrollable.dart'; import 'text_selection.dart'; import 'ticker_provider.dart'; -export 'package:flutter/services.dart' show TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType; export 'package:flutter/rendering.dart' show SelectionChangedCause; +export 'package:flutter/services.dart' show TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType; /// Signature for the callback that reports when the user changes the selection /// (including the cursor location). @@ -409,6 +410,7 @@ class EditableText extends StatefulWidget { paste: true, selectAll: true, ), + this.autofillHints, }) : assert(controller != null), assert(focusNode != null), assert(obscureText != null), @@ -1079,6 +1081,23 @@ class EditableText extends StatefulWidget { /// {@macro flutter.rendering.editable.selectionEnabled} bool get selectionEnabled => enableInteractiveSelection; + /// {@template flutter.widgets.editableText.autofillHints} + /// A list of strings that helps the autofill service identify the type of this + /// text input. + /// + /// When set to null or empty, the text input will not send any autofill related + /// information to the platform. As a result, it will not participate in + /// autofills triggered by a different [AutofillClient], even if they're in the + /// same [AutofillScope]. Additionally, on Android and web, setting this to null + /// or empty will disable autofill for this text field. + /// + /// The minimum platform SDK version that supports Autofill is API level 26 + /// for Android, and iOS 10.0 for iOS. + /// + /// {@macro flutter.services.autofill.autofillHints} + /// {@endtemplate} + final Iterable autofillHints; + @override EditableTextState createState() => EditableTextState(); @@ -1104,11 +1123,12 @@ class EditableText extends StatefulWidget { properties.add(DiagnosticsProperty('keyboardType', keyboardType, defaultValue: null)); properties.add(DiagnosticsProperty('scrollController', scrollController, defaultValue: null)); properties.add(DiagnosticsProperty('scrollPhysics', scrollPhysics, defaultValue: null)); + properties.add(DiagnosticsProperty>('autofillHints', autofillHints, defaultValue: null)); } } /// State for a [EditableText]. -class EditableTextState extends State with AutomaticKeepAliveClientMixin, WidgetsBindingObserver, TickerProviderStateMixin implements TextInputClient, TextSelectionDelegate { +class EditableTextState extends State with AutomaticKeepAliveClientMixin, WidgetsBindingObserver, TickerProviderStateMixin implements TextSelectionDelegate, TextInputClient, AutofillClient { Timer _cursorTimer; bool _targetCursorVisibility = false; final ValueNotifier _cursorVisibilityNotifier = ValueNotifier(true); @@ -1128,6 +1148,10 @@ class EditableTextState extends State with AutomaticKeepAliveClien bool _didAutoFocus = false; FocusAttachment _focusAttachment; + AutofillGroupState _currentAutofillScope; + @override + AutofillScope get currentAutofillScope => _currentAutofillScope; + // This value is an eyeball estimation of the time it takes for the iOS cursor // to ease in and out. static const Duration _fadeDuration = Duration(milliseconds: 250); @@ -1175,6 +1199,14 @@ class EditableTextState extends State with AutomaticKeepAliveClien @override void didChangeDependencies() { super.didChangeDependencies(); + + final AutofillGroupState newAutofillGroup = AutofillGroup.of(context); + if (currentAutofillScope != newAutofillGroup) { + _currentAutofillScope?.unregister(autofillId); + _currentAutofillScope = newAutofillGroup; + newAutofillGroup?.register(this); + } + if (!_didAutoFocus && widget.autofocus) { _didAutoFocus = true; SchedulerBinding.instance.addPostFrameCallback((_) { @@ -1210,6 +1242,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (oldWidget.readOnly && _hasFocus) _openInputConnection(); } + if (widget.style != oldWidget.style) { final TextStyle style = widget.style; // The _textInputConnection will pick up the new style when it attaches in @@ -1228,6 +1261,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien @override void dispose() { + _currentAutofillScope?.unregister(autofillId); widget.controller.removeListener(_didChangeTextEditingValue); _cursorBlinkOpacityController.removeListener(_onCursorColorTick); _floatingCursorResetController.removeListener(_onFloatingCursorResetTick); @@ -1278,10 +1312,12 @@ class EditableTextState extends State with AutomaticKeepAliveClien _formatAndSetValue(value); - // To keep the cursor from blinking while typing, we want to restart the - // cursor timer every time a new character is typed. - _stopCursorTimer(resetCharTicks: false); - _startCursorTimer(); + if (_hasInputConnection) { + // To keep the cursor from blinking while typing, we want to restart the + // cursor timer every time a new character is typed. + _stopCursorTimer(resetCharTicks: false); + _startCursorTimer(); + } } @override @@ -1465,26 +1501,16 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (!_hasInputConnection) { final TextEditingValue localValue = _value; _lastFormattedUnmodifiedTextEditingValue = localValue; - _textInputConnection = TextInput.attach( - this, - TextInputConfiguration( - inputType: widget.keyboardType, - obscureText: widget.obscureText, - autocorrect: widget.autocorrect, - smartDashesType: widget.smartDashesType ?? (widget.obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), - smartQuotesType: widget.smartQuotesType ?? (widget.obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), - enableSuggestions: widget.enableSuggestions, - inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline - ? TextInputAction.newline - : TextInputAction.done - ), - textCapitalization: widget.textCapitalization, - keyboardAppearance: widget.keyboardAppearance, - ), - ); - _textInputConnection.show(); + _textInputConnection = (widget.autofillHints?.isNotEmpty ?? false) && currentAutofillScope != null + ? currentAutofillScope.attach(this, textInputConfiguration) + : TextInput.attach(this, textInputConfiguration); + _textInputConnection.show(); _updateSizeAndTransform(); + // Request autofill AFTER the size and the transform have been sent to the + // platform side. + _textInputConnection.requestAutofill(); + final TextStyle style = widget.style; _textInputConnection ..setStyle( @@ -1904,6 +1930,33 @@ class EditableTextState extends State with AutomaticKeepAliveClien } } + @override + String get autofillId => 'EditableText-$hashCode'; + + @override + TextInputConfiguration get textInputConfiguration { + final bool isAutofillEnabled = widget.autofillHints?.isNotEmpty ?? false; + return TextInputConfiguration( + inputType: widget.keyboardType, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType ?? (widget.obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), + smartQuotesType: widget.smartQuotesType ?? (widget.obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), + enableSuggestions: widget.enableSuggestions, + inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline + ? TextInputAction.newline + : TextInputAction.done + ), + textCapitalization: widget.textCapitalization, + keyboardAppearance: widget.keyboardAppearance, + autofillConfiguration: !isAutofillEnabled ? null : AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: widget.autofillHints.toList(growable: false), + currentEditingValue: currentTextEditingValue, + ), + ); + } + // null if no promptRect should be shown. TextRange _currentPromptRectRange; diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index fae6eb35b45a2..807806e40feca 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -911,7 +911,7 @@ class ScrollAction extends Action { static const LocalKey key = ValueKey(ScrollAction); @override - bool get enabled { + bool isEnabled(ScrollIntent intent) { final FocusNode focus = primaryFocus; return focus != null && focus.context != null && Scrollable.of(focus.context) != null; } diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 3e17698d0e2af..361ff1e48564b 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -959,13 +959,13 @@ mixin WidgetInspectorService { SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart); final FlutterExceptionHandler structuredExceptionHandler = _reportError; - final FlutterExceptionHandler defaultExceptionHandler = FlutterError.onError; + final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError; _registerBoolServiceExtension( name: 'structuredErrors', - getter: () async => FlutterError.onError == structuredExceptionHandler, + getter: () async => FlutterError.presentError == structuredExceptionHandler, setter: (bool value) { - FlutterError.onError = value ? structuredExceptionHandler : defaultExceptionHandler; + FlutterError.presentError = value ? structuredExceptionHandler : defaultExceptionHandler; return Future.value(); }, ); diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 2db238b921bb4..38b3b5f07b4ab 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -22,6 +22,7 @@ export 'src/widgets/animated_switcher.dart'; export 'src/widgets/annotated_region.dart'; export 'src/widgets/app.dart'; export 'src/widgets/async.dart'; +export 'src/widgets/autofill.dart'; export 'src/widgets/automatic_keep_alive.dart'; export 'src/widgets/banner.dart'; export 'src/widgets/basic.dart'; diff --git a/packages/flutter/test/material/about_test.dart b/packages/flutter/test/material/about_test.dart index 7822bca73768a..401c07fbb6790 100644 --- a/packages/flutter/test/material/about_test.dart +++ b/packages/flutter/test/material/about_test.dart @@ -91,7 +91,7 @@ void main() { findsOneWidget, ); expect(find.text('Pirate license'), findsOneWidget); - }, skip: isBrowser); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/54385 testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async { await tester.pumpWidget( @@ -138,7 +138,7 @@ void main() { expect(find.text('BBB'), findsOneWidget); expect(find.text('Another package'), findsOneWidget); expect(find.text('Another license'), findsOneWidget); - }, skip: isBrowser); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/54385 testWidgets('LicensePage control test with all properties', (WidgetTester tester) async { const FlutterLogo logo = FlutterLogo(); @@ -199,7 +199,7 @@ void main() { expect(find.text('BBB'), findsOneWidget); expect(find.text('Another package'), findsOneWidget); expect(find.text('Another license'), findsOneWidget); - }, skip: isBrowser); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/54385 testWidgets('LicensePage respects the notch', (WidgetTester tester) async { const double safeareaPadding = 27.0; @@ -224,7 +224,7 @@ void main() { await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('DEF')), const Offset(8.0 + safeareaPadding, 287.0)); - }, skip: isBrowser); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/54385 testWidgets('LicensePage returns early if unmounted', (WidgetTester tester) async { final Completer licenseCompleter = Completer(); @@ -249,7 +249,7 @@ void main() { final FakeLicenseEntry licenseEntry = FakeLicenseEntry(); licenseCompleter.complete(licenseEntry); expect(licenseEntry.paragraphsCalled, false); - }, skip: isBrowser); + }); testWidgets('LicensePage returns late if unmounted', (WidgetTester tester) async { final Completer licenseCompleter = Completer(); @@ -274,7 +274,7 @@ void main() { await tester.pumpAndSettle(); expect(licenseEntry.paragraphsCalled, true); - }, skip: isBrowser); + }); testWidgets('LicensePage logic defaults to executable name for app name', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/material/bottom_app_bar_test.dart b/packages/flutter/test/material/bottom_app_bar_test.dart index 18eb488b292fd..b42bbf07eea5c 100644 --- a/packages/flutter/test/material/bottom_app_bar_test.dart +++ b/packages/flutter/test/material/bottom_app_bar_test.dart @@ -79,7 +79,8 @@ void main() { find.byKey(key), matchesGoldenFile('bottom_app_bar.custom_shape.2.png'), ); - }, skip: isBrowser); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/51675, + // https://github.com/flutter/flutter/issues/44572 testWidgets('color defaults to Theme.bottomAppBarColor', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/material/button_theme_test.dart b/packages/flutter/test/material/button_theme_test.dart index c9784046a8fcf..0e95ce89565ee 100644 --- a/packages/flutter/test/material/button_theme_test.dart +++ b/packages/flutter/test/material/button_theme_test.dart @@ -170,7 +170,7 @@ void main() { expect(tester.widget(find.byType(Material)).shape, shape); expect(tester.widget(find.byType(Material)).color, disabledColor); expect(tester.getSize(find.byType(Material)), const Size(88.0, 48.0)); - }, skip: isBrowser); + }); testWidgets('Theme buttonTheme ButtonTheme overrides', (WidgetTester tester) async { ButtonTextTheme textTheme; diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index c47ec51c1233f..997488e88c658 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -497,7 +497,7 @@ void main() { ); expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0)); expect(tester.getSize(find.byType(Chip)), const Size(800.0, 48.0)); - }, skip: isBrowser); + }); testWidgets('Chip elements are ordered horizontally for locale', (WidgetTester tester) async { final UniqueKey iconKey = UniqueKey(); @@ -618,7 +618,7 @@ void main() { expect(tester.getSize(find.byType(Chip).first).width, anyOf(318.0, 319.0)); expect(tester.getSize(find.byType(Chip).first).height, equals(50.0)); expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0))); - }, skip: isBrowser); + }); testWidgets('Labels can be non-text widgets', (WidgetTester tester) async { final Key keyA = GlobalKey(); @@ -652,7 +652,7 @@ void main() { anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)), ); expect(tester.getSize(find.byType(Chip).last), const Size(58.0, 48.0)); - }, skip: isBrowser); + }); testWidgets('Avatars can be non-circle avatar widgets', (WidgetTester tester) async { final Key keyA = GlobalKey(); @@ -876,7 +876,7 @@ void main() { expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); expect(find.byKey(avatarKey), findsNothing); - }, skip: isBrowser); + }); testWidgets('Delete button drawer works as expected on RawChip', (WidgetTester tester) async { final UniqueKey labelKey = UniqueKey(); @@ -992,7 +992,7 @@ void main() { expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); expect(find.byKey(deleteButtonKey), findsNothing); - }, skip: isBrowser); + }); testWidgets('Chip creates centered, unique ripple when label is tapped', (WidgetTester tester) async { // Creates a chip with a delete button. @@ -1046,7 +1046,7 @@ void main() { expect(findTooltipContainer('Delete'), findsNothing); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('Delete button creates non-centered, unique ripple when tapped', (WidgetTester tester) async { // Creates a chip with a delete button. @@ -1104,7 +1104,7 @@ void main() { expect(findTooltipContainer('Delete'), findsOneWidget); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('RTL delete button responds to tap on the left of the chip', (WidgetTester tester) async { // Creates an RTL chip with a delete button. @@ -1134,7 +1134,7 @@ void main() { expect(findTooltipContainer('Delete'), findsOneWidget); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('Chip without delete button creates correct ripple', (WidgetTester tester) async { // Creates a chip with a delete button. @@ -1188,7 +1188,7 @@ void main() { expect(findTooltipContainer('Delete'), findsNothing); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('Selection with avatar works as expected on RawChip', (WidgetTester tester) async { bool selected = false; @@ -1271,7 +1271,7 @@ void main() { expect(getSelectProgress(tester), equals(0.0)); expect(getAvatarDrawerProgress(tester), equals(1.0)); expect(getDeleteDrawerProgress(tester), equals(0.0)); - }, skip: isBrowser); + }); testWidgets('Selection without avatar works as expected on RawChip', (WidgetTester tester) async { bool selected = false; @@ -1347,7 +1347,7 @@ void main() { expect(getSelectProgress(tester), equals(0.0)); expect(getAvatarDrawerProgress(tester), equals(0.0)); expect(getDeleteDrawerProgress(tester), equals(0.0)); - }, skip: isBrowser); + }); testWidgets('Activation works as expected on RawChip', (WidgetTester tester) async { bool selected = false; @@ -1472,7 +1472,7 @@ void main() { ); expect(tester.getSize(find.byKey(key2)), const Size(80.0, 32.0)); - }, skip: isBrowser); + }); testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async { final ThemeData themeData = ThemeData( @@ -2105,7 +2105,7 @@ void main() { ], excludes: [ const Offset(4, 4), ])); - }, skip: isBrowser); + }); testWidgets('Chips should use InkWell instead of InkResponse.', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/28646 diff --git a/packages/flutter/test/material/chip_theme_test.dart b/packages/flutter/test/material/chip_theme_test.dart index db0b0f3b4e6e9..31330333414b6 100644 --- a/packages/flutter/test/material/chip_theme_test.dart +++ b/packages/flutter/test/material/chip_theme_test.dart @@ -164,7 +164,7 @@ void main() { expect(materialBox, paints..path(color: Color(customTheme.backgroundColor.value))); expect(material.elevation, customTheme.elevation); expect(material.shadowColor, customTheme.shadowColor); - }, skip: isBrowser); + }); testWidgets('ChipThemeData generates correct opacities for defaults', (WidgetTester tester) async { const Color customColor1 = Color(0xcafefeed); diff --git a/packages/flutter/test/material/circle_avatar_test.dart b/packages/flutter/test/material/circle_avatar_test.dart index d6e6b08a89e43..2a434a1f74ecc 100644 --- a/packages/flutter/test/material/circle_avatar_test.dart +++ b/packages/flutter/test/material/circle_avatar_test.dart @@ -186,7 +186,7 @@ void main() { ), ); expect(tester.getSize(find.text('Z')), equals(const Size(16.0, 16.0))); - }, skip: isBrowser); + }); testWidgets('CircleAvatar respects minRadius', (WidgetTester tester) async { final Color backgroundColor = Colors.blue.shade900; diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index 57ff78dafc466..1050e8950062c 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -30,6 +30,92 @@ Finder _iconRichText(Key iconKey) { ); } +Widget buildDropdown({ + bool isFormField, + Key buttonKey, + String value = 'two', + ValueChanged onChanged, + VoidCallback onTap, + Widget icon, + Color iconDisabledColor, + Color iconEnabledColor, + double iconSize = 24.0, + bool isDense = false, + bool isExpanded = false, + Widget hint, + Widget disabledHint, + Widget underline, + List items = menuItems, + List Function(BuildContext) selectedItemBuilder, + double itemHeight = kMinInteractiveDimension, + Alignment alignment = Alignment.center, + TextDirection textDirection = TextDirection.ltr, + Size mediaSize, + FocusNode focusNode, + bool autofocus = false, + Color focusColor, + Color dropdownColor, + }) { + final List> listItems = items == null + ? null + : items.map>((String item) { + return DropdownMenuItem( + key: ValueKey(item), + value: item, + child: Text(item, key: ValueKey(item + 'Text')), + ); + }).toList(); + + if (isFormField) { + return Form( + child: DropdownButtonFormField( + key: buttonKey, + value: value, + hint: hint, + disabledHint: disabledHint, + onChanged: onChanged, + onTap: onTap, + icon: icon, + iconSize: iconSize, + iconDisabledColor: iconDisabledColor, + iconEnabledColor: iconEnabledColor, + isDense: isDense, + isExpanded: isExpanded, + // No underline attribute + focusNode: focusNode, + autofocus: autofocus, + focusColor: focusColor, + dropdownColor: dropdownColor, + items: listItems, + selectedItemBuilder: selectedItemBuilder, + itemHeight: itemHeight, + ), + ); + } + return DropdownButton( + key: buttonKey, + value: value, + hint: hint, + disabledHint: disabledHint, + onChanged: onChanged, + onTap: onTap, + icon: icon, + iconSize: iconSize, + iconDisabledColor: iconDisabledColor, + iconEnabledColor: iconEnabledColor, + isDense: isDense, + isExpanded: isExpanded, + underline: underline, + focusNode: focusNode, + autofocus: autofocus, + focusColor: focusColor, + dropdownColor: dropdownColor, + items: listItems, + selectedItemBuilder: selectedItemBuilder, + itemHeight: itemHeight, + ); +} + Widget buildFrame({ Key buttonKey, String value = 'two', @@ -54,6 +140,7 @@ Widget buildFrame({ bool autofocus = false, Color focusColor, Color dropdownColor, + bool isFormField = false, }) { return TestApp( textDirection: textDirection, @@ -62,8 +149,9 @@ Widget buildFrame({ child: Align( alignment: alignment, child: RepaintBoundary( - child: DropdownButton( - key: buttonKey, + child: buildDropdown( + isFormField: isFormField, + buttonKey: buttonKey, value: value, hint: hint, disabledHint: disabledHint, @@ -80,16 +168,9 @@ Widget buildFrame({ autofocus: autofocus, focusColor: focusColor, dropdownColor: dropdownColor, - items: items == null ? null : items.map>((String item) { - return DropdownMenuItem( - key: ValueKey(item), - value: item, - child: Text(item, key: ValueKey(item + 'Text')), - ); - }).toList(), + items: items, selectedItemBuilder: selectedItemBuilder, - itemHeight: itemHeight, - ), + itemHeight: itemHeight,), ), ), ), @@ -176,22 +257,36 @@ void verifyPaintedShadow(Finder customPaint, int elevation) { ); } -Future checkDropdownColor(WidgetTester tester, {Color color}) async { +Future checkDropdownColor(WidgetTester tester, {Color color, bool isFormField = false }) async { const String text = 'foo'; await tester.pumpWidget( MaterialApp( home: Material( - child: DropdownButton( - dropdownColor: color, - value: text, - items: const >[ - DropdownMenuItem( - value: text, - child: Text(text), - ), - ], - onChanged: (_) { }, - ), + child: isFormField + ? Form( + child: DropdownButtonFormField( + dropdownColor: color, + value: text, + items: const >[ + DropdownMenuItem( + value: text, + child: Text(text), + ), + ], + onChanged: (_) {}, + ), + ) + : DropdownButton( + dropdownColor: color, + value: text, + items: const >[ + DropdownMenuItem( + value: text, + child: Text(text), + ), + ], + onChanged: (_) {}, + ), ), ), ); @@ -1815,10 +1910,14 @@ void main() { await checkDropdownColor(tester); }); - testWidgets('DropdownButton uses dropdownColor when expanded when given', (WidgetTester tester) async { + testWidgets('DropdownButton uses dropdownColor when expanded', (WidgetTester tester) async { await checkDropdownColor(tester, color: const Color.fromRGBO(120, 220, 70, 0.8)); }); + testWidgets('DropdownButtonFormField uses dropdownColor when expanded', (WidgetTester tester) async { + await checkDropdownColor(tester, color: const Color.fromRGBO(120, 220, 70, 0.8), isFormField: true); + }); + testWidgets('DropdownButton hint displays properly when selectedItemBuilder is defined', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/42340 final List items = ['1', '2', '3']; @@ -2100,6 +2199,20 @@ void main() { expect(buttonFinder, paints ..rrect(rrect: const RRect.fromLTRBXY(0.0, 0.0, 104.0, 48.0, 4.0, 4.0), color: const Color(0xff00ff00))); }); + testWidgets('DropdownButtonFormField can be focused, and has focusColor', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final UniqueKey buttonKey = UniqueKey(); + final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButtonFormField'); + await tester.pumpWidget(buildFrame(isFormField: true, buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, autofocus: true)); + await tester.pump(); // Pump a frame for autofocus to take effect. + expect(focusNode.hasPrimaryFocus, isTrue); + final Finder buttonFinder = find.descendant(of: find.byKey(buttonKey), matching: find.byType(InputDecorator)); + expect(buttonFinder, paints ..rrect(rrect: const RRect.fromLTRBXY(0.0, 12.0, 800.0, 60.0, 4.0, 4.0), color: const Color(0x1f000000))); + + await tester.pumpWidget(buildFrame(isFormField: true, buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, focusColor: const Color(0xff00ff00))); + expect(buttonFinder, paints ..rrect(rrect: const RRect.fromLTRBXY(0.0, 12.0, 800.0, 60.0, 4.0, 4.0), color: const Color(0xff00ff00))); + }); + testWidgets("DropdownButton won't be focused if not enabled", (WidgetTester tester) async { final UniqueKey buttonKey = UniqueKey(); final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); @@ -2223,7 +2336,7 @@ void main() { final Element element = tester.element(find.byKey(const ValueKey('two')).last); final FocusNode node = Focus.of(element); expect(node.hasFocus, isTrue); - }, skip: kIsWeb); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/55320 testWidgets('Selected element is correctly focused with dropdown that more items than fit on the screen', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); @@ -2286,7 +2399,7 @@ void main() { final Element element = tester.element(find.byKey(const ValueKey(42)).last); final FocusNode node = Focus.of(element); expect(node.hasFocus, isTrue); - }, skip: kIsWeb); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/55320 testWidgets("Having a focused element doesn't interrupt scroll when flung by touch", (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); @@ -2360,7 +2473,7 @@ void main() { // Scrolling to the top again has removed the one the focus was on from the // tree, causing it to lose focus. expect(Focus.of(tester.element(find.byKey(const ValueKey(91)).last)).hasPrimaryFocus, isFalse); - }, skip: kIsWeb); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/55320 testWidgets('DropdownButton onTap callback is called when defined', (WidgetTester tester) async { int dropdownButtonTapCounter = 0; diff --git a/packages/flutter/test/material/expansion_tile_test.dart b/packages/flutter/test/material/expansion_tile_test.dart index 90e9347c80e66..4783bf18ba8c1 100644 --- a/packages/flutter/test/material/expansion_tile_test.dart +++ b/packages/flutter/test/material/expansion_tile_test.dart @@ -229,4 +229,31 @@ void main() { expect(find.text('Subtitle'), findsOneWidget); }); + + testWidgets('ExpansionTile padding test', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp( + home: Material( + child: Center( + child: ExpansionTile( + title: Text('Hello'), + tilePadding: EdgeInsets.fromLTRB(8, 12, 4, 10), + ), + ), + ), + )); + + final Rect titleRect = tester.getRect(find.text('Hello')); + final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more)); + final Rect listTileRect = tester.getRect(find.byType(ListTile)); + final Rect tallerWidget = titleRect.height > trailingRect.height ? titleRect : trailingRect; + + // Check the positions of title and trailing Widgets, after padding is applied. + expect(listTileRect.left, titleRect.left - 8); + expect(listTileRect.right, trailingRect.right + 4); + + // Calculate the remaining height of ListTile from the default height. + final double remainingHeight = 56 - tallerWidget.height; + expect(listTileRect.top, tallerWidget.top - remainingHeight / 2 - 12); + expect(listTileRect.bottom, tallerWidget.bottom + remainingHeight / 2 + 10); + }); } diff --git a/packages/flutter/test/material/flat_button_test.dart b/packages/flutter/test/material/flat_button_test.dart index c211fcc6e1bca..6e8f34237c5d8 100644 --- a/packages/flutter/test/material/flat_button_test.dart +++ b/packages/flutter/test/material/flat_button_test.dart @@ -153,8 +153,8 @@ void main() { await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. await expectLater(tester, meetsGuideline(textContrastGuideline)); }, + skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 semanticsEnabled: true, - skip: isBrowser, ); testWidgets('FlatButton with colored theme meets a11y contrast guidelines', (WidgetTester tester) async { @@ -218,8 +218,8 @@ void main() { await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. await expectLater(tester, meetsGuideline(textContrastGuideline)); }, + skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 semanticsEnabled: true, - skip: isBrowser, ); testWidgets('FlatButton uses stateful color for text color in different states', (WidgetTester tester) async { @@ -571,7 +571,7 @@ void main() { expect(tester.getSize(find.byType(FlatButton)).height, equals(48.0)); expect(tester.getSize(find.byType(Text)).width, isIn([126.0, 127.0])); expect(tester.getSize(find.byType(Text)).height, equals(42.0)); - }, skip: isBrowser); + }); testWidgets('FlatButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { final Key key1 = UniqueKey(); diff --git a/packages/flutter/test/material/flexible_space_bar_test.dart b/packages/flutter/test/material/flexible_space_bar_test.dart index 431f7a47584c2..2c8354716135c 100644 --- a/packages/flutter/test/material/flexible_space_bar_test.dart +++ b/packages/flutter/test/material/flexible_space_bar_test.dart @@ -216,7 +216,7 @@ void main() { await tester.pumpWidget(buildFrame(TargetPlatform.macOS, false)); expect(getTitleBottomLeft(), const Offset(72.0, 16.0)); - }, skip: isBrowser); + }); testWidgets('FlexibleSpaceBar test titlePadding override', (WidgetTester tester) async { Widget buildFrame(TargetPlatform platform, bool centerTitle) { @@ -284,7 +284,7 @@ void main() { await tester.pumpWidget(buildFrame(TargetPlatform.linux, true)); expect(getTitleBottomLeft(), const Offset(390.0, 0.0)); - }, skip: isBrowser); + }); } class TestDelegate extends SliverPersistentHeaderDelegate { diff --git a/packages/flutter/test/material/floating_action_button_location_test.dart b/packages/flutter/test/material/floating_action_button_location_test.dart index 264e7197b9b0c..3a7544fbe9a26 100644 --- a/packages/flutter/test/material/floating_action_button_location_test.dart +++ b/packages/flutter/test/material/floating_action_button_location_test.dart @@ -213,7 +213,7 @@ void main() { ), ); await tester.pumpAndSettle(); - }, skip: isBrowser); + }); testWidgets('interrupting entrance of a new fab.', (WidgetTester tester) async { await tester.pumpWidget( @@ -238,7 +238,7 @@ void main() { ); await tester.pumpAndSettle(); }); - }, skip: isBrowser); + }); }); testWidgets('Docked floating action button locations', (WidgetTester tester) async { @@ -274,7 +274,7 @@ void main() { ); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0)); - }, skip: isBrowser); + }); testWidgets('Docked floating action button locations: no BAB, small BAB', (WidgetTester tester) async { await tester.pumpWidget( @@ -293,7 +293,7 @@ void main() { ), ); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0)); - }, skip: isBrowser); + }); testWidgets('Mini-start-top floating action button location', (WidgetTester tester) async { await tester.pumpWidget( @@ -314,7 +314,7 @@ void main() { ); expect(tester.getCenter(find.byType(FloatingActionButton)).dx, tester.getCenter(find.byType(CircleAvatar)).dx); expect(tester.getCenter(find.byType(FloatingActionButton)).dy, kToolbarHeight); - }, skip: isBrowser); + }); testWidgets('Start-top floating action button location LTR', (WidgetTester tester) async { await tester.pumpWidget( @@ -327,7 +327,7 @@ void main() { ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(16.0, 28.0, 56.0, 56.0))); - }, skip: isBrowser); + }); testWidgets('End-top floating action button location RTL', (WidgetTester tester) async { await tester.pumpWidget( @@ -343,7 +343,7 @@ void main() { ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(16.0, 28.0, 56.0, 56.0))); - }, skip: isBrowser); + }); testWidgets('Start-top floating action button location RTL', (WidgetTester tester) async { await tester.pumpWidget( @@ -359,7 +359,7 @@ void main() { ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0))); - }, skip: isBrowser); + }); testWidgets('End-top floating action button location LTR', (WidgetTester tester) async { await tester.pumpWidget( @@ -372,7 +372,7 @@ void main() { ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0))); - }, skip: isBrowser); + }); } diff --git a/packages/flutter/test/material/ink_paint_test.dart b/packages/flutter/test/material/ink_paint_test.dart index d81a81430f485..01689c16fbbd3 100644 --- a/packages/flutter/test/material/ink_paint_test.dart +++ b/packages/flutter/test/material/ink_paint_test.dart @@ -250,7 +250,7 @@ void main() { expect(box, isNot(paints..circle())); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('The InkWell widget renders an SelectAction or ActivateAction-induced ink ripple', (WidgetTester tester) async { const Color highlightColor = Color(0xAAFF0000); diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 7c84b50cf0997..66e9b6cd75443 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -21,6 +21,7 @@ Widget buildInputDecorator({ TextStyle baseStyle, TextAlignVertical textAlignVertical, VisualDensity visualDensity, + bool fixTextFieldOutlineLabel = false, Widget child = const Text( 'text', style: TextStyle(fontFamily: 'Ahem', fontSize: 16.0), @@ -34,6 +35,7 @@ Widget buildInputDecorator({ data: Theme.of(context).copyWith( inputDecorationTheme: inputDecorationTheme, visualDensity: visualDensity, + fixTextFieldOutlineLabel: fixTextFieldOutlineLabel, ), child: Align( alignment: Alignment.topLeft, @@ -3669,7 +3671,7 @@ void main() { ) ..restore(), ); - }, skip: isBrowser); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/55317 testWidgets('OutlineInputBorder radius carries over when lerping', (WidgetTester tester) async { // This is a regression test for https://github.com/flutter/flutter/issues/23982 @@ -3881,4 +3883,31 @@ void main() { // Ideographic (incorrect) value is 50.299999713897705 expect(tester.getBottomLeft(find.text('hint')).dy, isBrowser ? 45.75 : 47.75); }); + + testWidgets('InputDecorator floating label Y coordinate', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/54028 + await tester.pumpWidget( + buildInputDecorator( + // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028 + // Ensures that the floating label is vertically centered relative to + // center of the top edge of the InputDecorator's outline border. + fixTextFieldOutlineLabel: true, + isEmpty: true, + decoration: const InputDecoration( + labelText: 'label', + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(width: 4), + ), + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + ), + ); + + await tester.pumpAndSettle(); + + // floatingLabelGeight = 12 (ahem font size 16dps * 0.75 = 12) + // labelY = -floatingLabelHeight/2 + borderWidth/2 + expect(tester.getTopLeft(find.text('label')).dy, -4.0); + }); + } diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index b73571539f7fb..f91c98d49e62d 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -194,7 +194,7 @@ void main() { testChildren(); testHorizontalGeometry(); testVerticalGeometry(128.0); - }, skip: isBrowser); + }); testWidgets('ListTile geometry (RTL)', (WidgetTester tester) async { const double leftPadding = 10.0; @@ -806,7 +806,7 @@ void main() { expect(tester.getRect(find.byType(ListTile).at(1)), const Rect.fromLTWH( 0.0, 216.0 , 800.0, 56.0)); expect(tester.getRect(find.byType(Placeholder).at(2)), const Rect.fromLTWH( 16.0, 216.0 + 16.0, 24.0, 12.0)); expect(tester.getRect(find.byType(Placeholder).at(3)), const Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0, 24.0, 24.0)); - }, skip: isBrowser); + }); testWidgets('ListTile leading icon height does not exceed ListTile height', (WidgetTester tester) async { // regression test for https://github.com/flutter/flutter/issues/28765 diff --git a/packages/flutter/test/material/material_button_test.dart b/packages/flutter/test/material/material_button_test.dart index bba878603aa26..7bd183eaee549 100644 --- a/packages/flutter/test/material/material_button_test.dart +++ b/packages/flutter/test/material/material_button_test.dart @@ -260,8 +260,8 @@ void main() { await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. await expectLater(tester, meetsGuideline(textContrastGuideline)); }, + skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 semanticsEnabled: true, - skip: isBrowser, ); testWidgets('MaterialButton gets focus when autofocus is set.', (WidgetTester tester) async { @@ -596,7 +596,7 @@ void main() { semantics.dispose(); - }, skip: isBrowser); + }); testWidgets('MaterialButton minWidth and height parameters', (WidgetTester tester) async { Widget buildFrame({ double minWidth, double height, EdgeInsets padding = EdgeInsets.zero, Widget child }) { diff --git a/packages/flutter/test/material/outline_button_test.dart b/packages/flutter/test/material/outline_button_test.dart index 26f31694a95bc..cd1aecb406f86 100644 --- a/packages/flutter/test/material/outline_button_test.dart +++ b/packages/flutter/test/material/outline_button_test.dart @@ -200,8 +200,8 @@ void main() { await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. await expectLater(tester, meetsGuideline(textContrastGuideline)); }, + skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 semanticsEnabled: true, - skip: isBrowser, ); testWidgets('OutlineButton with colored theme meets a11y contrast guidelines', (WidgetTester tester) async { @@ -265,7 +265,7 @@ void main() { await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. await expectLater(tester, meetsGuideline(textContrastGuideline)); }, - skip: isBrowser, + skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 semanticsEnabled: true, ); @@ -747,7 +747,7 @@ void main() { clipPath: clipPath, clipRect: clipRect, ); - }, skip: isBrowser); + }); testWidgets('OutlineButton has no clip by default', (WidgetTester tester) async { final GlobalKey buttonKey = GlobalKey(); @@ -882,7 +882,7 @@ void main() { expect(tester.getSize(find.byType(FlatButton)).height, equals(48.0)); expect(tester.getSize(find.byType(Text)).width, isIn([126.0, 127.0])); expect(tester.getSize(find.byType(Text)).height, equals(42.0)); - }, skip: isBrowser); + }); testWidgets('OutlineButton pressed fillColor default', (WidgetTester tester) async { Widget buildFrame(ThemeData theme) { diff --git a/packages/flutter/test/material/paginated_data_table_test.dart b/packages/flutter/test/material/paginated_data_table_test.dart index 90bbe330ad9c6..d2cbcf8f84f61 100644 --- a/packages/flutter/test/material/paginated_data_table_test.dart +++ b/packages/flutter/test/material/paginated_data_table_test.dart @@ -262,7 +262,7 @@ void main() { expect(find.text('501'), findsOneWidget); // Test that it fits: expect(tester.getTopRight(find.text('501')).dx, greaterThanOrEqualTo(tester.getTopRight(find.text('Rows per page:')).dx + 40.0)); - }, skip: isBrowser); // TODO(yjbanov): https://github.com/flutter/flutter/issues/43433 + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/43433 testWidgets('PaginatedDataTable footer scrolls', (WidgetTester tester) async { final TestDataSource source = TestDataSource(); diff --git a/packages/flutter/test/material/raised_button_test.dart b/packages/flutter/test/material/raised_button_test.dart index 9bfd9a58c8f6d..cca0418145e10 100644 --- a/packages/flutter/test/material/raised_button_test.dart +++ b/packages/flutter/test/material/raised_button_test.dart @@ -155,7 +155,7 @@ void main() { await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. await expectLater(tester, meetsGuideline(textContrastGuideline)); }, - skip: isBrowser, + skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 semanticsEnabled: true, ); diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index afcaebef25868..4e1dbb3fdc4eb 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -1840,7 +1840,7 @@ void main() { final GlobalKey key = GlobalKey(); const Key buttonKey = Key('button'); final List errors = []; - FlutterError.onError = (FlutterErrorDetails error) => errors.add(error); + FlutterError.presentError = (FlutterErrorDetails error) => errors.add(error); int state = 0; await tester.pumpWidget( MaterialApp( diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index 4fdebc151a035..329a769e72fc0 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -1340,7 +1340,7 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); - }, skip: isBrowser); + }); testWidgets('Slider respects textScaleFactor', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); @@ -1439,7 +1439,7 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); - }, skip: isBrowser); + }); testWidgets('Tick marks are skipped when they are too dense', (WidgetTester tester) async { Widget buildSlider({ diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index fbfe6732367aa..e8a6571b4c86c 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -607,7 +607,7 @@ void main() { ) ); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('Default paddle slider value indicator shape draws correctly', (WidgetTester tester) async { final ThemeData theme = ThemeData( @@ -784,7 +784,7 @@ void main() { ), ); await gesture.up(); - }, skip: isBrowser); + }); testWidgets('The slider track height can be overridden', (WidgetTester tester) async { final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(trackHeight: 16); diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart index f3889286d3550..c90ea6d79f06a 100644 --- a/packages/flutter/test/material/snack_bar_test.dart +++ b/packages/flutter/test/material/snack_bar_test.dart @@ -468,7 +468,7 @@ void main() { expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0); expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0 + 40.0); // margin + bottom padding - }, skip: isBrowser); + }); testWidgets( 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed', @@ -523,9 +523,7 @@ void main() { expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0); expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0); // margin (with no bottom padding) - }, - skip: isBrowser, - ); + }); testWidgets('SnackBar should push FloatingActionButton above', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( @@ -630,7 +628,7 @@ void main() { expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0); expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 31.0 + 30.0); // margin + right padding expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 27.0); // margin (with no bottom padding) - }, skip: isBrowser); + }); testWidgets( 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating', @@ -688,9 +686,7 @@ void main() { expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0); expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 31.0 + 30.0); // margin + right padding expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 27.0); // margin (with no bottom padding) - }, - skip: isBrowser, - ); + }); testWidgets('SnackBarClosedReason', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index bba9e19ad5f79..6716bfe362c95 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -249,7 +249,7 @@ void main() { ); expect(tester.renderObject(find.byType(RichText)).text.style.fontFamily, 'Ahem'); expect(tester.getSize(find.byType(Tab)), const Size(14.0, 46.0)); - }, skip: isBrowser); + }); testWidgets('Tab sizing - icon and text', (WidgetTester tester) async { await tester.pumpWidget( @@ -257,7 +257,7 @@ void main() { ); expect(tester.renderObject(find.byType(RichText)).text.style.fontFamily, 'Ahem'); expect(tester.getSize(find.byType(Tab)), const Size(14.0, 72.0)); - }, skip: isBrowser); + }); testWidgets('Tab sizing - icon, iconMargin and text', (WidgetTester tester) async { await tester.pumpWidget( @@ -281,7 +281,7 @@ void main() { ); expect(tester.renderObject(find.byType(RichText)).text.style.fontFamily, 'Ahem'); expect(tester.getSize(find.byType(Tab)), const Size(210.0, 72.0)); - }, skip: isBrowser); + }); testWidgets('Tab sizing - icon and child', (WidgetTester tester) async { await tester.pumpWidget( @@ -289,7 +289,7 @@ void main() { ); expect(tester.renderObject(find.byType(RichText)).text.style.fontFamily, 'Ahem'); expect(tester.getSize(find.byType(Tab)), const Size(14.0, 72.0)); - }, skip: isBrowser); + }); testWidgets('Tab color - normal', (WidgetTester tester) async { final Widget tabBar = TabBar(tabs: const [SizedBox.shrink()], controller: TabController(length: 1, vsync: tester)); @@ -412,7 +412,7 @@ void main() { // Scrolling the TabBar doesn't change the selection expect(controller.index, 0); - }, skip: isBrowser); + }); testWidgets('TabBarView maintains state', (WidgetTester tester) async { final List tabs = ['AAAAAA', 'BBBBBB', 'CCCCCC', 'DDDDDD', 'EEEEEE']; @@ -1671,7 +1671,7 @@ void main() { expect(semantics, hasSemantics(expectedSemantics)); semantics.dispose(); - }, skip: isBrowser); + }); testWidgets('correct scrolling semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); @@ -1939,7 +1939,7 @@ void main() { expect(semantics, hasSemantics(expectedSemantics)); semantics.dispose(); - }, skip: isBrowser); + }); testWidgets('can be notified of TabBar onTap behavior', (WidgetTester tester) async { int tabIndex = -1; diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 256e444f29594..bcaa972f43c41 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -447,7 +447,7 @@ void main() { find.byType(Overlay), matchesGoldenFile('text_field_opacity_test.0.png'), ); - }, skip: isBrowser); + }); testWidgets('text field toolbar options correctly changes options', (WidgetTester tester) async { @@ -496,7 +496,9 @@ void main() { expect(find.text('Copy'), findsOneWidget); expect(find.text('Cut'), findsNothing); expect(find.text('Select All'), findsNothing); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + ); testWidgets('text selection style 1', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( @@ -625,7 +627,14 @@ void main() { expect(find.text('COPY'), findsOneWidget); expect(find.text('CUT'), findsNothing); expect(find.text('SELECT ALL'), findsNothing); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows })); + }, + variant: const TargetPlatformVariant({ + TargetPlatform.android, + TargetPlatform.fuchsia, + TargetPlatform.linux, + TargetPlatform.windows, + }), + ); testWidgets('cursor layout has correct width', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; @@ -2917,7 +2926,7 @@ void main() { // and the left edge of the input and label. expect(iconRight + 28.0, equals(tester.getTopLeft(find.text('label')).dx)); expect(iconRight + 28.0, equals(tester.getTopLeft(find.byType(EditableText)).dx)); - }, skip: isBrowser); + }); testWidgets('Collapsed hint text placement', (WidgetTester tester) async { await tester.pumpWidget( @@ -7408,6 +7417,48 @@ void main() { final RenderBox renderBox = tester.renderObject(find.byType(TextField)); expect(renderBox.size.height, lessThan(kMinInteractiveDimension)); }); + + group('intrinsics', () { + Widget _buildTest({ bool isDense }) { + return MaterialApp( + home: Scaffold( + body: CustomScrollView( + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Column( + children: [ + TextField( + decoration: InputDecoration( + isDense: isDense, + ) + ), + Container( + height: 1000, + ), + ], + ) + ) + ], + ) + ) + ); + } + + testWidgets('By default, intrinsic height is at least kMinInteractiveDimension high', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/54729 + // If the intrinsic height does not match that of the height after + // performLayout, this will fail. + tester.pumpWidget(_buildTest(isDense: false)); + }); + + testWidgets('When isDense, intrinsic height can go below kMinInteractiveDimension height', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/54729 + // If the intrinsic height does not match that of the height after + // performLayout, this will fail. + tester.pumpWidget(_buildTest(isDense: true)); + }); + }); }); testWidgets("Arrow keys don't move input focus", (WidgetTester tester) async { final TextEditingController controller1 = TextEditingController(); diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index 779ea790589e5..02a84b6184bbb 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -303,7 +303,7 @@ void main() { await tester.pump(const Duration(milliseconds: 200)); expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0)); - }, skip: isBrowser); // we do not use Flutter-rendered context menu on the Web + }, skip: isBrowser); // We do not use Flutter-rendered context menu on the Web testWidgets('onTap is called upon tap', (WidgetTester tester) async { int tapCount = 0; diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart index 366e86dcd2786..34a0fa596624a 100644 --- a/packages/flutter/test/material/text_selection_test.dart +++ b/packages/flutter/test/material/text_selection_test.dart @@ -123,7 +123,10 @@ void main() { expect(find.text('PASTE'), findsOneWidget); expect(find.text('SELECT ALL'), findsOneWidget); expect(find.byType(IconButton), findsNothing); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android })); + }, + skip: isBrowser, // We do not use Flutter-rendered context menu on the Web + variant: const TargetPlatformVariant({ TargetPlatform.android }), + ); testWidgets('When menu items don\'t fit, an overflow menu is used.', (WidgetTester tester) async { // Set the screen size to more narrow, so that SELECT ALL can't fit. @@ -194,7 +197,10 @@ void main() { expect(find.text('PASTE'), findsOneWidget); expect(find.text('SELECT ALL'), findsNothing); expect(find.byType(IconButton), findsOneWidget); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android })); + }, + skip: isBrowser, // We do not use Flutter-rendered context menu on the Web + variant: const TargetPlatformVariant({ TargetPlatform.android }), + ); testWidgets('A smaller menu bumps more items to the overflow menu.', (WidgetTester tester) async { // Set the screen size so narrow that only CUT and COPY can fit. @@ -256,7 +262,10 @@ void main() { expect(find.text('PASTE'), findsNothing); expect(find.text('SELECT ALL'), findsNothing); expect(find.byType(IconButton), findsOneWidget); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android })); + }, + skip: isBrowser, // We do not use Flutter-rendered context menu on the Web + variant: const TargetPlatformVariant({ TargetPlatform.android }), + ); testWidgets('When the menu renders below the text, the overflow menu back button is at the top.', (WidgetTester tester) async { // Set the screen size to more narrow, so that SELECT ALL can't fit. @@ -327,7 +336,10 @@ void main() { expect(find.text('PASTE'), findsOneWidget); expect(find.text('SELECT ALL'), findsNothing); expect(find.byType(IconButton), findsOneWidget); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android })); + }, + skip: isBrowser, // We do not use Flutter-rendered context menu on the Web + variant: const TargetPlatformVariant({ TargetPlatform.android }), + ); testWidgets('When the menu items change, the menu is closed and _closedWidth reset.', (WidgetTester tester) async { // Set the screen size to more narrow, so that SELECT ALL can't fit. @@ -430,7 +442,10 @@ void main() { expect(find.byType(IconButton), findsNothing); final Offset newCutOffset = tester.getTopLeft(find.text('CUT')); expect(newCutOffset, equals(cutOffset)); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android })); + }, + skip: isBrowser, // We do not use Flutter-rendered context menu on the Web + variant: const TargetPlatformVariant({ TargetPlatform.android }), + ); }); group('menu position', () { @@ -501,7 +516,10 @@ void main() { final Offset bottomHandlePos = endpoints[1].point; final Offset cutOffset = tester.getTopLeft(find.text('CUT')); expect(cutOffset.dy, greaterThan(bottomHandlePos.dy)); - }, skip: isBrowser, variant: const TargetPlatformVariant({ TargetPlatform.android })); + }, + skip: isBrowser, // We do not use Flutter-rendered context menu on the Web + variant: const TargetPlatformVariant({ TargetPlatform.android }), + ); }); group('material handles', () { diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index cd7ba44bd62c9..862fda8a08115 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -281,6 +281,7 @@ void main() { dividerTheme: const DividerThemeData(color: Colors.black), buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.start), bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed), + fixTextFieldOutlineLabel: false, ); final SliderThemeData otherSliderTheme = SliderThemeData.fromPrimaryColors( @@ -362,6 +363,7 @@ void main() { dividerTheme: const DividerThemeData(color: Colors.white), buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.end), bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.shifting), + fixTextFieldOutlineLabel: true, ); final ThemeData themeDataCopy = theme.copyWith( @@ -429,6 +431,7 @@ void main() { dividerTheme: otherTheme.dividerTheme, buttonBarTheme: otherTheme.buttonBarTheme, bottomNavigationBarTheme: otherTheme.bottomNavigationBarTheme, + fixTextFieldOutlineLabel: otherTheme.fixTextFieldOutlineLabel, ); expect(themeDataCopy.brightness, equals(otherTheme.brightness)); @@ -497,6 +500,7 @@ void main() { expect(themeDataCopy.dividerTheme, equals(otherTheme.dividerTheme)); expect(themeDataCopy.buttonBarTheme, equals(otherTheme.buttonBarTheme)); expect(themeDataCopy.bottomNavigationBarTheme, equals(otherTheme.bottomNavigationBarTheme)); + expect(themeDataCopy.fixTextFieldOutlineLabel, equals(otherTheme.fixTextFieldOutlineLabel)); }); testWidgets('ThemeData.toString has less than 200 characters output', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 24da03de4f9e1..b3667e7d82dc9 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -149,7 +149,7 @@ void main() { ); expect(tip.size.height, equals(24.0)); // 14.0 height + 5.0 padding * 2 (top, bottom) expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)), equals(const Offset(10.0, 20.0))); - }, skip: isBrowser); + }); testWidgets('Does tooltip end up in the right place - center prefer above fits', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -384,7 +384,7 @@ void main() { expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(310.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dx, equals(790.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(324.0)); - }, skip: isBrowser); + }); testWidgets('Does tooltip end up in the right place - near the edge', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -441,7 +441,7 @@ void main() { expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(310.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dx, equals(790.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(324.0)); - }, skip: isBrowser); + }); testWidgets('Custom tooltip margin', (WidgetTester tester) async { const double _customMarginValue = 10.0; @@ -677,7 +677,7 @@ void main() { rrect: RRect.fromRectAndRadius(tip.paintBounds, const Radius.circular(4.0)), color: const Color(0xe6616161), )); - }, skip: isBrowser); + }); testWidgets('Can tooltip decoration be customized', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -718,7 +718,7 @@ void main() { expect(tip, paints..path( color: const Color(0x80800000), )); - }, skip: isBrowser); + }); testWidgets('Tooltip stays after long press', (WidgetTester tester) async { await tester.pumpWidget( @@ -946,7 +946,7 @@ void main() { _findTooltipContainer(tooltipText), ); expect(tip.size.height, equals(56.0)); - }, skip: isBrowser); + }); testWidgets('Haptic feedback', (WidgetTester tester) async { final FeedbackTester feedback = FeedbackTester(); diff --git a/packages/flutter/test/material/tooltip_theme_test.dart b/packages/flutter/test/material/tooltip_theme_test.dart index 0be8543986ffd..c948f3d7ebd71 100644 --- a/packages/flutter/test/material/tooltip_theme_test.dart +++ b/packages/flutter/test/material/tooltip_theme_test.dart @@ -684,7 +684,7 @@ void main() { expect(tip, paints..path( color: const Color(0x80800000), )); - }, skip: isBrowser); + }); testWidgets('Tooltip decoration - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -726,7 +726,7 @@ void main() { expect(tip, paints..path( color: const Color(0x80800000), )); - }, skip: isBrowser); + }); testWidgets('Tooltip height and padding - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -773,7 +773,7 @@ void main() { expect(tip.size.height, equals(customTooltipHeight)); expect(content.size.height, equals(customTooltipHeight - 2 * customPaddingVal)); expect(content.size.width, equals(tip.size.width - 2 * customPaddingVal)); - }, skip: isBrowser); + }); testWidgets('Tooltip height and padding - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -818,7 +818,7 @@ void main() { expect(tip.size.height, equals(customTooltipHeight)); expect(content.size.height, equals(customTooltipHeight - 2 * customPaddingValue)); expect(content.size.width, equals(tip.size.width - 2 * customPaddingValue)); - }, skip: isBrowser); + }); testWidgets('Tooltip waitDuration - ThemeData.tooltipTheme', (WidgetTester tester) async { const Duration customWaitDuration = Duration(milliseconds: 500); diff --git a/packages/flutter/test/painting/image_stream_test.dart b/packages/flutter/test/painting/image_stream_test.dart index 1e928589168cb..7caf72f693bc5 100644 --- a/packages/flutter/test/painting/image_stream_test.dart +++ b/packages/flutter/test/painting/image_stream_test.dart @@ -78,6 +78,17 @@ class MockCodec implements Codec { } +class FakeEventReportingImageStreamCompleter extends ImageStreamCompleter { + FakeEventReportingImageStreamCompleter({Stream chunkEvents,}) { + if (chunkEvents != null) { + chunkEvents.listen((ImageChunkEvent event) { + reportImageChunkEvent(event); + }, + ); + } + } +} + void main() { testWidgets('Codec future fails', (WidgetTester tester) async { final Completer completer = Completer(); @@ -128,7 +139,54 @@ void main() { expect(mockCodec.numFramesAsked, 1); }); - testWidgets('Chunk events are delivered', (WidgetTester tester) async { + testWidgets('Chunk events of base ImageStreamCompleter are delivered', (WidgetTester tester) async { + final List chunkEvents = []; + final StreamController streamController = StreamController(); + final ImageStreamCompleter imageStream = FakeEventReportingImageStreamCompleter( + chunkEvents: streamController.stream, + ); + + imageStream.addListener(ImageStreamListener( + (ImageInfo image, bool synchronousCall) { }, + onChunk: (ImageChunkEvent event) { + chunkEvents.add(event); + }, + )); + streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 1, expectedTotalBytes: 3)); + streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 2, expectedTotalBytes: 3)); + await tester.idle(); + + expect(chunkEvents.length, 2); + expect(chunkEvents[0].cumulativeBytesLoaded, 1); + expect(chunkEvents[0].expectedTotalBytes, 3); + expect(chunkEvents[1].cumulativeBytesLoaded, 2); + expect(chunkEvents[1].expectedTotalBytes, 3); + }); + + testWidgets('Chunk events of base ImageStreamCompleter are not buffered before listener registration', (WidgetTester tester) async { + final List chunkEvents = []; + final StreamController streamController = StreamController(); + final ImageStreamCompleter imageStream = FakeEventReportingImageStreamCompleter( + chunkEvents: streamController.stream, + ); + + streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 1, expectedTotalBytes: 3)); + await tester.idle(); + imageStream.addListener(ImageStreamListener( + (ImageInfo image, bool synchronousCall) { }, + onChunk: (ImageChunkEvent event) { + chunkEvents.add(event); + }, + )); + streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 2, expectedTotalBytes: 3)); + await tester.idle(); + + expect(chunkEvents.length, 1); + expect(chunkEvents[0].cumulativeBytesLoaded, 2); + expect(chunkEvents[0].expectedTotalBytes, 3); + }); + + testWidgets('Chunk events of MultiFrameImageStreamCompleter are delivered', (WidgetTester tester) async { final List chunkEvents = []; final Completer completer = Completer(); final StreamController streamController = StreamController(); @@ -155,7 +213,7 @@ void main() { expect(chunkEvents[1].expectedTotalBytes, 3); }); - testWidgets('Chunk events are not buffered before listener registration', (WidgetTester tester) async { + testWidgets('Chunk events of MultiFrameImageStreamCompleter are not buffered before listener registration', (WidgetTester tester) async { final List chunkEvents = []; final Completer completer = Completer(); final StreamController streamController = StreamController(); diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index 297ed7d8281eb..48d793b40ca3a 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -14,10 +14,7 @@ import 'rendering_tester.dart'; class FakeEditableTextState with TextSelectionDelegate { @override - TextEditingValue get textEditingValue { return const TextEditingValue(); } - - @override - set textEditingValue(TextEditingValue value) { } + TextEditingValue textEditingValue = const TextEditingValue(); @override void hideToolbar() { } @@ -701,4 +698,102 @@ void main() { editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0))); expect(editable.maxScrollExtent, equals(10)); }, skip: isBrowser); // TODO(yjbanov): https://github.com/flutter/flutter/issues/42772 + + test('arrow keys and delete handle simple text correctly', () async { + final TextSelectionDelegate delegate = FakeEditableTextState(); + final ViewportOffset viewportOffset = ViewportOffset.zero(); + TextSelection currentSelection; + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + textDirection: TextDirection.ltr, + cursorColor: Colors.red, + offset: viewportOffset, + textSelectionDelegate: delegate, + onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) { + currentSelection = selection; + }, + startHandleLayerLink: LayerLink(), + endHandleLayerLink: LayerLink(), + text: const TextSpan( + text: 'test', + style: TextStyle( + height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', + ), + ), + selection: const TextSelection.collapsed( + offset: 0, + ), + ); + + layout(editable); + editable.hasFocus = true; + + editable.selectPositionAt(from: const Offset(0, 0), cause: SelectionChangedCause.tap); + editable.selection = const TextSelection.collapsed(offset: 0); + pumpFrame(); + + await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android'); + await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android'); + expect(currentSelection.isCollapsed, true); + expect(currentSelection.baseOffset, 1); + + await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android'); + await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android'); + expect(currentSelection.isCollapsed, true); + expect(currentSelection.baseOffset, 0); + + await simulateKeyDownEvent(LogicalKeyboardKey.delete, platform: 'android'); + await simulateKeyUpEvent(LogicalKeyboardKey.delete, platform: 'android'); + expect(delegate.textEditingValue.text, 'est'); + }, skip: kIsWeb); + + test('arrow keys and delete handle surrogate pairs correctly', () async { + final TextSelectionDelegate delegate = FakeEditableTextState(); + final ViewportOffset viewportOffset = ViewportOffset.zero(); + TextSelection currentSelection; + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + textDirection: TextDirection.ltr, + cursorColor: Colors.red, + offset: viewportOffset, + textSelectionDelegate: delegate, + onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) { + currentSelection = selection; + }, + startHandleLayerLink: LayerLink(), + endHandleLayerLink: LayerLink(), + text: const TextSpan( + text: '\u{1F44D}', // Thumbs up + style: TextStyle( + height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', + ), + ), + selection: const TextSelection.collapsed( + offset: 0, + ), + ); + + layout(editable); + editable.hasFocus = true; + + editable.selectPositionAt(from: const Offset(0, 0), cause: SelectionChangedCause.tap); + editable.selection = const TextSelection.collapsed(offset: 0); + pumpFrame(); + + await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android'); + await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android'); + expect(currentSelection.isCollapsed, true); + expect(currentSelection.baseOffset, 2); + + await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android'); + await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android'); + expect(currentSelection.isCollapsed, true); + expect(currentSelection.baseOffset, 0); + + await simulateKeyDownEvent(LogicalKeyboardKey.delete, platform: 'android'); + await simulateKeyUpEvent(LogicalKeyboardKey.delete, platform: 'android'); + expect(delegate.textEditingValue.text, ''); + }, skip: kIsWeb); // Key simulation doesn't work on web. } diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index 60dbb5f92871f..6a49841d6ddb2 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -493,6 +493,61 @@ void main() { box.translation = const Offset(0.3, 0.3); expect(box.markNeedsSemanticsUpdateCallCount, 3); }); + + test('RenderFollowerLayer hit test without a leader layer and the showWhenUnlinked is true', () { + final RenderFollowerLayer follower = RenderFollowerLayer( + link: LayerLink(), + showWhenUnlinked: true, + child: RenderSizedBox(const Size(1.0, 1.0)), + ); + layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); + final BoxHitTestResult hitTestResult = BoxHitTestResult(); + expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isTrue); + }); + + test('RenderFollowerLayer hit test without a leader layer and the showWhenUnlinked is false', () { + final RenderFollowerLayer follower = RenderFollowerLayer( + link: LayerLink(), + showWhenUnlinked: false, + child: RenderSizedBox(const Size(1.0, 1.0)), + ); + layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); + final BoxHitTestResult hitTestResult = BoxHitTestResult(); + expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isFalse); + }); + + test('RenderFollowerLayer hit test with a leader layer and the showWhenUnlinked is true', () { + // Creates a layer link with a leader. + final LayerLink link = LayerLink(); + final LeaderLayer leader = LeaderLayer(link: link); + leader.attach(Object()); + + final RenderFollowerLayer follower = RenderFollowerLayer( + link: link, + showWhenUnlinked: true, + child: RenderSizedBox(const Size(1.0, 1.0)), + ); + layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); + final BoxHitTestResult hitTestResult = BoxHitTestResult(); + expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isTrue); + }); + + test('RenderFollowerLayer hit test with a leader layer and the showWhenUnlinked is false', () { + // Creates a layer link with a leader. + final LayerLink link = LayerLink(); + final LeaderLayer leader = LeaderLayer(link: link); + leader.attach(Object()); + + final RenderFollowerLayer follower = RenderFollowerLayer( + link: link, + showWhenUnlinked: false, + child: RenderSizedBox(const Size(1.0, 1.0)), + ); + layout(follower, constraints: BoxConstraints.tight(const Size(200.0, 200.0))); + final BoxHitTestResult hitTestResult = BoxHitTestResult(); + // The follower is still hit testable because there is a leader layer. + expect(follower.hitTest(hitTestResult, position: const Offset(0.0, 0.0)), isTrue); + }); } class _TestRectClipper extends CustomClipper { diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart new file mode 100644 index 0000000000000..e170ab05a2a99 --- /dev/null +++ b/packages/flutter/test/services/autofill_test.dart @@ -0,0 +1,238 @@ +// Copyright 2014 The Flutter 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:convert' show utf8; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('TextInput message channels', () { + FakeTextChannel fakeTextChannel; + FakeAutofillScope scope; + + setUp(() { + fakeTextChannel = FakeTextChannel((MethodCall call) async {}); + TextInput.setChannel(fakeTextChannel); + scope ??= FakeAutofillScope(); + scope.clients.clear(); + }); + + tearDown(() { + TextInputConnection.debugResetId(); + TextInput.setChannel(SystemChannels.textInput); + }); + + test('mandatory fields are mandatory', () async { + AutofillConfiguration config; + try { + config = AutofillConfiguration( + uniqueIdentifier: null, + autofillHints: const ['test'], + ); + } catch (e) { + expect(e.toString(), contains('uniqueIdentifier != null')); + } + + expect(config, isNull); + + try { + config = AutofillConfiguration( + uniqueIdentifier: 'id', + autofillHints: null, + ); + } catch (e) { + expect(e.toString(), contains('autofillHints != null')); + } + + expect(config, isNull); + }); + + test('throws if the hint list is empty', () async { + Map json; + try { + const AutofillConfiguration config = AutofillConfiguration( + uniqueIdentifier: 'id', + autofillHints: [], + ); + + json = config.toJson(); + } catch (e) { + expect(e.toString(), contains('isNotEmpty')); + } + + expect(json, isNull); + }); + + test( + 'AutofillClients send the correct configuration to the platform' + 'and responds to updateEditingStateWithTag method correctly', + () async { + final FakeAutofillClient client1 = FakeAutofillClient(const TextEditingValue(text: 'test1')); + final FakeAutofillClient client2 = FakeAutofillClient(const TextEditingValue(text: 'test2')); + + client1.textInputConfiguration = TextInputConfiguration( + autofillConfiguration: AutofillConfiguration( + uniqueIdentifier: client1.autofillId, + autofillHints: const ['client1'], + currentEditingValue: client1.currentTextEditingValue, + ), + ); + + client2.textInputConfiguration = TextInputConfiguration( + autofillConfiguration: AutofillConfiguration( + uniqueIdentifier: client2.autofillId, + autofillHints: const ['client2'], + currentEditingValue: client2.currentTextEditingValue, + ), + ); + + scope.register(client1); + scope.register(client2); + client1.currentAutofillScope = scope; + client2.currentAutofillScope = scope; + + scope.attach(client1, client1.textInputConfiguration); + + final Map expectedConfiguration = client1.textInputConfiguration.toJson(); + expectedConfiguration['fields'] = >[ + client1.textInputConfiguration.toJson(), + client2.textInputConfiguration.toJson(), + ]; + + fakeTextChannel.validateOutgoingMethodCalls([ + MethodCall('TextInput.setClient', [1, expectedConfiguration]), + ]); + + const TextEditingValue text2 = TextEditingValue(text: 'Text 2'); + fakeTextChannel.incoming(MethodCall( + 'TextInputClient.updateEditingStateWithTag', + [0, { client2.autofillId : text2.toJSON() }], + )); + + expect(client2.currentTextEditingValue, text2); + }); + }); +} + +class FakeAutofillClient implements TextInputClient, AutofillClient { + FakeAutofillClient(this.currentTextEditingValue); + + @override + String get autofillId => hashCode.toString(); + + @override + TextInputConfiguration textInputConfiguration; + + @override + void updateEditingValue(TextEditingValue newEditingValue) { + currentTextEditingValue = newEditingValue; + latestMethodCall = 'updateEditingValue'; + } + + @override + AutofillScope currentAutofillScope; + + String latestMethodCall = ''; + + @override + TextEditingValue currentTextEditingValue; + + @override + void performAction(TextInputAction action) { + latestMethodCall = 'performAction'; + } + + @override + void updateFloatingCursor(RawFloatingCursorPoint point) { + latestMethodCall = 'updateFloatingCursor'; + } + + @override + void connectionClosed() { + latestMethodCall = 'connectionClosed'; + } + + @override + void showAutocorrectionPromptRect(int start, int end) { + latestMethodCall = 'showAutocorrectionPromptRect'; + } +} + +class FakeAutofillScope with AutofillScopeMixin implements AutofillScope { + final Map clients = {}; + + @override + Iterable get autofillClients => clients.values; + + @override + AutofillClient getAutofillClient(String autofillId) => clients[autofillId]; + + void register(AutofillClient client) { + clients.putIfAbsent(client.autofillId, () => client); + } +} + +class FakeTextChannel implements MethodChannel { + FakeTextChannel(this.outgoing) : assert(outgoing != null); + + Future Function(MethodCall) outgoing; + Future Function(MethodCall) incoming; + + List outgoingCalls = []; + + @override + BinaryMessenger get binaryMessenger => throw UnimplementedError(); + + @override + MethodCodec get codec => const JSONMethodCodec(); + + @override + Future> invokeListMethod(String method, [dynamic arguments]) => throw UnimplementedError(); + + @override + Future> invokeMapMethod(String method, [dynamic arguments]) => throw UnimplementedError(); + + @override + Future invokeMethod(String method, [dynamic arguments]) async { + final MethodCall call = MethodCall(method, arguments); + outgoingCalls.add(call); + return await outgoing(call) as T; + } + + @override + String get name => 'flutter/textinput'; + + @override + void setMethodCallHandler(Future Function(MethodCall call) handler) { + incoming = handler; + } + + @override + void setMockMethodCallHandler(Future Function(MethodCall call) handler) => throw UnimplementedError(); + + void validateOutgoingMethodCalls(List calls) { + expect(outgoingCalls.length, calls.length); + bool hasError = false; + for (int i = 0; i < calls.length; i++) { + final ByteData outgoingData = codec.encodeMethodCall(outgoingCalls[i]); + final ByteData expectedData = codec.encodeMethodCall(calls[i]); + final String outgoingString = utf8.decode(outgoingData.buffer.asUint8List()); + final String expectedString = utf8.decode(expectedData.buffer.asUint8List()); + + if (outgoingString != expectedString) { + print( + 'Index $i did not match:\n' + ' actual: ${outgoingCalls[i]}\n' + ' expected: ${calls[i]}'); + hasError = true; + } + } + if (hasError) { + fail('Calls did not match.'); + } + } +} diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index 380b909fcf7ec..cb6020f6dbbb0 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -200,6 +200,9 @@ class FakeTextInputClient implements TextInputClient { @override TextEditingValue currentTextEditingValue; + @override + AutofillScope get currentAutofillScope => null; + @override void performAction(TextInputAction action) { latestMethodCall = 'performAction'; diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart index 65e98261cacd3..728f6b2c64482 100644 --- a/packages/flutter/test/widgets/actions_test.dart +++ b/packages/flutter/test/widgets/actions_test.dart @@ -30,6 +30,8 @@ class TestAction extends CallbackAction { super(onInvoke: onInvoke); @override + bool isEnabled(TestIntent intent) => enabled; + bool get enabled => _enabled; bool _enabled = true; set enabled(bool value) { @@ -412,17 +414,17 @@ void main() { }, ); bool enabled1 = true; - action1.addActionListener((Action action) => enabled1 = action.enabled); + action1.addActionListener((Action action) => enabled1 = action.isEnabled(const TestIntent())); action1.enabled = false; expect(enabled1, isFalse); bool enabled2 = true; - action2.addActionListener((Action action) => enabled2 = action.enabled); + action2.addActionListener((Action action) => enabled2 = action.isEnabled(const SecondTestIntent())); action2.enabled = false; expect(enabled2, isFalse); bool enabled3 = true; - action3.addActionListener((Action action) => enabled3 = action.enabled); + action3.addActionListener((Action action) => enabled3 = action.isEnabled(const ThirdTestIntent())); action3.enabled = false; expect(enabled3, isFalse); @@ -466,7 +468,7 @@ void main() { SecondTestIntent: action2, }, child: ActionListener( - listener: (Action action) => enabledChanged = action.enabled, + listener: (Action action) => enabledChanged = action.isEnabled(const ThirdTestIntent()), action: action2, child: Actions( actions: >{ diff --git a/packages/flutter/test/widgets/autofill_group_test.dart b/packages/flutter/test/widgets/autofill_group_test.dart new file mode 100644 index 0000000000000..282edc52888a1 --- /dev/null +++ b/packages/flutter/test/widgets/autofill_group_test.dart @@ -0,0 +1,166 @@ +// Copyright 2014 The Flutter 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/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('AutofillGroup has the right clients', (WidgetTester tester) async { + const Key outerKey = Key('outer'); + const Key innerKey = Key('inner'); + + const TextField client1 = TextField(autofillHints: ['1']); + const TextField client2 = TextField(autofillHints: ['2']); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: AutofillGroup( + key: outerKey, + child: Column(children: [ + client1, + AutofillGroup( + key: innerKey, + child: Column(children: const [client2, TextField()]), + ), + ]), + ), + ), + ), + ); + + final AutofillGroupState innerState = tester.state(find.byKey(innerKey)); + final AutofillGroupState outerState = tester.state(find.byKey(outerKey)); + + final EditableTextState clientState1 = tester.state( + find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)), + ); + final EditableTextState clientState2 = tester.state( + find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)), + ); + + expect(outerState.autofillClients, [clientState1]); + expect(innerState.autofillClients, [clientState2]); + }); + + testWidgets('new clients can be added & removed to a scope', (WidgetTester tester) async { + const Key scopeKey = Key('scope'); + + final List hints = []; + + const TextField client1 = TextField(autofillHints: ['1']); + final TextField client2 = TextField(autofillHints: hints); + + StateSetter setState; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: AutofillGroup( + key: scopeKey, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setter) { + setState = setter; + return Column(children: [client1, client2]); + }, + ), + ), + ), + ), + ); + + final AutofillGroupState scopeState = tester.state(find.byKey(scopeKey)); + + final EditableTextState clientState1 = tester.state( + find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)), + ); + final EditableTextState clientState2 = tester.state( + find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)), + ); + + expect(scopeState.autofillClients, [clientState1]); + + // Add to scope. + setState(() { hints.add('2'); }); + + await tester.pump(); + + expect(scopeState.autofillClients.length, 2); + expect(scopeState.autofillClients, contains(clientState1)); + expect(scopeState.autofillClients, contains(clientState2)); + + // Remove from scope again. + setState(() { hints.clear(); }); + + await tester.pump(); + + expect(scopeState.autofillClients, [clientState1]); + }); + + testWidgets('AutofillGroup has the right clients after reparenting', (WidgetTester tester) async { + const Key outerKey = Key('outer'); + const Key innerKey = Key('inner'); + final GlobalKey keyClient3 = GlobalKey(); + + const TextField client1 = TextField(autofillHints: ['1']); + const TextField client2 = TextField(autofillHints: ['2']); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: AutofillGroup( + key: outerKey, + child: Column(children: [ + client1, + AutofillGroup( + key: innerKey, + child: Column(children: [ + client2, + TextField(key: keyClient3, autofillHints: const ['3']), + ]), + ), + ]), + ), + ), + ), + ); + + final AutofillGroupState innerState = tester.state(find.byKey(innerKey)); + final AutofillGroupState outerState = tester.state(find.byKey(outerKey)); + + final EditableTextState clientState1 = tester.state( + find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)), + ); + final EditableTextState clientState2 = tester.state( + find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)), + ); + + final EditableTextState clientState3 = tester.state( + find.descendant(of: find.byKey(keyClient3), matching: find.byType(EditableText)), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: AutofillGroup( + key: outerKey, + child: Column(children: [ + client1, + TextField(key: keyClient3, autofillHints: const ['3']), + AutofillGroup( + key: innerKey, + child: Column(children: const [client2]), + ), + ]), + ), + ), + ), + ); + + expect(outerState.autofillClients.length, 2); + expect(outerState.autofillClients, contains(clientState1)); + expect(outerState.autofillClients, contains(clientState3)); + expect(innerState.autofillClients, [clientState2]); + }); +} diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index aeacc0e78a32f..69457bbf79960 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -4116,8 +4116,17 @@ void main() { await tester.showKeyboard(find.byType(EditableText)); // TextInput.show should be before TextInput.setEditingState - final List logOrder = ['TextInput.setClient', 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setStyle', 'TextInput.setEditingState', 'TextInput.setEditingState', 'TextInput.show']; - expect(tester.testTextInput.log.length, 7); + final List logOrder = [ + 'TextInput.setClient', + 'TextInput.show', + 'TextInput.setEditableSizeAndTransform', + 'TextInput.requestAutofill', + 'TextInput.setStyle', + 'TextInput.setEditingState', + 'TextInput.setEditingState', + 'TextInput.show', + ]; + expect(tester.testTextInput.log.length, 8); int index = 0; for (final MethodCall m in tester.testTextInput.log) { expect(m.method, logOrder[index]); @@ -4156,6 +4165,7 @@ void main() { 'TextInput.setClient', 'TextInput.show', 'TextInput.setEditableSizeAndTransform', + 'TextInput.requestAutofill', 'TextInput.setStyle', 'TextInput.setEditingState', 'TextInput.setEditingState', @@ -4203,6 +4213,7 @@ void main() { 'TextInput.setClient', 'TextInput.show', 'TextInput.setEditableSizeAndTransform', + 'TextInput.requestAutofill', 'TextInput.setStyle', 'TextInput.setEditingState', 'TextInput.setEditingState', diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart index bea9a12d7d5b5..d4a98e24b6f00 100644 --- a/packages/flutter/test/widgets/image_test.dart +++ b/packages/flutter/test/widgets/image_test.dart @@ -528,6 +528,7 @@ void main() { final ImageListener listener = (ImageInfo info, bool synchronous) { capturedImage = info; }; + final FlutterExceptionHandler oldHandler = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails flutterError) { reportedException = flutterError.exception; reportedStackTrace = flutterError.stack; @@ -564,6 +565,7 @@ void main() { // The image stream error handler should have the original exception. expect(capturedException, testException); expect(capturedStackTrace, testStack); + FlutterError.onError = oldHandler; }); testWidgets('Duplicate listener registration does not affect error listeners', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/widget_inspector_structure_error_test.dart b/packages/flutter/test/widgets/widget_inspector_structure_error_test.dart new file mode 100644 index 0000000000000..c87746d3bcf78 --- /dev/null +++ b/packages/flutter/test/widgets/widget_inspector_structure_error_test.dart @@ -0,0 +1,91 @@ +// Copyright 2014 The Flutter 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 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + StructureErrorTestWidgetInspectorService.runTests(); +} + +typedef InspectorServiceExtensionCallback = FutureOr> Function(Map parameters); + +class StructureErrorTestWidgetInspectorService extends Object with WidgetInspectorService { + final Map extensions = {}; + + final Map>> eventsDispatched = >>{}; + + @override + void registerServiceExtension({ + @required String name, + @required FutureOr> callback(Map parameters), + }) { + assert(!extensions.containsKey(name)); + extensions[name] = callback; + } + + @override + void postEvent(String eventKind, Map eventData) { + getEventsDispatched(eventKind).add(eventData); + } + + List> getEventsDispatched(String eventKind) { + return eventsDispatched.putIfAbsent(eventKind, () => >[]); + } + + Iterable> getServiceExtensionStateChangedEvents(String extensionName) { + return getEventsDispatched('Flutter.ServiceExtensionStateChanged') + .where((Map event) => event['extension'] == extensionName); + } + + Future testBoolExtension(String name, Map arguments) async { + expect(extensions, contains(name)); + // Encode and decode to JSON to match behavior using a real service + // extension where only JSON is allowed. + return json.decode(json.encode(await extensions[name](arguments)))['enabled'] as String; + } + + + static void runTests() { + final StructureErrorTestWidgetInspectorService service = StructureErrorTestWidgetInspectorService(); + WidgetInspectorService.instance = service; + + test('ext.flutter.inspector.structuredErrors still report error to original on error', () async { + final FlutterExceptionHandler oldHandler = FlutterError.onError; + + FlutterErrorDetails actualError; + // Creates a spy onError. This spy needs to be set before widgets binding + // initializes. + FlutterError.onError = (FlutterErrorDetails details) { + actualError = details; + }; + + WidgetsFlutterBinding.ensureInitialized(); + try { + // Enables structured errors. + expect(await service.testBoolExtension( + 'structuredErrors', {'enabled': 'true'}), + equals('true')); + + // Creates an error. + final FlutterErrorDetails expectedError = FlutterErrorDetailsForRendering( + library: 'rendering library', + context: ErrorDescription('during layout'), + exception: StackTrace.current, + ); + FlutterError.reportError(expectedError); + + // Validates the spy still received an error. + expect(actualError, expectedError); + } finally { + FlutterError.onError = oldHandler; + } + }); + } +} \ No newline at end of file diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index a0b5b5cd2b9cc..04b52814b9c18 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -2280,7 +2280,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { List> flutterErrorEvents = service.getEventsDispatched('Flutter.Error'); expect(flutterErrorEvents, isEmpty); - final FlutterExceptionHandler oldHandler = FlutterError.onError; + final FlutterExceptionHandler oldHandler = FlutterError.presentError; try { // Enable structured errors. @@ -2337,7 +2337,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { error = flutterErrorEvents.last; expect(error['errorsSinceReload'], 0); } finally { - FlutterError.onError = oldHandler; + FlutterError.presentError = oldHandler; } }); diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index e3b2b7db66531..c2e6592af5a75 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: file: 5.1.0 - json_rpc_2: 2.1.0 + json_rpc_2: 2.1.1 meta: 1.1.8 path: 1.7.0 web_socket_channel: 1.1.0 @@ -51,4 +51,4 @@ dev_dependencies: mockito: 4.1.1 quiver: 2.1.3 -# PUBSPEC CHECKSUM: 6770 +# PUBSPEC CHECKSUM: 1371 diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 4e217ffda22a2..d1d4ecba4367d 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -572,9 +572,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase }) { assert(description != null); assert(inTest); - _oldExceptionHandler = FlutterError.onError; + _oldExceptionHandler = FlutterError.presentError; int _exceptionCount = 0; // number of un-taken exceptions - FlutterError.onError = (FlutterErrorDetails details) { + FlutterError.presentError = (FlutterErrorDetails details) { if (_pendingExceptionDetails != null) { debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors! if (_exceptionCount == 0) { @@ -800,7 +800,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// Called by the [testWidgets] function after a test is executed. void postTest() { assert(inTest); - FlutterError.onError = _oldExceptionHandler; + FlutterError.presentError = _oldExceptionHandler; _pendingExceptionDetails = null; _parentZone = null; buildOwner.focusManager = FocusManager(); diff --git a/packages/flutter_tools/bin/fuchsia_tester.dart b/packages/flutter_tools/bin/fuchsia_tester.dart index 0fc00e521247d..fb032bfb7fc9f 100644 --- a/packages/flutter_tools/bin/fuchsia_tester.dart +++ b/packages/flutter_tools/bin/fuchsia_tester.dart @@ -111,7 +111,7 @@ Future run(List args) async { // TODO(tvolkert): Remove once flutter_tester no longer looks for this. globals.fs.link(sdkRootDest.childFile('platform.dill').path).createSync('platform_strong.dill'); - PackageMap.globalPackagesPath = + globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(argResults[_kOptionPackages] as String)); Directory testDirectory; diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh index ad90d9f615330..704f691d344fa 100755 --- a/packages/flutter_tools/bin/macos_assemble.sh +++ b/packages/flutter_tools/bin/macos_assemble.sh @@ -84,6 +84,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \ -dDartObfuscation="${dart_obfuscation_flag}" \ -dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \ --DartDefines="${DART_DEFINES}" \ + --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \ -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ --build-inputs="${build_inputs_path}" \ --build-outputs="${build_outputs_path}" \ diff --git a/packages/flutter_tools/bin/tool_backend.dart b/packages/flutter_tools/bin/tool_backend.dart index e63228f08f81d..844586861b12f 100644 --- a/packages/flutter_tools/bin/tool_backend.dart +++ b/packages/flutter_tools/bin/tool_backend.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; // ignore: dart_convert_import. import 'dart:io'; // ignore: dart_io_import. import 'package:path/path.dart' as path; // ignore: package_path_import. @@ -32,65 +33,42 @@ or '''); exit(1); } + final String flutterExecutable = path.join( flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); + final String target = targetPlatform == 'windows-x64' + ? 'debug_bundle_windows_assets' + : 'debug_bundle_linux_assets'; - if (targetPlatform == 'linux-x64') { - // TODO(jonahwilliams): currently all builds are debug builds. Remove the - // hardcoded mode when profile and release support is added. - final ProcessResult unpackResult = await Process.run( - flutterExecutable, - [ - '--suppress-analytics', - '--verbose', - if (flutterEngine != null) '--local-engine-src-path=$flutterEngine', - if (localEngine != null) '--local-engine=$localEngine', - 'assemble', - '-dTargetPlatform=$targetPlatform', - '-dBuildMode=debug', - '-dTargetFile=$flutterTarget', - '--output=build', - 'debug_bundle_linux_assets', - ]); - if (unpackResult.exitCode != 0) { - stderr.write(unpackResult.stderr); - exit(1); - } - return; - } - - const String cacheDirectory = 'windows/flutter/ephemeral'; - final ProcessResult unpackResult = await Process.run( - flutterExecutable, - [ - '--suppress-analytics', - if (verbose) '--verbose', - 'unpack', - '--target-platform=$targetPlatform', - '--cache-dir=$cacheDirectory', - if (flutterEngine != null) '--local-engine-src-path=$flutterEngine', - if (localEngine != null) '--local-engine=$localEngine', - ]); - if (unpackResult.exitCode != 0) { - stderr.write(unpackResult.stderr); - exit(1); - } - final ProcessResult buildResult = await Process.run( + // TODO(jonahwilliams): currently all builds are debug builds. Remove the + // hardcoded mode when profile and release support is added. + final Process assembleProcess = await Process.start( flutterExecutable, [ - '--suppress-analytics', - if (verbose) '--verbose', - 'build', - 'bundle', - '--target=$flutterTarget', - '--target-platform=$targetPlatform', - if (trackWidgetCreation) '--track-widget-creation', + if (verbose) + '--verbose', if (flutterEngine != null) '--local-engine-src-path=$flutterEngine', if (localEngine != null) '--local-engine=$localEngine', - ]); - if (buildResult.exitCode != 0) { - stderr.write(buildResult.stderr); + 'assemble', + if (trackWidgetCreation) + '-dTrackWidgetCreation=$trackWidgetCreation', + '-dTargetPlatform=$targetPlatform', + '-dBuildMode=debug', + '-dTargetFile=$flutterTarget', + '--output=build', + target, + ], + ); + assembleProcess.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(stdout.writeln); + assembleProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(stderr.writeln); + + if (await assembleProcess.exitCode != 0) { exit(1); } - exit(0); } diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index 37806d550b773..888bd70cb2562 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -186,6 +186,7 @@ BuildApp() { -dTrackWidgetCreation="${track_widget_creation_flag}" \ -dDartObfuscation="${dart_obfuscation_flag}" \ -dEnableBitcode="${bitcode_flag}" \ + --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \ --DartDefines="${DART_DEFINES}" \ -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ "${build_mode}_ios_bundle_flutter_assets" diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 2fe79bad019f0..f6b1293489852 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -44,7 +44,6 @@ import 'src/commands/shell_completion.dart'; import 'src/commands/symbolize.dart'; import 'src/commands/test.dart'; import 'src/commands/train.dart'; -import 'src/commands/unpack.dart'; import 'src/commands/update_packages.dart'; import 'src/commands/upgrade.dart'; import 'src/commands/version.dart'; @@ -102,7 +101,6 @@ Future main(List args) async { ShellCompletionCommand(), TestCommand(verboseHelp: verboseHelp), TrainingCommand(), - UnpackCommand(), UpdatePackagesCommand(hidden: !verboseHelp), UpgradeCommand(), VersionCommand(), diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index 6b94d8606c8c9..b72368238eb08 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'package:args/command_runner.dart'; import 'package:intl/intl.dart' as intl; import 'package:intl/intl_standalone.dart' as intl_standalone; -import 'package:meta/meta.dart'; +import 'package:http/http.dart' as http; import 'src/base/common.dart'; import 'src/base/context.dart'; @@ -27,17 +27,17 @@ import 'src/runner/flutter_command_runner.dart'; Future run( List args, List commands, { - bool muteCommandLogging = false, - bool verbose = false, - bool verboseHelp = false, - bool reportCrashes, - String flutterVersion, - Map overrides, -}) async { + bool muteCommandLogging = false, + bool verbose = false, + bool verboseHelp = false, + bool reportCrashes, + String flutterVersion, + Map overrides, + }) async { if (muteCommandLogging) { // Remove the verbose option; for help and doctor, users don't need to see // verbose logs. - args = List.from(args); + args = List.of(args); args.removeWhere((String option) => option == '-v' || option == '--verbose'); } @@ -121,7 +121,14 @@ Future _handleToolError( // Report to both [Usage] and [CrashReportSender]. globals.flutterUsage.sendException(error); - await CrashReportSender.instance.sendReport( + final CrashReportSender crashReportSender = CrashReportSender( + client: http.Client(), + usage: globals.flutterUsage, + platform: globals.platform, + logger: globals.logger, + operatingSystemUtils: globals.os, + ); + await crashReportSender.sendReport( error: error, stackTrace: stackTrace, getFlutterVersion: getFlutterVersion, @@ -184,18 +191,10 @@ String _crashCommand(List args) => 'flutter ${args.join(' ')}'; String _crashException(dynamic error) => '${error.runtimeType}: $error'; -/// File system used by the crash reporting logic. -/// -/// We do not want to use the file system stored in the context because it may -/// be recording. Additionally, in the case of a crash we do not trust the -/// integrity of the [AppContext]. -@visibleForTesting -FileSystem crashFileSystem = const LocalFileSystem(); - /// Saves the crash report to a local file. Future _createLocalCrashReport(List args, dynamic error, StackTrace stackTrace, String doctorText) async { File crashFile = globals.fsUtils.getUniqueFile( - crashFileSystem.currentDirectory, + globals.fs.currentDirectory, 'flutter', 'log', ); @@ -219,7 +218,7 @@ Future _createLocalCrashReport(List args, dynamic error, StackTrac } on FileSystemException catch (_) { // Fallback to the system temporary directory. crashFile = globals.fsUtils.getUniqueFile( - crashFileSystem.systemTempDirectory, + globals.fs.systemTempDirectory, 'flutter', 'log', ); diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 61f253d52ada9..16c12a130e9ea 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -315,7 +315,7 @@ Future buildGradleApp({ command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions.join(',')}'); } if (buildInfo.extraGenSnapshotOptions != null) { - command.add('-Pextra-gen-snapshot-options=${buildInfo.extraGenSnapshotOptions}'); + command.add('-Pextra-gen-snapshot-options=${buildInfo.extraGenSnapshotOptions.join(',')}'); } if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) { command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}'); diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 0b7b92d19eba1..7ab8fab946e88 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -21,7 +21,7 @@ import 'android_studio.dart'; /// The environment variables needed to run Gradle. Map get gradleEnvironment { - final Map environment = Map.from(globals.platform.environment); + final Map environment = Map.of(globals.platform.environment); if (javaPath != null) { // Use java bundled with Android Studio. environment['JAVA_HOME'] = javaPath; diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 6db13de90df65..e93a63fdb210b 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -125,7 +125,7 @@ class _ManifestAssetBundle implements AssetBundle { bool reportLicensedPackages = false, }) async { assetDirPath ??= getAssetBuildDirectory(); - packagesPath ??= globals.fs.path.absolute(PackageMap.globalPackagesPath); + packagesPath ??= globals.fs.path.absolute(globalPackagesPath); FlutterManifest flutterManifest; try { flutterManifest = FlutterManifest.createFromPath( @@ -152,15 +152,9 @@ class _ManifestAssetBundle implements AssetBundle { } final String assetBasePath = globals.fs.path.dirname(globals.fs.path.absolute(manifestPath)); - final PackageConfig packageConfig = await loadPackageConfigUri( - globals.fs.file(packagesPath).absolute.uri, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - }, + final PackageConfig packageConfig = await loadPackageConfigOrFail( + globals.fs.file(packagesPath), + logger: globals.logger, ); final List wildcardDirectories = []; diff --git a/packages/flutter_tools/lib/src/base/fingerprint.dart b/packages/flutter_tools/lib/src/base/fingerprint.dart index 1f7f5aa91a370..c9681d79a8784 100644 --- a/packages/flutter_tools/lib/src/base/fingerprint.dart +++ b/packages/flutter_tools/lib/src/base/fingerprint.dart @@ -28,7 +28,7 @@ class Fingerprinter { Iterable depfilePaths = const [], FingerprintPathFilter pathFilter, }) : _paths = paths.toList(), - _properties = Map.from(properties), + _properties = Map.of(properties), _depfilePaths = depfilePaths.toList(), _pathFilter = pathFilter, assert(fingerprintPath != null), diff --git a/packages/flutter_tools/lib/src/base/terminal.dart b/packages/flutter_tools/lib/src/base/terminal.dart index 3332fe1bc935a..4a859197a83ce 100644 --- a/packages/flutter_tools/lib/src/base/terminal.dart +++ b/packages/flutter_tools/lib/src/base/terminal.dart @@ -273,7 +273,7 @@ class AnsiTerminal implements Terminal { List charactersToDisplay = acceptedCharacters; if (defaultChoiceIndex != null) { assert(defaultChoiceIndex >= 0 && defaultChoiceIndex < acceptedCharacters.length); - charactersToDisplay = List.from(charactersToDisplay); + charactersToDisplay = List.of(charactersToDisplay); charactersToDisplay[defaultChoiceIndex] = bolden(charactersToDisplay[defaultChoiceIndex]); acceptedCharacters.add('\n'); } diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index 30b3f7ba731d4..8c63b661431c4 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -79,7 +79,7 @@ class ItemListNotifier { } ItemListNotifier.from(List items) { - _items = Set.from(items); + _items = Set.of(items); } Set _items; @@ -93,7 +93,7 @@ class ItemListNotifier { List get items => _items.toList(); void updateWithNewList(List updatedList) { - final Set updatedSet = Set.from(updatedList); + final Set updatedSet = Set.of(updatedList); final Set addedItems = updatedSet.difference(_items); final Set removedItems = _items.difference(updatedSet); diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 324c77268e857..70e45ccc04f09 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -24,6 +24,7 @@ class BuildInfo { this.splitDebugInfoPath, this.dartObfuscation = false, this.dartDefines = const [], + this.dartExperiments = const [], @required this.treeShakeIcons, }); @@ -79,6 +80,9 @@ class BuildInfo { /// [bool], [String], [int], and [double]. final List dartDefines; + /// A list of Dart experiments. + final List dartExperiments; + static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault); static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault); diff --git a/packages/flutter_tools/lib/src/build_runner/devfs_web.dart b/packages/flutter_tools/lib/src/build_runner/devfs_web.dart index b3d935e6fe177..4e7ccf343c3e2 100644 --- a/packages/flutter_tools/lib/src/build_runner/devfs_web.dart +++ b/packages/flutter_tools/lib/src/build_runner/devfs_web.dart @@ -125,6 +125,7 @@ class WebAssetServer implements AssetReader { /// Unhandled exceptions will throw a [ToolExit] with the error and stack /// trace. static Future start( + ChromiumLauncher chromiumLauncher, String hostname, int port, UrlTunneller urlTunneller, @@ -144,15 +145,9 @@ class WebAssetServer implements AssetReader { address = (await InternetAddress.lookup(hostname)).first; } final HttpServer httpServer = await HttpServer.bind(address, port); - final PackageConfig packageConfig = await loadPackageConfigUri( - globals.fs.file(PackageMap.globalPackagesPath).absolute.uri, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + final PackageConfig packageConfig = await loadPackageConfigOrFail( + globals.fs.file(globalPackagesPath), + logger: globals.logger, ); final Map digests = {}; final Map modules = {}; @@ -214,8 +209,8 @@ class WebAssetServer implements AssetReader { enableDebugExtension: true, buildResults: const Stream.empty(), chromeConnection: () async { - final Chrome chrome = await ChromeLauncher.connectedInstance; - return chrome.chromeConnection; + final Chromium chromium = await chromiumLauncher.connectedInstance; + return chromium.chromeConnection; }, hostname: hostname, urlEncoder: urlTunneller, @@ -568,6 +563,7 @@ class WebDevFS implements DevFS { @required this.enableDwds, @required this.entrypoint, @required this.expressionCompiler, + @required this.chromiumLauncher, this.testMode = false, }); @@ -581,6 +577,7 @@ class WebDevFS implements DevFS { final bool enableDwds; final bool testMode; final ExpressionCompiler expressionCompiler; + final ChromiumLauncher chromiumLauncher; WebAssetServer webAssetServer; @@ -638,6 +635,7 @@ class WebDevFS implements DevFS { @override Future create() async { webAssetServer = await WebAssetServer.start( + chromiumLauncher, hostname, port, urlTunneller, diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart index e85641207b789..1805ef21616b8 100644 --- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart @@ -34,6 +34,7 @@ import '../project.dart'; import '../reporting/reporting.dart'; import '../resident_runner.dart'; import '../run_hot.dart'; +import '../vmservice.dart'; import '../web/chrome.dart'; import '../web/compile.dart'; import '../web/web_device.dart'; @@ -113,6 +114,7 @@ abstract class ResidentWebRunner extends ResidentRunner { StreamSubscription _stdErrSub; bool _exited = false; WipConnection _wipConnection; + ChromiumLauncher _chromiumLauncher; vmservice.VmService get _vmService => _connectionResult?.debugConnection?.vmService; @@ -157,10 +159,6 @@ abstract class ResidentWebRunner extends ResidentRunner { 'Failed to clean up temp directory: ${_generatedEntrypointDirectory.path}', ); } - if (ChromeLauncher.hasChromeInstance) { - final Chrome chrome = await ChromeLauncher.connectedInstance; - await chrome.close(); - } _exited = true; } @@ -195,9 +193,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugDumpApp() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpApp', - ); + await _vmService + ?.flutterDebugDumpApp( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -206,9 +205,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugDumpRenderTree() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpRenderTree', - ); + await _vmService + ?.flutterDebugDumpRenderTree( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -217,9 +217,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugDumpLayerTree() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpLayerTree', - ); + await _vmService + ?.flutterDebugDumpLayerTree( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -228,8 +229,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugDumpSemanticsTreeInTraversalOrder() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder'); + await _vmService + ?.flutterDebugDumpSemanticsTreeInTraversalOrder( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -238,14 +241,16 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugTogglePlatform() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.platformOverride'); - final String currentPlatform = response.json['value'] as String; + final String currentPlatform = await _vmService + ?.flutterPlatformOverride( + isolateId: null, + ); final String platform = nextPlatform(currentPlatform, featureFlags); - await _vmService?.callServiceExtension('ext.flutter.platformOverride', - args: { - 'value': platform, - }); + await _vmService + ?.flutterPlatformOverride( + platform: platform, + isolateId: null, + ); globals.printStatus('Switched operating system to $platform'); } on vmservice.RPCError { return; @@ -261,8 +266,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugDumpSemanticsTreeInInverseHitTestOrder() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder'); + await _vmService + ?.flutterDebugDumpSemanticsTreeInInverseHitTestOrder( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -271,16 +278,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugToggleDebugPaintSizeEnabled() async { try { - final vmservice.Response response = - await _vmService?.callServiceExtension( - 'ext.flutter.debugPaint', - ); - await _vmService?.callServiceExtension( - 'ext.flutter.debugPaint', - args: { - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleDebugPaintSizeEnabled( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -289,16 +290,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugToggleDebugCheckElevationsEnabled() async { try { - final vmservice.Response response = - await _vmService?.callServiceExtension( - 'ext.flutter.debugCheckElevationsEnabled', - ); - await _vmService?.callServiceExtension( - 'ext.flutter.debugCheckElevationsEnabled', - args: { - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleDebugCheckElevationsEnabled( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -307,14 +302,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugTogglePerformanceOverlayOverride() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.showPerformanceOverlay'); - await _vmService?.callServiceExtension( - 'ext.flutter.showPerformanceOverlay', - args: { - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterTogglePerformanceOverlayOverride( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -323,14 +314,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugToggleWidgetInspector() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.debugToggleWidgetInspector'); - await _vmService?.callServiceExtension( - 'ext.flutter.debugToggleWidgetInspector', - args: { - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleWidgetInspector( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -339,14 +326,10 @@ abstract class ResidentWebRunner extends ResidentRunner { @override Future debugToggleProfileWidgetBuilds() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.profileWidgetBuilds'); - await _vmService?.callServiceExtension( - 'ext.flutter.profileWidgetBuilds', - args: { - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleProfileWidgetBuilds( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -408,6 +391,10 @@ class _ResidentWebRunner extends ResidentWebRunner { ? await globals.os.findFreePort() : int.tryParse(debuggingOptions.port); + if (device.device is ChromiumDevice) { + _chromiumLauncher = (device.device as ChromiumDevice).chromeLauncher; + } + try { return await asyncGuard(() async { // Ensure dwds resources are cached. If the .packages file is missing then @@ -433,6 +420,7 @@ class _ResidentWebRunner extends ResidentWebRunner { enableDwds: _enableDwds, entrypoint: globals.fs.file(target).uri, expressionCompiler: expressionCompiler, + chromiumLauncher: _chromiumLauncher, ); final Uri url = await device.devFS.create(); if (debuggingOptions.buildInfo.isDebug) { @@ -449,6 +437,7 @@ class _ResidentWebRunner extends ResidentWebRunner { debuggingOptions.buildInfo, debuggingOptions.initializePlatform, false, + debuggingOptions.buildInfo.dartExperiments, ); } await device.device.startApp( @@ -510,6 +499,7 @@ class _ResidentWebRunner extends ResidentWebRunner { debuggingOptions.buildInfo, debuggingOptions.initializePlatform, false, + debuggingOptions.buildInfo.dartExperiments, ); } on ToolExit { return OperationResult(1, 'Failed to recompile application.'); @@ -565,7 +555,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ..createSync(); result = _generatedEntrypointDirectory.childFile('web_entrypoint.dart'); - final bool hasWebPlugins = findPlugins(flutterProject) + final bool hasWebPlugins = (await findPlugins(flutterProject)) .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); await injectPlugins(flutterProject, checkProjects: true); @@ -657,8 +647,8 @@ class _ResidentWebRunner extends ResidentWebRunner { Completer connectionInfoCompleter, Completer appStartedCompleter, }) async { - if (device.device is ChromeDevice) { - final Chrome chrome = await ChromeLauncher.connectedInstance; + if (_chromiumLauncher != null) { + final Chromium chrome = await _chromiumLauncher.connectedInstance; final ChromeTab chromeTab = await chrome.chromeConnection.getTab((ChromeTab chromeTab) { return !chromeTab.url.startsWith('chrome-extension'); }); @@ -674,6 +664,14 @@ class _ResidentWebRunner extends ResidentWebRunner { _connectionResult = await webDevFS.connect(useDebugExtension); unawaited(_connectionResult.debugConnection.onDone.whenComplete(_cleanupAndExit)); + _stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) { + final String message = utf8.decode(base64.decode(log.bytes)); + globals.printStatus(message, newline: false); + }); + _stdErrSub = _vmService.onStderrEvent.listen((vmservice.Event log) { + final String message = utf8.decode(base64.decode(log.bytes)); + globals.printStatus(message, newline: false); + }); try { await _vmService.streamListen(vmservice.EventStreams.kStdout); } on vmservice.RPCError { @@ -692,14 +690,6 @@ class _ResidentWebRunner extends ResidentWebRunner { // It is safe to ignore this error because we expect an error to be // thrown if we're not already subscribed. } - _stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) { - final String message = utf8.decode(base64.decode(log.bytes)); - globals.printStatus(message, newline: false); - }); - _stdErrSub = _vmService.onStderrEvent.listen((vmservice.Event log) { - final String message = utf8.decode(base64.decode(log.bytes)); - globals.printStatus(message, newline: false); - }); unawaited(_vmService.registerService('reloadSources', 'FlutterTools')); _vmService.registerServiceCallback('reloadSources', (Map params) async { final bool pause = params['pause'] as bool ?? false; diff --git a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart index b41322c3644a3..1df05da1a1114 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart @@ -41,7 +41,7 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { .childDirectory('.dart_tool') .createSync(); final FlutterProject flutterProject = FlutterProject.fromDirectory(projectDirectory); - final bool hasWebPlugins = findPlugins(flutterProject) + final bool hasWebPlugins = (await findPlugins(flutterProject)) .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); final BuildDaemonClient client = await const BuildDaemonCreator().startBuildDaemon( projectDirectory.path, diff --git a/packages/flutter_tools/lib/src/build_system/depfile.dart b/packages/flutter_tools/lib/src/build_system/depfile.dart index d59687e8c89d2..9c7b1f11e3d63 100644 --- a/packages/flutter_tools/lib/src/build_system/depfile.dart +++ b/packages/flutter_tools/lib/src/build_system/depfile.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:meta/meta.dart'; -import 'package:platform/platform.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; @@ -13,14 +12,11 @@ class DepfileService { DepfileService({ @required Logger logger, @required FileSystem fileSystem, - @required Platform platform, }) : _logger = logger, - _fileSystem = fileSystem, - _platform = platform; + _fileSystem = fileSystem; final Logger _logger; final FileSystem _fileSystem; - final Platform _platform; static final RegExp _separatorExpr = RegExp(r'([^\\]) '); static final RegExp _escapeExpr = RegExp(r'\\(.)'); @@ -82,8 +78,9 @@ class DepfileService { void _writeFilesToBuffer(List files, StringBuffer buffer) { for (final File outputFile in files) { - if (_platform.isWindows) { - // Foward slashes and spaces in a depfile have to be escaped on windows. + if (_fileSystem.path.style.separator == r'\') { + // backslashes and spaces in a depfile have to be escaped if the + // platform separator is a backslash. final String path = outputFile.path .replaceAll(r'\', r'\\') .replaceAll(r' ', r'\ '); diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index d832c50f5831a..6ecb493b83263 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -66,7 +66,6 @@ abstract class AndroidAssetBundle extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( assetDepfile, diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index a5c79324ea3e8..2f61d9f88cc43 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -114,7 +114,6 @@ class CopyAssets extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( depfile, diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart index a5a479f508555..7839a4938803a 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart @@ -9,6 +9,7 @@ import '../../base/build.dart'; import '../../base/file_system.dart'; import '../../build_info.dart'; import '../../compile.dart'; +import '../../dart/package_map.dart'; import '../../globals.dart' as globals; import '../../project.dart'; import '../build_system.dart'; @@ -122,7 +123,6 @@ class CopyFlutterBundle extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( assetDepfile, @@ -234,15 +234,9 @@ class KernelSnapshot extends Target { forceLinkPlatform = false; } - final PackageConfig packageConfig = await loadPackageConfigUri( - packagesFile.absolute.uri, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + final PackageConfig packageConfig = await loadPackageConfigOrFail( + environment.projectDir.childFile('.packages'), + logger: environment.logger, ); final CompilerOutput output = await compiler.compile( diff --git a/packages/flutter_tools/lib/src/build_system/targets/desktop.dart b/packages/flutter_tools/lib/src/build_system/targets/desktop.dart new file mode 100644 index 0000000000000..1fb48c1bb337b --- /dev/null +++ b/packages/flutter_tools/lib/src/build_system/targets/desktop.dart @@ -0,0 +1,65 @@ +// Copyright 2014 The Flutter 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:meta/meta.dart'; + +import '../../base/file_system.dart'; +import '../depfile.dart'; + +/// Unpack the artifact list [artifacts] from [artifactPath] into a directory +/// [outputDirectory], returning a [Depfile] including all copied files. +Depfile unpackDesktopArtifacts({ + @required FileSystem fileSystem, + @required List artifacts, + @required Directory outputDirectory, + @required String artifactPath, +}) { + final List inputs = []; + final List outputs = []; + for (final String artifact in artifacts) { + final String entityPath = fileSystem.path.join(artifactPath, artifact); + final FileSystemEntityType entityType = fileSystem.typeSync(entityPath); + + if (entityType == FileSystemEntityType.notFound + || entityType == FileSystemEntityType.link) { + throw Exception('Unsupported file type: $entityType'); + } + + // If this artifact is a file then copy the source over. + if (entityType == FileSystemEntityType.file) { + final String outputPath = fileSystem.path.join( + outputDirectory.path, + fileSystem.path.relative(entityPath, from: artifactPath), + ); + final File destinationFile = fileSystem.file(outputPath); + if (!destinationFile.parent.existsSync()) { + destinationFile.parent.createSync(recursive: true); + } + final File inputFile = fileSystem.file(entityPath); + inputFile.copySync(destinationFile.path); + inputs.add(inputFile); + outputs.add(destinationFile); + continue; + } + + // If the artifact is a directory, recursively copy every file from it. + for (final File input in fileSystem.directory(entityPath) + .listSync(recursive: true) + .whereType()) { + final String outputPath = fileSystem.path.join( + outputDirectory.path, + fileSystem.path.relative(input.path, from: artifactPath), + ); + final File destinationFile = fileSystem.file(outputPath); + if (!destinationFile.parent.existsSync()) { + destinationFile.parent.createSync(recursive: true); + } + final File inputFile = fileSystem.file(input); + inputFile.copySync(destinationFile.path); + inputs.add(inputFile); + outputs.add(destinationFile); + } + } + return Depfile(inputs, outputs); +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 8d1943d9a6132..559ea0fcb4ceb 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -46,8 +46,7 @@ abstract class AotAssemblyBase extends Target { if (environment.defines[kTargetPlatform] == null) { throw MissingDefineException(kTargetPlatform, 'aot_assembly'); } - final List extraGenSnapshotOptions = environment - .defines[kExtraGenSnapshotOptions]?.split(',') ?? const []; + final List extraGenSnapshotOptions = parseExtraGenSnapshotOptions(environment); final bool bitcode = environment.defines[kBitcodeFlag] == 'true'; final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); @@ -302,7 +301,6 @@ abstract class IosAssetBundle extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( assetDepfile, @@ -438,3 +436,13 @@ Future createStubAppFramework(File outputFile, SdkType sdk, { bool in } } } + +/// iOS and macOS build scripts may pass extraGenSnapshotOptions as an empty +/// string. +List parseExtraGenSnapshotOptions(Environment environment) { + final String value = environment.defines[kExtraGenSnapshotOptions]; + if (value == null || value.trim().isEmpty) { + return []; + } + return value.split(','); +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/linux.dart b/packages/flutter_tools/lib/src/build_system/targets/linux.dart index b106fae379880..e26a175c38392 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart @@ -11,6 +11,7 @@ import '../depfile.dart'; import '../exceptions.dart'; import 'assets.dart'; import 'dart.dart'; +import 'desktop.dart'; import 'icon_tree_shaker.dart'; /// The only files/subdirectories we care out. @@ -24,6 +25,8 @@ const List _kLinuxArtifacts = [ 'cpp_client_wrapper_glfw/', ]; +const String _kLinuxDepfile = 'linux_engine_sources.d'; + /// Copies the Linux desktop embedding files to the copy directory. class UnpackLinuxDebug extends Target { const UnpackLinuxDebug(); @@ -40,72 +43,34 @@ class UnpackLinuxDebug extends Target { List get outputs => const []; @override - List get depfiles => [ - 'linux_engine_sources.d' - ]; + List get depfiles => const [_kLinuxDepfile]; @override List get dependencies => []; @override Future build(Environment environment) async { - final String basePath = globals.artifacts.getArtifactPath(Artifact.linuxDesktopPath); - final List inputs = []; - final List outputs = []; - final String outputPrefix = globals.fs.path.join( + final String artifactPath = globals.artifacts.getArtifactPath(Artifact.linuxDesktopPath); + final Directory outputDirectory = environment.fileSystem.directory( + environment.fileSystem.path.join( environment.projectDir.path, 'linux', 'flutter', 'ephemeral', + )); + final Depfile depfile = unpackDesktopArtifacts( + fileSystem: environment.fileSystem, + artifactPath: artifactPath, + outputDirectory: outputDirectory, + artifacts: _kLinuxArtifacts, ); - // The native linux artifacts are composed of 6 files and a directory (listed above) - // which need to be copied to the target directory. - for (final String artifact in _kLinuxArtifacts) { - final String entityPath = globals.fs.path.join(basePath, artifact); - // If this artifact is a file, just copy the source over. - if (globals.fs.isFileSync(entityPath)) { - final String outputPath = globals.fs.path.join( - outputPrefix, - globals.fs.path.relative(entityPath, from: basePath), - ); - final File destinationFile = globals.fs.file(outputPath); - if (!destinationFile.parent.existsSync()) { - destinationFile.parent.createSync(recursive: true); - } - final File inputFile = globals.fs.file(entityPath); - inputFile.copySync(destinationFile.path); - inputs.add(inputFile); - outputs.add(destinationFile); - continue; - } - // If the artifact is the directory cpp_client_wrapper, recursively - // copy every file from it. - for (final File input in globals.fs.directory(entityPath) - .listSync(recursive: true) - .whereType()) { - final String outputPath = globals.fs.path.join( - outputPrefix, - globals.fs.path.relative(input.path, from: basePath), - ); - final File destinationFile = globals.fs.file(outputPath); - if (!destinationFile.parent.existsSync()) { - destinationFile.parent.createSync(recursive: true); - } - final File inputFile = globals.fs.file(input); - inputFile.copySync(destinationFile.path); - inputs.add(inputFile); - outputs.add(destinationFile); - } - } - final Depfile depfile = Depfile(inputs, outputs); final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( depfile, - environment.buildDir.childFile('linux_engine_sources.d'), + environment.buildDir.childFile(_kLinuxDepfile), ); } } @@ -162,7 +127,6 @@ class DebugBundleLinuxAssets extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( depfile, diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart index 447212c57d96b..ee648f4323b3b 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -15,6 +15,7 @@ import '../exceptions.dart'; import 'assets.dart'; import 'dart.dart'; import 'icon_tree_shaker.dart'; +import 'ios.dart'; /// Copy the macOS framework to the correct copy dir by invoking 'cp -R'. /// @@ -80,7 +81,6 @@ abstract class UnpackMacOS extends Target { final DepfileService depfileService = DepfileService( logger: globals.logger, fileSystem: globals.fs, - platform: globals.platform, ); depfileService.writeToFile( Depfile(inputs, outputs), @@ -198,6 +198,7 @@ class CompileMacOSFramework extends Target { } final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true'; + final List extraGenSnapshotOptions = parseExtraGenSnapshotOptions(environment); final AOTSnapshotter snapshotter = AOTSnapshotter( reportTimings: false, fileSystem: globals.fs, @@ -216,6 +217,7 @@ class CompileMacOSFramework extends Target { packagesPath: environment.projectDir.childFile('.packages').path, splitDebugInfo: splitDebugInfo, dartObfuscation: dartObfuscation, + extraGenSnapshotOptions: extraGenSnapshotOptions, ); if (result != 0) { throw Exception('gen shapshot failed.'); @@ -295,7 +297,6 @@ abstract class MacOSBundleFlutterAssets extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( depfile, diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index a35a5394caed7..e4bf4919eb44f 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -28,6 +28,11 @@ const String kHasWebPlugins = 'HasWebPlugins'; /// Valid values are O1 (lowest, profile default) to O4 (highest, release default). const String kDart2jsOptimization = 'Dart2jsOptimization'; +/// Allow specifying experiments for dart2js. +/// +/// Multiple values should be encoded as a comma-separated list. +const String kEnableExperiment = 'EnableExperiment'; + /// Whether to disable dynamic generation code to satisfy csp policies. const String kCspMode = 'cspMode'; @@ -58,15 +63,9 @@ class WebEntrypointTarget extends Target { final bool shouldInitializePlatform = environment.defines[kInitializePlatform] == 'true'; final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true'; final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri; - final PackageConfig packageConfig = await loadPackageConfigUri( - environment.projectDir.childFile('.packages').absolute.uri, - loader: (Uri uri) { - final File file = environment.fileSystem.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + final PackageConfig packageConfig = await loadPackageConfigOrFail( + environment.projectDir.childFile('.packages'), + logger: environment.logger, ); // Use the PackageConfig to find the correct package-scheme import path @@ -160,11 +159,13 @@ class Dart2JSTarget extends Target { final String dart2jsOptimization = environment.defines[kDart2jsOptimization]; final bool csp = environment.defines[kCspMode] == 'true'; final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); - final String specPath = globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json'); - final String packageFile = PackageMap.globalPackagesPath; + final String specPath = globals.fs.path.join( + globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json'); + final String packageFile = globalPackagesPath; final File outputKernel = environment.buildDir.childFile('app.dill'); final File outputFile = environment.buildDir.childFile('main.dart.js'); final List dartDefines = parseDartDefines(environment); + final String enabledExperiments = environment.defines[kEnableExperiment]; // Run the dart2js compilation in two stages, so that icon tree shaking can // parse the kernel file for web builds. @@ -172,11 +173,17 @@ class Dart2JSTarget extends Target { globals.artifacts.getArtifactPath(Artifact.engineDartBinary), globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot), '--libraries-spec=$specPath', + if (enabledExperiments != null) + '--enable-experiment=$enabledExperiments', '-o', outputKernel.path, + '--packages=$packageFile', + if (buildMode == BuildMode.profile) + '-Ddart.vm.profile=true' + else + '-Ddart.vm.product=true', for (final String dartDefine in dartDefines) '-D$dartDefine', - '--packages=$packageFile', '--cfe-only', environment.buildDir.childFile('main.dart').path, ]); @@ -187,6 +194,8 @@ class Dart2JSTarget extends Target { globals.artifacts.getArtifactPath(Artifact.engineDartBinary), globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot), '--libraries-spec=$specPath', + if (enabledExperiments != null) + '--enable-experiment=$enabledExperiments', if (dart2jsOptimization != null) '-$dart2jsOptimization' else @@ -218,7 +227,6 @@ class Dart2JSTarget extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); final Depfile depfile = depfileService.parseDart2js( environment.buildDir.childFile('app.dill.deps'), @@ -282,7 +290,6 @@ class WebReleaseBundle extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( depfile, @@ -377,7 +384,6 @@ class WebServiceWorker extends Target { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile( depfile, diff --git a/packages/flutter_tools/lib/src/build_system/targets/windows.dart b/packages/flutter_tools/lib/src/build_system/targets/windows.dart index 16b7acc26a75a..66250c3aa91a6 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart @@ -6,6 +6,28 @@ import '../../artifacts.dart'; import '../../base/file_system.dart'; import '../../build_info.dart'; import '../build_system.dart'; +import '../depfile.dart'; +import '../exceptions.dart'; +import 'assets.dart'; +import 'dart.dart'; +import 'desktop.dart'; +import 'icon_tree_shaker.dart'; + +/// The only files/subdirectories we care about. +const List _kWindowsArtifacts = [ + 'flutter_windows.dll', + 'flutter_windows.dll.exp', + 'flutter_windows.dll.lib', + 'flutter_windows.dll.pdb', + 'flutter_export.h', + 'flutter_messenger.h', + 'flutter_plugin_registrar.h', + 'flutter_windows.h', + 'icudtl.dat', + 'cpp_client_wrapper', +]; + +const String _kWindowsDepfile = 'windows_engine_sources.d'; /// Copies the Windows desktop embedding files to the copy directory. class UnpackWindows extends Target { @@ -17,46 +39,101 @@ class UnpackWindows extends Target { @override List get inputs => const [ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/windows.dart'), - Source.artifact(Artifact.windowsDesktopPath, mode: BuildMode.debug), ]; @override - List get outputs => const [ - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll.exp'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll.lib'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll.pdb'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_export.h'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_messenger.h'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_plugin_registrar.h'), - Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.h'), - Source.pattern('{PROJECT_DIR}/windows/flutter/icudtl.dat'), - ]; + List get outputs => const []; + + @override + List get depfiles => const [_kWindowsDepfile]; @override List get dependencies => const []; @override Future build(Environment environment) async { - // This path needs to match the prefix in the rule below. - final String basePath = environment.artifacts - .getArtifactPath(Artifact.windowsDesktopPath); - for (final File input in environment.fileSystem.directory(basePath) - .listSync(recursive: true) - .whereType()) { - final String outputPath = environment.fileSystem.path.join( + final String artifactPath = environment.artifacts.getArtifactPath(Artifact.windowsDesktopPath); + final Directory outputDirectory = environment.fileSystem.directory( + environment.fileSystem.path.join( environment.projectDir.path, 'windows', 'flutter', - environment.fileSystem.path - .relative(input.path, from: basePath), - ); - final File destinationFile = environment.fileSystem.file(outputPath); - if (!destinationFile.parent.existsSync()) { - destinationFile.parent.createSync(recursive: true); - } - environment.fileSystem - .file(input).copySync(destinationFile.path); + 'ephemeral', + ), + ); + final Depfile depfile = unpackDesktopArtifacts( + fileSystem: environment.fileSystem, + artifacts: _kWindowsArtifacts, + artifactPath: artifactPath, + outputDirectory: outputDirectory, + ); + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + depfile, + environment.buildDir.childFile(_kWindowsDepfile), + ); + } +} + +/// Creates a debug bundle for the Windows desktop target. +class DebugBundleWindowsAssets extends Target { + const DebugBundleWindowsAssets(); + + @override + String get name => 'debug_bundle_windows_assets'; + + @override + List get dependencies => const [ + KernelSnapshot(), + UnpackWindows(), + ]; + + @override + List get inputs => const [ + Source.pattern('{BUILD_DIR}/app.dill'), + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/windows.dart'), + Source.pattern('{PROJECT_DIR}/pubspec.yaml'), + ...IconTreeShaker.inputs, + ]; + + @override + List get outputs => const [ + Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'), + ]; + + @override + List get depfiles => const [ + 'flutter_assets.d', + ]; + + @override + Future build(Environment environment) async { + if (environment.defines[kBuildMode] == null) { + throw MissingDefineException(kBuildMode, 'debug_bundle_windows_assets'); + } + final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); + final Directory outputDirectory = environment.outputDir + .childDirectory('flutter_assets'); + if (!outputDirectory.existsSync()) { + outputDirectory.createSync(); + } + + // Only copy the kernel blob in debug mode. + if (buildMode == BuildMode.debug) { + environment.buildDir.childFile('app.dill') + .copySync(outputDirectory.childFile('kernel_blob.bin').path); } + final Depfile depfile = await copyAssets(environment, outputDirectory); + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + depfile, + environment.buildDir.childFile('flutter_assets.d'), + ); } } diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart index 8fd7fd69a8ff9..0621d9e350c16 100644 --- a/packages/flutter_tools/lib/src/bundle.dart +++ b/packages/flutter_tools/lib/src/bundle.dart @@ -74,7 +74,7 @@ class BundleBuilder { mainPath ??= defaultMainPath; depfilePath ??= defaultDepfilePath; assetDirPath ??= getAssetBuildDirectory(); - packagesPath ??= globals.fs.path.absolute(PackageMap.globalPackagesPath); + packagesPath ??= globals.fs.path.absolute(globalPackagesPath); final FlutterProject flutterProject = FlutterProject.current(); await buildWithAssemble( buildMode: buildInfo.mode, @@ -163,7 +163,6 @@ Future buildWithAssemble({ final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile(depfile, outputDepfile); } @@ -177,7 +176,7 @@ Future buildAssets({ bool reportLicensedPackages = false, }) async { assetDirPath ??= getAssetBuildDirectory(); - packagesPath ??= globals.fs.path.absolute(PackageMap.globalPackagesPath); + packagesPath ??= globals.fs.path.absolute(globalPackagesPath); // Build the asset bundle. final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart index 286a620dce9fa..05035f13f48f6 100644 --- a/packages/flutter_tools/lib/src/commands/analyze.dart +++ b/packages/flutter_tools/lib/src/commands/analyze.dart @@ -30,7 +30,7 @@ class AnalyzeCommand extends FlutterCommand { _logger = logger, _terminal = terminal, _platform = platform { - addEnableExperimentation(verbose: verboseHelp); + addEnableExperimentation(hide: !verboseHelp); argParser.addFlag('flutter-repo', negatable: false, help: 'Include all the examples and tests from the Flutter repository.', diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart index 82480134500bc..35d28d8135e2e 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_once.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart @@ -54,7 +54,7 @@ class AnalyzeOnce extends AnalyzeBase { (workingDirectory ?? fileSystem.currentDirectory).path; // find directories from argResults.rest - final Set directories = Set.from(argResults.rest + final Set directories = Set.of(argResults.rest .map((String path) => fileSystem.path.canonicalize(path))); if (directories.isNotEmpty) { for (final String directory in directories) { diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index afa749fc39cdc..c1b2bff085b07 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -24,25 +24,29 @@ import '../runner/flutter_command.dart'; /// All currently implemented targets. const List _kDefaultTargets = [ - UnpackWindows(), + // Shared targets CopyAssets(), KernelSnapshot(), AotElfProfile(), AotElfRelease(), AotAssemblyProfile(), AotAssemblyRelease(), + // macOS targets DebugMacOSFramework(), DebugMacOSBundleFlutterAssets(), ProfileMacOSBundleFlutterAssets(), ReleaseMacOSBundleFlutterAssets(), + // Linux targets DebugBundleLinuxAssets(), + // Web targets WebServiceWorker(), - DebugAndroidApplication(), - FastStartAndroidApplication(), - ProfileAndroidApplication(), ReleaseAndroidApplication(), // This is a one-off rule for bundle and aot compat. CopyFlutterBundle(), + // Android targets, + DebugAndroidApplication(), + FastStartAndroidApplication(), + ProfileAndroidApplication(), // Android ABI specific AOT rules. androidArmProfileBundle, androidArm64ProfileBundle, @@ -50,9 +54,13 @@ const List _kDefaultTargets = [ androidArmReleaseBundle, androidArm64ReleaseBundle, androidx64ReleaseBundle, + // iOS targets DebugIosApplicationBundle(), ProfileIosApplicationBundle(), ReleaseIosApplicationBundle(), + // Windows targets + UnpackWindows(), + DebugBundleWindowsAssets(), ]; /// Assemble provides a low level API to interact with the flutter tool build @@ -229,7 +237,6 @@ class AssembleCommand extends FlutterCommand { final DepfileService depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); depfileService.writeToFile(depfile, globals.fs.file(depfileFile)); } diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index 24cc9cc34dfd5..b2051c9f2b268 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -21,19 +21,19 @@ import 'build_ios_framework.dart'; import 'build_web.dart'; class BuildCommand extends FlutterCommand { - BuildCommand({bool verboseHelp = false}) { + BuildCommand({ bool verboseHelp = false }) { addSubcommand(BuildAarCommand()); addSubcommand(BuildApkCommand(verboseHelp: verboseHelp)); addSubcommand(BuildAppBundleCommand(verboseHelp: verboseHelp)); addSubcommand(BuildAotCommand()); - addSubcommand(BuildIOSCommand()); + addSubcommand(BuildIOSCommand(verboseHelp: verboseHelp)); addSubcommand(BuildIOSFrameworkCommand( buildSystem: globals.buildSystem, bundleBuilder: BundleBuilder(), )); addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp)); - addSubcommand(BuildWebCommand()); - addSubcommand(BuildMacosCommand()); + addSubcommand(BuildWebCommand(verboseHelp: verboseHelp)); + addSubcommand(BuildMacosCommand(verboseHelp: verboseHelp)); addSubcommand(BuildLinuxCommand()); addSubcommand(BuildWindowsCommand()); addSubcommand(BuildFuchsiaCommand(verboseHelp: verboseHelp)); diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index b24e78368dfb1..9e0cb633e93ec 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -30,6 +30,7 @@ class BuildApkCommand extends BuildSubCommand { addDartObfuscationOption(); usesDartDefineOption(); usesExtraFrontendOptions(); + addEnableExperimentation(hide: !verboseHelp); argParser ..addFlag('split-per-abi', negatable: false, diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index ee319903dd281..1bfab41245e4f 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import '../application_package.dart'; import '../base/common.dart'; import '../base/utils.dart'; @@ -17,7 +19,7 @@ import 'build.dart'; /// or simulator. Can only be run on a macOS host. For producing deployment /// .ipas, see https://flutter.dev/docs/deployment/ios. class BuildIOSCommand extends BuildSubCommand { - BuildIOSCommand() { + BuildIOSCommand({ @required bool verboseHelp }) { addTreeShakeIconsFlag(); addSplitDebugInfoOption(); addBuildModeFlags(defaultToRelease: false); @@ -29,6 +31,7 @@ class BuildIOSCommand extends BuildSubCommand { addDartObfuscationOption(); usesDartDefineOption(); usesExtraFrontendOptions(); + addEnableExperimentation(hide: !verboseHelp); argParser ..addFlag('simulator', help: 'Build for the iOS simulator instead of the device.', diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart index af66d4d37ccc9..1e8eade1758e6 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import '../base/common.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -16,7 +18,7 @@ import 'build.dart'; /// A command to build a macOS desktop target through a build shell script. class BuildMacosCommand extends BuildSubCommand { - BuildMacosCommand() { + BuildMacosCommand({ @required bool verboseHelp }) { addTreeShakeIconsFlag(); addSplitDebugInfoOption(); usesTargetOption(); @@ -25,6 +27,7 @@ class BuildMacosCommand extends BuildSubCommand { usesExtraFrontendOptions(); usesBuildNumberOption(); usesBuildNameOption(); + addEnableExperimentation(hide: !verboseHelp); } @override diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 95b09e504c947..9ad38319e1307 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -4,22 +4,27 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import '../base/common.dart'; import '../build_info.dart'; import '../features.dart'; import '../project.dart'; import '../runner/flutter_command.dart' - show DevelopmentArtifact, FlutterCommandResult; + show DevelopmentArtifact, FlutterCommandResult, FlutterOptions; import '../web/compile.dart'; import 'build.dart'; class BuildWebCommand extends BuildSubCommand { - BuildWebCommand() { + BuildWebCommand({ + @required bool verboseHelp, + }) { addTreeShakeIconsFlag(); usesTargetOption(); usesPubOption(); addBuildModeFlags(excludeDebug: true); usesDartDefineOption(); + addEnableExperimentation(hide: !verboseHelp); argParser.addFlag('web-initialize-platform', defaultsTo: true, negatable: true, @@ -66,6 +71,7 @@ class BuildWebCommand extends BuildSubCommand { buildInfo, boolArg('web-initialize-platform'), boolArg('csp'), + stringsArg(FlutterOptions.kEnableExperiment), ); return FlutterCommandResult.success(); } diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 9f94984fdc1c9..47dfded8c9272 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -605,7 +605,6 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi 'useAndroidEmbeddingV2': featureFlags.isAndroidEmbeddingV2Enabled, 'androidMinApiLevel': android.minApiLevel, 'androidSdkVersion': android_sdk.minimumAndroidSdkVersion, - 'androidFlutterJar': '$flutterRoot/bin/cache/artifacts/engine/android-arm/flutter.jar', 'withDriverTest': renderDriverTest, 'pluginClass': pluginClass, 'pluginDartClass': pluginDartClass, diff --git a/packages/flutter_tools/lib/src/commands/devices.dart b/packages/flutter_tools/lib/src/commands/devices.dart index 0a239e422ff81..312e82ac11cc6 100644 --- a/packages/flutter_tools/lib/src/commands/devices.dart +++ b/packages/flutter_tools/lib/src/commands/devices.dart @@ -14,12 +14,10 @@ import '../runner/flutter_command.dart'; class DevicesCommand extends FlutterCommand { DevicesCommand() { - argParser.addFlag('machine', negatable: false, help: 'Output device information in machine readable structured JSON format', ); - argParser.addOption( 'timeout', abbr: 't', @@ -60,7 +58,9 @@ class DevicesCommand extends FlutterCommand { final List devices = await deviceManager.refreshAllConnectedDevices(timeout: timeout); - if (devices.isEmpty) { + if (boolArg('machine')) { + await printDevicesAsJson(devices); + } else if (devices.isEmpty) { final StringBuffer status = StringBuffer('No devices detected.'); status.writeln(); status.writeln(); @@ -80,8 +80,6 @@ class DevicesCommand extends FlutterCommand { globals.printStatus('• $diagnostic', hangingIndent: 2); } } - } else if (boolArg('machine')) { - await printDevicesAsJson(devices); } else { globals.printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n'); await Device.printDevices(devices); @@ -97,5 +95,4 @@ class DevicesCommand extends FlutterCommand { ) ); } - } diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index 871a9327e2588..b6bfdf26c65e9 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart @@ -470,14 +470,14 @@ void restoreTestRunner() { Future _runTests(List testArgs, Map environment) async { globals.printTrace('Running driver tests.'); - PackageMap.globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(PackageMap.globalPackagesPath)); + globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(globalPackagesPath)); final String dartVmPath = globals.fs.path.join(dartSdkPath, 'bin', 'dart'); final int result = await processUtils.stream( [ dartVmPath, ...dartVmFlags, ...testArgs, - '--packages=${PackageMap.globalPackagesPath}', + '--packages=$globalPackagesPath', '-rexpanded', ], environment: environment, diff --git a/packages/flutter_tools/lib/src/commands/inject_plugins.dart b/packages/flutter_tools/lib/src/commands/inject_plugins.dart index 2cf56338fc0b6..9f6b0e05e11b5 100644 --- a/packages/flutter_tools/lib/src/commands/inject_plugins.dart +++ b/packages/flutter_tools/lib/src/commands/inject_plugins.dart @@ -29,7 +29,7 @@ class InjectPluginsCommand extends FlutterCommand { @override Future runCommand() async { final FlutterProject project = FlutterProject.current(); - refreshPluginsList(project, checkProjects: true); + await refreshPluginsList(project, checkProjects: true); await injectPlugins(project, checkProjects: true); final bool result = hasPlugins(project); if (result) { diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index 38ef7e496ab17..7ff7e0b44c973 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart @@ -205,7 +205,7 @@ class PackagesPublishCommand extends FlutterCommand { if (boolArg('force')) '--force', ]; Cache.releaseLockEarly(); - await pub.interactively(['publish', ...args]); + await pub.interactively(['publish', ...args], stdio: globals.stdio); return FlutterCommandResult.success(); } } @@ -236,7 +236,7 @@ class PackagesForwardCommand extends FlutterCommand { @override Future runCommand() async { Cache.releaseLockEarly(); - await pub.interactively([_commandName, ...argResults.rest]); + await pub.interactively([_commandName, ...argResults.rest], stdio: globals.stdio); return FlutterCommandResult.success(); } @@ -264,7 +264,7 @@ class PackagesPassthroughCommand extends FlutterCommand { @override Future runCommand() async { Cache.releaseLockEarly(); - await pub.interactively(argResults.rest); + await pub.interactively(argResults.rest, stdio: globals.stdio); return FlutterCommandResult.success(); } } diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 01b48a8d82587..2b2e38c633a42 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -80,6 +80,8 @@ class RunCommand extends RunCommandBase { RunCommand({ bool verboseHelp = false }) : super(verboseHelp: verboseHelp) { requiresPubspecYaml(); usesFilesystemOptions(hide: !verboseHelp); + usesExtraFrontendOptions(); + addEnableExperimentation(hide: !verboseHelp); argParser ..addFlag('start-paused', negatable: false, @@ -208,11 +210,6 @@ class RunCommand extends RunCommandBase { help: 'Whether to quickly bootstrap applications with a minimal app. ' 'Currently this is only supported on Android devices. This option ' 'cannot be paired with --use-application-binary.' - ) - ..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true) - ..addMultiOption(FlutterOptions.kEnableExperiment, - splitCommas: true, - hide: true, ); } diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 5e48c4ceacb2d..ea5ee53b9f6e7 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -42,6 +42,14 @@ class TestCommand extends FlutterCommand { valueHelp: 'substring', splitCommas: false, ) + ..addOption('tags', + abbr: 't', + help: 'Run only tests associated with tags', + ) + ..addOption('exclude-tags', + abbr: 'x', + help: 'Run only tests WITHOUT given tags', + ) ..addFlag('start-paused', defaultsTo: false, negatable: false, @@ -160,6 +168,8 @@ class TestCommand extends FlutterCommand { final bool buildTestAssets = boolArg('test-assets'); final List names = stringsArg('name'); final List plainNames = stringsArg('plain-name'); + final String tags = stringArg('tags'); + final String excludeTags = stringArg('exclude-tags'); final FlutterProject flutterProject = FlutterProject.current(); if (buildTestAssets && flutterProject.manifest.assets.isNotEmpty) { @@ -250,6 +260,8 @@ class TestCommand extends FlutterCommand { workDir: workDir, names: names, plainNames: plainNames, + tags: tags, + excludeTags: excludeTags, watcher: watcher, enableObservatory: collector != null || startPaused || boolArg('enable-vmservice'), startPaused: startPaused, diff --git a/packages/flutter_tools/lib/src/commands/unpack.dart b/packages/flutter_tools/lib/src/commands/unpack.dart deleted file mode 100644 index 885c4ddb0adba..0000000000000 --- a/packages/flutter_tools/lib/src/commands/unpack.dart +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright 2014 The Flutter 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 '../artifacts.dart'; -import '../base/common.dart'; -import '../base/file_system.dart'; -import '../build_info.dart'; -import '../cache.dart'; -import '../globals.dart' as globals; -import '../runner/flutter_command.dart'; - -/// The directory in the Flutter cache for each platform's artifacts. -const Map flutterArtifactPlatformDirectory = { - TargetPlatform.windows_x64: 'windows-x64', - TargetPlatform.linux_x64: 'linux-x64', -}; - -// TODO(jonahwilliams): this should come from a configuration in each build -// directory. -const Map> artifactFilesByPlatform = >{ - TargetPlatform.windows_x64: [ - 'flutter_windows.dll', - 'flutter_windows.dll.exp', - 'flutter_windows.dll.lib', - 'flutter_windows.dll.pdb', - 'flutter_export.h', - 'flutter_messenger.h', - 'flutter_plugin_registrar.h', - 'flutter_windows.h', - 'icudtl.dat', - 'cpp_client_wrapper/', - ], -}; - -/// Copies desktop artifacts to local cache directories. -class UnpackCommand extends FlutterCommand { - UnpackCommand() { - argParser.addOption( - 'target-platform', - allowed: ['windows-x64', 'linux-x64'], - ); - argParser.addOption('cache-dir', - help: 'Location to output platform specific artifacts.'); - } - - @override - String get description => '(DEPRECATED) unpack desktop artifacts'; - - @override - String get name => 'unpack'; - - @override - bool get hidden => true; - - @override - Future> get requiredArtifacts async { - final Set result = {}; - final TargetPlatform targetPlatform = getTargetPlatformForName(stringArg('target-platform')); - switch (targetPlatform) { - case TargetPlatform.windows_x64: - result.add(DevelopmentArtifact.windows); - break; - case TargetPlatform.linux_x64: - result.add(DevelopmentArtifact.linux); - break; - default: - } - return result; - } - - @override - Future runCommand() async { - final String targetName = stringArg('target-platform'); - final String targetDirectory = stringArg('cache-dir'); - if (!globals.fs.directory(targetDirectory).existsSync()) { - globals.fs.directory(targetDirectory).createSync(recursive: true); - } - final TargetPlatform targetPlatform = getTargetPlatformForName(targetName); - final ArtifactUnpacker flutterArtifactFetcher = ArtifactUnpacker(targetPlatform); - bool success = true; - if (globals.artifacts is LocalEngineArtifacts) { - final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; - success = flutterArtifactFetcher.copyLocalBuildArtifacts( - localEngineArtifacts.engineOutPath, - targetDirectory, - ); - } else { - success = flutterArtifactFetcher.copyCachedArtifacts( - targetDirectory, - ); - } - if (!success) { - throwToolExit('Failed to unpack desktop artifacts.'); - } - return FlutterCommandResult.success(); - } -} - -/// Manages the copying of cached or locally built Flutter artifacts, including -/// tracking the last-copied versions and updating only if necessary. -class ArtifactUnpacker { - /// Creates a new fetcher for the given configuration. - const ArtifactUnpacker(this.platform); - - /// The platform to copy artifacts for. - final TargetPlatform platform; - - /// Checks [targetDirectory] to see if artifacts have already been copied for - /// the current hash, and if not, copies the artifacts for [platform] from the - /// Flutter cache (after ensuring that the cache is present). - /// - /// Returns true if the artifacts were successfully copied, or were already - /// present with the correct hash. - bool copyCachedArtifacts(String targetDirectory) { - String cacheStamp; - switch (platform) { - case TargetPlatform.windows_x64: - cacheStamp = 'windows-sdk'; - break; - case TargetPlatform.linux_x64: - return true; - default: - throwToolExit('Unsupported target platform: $platform'); - } - final String targetHash = - readHashFileIfPossible(globals.cache.getStampFileFor(cacheStamp)); - if (targetHash == null) { - globals.printError('Failed to find engine stamp file'); - return false; - } - - try { - final String currentHash = _lastCopiedHash(targetDirectory); - if (currentHash == null || targetHash != currentHash) { - // Copy them to the target directory. - final String flutterCacheDirectory = globals.fs.path.join( - Cache.flutterRoot, - 'bin', - 'cache', - 'artifacts', - 'engine', - flutterArtifactPlatformDirectory[platform], - ); - if (!_copyArtifactFiles(flutterCacheDirectory, targetDirectory)) { - return false; - } - _setLastCopiedHash(targetDirectory, targetHash); - globals.printTrace('Copied artifacts for version $targetHash.'); - } else { - globals.printTrace('Artifacts for version $targetHash already present.'); - } - } on Exception catch (error, stackTrace) { - globals.printError(stackTrace.toString()); - globals.printError(error.toString()); - return false; - } - return true; - } - - /// Acts like [copyCachedArtifacts], replacing the artifacts and updating - /// the version stamp, except that it pulls the artifact from a local engine - /// build with the given [buildConfiguration] (e.g., host_debug_unopt) whose - /// checkout is rooted at [engineRoot]. - bool copyLocalBuildArtifacts(String buildOutput, String targetDirectory) { - if (!_copyArtifactFiles(buildOutput, targetDirectory)) { - return false; - } - - // Update the hash file to indicate that it's a local build, so that it's - // obvious where it came from. - _setLastCopiedHash(targetDirectory, 'local build: $buildOutput'); - - return true; - } - - /// Copies the artifact files for [platform] from [sourceDirectory] to - /// [targetDirectory]. - bool _copyArtifactFiles(String sourceDirectory, String targetDirectory) { - final List artifactFiles = artifactFilesByPlatform[platform]; - if (artifactFiles == null) { - globals.printError('Unsupported platform: $platform.'); - return false; - } - - try { - globals.fs.directory(targetDirectory).createSync(recursive: true); - for (final String entityName in artifactFiles) { - final String sourcePath = globals.fs.path.join(sourceDirectory, entityName); - final String targetPath = globals.fs.path.join(targetDirectory, entityName); - if (entityName.endsWith('/')) { - globals.fsUtils.copyDirectorySync( - globals.fs.directory(sourcePath), - globals.fs.directory(targetPath), - ); - } else { - globals.fs.file(sourcePath) - .copySync(globals.fs.path.join(targetDirectory, entityName)); - } - } - - globals.printTrace('Copied artifacts from $sourceDirectory.'); - } on Exception catch (e, stackTrace) { - globals.printError(e.toString()); - globals.printError(stackTrace.toString()); - return false; - } - return true; - } - - /// Returns a File object for the file containing the last copied hash - /// in [directory]. - File _lastCopiedHashFile(String directory) { - return globals.fs.file(globals.fs.path.join(directory, '.last_artifact_version')); - } - - /// Returns the hash of the artifacts last copied to [directory], or null if - /// they haven't been copied. - String _lastCopiedHash(String directory) { - // Sanity check that at least one file is present; this won't catch every - // case, but handles someone deleting all the non-hidden cached files to - // force fresh copy. - final String artifactFilePath = globals.fs.path.join( - directory, - artifactFilesByPlatform[platform].first, - ); - if (!globals.fs.file(artifactFilePath).existsSync()) { - return null; - } - final File hashFile = _lastCopiedHashFile(directory); - return readHashFileIfPossible(hashFile); - } - - /// Writes [hash] to the file that stores the last copied hash for - /// in [directory]. - void _setLastCopiedHash(String directory, String hash) { - _lastCopiedHashFile(directory).writeAsStringSync(hash); - } - - /// Returns the engine hash from [file] as a String, or null. - /// - /// If the file is missing, or cannot be read, returns null. - String readHashFileIfPossible(File file) { - if (!file.existsSync()) { - return null; - } - try { - return file.readAsStringSync().trim(); - } on FileSystemException { - // If the file can't be read for any reason, just treat it as missing. - return null; - } - } -} diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index ecd6a383ad08d..33516a3d4c251 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -26,7 +26,6 @@ const Map _kManuallyPinnedDependencies = { 'mockito': '^4.1.0', // Prevent mockito from downgrading to 4.0.0 'vm_service_client': '0.2.6+2', // Final version before being marked deprecated. 'video_player': '0.10.6', // 0.10.7 fails a gallery smoke test for toString. - 'package_config': '1.9.1', 'flutter_template_images': '1.0.1', // Must always exactly match flutter_tools template. }; @@ -1290,7 +1289,7 @@ class PubDependencyTree { dependencies = const []; } _versions[package] = version; - _dependencyTree[package] = Set.from(dependencies); + _dependencyTree[package] = Set.of(dependencies); } } return null; diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 73bf3d49332cb..bc6d94e5a4195 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -45,7 +45,6 @@ import 'persistent_tool_state.dart'; import 'reporting/reporting.dart'; import 'run_hot.dart'; import 'version.dart'; -import 'web/chrome.dart'; import 'web/workflow.dart'; import 'windows/visual_studio.dart'; import 'windows/visual_studio_validator.dart'; @@ -99,13 +98,6 @@ Future runInContext( logger: globals.logger, platform: globals.platform, ), - ChromeLauncher: () => ChromeLauncher( - fileSystem: globals.fs, - processManager: globals.processManager, - logger: globals.logger, - operatingSystemUtils: globals.os, - platform: globals.platform, - ), CocoaPods: () => CocoaPods( fileSystem: globals.fs, processManager: globals.processManager, @@ -175,7 +167,14 @@ Future runInContext( processManager: globals.processManager, logger: globals.logger, ), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + botDetector: globals.botDetector, + platform: globals.platform, + usage: globals.flutterUsage, + ), ShutdownHooks: () => ShutdownHooks(logger: globals.logger), Signals: () => Signals(), Stdio: () => Stdio(), diff --git a/packages/flutter_tools/lib/src/dart/package_map.dart b/packages/flutter_tools/lib/src/dart/package_map.dart index 316aae4b5859c..e7b122128e81a 100644 --- a/packages/flutter_tools/lib/src/dart/package_map.dart +++ b/packages/flutter_tools/lib/src/dart/package_map.dart @@ -2,86 +2,53 @@ // 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 'package:meta/meta.dart'; -// TODO(bkonyi): remove deprecated member usage, https://github.com/flutter/flutter/issues/51951 -// ignore: deprecated_member_use -import 'package:package_config/packages_file.dart' as packages_file; +import 'package:package_config/package_config.dart'; +import '../base/common.dart'; import '../base/file_system.dart'; -import '../globals.dart' as globals hide fs; +import '../base/logger.dart'; const String kPackagesFileName = '.packages'; -Map _parse(String packagesPath, FileSystem fileSystem) { - final List source = fileSystem.file(packagesPath).readAsBytesSync(); - return packages_file.parse(source, - Uri.file(packagesPath, windows: globals.platform.isWindows)); -} - -class PackageMap { - PackageMap(this.packagesPath, { - @required FileSystem fileSystem, - }) : _fileSystem = fileSystem; - - /// Create a [PackageMap] for testing. - PackageMap.test(Map input, { - @required FileSystem fileSystem, - }) : packagesPath = '.packages', - _map = input, - _fileSystem = fileSystem; - - final FileSystem _fileSystem; - - static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName; - - static set globalPackagesPath(String value) { - _globalPackagesPath = value; - } - - static bool get isUsingCustomPackagesPath => _globalPackagesPath != null; +String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName; - static String _globalPackagesPath; - - final String packagesPath; - - /// Load and parses the .packages file. - void load() { - _map ??= _parse(packagesPath, _fileSystem); - } - - Map get map { - load(); - return _map; - } - Map _map; - - /// Returns the path to [packageUri]. - String pathForPackage(Uri packageUri) => uriForPackage(packageUri).path; - - /// Returns the path to [packageUri] as URL. - Uri uriForPackage(Uri packageUri) { - assert(packageUri.scheme == 'package'); - final List pathSegments = packageUri.pathSegments.toList(); - final String packageName = pathSegments.removeAt(0); - final Uri packageBase = map[packageName]; - if (packageBase == null) { - return null; - } - final String packageRelativePath = _fileSystem.path.joinAll(pathSegments); - return packageBase.resolveUri(_fileSystem.path.toUri(packageRelativePath)); - } +set globalPackagesPath(String value) { + _globalPackagesPath = value; +} - String checkValid() { - if (_fileSystem.isFileSync(packagesPath)) { - return null; - } - String message = '$packagesPath does not exist.'; - final String pubspecPath = _fileSystem.path.absolute(_fileSystem.path.dirname(packagesPath), 'pubspec.yaml'); - if (_fileSystem.isFileSync(pubspecPath)) { - message += '\nDid you run "flutter pub get" in this directory?'; - } else { - message += '\nDid you run this command from the same directory as your pubspec.yaml file?'; +bool get isUsingCustomPackagesPath => _globalPackagesPath != null; + +String _globalPackagesPath; + +/// Load the package configuration from [file] or throws a [ToolExit] +/// if the operation would fail. +Future loadPackageConfigOrFail(File file, { + @required Logger logger, +}) { + final FileSystem fileSystem = file.fileSystem; + return loadPackageConfigUri( + file.absolute.uri, + loader: (Uri uri) { + final File configFile = fileSystem.file(uri); + if (!configFile.existsSync()) { + return null; + } + return Future.value(configFile.readAsBytesSync()); + }, + onError: (dynamic error) { + logger.printTrace(error.toString()); + String message = '${file.path} does not exist.'; + final String pubspecPath = fileSystem.path.absolute(fileSystem.path.dirname(file.path), 'pubspec.yaml'); + if (fileSystem.isFileSync(pubspecPath)) { + message += '\nDid you run "flutter pub get" in this directory?'; + } else { + message += '\nDid you run this command from the same directory as your pubspec.yaml file?'; + } + logger.printError(message); + throwToolExit(null); } - return message; - } + ); } diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart index aa5e8732e4064..366334f5b5bf3 100644 --- a/packages/flutter_tools/lib/src/dart/pub.dart +++ b/packages/flutter_tools/lib/src/dart/pub.dart @@ -5,7 +5,10 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; +import '../base/bot_detector.dart'; import '../base/common.dart'; import '../base/context.dart'; import '../base/file_system.dart'; @@ -13,14 +16,19 @@ import '../base/io.dart' as io; import '../base/logger.dart'; import '../base/process.dart'; import '../cache.dart'; -import '../globals.dart' as globals; import '../reporting/reporting.dart'; -import '../runner/flutter_command.dart'; -import 'sdk.dart'; /// The [Pub] instance. Pub get pub => context.get(); +/// The console environment key used by the pub tool. +const String _kPubEnvironmentKey = 'PUB_ENVIRONMENT'; + +/// The console environment key used by the pub tool to find the cache directory. +const String _kPubCacheEnvironmentKey = 'PUB_CACHE'; + +typedef MessageFilter = String Function(String message); + /// Represents Flutter-specific data that is added to the `PUB_ENVIRONMENT` /// environment variable and allows understanding the type of requests made to /// the package site on Flutter's behalf. @@ -47,7 +55,6 @@ class PubContext { static final PubContext pubUpgrade = PubContext._(['upgrade']); static final PubContext pubForward = PubContext._(['forward']); static final PubContext runTest = PubContext._(['run_test']); - static final PubContext flutterTests = PubContext._(['flutter_tests']); static final PubContext updatePackages = PubContext._(['update_packages']); @@ -63,26 +70,17 @@ class PubContext { } } -bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) { - if (!dotPackages.existsSync()) { - return true; - } - final DateTime dotPackagesLastModified = dotPackages.lastModifiedSync(); - if (pubSpecYaml.lastModifiedSync().isAfter(dotPackagesLastModified)) { - return true; - } - final File flutterToolsStamp = globals.cache.getStampFileFor('flutter_tools'); - if (flutterToolsStamp.existsSync() && - flutterToolsStamp.lastModifiedSync().isAfter(dotPackagesLastModified)) { - return true; - } - return false; -} - /// A handle for interacting with the pub tool. abstract class Pub { /// Create a default [Pub] instance. - const factory Pub() = _DefaultPub; + factory Pub({ + @required FileSystem fileSystem, + @required Logger logger, + @required ProcessManager processManager, + @required Platform platform, + @required BotDetector botDetector, + @required Usage usage, + }) = _DefaultPub; /// Runs `pub get`. /// @@ -129,11 +127,34 @@ abstract class Pub { Future interactively( List arguments, { String directory, + @required io.Stdio stdio, }); } class _DefaultPub implements Pub { - const _DefaultPub(); + _DefaultPub({ + @required FileSystem fileSystem, + @required Logger logger, + @required ProcessManager processManager, + @required Platform platform, + @required BotDetector botDetector, + @required Usage usage, + }) : _fileSystem = fileSystem, + _logger = logger, + _platform = platform, + _botDetector = botDetector, + _usage = usage, + _processUtils = ProcessUtils( + logger: logger, + processManager: processManager, + ); + + final FileSystem _fileSystem; + final Logger _logger; + final ProcessUtils _processUtils; + final Platform _platform; + final BotDetector _botDetector; + final Usage _usage; @override Future get({ @@ -146,10 +167,12 @@ class _DefaultPub implements Pub { bool skipPubspecYamlCheck = false, String flutterRootOverride, }) async { - directory ??= globals.fs.currentDirectory.path; + directory ??= _fileSystem.currentDirectory.path; - final File pubSpecYaml = globals.fs.file(globals.fs.path.join(directory, 'pubspec.yaml')); - final File dotPackages = globals.fs.file(globals.fs.path.join(directory, '.packages')); + final File pubSpecYaml = _fileSystem.file( + _fileSystem.path.join(directory, 'pubspec.yaml')); + final File packageConfigFile = _fileSystem.file( + _fileSystem.path.join(directory, '.dart_tool', 'package_config.json')); if (!skipPubspecYamlCheck && !pubSpecYaml.existsSync()) { if (!skipIfAbsent) { @@ -160,24 +183,33 @@ class _DefaultPub implements Pub { final DateTime originalPubspecYamlModificationTime = pubSpecYaml.lastModifiedSync(); - if (!checkLastModified || _shouldRunPubGet(pubSpecYaml: pubSpecYaml, dotPackages: dotPackages)) { + if (!checkLastModified || _shouldRunPubGet( + pubSpecYaml: pubSpecYaml, + packageConfigFile: packageConfigFile, + )) { final String command = upgrade ? 'upgrade' : 'get'; - final Status status = globals.logger.startProgress( - 'Running "flutter pub $command" in ${globals.fs.path.basename(directory)}...', - timeout: timeoutConfiguration.slowOperation, + final Status status = _logger.startProgress( + 'Running "flutter pub $command" in ${_fileSystem.path.basename(directory)}...', + timeout: const TimeoutConfiguration().slowOperation, ); - final bool verbose = FlutterCommand.current != null && FlutterCommand.current.globalResults['verbose'] as bool; + final bool verbose = _logger.isVerbose; final List args = [ - if (verbose) '--verbose' else '--verbosity=warning', - ...[command, '--no-precompile'], - if (offline) '--offline', + if (verbose) + '--verbose' + else + '--verbosity=warning', + ...[ + command, + '--no-precompile', + ], + if (offline) + '--offline', ]; try { await batch( args, context: context, directory: directory, - filter: _filterOverrideWarnings, failureMessage: 'pub $command failed', retry: true, flutterRootOverride: flutterRootOverride, @@ -190,11 +222,13 @@ class _DefaultPub implements Pub { } } - if (!dotPackages.existsSync()) { + if (!packageConfigFile.existsSync()) { throwToolExit('$directory: pub did not create .packages file.'); } if (pubSpecYaml.lastModifiedSync() != originalPubspecYamlModificationTime) { - throwToolExit('$directory: unexpected concurrent modification of pubspec.yaml while running pub.'); + throwToolExit( + '$directory: unexpected concurrent modification of ' + 'pubspec.yaml while running pub.'); } // We don't check if dotPackages was actually modified, because as far as we can tell sometimes // pub will decide it does not need to actually modify it. @@ -202,29 +236,28 @@ class _DefaultPub implements Pub { // file to be more recently modified. final DateTime now = DateTime.now(); if (now.isBefore(originalPubspecYamlModificationTime)) { - globals.printError( - 'Warning: File "${globals.fs.path.absolute(pubSpecYaml.path)}" was created in the future. ' + _logger.printError( + 'Warning: File "${_fileSystem.path.absolute(pubSpecYaml.path)}" was created in the future. ' 'Optimizations that rely on comparing time stamps will be unreliable. Check your ' 'system clock for accuracy.\n' 'The timestamp was: $originalPubspecYamlModificationTime\n' 'The time now is: $now' ); } else { - dotPackages.setLastModifiedSync(now); - final DateTime newDotPackagesTimestamp = dotPackages.lastModifiedSync(); + packageConfigFile.setLastModifiedSync(now); + final DateTime newDotPackagesTimestamp = packageConfigFile.lastModifiedSync(); if (newDotPackagesTimestamp.isBefore(originalPubspecYamlModificationTime)) { - globals.printError( - 'Warning: Failed to set timestamp of "${globals.fs.path.absolute(dotPackages.path)}". ' + _logger.printError( + 'Warning: Failed to set timestamp of "${_fileSystem.path.absolute(packageConfigFile.path)}". ' 'Tried to set timestamp to $now, but new timestamp is $newDotPackagesTimestamp.' ); if (newDotPackagesTimestamp.isAfter(now)) { - globals.printError('Maybe the file was concurrently modified?'); + _logger.printError('Maybe the file was concurrently modified?'); } } } } - @override Future batch( List arguments, { @@ -236,7 +269,7 @@ class _DefaultPub implements Pub { bool showTraceForErrors, String flutterRootOverride, }) async { - showTraceForErrors ??= await globals.isRunningOnBot; + showTraceForErrors ??= await _botDetector.isRunningOnBot; String lastPubMessage = 'no message'; bool versionSolvingFailed = false; @@ -259,7 +292,7 @@ class _DefaultPub implements Pub { int code; loop: while (true) { attempts += 1; - code = await processUtils.stream( + code = await _processUtils.stream( _pubCommand(arguments), workingDirectory: directory, mapFunction: filterWrapper, // may set versionSolvingFailed, lastPubMessage @@ -275,7 +308,10 @@ class _DefaultPub implements Pub { } assert(message != null); versionSolvingFailed = false; - globals.printStatus('$failureMessage ($message) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...'); + _logger.printStatus( + '$failureMessage ($message) -- attempting retry $attempts in $duration ' + 'second${ duration == 1 ? "" : "s"}...', + ); await Future.delayed(Duration(seconds: duration)); if (duration < 64) { duration *= 2; @@ -292,6 +328,7 @@ class _DefaultPub implements Pub { PubResultEvent( context: context.toAnalyticsString(), result: result, + usage: _usage, ).send(); if (code != 0) { @@ -303,33 +340,34 @@ class _DefaultPub implements Pub { Future interactively( List arguments, { String directory, + @required io.Stdio stdio, }) async { Cache.releaseLockEarly(); - final io.Process process = await processUtils.start( + final io.Process process = await _processUtils.start( _pubCommand(arguments), workingDirectory: directory, environment: await _createPubEnvironment(PubContext.interactive), ); // Pipe the Flutter tool stdin to the pub stdin. - unawaited(process.stdin.addStream(globals.stdio.stdin) + unawaited(process.stdin.addStream(stdio.stdin) // If pub exits unexpectedly with an error, that will be reported below // by the tool exit after the exit code check. .catchError((dynamic err, StackTrace stack) { - globals.printTrace('Echoing stdin to the pub subprocess failed:'); - globals.printTrace('$err\n$stack'); + _logger.printTrace('Echoing stdin to the pub subprocess failed:'); + _logger.printTrace('$err\n$stack'); } )); // Pipe the pub stdout and stderr to the tool stdout and stderr. try { await Future.wait(>[ - globals.stdio.addStdoutStream(process.stdout), - globals.stdio.addStderrStream(process.stderr), + stdio.addStdoutStream(process.stdout), + stdio.addStderrStream(process.stderr), ]); } on Exception catch (err, stack) { - globals.printTrace('Echoing stdout or stderr from the pub subprocess failed:'); - globals.printTrace('$err\n$stack'); + _logger.printTrace('Echoing stdout or stderr from the pub subprocess failed:'); + _logger.printTrace('$err\n$stack'); } // Wait for pub to exit. @@ -341,81 +379,79 @@ class _DefaultPub implements Pub { /// The command used for running pub. List _pubCommand(List arguments) { - return [sdkBinaryName('pub'), ...arguments]; + // TODO(jonahwilliams): refactor to use artifacts. + final String sdkPath = _fileSystem.path.joinAll([ + Cache.flutterRoot, + 'bin', + 'cache', + 'dart-sdk', + 'bin', + if (_platform.isWindows) + 'pub.bat' + else + 'pub' + ]); + return [sdkPath, ...arguments]; } -} - -typedef MessageFilter = String Function(String message); - -/// The full environment used when running pub. -/// -/// [context] provides extra information to package server requests to -/// understand usage. -Future> _createPubEnvironment(PubContext context, [ String flutterRootOverride ]) async { - final Map environment = { - 'FLUTTER_ROOT': flutterRootOverride ?? Cache.flutterRoot, - _pubEnvironmentKey: await _getPubEnvironmentValue(context), - }; - final String pubCache = _getRootPubCacheIfAvailable(); - if (pubCache != null) { - environment[_pubCacheEnvironmentKey] = pubCache; + bool _shouldRunPubGet({ @required File pubSpecYaml, @required File packageConfigFile }) { + if (!packageConfigFile.existsSync()) { + return true; + } + final DateTime dotPackagesLastModified = packageConfigFile.lastModifiedSync(); + if (pubSpecYaml.lastModifiedSync().isAfter(dotPackagesLastModified)) { + return true; + } + return false; } - return environment; -} - -final RegExp _analyzerWarning = RegExp(r'^! \w+ [^ ]+ from path \.\./\.\./bin/cache/dart-sdk/lib/\w+$'); - -/// The console environment key used by the pub tool. -const String _pubEnvironmentKey = 'PUB_ENVIRONMENT'; -/// The console environment key used by the pub tool to find the cache directory. -const String _pubCacheEnvironmentKey = 'PUB_CACHE'; - -/// Returns the environment value that should be used when running pub. -/// -/// Includes any existing environment variable, if one exists. -/// -/// [context] provides extra information to package server requests to -/// understand usage. -Future _getPubEnvironmentValue(PubContext pubContext) async { - // DO NOT update this function without contacting kevmoo. - // We have server-side tooling that assumes the values are consistent. - final String existing = globals.platform.environment[_pubEnvironmentKey]; - final List values = [ - if (existing != null && existing.isNotEmpty) existing, - if (await globals.isRunningOnBot) 'flutter_bot', - 'flutter_cli', - ...pubContext._values, - ]; - return values.join(':'); -} - -String _getRootPubCacheIfAvailable() { - if (globals.platform.environment.containsKey(_pubCacheEnvironmentKey)) { - return globals.platform.environment[_pubCacheEnvironmentKey]; + // Returns the environment value that should be used when running pub. + // + // Includes any existing environment variable, if one exists. + // + // [context] provides extra information to package server requests to + // understand usage. + Future _getPubEnvironmentValue(PubContext pubContext) async { + // DO NOT update this function without contacting kevmoo. + // We have server-side tooling that assumes the values are consistent. + final String existing = _platform.environment[_kPubEnvironmentKey]; + final List values = [ + if (existing != null && existing.isNotEmpty) existing, + if (await _botDetector.isRunningOnBot) 'flutter_bot', + 'flutter_cli', + ...pubContext._values, + ]; + return values.join(':'); } - final String cachePath = globals.fs.path.join(Cache.flutterRoot, '.pub-cache'); - if (globals.fs.directory(cachePath).existsSync()) { - globals.printTrace('Using $cachePath for the pub cache.'); - return cachePath; - } + String _getRootPubCacheIfAvailable() { + if (_platform.environment.containsKey(_kPubCacheEnvironmentKey)) { + return _platform.environment[_kPubCacheEnvironmentKey]; + } - // Use pub's default location by returning null. - return null; -} + final String cachePath = _fileSystem.path.join(Cache.flutterRoot, '.pub-cache'); + if (_fileSystem.directory(cachePath).existsSync()) { + _logger.printTrace('Using $cachePath for the pub cache.'); + return cachePath; + } -String _filterOverrideWarnings(String message) { - // This function filters out these three messages: - // Warning: You are using these overridden dependencies: - // ! analyzer 0.29.0-alpha.0 from path ../../bin/cache/dart-sdk/lib/analyzer - // ! front_end 0.1.0-alpha.0 from path ../../bin/cache/dart-sdk/lib/front_end - if (message == 'Warning: You are using these overridden dependencies:') { + // Use pub's default location by returning null. return null; } - if (message.contains(_analyzerWarning)) { - return null; + + /// The full environment used when running pub. + /// + /// [context] provides extra information to package server requests to + /// understand usage. + Future> _createPubEnvironment(PubContext context, [ String flutterRootOverride ]) async { + final Map environment = { + 'FLUTTER_ROOT': flutterRootOverride ?? Cache.flutterRoot, + _kPubEnvironmentKey: await _getPubEnvironmentValue(context), + }; + final String pubCache = _getRootPubCacheIfAvailable(); + if (pubCache != null) { + environment[_kPubCacheEnvironmentKey] = pubCache; + } + return environment; } - return message; } diff --git a/packages/flutter_tools/lib/src/desktop_device.dart b/packages/flutter_tools/lib/src/desktop_device.dart index f5d4041ecff1f..18ac7336737c5 100644 --- a/packages/flutter_tools/lib/src/desktop_device.dart +++ b/packages/flutter_tools/lib/src/desktop_device.dart @@ -139,7 +139,7 @@ abstract class DesktopDevice extends Device { bool succeeded = true; // Walk a copy of _runningProcesses, since the exit handler removes from the // set. - for (final Process process in Set.from(_runningProcesses)) { + for (final Process process in Set.of(_runningProcesses)) { succeeded &= process.kill(); } return succeeded; diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 6dcaae381ef6c..59fc388242ffe 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -293,7 +293,7 @@ class _DevFSHttpWriter { Future write(Map entries) async { _client.maxConnectionsPerHost = kMaxInFlight; _completer = Completer(); - _outstanding = Map.from(entries); + _outstanding = Map.of(entries); _scheduleWrites(); await _completer.future; } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 3286a171820eb..a85ae7f909704 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:math' as math; import 'package:meta/meta.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'android/android_device_discovery.dart'; import 'android/android_workflow.dart'; @@ -25,7 +26,6 @@ import 'linux/linux_device.dart'; import 'macos/macos_device.dart'; import 'project.dart'; import 'tester/flutter_tester.dart'; -import 'vmservice.dart'; import 'web/web_device.dart'; import 'windows/windows_device.dart'; @@ -90,7 +90,13 @@ class DeviceManager { featureFlags: featureFlags, ), WindowsDevices(), - WebDevices(), + WebDevices( + featureFlags: featureFlags, + fileSystem: globals.fs, + logger: globals.logger, + platform: globals.platform, + processManager: globals.processManager, + ), ]); String _specifiedDeviceId; @@ -755,7 +761,7 @@ abstract class DeviceLogReader { /// Some logs can be obtained from a VM service stream. /// Set this after the VM services are connected. - VMService connectedVMService; + vm_service.VmService connectedVMService; @override String toString() => name; @@ -785,7 +791,7 @@ class NoOpDeviceLogReader implements DeviceLogReader { int appPid; @override - VMService connectedVMService; + vm_service.VmService connectedVMService; @override Stream get logLines => const Stream.empty(); diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 6fa37d479d8a0..bad0a9dd54054 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -35,6 +35,7 @@ import 'reporting/reporting.dart'; import 'tester/flutter_tester.dart'; import 'version.dart'; import 'vscode/vscode_validator.dart'; +import 'web/chrome.dart'; import 'web/web_validator.dart'; import 'web/workflow.dart'; import 'windows/visual_studio_validator.dart'; @@ -82,10 +83,16 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { if (globals.iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform) GroupedValidator([XcodeValidator(xcode: globals.xcode, userMessages: userMessages), cocoapodsValidator]), if (webWorkflow.appliesToHostPlatform) - WebValidator( - chromeLauncher: globals.chromeLauncher, + ChromeValidator( + chromiumLauncher: ChromiumLauncher( + browserFinder: findChromeExecutable, + fileSystem: globals.fs, + logger: globals.logger, + operatingSystemUtils: globals.os, + platform: globals.platform, + processManager: globals.processManager, + ), platform: globals.platform, - fileSystem: globals.fs, ), if (linuxWorkflow.appliesToHostPlatform) LinuxDoctorValidator( diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart index c0e2875b6c62a..59066378662f7 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart @@ -131,7 +131,7 @@ Future _buildAssets( ); final Map assetEntries = - Map.from(assets.entries); + Map.of(assets.entries); await writeBundle(globals.fs.directory(assetDir), assetEntries); final String appName = fuchsiaProject.project.manifest.appName; diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index 11c2d32b85864..a30075825a187 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart @@ -809,7 +809,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder { @override Future dispose() async { final List forwardedPortsCopy = - List.from(forwardedPorts); + List.of(forwardedPorts); for (final ForwardedPort port in forwardedPortsCopy) { await unforward(port); } diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index b91cbf145bb25..22bee70e36037 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -34,7 +34,6 @@ import 'persistent_tool_state.dart'; import 'project.dart'; import 'reporting/reporting.dart'; import 'version.dart'; -import 'web/chrome.dart'; Artifacts get artifacts => context.get(); BuildSystem get buildSystem => context.get(); @@ -183,8 +182,5 @@ PlistParser get plistParser => context.get() ?? ( )); PlistParser _plistInstance; -/// The [ChromeLauncher] instance. -ChromeLauncher get chromeLauncher => context.get(); - /// The global template renderer TemplateRenderer get templateRenderer => context.get(); diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 4a35480d1ddbc..0bfd3413a1dba 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -24,7 +24,6 @@ import '../macos/xcode.dart'; import '../mdns_discovery.dart'; import '../project.dart'; import '../protocol_discovery.dart'; -import '../vmservice.dart'; import 'fallback_discovery.dart'; import 'ios_deploy.dart'; import 'ios_workflow.dart'; @@ -561,18 +560,18 @@ class IOSDeviceLogReader extends DeviceLogReader { Stream get logLines => _linesController.stream; @override - VMService get connectedVMService => _connectedVMService; - VMService _connectedVMService; + vm_service.VmService get connectedVMService => _connectedVMService; + vm_service.VmService _connectedVMService; @override - set connectedVMService(VMService connectedVmService) { + set connectedVMService(vm_service.VmService connectedVmService) { _listenToUnifiedLoggingEvents(connectedVmService); _connectedVMService = connectedVmService; } static const int _minimumUniversalLoggingSdkVersion = 13; - Future _listenToUnifiedLoggingEvents(VMService connectedVmService) async { + Future _listenToUnifiedLoggingEvents(vm_service.VmService connectedVmService) async { if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) { return; } diff --git a/packages/flutter_tools/lib/src/ios/ios_deploy.dart b/packages/flutter_tools/lib/src/ios/ios_deploy.dart index 8c79a706574d4..3d516c2e27a66 100644 --- a/packages/flutter_tools/lib/src/ios/ios_deploy.dart +++ b/packages/flutter_tools/lib/src/ios/ios_deploy.dart @@ -47,7 +47,7 @@ class IOSDeploy { // Python script that uses package 'six'. LLDB.framework relies on the // python at the front of the path, which may not include package 'six'. // Ensure that we pick up the system install of python, which includes it. - final Map environment = Map.from(_platform.environment); + final Map environment = Map.of(_platform.environment); environment['PATH'] = '/usr/bin:${environment['PATH']}'; environment.addEntries(>[_cache.dyLdLibEntry]); return environment; diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 76e7361d85512..9c07e784f82ba 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -342,7 +342,7 @@ Future buildXcodeProject({ // it a lot of wiggle room (locally on Flutter Gallery, this takes ~1s). // When there is a timeout, we retry once. See issue #35988. final List showBuildSettingsCommand = (List - .from(buildCommands) + .of(buildCommands) ..add('-showBuildSettings')) // Undocumented behavior: xcodebuild craps out if -showBuildSettings // is used together with -allowProvisioningUpdates or diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 876823fa97b59..0ac16acc986b1 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -897,7 +897,7 @@ class _IOSSimulatorDevicePortForwarder extends DevicePortForwarder { @override Future dispose() async { - final List portsCopy = List.from(_ports); + final List portsCopy = List.of(_ports); for (final ForwardedPort port in portsCopy) { await unforward(port); } diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index b100f36374518..1282681e5fdf3 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -239,7 +239,16 @@ List _xcodeBuildSettingsLines({ } if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) { - xcodeBuildSettings.add('EXTRA_FRONT_END_OPTIONS=${buildInfo.extraFrontEndOptions.join(',')}'); + xcodeBuildSettings.add( + 'EXTRA_FRONT_END_OPTIONS=' + '${buildInfo.extraFrontEndOptions.join(',')}', + ); + } + if (buildInfo.extraGenSnapshotOptions?.isNotEmpty ?? false) { + xcodeBuildSettings.add( + 'EXTRA_GEN_SNAPSHOT_OPTIONS=' + '${buildInfo.extraGenSnapshotOptions.join(',')}', + ); } return xcodeBuildSettings; diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart index f45b2d44f28cd..86667f430c911 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart @@ -16,7 +16,7 @@ import '../project.dart'; Future processPodsIfNeeded(XcodeBasedProject xcodeProject, String buildDirectory, BuildMode buildMode) async { final FlutterProject project = xcodeProject.parent; // Ensure that the plugin list is up to date, since hasPlugins relies on it. - refreshPluginsList(project); + await refreshPluginsList(project); if (!(hasPlugins(project) || (project.isModule && xcodeProject.podfile.existsSync()))) { return; } diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 366bcbc916a7a..af951b1030bd3 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:package_config/package_config.dart'; import 'package:yaml/yaml.dart'; import 'android/gradle.dart'; @@ -298,26 +299,23 @@ Plugin _pluginFromPackage(String name, Uri packageRoot) { ); } -List findPlugins(FlutterProject project) { +Future> findPlugins(FlutterProject project) async { final List plugins = []; - Map packages; - try { - final String packagesFile = globals.fs.path.join( - project.directory.path, - PackageMap.globalPackagesPath, - ); - packages = PackageMap(packagesFile, fileSystem: globals.fs).map; - } on FormatException catch (e) { - globals.printTrace('Invalid .packages file: $e'); - return plugins; - } - packages.forEach((String name, Uri uri) { - final Uri packageRoot = uri.resolve('..'); - final Plugin plugin = _pluginFromPackage(name, packageRoot); + final String packagesFile = globals.fs.path.join( + project.directory.path, + globalPackagesPath, + ); + final PackageConfig packageConfig = await loadPackageConfigOrFail( + globals.fs.file(packagesFile), + logger: globals.logger, + ); + for (final Package package in packageConfig.packages) { + final Uri packageRoot = package.packageUriRoot.resolve('..'); + final Plugin plugin = _pluginFromPackage(package.name, packageRoot); if (plugin != null) { plugins.add(plugin); } - }); + } return plugins; } @@ -1065,8 +1063,8 @@ void _createPlatformPluginSymlinks(Directory symlinkDirectory, List pla /// which already exist. /// /// Assumes `pub get` has been executed since last change to `pubspec.yaml`. -void refreshPluginsList(FlutterProject project, {bool checkProjects = false}) { - final List plugins = findPlugins(project); +Future refreshPluginsList(FlutterProject project, {bool checkProjects = false}) async { + final List plugins = await findPlugins(project); // TODO(franciscojma): Remove once migration is complete. // Write the legacy plugin files to avoid breaking existing apps. @@ -1093,7 +1091,7 @@ void refreshPluginsList(FlutterProject project, {bool checkProjects = false}) { /// /// Assumes [refreshPluginsList] has been called since last change to `pubspec.yaml`. Future injectPlugins(FlutterProject project, {bool checkProjects = false}) async { - final List plugins = findPlugins(project); + final List plugins = await findPlugins(project); if ((checkProjects && project.android.existsSync()) || !checkProjects) { await _writeAndroidPluginRegistrant(project, plugins); } diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index fd7fa2e0b1c9d..7ec69d04ba74d 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -108,7 +108,7 @@ class FlutterProject { example.android.applicationId, await example.ios.productBundleIdentifier, ]; - return Set.from(candidates + return Set.of(candidates .map(_organizationNameFromPackageName) .where((String name) => name != null)); } @@ -226,7 +226,7 @@ class FlutterProject { if (!directory.existsSync() || hasExampleApp) { return; } - refreshPluginsList(this); + await refreshPluginsList(this); if ((android.existsSync() && checkProjects) || !checkProjects) { await android.ensureReadyForPlatformSpecificTooling(); } diff --git a/packages/flutter_tools/lib/src/reporting/crash_reporting.dart b/packages/flutter_tools/lib/src/reporting/crash_reporting.dart index ee52f550c7a2a..04425ec27411a 100644 --- a/packages/flutter_tools/lib/src/reporting/crash_reporting.dart +++ b/packages/flutter_tools/lib/src/reporting/crash_reporting.dart @@ -35,28 +35,29 @@ const String _kStackTraceFilename = 'stacktrace_file'; /// environment is behind a firewall and unable to send crash reports to /// Google, or when you wish to use your own server for collecting crash /// reports from Flutter Tools. -/// * In tests call [initializeWith] and provide a mock implementation of -/// [http.Client]. class CrashReportSender { - CrashReportSender._(this._client); + CrashReportSender({ + @required http.Client client, + @required Usage usage, + @required Platform platform, + @required Logger logger, + @required OperatingSystemUtils operatingSystemUtils, + }) : _client = client, + _usage = usage, + _platform = platform, + _logger = logger, + _operatingSystemUtils = operatingSystemUtils; - static CrashReportSender _instance; - - static CrashReportSender get instance => _instance ?? CrashReportSender._(http.Client()); + final http.Client _client; + final Usage _usage; + final Platform _platform; + final Logger _logger; + final OperatingSystemUtils _operatingSystemUtils; bool _crashReportSent = false; - /// Overrides the default [http.Client] with [client] for testing purposes. - @visibleForTesting - static void initializeWith(http.Client client) { - _instance = CrashReportSender._(client); - } - - final http.Client _client; - final Usage _usage = globals.flutterUsage; - Uri get _baseUrl { - final String overrideUrl = globals.platform.environment['FLUTTER_CRASH_SERVER_BASE_URL']; + final String overrideUrl = _platform.environment['FLUTTER_CRASH_SERVER_BASE_URL']; if (overrideUrl != null) { return Uri.parse(overrideUrl); @@ -90,7 +91,7 @@ class CrashReportSender { return; } - globals.printTrace('Sending crash report to Google.'); + _logger.printTrace('Sending crash report to Google.'); final Uri uri = _baseUrl.replace( queryParameters: { @@ -103,8 +104,8 @@ class CrashReportSender { req.fields['uuid'] = _usage.clientId; req.fields['product'] = _kProductId; req.fields['version'] = flutterVersion; - req.fields['osName'] = globals.platform.operatingSystem; - req.fields['osVersion'] = globals.os.name; // this actually includes version + req.fields['osName'] = _platform.operatingSystem; + req.fields['osVersion'] = _operatingSystemUtils.name; // this actually includes version req.fields['type'] = _kDartTypeId; req.fields['error_runtime_type'] = '${error.runtimeType}'; req.fields['error_message'] = '$error'; @@ -120,20 +121,20 @@ class CrashReportSender { if (resp.statusCode == 200) { final String reportId = await http.ByteStream(resp.stream) - .bytesToString(); - globals.printTrace('Crash report sent (report ID: $reportId)'); + .bytesToString(); + _logger.printTrace('Crash report sent (report ID: $reportId)'); _crashReportSent = true; } else { - globals.printError('Failed to send crash report. Server responded with HTTP status code ${resp.statusCode}'); + _logger.printError('Failed to send crash report. Server responded with HTTP status code ${resp.statusCode}'); } // Catch all exceptions to print the message that makes clear that the // crash logger crashed. } catch (sendError, sendStackTrace) { // ignore: avoid_catches_without_on_clauses if (sendError is SocketException || sendError is HttpException) { - globals.printError('Failed to send crash report due to a network error: $sendError'); + _logger.printError('Failed to send crash report due to a network error: $sendError'); } else { // If the sender itself crashes, just print. We did our best. - globals.printError('Crash report sender itself crashed: $sendError\n$sendStackTrace'); + _logger.printError('Crash report sender itself crashed: $sendError\n$sendStackTrace'); } } } diff --git a/packages/flutter_tools/lib/src/reporting/events.dart b/packages/flutter_tools/lib/src/reporting/events.dart index 8b0274bef5a10..9fdf7c28ca785 100644 --- a/packages/flutter_tools/lib/src/reporting/events.dart +++ b/packages/flutter_tools/lib/src/reporting/events.dart @@ -129,7 +129,8 @@ class PubResultEvent extends UsageEvent { PubResultEvent({ @required String context, @required String result, - }) : super('pub-result', context, label: result, flutterUsage: globals.flutterUsage); + @required Usage usage, + }) : super('pub-result', context, label: result, flutterUsage: usage); } /// An event that reports something about a build. diff --git a/packages/flutter_tools/lib/src/reporting/reporting.dart b/packages/flutter_tools/lib/src/reporting/reporting.dart index e583eb4eb2241..3666946c27dd8 100644 --- a/packages/flutter_tools/lib/src/reporting/reporting.dart +++ b/packages/flutter_tools/lib/src/reporting/reporting.dart @@ -10,11 +10,13 @@ import 'package:file/file.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; import 'package:usage/usage_io.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; +import '../base/os.dart'; import '../base/process.dart'; import '../base/time.dart'; import '../build_system/exceptions.dart'; diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 816e4884e9746..c3b7beede94ff 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -59,7 +59,7 @@ class FlutterDevice { targetModel: targetModel, experimentalFlags: experimentalFlags, dartDefines: buildInfo.dartDefines, - packagesPath: PackageMap.globalPackagesPath, + packagesPath: globalPackagesPath, ); /// Create a [FlutterDevice] with optional code generation enabled. @@ -107,7 +107,7 @@ class FlutterDevice { dartDefines: buildInfo.dartDefines, librariesSpec: globals.fs.file(globals.artifacts .getArtifactPath(Artifact.flutterWebLibrariesJson)).uri.toString(), - packagesPath: PackageMap.globalPackagesPath, + packagesPath: globalPackagesPath, ); } else { generator = ResidentCompiler( @@ -124,7 +124,7 @@ class FlutterDevice { experimentalFlags: experimentalFlags, dartDefines: buildInfo.dartDefines, initializeFromDill: globals.fs.path.join(getBuildDirectory(), 'cache.dill'), - packagesPath: PackageMap.globalPackagesPath, + packagesPath: globalPackagesPath, ); } @@ -152,7 +152,7 @@ class FlutterDevice { final ResidentCompiler generator; final BuildInfo buildInfo; Stream observatoryUris; - VMService vmService; + vm_service.VmService vmService; DevFS devFS; ApplicationPackage package; List fileSystemRoots; @@ -226,24 +226,28 @@ class FlutterDevice { return completer.future; } + // TODO(jonahwilliams): remove once all callsites are updated. + VMService get flutterDeprecatedVmService => vmService as VMService; + Future refreshViews() async { if (vmService == null) { return; } - await vmService.vm.refreshViews(waitForViews: true); + await flutterDeprecatedVmService.vm.refreshViews(waitForViews: true); } List get views { - if (vmService == null || vmService.isClosed) { + if (vmService == null || flutterDeprecatedVmService.isClosed) { return []; } + return (viewFilter != null - ? vmService.vm.allViewsWithName(viewFilter) - : vmService.vm.views).toList(); + ? flutterDeprecatedVmService.vm.allViewsWithName(viewFilter) + : flutterDeprecatedVmService.vm.views).toList(); } - Future getVMs() => vmService.getVMOld(); + Future getVMs() => flutterDeprecatedVmService.getVMOld(); Future exitApps() async { if (!device.supportsFlutterExit) { @@ -270,7 +274,9 @@ class FlutterDevice { for (final FlutterView view in flutterViews) { if (view != null && view.uiIsolate != null) { assert(!view.uiIsolate.pauseEvent.isPauseEvent); - futures.add(view.uiIsolate.flutterExit()); + futures.add(vmService.flutterExit( + isolateId: view.uiIsolate.id, + )); } } // The flutterExit message only returns if it fails, so just wait a few @@ -286,7 +292,7 @@ class FlutterDevice { }) { // One devFS per device. Shared by all running instances. devFS = DevFS( - vmService, + flutterDeprecatedVmService, fsName, rootDirectory, osUtils: globals.os, @@ -301,7 +307,7 @@ class FlutterDevice { final String deviceEntryUri = devFS.baseUri .resolveUri(globals.fs.path.toUri(entryPath)).toString(); return >[ - for (final Isolate isolate in vmService.vm.isolates) + for (final Isolate isolate in flutterDeprecatedVmService.vm.isolates) vmService.reloadSources( isolate.id, pause: pause, @@ -325,68 +331,91 @@ class FlutterDevice { Future debugDumpApp() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpApp(); + await vmService.flutterDebugDumpApp( + isolateId: view.uiIsolate.id, + ); } } Future debugDumpRenderTree() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpRenderTree(); + await vmService.flutterDebugDumpRenderTree( + isolateId: view.uiIsolate.id, + ); } } Future debugDumpLayerTree() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpLayerTree(); + await vmService.flutterDebugDumpLayerTree( + isolateId: view.uiIsolate.id, + ); } } Future debugDumpSemanticsTreeInTraversalOrder() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpSemanticsTreeInTraversalOrder(); + await vmService.flutterDebugDumpSemanticsTreeInTraversalOrder( + isolateId: view.uiIsolate.id, + ); } } Future debugDumpSemanticsTreeInInverseHitTestOrder() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(); + await vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder( + isolateId: view.uiIsolate.id, + ); } } Future toggleDebugPaintSizeEnabled() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleDebugPaintSizeEnabled(); + await vmService.flutterToggleDebugPaintSizeEnabled( + isolateId: view.uiIsolate.id, + ); } } Future toggleDebugCheckElevationsEnabled() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleDebugCheckElevationsEnabled(); + await vmService.flutterToggleDebugCheckElevationsEnabled( + isolateId: view.uiIsolate.id, + ); } } Future debugTogglePerformanceOverlayOverride() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterTogglePerformanceOverlayOverride(); + await vmService.flutterTogglePerformanceOverlayOverride( + isolateId: view.uiIsolate.id, + ); } } Future toggleWidgetInspector() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleWidgetInspector(); + await vmService.flutterToggleWidgetInspector( + isolateId: view.uiIsolate.id, + ); } } Future toggleProfileWidgetBuilds() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleProfileWidgetBuilds(); + await vmService.flutterToggleProfileWidgetBuilds( + isolateId: view.uiIsolate.id, + ); } } Future togglePlatform({ String from }) async { final String to = nextPlatform(from, featureFlags); for (final FlutterView view in views) { - await view.uiIsolate.flutterPlatformOverride(to); + await vmService.flutterPlatformOverride( + platform: to, + isolateId: view.uiIsolate.id, + ); } return to; } @@ -416,7 +445,9 @@ class FlutterDevice { } Future initLogReader() async { - (await device.getLogReader(app: package)).appPid = vmService.vm.pid; + final vm_service.VM vm = await vmService.getVM(); + final DeviceLogReader logReader = await device.getLogReader(app: package); + logReader.appPid = vm.pid; } Future runHot({ @@ -634,7 +665,7 @@ abstract class ResidentRunner { String dillOutputPath, }) : mainPath = findMainDartFile(target), projectRootPath = projectRootPath ?? globals.fs.currentDirectory.path, - packagesFilePath = packagesFilePath ?? globals.fs.path.absolute(PackageMap.globalPackagesPath), + packagesFilePath = packagesFilePath ?? globals.fs.path.absolute(globalPackagesPath), _dillOutputPath = dillOutputPath, artifactDirectory = dillOutputPath == null ? globals.fs.systemTempDirectory.createTempSync('flutter_tool.') @@ -721,8 +752,16 @@ abstract class ResidentRunner { String method, { Map params, }) { - return flutterDevices.first.views.first.uiIsolate - .invokeFlutterExtensionRpcRaw(method, params: params); + return flutterDevices + .first + .vmService + .invokeFlutterExtensionRpcRaw( + method, + args: params, + isolateId: flutterDevices + .first.views + .first.uiIsolate.id + ); } /// Whether this runner can hot reload. @@ -812,7 +851,7 @@ abstract class ResidentRunner { void writeVmserviceFile() { if (debuggingOptions.vmserviceOutFile != null) { try { - final String address = flutterDevices.first.vmService.wsAddress.toString(); + final String address = flutterDevices.first.flutterDeprecatedVmService.wsAddress.toString(); final File vmserviceOutFile = globals.fs.file(debuggingOptions.vmserviceOutFile); vmserviceOutFile.createSync(recursive: true); vmserviceOutFile.writeAsStringSync(address); @@ -944,7 +983,10 @@ abstract class ResidentRunner { await device.refreshViews(); try { for (final FlutterView view in device.views) { - await view.uiIsolate.flutterDebugAllowBanner(false); + await device.vmService.flutterDebugAllowBanner( + false, + isolateId: view.uiIsolate.id, + ); } } on Exception catch (error) { status.cancel(); @@ -958,7 +1000,10 @@ abstract class ResidentRunner { if (supportsServiceProtocol && isRunningDebug) { try { for (final FlutterView view in device.views) { - await view.uiIsolate.flutterDebugAllowBanner(true); + await device.vmService.flutterDebugAllowBanner( + true, + isolateId: view.uiIsolate.id, + ); } } on Exception catch (error) { status.cancel(); @@ -980,7 +1025,12 @@ abstract class ResidentRunner { Future debugTogglePlatform() async { await refreshViews(); - final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride(); + final String isolateId = flutterDevices + .first.views.first.uiIsolate.id; + final String from = await flutterDevices + .first.vmService.flutterPlatformOverride( + isolateId: isolateId, + ); String to; for (final FlutterDevice device in flutterDevices) { to = await device.togglePlatform(from: from); @@ -1039,7 +1089,7 @@ abstract class ResidentRunner { // This hooks up callbacks for when the connection stops in the future. // We don't want to wait for them. We don't handle errors in those callbacks' // futures either because they just print to logger and is not critical. - unawaited(device.vmService.done.then( + unawaited(device.vmService.onDone.then( _serviceProtocolDone, onError: _serviceProtocolError, ).whenComplete(_serviceDisconnected)); @@ -1056,7 +1106,7 @@ abstract class ResidentRunner { { 'reuseWindows': true, }, - flutterDevices.first.vmService.httpAddress, + flutterDevices.first.flutterDeprecatedVmService.httpAddress, 'http://${_devtoolsServer.address.host}:${_devtoolsServer.port}', false, // headless mode, false, // machine mode diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index 177942604f08f..1b8427979e366 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart @@ -83,8 +83,8 @@ class ColdRunner extends ResidentRunner { if (flutterDevices.first.observatoryUris != null) { // For now, only support one debugger connection. connectionInfoCompleter?.complete(DebugConnectionInfo( - httpUri: flutterDevices.first.vmService.httpAddress, - wsUri: flutterDevices.first.vmService.wsAddress, + httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress, + wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress, )); } @@ -105,7 +105,7 @@ class ColdRunner extends ResidentRunner { if (device.vmService != null) { globals.printStatus('Tracing startup on ${device.device.name}.'); await downloadStartupTrace( - device.vmService, + device.flutterDeprecatedVmService, awaitFirstFrame: awaitFirstFrameWhenTracing, ); } @@ -197,7 +197,7 @@ class ColdRunner extends ResidentRunner { // Caution: This log line is parsed by device lab tests. globals.printStatus( 'An Observatory debugger and profiler on $dname is available at: ' - '${device.vmService.httpAddress}', + '${device.flutterDeprecatedVmService.httpAddress}', ); } } diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 55094f410de93..9a2c6414561df 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -157,15 +157,9 @@ class HotRunner extends ResidentRunner { final Stopwatch stopwatch = Stopwatch()..start(); final UpdateFSReport results = UpdateFSReport(success: true); final List invalidated = [Uri.parse(libraryId)]; - final PackageConfig packageConfig = await loadPackageConfigUri( - globals.fs.file(PackageMap.globalPackagesPath).absolute.uri, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + final PackageConfig packageConfig = await loadPackageConfigOrFail( + globals.fs.file(globalPackagesPath), + logger: globals.logger, ); for (final FlutterDevice device in flutterDevices) { results.incorporateResults(await device.updateDevFS( @@ -205,7 +199,10 @@ class HotRunner extends ResidentRunner { for (final FlutterDevice device in flutterDevices) { for (final FlutterView view in device.views) { - await view.uiIsolate.flutterFastReassemble(classId); + await device.vmService.flutterFastReassemble( + classId, + isolateId: view.uiIsolate.id, + ); } } @@ -260,8 +257,8 @@ class HotRunner extends ResidentRunner { // Only handle one debugger connection. connectionInfoCompleter.complete( DebugConnectionInfo( - httpUri: flutterDevices.first.vmService.httpAddress, - wsUri: flutterDevices.first.vmService.wsAddress, + httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress, + wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress, baseUri: baseUris.first.toString(), ), ); @@ -358,15 +355,9 @@ class HotRunner extends ResidentRunner { firstBuildTime = DateTime.now(); final List> startupTasks = >[]; - final PackageConfig packageConfig = await loadPackageConfigUri( - globals.fs.file(PackageMap.globalPackagesPath).absolute.uri, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + final PackageConfig packageConfig = await loadPackageConfigOrFail( + globals.fs.file(globalPackagesPath), + logger: globals.logger, ); for (final FlutterDevice device in flutterDevices) { // Here we initialize the frontend_server concurrently with the platform @@ -570,7 +561,7 @@ class HotRunner extends ResidentRunner { // The engine handles killing and recreating isolates that it has spawned // ("uiIsolates"). The isolates that were spawned from these uiIsolates // will not be restared, and so they must be manually killed. - for (final Isolate isolate in device?.vmService?.vm?.isolates ?? []) { + for (final Isolate isolate in device?.flutterDeprecatedVmService?.vm?.isolates ?? []) { if (!uiIsolates.contains(isolate)) { operations.add(isolate.invokeRpcRaw('kill', params: { 'isolateId': isolate.id, @@ -938,10 +929,16 @@ class HotRunner extends ResidentRunner { } await Future.wait(allDevices); - // Check if any isolates are paused. + globals.printTrace('Evicting dirty assets'); + await _evictDirtyAssets(); + + // Check if any isolates are paused and reassemble those + // that aren't. final List reassembleViews = []; + final List> reassembleFutures = >[]; String serviceEventKind; int pausedIsolatesFound = 0; + bool failedReassemble = false; for (final FlutterDevice device in flutterDevices) { for (final FlutterView view in device.views) { // Check if the isolate is paused, and if so, don't reassemble. Ignore the @@ -956,6 +953,12 @@ class HotRunner extends ResidentRunner { } } else { reassembleViews.add(view); + reassembleFutures.add(device.vmService.flutterReassemble( + isolateId: view.uiIsolate.id, + ).catchError((dynamic error) { + failedReassemble = true; + globals.printError('Reassembling ${view.uiIsolate.name} failed: $error'); + }, test: (dynamic error) => error is Exception)); } } } @@ -968,24 +971,10 @@ class HotRunner extends ResidentRunner { return OperationResult(OperationResult.ok.code, reloadMessage); } } - globals.printTrace('Evicting dirty assets'); - await _evictDirtyAssets(); assert(reassembleViews.isNotEmpty); + globals.printTrace('Reassembling application'); - bool failedReassemble = false; - final List> futures = >[ - for (final FlutterView view in reassembleViews) - () async { - try { - await view.uiIsolate.flutterReassemble(); - } on Exception catch (error) { - failedReassemble = true; - globals.printError('Reassembling ${view.uiIsolate.name} failed: $error'); - return; - } - }(), - ]; - final Future reassembleFuture = Future.wait(futures); + final Future reassembleFuture = Future.wait(reassembleFutures); await reassembleFuture.timeout( const Duration(seconds: 2), onTimeout: () async { @@ -1128,7 +1117,7 @@ class HotRunner extends ResidentRunner { // Caution: This log line is parsed by device lab tests. globals.printStatus( 'An Observatory debugger and profiler on $dname is available at: ' - '${device.vmService.httpAddress}', + '${device.flutterDeprecatedVmService.httpAddress}', ); } } @@ -1144,7 +1133,13 @@ class HotRunner extends ResidentRunner { continue; } for (final String assetPath in device.devFS.assetPathsToEvict) { - futures.add(device.views.first.uiIsolate.flutterEvictAsset(assetPath)); + futures.add( + device.views.first.uiIsolate.vmService + .flutterEvictAsset( + assetPath, + isolateId: device.views.first.uiIsolate.id, + ) + ); } device.devFS.assetPathsToEvict.clear(); } @@ -1294,15 +1289,9 @@ class ProjectFileInvalidator { } Future _createPackageConfig(String packagesPath) { - return loadPackageConfigUri( - _fileSystem.file(packagesPath).absolute.uri, - loader: (Uri uri) { - final File file = _fileSystem.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + return loadPackageConfigOrFail( + _fileSystem.file(globalPackagesPath), + logger: _logger, ); } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 31a9dc9dca3cb..d7cd98ddb3a2a 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -470,14 +470,14 @@ abstract class FlutterCommand extends Command { ); } - void addEnableExperimentation({ bool verbose }) { + void addEnableExperimentation({ bool hide = false }) { argParser.addMultiOption( FlutterOptions.kEnableExperiment, help: 'The name of an experimental Dart feature to enable. For more info ' 'see: https://github.com/dart-lang/sdk/blob/master/docs/process/' 'experimental-flags.md', - hide: !verbose, + hide: hide, ); } @@ -542,18 +542,27 @@ abstract class FlutterCommand extends Command { boolArg('track-widget-creation'); final String buildNumber = argParser.options.containsKey('build-number') - ? stringArg('build-number') - : null; + ? stringArg('build-number') + : null; + final List experiments = + argParser.options.containsKey(FlutterOptions.kEnableExperiment) + ? stringsArg(FlutterOptions.kEnableExperiment) + : []; + final List extraGenSnapshotOptions = + argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions) + ? stringsArg(FlutterOptions.kExtraGenSnapshotOptions) + : []; final List extraFrontEndOptions = - argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions) - ? stringsArg(FlutterOptions.kExtraFrontEndOptions) - : []; - if (argParser.options.containsKey(FlutterOptions.kEnableExperiment) && - argResults[FlutterOptions.kEnableExperiment] != null) { - for (final String expFlag in stringsArg(FlutterOptions.kEnableExperiment)) { + argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions) + ? stringsArg(FlutterOptions.kExtraFrontEndOptions) + : []; + + if (experiments.isNotEmpty) { + for (final String expFlag in experiments) { final String flag = '--enable-experiment=' + expFlag; extraFrontEndOptions.add(flag); + extraGenSnapshotOptions.add(flag); } } @@ -579,8 +588,8 @@ abstract class FlutterCommand extends Command { extraFrontEndOptions: extraFrontEndOptions?.isNotEmpty ?? false ? extraFrontEndOptions : null, - extraGenSnapshotOptions: argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions) - ? stringsArg(FlutterOptions.kExtraGenSnapshotOptions) + extraGenSnapshotOptions: extraGenSnapshotOptions?.isNotEmpty ?? false + ? extraGenSnapshotOptions : null, fileSystemRoots: argParser.options.containsKey(FlutterOptions.kFileSystemRoot) ? stringsArg(FlutterOptions.kFileSystemRoot) @@ -600,6 +609,7 @@ abstract class FlutterCommand extends Command { dartDefines: argParser.options.containsKey(FlutterOptions.kDartDefinesOption) ? stringsArg(FlutterOptions.kDartDefinesOption) : const [], + dartExperiments: experiments, ); } @@ -818,7 +828,7 @@ abstract class FlutterCommand extends Command { @protected @mustCallSuper Future validateCommand() async { - if (_requiresPubspecYaml && !PackageMap.isUsingCustomPackagesPath) { + if (_requiresPubspecYaml && !isUsingCustomPackagesPath) { // Don't expect a pubspec.yaml file if the user passed in an explicit .packages file path. // If there is no pubspec in the current directory, look in the parent @@ -835,14 +845,6 @@ abstract class FlutterCommand extends Command { if (changedDirectory) { globals.printStatus('Changing current working directory to: ${globals.fs.currentDirectory.path}'); } - - // Validate the current package map only if we will not be running "pub get" later. - if (parent?.name != 'pub' && !(_usesPubOption && boolArg('pub'))) { - final String error = PackageMap(PackageMap.globalPackagesPath, fileSystem: globals.fs).checkValid(); - if (error != null) { - throw ToolExit(error); - } - } } if (_usesTargetOption) { diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 5059c4054e247..9ba234948f6ec 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -314,7 +314,7 @@ class FlutterCommandRunner extends CommandRunner { } if (topLevelResults.wasParsed('packages')) { - PackageMap.globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(topLevelResults['packages'] as String)); + globalPackagesPath = globals.fs.path.normalize(globals.fs.path.absolute(topLevelResults['packages'] as String)); } // See if the user specified a specific device. @@ -354,19 +354,9 @@ class FlutterCommandRunner extends CommandRunner { if (engineSourcePath == null && globalResults['local-engine'] != null) { try { - final PackageConfig packageConfig = await loadPackageConfigUri( - globals.fs.file(PackageMap.globalPackagesPath).absolute.uri, - onError: (dynamic error) { - // Errors indicate the automatic detection will fail, but are not - // fatal. - }, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - }, + final PackageConfig packageConfig = await loadPackageConfigOrFail( + globals.fs.file(globalPackagesPath), + logger: globals.logger, ); Uri engineUri = packageConfig[kFlutterEnginePackageName]?.packageUriRoot; // Skip if sky_engine is the self-contained one. diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index 499c8aba18393..31e8bc7f7a7e1 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart @@ -3,6 +3,8 @@ // found in the LICENSE file. import 'package:meta/meta.dart'; +import 'package:package_config/package_config.dart'; +import 'package:package_config/package_config_types.dart'; import 'base/common.dart'; import 'base/file_system.dart'; @@ -247,8 +249,11 @@ Future _templateImageDirectory(String name, FileSystem fileSystem) as if (!fileSystem.file(packageFilePath).existsSync()) { await _ensurePackageDependencies(toolPackagePath); } - final PackageMap packageConfig = PackageMap(packageFilePath, fileSystem: fileSystem); - final Uri imagePackageLibDir = packageConfig.map['flutter_template_images']; + final PackageConfig packageConfig = await loadPackageConfigOrFail( + fileSystem.file(packageFilePath), + logger: globals.logger, + ); + final Uri imagePackageLibDir = packageConfig['flutter_template_images']?.packageUriRoot; // Ensure that the template image package is present. if (imagePackageLibDir == null || !fileSystem.directory(imagePackageLibDir).existsSync()) { await _ensurePackageDependencies(toolPackagePath); diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart index be30846906088..cb18c6ccc3f3b 100644 --- a/packages/flutter_tools/lib/src/test/coverage_collector.dart +++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart @@ -115,7 +115,7 @@ class CoverageCollector extends TestWatcher { return null; } if (formatter == null) { - final coverage.Resolver resolver = coverage.Resolver(packagesPath: PackageMap.globalPackagesPath); + final coverage.Resolver resolver = coverage.Resolver(packagesPath: globalPackagesPath); final String packagePath = globals.fs.currentDirectory.path; final List reportOn = coverageDirectory == null ? [globals.fs.path.join(packagePath, 'lib')] diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index a27aa8f50c79d..a27e406abd8cd 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -459,7 +459,7 @@ class FlutterPlatform extends PlatformPlugin { final Process process = await _startProcess( shellPath, mainDart, - packages: PackageMap.globalPackagesPath, + packages: globalPackagesPath, enableObservatory: enableObservatory, startPaused: startPaused, disableServiceAuthCodes: disableServiceAuthCodes, diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index fa153ec3cc9ae..9ba5dad731a7a 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -97,32 +97,20 @@ class FlutterWebPlatform extends PlatformPlugin { ); } - final Future _packagesFuture = loadPackageConfigUri( - Uri.base.resolve('.packages'), - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + final Future _packagesFuture = loadPackageConfigOrFail( + globals.fs.file(globalPackagesPath), + logger: globals.logger, ); - final Future _flutterToolsPackageMap = loadPackageConfigUri( + final Future _flutterToolsPackageMap = loadPackageConfigOrFail( globals.fs.file(globals.fs.path.join( Cache.flutterRoot, 'packages', 'flutter_tools', '.packages', - )).absolute.uri, - loader: (Uri uri) { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } - ); + )), + logger: globals.logger, + ); /// Uri of the test package. Future get testUri async => (await _flutterToolsPackageMap)['test']?.packageUriRoot; @@ -567,7 +555,7 @@ class BrowserManager { } /// The browser instance that this is connected to via [_channel]. - final Chrome _browser; + final Chromium _browser; // TODO(nweiz): Consider removing the duplication between this and // [_browser.name]. @@ -635,8 +623,16 @@ class BrowserManager { bool debug = false, bool headless = true, }) async { - final Chrome chrome = - await globals.chromeLauncher.launch(url.toString(), headless: headless); + final ChromiumLauncher chromiumLauncher = ChromiumLauncher( + browserFinder: findChromeExecutable, + fileSystem: globals.fs, + operatingSystemUtils: globals.os, + logger: globals.logger, + platform: globals.platform, + processManager: globals.processManager, + ); + final Chromium chrome = + await chromiumLauncher.launch(url.toString(), headless: headless); final Completer completer = Completer(); @@ -874,7 +870,7 @@ class TestGoldenComparator { shellPath, '--disable-observatory', '--non-interactive', - '--packages=${PackageMap.globalPackagesPath}', + '--packages=$globalPackagesPath', output, ]; diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index 3be01e8b12bbf..8df2eff329a1d 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -31,6 +31,8 @@ abstract class FlutterTestRunner { Directory workDir, List names = const [], List plainNames = const [], + String tags, + String excludeTags, bool enableObservatory = false, bool startPaused = false, bool disableServiceAuthCodes = false, @@ -62,6 +64,8 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { Directory workDir, List names = const [], List plainNames = const [], + String tags, + String excludeTags, bool enableObservatory = false, bool startPaused = false, bool disableServiceAuthCodes = false, @@ -104,6 +108,10 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { ...['--plain-name', plainName], if (randomSeed != null) '--test-randomize-ordering-seed=$randomSeed', + if (tags != null) + ...['--tags', tags], + if (excludeTags != null) + ...['--exclude-tags', excludeTags], ]; if (web) { final String tempBuildDir = globals.fs.systemTempDirectory @@ -171,8 +179,8 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { // Make the global packages path absolute. // (Makes sure it still works after we change the current directory.) - PackageMap.globalPackagesPath = - globals.fs.path.normalize(globals.fs.path.absolute(PackageMap.globalPackagesPath)); + globalPackagesPath = + globals.fs.path.normalize(globals.fs.path.absolute(globalPackagesPath)); // Call package:test's main method in the appropriate directory. final Directory saved = globals.fs.currentDirectory; diff --git a/packages/flutter_tools/lib/src/test/test_compiler.dart b/packages/flutter_tools/lib/src/test/test_compiler.dart index c569c37978679..c6c5bc7dcb612 100644 --- a/packages/flutter_tools/lib/src/test/test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/test_compiler.dart @@ -103,7 +103,7 @@ class TestCompiler { initializeFromDill: testFilePath, unsafePackageSerialization: false, dartDefines: const [], - packagesPath: PackageMap.globalPackagesPath, + packagesPath: globalPackagesPath, ); if (flutterProject.hasBuilders) { return CodeGeneratingResidentCompiler.create( @@ -126,15 +126,9 @@ class TestCompiler { if (!isEmpty) { return; } - _packageConfig ??= await loadPackageConfigUri( - globals.fs.file(PackageMap.globalPackagesPath).absolute.uri, - loader: (Uri uri) async { - final File file = globals.fs.file(uri); - if (!file.existsSync()) { - return null; - } - return file.readAsBytes(); - } + _packageConfig ??= await loadPackageConfigOrFail( + globals.fs.file(globalPackagesPath), + logger: globals.logger, ); while (compilationQueue.isNotEmpty) { final _CompilationRequest request = compilationQueue.first; diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart index 1158886447c67..8ae000a119ae9 100644 --- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart +++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart @@ -128,7 +128,7 @@ class FlutterTesterDevice extends Device { '--run-forever', '--non-interactive', '--enable-dart-profiling', - '--packages=${PackageMap.globalPackagesPath}', + '--packages=$globalPackagesPath', ]; if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.startPaused) { diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart index 5ea6cfb1edef4..6217717f15666 100644 --- a/packages/flutter_tools/lib/src/tracing.dart +++ b/packages/flutter_tools/lib/src/tracing.dart @@ -54,7 +54,10 @@ class Tracing { }); bool done = false; for (final FlutterView view in vmService.vm.views) { - if (await view.uiIsolate.flutterAlreadyPaintedFirstUsefulFrame()) { + if (await view.uiIsolate.vmService + .flutterAlreadyPaintedFirstUsefulFrame( + isolateId: view.uiIsolate.id, + )) { done = true; break; } @@ -99,7 +102,7 @@ Future downloadStartupTrace(VMService observatory, { bool awaitFirstFrame int extractInstantEventTimestamp(String eventName) { final List> events = - List>.from((timeline['traceEvents'] as List).cast>()); + List>.from(timeline['traceEvents'] as List); final Map event = events.firstWhere( (Map event) => event['name'] == eventName, orElse: () => null, ); diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 3de223858ad52..75b6f92c24781 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:meta/meta.dart' show required; import 'package:vm_service/vm_service.dart' as vm_service; @@ -476,6 +475,18 @@ class VMService implements vm_service.VmService { return _delegateService.callMethod(method, isolateId: isolateId, args: args); } + @override + Future get onDone => _delegateService.onDone; + + @override + Future callServiceExtension(String method, + {String isolateId, Map args}) { + return _delegateService.callServiceExtension(method, isolateId: isolateId, args: args); + } + + @override + Future getVM() => _delegateService.getVM(); + StreamController _getEventController(String eventName) { StreamController controller = _eventControllers[eventName]; if (controller == null) { @@ -890,7 +901,6 @@ class VM extends ServiceObjectOwner { _upgradeCollection(map, this); _loaded = true; - _pid = map['pid'] as int; if (map['_heapAllocatedMemoryUsage'] != null) { _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'] as int; } @@ -910,10 +920,6 @@ class VM extends ServiceObjectOwner { /// The set of live views. final Map _viewCache = {}; - /// The pid of the VM's process. - int _pid; - int get pid => _pid; - /// The number of bytes allocated (e.g. by malloc) in the native heap. int _heapAllocatedMemoryUsage; int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0; @@ -1131,44 +1137,6 @@ class VM extends ServiceObjectOwner { } } -class HeapSpace extends ServiceObject { - HeapSpace._empty(ServiceObjectOwner owner) : super._empty(owner); - - int _used = 0; - int _capacity = 0; - int _external = 0; - int _collections = 0; - double _totalCollectionTimeInSeconds = 0.0; - double _averageCollectionPeriodInMillis = 0.0; - - int get used => _used; - int get capacity => _capacity; - int get external => _external; - - Duration get avgCollectionTime { - final double mcs = _totalCollectionTimeInSeconds * - Duration.microsecondsPerSecond / - math.max(_collections, 1); - return Duration(microseconds: mcs.ceil()); - } - - Duration get avgCollectionPeriod { - final double mcs = _averageCollectionPeriodInMillis * - Duration.microsecondsPerMillisecond; - return Duration(microseconds: mcs.ceil()); - } - - @override - void _update(Map map, bool mapIsRef) { - _used = map['used'] as int; - _capacity = map['capacity'] as int; - _external = map['external'] as int; - _collections = map['collections'] as int; - _totalCollectionTimeInSeconds = map['time'] as double; - _averageCollectionPeriodInMillis = map['avgCollectionPeriodMillis'] as double; - } -} - /// An isolate running inside the VM. Instances of the Isolate class are always /// canonicalized. class Isolate extends ServiceObjectOwner { @@ -1250,118 +1218,6 @@ class Isolate extends ServiceObjectOwner { pauseEvent = map['pauseEvent'] as ServiceEvent; } - // Flutter extension methods. - - // Invoke a flutter extension method, if the flutter extension is not - // available, returns null. - Future> invokeFlutterExtensionRpcRaw( - String method, { - Map params, - }) async { - try { - return await invokeRpcRaw(method, params: params); - } on vm_service.RPCError catch (err) { - // If an application is not using the framework - if (err.code == RPCErrorCodes.kMethodNotFound) { - return null; - } - rethrow; - } - } - - Future> flutterDebugDumpApp() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp'); - } - - Future> flutterDebugDumpRenderTree() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree'); - } - - Future> flutterDebugDumpLayerTree() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree'); - } - - Future> flutterDebugDumpSemanticsTreeInTraversalOrder() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder'); - } - - Future> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder'); - } - - Future> _flutterToggle(String name) async { - Map state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name'); - if (state != null && state.containsKey('enabled') && state['enabled'] is String) { - state = await invokeFlutterExtensionRpcRaw( - 'ext.flutter.$name', - params: {'enabled': state['enabled'] == 'true' ? 'false' : 'true'}, - ); - } - return state; - } - - Future> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint'); - - Future> flutterToggleDebugCheckElevationsEnabled() => _flutterToggle('debugCheckElevationsEnabled'); - - Future> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay'); - - Future> flutterToggleWidgetInspector() => _flutterToggle('inspector.show'); - - Future> flutterToggleProfileWidgetBuilds() => _flutterToggle('profileWidgetBuilds'); - - Future> flutterDebugAllowBanner(bool show) { - return invokeFlutterExtensionRpcRaw( - 'ext.flutter.debugAllowBanner', - params: {'enabled': show ? 'true' : 'false'}, - ); - } - - Future> flutterReassemble() { - return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble'); - } - - Future> flutterFastReassemble(String classId) { - return invokeFlutterExtensionRpcRaw('ext.flutter.fastReassemble', params: { - 'class': classId, - }); - } - - Future flutterAlreadyPaintedFirstUsefulFrame() async { - final Map result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameRasterizedEvent'); - // result might be null when the service extension is not initialized - return result != null && result['enabled'] == 'true'; - } - - Future> uiWindowScheduleFrame() { - return invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame'); - } - - Future> flutterEvictAsset(String assetPath) { - return invokeFlutterExtensionRpcRaw( - 'ext.flutter.evict', - params: { - 'value': assetPath, - }, - ); - } - - // Application control extension methods. - Future> flutterExit() { - return invokeFlutterExtensionRpcRaw('ext.flutter.exit'); - } - - Future flutterPlatformOverride([ String platform ]) async { - final Map result = await invokeFlutterExtensionRpcRaw( - 'ext.flutter.platformOverride', - params: platform != null ? {'value': platform} : {}, - ); - if (result != null && result['value'] is String) { - return result['value'] as String; - } - return 'unknown'; - } - @override String toString() => 'Isolate $id'; } @@ -1523,4 +1379,213 @@ extension FlutterVmService on vm_service.VmService { ); await onRunnable; } + + Future> flutterDebugDumpApp({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpApp', + isolateId: isolateId, + ); + } + + Future> flutterDebugDumpRenderTree({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpRenderTree', + isolateId: isolateId, + ); + } + + Future> flutterDebugDumpLayerTree({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpLayerTree', + isolateId: isolateId, + ); + } + + Future> flutterDebugDumpSemanticsTreeInTraversalOrder({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', + isolateId: isolateId, + ); + } + + Future> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', + isolateId: isolateId, + ); + } + + Future> _flutterToggle(String name, { + @required String isolateId, + }) async { + Map state = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.$name', + isolateId: isolateId, + ); + if (state != null && state.containsKey('enabled') && state['enabled'] is String) { + state = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.$name', + isolateId: isolateId, + args: { + 'enabled': state['enabled'] == 'true' ? 'false' : 'true', + }, + ); + } + + return state; + } + + Future> flutterToggleDebugPaintSizeEnabled({ + @required String isolateId, + }) => _flutterToggle('debugPaint', isolateId: isolateId); + + Future> flutterToggleDebugCheckElevationsEnabled({ + @required String isolateId, + }) => _flutterToggle('debugCheckElevationsEnabled', isolateId: isolateId); + + Future> flutterTogglePerformanceOverlayOverride({ + @required String isolateId, + }) => _flutterToggle('showPerformanceOverlay', isolateId: isolateId); + + Future> flutterToggleWidgetInspector({ + @required String isolateId, + }) => _flutterToggle('inspector.show', isolateId: isolateId); + + Future> flutterToggleProfileWidgetBuilds({ + @required String isolateId, + }) => _flutterToggle('profileWidgetBuilds', isolateId: isolateId); + + Future> flutterDebugAllowBanner(bool show, { + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugAllowBanner', + isolateId: isolateId, + args: {'enabled': show ? 'true' : 'false'}, + ); + } + + Future> flutterReassemble({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.reassemble', + isolateId: isolateId, + ); + } + + Future> flutterFastReassemble(String classId, { + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.fastReassemble', + isolateId: isolateId, + args: { + 'class': classId, + }, + ); + } + + Future flutterAlreadyPaintedFirstUsefulFrame({ + @required String isolateId, + }) async { + final Map result = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.didSendFirstFrameRasterizedEvent', + isolateId: isolateId, + ); + // result might be null when the service extension is not initialized + return result != null && result['enabled'] == 'true'; + } + + Future> uiWindowScheduleFrame({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.ui.window.scheduleFrame', + isolateId: isolateId, + ); + } + + Future> flutterEvictAsset(String assetPath, { + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.evict', + isolateId: isolateId, + args: { + 'value': assetPath, + }, + ); + } + + /// Exit the application by calling [exit] from `dart:io`. + /// + /// This method is only supported by certain embedders. This is + /// described by [Device.supportsFlutterExit]. + Future> flutterExit({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.exit', + isolateId: isolateId, + ); + } + + /// Return the current platform override for the flutter view running with + /// the main isolate [isolateId]. + /// + /// If a non-null value is provided for [platform], the platform override + /// is updated with this value. + Future flutterPlatformOverride({ + String platform, + @required String isolateId, + }) async { + final Map result = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.platformOverride', + isolateId: isolateId, + args: platform != null + ? {'value': platform} + : {}, + ); + if (result != null && result['value'] is String) { + return result['value'] as String; + } + return 'unknown'; + } + + /// Invoke a flutter extension method, if the flutter extension is not + /// available, returns null. + Future> invokeFlutterExtensionRpcRaw( + String method, { + @required String isolateId, + Map args, + }) async { + try { + + final vm_service.Response response = await callServiceExtension( + method, + args: { + 'isolateId': isolateId, + ...?args, + }, + ); + return response.json; + } on vm_service.RPCError catch (err) { + // If an application is not using the framework + if (err.code == RPCErrorCodes.kMethodNotFound) { + return null; + } + rethrow; + } + } } diff --git a/packages/flutter_tools/lib/src/web/chrome.dart b/packages/flutter_tools/lib/src/web/chrome.dart index e1e882ff9ff84..214c10ff94d4f 100644 --- a/packages/flutter_tools/lib/src/web/chrome.dart +++ b/packages/flutter_tools/lib/src/web/chrome.dart @@ -15,11 +15,13 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/os.dart'; import '../convert.dart'; -import '../globals.dart' as globals; -/// An environment variable used to override the location of chrome. +/// An environment variable used to override the location of Google Chrome. const String kChromeEnvironment = 'CHROME_EXECUTABLE'; +/// An environment variable used to override the location of Microsoft Edge. +const String kEdgeEnvironment = 'EDGE_ENVIRONMENT'; + /// The expected executable name on linux. const String kLinuxExecutable = 'google-chrome'; @@ -27,9 +29,14 @@ const String kLinuxExecutable = 'google-chrome'; const String kMacOSExecutable = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; -/// The expected executable name on Windows. +/// The expected Chrome executable name on Windows. const String kWindowsExecutable = r'Google\Chrome\Application\chrome.exe'; +/// The expected Edge executable name on Windows. +const String kWindowsEdgeExecutable = r'Microsoft\Edge\Application\msedge.exe'; + +typedef BrowserFinder = String Function(Platform, FileSystem); + /// Find the chrome executable on the current platform. /// /// Does not verify whether the executable exists. @@ -63,43 +70,73 @@ String findChromeExecutable(Platform platform, FileSystem fileSystem) { return null; } -@visibleForTesting -void resetChromeForTesting() { - ChromeLauncher._currentCompleter = Completer(); -} - -@visibleForTesting -void launchChromeInstance(Chrome chrome) { - ChromeLauncher._currentCompleter.complete(chrome); +/// Find the Microsoft Edge executable on the current platform. +/// +/// Does not verify whether the executable exists. +String findEdgeExecutable(Platform platform, FileSystem fileSystem) { + if (platform.environment.containsKey(kEdgeEnvironment)) { + return platform.environment[kEdgeEnvironment]; + } + if (platform.isWindows) { + /// The possible locations where the Edge executable can be located on windows. + final List kWindowsPrefixes = [ + platform.environment['LOCALAPPDATA'], + platform.environment['PROGRAMFILES'], + platform.environment['PROGRAMFILES(X86)'], + ]; + final String windowsPrefix = kWindowsPrefixes.firstWhere((String prefix) { + if (prefix == null) { + return false; + } + final String path = fileSystem.path.join(prefix, kWindowsEdgeExecutable); + return fileSystem.file(path).existsSync(); + }, orElse: () => '.'); + return fileSystem.path.join(windowsPrefix, kWindowsEdgeExecutable); + } + // Not yet supported for macOS and Linux. + return ''; } -/// Responsible for launching chrome with devtools configured. -class ChromeLauncher { - const ChromeLauncher({ +/// A launcher for Chromium browsers with devtools configured. +class ChromiumLauncher { + ChromiumLauncher({ @required FileSystem fileSystem, @required Platform platform, @required ProcessManager processManager, @required OperatingSystemUtils operatingSystemUtils, @required Logger logger, + @required BrowserFinder browserFinder, }) : _fileSystem = fileSystem, _platform = platform, _processManager = processManager, _operatingSystemUtils = operatingSystemUtils, - _logger = logger; + _logger = logger, + _browserFinder = browserFinder, + _fileSystemUtils = FileSystemUtils( + fileSystem: fileSystem, + platform: platform, + ); final FileSystem _fileSystem; final Platform _platform; final ProcessManager _processManager; final OperatingSystemUtils _operatingSystemUtils; final Logger _logger; + final BrowserFinder _browserFinder; + final FileSystemUtils _fileSystemUtils; - static bool get hasChromeInstance => _currentCompleter.isCompleted; + bool get hasChromeInstance => _currentCompleter.isCompleted; - static Completer _currentCompleter = Completer(); + Completer _currentCompleter = Completer(); + + @visibleForTesting + void testLaunchChromium(Chromium chromium) { + _currentCompleter.complete(chromium); + } /// Whether we can locate the chrome executable. - bool canFindChrome() { - final String chrome = findChromeExecutable(_platform, _fileSystem); + bool canFindExecutable() { + final String chrome = _browserFinder(_platform, _fileSystem); try { return _processManager.canRun(chrome); } on ArgumentError { @@ -107,7 +144,10 @@ class ChromeLauncher { } } - /// Launch the chrome browser to a particular `host` page. + /// The executable this launcher will use. + String findExecutable() => _browserFinder(_platform, _fileSystem); + + /// Launch a Chromium browser to a particular `host` page. /// /// `headless` defaults to false, and controls whether we open a headless or /// a `headfull` browser. @@ -116,13 +156,19 @@ class ChromeLauncher { /// port is picked automatically. /// /// `skipCheck` does not attempt to make a devtools connection before returning. - Future launch(String url, { bool headless = false, int debugPort, bool skipCheck = false, Directory cacheDir }) async { + Future launch(String url, { + bool headless = false, + int debugPort, + bool skipCheck = false, + Directory cacheDir, + }) async { if (_currentCompleter.isCompleted) { throwToolExit('Only one instance of chrome can be started.'); } - final String chromeExecutable = findChromeExecutable(_platform, _fileSystem); - final Directory userDataDir = _fileSystem.systemTempDirectory.createTempSync('flutter_tools_chrome_device.'); + final String chromeExecutable = _browserFinder(_platform, _fileSystem); + final Directory userDataDir = _fileSystem.systemTempDirectory + .createTempSync('flutter_tools_chrome_device.'); if (cacheDir != null) { // Seed data dir with previous state. @@ -148,7 +194,12 @@ class ChromeLauncher { '--disable-default-apps', '--disable-translate', if (headless) - ...['--headless', '--disable-gpu', '--no-sandbox', '--window-size=2400,1800'], + ...[ + '--headless', + '--disable-gpu', + '--no-sandbox', + '--window-size=2400,1800', + ], url, ]; @@ -180,12 +231,13 @@ class ChromeLauncher { return 'Failed to spawn stderr'; }); final Uri remoteDebuggerUri = await _getRemoteDebuggerUrl(Uri.parse('http://localhost:$port')); - return _connect(Chrome._( + return _connect(Chromium._( port, ChromeConnection('localhost', port), url: url, process: process, remoteDebuggerUri: remoteDebuggerUri, + chromiumLauncher: this, ), skipCheck); } @@ -217,7 +269,7 @@ class ChromeLauncher { if (sourceLocalStorageDir.existsSync()) { targetLocalStorageDir.createSync(recursive: true); - globals.fsUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir); + _fileSystemUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir); } } @@ -236,11 +288,11 @@ class ChromeLauncher { if (sourceLocalStorageDir.existsSync()) { targetLocalStorageDir.createSync(recursive: true); - globals.fsUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir); + _fileSystemUtils.copyDirectorySync(sourceLocalStorageDir, targetLocalStorageDir); } } - static Future _connect(Chrome chrome, bool skipCheck) async { + Future _connect(Chromium chrome, bool skipCheck) async { // The connection is lazy. Try a simple call to make sure the provided // connection is valid. if (!skipCheck) { @@ -256,7 +308,7 @@ class ChromeLauncher { return chrome; } - static Future get connectedInstance => _currentCompleter.future; + Future get connectedInstance => _currentCompleter.future; /// Returns the full URL of the Chrome remote debugger for the main page. /// @@ -281,27 +333,30 @@ class ChromeLauncher { } } -/// A class for managing an instance of Chrome. -class Chrome { - Chrome._( +/// A class for managing an instance of a Chromium browser. +class Chromium { + Chromium._( this.debugPort, this.chromeConnection, { this.url, Process process, this.remoteDebuggerUri, - }) : _process = process; + @required ChromiumLauncher chromiumLauncher, + }) : _process = process, + _chromiumLauncher = chromiumLauncher; final String url; final int debugPort; final Process _process; final ChromeConnection chromeConnection; final Uri remoteDebuggerUri; + final ChromiumLauncher _chromiumLauncher; Future get onExit => _process.exitCode; Future close() async { - if (ChromeLauncher.hasChromeInstance) { - ChromeLauncher._currentCompleter = Completer(); + if (_chromiumLauncher.hasChromeInstance) { + _chromiumLauncher._currentCompleter = Completer(); } chromeConnection.close(); _process?.kill(); diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index e4d99ecdc4680..e6156f7cc4176 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -28,11 +28,12 @@ Future buildWeb( BuildInfo buildInfo, bool initializePlatform, bool csp, + List experiments, ) async { if (!flutterProject.web.existsSync()) { throwToolExit('Missing index.html.'); } - final bool hasWebPlugins = findPlugins(flutterProject) + final bool hasWebPlugins = (await findPlugins(flutterProject)) .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); await injectPlugins(flutterProject, checkProjects: true); final Status status = globals.logger.startProgress('Compiling $target for the Web...', timeout: null); @@ -52,6 +53,8 @@ Future buildWeb( kDartDefines: buildInfo.dartDefines.join(','), kCspMode: csp.toString(), kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), + if (experiments.isNotEmpty) + kEnableExperiment: experiments?.join(','), }, artifacts: globals.artifacts, fileSystem: globals.fs, diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart index 649afa70b887f..d2bdc8ae9f437 100644 --- a/packages/flutter_tools/lib/src/web/web_device.dart +++ b/packages/flutter_tools/lib/src/web/web_device.dart @@ -3,14 +3,17 @@ // found in the LICENSE file. import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; import '../application_package.dart'; import '../base/file_system.dart'; import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/os.dart'; import '../build_info.dart'; import '../device.dart'; import '../features.dart'; -import '../globals.dart' as globals; import '../project.dart'; import 'chrome.dart'; @@ -26,16 +29,29 @@ class WebApplicationPackage extends ApplicationPackage { Directory get webSourcePath => flutterProject.directory.childDirectory('web'); } -class ChromeDevice extends Device { - ChromeDevice() : super( - 'chrome', - category: Category.web, - platformType: PlatformType.web, - ephemeral: false, - ); +/// A web device that supports a chromium browser. +abstract class ChromiumDevice extends Device { + ChromiumDevice({ + @required String name, + @required this.chromeLauncher, + @required FileSystem fileSystem, + @required Logger logger, + }) : _fileSystem = fileSystem, + _logger = logger, + super( + name, + category: Category.web, + platformType: PlatformType.web, + ephemeral: false, + ); + + final ChromiumLauncher chromeLauncher; + + final FileSystem _fileSystem; + final Logger _logger; /// The active chrome instance. - Chrome _chrome; + Chromium _chrome; // TODO(jonahwilliams): this is technically false, but requires some refactoring // to allow hot mode restart only devices. @@ -83,47 +99,11 @@ class ChromeDevice extends Device { Future get emulatorId async => null; @override - bool isSupported() => featureFlags.isWebEnabled && globals.chromeLauncher.canFindChrome(); - - @override - String get name => 'Chrome'; + bool isSupported() => chromeLauncher.canFindExecutable(); @override DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder(); - @override - Future get sdkNameAndVersion async => _sdkNameAndVersion ??= await _computeSdkNameAndVersion(); - - String _sdkNameAndVersion; - Future _computeSdkNameAndVersion() async { - if (!isSupported()) { - return 'unknown'; - } - // See https://bugs.chromium.org/p/chromium/issues/detail?id=158372 - String version = 'unknown'; - if (globals.platform.isWindows) { - final ProcessResult result = await globals.processManager.run([ - r'reg', 'query', r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', '/v', 'version', - ]); - if (result.exitCode == 0) { - final List parts = (result.stdout as String).split(RegExp(r'\s+')); - if (parts.length > 2) { - version = 'Google Chrome ' + parts[parts.length - 2]; - } - } - } else { - final String chrome = findChromeExecutable(globals.platform, globals.fs); - final ProcessResult result = await globals.processManager.run([ - chrome, - '--version', - ]); - if (result.exitCode == 0) { - version = result.stdout as String; - } - } - return version.trim(); - } - @override Future startApp( covariant WebApplicationPackage package, { @@ -139,9 +119,9 @@ class ChromeDevice extends Device { final String url = platformArgs['uri'] as String; final bool launchChrome = platformArgs['no-launch-chrome'] != true; if (launchChrome) { - _chrome = await globals.chromeLauncher.launch( + _chrome = await chromeLauncher.launch( url, - cacheDir: globals.fs.currentDirectory + cacheDir: _fileSystem.currentDirectory .childDirectory('.dart_tool') .childDirectory('chrome-device'), headless: debuggingOptions.webRunHeadless, @@ -149,7 +129,7 @@ class ChromeDevice extends Device { ); } - globals.logger.sendEvent('app.webLaunchUrl', {'url': url, 'launched': launchChrome}); + _logger.sendEvent('app.webLaunchUrl', {'url': url, 'launched': launchChrome}); return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null); } @@ -177,27 +157,140 @@ class ChromeDevice extends Device { } } +/// The Google Chrome browser based on Chromium. +class GoogleChromeDevice extends ChromiumDevice { + GoogleChromeDevice({ + @required Platform platform, + @required ProcessManager processManager, + @required ChromiumLauncher chromiumLauncher, + @required Logger logger, + @required FileSystem fileSystem, + }) : _platform = platform, + _processManager = processManager, + super( + name: 'chrome', + chromeLauncher: chromiumLauncher, + logger: logger, + fileSystem: fileSystem, + ); + + final Platform _platform; + final ProcessManager _processManager; + + @override + String get name => 'Chrome'; + + @override + Future get sdkNameAndVersion async => _sdkNameAndVersion ??= await _computeSdkNameAndVersion(); + + String _sdkNameAndVersion; + Future _computeSdkNameAndVersion() async { + if (!isSupported()) { + return 'unknown'; + } + // See https://bugs.chromium.org/p/chromium/issues/detail?id=158372 + String version = 'unknown'; + if (_platform.isWindows) { + final ProcessResult result = await _processManager.run([ + r'reg', 'query', r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', '/v', 'version', + ]); + if (result.exitCode == 0) { + final List parts = (result.stdout as String).split(RegExp(r'\s+')); + if (parts.length > 2) { + version = 'Google Chrome ' + parts[parts.length - 2]; + } + } + } else { + final String chrome = chromeLauncher.findExecutable(); + final ProcessResult result = await _processManager.run([ + chrome, + '--version', + ]); + if (result.exitCode == 0) { + version = result.stdout as String; + } + } + return version.trim(); + } + +} + +/// The Microsoft Edge browser based on Chromium. +// This is not currently used, see https://github.com/flutter/flutter/issues/55322 +class MicrosoftEdgeDevice extends ChromiumDevice { + MicrosoftEdgeDevice({ + @required ChromiumLauncher chromiumLauncher, + @required Logger logger, + @required FileSystem fileSystem, + }) : super( + name: 'edge', + chromeLauncher: chromiumLauncher, + logger: logger, + fileSystem: fileSystem, + ); + + @override + String get name => 'Edge'; + + @override + Future get sdkNameAndVersion async => ''; +} + class WebDevices extends PollingDeviceDiscovery { - WebDevices() : super('chrome'); + WebDevices({ + @required FileSystem fileSystem, + @required Logger logger, + @required Platform platform, + @required ProcessManager processManager, + @required FeatureFlags featureFlags, + }) : _featureFlags = featureFlags, + super('Chrome') { + final OperatingSystemUtils operatingSystemUtils = OperatingSystemUtils( + fileSystem: fileSystem, + platform: platform, + logger: logger, + processManager: processManager, + ); + _chromeDevice = GoogleChromeDevice( + fileSystem: fileSystem, + logger: logger, + platform: platform, + processManager: processManager, + chromiumLauncher: ChromiumLauncher( + browserFinder: findChromeExecutable, + fileSystem: fileSystem, + logger: logger, + platform: platform, + processManager: processManager, + operatingSystemUtils: operatingSystemUtils, + ), + ); + _webServerDevice = WebServerDevice( + logger: logger, + ); + } - final bool _chromeIsAvailable = globals.chromeLauncher.canFindChrome(); - final ChromeDevice _webDevice = ChromeDevice(); - final WebServerDevice _webServerDevice = WebServerDevice(); + GoogleChromeDevice _chromeDevice; + WebServerDevice _webServerDevice; + final FeatureFlags _featureFlags; @override bool get canListAnything => featureFlags.isWebEnabled; @override Future> pollingGetDevices({ Duration timeout }) async { + if (!_featureFlags.isWebEnabled) { + return []; + } return [ - if (_chromeIsAvailable) - _webDevice, _webServerDevice, + if (_chromeDevice.isSupported()) + _chromeDevice, ]; } @override - bool get supportsPlatform => featureFlags.isWebEnabled; + bool get supportsPlatform => _featureFlags.isWebEnabled; } @visibleForTesting @@ -208,12 +301,17 @@ String parseVersionForWindows(String input) { /// A special device type to allow serving for arbitrary browsers. class WebServerDevice extends Device { - WebServerDevice() : super( - 'web-server', - platformType: PlatformType.web, - category: Category.web, - ephemeral: false, - ); + WebServerDevice({ + @required Logger logger, + }) : _logger = logger, + super( + 'web-server', + platformType: PlatformType.web, + category: Category.web, + ephemeral: false, + ); + + final Logger _logger; @override void clearLogs() { } @@ -244,7 +342,7 @@ class WebServerDevice extends Device { Future get isLocalEmulator async => false; @override - bool isSupported() => featureFlags.isWebEnabled; + bool isSupported() => true; @override bool isSupportedForProject(FlutterProject flutterProject) { @@ -271,11 +369,11 @@ class WebServerDevice extends Device { }) async { final String url = platformArgs['uri'] as String; if (debuggingOptions.startPaused) { - globals.printStatus('Waiting for connection from Dart debug extension at $url', emphasis: true); + _logger.printStatus('Waiting for connection from Dart debug extension at $url', emphasis: true); } else { - globals.printStatus('$mainPath is being served at $url', emphasis: true); + _logger.printStatus('$mainPath is being served at $url', emphasis: true); } - globals.logger.sendEvent('app.webLaunchUrl', {'url': url, 'launched': false}); + _logger.sendEvent('app.webLaunchUrl', {'url': url, 'launched': false}); return LaunchResult.succeeded(observatoryUri: url != null ? Uri.parse(url): null); } diff --git a/packages/flutter_tools/lib/src/web/web_validator.dart b/packages/flutter_tools/lib/src/web/web_validator.dart index 70cf224b6664f..43cb00d692f27 100644 --- a/packages/flutter_tools/lib/src/web/web_validator.dart +++ b/packages/flutter_tools/lib/src/web/web_validator.dart @@ -5,47 +5,39 @@ import 'package:meta/meta.dart'; import 'package:platform/platform.dart'; -import '../base/file_system.dart'; import '../doctor.dart'; import 'chrome.dart'; -/// A validator that checks whether chrome is installed and can run. -class WebValidator extends DoctorValidator { - const WebValidator({ - @required Platform platform, - @required ChromeLauncher chromeLauncher, - @required FileSystem fileSystem, - }) : _platform = platform, - _chromeLauncher = chromeLauncher, - _fileSystem = fileSystem, - super('Chrome - develop for the web'); +/// A validator for Chromium-based brosers. +abstract class ChromiumValidator extends DoctorValidator { + const ChromiumValidator(String title) : super(title); - final Platform _platform; - final ChromeLauncher _chromeLauncher; - final FileSystem _fileSystem; + Platform get _platform; + ChromiumLauncher get _chromiumLauncher; + String get _name; @override Future validate() async { - final String chrome = findChromeExecutable(_platform, _fileSystem); - final bool canRunChrome = _chromeLauncher.canFindChrome(); + final bool canRunChromium = _chromiumLauncher.canFindExecutable(); + final String chromimSearchLocation = _chromiumLauncher.findExecutable(); final List messages = [ if (_platform.environment.containsKey(kChromeEnvironment)) - if (!canRunChrome) - ValidationMessage.hint('$chrome is not executable.') + if (!canRunChromium) + ValidationMessage.hint('$chromimSearchLocation is not executable.') else - ValidationMessage('$kChromeEnvironment = $chrome') + ValidationMessage('$kChromeEnvironment = $chromimSearchLocation') else - if (!canRunChrome) - const ValidationMessage.hint('Cannot find Chrome. Try setting ' - '$kChromeEnvironment to a Chrome executable.') + if (!canRunChromium) + ValidationMessage.hint('Cannot find $_name. Try setting ' + '$kChromeEnvironment to a $_name executable.') else - ValidationMessage('Chrome at $chrome'), + ValidationMessage('$_name at $chromimSearchLocation'), ]; - if (!canRunChrome) { + if (!canRunChromium) { return ValidationResult( ValidationType.missing, messages, - statusInfo: 'Cannot find chrome executable at $chrome', + statusInfo: 'Cannot find $_name executable at $chromimSearchLocation', ); } return ValidationResult( @@ -54,3 +46,42 @@ class WebValidator extends DoctorValidator { ); } } + +/// A validator that checks whether Chrome is installed and can run. +class ChromeValidator extends ChromiumValidator { + const ChromeValidator({ + @required Platform platform, + @required ChromiumLauncher chromiumLauncher, + }) : _platform = platform, + _chromiumLauncher = chromiumLauncher, + super('Chrome - develop for the web'); + + @override + final Platform _platform; + + @override + final ChromiumLauncher _chromiumLauncher; + + @override + String get _name => 'Chrome'; +} + +/// A validator that checks whethere Edge is installed and can run. +// This is not currently used, see https://github.com/flutter/flutter/issues/55322 +class EdgeValidator extends ChromiumValidator { + const EdgeValidator({ + @required Platform platform, + @required ChromiumLauncher chromiumLauncher, + }) : _platform = platform, + _chromiumLauncher = chromiumLauncher, + super('Edge - develop for the web'); + + @override + final Platform _platform; + + @override + final ChromiumLauncher _chromiumLauncher; + + @override + String get _name => 'Edge'; +} diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index d14515a8efd49..920a1e42e3150 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: meta: 1.1.8 multicast_dns: 0.2.2 mustache_template: 1.0.0+1 - package_config: 1.9.1 + package_config: 1.9.3 platform: 2.2.1 process: 3.0.12 quiver: 2.1.3 @@ -91,7 +91,7 @@ dependencies: term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" uuid: 2.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: @@ -110,4 +110,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 426a +# PUBSPEC CHECKSUM: 266d diff --git a/packages/flutter_tools/templates/app/.idea/libraries/Flutter_for_Android.xml.tmpl b/packages/flutter_tools/templates/app/.idea/libraries/Flutter_for_Android.xml.tmpl deleted file mode 100644 index 23c962c006d24..0000000000000 --- a/packages/flutter_tools/templates/app/.idea/libraries/Flutter_for_Android.xml.tmpl +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/flutter_tools/templates/module/common/.idea/libraries/Flutter_for_Android.xml.tmpl b/packages/flutter_tools/templates/module/common/.idea/libraries/Flutter_for_Android.xml.tmpl deleted file mode 100644 index 23c962c006d24..0000000000000 --- a/packages/flutter_tools/templates/module/common/.idea/libraries/Flutter_for_Android.xml.tmpl +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl b/packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl deleted file mode 100644 index 23c962c006d24..0000000000000 --- a/packages/flutter_tools/templates/plugin/.idea/libraries/Flutter_for_Android.xml.tmpl +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart index bdb9f22cdc72d..8ca9c183ff77c 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/analyze_continuously_test.dart @@ -15,6 +15,7 @@ import 'package:flutter_tools/src/dart/sdk.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; import '../../src/common.dart'; import '../../src/context.dart'; @@ -65,7 +66,15 @@ void main() { testUsingContext('AnalysisServer success', () async { _createSampleProject(tempDir); - await const Pub().get(context: PubContext.flutterTests, directory: tempDir.path); + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + platform: const LocalPlatform(), + botDetector: globals.botDetector, + usage: globals.flutterUsage, + ); + await pub.get(context: PubContext.flutterTests, directory: tempDir.path); server = AnalysisServer(dartSdkPath, [tempDir.path], fileSystem: fileSystem, @@ -90,7 +99,15 @@ void main() { testUsingContext('AnalysisServer errors', () async { _createSampleProject(tempDir, brokenCode: true); - await const Pub().get(context: PubContext.flutterTests, directory: tempDir.path); + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + platform: const LocalPlatform(), + usage: globals.flutterUsage, + botDetector: globals.botDetector, + ); + await pub.get(context: PubContext.flutterTests, directory: tempDir.path); server = AnalysisServer(dartSdkPath, [tempDir.path], fileSystem: fileSystem, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index edef46efcc3f3..5bc54f06d9c86 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -5,12 +5,17 @@ import 'dart:async'; import 'package:file/memory.dart'; +import 'package:meta/meta.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; +import 'package:quiver/testing/async.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/net.dart'; - import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/attach.dart'; @@ -22,10 +27,6 @@ import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:meta/meta.dart'; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; -import 'package:quiver/testing/async.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import '../../src/common.dart'; @@ -142,7 +143,7 @@ void main() { final Process dartProcess = MockProcess(); final StreamController> compilerStdoutController = StreamController>(); - when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream); + when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream); when(dartProcess.stderr) .thenAnswer((_) => Stream>.fromFuture(Future>.value(const []))); @@ -787,6 +788,23 @@ VMServiceConnector getFakeVmServiceFactory({ when(vmService.done).thenAnswer((_) { return Future.value(null); }); + when(vmService.onDone).thenAnswer((_) { + return Future.value(null); + }); + when(vmService.getVM()).thenAnswer((_) async { + return vm_service.VM( + pid: 1, + architectureBits: 64, + hostCPU: '', + name: '', + isolates: [], + isolateGroups: [], + startTime: 0, + targetCPU: '', + operatingSystem: '', + version: '', + ); + }); when(vm.refreshViews(waitForViews: anyNamed('waitForViews'))) .thenAnswer((_) => Future.value(null)); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart index a9d97ad815100..9447a5b458055 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart @@ -249,14 +249,14 @@ void main() { }); testUsingContext('hidden when not enabled on macOS host', () { - expect(BuildMacosCommand().hidden, true); + expect(BuildMacosCommand(verboseHelp: false).hidden, true); }, overrides: { FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false), Platform: () => macosPlatform, }); testUsingContext('Not hidden when enabled and on macOS host', () { - expect(BuildMacosCommand().hidden, false); + expect(BuildMacosCommand(verboseHelp: false).hidden, false); }, overrides: { FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), Platform: () => macosPlatform, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart index cb3e741b54bab..2bc342c3bcba0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart @@ -59,6 +59,7 @@ void main() { BuildInfo.debug, false, false, + [], ), throwsToolExit()); }, overrides: { Platform: () => fakePlatform, @@ -186,7 +187,7 @@ class UrlLauncherPlugin {} }); testUsingContext('hidden if feature flag is not enabled', () async { - expect(BuildWebCommand().hidden, true); + expect(BuildWebCommand(verboseHelp: false).hidden, true); }, overrides: { Platform: () => fakePlatform, FileSystem: () => fileSystem, @@ -196,7 +197,7 @@ class UrlLauncherPlugin {} }); testUsingContext('not hidden if feature flag is enabled', () async { - expect(BuildWebCommand().hidden, false); + expect(BuildWebCommand(verboseHelp: false).hidden, false); }, overrides: { Platform: () => fakePlatform, FileSystem: () => fileSystem, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index c0a52258eaf86..a8830a058a530 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -164,6 +164,8 @@ class FakeFlutterTestRunner implements FlutterTestRunner { Directory workDir, List names = const [], List plainNames = const [], + String tags, + String excludeTags, bool enableObservatory = false, bool startPaused = false, bool disableServiceAuthCodes = false, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/unpack_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/unpack_test.dart deleted file mode 100644 index aacd350ffd09e..0000000000000 --- a/packages/flutter_tools/test/commands.shard/hermetic/unpack_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2014 The Flutter 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_tools/src/cache.dart'; -import 'package:flutter_tools/src/commands/unpack.dart'; - -import '../../src/common.dart'; -import '../../src/mocks.dart'; -import '../../src/testbed.dart'; - -void main() { - Testbed testbed; - - setUpAll(() { - Cache.disableLocking(); - }); - - tearDownAll(() { - Cache.enableLocking(); - }); - - setUp(() { - testbed = Testbed(); - }); - - test('Returns success for linux unconditionally', () => testbed.run(() async { - final UnpackCommand unpackCommand = UnpackCommand(); - applyMocksToCommand(unpackCommand); - - await createTestCommandRunner(unpackCommand).run( - [ - 'unpack', - '--cache-dir=foo', - '--target-platform=linux-x64', - ], - ); - })); -} diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 4b048aeced221..7c4ebb17ecf0e 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -86,7 +86,14 @@ void main() { ); return _runFlutterTest(projectDir); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('can create a default project if empty directory exists', () async { @@ -104,7 +111,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('creates a module project correctly', () async { @@ -126,7 +140,14 @@ void main() { ]); return _runFlutterTest(projectDir); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('cannot create a project if non-empty non-project directory exists with .metadata', () async { @@ -144,7 +165,14 @@ void main() { ]), throwsToolExit(message: 'Sorry, unable to detect the type of project to recreate')); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), ...noColorTerminalOverride, }); @@ -170,7 +198,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('detects and recreates an app project correctly', () async { @@ -195,7 +230,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('detects and recreates a plugin project correctly', () async { @@ -220,7 +262,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('detects and recreates a package project correctly', () async { @@ -251,7 +300,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('kotlin/swift legacy app project', () async { @@ -273,7 +329,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('can create a package project', () async { @@ -303,7 +366,14 @@ void main() { ); return _runFlutterTest(projectDir); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('can create a plugin project', () async { @@ -325,7 +395,14 @@ void main() { ); return _runFlutterTest(projectDir.childDirectory('example')); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('plugin example app depends on plugin', () async { @@ -344,7 +421,14 @@ void main() { final PathDependency pathDependency = pubspec.dependencies[pluginName] as PathDependency; expect(pathDependency.path, '../'); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('kotlin/swift plugin project', () async { @@ -429,7 +513,14 @@ void main() { ['lib/main.dart'], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('module project with pub', () async { @@ -464,7 +555,14 @@ void main() { '.android/Flutter/src/main/java/io/flutter/facade/Flutter.java', ]); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); @@ -1038,7 +1136,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('can re-gen module .ios/ folder, reusing custom org', () async { @@ -1055,7 +1160,14 @@ void main() { 'com.bar.foo.flutterProject', ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('can re-gen app android/ folder, reusing custom org', () async { @@ -1210,7 +1322,14 @@ void main() { ], ); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext( @@ -1227,7 +1346,14 @@ void main() { }, overrides: { ProcessManager: () => loggingProcessManager, - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }, ); @@ -1245,7 +1371,14 @@ void main() { }, overrides: { ProcessManager: () => loggingProcessManager, - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }, ); diff --git a/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart b/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart index a39d3feacfedd..4be5edab05161 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart @@ -4,92 +4,80 @@ import 'dart:convert'; -import 'package:file/file.dart'; import 'package:args/command_runner.dart'; + import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/device.dart'; -import 'package:flutter_tools/src/web/chrome.dart'; import 'package:flutter_tools/src/commands/devices.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/config.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/web/web_device.dart'; +import 'package:mockito/mockito.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/testbed.dart'; void main() { - group('devices', () { - Directory configDir; - Config config; + setUpAll(() { + Cache.disableLocking(); + }); - tearDown(() { - if (configDir != null) { - tryToDelete(configDir); - configDir = null; - } - }); + testUsingContext('devices can display no connected devices with the --machine flag', () async { + final BufferLogger logger = context.get() as BufferLogger; + final DevicesCommand command = DevicesCommand(); + final CommandRunner runner = createTestCommandRunner(command); + await runner.run(['devices', '--machine']); - setUpAll(() { - Cache.disableLocking(); - configDir ??= globals.fs.systemTempDirectory.createTempSync( - 'flutter_config_dir_test.', - ); - config = Config.test( - Config.kFlutterSettings, - directory: configDir, - logger: globals.logger, - )..setValue('enable-web', true); + expect( + json.decode(logger.statusText), + isEmpty, + ); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isWebEnabled: false), + }); + + testUsingContext('devices can display via the --machine flag', () async { + when(deviceManager.refreshAllConnectedDevices()).thenAnswer((Invocation invocation) async { + return [ + WebServerDevice(logger: BufferLogger.test()), + ]; }); - // Test assumes no devices connected. - // Should return only `web-server` device - testUsingContext('Test the --machine flag', () async { - final BufferLogger logger = context.get() as BufferLogger; - final DevicesCommand command = DevicesCommand(); - final CommandRunner runner = createTestCommandRunner(command); - await runner.run(['devices', '--machine']); - expect( - json.decode(logger.statusText), - >[ - { - 'name': 'Web Server', - 'id': 'web-server', - 'isSupported': true, - 'targetPlatform': 'web-javascript', - 'emulator': false, - 'sdk': 'Flutter Tools', - 'capabilities': { - 'hotReload': true, - 'hotRestart': true, - 'screenshot': false, - 'fastStart': false, - 'flutterExit': true, - 'hardwareRendering': false, - 'startPaused': true - } + final BufferLogger logger = context.get() as BufferLogger; + final DevicesCommand command = DevicesCommand(); + final CommandRunner runner = createTestCommandRunner(command); + await runner.run(['devices', '--machine']); + + expect( + json.decode(logger.statusText), + contains(equals( + { + 'name': 'Web Server', + 'id': 'web-server', + 'isSupported': true, + 'targetPlatform': 'web-javascript', + 'emulator': false, + 'sdk': 'Flutter Tools', + 'capabilities': { + 'hotReload': true, + 'hotRestart': true, + 'screenshot': false, + 'fastStart': false, + 'flutterExit': true, + 'hardwareRendering': false, + 'startPaused': true } - ] - ); - }, - overrides: { - DeviceManager: () => DeviceManager(), - Config: () => config, - ChromeLauncher: () => _DisabledChromeLauncher(), - }); + } + )), + ); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isWebEnabled: true), + DeviceManager: () => MockDeviceManager(), }); } -// Without ChromeLauncher DeviceManager constructor fails with noSuchMethodError -// trying to call canFindChrome on null -// Also, Chrome may have different versions on different machines and -// JSON will not match, because the `sdk` field of the Device contains version number -// Mock the launcher to make it appear that we don't have Chrome. -class _DisabledChromeLauncher implements ChromeLauncher { - @override - bool canFindChrome() => false; +class MockDeviceManager extends Mock implements DeviceManager { - @override - Future launch(String url, {bool headless = false, int debugPort, bool skipCheck = false, Directory cacheDir}) - => Future.error('Chrome disabled'); -} +} \ No newline at end of file diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart index 97210055ab281..edd3eb9d9f86a 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart @@ -195,7 +195,14 @@ void main() { expectDependenciesResolved(projectPath); expectZeroPluginsInjected(projectPath); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('get --offline fetches packages', () async { @@ -208,7 +215,14 @@ void main() { expectDependenciesResolved(projectPath); expectZeroPluginsInjected(projectPath); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('set the number of plugins as usage value', () async { @@ -222,7 +236,14 @@ void main() { expect(await getCommand.usageValues, containsPair(CustomDimensions.commandPackagesNumberPlugins, '0')); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('indicate that the project is not a module in usage value', () async { @@ -236,7 +257,14 @@ void main() { expect(await getCommand.usageValues, containsPair(CustomDimensions.commandPackagesProjectModule, 'false')); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('indicate that the project is a module in usage value', () async { @@ -250,7 +278,14 @@ void main() { expect(await getCommand.usageValues, containsPair(CustomDimensions.commandPackagesProjectModule, 'true')); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('indicate that Android project reports v1 in usage value', () async { @@ -265,7 +300,14 @@ void main() { containsPair(CustomDimensions.commandPackagesAndroidEmbeddingVersion, 'v1')); }, overrides: { FeatureFlags: () => TestFeatureFlags(isAndroidEmbeddingV2Enabled: false), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('indicate that Android project reports v2 in usage value', () async { @@ -280,7 +322,14 @@ void main() { containsPair(CustomDimensions.commandPackagesAndroidEmbeddingVersion, 'v2')); }, overrides: { FeatureFlags: () => TestFeatureFlags(isAndroidEmbeddingV2Enabled: true), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('upgrade fetches packages', () async { @@ -293,7 +342,14 @@ void main() { expectDependenciesResolved(projectPath); expectZeroPluginsInjected(projectPath); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('get fetches packages and injects plugin', () async { @@ -306,7 +362,14 @@ void main() { expectDependenciesResolved(projectPath); expectModulePluginInjected(projectPath); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('get fetches packages and injects plugin in plugin project', () async { @@ -327,7 +390,14 @@ void main() { expectDependenciesResolved(exampleProjectPath); expectPluginInjected(exampleProjectPath); }, overrides: { - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); }); @@ -351,7 +421,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysFalseBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('test with bot', () async { @@ -366,7 +443,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('run', () async { @@ -380,7 +464,14 @@ void main() { }, overrides: { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('pub publish', () async { @@ -405,7 +496,14 @@ void main() { }, overrides: { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('pub publish input fails', () async { @@ -426,7 +524,14 @@ void main() { }, overrides: { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('publish', () async { @@ -439,7 +544,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('packages publish', () async { @@ -452,7 +564,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('deps', () async { @@ -465,7 +584,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('cache', () async { @@ -478,7 +604,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('version', () async { @@ -491,7 +624,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('uploader', () async { @@ -504,7 +644,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('global', () async { @@ -518,7 +665,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); testUsingContext('outdated', () async { @@ -531,7 +685,14 @@ void main() { ProcessManager: () => mockProcessManager, Stdio: () => mockStdio, BotDetector: () => const AlwaysTrueBotDetector(), - Pub: () => const Pub(), + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), }); }); } diff --git a/packages/flutter_tools/test/commands.shard/permeable/test_test.dart b/packages/flutter_tools/test/commands.shard/permeable/test_test.dart index 88005bef0f09a..e2347d130808e 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/test_test.dart @@ -96,6 +96,35 @@ void main() { expect(result.exitCode, 0); }); + testUsingContext('flutter test should run a test with a given tag', () async { + Cache.flutterRoot = '../..'; + final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, + extraArguments: const ['--tags', 'include-tag']); + if (!(result.stdout as String).contains('+1: All tests passed')) { + fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); + } + expect(result.exitCode, 0); + }); + + testUsingContext('flutter test should not run a test with excluded tag', () async { + Cache.flutterRoot = '../..'; + final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, + extraArguments: const ['--exclude-tags', 'exclude-tag']); + if (!(result.stdout as String).contains('+1: All tests passed')) { + fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); + } + expect(result.exitCode, 0); + }); + + testUsingContext('flutter test should run all tests when tags are unspecified', () async { + Cache.flutterRoot = '../..'; + final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory); + if (!(result.stdout as String).contains('+1 -1: Some tests failed')) { + fail('unexpected output from test:\n\n${result.stdout}\n-- end stdout --\n\n'); + } + expect(result.exitCode, 1); + }); + testUsingContext('flutter test should test runs to completion', () async { Cache.flutterRoot = '../..'; final ProcessResult result = await _runFlutterTest('trivial', automatedTestsDirectory, flutterTestDirectory, diff --git a/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart b/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart index 28518b242c7f9..faede00c6eb15 100644 --- a/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart +++ b/packages/flutter_tools/test/general.shard/base/fingerprint_test.dart @@ -362,7 +362,7 @@ void main() { }, 'files': {}, }; - final Map b = Map.from(a); + final Map b = Map.of(a); b['properties'] = { 'buildMode': BuildMode.release.toString(), }; @@ -380,7 +380,7 @@ void main() { 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; - final Map b = Map.from(a); + final Map b = Map.of(a); b['files'] = { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07d', @@ -399,7 +399,7 @@ void main() { 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; - final Map b = Map.from(a); + final Map b = Map.of(a); b['files'] = { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'c.dart': '6f144e08b58cd0925328610fad7ac07d', diff --git a/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart b/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart index cbc8b7fcf4a45..086fcad9895a5 100644 --- a/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart @@ -6,7 +6,6 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_system/depfile.dart'; -import 'package:platform/platform.dart'; import '../../src/common.dart'; @@ -19,7 +18,6 @@ void main() { depfileService = DepfileService( logger: BufferLogger.test(), fileSystem: fileSystem, - platform: FakePlatform(operatingSystem: 'linux'), ); }); testWithoutContext('Can parse depfile from file', () { @@ -65,7 +63,6 @@ a.txt c.txt d.txt: b.txt depfileService = DepfileService( logger: BufferLogger.test(), fileSystem: fileSystem, - platform: FakePlatform(operatingSystem: 'windows'), ); final File depfileSource = fileSystem.file('example.d')..writeAsStringSync(r''' C:\\a.txt: C:\\b.txt @@ -81,7 +78,6 @@ C:\\a.txt: C:\\b.txt depfileService = DepfileService( logger: BufferLogger.test(), fileSystem: fileSystem, - platform: FakePlatform(operatingSystem: 'windows'), ); final File inputFile = fileSystem.directory(r'Hello Flutter').childFile('a.txt').absolute ..createSync(recursive: true); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart index 5725e13435320..f7ab4e79d1e34 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart @@ -68,7 +68,6 @@ flutter: final DepfileService depfileService = DepfileService( logger: null, fileSystem: fileSystem, - platform: platform, ); final Depfile dependencies = depfileService.parse(depfile); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/desktop_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/desktop_test.dart new file mode 100644 index 0000000000000..52b9fe56e5161 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/targets/desktop_test.dart @@ -0,0 +1,66 @@ +// Copyright 2014 The Flutter 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:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/build_system/depfile.dart'; +import 'package:flutter_tools/src/build_system/targets/desktop.dart'; + +import '../../../src/common.dart'; + +void main() { + testWithoutContext('unpackDesktopArtifacts copies files/directories to target', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + fileSystem.directory('inputs/foo').createSync(recursive: true); + // Should be copied. + fileSystem.file('inputs/a.txt').createSync(); + fileSystem.file('inputs/b.txt').createSync(); + fileSystem.file('inputs/foo/c.txt').createSync(); + // Sould not be copied. + fileSystem.file('inputs/d.txt').createSync(); + + final Depfile depfile = unpackDesktopArtifacts( + fileSystem: fileSystem, + artifactPath: 'inputs', + outputDirectory: fileSystem.directory('outputs'), + artifacts: [ + 'a.txt', + 'b.txt', + 'foo/' + ], + ); + + // Files are copied + expect(fileSystem.file('outputs/a.txt'), exists); + expect(fileSystem.file('outputs/b.txt'), exists); + expect(fileSystem.file('outputs/foo/c.txt'), exists); + expect(fileSystem.file('outputs/d.txt'), isNot(exists)); + + // Depfile is correct. + expect(depfile.inputs.map((File file) => file.path), unorderedEquals([ + 'inputs/a.txt', + 'inputs/b.txt', + 'inputs/foo/c.txt', + ])); + expect(depfile.outputs.map((File file) => file.path), unorderedEquals([ + 'outputs/a.txt', + 'outputs/b.txt', + 'outputs/foo/c.txt', + ])); + }); + + testWithoutContext('unpackDesktopArtifacts throws when attempting to copy missing file', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + + expect(() => unpackDesktopArtifacts( + fileSystem: fileSystem, + artifactPath: 'inputs', + outputDirectory: fileSystem.directory('outputs'), + artifacts: [ + 'a.txt', + ], + ), throwsA(isA())); + }); +} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index ee6a1ee404e20..96a37f92cb4a7 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -46,7 +46,7 @@ void main() { final File packagesFile = globals.fs.file(globals.fs.path.join('foo', '.packages')) ..createSync(recursive: true) ..writeAsStringSync('foo:lib/\n'); - PackageMap.globalPackagesPath = packagesFile.path; + globalPackagesPath = packagesFile.path; globals.fs.currentDirectory.childDirectory('bar').createSync(); processManager = FakeProcessManager.list([]); @@ -65,7 +65,6 @@ void main() { depfileService = DepfileService( fileSystem: globals.fs, logger: globals.logger, - platform: globals.platform, ); environment.buildDir.createSync(recursive: true); }, overrides: { @@ -229,7 +228,8 @@ void main() { ...kDart2jsLinuxArgs, '-o', environment.buildDir.childFile('app.dill').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.profile=true', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] @@ -253,6 +253,39 @@ void main() { })); + test('Dart2JSTarget calls dart2js with expected args with enabled experiment', () => testbed.run(() async { + environment.defines[kBuildMode] = 'profile'; + environment.defines[kEnableExperiment] = 'non-nullable'; + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '--enable-experiment=non-nullable', + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.profile=true', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '--enable-experiment=non-nullable', + '-O4', + '-Ddart.vm.profile=true', + '--no-minify', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); + + await const Dart2JSTarget().build(environment); + }, overrides: { + ProcessManager: () => processManager, + })); + test('Dart2JSTarget calls dart2js with expected args in profile mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; processManager.addCommand(FakeCommand( @@ -260,7 +293,8 @@ void main() { ...kDart2jsLinuxArgs, '-o', environment.buildDir.childFile('app.dill').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.profile=true', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] @@ -289,7 +323,8 @@ void main() { ...kDart2jsLinuxArgs, '-o', environment.buildDir.childFile('app.dill').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.product=true', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] @@ -318,7 +353,8 @@ void main() { ...kDart2jsLinuxArgs, '-o', environment.buildDir.childFile('app.dill').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.product=true', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] @@ -366,10 +402,11 @@ void main() { ...kDart2jsLinuxArgs, '-o', environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.product=true', '-DFOO=bar', '-DBAZ=qux', - '--packages=${globals.fs.path.join('foo', '.packages')}', - '--cfe-only', + '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); @@ -399,9 +436,10 @@ void main() { ...kDart2jsLinuxArgs, '-o', environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '-Ddart.vm.profile=true', '-DFOO=bar', '-DBAZ=qux', - '--packages=${globals.fs.path.join('foo', '.packages')}', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart index 04e46b6d748b0..8c3a65493f85e 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart @@ -4,15 +4,18 @@ import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_system/targets/dart.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_system/depfile.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/targets/windows.dart'; import '../../../src/common.dart'; +import '../../../src/context.dart'; import '../../../src/fake_process_manager.dart'; final Platform kWindowsPlatform = FakePlatform( @@ -27,8 +30,6 @@ const List kRequiredFiles = [ r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp', r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib', r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb', - r'C:\bin\cache\artifacts\engine\windows-x64\lutter_export.h', - r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h', r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h', r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.h', r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat', @@ -37,42 +38,105 @@ const List kRequiredFiles = [ ]; void main() { - Environment environment; - FileSystem fileSystem; - - setUp(() { + testWithoutContext('UnpackWindows copies files to the correct cache directory', () async { final MockArtifacts artifacts = MockArtifacts(); - when(artifacts.getArtifactPath(Artifact.windowsDesktopPath)) - .thenReturn(r'C:\bin\cache\artifacts\engine\windows-x64\'); - fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); - environment = Environment.test( + final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); + final Environment environment = Environment.test( fileSystem.currentDirectory, artifacts: artifacts, processManager: FakeProcessManager.any(), fileSystem: fileSystem, logger: BufferLogger.test(), ); + final DepfileService depfileService = DepfileService( + logger: BufferLogger.test(), + fileSystem: fileSystem, + ); + environment.buildDir.createSync(recursive: true); + + when(artifacts.getArtifactPath(Artifact.windowsDesktopPath)) + .thenReturn(r'C:\bin\cache\artifacts\engine\windows-x64\'); for (final String path in kRequiredFiles) { fileSystem.file(path).createSync(recursive: true); } fileSystem.directory('windows').createSync(); - }); - testWithoutContext('UnpackWindows copies files to the correct cache directory', () async { await const UnpackWindows().build(environment); - expect(fileSystem.file(r'C:\windows\flutter\flutter_export.h'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_messenger.h'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll.exp'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll.lib'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.dll.pdb'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_export.h'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_messenger.h'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_plugin_registrar.h'), exists); - expect(fileSystem.file(r'C:\windows\flutter\flutter_windows.h'), exists); - expect(fileSystem.file(r'C:\windows\flutter\icudtl.dat'), exists); - expect(fileSystem.file(r'C:\windows\flutter\cpp_client_wrapper\foo'), exists); + // Output files are copied correctly. + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_export.h'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_messenger.h'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_windows.dll'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_windows.dll.exp'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_windows.dll.lib'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_windows.dll.pdb'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_export.h'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_messenger.h'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_plugin_registrar.h'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_windows.h'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\icudtl.dat'), exists); + expect(fileSystem.file(r'C:\windows\flutter\ephemeral\cpp_client_wrapper\foo'), exists); + + + final File outputDepfile = environment.buildDir + .childFile('windows_engine_sources.d'); + + // Depfile is created correctly. + expect(outputDepfile, exists); + + final List inputPaths = depfileService.parse(outputDepfile) + .inputs.map((File file) => file.path).toList(); + final List outputPaths = depfileService.parse(outputDepfile) + .outputs.map((File file) => file.path).toList(); + + // Depfile has expected sources. + expect(inputPaths, unorderedEquals([ + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h', + r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.h', + r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat', + r'C:\bin\cache\artifacts\engine\windows-x64\cpp_client_wrapper\foo', + ])); + expect(outputPaths, unorderedEquals([ + r'C:\windows\flutter\ephemeral\flutter_export.h', + r'C:\windows\flutter\ephemeral\flutter_messenger.h', + r'C:\windows\flutter\ephemeral\flutter_windows.dll', + r'C:\windows\flutter\ephemeral\flutter_windows.dll.exp', + r'C:\windows\flutter\ephemeral\flutter_windows.dll.lib', + r'C:\windows\flutter\ephemeral\flutter_windows.dll.pdb', + r'C:\windows\flutter\ephemeral\flutter_plugin_registrar.h', + r'C:\windows\flutter\ephemeral\flutter_windows.h', + r'C:\windows\flutter\ephemeral\icudtl.dat', + r'C:\windows\flutter\ephemeral\cpp_client_wrapper\foo', + ])); + }); + + // AssetBundleFactory still uses context injection + testUsingContext('DebugBundleWindowsAssets creates correct bundle structure', () async { + final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); + final Environment environment = Environment.test( + fileSystem.currentDirectory, + artifacts: MockArtifacts(), + processManager: FakeProcessManager.any(), + fileSystem: fileSystem, + logger: BufferLogger.test(), + defines: { + kBuildMode: 'debug', + } + ); + + environment.buildDir.childFile('app.dill').createSync(recursive: true); + + await const DebugBundleWindowsAssets().build(environment); + + // Depfile is created and dill is copied. + expect(environment.buildDir.childFile('flutter_assets.d'), exists); + expect(fileSystem.file(r'C:\flutter_assets\kernel_blob.bin'), exists); }); } diff --git a/packages/flutter_tools/test/general.shard/crash_reporting_test.dart b/packages/flutter_tools/test/general.shard/crash_reporting_test.dart index fe401d70a53ca..5162581992ef8 100644 --- a/packages/flutter_tools/test/general.shard/crash_reporting_test.dart +++ b/packages/flutter_tools/test/general.shard/crash_reporting_test.dart @@ -5,160 +5,205 @@ import 'dart:async'; import 'dart:convert'; -import 'package:file/file.dart'; -import 'package:file/local.dart'; import 'package:file/memory.dart'; -import 'package:flutter_tools/runner.dart' as tools; -import 'package:flutter_tools/src/base/common.dart'; -import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; -import 'package:flutter_tools/src/runner/flutter_command.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; import 'package:http/http.dart'; import 'package:http/testing.dart'; -import 'package:quiver/testing/async.dart'; +import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import '../src/common.dart'; import '../src/context.dart'; void main() { - group('crash reporting', () { - setUpAll(() { - Cache.disableLocking(); - }); + BufferLogger logger; + MockUsage mockUsage; + Platform platform; + OperatingSystemUtils operatingSystemUtils; + + setUp(() async { + logger = BufferLogger.test(); + + mockUsage = MockUsage(); + when(mockUsage.clientId).thenReturn('00000000-0000-4000-0000-000000000000'); + + platform = FakePlatform(environment: {}, operatingSystem: 'linux'); + operatingSystemUtils = OperatingSystemUtils( + fileSystem: MemoryFileSystem.test(), + logger: logger, + platform: platform, + processManager: FakeProcessManager.any(), + ); - setUp(() async { - tools.crashFileSystem = MemoryFileSystem(); - setExitFunctionForTests((_) { }); - MockCrashReportSender.sendCalls = 0; - }); + MockCrashReportSender.sendCalls = 0; + }); + + Future verifyCrashReportSent(RequestInfo crashInfo, { + int crashes = 1, + }) async { + // Verify that we sent the crash report. + expect(crashInfo.method, 'POST'); + expect(crashInfo.uri, Uri( + scheme: 'https', + host: 'clients2.google.com', + port: 443, + path: '/cr/report', + queryParameters: { + 'product': 'Flutter_Tools', + 'version': 'test-version', + }, + )); + expect(crashInfo.fields['uuid'], '00000000-0000-4000-0000-000000000000'); + expect(crashInfo.fields['product'], 'Flutter_Tools'); + expect(crashInfo.fields['version'], 'test-version'); + expect(crashInfo.fields['osName'], 'linux'); + expect(crashInfo.fields['osVersion'], 'Linux'); + expect(crashInfo.fields['type'], 'DartError'); + expect(crashInfo.fields['error_runtime_type'], 'StateError'); + expect(crashInfo.fields['error_message'], 'Bad state: Test bad state error'); + expect(crashInfo.fields['comments'], 'crash'); + + expect(logger.traceText, contains('Sending crash report to Google.')); + expect(logger.traceText, contains('Crash report sent (report ID: test-report-id)')); + } + + testWithoutContext('suppress analytics', () async { + when(mockUsage.suppressAnalytics).thenReturn(true); + + final CrashReportSender crashReportSender = CrashReportSender( + client: CrashingCrashReportSender(const SocketException('no internets')), + usage: mockUsage, + platform: platform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, + ); + + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); + + expect(logger.traceText, isEmpty); + }); - tearDown(() { - tools.crashFileSystem = const LocalFileSystem(); - restoreExitFunction(); + group('allow analytics', () { + setUp(() async { + when(mockUsage.suppressAnalytics).thenReturn(false); }); - testUsingContext('should send crash reports', () async { + testWithoutContext('should send crash reports', () async { final RequestInfo requestInfo = RequestInfo(); - CrashReportSender.initializeWith(MockCrashReportSender(requestInfo)); - final int exitCode = await tools.run( - ['crash'], - [_CrashCommand()], - reportCrashes: true, - flutterVersion: 'test-version', + final CrashReportSender crashReportSender = CrashReportSender( + client: MockCrashReportSender(requestInfo), + usage: mockUsage, + platform: platform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, + ); + + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', ); - expect(exitCode, 1); await verifyCrashReportSent(requestInfo); - }, overrides: { - Stdio: () => _NoStderr(), }); - testUsingContext('should print an explanatory message when there is a SocketException', () async { - final Completer exitCodeCompleter = Completer(); - setExitFunctionForTests((int exitCode) { - exitCodeCompleter.complete(exitCode); - }); + testWithoutContext('should print an explanatory message when there is a SocketException', () async { + final CrashReportSender crashReportSender = CrashReportSender( + client: CrashingCrashReportSender(const SocketException('no internets')), + usage: mockUsage, + platform: platform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, + ); - CrashReportSender.initializeWith( - CrashingCrashReportSender(const SocketException('no internets'))); + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); - unawaited(tools.run( - ['crash'], - [_CrashAsyncCommand()], - reportCrashes: true, - flutterVersion: 'test-version', - )); - expect(await exitCodeCompleter.future, 1); - expect(testLogger.errorText, contains('Failed to send crash report due to a network error')); - }, overrides: { - Stdio: () => _NoStderr(), + expect(logger.errorText, contains('Failed to send crash report due to a network error')); }); - testUsingContext('should print an explanatory message when there is an HttpException', () async { - final Completer exitCodeCompleter = Completer(); - setExitFunctionForTests((int exitCode) { - exitCodeCompleter.complete(exitCode); - }); + testWithoutContext('should print an explanatory message when there is an HttpException', () async { + final CrashReportSender crashReportSender = CrashReportSender( + client: CrashingCrashReportSender(const HttpException('no internets')), + usage: mockUsage, + platform: platform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, + ); - CrashReportSender.initializeWith( - CrashingCrashReportSender(const HttpException('no internets'))); + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); - unawaited(tools.run( - ['crash'], - [_CrashAsyncCommand()], - reportCrashes: true, - flutterVersion: 'test-version', - )); - expect(await exitCodeCompleter.future, 1); - expect(testLogger.errorText, contains('Failed to send crash report due to a network error')); - }, overrides: { - Stdio: () => _NoStderr(), + expect(logger.errorText, contains('Failed to send crash report due to a network error')); }); - testUsingContext('should send crash reports when async throws', () async { - final Completer exitCodeCompleter = Completer(); - setExitFunctionForTests((int exitCode) { - exitCodeCompleter.complete(exitCode); - }); - + testWithoutContext('should send only one crash report when sent many times', () async { final RequestInfo requestInfo = RequestInfo(); - CrashReportSender.initializeWith(MockCrashReportSender(requestInfo)); + final CrashReportSender crashReportSender = CrashReportSender( + client: MockCrashReportSender(requestInfo), + usage: mockUsage, + platform: platform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, + ); - unawaited(tools.run( - ['crash'], - [_CrashAsyncCommand()], - reportCrashes: true, - flutterVersion: 'test-version', - )); - expect(await exitCodeCompleter.future, 1); - await verifyCrashReportSent(requestInfo); - }, overrides: { - Stdio: () => _NoStderr(), - }); + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); - testUsingContext('should send only one crash report when async throws many', () async { - final Completer exitCodeCompleter = Completer(); - setExitFunctionForTests((int exitCode) { - if (!exitCodeCompleter.isCompleted) { - exitCodeCompleter.complete(exitCode); - } - }); + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); + + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); + + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); - final RequestInfo requestInfo = RequestInfo(); - final MockCrashReportSender sender = MockCrashReportSender(requestInfo); - CrashReportSender.initializeWith(sender); - - FakeAsync().run((FakeAsync time) { - time.elapse(const Duration(seconds: 1)); - unawaited(tools.run( - ['crash'], - [_MultiCrashAsyncCommand(crashes: 4)], - reportCrashes: true, - flutterVersion: 'test-version', - )); - time.elapse(const Duration(seconds: 1)); - time.flushMicrotasks(); - }); - expect(await exitCodeCompleter.future, 1); expect(MockCrashReportSender.sendCalls, 1); await verifyCrashReportSent(requestInfo, crashes: 4); - }, overrides: { - DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(), - Stdio: () => _NoStderr(), }); - testUsingContext('should not send a crash report if on a user-branch', () async { + testWithoutContext('should not send a crash report if on a user-branch', () async { String method; Uri uri; - CrashReportSender.initializeWith(MockClient((Request request) async { + final MockClient mockClient = MockClient((Request request) async { method = request.method; uri = request.url; @@ -166,41 +211,60 @@ void main() { 'test-report-id', 200, ); - })); + }); - final int exitCode = await tools.run( - ['crash'], - [_CrashCommand()], - reportCrashes: true, - flutterVersion: '[user-branch]/v1.2.3', + final CrashReportSender crashReportSender = CrashReportSender( + client: mockClient, + usage: mockUsage, + platform: platform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, ); - expect(exitCode, 1); + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => '[user-branch]/v1.2.3', + command: 'crash', + ); // Verify that the report wasn't sent expect(method, null); expect(uri, null); - expect(testLogger.traceText, isNot(contains('Crash report sent'))); - }, overrides: { - Stdio: () => _NoStderr(), + expect(logger.traceText, isNot(contains('Crash report sent'))); }); - testUsingContext('can override base URL', () async { + testWithoutContext('can override base URL', () async { Uri uri; - CrashReportSender.initializeWith(MockClient((Request request) async { + final MockClient mockClient = MockClient((Request request) async { uri = request.url; return Response('test-report-id', 200); - })); + }); + + final Platform environmentPlatform = FakePlatform( + operatingSystem: 'linux', + environment: { + 'HOME': '/', + 'FLUTTER_CRASH_SERVER_BASE_URL': 'https://localhost:12345/fake_server', + }, + script: Uri(scheme: 'data'), + ); - final int exitCode = await tools.run( - ['crash'], - [_CrashCommand()], - reportCrashes: true, - flutterVersion: 'test-version', + final CrashReportSender crashReportSender = CrashReportSender( + client: mockClient, + usage: mockUsage, + platform: environmentPlatform, + logger: logger, + operatingSystemUtils: operatingSystemUtils, ); - expect(exitCode, 1); + await crashReportSender.sendReport( + error: StateError('Test bad state error'), + stackTrace: null, + getFlutterVersion: () => 'test-version', + command: 'crash', + ); // Verify that we sent the crash report. expect(uri, isNotNull); @@ -214,16 +278,6 @@ void main() { 'version': 'test-version', }, )); - }, overrides: { - Platform: () => FakePlatform( - operatingSystem: 'linux', - environment: { - 'HOME': '/', - 'FLUTTER_CRASH_SERVER_BASE_URL': 'https://localhost:12345/fake_server', - }, - script: Uri(scheme: 'data'), - ), - Stdio: () => _NoStderr(), }); }); } @@ -234,42 +288,6 @@ class RequestInfo { Map fields; } -Future verifyCrashReportSent(RequestInfo crashInfo, { - int crashes = 1, -}) async { - // Verify that we sent the crash report. - expect(crashInfo.method, 'POST'); - expect(crashInfo.uri, Uri( - scheme: 'https', - host: 'clients2.google.com', - port: 443, - path: '/cr/report', - queryParameters: { - 'product': 'Flutter_Tools', - 'version': 'test-version', - }, - )); - expect(crashInfo.fields['uuid'], '00000000-0000-4000-0000-000000000000'); - expect(crashInfo.fields['product'], 'Flutter_Tools'); - expect(crashInfo.fields['version'], 'test-version'); - expect(crashInfo.fields['osName'], globals.platform.operatingSystem); - expect(crashInfo.fields['osVersion'], 'fake OS name and version'); - expect(crashInfo.fields['type'], 'DartError'); - expect(crashInfo.fields['error_runtime_type'], 'StateError'); - expect(crashInfo.fields['error_message'], 'Bad state: Test bad state error'); - expect(crashInfo.fields['comments'], 'crash'); - - expect(testLogger.traceText, contains('Sending crash report to Google.')); - expect(testLogger.traceText, contains('Crash report sent (report ID: test-report-id)')); - - // Verify that we've written the crash report to disk. - final List writtenFiles = - (await tools.crashFileSystem.directory('/').list(recursive: true).toList()) - .map((FileSystemEntity e) => e.path).toList(); - expect(writtenFiles, hasLength(crashes)); - expect(writtenFiles, contains('flutter_01.log')); -} - class MockCrashReportSender extends MockClient { MockCrashReportSender(RequestInfo crashInfo) : super((Request request) async { MockCrashReportSender.sendCalls++; @@ -283,14 +301,14 @@ class MockCrashReportSender extends MockClient { utf8.decode(request.bodyBytes) .split('--$boundary') .map>((String part) { - final Match nameMatch = RegExp(r'name="(.*)"').firstMatch(part); - if (nameMatch == null) { - return null; - } - final String name = nameMatch[1]; - final String value = part.split('\n').skip(2).join('\n').trim(); - return [name, value]; - }) + final Match nameMatch = RegExp(r'name="(.*)"').firstMatch(part); + if (nameMatch == null) { + return null; + } + final String name = nameMatch[1]; + final String value = part.split('\n').skip(2).join('\n').trim(); + return [name, value]; + }) .where((List pair) => pair != null), key: (dynamic key) { final List pair = key as List; @@ -317,78 +335,6 @@ class CrashingCrashReportSender extends MockClient { }); } -/// Throws a random error to simulate a CLI crash. -class _CrashCommand extends FlutterCommand { - - @override - String get description => 'Simulates a crash'; - - @override - String get name => 'crash'; - - @override - Future runCommand() async { - void fn1() { - throw StateError('Test bad state error'); - } - - void fn2() { - fn1(); - } - - void fn3() { - fn2(); - } - - fn3(); - - return FlutterCommandResult.success(); - } -} - -/// Throws StateError from async callback. -class _CrashAsyncCommand extends FlutterCommand { - - @override - String get description => 'Simulates a crash'; - - @override - String get name => 'crash'; - - @override - Future runCommand() async { - Timer.run(() { - throw StateError('Test bad state error'); - }); - return Completer().future; // expect StateError - } -} - -/// Generates multiple asynchronous unhandled exceptions. -class _MultiCrashAsyncCommand extends FlutterCommand { - _MultiCrashAsyncCommand({ - int crashes = 1, - }) : _crashes = crashes; - - final int _crashes; - - @override - String get description => 'Simulates a crash'; - - @override - String get name => 'crash'; - - @override - Future runCommand() async { - for (int i = 0; i < _crashes; i++) { - Timer.run(() { - throw StateError('Test bad state error'); - }); - } - return Completer().future; // expect StateError - } -} - /// A DoctorValidatorsProvider that overrides the default validators without /// overriding the doctor. class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider { @@ -399,49 +345,4 @@ class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider { List get workflows => []; } -class _NoStderr extends Stdio { - _NoStderr(); - - @override - IOSink get stderr => const _NoopIOSink(); -} - -class _NoopIOSink implements IOSink { - const _NoopIOSink(); - - @override - Encoding get encoding => utf8; - - @override - set encoding(_) => throw UnsupportedError(''); - - @override - void add(_) { } - - @override - void write(_) { } - - @override - void writeAll(_, [ __ = '' ]) { } - - @override - void writeln([ _ = '' ]) { } - - @override - void writeCharCode(_) { } - - @override - void addError(_, [ __ ]) { } - - @override - Future addStream(_) async { } - - @override - Future flush() async { } - - @override - Future close() async { } - - @override - Future get done async { } -} +class MockUsage extends Mock implements Usage {} diff --git a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart index cdc7fb88abc44..798e5d8c11ba5 100644 --- a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart +++ b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart @@ -3,73 +3,81 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:collection'; import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; +import 'package:quiver/testing/async.dart'; +import 'package:platform/platform.dart'; + import 'package:flutter_tools/src/base/bot_detector.dart'; import 'package:flutter_tools/src/base/common.dart'; -import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; - -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; -import 'package:quiver/testing/async.dart'; -import 'package:platform/platform.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/mocks.dart' as mocks; -import '../../src/testbed.dart'; void main() { setUpAll(() { - Cache.flutterRoot = getFlutterRoot(); + Cache.flutterRoot = ''; }); tearDown(() { MockDirectory.findCache = false; }); - testUsingContext('pub get 69', () async { + testWithoutContext('pub get 69', () async { String error; - final MockProcessManager processMock = context.get() as MockProcessManager; + final MockProcessManager processMock = MockProcessManager(69); + final BufferLogger logger = BufferLogger.test(); + final Pub pub = Pub( + fileSystem: MockFileSystem(), + logger: logger, + processManager: processMock, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); FakeAsync().run((FakeAsync time) { expect(processMock.lastPubEnvironment, isNull); - expect(testLogger.statusText, ''); + expect(logger.statusText, ''); pub.get(context: PubContext.flutterTests, checkLastModified: false).then((void value) { error = 'test completed unexpectedly'; }, onError: (dynamic thrownError) { error = 'test failed unexpectedly: $thrownError'; }); time.elapse(const Duration(milliseconds: 500)); - expect(testLogger.statusText, + expect(logger.statusText, 'Running "flutter pub get" in /...\n' 'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n', ); expect(processMock.lastPubEnvironment, contains('flutter_cli:flutter_tests')); expect(processMock.lastPubCache, isNull); time.elapse(const Duration(milliseconds: 500)); - expect(testLogger.statusText, + expect(logger.statusText, 'Running "flutter pub get" in /...\n' 'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n' 'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n', ); time.elapse(const Duration(seconds: 1)); - expect(testLogger.statusText, + expect(logger.statusText, 'Running "flutter pub get" in /...\n' 'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n' 'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n', ); time.elapse(const Duration(seconds: 100)); // from t=0 to t=100 - expect(testLogger.statusText, + expect(logger.statusText, 'Running "flutter pub get" in /...\n' 'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n' 'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n' @@ -80,7 +88,7 @@ void main() { 'pub get failed (server unavailable) -- attempting retry 7 in 64 seconds...\n', // at t=61 ); time.elapse(const Duration(seconds: 200)); // from t=0 to t=200 - expect(testLogger.statusText, + expect(logger.statusText, 'Running "flutter pub get" in /...\n' 'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n' 'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n' @@ -94,49 +102,51 @@ void main() { 'pub get failed (server unavailable) -- attempting retry 10 in 64 seconds...\n', // at t=167 ); }); - expect(testLogger.errorText, isEmpty); + expect(logger.errorText, isEmpty); expect(error, isNull); - }, overrides: { - FileSystem: () => MockFileSystem(), - ProcessManager: () => MockProcessManager(69), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({}), - ), - Pub: () => const Pub(), }); - testUsingContext('pub get 66 shows message from pub', () async { + testWithoutContext('pub get 66 shows message from pub', () async { + final BufferLogger logger = BufferLogger.test(); + final Pub pub = Pub( + platform: FakePlatform(environment: const {}), + fileSystem: MockFileSystem(), + logger: logger, + usage: MockUsage(), + botDetector: const BotDetectorAlwaysNo(), + processManager: MockProcessManager(66, stderr: 'err1\nerr2\nerr3\n', stdout: 'out1\nout2\nout3\n'), + ); try { await pub.get(context: PubContext.flutterTests, checkLastModified: false); throw AssertionError('pubGet did not fail'); } on ToolExit catch (error) { expect(error.message, 'pub get failed (66; err3)'); } - expect(testLogger.statusText, + expect(logger.statusText, 'Running "flutter pub get" in /...\n' 'out1\n' 'out2\n' 'out3\n' ); - expect(testLogger.errorText, + expect(logger.errorText, 'err1\n' 'err2\n' 'err3\n' ); - }, overrides: { - ProcessManager: () => MockProcessManager(66, stderr: 'err1\nerr2\nerr3\n', stdout: 'out1\nout2\nout3\n'), - FileSystem: () => MockFileSystem(), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({}), - ), - Pub: () => const Pub(), }); - testUsingContext('pub cache in root is used', () async { + testWithoutContext('pub cache in root is used', () async { String error; - - final MockProcessManager processMock = context.get() as MockProcessManager; - final MockFileSystem fsMock = context.get() as MockFileSystem; + final MockProcessManager processMock = MockProcessManager(69); + final MockFileSystem fsMock = MockFileSystem(); + final Pub pub = Pub( + platform: FakePlatform(environment: const {}), + usage: MockUsage(), + fileSystem: fsMock, + logger: BufferLogger.test(), + processManager: processMock, + botDetector: const BotDetectorAlwaysNo(), + ); FakeAsync().run((FakeAsync time) { MockDirectory.findCache = true; @@ -148,124 +158,137 @@ void main() { error = 'test failed unexpectedly: $thrownError'; }); time.elapse(const Duration(milliseconds: 500)); + expect(processMock.lastPubCache, equals(fsMock.path.join(Cache.flutterRoot, '.pub-cache'))); expect(error, isNull); }); - }, overrides: { - FileSystem: () => MockFileSystem(), - ProcessManager: () => MockProcessManager(69), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({}), - ), - Pub: () => const Pub(), }); - testUsingContext('pub cache in environment is used', () async { - String error; - - final MockProcessManager processMock = context.get() as MockProcessManager; + testWithoutContext('pub cache in environment is used', () async { + final MockProcessManager processMock = MockProcessManager(69); + final Pub pub = Pub( + fileSystem: MockFileSystem(), + logger: BufferLogger.test(), + processManager: processMock, + usage: MockUsage(), + botDetector: const BotDetectorAlwaysNo(), + platform: FakePlatform( + environment: const { + 'PUB_CACHE': 'custom/pub-cache/path', + }, + ), + ); FakeAsync().run((FakeAsync time) { MockDirectory.findCache = true; expect(processMock.lastPubEnvironment, isNull); expect(processMock.lastPubCache, isNull); + + String error; pub.get(context: PubContext.flutterTests, checkLastModified: false).then((void value) { error = 'test completed unexpectedly'; }, onError: (dynamic thrownError) { error = 'test failed unexpectedly: $thrownError'; }); time.elapse(const Duration(milliseconds: 500)); + expect(processMock.lastPubCache, equals('custom/pub-cache/path')); expect(error, isNull); }); - }, overrides: { - FileSystem: () => MockFileSystem(), - ProcessManager: () => MockProcessManager(69), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({ - 'PUB_CACHE': 'custom/pub-cache/path', - }), - ), - Pub: () => const Pub(), }); - testUsingContext('analytics sent on success', () async { + testWithoutContext('analytics sent on success', () async { MockDirectory.findCache = true; + final MockUsage usage = MockUsage(); + final Pub pub = Pub( + fileSystem: MockFileSystem(), + logger: BufferLogger.test(), + processManager: MockProcessManager(0), + botDetector: const BotDetectorAlwaysNo(), + usage: usage, + platform: FakePlatform( + environment: const { + 'PUB_CACHE': 'custom/pub-cache/path', + } + ), + ); + await pub.get(context: PubContext.flutterTests, checkLastModified: false); - verify(globals.flutterUsage.sendEvent('pub-result', 'flutter-tests', label: 'success')).called(1); - }, overrides: { - FileSystem: () => MockFileSystem(), - ProcessManager: () => MockProcessManager(0), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({ - 'PUB_CACHE': 'custom/pub-cache/path', - }), - ), - Usage: () => MockUsage(), - Pub: () => const Pub(), + + verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'success')).called(1); }); - testUsingContext('analytics sent on failure', () async { + testWithoutContext('analytics sent on failure', () async { MockDirectory.findCache = true; + final MockUsage usage = MockUsage(); + final Pub pub = Pub( + usage: usage, + fileSystem: MockFileSystem(), + logger: BufferLogger.test(), + processManager: MockProcessManager(1), + botDetector: const BotDetectorAlwaysNo(), + platform: FakePlatform( + environment: const { + 'PUB_CACHE': 'custom/pub-cache/path', + }, + ), + ); try { await pub.get(context: PubContext.flutterTests, checkLastModified: false); } on ToolExit { // Ignore. } - verify(globals.flutterUsage.sendEvent('pub-result', 'flutter-tests', label: 'failure')).called(1); - }, overrides: { - FileSystem: () => MockFileSystem(), - ProcessManager: () => MockProcessManager(1), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({ - 'PUB_CACHE': 'custom/pub-cache/path', - }), - ), - Usage: () => MockUsage(), - Pub: () => const Pub(), + + verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'failure')).called(1); }); - testUsingContext('analytics sent on failed version solve', () async { + testWithoutContext('analytics sent on failed version solve', () async { MockDirectory.findCache = true; + final MockUsage usage = MockUsage(); + final Pub pub = Pub( + fileSystem: MockFileSystem(), + logger: BufferLogger.test(), + processManager: MockProcessManager( + 1, + stderr: 'version solving failed', + ), + platform: FakePlatform( + environment: { + 'PUB_CACHE': 'custom/pub-cache/path', + }, + ), + usage: usage, + botDetector: const BotDetectorAlwaysNo(), + ); + try { await pub.get(context: PubContext.flutterTests, checkLastModified: false); } on ToolExit { // Ignore. } - verify(globals.flutterUsage.sendEvent('pub-result', 'flutter-tests', label: 'version-solving-failed')).called(1); - }, overrides: { - FileSystem: () => MockFileSystem(), - ProcessManager: () => MockProcessManager( - 1, - stderr: 'version solving failed', - ), - Platform: () => FakePlatform( - environment: UnmodifiableMapView({ - 'PUB_CACHE': 'custom/pub-cache/path', - }), - ), - Usage: () => MockUsage(), - Pub: () => const Pub(), + + verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'version-solving-failed')).called(1); }); - test('Pub error handling', () async { - final MemoryFileSystem fileSystem = MemoryFileSystem(); + testWithoutContext('Pub error handling', () async { + final BufferLogger logger = BufferLogger.test(); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final FakeProcessManager processManager = FakeProcessManager.list([ FakeCommand( command: const [ - '/bin/cache/dart-sdk/bin/pub', + 'bin/cache/dart-sdk/bin/pub', '--verbosity=warning', 'get', '--no-precompile', ], onRun: () { - globals.fs.file('.packages') + fileSystem.file('.dart_tool/package_config.json') .setLastModifiedSync(DateTime(2002)); } ), const FakeCommand( command: [ - '/bin/cache/dart-sdk/bin/pub', + 'bin/cache/dart-sdk/bin/pub', '--verbosity=warning', 'get', '--no-precompile', @@ -273,96 +296,105 @@ void main() { ), FakeCommand( command: const [ - '/bin/cache/dart-sdk/bin/pub', + 'bin/cache/dart-sdk/bin/pub', '--verbosity=warning', 'get', '--no-precompile', ], onRun: () { - globals.fs.file('pubspec.yaml') + fileSystem.file('pubspec.yaml') .setLastModifiedSync(DateTime(2002)); } ), const FakeCommand( command: [ - '/bin/cache/dart-sdk/bin/pub', + 'bin/cache/dart-sdk/bin/pub', '--verbosity=warning', 'get', '--no-precompile', ], ), ]); - await Testbed().run(() async { - // the good scenario: .packages is old, pub updates the file. - globals.fs.file('.packages') - ..createSync() - ..setLastModifiedSync(DateTime(2000)); - globals.fs.file('pubspec.yaml') - ..createSync() - ..setLastModifiedSync(DateTime(2001)); - await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub sets date of .packages to 2002 - expect(testLogger.statusText, 'Running "flutter pub get" in /...\n'); - expect(testLogger.errorText, isEmpty); - expect(globals.fs.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it - expect(globals.fs.file('.packages').lastModifiedSync(), isNot(DateTime(2000))); // because pub changes it to 2002 - expect(globals.fs.file('.packages').lastModifiedSync(), isNot(DateTime(2002))); // because we set the timestamp again after pub - testLogger.clear(); - // bad scenario 1: pub doesn't update file; doesn't matter, because we do instead - globals.fs.file('.packages') - .setLastModifiedSync(DateTime(2000)); - globals.fs.file('pubspec.yaml') - .setLastModifiedSync(DateTime(2001)); - await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub does nothing - expect(testLogger.statusText, 'Running "flutter pub get" in /...\n'); - expect(testLogger.errorText, isEmpty); - expect(globals.fs.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it - expect(globals.fs.file('.packages').lastModifiedSync(), isNot(DateTime(2000))); // because we set the timestamp - expect(globals.fs.file('.packages').lastModifiedSync(), isNot(DateTime(2002))); // just in case FakeProcessManager is buggy - testLogger.clear(); - // bad scenario 2: pub changes pubspec.yaml instead - globals.fs.file('.packages') - .setLastModifiedSync(DateTime(2000)); - globals.fs.file('pubspec.yaml') - .setLastModifiedSync(DateTime(2001)); - try { - await pub.get(context: PubContext.flutterTests, checkLastModified: true); - expect(true, isFalse, reason: 'pub.get did not throw'); - } on ToolExit catch (error) { - expect(error.message, '/: unexpected concurrent modification of pubspec.yaml while running pub.'); - } - expect(testLogger.statusText, 'Running "flutter pub get" in /...\n'); - expect(testLogger.errorText, isEmpty); - expect(globals.fs.file('pubspec.yaml').lastModifiedSync(), DateTime(2002)); // because fake pub above touched it - expect(globals.fs.file('.packages').lastModifiedSync(), DateTime(2000)); // because nothing touched it - // bad scenario 3: pubspec.yaml was created in the future - globals.fs.file('.packages') - .setLastModifiedSync(DateTime(2000)); - globals.fs.file('pubspec.yaml') - .setLastModifiedSync(DateTime(9999)); - assert(DateTime(9999).isAfter(DateTime.now())); - await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub does nothing - expect(testLogger.statusText, contains('Running "flutter pub get" in /...\n')); - expect(testLogger.errorText, startsWith( - 'Warning: File "/pubspec.yaml" was created in the future. Optimizations that rely on ' - 'comparing time stamps will be unreliable. Check your system clock for accuracy.\n' - 'The timestamp was:' - )); - testLogger.clear(); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - Pub: () => const Pub(), - Platform: () => FakePlatform( + final Pub pub = Pub( + usage: MockUsage(), + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + platform: FakePlatform( operatingSystem: 'linux', // so that the command executed is consistent environment: {}, ), - BotDetector: () => const BotDetectorAlwaysNo(), // so that the test never adds --trace to the pub command - }); + botDetector: const BotDetectorAlwaysNo() + ); + + // the good scenario: .packages is old, pub updates the file. + fileSystem.file('.dart_tool/package_config.json') + ..createSync(recursive: true) + ..setLastModifiedSync(DateTime(2000)); + fileSystem.file('pubspec.yaml') + ..createSync() + ..setLastModifiedSync(DateTime(2001)); + await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub sets date of .packages to 2002 + + expect(logger.statusText, 'Running "flutter pub get" in /...\n'); + expect(logger.errorText, isEmpty); + expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it + expect(fileSystem.file('.dart_tool/package_config.json').lastModifiedSync(), isNot(DateTime(2000))); // because pub changes it to 2002 + expect(fileSystem.file('.dart_tool/package_config.json').lastModifiedSync(), isNot(DateTime(2002))); // because we set the timestamp again after pub + logger.clear(); + + // bad scenario 1: pub doesn't update file; doesn't matter, because we do instead + fileSystem.file('.dart_tool/package_config.json') + .setLastModifiedSync(DateTime(2000)); + fileSystem.file('pubspec.yaml') + .setLastModifiedSync(DateTime(2001)); + await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub does nothing + + expect(logger.statusText, 'Running "flutter pub get" in /...\n'); + expect(logger.errorText, isEmpty); + expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it + expect(fileSystem.file('.dart_tool/package_config.json').lastModifiedSync(), isNot(DateTime(2000))); // because we set the timestamp + expect(fileSystem.file('.dart_tool/package_config.json').lastModifiedSync(), isNot(DateTime(2002))); // just in case FakeProcessManager is buggy + logger.clear(); + + // bad scenario 2: pub changes pubspec.yaml instead + fileSystem.file('.dart_tool/package_config.json') + .setLastModifiedSync(DateTime(2000)); + fileSystem.file('pubspec.yaml') + .setLastModifiedSync(DateTime(2001)); + try { + await pub.get(context: PubContext.flutterTests, checkLastModified: true); + expect(true, isFalse, reason: 'pub.get did not throw'); + } on ToolExit catch (error) { + expect(error.message, '/: unexpected concurrent modification of pubspec.yaml while running pub.'); + } + expect(logger.statusText, 'Running "flutter pub get" in /...\n'); + expect(logger.errorText, isEmpty); + expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2002)); // because fake pub above touched it + expect(fileSystem.file('.dart_tool/package_config.json').lastModifiedSync(), DateTime(2000)); // because nothing touched it + + // bad scenario 3: pubspec.yaml was created in the future + fileSystem.file('.dart_tool/package_config.json') + .setLastModifiedSync(DateTime(2000)); + fileSystem.file('pubspec.yaml') + .setLastModifiedSync(DateTime(9999)); + assert(DateTime(9999).isAfter(DateTime.now())); + + await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub does nothing + + expect(logger.statusText, contains('Running "flutter pub get" in /...\n')); + expect(logger.errorText, startsWith( + 'Warning: File "/pubspec.yaml" was created in the future. Optimizations that rely on ' + 'comparing time stamps will be unreliable. Check your system clock for accuracy.\n' + 'The timestamp was:' + )); + logger.clear(); }); } class BotDetectorAlwaysNo implements BotDetector { const BotDetectorAlwaysNo(); + @override Future get isRunningOnBot async => false; } @@ -405,7 +437,7 @@ class MockProcessManager implements ProcessManager { } class MockFileSystem extends ForwardingFileSystem { - MockFileSystem() : super(MemoryFileSystem()); + MockFileSystem() : super(MemoryFileSystem.test()); @override File file(dynamic path) { diff --git a/packages/flutter_tools/test/general.shard/doctor.dart b/packages/flutter_tools/test/general.shard/doctor.dart index 3839adf34415e..cc2ae8c75b8b8 100644 --- a/packages/flutter_tools/test/general.shard/doctor.dart +++ b/packages/flutter_tools/test/general.shard/doctor.dart @@ -44,7 +44,7 @@ void main() { test('doctor validators includes web when feature is enabled', () => testbed.run(() { expect(DoctorValidatorsProvider.defaultInstance.validators, - contains(isA())); + contains(isA())); }, overrides: { FeatureFlags: () => TestFeatureFlags( isWebEnabled: true, @@ -53,7 +53,7 @@ void main() { test('doctor validators does not include web when feature is disabled', () => testbed.run(() { expect(DoctorValidatorsProvider.defaultInstance.validators, - isNot(contains(isA()))); + isNot(contains(isA()))); }, overrides: { FeatureFlags: () => TestFeatureFlags( isWebEnabled: false, diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index bbc52a18fbf90..709ac06e1f5ad 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -104,7 +104,7 @@ void main() { // Set up a simple .packages file for all the tests to use, pointing to one package. dummyPackageDirectory = fs.directory('/pubcache/apackage/lib/'); - packagesFile = fs.file(fs.path.join(flutterProject.directory.path, PackageMap.globalPackagesPath)); + packagesFile = fs.file(fs.path.join(flutterProject.directory.path, globalPackagesPath)); packagesFile..createSync(recursive: true) ..writeAsStringSync('apackage:file://${dummyPackageDirectory.path}\n'); }); @@ -359,8 +359,9 @@ EndGlobal'''); } group('refreshPlugins', () { - testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () { - refreshPluginsList(flutterProject); + testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () async { + await refreshPluginsList(flutterProject); + expect(flutterProject.flutterPluginsFile.existsSync(), false); expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false); }, overrides: { @@ -368,11 +369,12 @@ EndGlobal'''); ProcessManager: () => FakeProcessManager.any(), }); - testUsingContext('Refreshing the plugin list deletes the plugin file when there were plugins but no longer are', () { + testUsingContext('Refreshing the plugin list deletes the plugin file when there were plugins but no longer are', () async { flutterProject.flutterPluginsFile.createSync(); flutterProject.flutterPluginsDependenciesFile.createSync(); - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); + expect(flutterProject.flutterPluginsFile.existsSync(), false); expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false); }, overrides: { @@ -380,11 +382,12 @@ EndGlobal'''); ProcessManager: () => FakeProcessManager.any(), }); - testUsingContext('Refreshing the plugin list creates a plugin directory when there are plugins', () { + testUsingContext('Refreshing the plugin list creates a plugin directory when there are plugins', () async { configureDummyPackageAsPlugin(); when(iosProject.existsSync()).thenReturn(true); - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); + expect(flutterProject.flutterPluginsFile.existsSync(), true); expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true); }, overrides: { @@ -392,7 +395,9 @@ EndGlobal'''); ProcessManager: () => FakeProcessManager.any(), }); - testUsingContext('Refreshing the plugin list modifies .flutter-plugins and .flutter-plugins-dependencies when there are plugins', () { + testUsingContext( + 'Refreshing the plugin list modifies .flutter-plugins ' + 'and .flutter-plugins-dependencies when there are plugins', () async { final Directory pluginA = createPluginWithDependencies(name: 'plugin-a', dependencies: const ['plugin-b', 'plugin-c', 'random-package']); final Directory pluginB = createPluginWithDependencies(name: 'plugin-b', dependencies: const ['plugin-c']); final Directory pluginC = createPluginWithDependencies(name: 'plugin-c', dependencies: const []); @@ -407,7 +412,7 @@ EndGlobal'''); (Invocation _) => version ); - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); // Verify .flutter-plugins-dependencies is configured correctly. expect(flutterProject.flutterPluginsFile.existsSync(), true); @@ -494,13 +499,14 @@ EndGlobal'''); FlutterVersion: () => mockVersion }); - testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () { + testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async { simulatePodInstallRun(iosProject); simulatePodInstallRun(macosProject); configureDummyPackageAsPlugin(); when(iosProject.existsSync()).thenReturn(true); when(macosProject.existsSync()).thenReturn(true); - refreshPluginsList(flutterProject); + + await refreshPluginsList(flutterProject); expect(iosProject.podManifestLock.existsSync(), false); expect(macosProject.podManifestLock.existsSync(), false); }, overrides: { @@ -510,7 +516,7 @@ EndGlobal'''); FlutterVersion: () => mockVersion }); - testUsingContext('No changes to the plugin list does not invalidate the Cocoapod lockfiles', () { + testUsingContext('No changes to the plugin list does not invalidate the Cocoapod lockfiles', () async { configureDummyPackageAsPlugin(); when(iosProject.existsSync()).thenReturn(true); when(macosProject.existsSync()).thenReturn(true); @@ -519,11 +525,11 @@ EndGlobal'''); // Since there was no plugins list, the lock files will be invalidated. // The second call is where the plugins list is compared to the existing one, and if there is no change, // the podfiles shouldn't be invalidated. - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); simulatePodInstallRun(iosProject); simulatePodInstallRun(macosProject); - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); expect(iosProject.podManifestLock.existsSync(), true); expect(macosProject.podManifestLock.existsSync(), true); }, overrides: { @@ -1081,11 +1087,11 @@ flutter: when(featureFlags.isWindowsEnabled).thenReturn(true); }); - testUsingContext('Symlinks are created for Linux plugins', () { + testUsingContext('Symlinks are created for Linux plugins', () async { when(linuxProject.existsSync()).thenReturn(true); configureDummyPackageAsPlugin(); // refreshPluginsList should call createPluginSymlinks. - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); expect(linuxProject.pluginSymlinkDirectory.childLink('apackage').existsSync(), true); }, overrides: { @@ -1094,11 +1100,11 @@ flutter: FeatureFlags: () => featureFlags, }); - testUsingContext('Symlinks are created for Windows plugins', () { + testUsingContext('Symlinks are created for Windows plugins', () async { when(windowsProject.existsSync()).thenReturn(true); configureDummyPackageAsPlugin(); // refreshPluginsList should call createPluginSymlinks. - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); expect(windowsProject.pluginSymlinkDirectory.childLink('apackage').existsSync(), true); }, overrides: { @@ -1130,7 +1136,7 @@ flutter: FeatureFlags: () => featureFlags, }); - testUsingContext('Existing symlinks are removed automatically on refresh when no longer in use', () { + testUsingContext('Existing symlinks are removed automatically on refresh when no longer in use', () async { when(linuxProject.existsSync()).thenReturn(true); when(windowsProject.existsSync()).thenReturn(true); @@ -1144,7 +1150,7 @@ flutter: // refreshPluginsList should remove existing links and recreate on changes. configureDummyPackageAsPlugin(); - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); for (final File file in dummyFiles) { expect(file.existsSync(), false); @@ -1179,11 +1185,11 @@ flutter: FeatureFlags: () => featureFlags, }); - testUsingContext('createPluginSymlinks repairs missing links', () { + testUsingContext('createPluginSymlinks repairs missing links', () async { when(linuxProject.existsSync()).thenReturn(true); when(windowsProject.existsSync()).thenReturn(true); configureDummyPackageAsPlugin(); - refreshPluginsList(flutterProject); + await refreshPluginsList(flutterProject); final List links = [ linuxProject.pluginSymlinkDirectory.childLink('apackage'), diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 5a33104982e26..93f1c7a332b96 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -41,6 +41,7 @@ void main() { ResidentRunner residentRunner; MockDevice mockDevice; MockIsolate mockIsolate; + FakeVmServiceHost fakeVmServiceHost; setUp(() { testbed = Testbed(setup: () { @@ -89,31 +90,12 @@ void main() { invalidatedSourcesCount: 0, ); }); - // TODO(jonahwilliams): replace mock with FakeVmServiceHost once all methods - // are moved to real vm service. when(mockFlutterDevice.devFS).thenReturn(mockDevFS); when(mockFlutterDevice.views).thenReturn([ mockFlutterView, ]); when(mockFlutterDevice.device).thenReturn(mockDevice); when(mockFlutterView.uiIsolate).thenReturn(mockIsolate); - final MockVM mockVM = MockVM(); - when(mockVMService.vm).thenReturn(mockVM); - when(mockVM.isolates).thenReturn([mockIsolate]); - when(mockVMService.streamListen('Isolate')).thenAnswer((Invocation invocation) async { - return vm_service.Success(); - }); - when(mockVMService.onIsolateEvent).thenAnswer((Invocation invocation) { - return Stream.fromIterable([ - vm_service.Event(kind: vm_service.EventKind.kIsolateRunnable, timestamp: 0), - ]); - }); - when(mockVMService.callMethod( - kRunInViewMethod, - args: anyNamed('args'), - )).thenAnswer((Invocation invocation) async { - return vm_service.Success(); - }); when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream.value(testUri)); when(mockFlutterDevice.connect( @@ -125,7 +107,12 @@ void main() { .thenAnswer((Invocation invocation) async { return testUri; }); - when(mockFlutterDevice.vmService).thenReturn(mockVMService); + when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) { + return fakeVmServiceHost.vmService; + }); + when(mockFlutterDevice.flutterDeprecatedVmService).thenAnswer((Invocation invocation) { + return mockVMService; + }); when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.getVMs()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(>[ @@ -147,15 +134,13 @@ void main() { final Completer result = Completer.sync(); return result.future; }); - when(mockIsolate.flutterExit()).thenAnswer((Invocation invocation) { - return Future>.value(null); - }); when(mockIsolate.reload()).thenAnswer((Invocation invocation) { return Future.value(null); }); }); test('ResidentRunner can attach to device successfully', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); final Completer onConnectionInfo = Completer.sync(); final Completer onAppStart = Completer.sync(); final Future result = residentRunner.attach( @@ -174,6 +159,31 @@ void main() { })); test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'streamListen', + args: { + 'streamId': 'Isolate', + } + ), + const FakeVmServiceRequest( + id: '2', + method: kRunInViewMethod, + args: { + 'viewId': null, + 'mainScript': 'lib/main.dart.dill', + 'assetDirectory': 'build/flutter_assets', + } + ), + FakeVmServiceStreamResponse( + streamId: 'Isolate', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventKind.kIsolateRunnable, + ) + ), + ]); when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; @@ -189,7 +199,11 @@ void main() { mockFlutterDevice, ], stayResident: false, - debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true), + debuggingOptions: DebuggingOptions.enabled( + BuildInfo.debug, + fastStart: true, + startPaused: true, + ), ); final Completer onConnectionInfo = Completer.sync(); final Completer onAppStart = Completer.sync(); @@ -209,6 +223,7 @@ void main() { })); test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -255,6 +270,16 @@ void main() { })); test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + // Not all requests are present due to existing mocks + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.reassemble', + args: { + 'isolateId': null, + }, + ), + ]); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -284,6 +309,32 @@ void main() { })); test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async { + // Not all requests are present due to existing mocks + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'streamListen', + args: { + 'streamId': 'Isolate', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: kRunInViewMethod, + args: { + 'viewId': null, + 'mainScript': 'lib/main.dart.dill', + 'assetDirectory': 'build/flutter_assets', + }, + ), + FakeVmServiceStreamResponse( + streamId: 'Isolate', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventKind.kIsolateRunnable, + ) + ) + ]); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -314,6 +365,7 @@ void main() { })); test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -361,6 +413,7 @@ void main() { })); test('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: []); expect(residentRunner.artifactDirectory.path, contains('flutter_tool.')); final ResidentRunner otherRunner = HotRunner( @@ -375,6 +428,7 @@ void main() { })); test('ResidentRunner copies output dill to cache location during preExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('hello'); await residentRunner.preExit(); final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill')); @@ -384,6 +438,7 @@ void main() { })); test('ResidentRunner handles output dill missing during preExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.preExit(); final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill')); @@ -391,6 +446,7 @@ void main() { })); test('ResidentRunner printHelpDetails', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: []); when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.supportsScreenshot).thenReturn(true); @@ -436,39 +492,48 @@ void main() { })); test('ResidentRunner does support CanvasKit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); expect(() => residentRunner.toggleCanvaskit(), throwsA(isA())); })); test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async { - when(mockVMService.callMethod( - kGetSkSLsMethod, - args: anyNamed('args'), - )).thenAnswer((Invocation invocation) async { - return vm_service.Response.parse({ - 'SkSLs': {} - }); - }); + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: kGetSkSLsMethod, + args: { + 'viewId': null, + }, + jsonResponse: { + 'SkSLs': {} + } + ) + ]); await residentRunner.writeSkSL(); expect(testLogger.statusText, contains('No data was receieved')); })); test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: kGetSkSLsMethod, + args: { + 'viewId': null, + }, + jsonResponse: { + 'SkSLs': { + 'A': 'B', + } + } + ) + ]); when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { return TargetPlatform.android_arm; }); when(mockDevice.name).thenReturn('test device'); - when(mockVMService.callMethod( - kGetSkSLsMethod, - args: anyNamed('args'), - )).thenAnswer((Invocation invocation) async { - return vm_service.Response.parse({ - 'SkSLs': { - 'A': 'B', - } - }); - }); await residentRunner.writeSkSL(); expect(testLogger.statusText, contains('flutter_01.sksl')); @@ -479,9 +544,28 @@ void main() { 'engineRevision': '42.2', // From FakeFlutterVersion 'data': {'A': 'B'} }); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'false', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + ) + ]); when(mockDevice.supportsScreenshot).thenReturn(true); when(mockDevice.takeScreenshot(any)) .thenAnswer((Invocation invocation) async { @@ -491,23 +575,12 @@ void main() { await residentRunner.screenshot(mockFlutterDevice); - // disables debug banner. - verify(mockIsolate.flutterDebugAllowBanner(false)).called(1); - // Enables debug banner. - verify(mockIsolate.flutterDebugAllowBanner(true)).called(1); expect(testLogger.statusText, contains('1kB')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); - test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws pre', () => testbed.run(() async { - when(mockDevice.supportsScreenshot).thenReturn(true); - when(mockIsolate.flutterDebugAllowBanner(false)).thenThrow(Exception()); - - await residentRunner.screenshot(mockFlutterDevice); - - expect(testLogger.errorText, contains('Error')); - })); - - test('ResidentTunner clears the screen when it should', () => testbed.run(() async { + test('ResidentRunner clears the screen when it should', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); const String message = 'This should be cleared'; expect(testLogger.statusText, equals('')); testLogger.printStatus(message); @@ -516,16 +589,72 @@ void main() { expect(testLogger.statusText, equals('')); })); - test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws post', () => testbed.run(() async { + test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'false', + }, + // Failed response, + errorCode: RPCErrorCodes.kInternalError, + ) + ]); when(mockDevice.supportsScreenshot).thenReturn(true); - when(mockIsolate.flutterDebugAllowBanner(true)).thenThrow(Exception()); + await residentRunner.screenshot(mockFlutterDevice); + expect(testLogger.errorText, contains('Error')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); + })); + + test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'false', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + // Failed response, + errorCode: RPCErrorCodes.kInternalError, + ) + ]); + when(mockDevice.supportsScreenshot).thenReturn(true); await residentRunner.screenshot(mockFlutterDevice); expect(testLogger.errorText, contains('Error')); })); test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'false', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: 'ext.flutter.debugAllowBanner', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + ), + ]); when(mockDevice.supportsScreenshot).thenReturn(true); when(mockDevice.takeScreenshot(any)).thenThrow(Exception()); @@ -535,6 +664,7 @@ void main() { })); test("ResidentRunner can't take screenshot on device without support", () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: []); when(mockDevice.supportsScreenshot).thenReturn(false); expect(() => residentRunner.screenshot(mockFlutterDevice), @@ -542,6 +672,7 @@ void main() { })); test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); residentRunner = HotRunner( [ mockFlutterDevice, @@ -558,18 +689,17 @@ void main() { await residentRunner.screenshot(mockFlutterDevice); - // doesn't disabled debug banner. - verifyNever(mockIsolate.flutterDebugAllowBanner(false)); - // doesn't enable debug banner. - verifyNever(mockIsolate.flutterDebugAllowBanner(true)); expect(testLogger.statusText, contains('1kB')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); final TestFlutterDevice flutterDevice = TestFlutterDevice( mockDevice, [ mockFlutterView ], ); + flutterDevice.vmService = fakeVmServiceHost.vmService; final MockServiceEvent mockServiceEvent = MockServiceEvent(); when(mockServiceEvent.isPauseEvent).thenReturn(true); when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent); @@ -577,15 +707,25 @@ void main() { await flutterDevice.exitApps(); - verifyNever(mockIsolate.flutterExit()); verify(mockDevice.stopApp(any)).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.exit', + args: { + 'isolateId': null, + }, + ) + ]); final TestFlutterDevice flutterDevice = TestFlutterDevice( mockDevice, - [mockFlutterView ], + [mockFlutterView], ); + flutterDevice.vmService = fakeVmServiceHost.vmService; final MockServiceEvent mockServiceEvent = MockServiceEvent(); when(mockServiceEvent.isPauseEvent).thenReturn(false); @@ -593,17 +733,18 @@ void main() { when(mockDevice.supportsFlutterExit).thenReturn(true); await flutterDevice.exitApps(); - - verify(mockIsolate.flutterExit()).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('ResidentRunner refreshViews calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.refreshViews(); verify(mockFlutterDevice.refreshViews()).called(1); })); test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugDumpApp(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -611,6 +752,7 @@ void main() { })); test('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugDumpRenderTree(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -618,6 +760,7 @@ void main() { })); test('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugDumpLayerTree(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -625,6 +768,7 @@ void main() { })); test('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugDumpSemanticsTreeInTraversalOrder(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -632,6 +776,7 @@ void main() { })); test('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -639,6 +784,7 @@ void main() { })); test('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugToggleDebugPaintSizeEnabled(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -646,13 +792,15 @@ void main() { })); test('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugToggleDebugCheckElevationsEnabled(); verify(mockFlutterDevice.refreshViews()).called(1); verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1); })); - test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(()async { + test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugTogglePerformanceOverlayOverride(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -660,6 +808,7 @@ void main() { })); test('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugToggleWidgetInspector(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -667,6 +816,7 @@ void main() { })); test('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); await residentRunner.debugToggleProfileWidgetBuilds(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -674,6 +824,7 @@ void main() { })); test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = HotRunner( [ @@ -694,6 +845,7 @@ void main() { })); test('HotRunner unforwards device ports', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder(); when(mockDevice.portForwarder).thenReturn(mockPortForwarder); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); @@ -721,6 +873,7 @@ void main() { })); test('HotRunner handles failure to write vmservice file', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = HotRunner( [ @@ -744,6 +897,7 @@ void main() { test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = ColdRunner( [ @@ -764,6 +918,7 @@ void main() { })); test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); final MockDevice mockDevice = MockDevice(); when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { return TargetPlatform.web_javascript; @@ -792,6 +947,7 @@ void main() { })); test('connect sets up log reader', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); final MockDevice mockDevice = MockDevice(); final MockDeviceLogReader mockLogReader = MockDeviceLogReader(); when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader); diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart index 9444d43fa2c19..9b9027ad080ac 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart @@ -131,14 +131,18 @@ void main() { final MockChromeConnection mockChromeConnection = MockChromeConnection(); final MockChromeTab mockChromeTab = MockChromeTab(); final MockWipConnection mockWipConnection = MockWipConnection(); + final MockChromiumLauncher chromiumLauncher = MockChromiumLauncher(); when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async { return mockChromeTab; }); when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async { return mockWipConnection; }); + when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { + return chrome; + }); when(chrome.chromeConnection).thenReturn(mockChromeConnection); - launchChromeInstance(chrome); + when(chromeDevice.chromeLauncher).thenReturn(chromiumLauncher); when(mockFlutterDevice.device).thenReturn(chromeDevice); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( @@ -163,10 +167,11 @@ class MockDebugConnection extends Mock implements DebugConnection {} class MockVmService extends Mock implements VmService {} class MockStatus extends Mock implements Status {} class MockFlutterDevice extends Mock implements FlutterDevice {} -class MockChromeDevice extends Mock implements ChromeDevice {} -class MockChrome extends Mock implements Chrome {} +class MockChromeDevice extends Mock implements ChromiumDevice {} +class MockChrome extends Mock implements Chromium {} class MockChromeConnection extends Mock implements ChromeConnection {} class MockChromeTab extends Mock implements ChromeTab {} class MockWipConnection extends Mock implements WipConnection {} class MockBuildSystem extends Mock implements BuildSystem {} class MockPub extends Mock implements Pub {} +class MockChromiumLauncher extends Mock implements ChromiumLauncher {} diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index b018a46b3ff8f..6eee507ddfde0 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter_tools/src/vmservice.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'package:dwds/dwds.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/io.dart'; @@ -32,11 +34,50 @@ import '../src/common.dart'; import '../src/context.dart'; import '../src/testbed.dart'; +const List kAttachLogExpectations = [ + FakeVmServiceRequest( + id: '1', + method: 'streamListen', + args: { + 'streamId': 'Stdout', + }, + ), + FakeVmServiceRequest( + id: '2', + method: 'streamListen', + args: { + 'streamId': 'Stderr', + }, + ) +]; + +const List kAttachIsolateExpectations = [ + FakeVmServiceRequest( + id: '3', + method: 'streamListen', + args: { + 'streamId': 'Isolate' + } + ), + FakeVmServiceRequest( + id: '4', + method: 'registerService', + args: { + 'service': 'reloadSources', + 'alias': 'FlutterTools', + } + ) +]; + +const List kAttachExpectations = [ + ...kAttachLogExpectations, + ...kAttachIsolateExpectations, +]; + void main() { Testbed testbed; ResidentWebRunner residentWebRunner; MockDebugConnection mockDebugConnection; - MockVmService mockVmService; MockChromeDevice mockChromeDevice; MockAppConnection mockAppConnection; MockFlutterDevice mockFlutterDevice; @@ -49,11 +90,10 @@ void main() { MockWipDebugger mockWipDebugger; MockWebServerDevice mockWebServerDevice; MockDevice mockDevice; + FakeVmServiceHost fakeVmServiceHost; setUp(() { - resetChromeForTesting(); mockDebugConnection = MockDebugConnection(); - mockVmService = MockVmService(); mockDevice = MockDevice(); mockAppConnection = MockAppConnection(); mockFlutterDevice = MockFlutterDevice(); @@ -110,24 +150,12 @@ void main() { )).thenAnswer((Invocation _) async { return UpdateFSReport(success: true, syncedBytes: 0)..invalidatedModules = []; }); - when(mockDebugConnection.vmService).thenReturn(mockVmService); + when(mockDebugConnection.vmService).thenAnswer((Invocation invocation) { + return fakeVmServiceHost.vmService; + }); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { return Completer().future; }); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) { - return const Stream.empty(); - }); - when(mockVmService.onStderrEvent).thenAnswer((Invocation _) { - return const Stream.empty(); - }); - when(mockVmService.onDebugEvent).thenAnswer((Invocation _) { - return const Stream.empty(); - }); - when(mockVmService.onIsolateEvent).thenAnswer((Invocation _) { - return Stream.fromIterable([ - Event(kind: EventKind.kIsolateStart, timestamp: 1), - ]); - }); when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/'); when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); when(mockWebDevFS.sources).thenReturn([]); @@ -144,7 +172,10 @@ void main() { } test('runner with web server device does not support debugging without --start-paused', () => testbed.run(() { - when(mockFlutterDevice.device).thenReturn(WebServerDevice()); + when(mockFlutterDevice.device).thenReturn(WebServerDevice( + logger: BufferLogger.test(), + )); + fakeVmServiceHost = FakeVmServiceHost(requests: []); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), @@ -158,11 +189,15 @@ void main() { when(mockFlutterDevice.device).thenReturn(MockChromeDevice()); expect(residentWebRunner.debuggingEnabled, true); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('runner with web server device supports debugging with --start-paused', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); - when(mockFlutterDevice.device).thenReturn(WebServerDevice()); + when(mockFlutterDevice.device).thenReturn(WebServerDevice( + logger: BufferLogger.test(), + )); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), @@ -177,6 +212,7 @@ void main() { })); test('profile does not supportsServiceProtocol', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: []); when(mockFlutterDevice.device).thenReturn(mockChromeDevice); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, @@ -192,6 +228,7 @@ void main() { })); test('Exits on run if application does not support the web', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); globals.fs.file('pubspec.yaml').createSync(); expect(await residentWebRunner.run(), 1); @@ -199,6 +236,7 @@ void main() { })); test('Exits on run if target file does not exist', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file(globals.fs.path.join('web', 'index.html')).createSync(recursive: true); @@ -208,6 +246,7 @@ void main() { })); test('Can successfully run and connect to vmservice', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final BufferLogger bufferLogger = delegateLogger.delegate as BufferLogger; @@ -220,7 +259,6 @@ void main() { final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; verify(mockAppConnection.runMain()).called(1); - verify(mockVmService.registerService('reloadSources', 'FlutterTools')).called(1); verify(status.stop()).called(1); verify(pub.get( context: PubContext.pubGet, @@ -240,6 +278,7 @@ void main() { })); test('Can successfully run and disconnect with --no-resident', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, @@ -254,30 +293,28 @@ void main() { })); test('Listens to stdout and stderr streams before running main', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachLogExpectations, + FakeVmServiceStreamResponse( + streamId: 'Stdout', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventStreams.kStdout, + bytes: base64.encode(utf8.encode('THIS MESSAGE IS IMPORTANT')) + ), + ), + FakeVmServiceStreamResponse( + streamId: 'Stderr', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventStreams.kStderr, + bytes: base64.encode(utf8.encode('SO IS THIS')) + ), + ), + ...kAttachIsolateExpectations, + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); - final StreamController stdoutController = StreamController.broadcast(); - final StreamController stderrController = StreamController.broadcast(); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) { - return stdoutController.stream; - }); - when(mockVmService.onStderrEvent).thenAnswer((Invocation _) { - return stderrController.stream; - }); - when(mockAppConnection.runMain()).thenAnswer((Invocation invocation) { - stdoutController.add(Event.parse({ - 'type': 'Event', - 'kind': 'WriteEvent', - 'timestamp': 1569473488296, - 'bytes': base64.encode('THIS MESSAGE IS IMPORTANT'.codeUnits), - })); - stderrController.add(Event.parse({ - 'type': 'Event', - 'kind': 'WriteEvent', - 'timestamp': 1569473488296, - 'bytes': base64.encode('SO IS THIS'.codeUnits), - })); - }); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); @@ -288,6 +325,7 @@ void main() { })); test('Does not run main with --start-paused', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), @@ -298,10 +336,7 @@ void main() { ) as ResidentWebRunner; _setupMocks(); final Completer connectionInfoCompleter = Completer(); - final StreamController controller = StreamController.broadcast(); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) { - return controller.stream; - }); + unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); @@ -311,8 +346,35 @@ void main() { })); test('Can hot reload after attaching', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + method: 'hotRestart', + id: '5', + args: null, + jsonResponse: { + 'type': 'Success', + } + ), + ]); _setupMocks(); - launchChromeInstance(mockChrome); + final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); + when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir'))) + .thenAnswer((Invocation invocation) async { + return mockChrome; + }); + when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { + return mockChrome; + }); + when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice( + fileSystem: globals.fs, + chromiumLauncher: chromiumLauncher, + logger: globals.logger, + platform: FakePlatform(operatingSystem: 'linux'), + processManager: FakeProcessManager.any(), + )); + when(chromiumLauncher.canFindExecutable()).thenReturn(true); + chromiumLauncher.testLaunchChromium(mockChrome); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), target: anyNamed('target'), @@ -352,7 +414,7 @@ void main() { expect(config, allOf([ containsPair('cd27', 'web-javascript'), - containsPair('cd28', null), + containsPair('cd28', ''), containsPair('cd29', 'false'), containsPair('cd30', 'true'), ])); @@ -362,8 +424,35 @@ void main() { })); test('Can hot restart after attaching', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + method: 'hotRestart', + id: '5', + args: null, + jsonResponse: { + 'type': 'Success', + } + ), + ]); _setupMocks(); - launchChromeInstance(mockChrome); + final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); + when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir'))) + .thenAnswer((Invocation invocation) async { + return mockChrome; + }); + when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { + return mockChrome; + }); + when(chromiumLauncher.canFindExecutable()).thenReturn(true); + when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice( + fileSystem: globals.fs, + chromiumLauncher: chromiumLauncher, + logger: globals.logger, + platform: FakePlatform(operatingSystem: 'linux'), + processManager: FakeProcessManager.any(), + )); + chromiumLauncher.testLaunchChromium(mockChrome); Uri entrypointFileUri; when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), @@ -407,7 +496,7 @@ void main() { expect(config, allOf([ containsPair('cd27', 'web-javascript'), - containsPair('cd28', null), + containsPair('cd28', ''), containsPair('cd29', 'false'), containsPair('cd30', 'true'), ])); @@ -417,6 +506,7 @@ void main() { })); test('Can hot restart after attaching with web-server device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests :kAttachExpectations); _setupMocks(); when(mockFlutterDevice.device).thenReturn(mockWebServerDevice); when(mockWebDevFS.update( @@ -454,10 +544,13 @@ void main() { })); test('web resident runner is debuggable', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); + expect(residentWebRunner.debuggingEnabled, true); })); test('web resident runner can toggle CanvasKit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null); when(mockWebDevFS.webAssetServer).thenReturn(webAssetServer); @@ -471,6 +564,7 @@ void main() { })); test('Exits when initial compile fails', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), @@ -501,30 +595,34 @@ void main() { })); test('Faithfully displays stdout messages with leading/trailing spaces', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachLogExpectations, + FakeVmServiceStreamResponse( + streamId: 'Stdout', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventStreams.kStdout, + bytes: base64.encode( + utf8.encode(' This is a message with 4 leading and trailing spaces '), + ), + ), + ), + ...kAttachIsolateExpectations, + ]); _setupMocks(); - final StreamController stdoutController = StreamController(); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation invocation) { - return stdoutController.stream; - }); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - stdoutController.add(Event( - timestamp: 0, - kind: 'Stdout', - bytes: base64.encode(utf8.encode(' This is a message with 4 leading and trailing spaces '))), - ); - // Wait one event loop for the stream listener to fire. - await null; - expect(testLogger.statusText, contains(' This is a message with 4 leading and trailing spaces ')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Fails on compilation errors in hot restart', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( @@ -559,62 +657,55 @@ void main() { })); test('Fails non-fatally on vmservice response error for hot restart', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'hotRestart', + args: null, + jsonResponse: { + 'type': 'Failed', + } + ) + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenAnswer((Invocation _) async { - return Response.parse({'type': 'Failed'}); - }); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); - - expect(result.code, 0); - })); - - test('Fails fatally on vmservice RpcError', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenThrow(RPCError('Something went wrong', 2, '123')); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); - expect(result.code, 1); - expect(result.message, contains('Something went wrong')); - })); - - test('Fails fatally on vmservice WipError', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenThrow(WipError({})); final OperationResult result = await residentWebRunner.restart(fullRestart: false); - expect(result.code, 1); + expect(result.code, 0); })); - test('Fails fatally on vmservice Exception', () => testbed.run(() async { + test('Fails fatally on Vm Service error response', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'hotRestart', + args: null, + // Failed response, + errorCode: RPCErrorCodes.kInternalError, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenThrow(Exception('Something went wrong')); final OperationResult result = await residentWebRunner.restart(fullRestart: false); expect(result.code, 1); - expect(result.message, contains('Something went wrong')); + expect(result.message, + contains(RPCErrorCodes.kInternalError.toString())); })); test('printHelp without details has web warning', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); residentWebRunner.printHelp(details: false); expect(testLogger.statusText, contains('Warning')); @@ -623,6 +714,16 @@ void main() { })); test('debugDumpApp', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpApp', + args: { + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( @@ -631,10 +732,20 @@ void main() { await connectionInfoCompleter.future; await residentWebRunner.debugDumpApp(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpApp')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpLayerTree', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpLayerTree', + args: { + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( @@ -643,10 +754,20 @@ void main() { await connectionInfoCompleter.future; await residentWebRunner.debugDumpLayerTree(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpLayerTree')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpRenderTree', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpRenderTree', + args: { + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( @@ -655,10 +776,20 @@ void main() { await connectionInfoCompleter.future; await residentWebRunner.debugDumpRenderTree(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpRenderTree')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpSemanticsTreeInTraversalOrder', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', + args: { + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( @@ -667,113 +798,224 @@ void main() { await connectionInfoCompleter.future; await residentWebRunner.debugDumpSemanticsTreeInTraversalOrder(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInTraversalOrder')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpSemanticsTreeInInverseHitTestOrder', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', + args: { + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); + await connectionInfoCompleter.future; await residentWebRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugToggleDebugPaintSizeEnabled', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugPaint', + args: { + 'isolateId': null, + }, + jsonResponse: { + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.debugPaint', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: { + 'value': 'true' + }, + ) + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.debugPaint')) - .thenAnswer((Invocation _) async { - return Response.parse({'enabled': false}); - }); + await residentWebRunner.debugToggleDebugPaintSizeEnabled(); - verify(mockVmService.callServiceExtension('ext.flutter.debugPaint', - args: {'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugTogglePerformanceOverlayOverride', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.showPerformanceOverlay', + args: { + 'isolateId': null, + }, + jsonResponse: { + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.showPerformanceOverlay', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: { + 'enabled': 'true' + }, + ) + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay')) - .thenAnswer((Invocation _) async { - return Response.parse({'enabled': false}); - }); await residentWebRunner.debugTogglePerformanceOverlayOverride(); - verify(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay', - args: {'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugToggleWidgetInspector', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.inspector.show', + args: { + 'isolateId': null, + }, + jsonResponse: { + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.inspector.show', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: { + 'enabled': 'true' + }, + ) + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector')) - .thenAnswer((Invocation _) async { - return Response.parse({'enabled': false}); - }); await residentWebRunner.debugToggleWidgetInspector(); - verify(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector', - args: {'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugToggleProfileWidgetBuilds', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.profileWidgetBuilds', + args: { + 'isolateId': null, + }, + jsonResponse: { + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.profileWidgetBuilds', + args: { + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: { + 'enabled': 'true' + }, + ) + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds')) - .thenAnswer((Invocation _) async { - return Response.parse({'enabled': false}); - }); await residentWebRunner.debugToggleProfileWidgetBuilds(); - verify(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds', - args: {'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugTogglePlatform', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.platformOverride', + args: { + 'isolateId': null, + }, + jsonResponse: { + 'value': 'iOS' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.platformOverride', + args: { + 'isolateId': null, + 'value': 'fuchsia', + }, + jsonResponse: { + 'value': 'fuchsia' + }, + ), + ]); _setupMocks(); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.platformOverride')) - .thenAnswer((Invocation _) async { - return Response.parse({'value': 'iOS'}); - }); await residentWebRunner.debugTogglePlatform(); - expect(testLogger.statusText, contains('Switched operating system to fuchsia')); - verify(mockVmService.callServiceExtension('ext.flutter.platformOverride', - args: {'value': 'fuchsia'})).called(1); + expect(testLogger.statusText, + contains('Switched operating system to fuchsia')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('cleanup of resources is safe to call multiple times', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + ]); _setupMocks(); bool debugClosed = false; when(mockDevice.stopApp(any)).thenAnswer((Invocation invocation) async { @@ -793,9 +1035,13 @@ void main() { await residentWebRunner.exit(); verifyNever(mockDebugConnection.close()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('cleans up Chrome if tab is closed', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + ]); _setupMocks(); final Completer onDone = Completer(); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { @@ -809,9 +1055,13 @@ void main() { onDone.complete(); await result; + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Prints target and device name on run', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachExpectations, + ]); _setupMocks(); when(mockDevice.name).thenReturn('Chromez'); final Completer connectionInfoCompleter = Completer(); @@ -820,12 +1070,49 @@ void main() { )); await connectionInfoCompleter.future; - expect(testLogger.statusText, contains('Launching ${globals.fs.path.join('lib', 'main.dart')} on Chromez in debug mode')); + expect(testLogger.statusText, contains( + 'Launching ${globals.fs.path.join('lib', 'main.dart')} on ' + 'Chromez in debug mode', + )); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: [ + ...kAttachLogExpectations, + const FakeVmServiceRequest( + id: '3', + method: 'streamListen', + args: { + 'streamId': 'Isolate' + } + ), + const FakeVmServiceRequest( + id: '4', + method: 'registerService', + args: { + 'service': 'reloadSources', + 'alias': 'FlutterTools', + } + ) + ]); _setupMocks(); - when(mockFlutterDevice.device).thenReturn(ChromeDevice()); + final ChromiumLauncher chromiumLauncher = MockChromeLauncher(); + when(chromiumLauncher.launch(any, cacheDir: anyNamed('cacheDir'))) + .thenAnswer((Invocation invocation) async { + return mockChrome; + }); + when(chromiumLauncher.connectedInstance).thenAnswer((Invocation invocation) async { + return mockChrome; + }); + when(mockFlutterDevice.device).thenReturn(GoogleChromeDevice( + fileSystem: globals.fs, + chromiumLauncher: chromiumLauncher, + logger: globals.logger, + platform: FakePlatform(operatingSystem: 'linux'), + processManager: FakeProcessManager.any(), + )); + when(chromiumLauncher.canFindExecutable()).thenReturn(true); when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { return Uri.parse('http://localhost:8765/app/'); }); @@ -840,7 +1127,7 @@ void main() { return mockWipConnection; }); when(chrome.chromeConnection).thenReturn(mockChromeConnection); - launchChromeInstance(chrome); + chromiumLauncher.testLaunchChromium(chrome); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); @@ -870,14 +1157,17 @@ void main() { }, }, ))); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: { Logger: () => DelegateLogger(BufferLogger.test()), - ChromeLauncher: () => MockChromeLauncher(), })); test('Sends unlaunched app.webLaunchUrl event for Web Server device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); - when(mockFlutterDevice.device).thenReturn(WebServerDevice()); + when(mockFlutterDevice.device).thenReturn(WebServerDevice( + logger: globals.logger, + )); when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { return Uri.parse('http://localhost:8765/app/'); }); @@ -910,106 +1200,65 @@ void main() { }, }, ))); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: { Logger: () => DelegateLogger(BufferLogger.test()) })); test('Successfully turns WebSocketException into ToolExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw const WebSocketException(); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); + when(mockWebDevFS.connect(any)) + .thenThrow(const WebSocketException()); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsToolExit()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Successfully turns AppConnectionException into ToolExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw AppConnectionException('Could not connect to application with appInstanceId: c0ae0750-ee91-11e9-cea6-35d95a968356'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); + when(mockWebDevFS.connect(any)) + .thenThrow(AppConnectionException('')); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsToolExit()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Successfully turns ChromeDebugError into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw ChromeDebugException({}); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); + fakeVmServiceHost = FakeVmServiceHost(requests: []); + _setupMocks(); - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); + when(mockWebDevFS.connect(any)) + .thenThrow(ChromeDebugException({})); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsToolExit()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); - test('Rethrows Exception type', () => testbed.run(() async { + test('Rethrows unknown Exception type from dwds', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw Exception('Something went wrong'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); + when(mockWebDevFS.connect(any)).thenThrow(Exception()); - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsException); - - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsException); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); - test('Rethrows unknown exception type from web tooling', () => testbed.run(() async { + + test('Rethrows unknown Error type from dwds tooling', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); _setupMocks(); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); delegateLogger.status = mockStatus; - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw StateError('Something went wrong'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsStateError); + when(mockWebDevFS.connect(any)).thenThrow(StateError('')); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsStateError); verify(mockStatus.stop()).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: { Logger: () => DelegateLogger(BufferLogger( terminal: AnsiTerminal( @@ -1021,9 +1270,9 @@ void main() { })); } -class MockChromeLauncher extends Mock implements ChromeLauncher {} +class MockChromeLauncher extends Mock implements ChromiumLauncher {} class MockFlutterUsage extends Mock implements Usage {} -class MockChromeDevice extends Mock implements ChromeDevice {} +class MockChromeDevice extends Mock implements ChromiumDevice {} class MockDebugConnection extends Mock implements DebugConnection {} class MockAppConnection extends Mock implements AppConnection {} class MockVmService extends Mock implements VmService {} @@ -1031,7 +1280,7 @@ class MockStatus extends Mock implements Status {} class MockFlutterDevice extends Mock implements FlutterDevice {} class MockWebDevFS extends Mock implements WebDevFS {} class MockResidentCompiler extends Mock implements ResidentCompiler {} -class MockChrome extends Mock implements Chrome {} +class MockChrome extends Mock implements Chromium {} class MockChromeConnection extends Mock implements ChromeConnection {} class MockChromeTab extends Mock implements ChromeTab {} class MockWipConnection extends Mock implements WipConnection {} diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart index 3779ff895a172..19126608500cb 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart @@ -116,11 +116,13 @@ void main() { fs.directory('$_kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/').createSync(recursive: true); fs.directory('$_kArbitraryEngineRoot/src/out/host_debug').createSync(recursive: true); fs.file(_kDotPackages).writeAsStringSync('sky_engine:file://$_kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/'); + await runner.run(['dummy', '--local-engine=ios_debug']); // Verify that this also works if the sky_engine path is a symlink to the engine root. fs.link('/symlink').createSync(_kArbitraryEngineRoot); fs.file(_kDotPackages).writeAsStringSync('sky_engine:file:///symlink/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/'); + await runner.run(['dummy', '--local-engine=ios_debug']); }, overrides: { FileSystem: () => fs, @@ -129,8 +131,10 @@ void main() { }, initializeFlutterRoot: false); testUsingContext('works if --local-engine is specified and --local-engine-src-path is specified', () async { + fs.file(_kDotPackages).writeAsStringSync('\n'); fs.directory('$_kArbitraryEngineRoot/src/out/ios_debug').createSync(recursive: true); fs.directory('$_kArbitraryEngineRoot/src/out/host_debug').createSync(recursive: true); + await runner.run(['dummy', '--local-engine-src-path=$_kArbitraryEngineRoot/src', '--local-engine=ios_debug']); }, overrides: { FileSystem: () => fs, @@ -139,8 +143,10 @@ void main() { }, initializeFlutterRoot: false); testUsingContext('works if --local-engine is specified and --local-engine-src-path is determined by flutter root', () async { + fs.file(_kDotPackages).writeAsStringSync('\n'); fs.directory('$_kEngineRoot/src/out/ios_debug').createSync(recursive: true); fs.directory('$_kEngineRoot/src/out/host_debug').createSync(recursive: true); + await runner.run(['dummy', '--local-engine=ios_debug']); }, overrides: { FileSystem: () => fs, diff --git a/packages/flutter_tools/test/general.shard/runner/runner_test.dart b/packages/flutter_tools/test/general.shard/runner/runner_test.dart index 38ff6a47146d2..44d3d793ca1e4 100644 --- a/packages/flutter_tools/test/general.shard/runner/runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/runner_test.dart @@ -24,7 +24,6 @@ void main() { MockGitHubTemplateCreator mockGitHubTemplateCreator; setUp(() { mockGitHubTemplateCreator = MockGitHubTemplateCreator(); - runner.crashFileSystem = MemoryFileSystem(); // Instead of exiting with dart:io exit(), this causes an exception to // be thrown, which we catch with the onError callback in the zone below. io.setExitFunctionForTests((int _) { throw 'test exit';}); @@ -32,7 +31,6 @@ void main() { }); tearDown(() { - runner.crashFileSystem = const LocalFileSystem(); io.restoreExitFunction(); Cache.enableLocking(); }); diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart index 9439e852e141a..88db3c1903b45 100644 --- a/packages/flutter_tools/test/general.shard/vmservice_test.dart +++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/convert.dart'; -import 'package:meta/meta.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:mockito/mockito.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -285,10 +284,10 @@ void main() { testWithoutContext('runInView forwards arguments correctly', () async { final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [ - const FakeVmServiceRequest(method: 'streamListen', id: '1', params: { + const FakeVmServiceRequest(method: 'streamListen', id: '1', args: { 'streamId': 'Isolate' }), - const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', params: { + const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', args: { 'viewId': '1234', 'mainScript': 'main.dart', 'assetDirectory': 'flutter_assets/', @@ -312,95 +311,6 @@ void main() { }); } -class FakeVmServiceHost { - FakeVmServiceHost({ - @required List requests, - }) : _requests = requests { - _vmService = vm_service.VmService( - _input.stream, - _output.add, - ); - _applyStreamListen(); - _output.stream.listen((String data) { - final Map request = json.decode(data) as Map; - if (_requests.isEmpty) { - throw Exception('Unexpected request: $request'); - } - final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest; - expect(fakeRequest, isA() - .having((FakeVmServiceRequest request) => request.method, 'method', request['method']) - .having((FakeVmServiceRequest request) => request.id, 'id', request['id']) - .having((FakeVmServiceRequest request) => request.params, 'params', request['params']) - ); - _input.add(json.encode({ - 'jsonrpc': '2.0', - 'id': fakeRequest.id, - 'result': fakeRequest.jsonResponse ?? {'type': 'Success'}, - })); - _applyStreamListen(); - }); - } - - final List _requests; - final StreamController _input = StreamController(); - final StreamController _output = StreamController(); - - vm_service.VmService get vmService => _vmService; - vm_service.VmService _vmService; - - bool get hasRemainingExpectations => _requests.isNotEmpty; - - // remove FakeStreamResponse objects from _requests until it is empty - // or until we hit a FakeRequest - void _applyStreamListen() { - while (_requests.isNotEmpty && !_requests.first.isRequest) { - final FakeVmServiceStreamResponse response = _requests.removeAt(0) as FakeVmServiceStreamResponse; - _input.add(json.encode({ - 'jsonrpc': '2.0', - 'method': 'streamNotify', - 'params': { - 'streamId': response.streamId, - 'event': response.event.toJson(), - }, - })); - } - } -} - -abstract class VmServiceExpectation { - bool get isRequest; -} - -class FakeVmServiceRequest implements VmServiceExpectation { - const FakeVmServiceRequest({ - @required this.method, - @required this.id, - @required this.params, - this.jsonResponse, - }); - - final String method; - final String id; - final Map params; - final Map jsonResponse; - - @override - bool get isRequest => true; -} - -class FakeVmServiceStreamResponse implements VmServiceExpectation { - const FakeVmServiceStreamResponse({ - @required this.event, - @required this.streamId, - }); - - final vm_service.Event event; - final String streamId; - - @override - bool get isRequest => false; -} - class MockDevice extends Mock implements Device {} class MockVMService extends Mock implements vm_service.VmService {} class MockFlutterVersion extends Mock implements FlutterVersion { diff --git a/packages/flutter_tools/test/general.shard/web/chrome_test.dart b/packages/flutter_tools/test/general.shard/web/chrome_test.dart index 2ee9d2c7f90ad..fef3b822aa2b9 100644 --- a/packages/flutter_tools/test/general.shard/web/chrome_test.dart +++ b/packages/flutter_tools/test/general.shard/web/chrome_test.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:file/memory.dart'; -import 'package:flutter_tools/src/base/common.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; @@ -16,7 +16,7 @@ import 'package:platform/platform.dart'; import '../../src/common.dart'; import '../../src/context.dart'; -const List _kChromeArgs = [ +const List kChromeArgs = [ '--disable-background-timer-throttling', '--disable-extensions', '--disable-popup-blocking', @@ -30,7 +30,7 @@ const List _kChromeArgs = [ const String kDevtoolsStderr = '\n\nDevTools listening\n\n'; void main() { - ChromeLauncher chromeLauncher; + ChromiumLauncher chromeLauncher; FileSystem fileSystem; Platform platform; FakeProcessManager processManager; @@ -49,66 +49,91 @@ void main() { }); fileSystem = MemoryFileSystem.test(); processManager = FakeProcessManager.list([]); - chromeLauncher = ChromeLauncher( + chromeLauncher = ChromiumLauncher( fileSystem: fileSystem, platform: platform, processManager: processManager, operatingSystemUtils: operatingSystemUtils, logger: logger, + browserFinder: findChromeExecutable, ); }); - tearDown(() { - resetChromeForTesting(); + testWithoutContext('can launch chrome and connect to the devtools', () async { + expect( + () async => await testLaunchChrome( + '/.tmp_rand0/flutter_tools_chrome_device.rand0', + processManager, + chromeLauncher, + ), + returnsNormally, + ); }); - test('can launch chrome and connect to the devtools', () async { - await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand0', processManager, chromeLauncher); - }); + testWithoutContext('cannot have two concurrent instances of chrome', () async { + await testLaunchChrome( + '/.tmp_rand0/flutter_tools_chrome_device.rand0', + processManager, + chromeLauncher, + ); - test('cannot have two concurrent instances of chrome', () async { - await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand0', processManager, chromeLauncher); - bool pass = false; - try { - await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand1', processManager, chromeLauncher); - } on ToolExit catch (_) { - pass = true; - } - expect(pass, isTrue); + expect( + () async => await testLaunchChrome( + '/.tmp_rand0/flutter_tools_chrome_device.rand1', + processManager, + chromeLauncher, + ), + throwsToolExit(), + ); }); - test('can launch new chrome after stopping a previous chrome', () async { - final Chrome chrome = await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand0', processManager, chromeLauncher); + testWithoutContext('can launch new chrome after stopping a previous chrome', () async { + final Chromium chrome = await testLaunchChrome( + '/.tmp_rand0/flutter_tools_chrome_device.rand0', + processManager, + chromeLauncher, + ); await chrome.close(); - await testLaunchChrome('/.tmp_rand0/flutter_tools_chrome_device.rand1', processManager, chromeLauncher); + + expect( + () async => await testLaunchChrome( + '/.tmp_rand0/flutter_tools_chrome_device.rand1', + processManager, + chromeLauncher, + ), + returnsNormally, + ); }); - test('can launch chrome with a custom debug port', () async { + testWithoutContext('can launch chrome with a custom debug port', () async { processManager.addCommand(const FakeCommand( command: [ 'example_chrome', '--user-data-dir=/.tmp_rand1/flutter_tools_chrome_device.rand1', '--remote-debugging-port=10000', - ..._kChromeArgs, + ...kChromeArgs, 'example_url', ], stderr: kDevtoolsStderr, )); - await chromeLauncher.launch( - 'example_url', - skipCheck: true, - debugPort: 10000, + expect( + () async => await chromeLauncher.launch( + 'example_url', + skipCheck: true, + debugPort: 10000, + ), + returnsNormally, ); }); - test('can launch chrome headless', () async { + testWithoutContext('can launch chrome headless', () async { processManager.addCommand(const FakeCommand( command: [ 'example_chrome', '--user-data-dir=/.tmp_rand1/flutter_tools_chrome_device.rand1', '--remote-debugging-port=1234', - ..._kChromeArgs, + ...kChromeArgs, '--headless', '--disable-gpu', '--no-sandbox', @@ -118,14 +143,17 @@ void main() { stderr: kDevtoolsStderr, )); - await chromeLauncher.launch( - 'example_url', - skipCheck: true, - headless: true, + expect( + () async => await chromeLauncher.launch( + 'example_url', + skipCheck: true, + headless: true, + ), + returnsNormally, ); }); - test('can seed chrome temp directory with existing session data', () async { + testWithoutContext('can seed chrome temp directory with existing session data', () async { final Completer exitCompleter = Completer.sync(); final Directory dataDir = fileSystem.directory('chrome-stuff'); @@ -148,7 +176,7 @@ void main() { 'example_chrome', '--user-data-dir=/.tmp_rand1/flutter_tools_chrome_device.rand1', '--remote-debugging-port=1234', - ..._kChromeArgs, + ...kChromeArgs, 'example_url', ], completer: exitCompleter)); @@ -183,23 +211,23 @@ void main() { expect(storageDir.existsSync(), true); - expect(storageDir.childFile('LOCK').existsSync(), true); + expect(storageDir.childFile('LOCK'), exists); expect(storageDir.childFile('LOCK').readAsBytesSync(), hasLength(0)); - expect(storageDir.childFile('LOG').existsSync(), true); + expect(storageDir.childFile('LOG'), exists); expect(storageDir.childFile('LOG').readAsStringSync(), 'contents'); }); } class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} -Future testLaunchChrome(String userDataDir, FakeProcessManager processManager, ChromeLauncher chromeLauncher) { +Future testLaunchChrome(String userDataDir, FakeProcessManager processManager, ChromiumLauncher chromeLauncher) { processManager.addCommand(FakeCommand( command: [ 'example_chrome', '--user-data-dir=$userDataDir', '--remote-debugging-port=1234', - ..._kChromeArgs, + ...kChromeArgs, 'example_url', ], stderr: kDevtoolsStderr, diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index db565fe851c53..5b6ceb2beda82 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -376,6 +376,7 @@ void main() { entrypoint: Uri.base, testMode: true, expressionCompiler: null, + chromiumLauncher: null, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -469,6 +470,7 @@ void main() { entrypoint: Uri.base, testMode: true, expressionCompiler: null, + chromiumLauncher: null, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -482,6 +484,7 @@ void main() { test('Launches DWDS with the correct arguments', () => testbed.run(() async { globals.fs.file('.packages').writeAsStringSync('\n'); final WebAssetServer server = await WebAssetServer.start( + null, 'any', 8123, (String url) => null, diff --git a/packages/flutter_tools/test/general.shard/web/devices_test.dart b/packages/flutter_tools/test/general.shard/web/devices_test.dart index e29c62dce97ed..7422d62dfc8df 100644 --- a/packages/flutter_tools/test/general.shard/web/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devices_test.dart @@ -2,36 +2,42 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_tools/src/base/io.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/web/chrome.dart'; import 'package:flutter_tools/src/web/web_device.dart'; import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; import 'package:platform/platform.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/testbed.dart'; void main() { - MockChromeLauncher mockChromeLauncher; - MockPlatform mockPlatform; - MockProcessManager mockProcessManager; - MockWebApplicationPackage mockWebApplicationPackage; - - setUp(() async { - mockWebApplicationPackage = MockWebApplicationPackage(); - mockProcessManager = MockProcessManager(); - mockChromeLauncher = MockChromeLauncher(); - mockPlatform = MockPlatform(); - when(mockChromeLauncher.launch(any)).thenAnswer((Invocation invocation) async { - return null; - }); - when(mockWebApplicationPackage.name).thenReturn('test'); + testWithoutContext('No web devices listed if feature is disabled', () async { + final WebDevices webDevices = WebDevices( + featureFlags: TestFeatureFlags(isWebEnabled: false), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform( + operatingSystem: 'linux', + environment: {} + ), + processManager: FakeProcessManager.any(), + ); + + expect(await webDevices.pollingGetDevices(), isEmpty); }); - test('Chrome defaults', () async { - final ChromeDevice chromeDevice = ChromeDevice(); + testWithoutContext('GoogleChromeDevice defaults', () async { + final GoogleChromeDevice chromeDevice = GoogleChromeDevice( + chromiumLauncher: null, + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform(operatingSystem: 'linux'), + processManager: FakeProcessManager.any(), + ); expect(chromeDevice.name, 'Chrome'); expect(chromeDevice.id, 'chrome'); @@ -41,13 +47,35 @@ void main() { expect(chromeDevice.supportsFlutterExit, true); expect(chromeDevice.supportsScreenshot, false); expect(await chromeDevice.isLocalEmulator, false); - expect(chromeDevice.getLogReader(app: mockWebApplicationPackage), isA()); + expect(chromeDevice.getLogReader(), isA()); + expect(chromeDevice.getLogReader(), isA()); + expect(await chromeDevice.portForwarder.forward(1), 1); + }); + + testWithoutContext('MicrosoftEdge defaults', () async { + final MicrosoftEdgeDevice chromeDevice = MicrosoftEdgeDevice( + chromiumLauncher: null, + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + ); + + expect(chromeDevice.name, 'Edge'); + expect(chromeDevice.id, 'edge'); + expect(chromeDevice.supportsHotReload, true); + expect(chromeDevice.supportsHotRestart, true); + expect(chromeDevice.supportsStartPaused, true); + expect(chromeDevice.supportsFlutterExit, true); + expect(chromeDevice.supportsScreenshot, false); + expect(await chromeDevice.isLocalEmulator, false); + expect(chromeDevice.getLogReader(), isA()); expect(chromeDevice.getLogReader(), isA()); expect(await chromeDevice.portForwarder.forward(1), 1); }); - test('Server defaults', () async { - final WebServerDevice device = WebServerDevice(); + testWithoutContext('Server defaults', () async { + final WebServerDevice device = WebServerDevice( + logger: BufferLogger.test(), + ); expect(device.name, 'Web Server'); expect(device.id, 'web-server'); @@ -57,95 +85,130 @@ void main() { expect(device.supportsFlutterExit, true); expect(device.supportsScreenshot, false); expect(await device.isLocalEmulator, false); - expect(device.getLogReader(app: mockWebApplicationPackage), isA()); + expect(device.getLogReader(), isA()); expect(device.getLogReader(), isA()); expect(await device.portForwarder.forward(1), 1); }); - testUsingContext('Chrome device is listed when Chrome is available', () async { - when(mockChromeLauncher.canFindChrome()).thenReturn(true); - - final WebDevices deviceDiscoverer = WebDevices(); - final List devices = await deviceDiscoverer.pollingGetDevices(); - expect(devices, contains(isA())); - }, overrides: { - ChromeLauncher: () => mockChromeLauncher, + testWithoutContext('Chrome device is listed when Chrome can be run', () async { + final WebDevices webDevices = WebDevices( + featureFlags: TestFeatureFlags(isWebEnabled: true), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform( + operatingSystem: 'linux', + environment: {} + ), + processManager: FakeProcessManager.any(), + ); + + expect(await webDevices.pollingGetDevices(), + contains(isA())); }); - testUsingContext('Chrome device is not listed when Chrome is not available', () async { - when(mockChromeLauncher.canFindChrome()).thenReturn(false); - - final WebDevices deviceDiscoverer = WebDevices(); - final List devices = await deviceDiscoverer.pollingGetDevices(); - expect(devices, isNot(contains(isA()))); - }, overrides: { - ChromeLauncher: () => mockChromeLauncher, + testWithoutContext('Chrome device is not listed when Chrome cannot be run', () async { + final MockProcessManager processManager = MockProcessManager(); + when(processManager.canRun(any)).thenReturn(false); + final WebDevices webDevices = WebDevices( + featureFlags: TestFeatureFlags(isWebEnabled: true), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform( + operatingSystem: 'linux', + environment: {} + ), + processManager: processManager, + ); + + expect(await webDevices.pollingGetDevices(), + isNot(contains(isA()))); }); - testUsingContext('Web Server device is listed even when Chrome is not available', () async { - when(mockChromeLauncher.canFindChrome()).thenReturn(false); - - final WebDevices deviceDiscoverer = WebDevices(); - final List devices = await deviceDiscoverer.pollingGetDevices(); - expect(devices, contains(isA())); - }, overrides: { - ChromeLauncher: () => mockChromeLauncher, + testWithoutContext('Web Server device is listed by default', () async { + final WebDevices webDevices = WebDevices( + featureFlags: TestFeatureFlags(isWebEnabled: true), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform( + operatingSystem: 'linux', + environment: {} + ), + processManager: FakeProcessManager.any(), + ); + + expect(await webDevices.pollingGetDevices(), + contains(isA())); }); - testUsingContext('Chrome invokes version command on non-Windows platforms', () async{ - when(mockPlatform.isWindows).thenReturn(false); - when(mockProcessManager.canRun('chrome.foo')).thenReturn(true); - when(mockProcessManager.run(['chrome.foo', '--version'])).thenAnswer((Invocation invocation) async { - return MockProcessResult(0, 'ABC'); - }); - final ChromeDevice chromeDevice = ChromeDevice(); + testWithoutContext('Chrome invokes version command on non-Windows platforms', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: [ + kLinuxExecutable, + '--version', + ], + stdout: 'ABC' + ) + ]); + final WebDevices webDevices = WebDevices( + featureFlags: TestFeatureFlags(isWebEnabled: true), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform( + operatingSystem: 'linux', + environment: {} + ), + processManager: processManager, + ); + + + final GoogleChromeDevice chromeDevice = (await webDevices.pollingGetDevices()) + .whereType().first; expect(chromeDevice.isSupported(), true); expect(await chromeDevice.sdkNameAndVersion, 'ABC'); // Verify caching works correctly. expect(await chromeDevice.sdkNameAndVersion, 'ABC'); - verify(mockProcessManager.run(['chrome.foo', '--version'])).called(1); - }, overrides: { - Platform: () => mockPlatform, - ProcessManager: () => mockProcessManager, + expect(processManager.hasRemainingExpectations, false); }); - testUsingContext('Chrome invokes different version command on windows.', () async { - when(mockPlatform.isWindows).thenReturn(true); - when(mockProcessManager.canRun('chrome.foo')).thenReturn(true); - when(mockProcessManager.run([ - 'reg', - 'query', - r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', - '/v', - 'version', - ])).thenAnswer((Invocation invocation) async { - return MockProcessResult(0, r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A'); - }); - final ChromeDevice chromeDevice = ChromeDevice(); + testWithoutContext('Chrome version check invokes registry query on windows.', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: [ + 'reg', + 'query', + r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon', + '/v', + 'version', + ], + stdout: r'HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon\ version REG_SZ 74.0.0 A', + ) + ]); + final WebDevices webDevices = WebDevices( + featureFlags: TestFeatureFlags(isWebEnabled: true), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + platform: FakePlatform( + operatingSystem: 'windows', + environment: {} + ), + processManager: processManager, + ); + + + final GoogleChromeDevice chromeDevice = (await webDevices.pollingGetDevices()) + .whereType().first; expect(chromeDevice.isSupported(), true); expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0'); - }, overrides: { - Platform: () => mockPlatform, - ProcessManager: () => mockProcessManager, + + // Verify caching works correctly. + expect(await chromeDevice.sdkNameAndVersion, 'Google Chrome 74.0.0'); + expect(processManager.hasRemainingExpectations, false); }); } -class MockChromeLauncher extends Mock implements ChromeLauncher {} -class MockPlatform extends Mock implements Platform { - @override - Map environment = {'FLUTTER_WEB': 'true', kChromeEnvironment: 'chrome.foo'}; -} +// This is used to set `canRun` to false in a test. class MockProcessManager extends Mock implements ProcessManager {} -class MockProcessResult extends Mock implements ProcessResult { - MockProcessResult(this.exitCode, this.stdout); - - @override - final int exitCode; - - @override - final String stdout; -} -class MockWebApplicationPackage extends Mock implements WebApplicationPackage {} diff --git a/packages/flutter_tools/test/general.shard/web/web_validator_test.dart b/packages/flutter_tools/test/general.shard/web/web_validator_test.dart index 2adcbd8e912ed..a6a6ca8ba6767 100644 --- a/packages/flutter_tools/test/general.shard/web/web_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/web/web_validator_test.dart @@ -4,6 +4,7 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/web/chrome.dart'; import 'package:flutter_tools/src/web/web_validator.dart'; @@ -17,9 +18,9 @@ import '../../src/fake_process_manager.dart'; void main() { Platform platform; ProcessManager processManager; - ChromeLauncher chromeLauncher; + ChromiumLauncher chromeLauncher; FileSystem fileSystem; - WebValidator webValidator; + ChromiumValidator webValidator; setUp(() { fileSystem = MemoryFileSystem.test(); @@ -28,17 +29,17 @@ void main() { operatingSystem: 'macos', environment: {}, ); - chromeLauncher = ChromeLauncher( + chromeLauncher = ChromiumLauncher( fileSystem: fileSystem, platform: platform, processManager: processManager, operatingSystemUtils: null, - logger: null, + logger: BufferLogger.test(), + browserFinder: findChromeExecutable, ); - webValidator = webValidator = WebValidator( + webValidator = webValidator = ChromeValidator( platform: platform, - chromeLauncher: chromeLauncher, - fileSystem: fileSystem, + chromiumLauncher: chromeLauncher, ); }); diff --git a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart index 2d7edc7b1068a..1edb574e6b654 100644 --- a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart @@ -387,7 +387,7 @@ void main() { final VisualStudioFixture fixture = setUpVisualStudio(); final VisualStudio visualStudio = fixture.visualStudio; - final Map response = Map.from(_defaultResponse) + final Map response = Map.of(_defaultResponse) ..['isPrerelease'] = true; setMockCompatibleVisualStudioInstallation( null, @@ -448,7 +448,7 @@ void main() { fixture.processManager, ); - final Map response = Map.from(_defaultResponse) + final Map response = Map.of(_defaultResponse) ..['isComplete'] = false; setMockAnyVisualStudioInstallation( response, @@ -476,7 +476,7 @@ void main() { fixture.processManager, ); - final Map response = Map.from(_defaultResponse) + final Map response = Map.of(_defaultResponse) ..['isLaunchable'] = false; setMockAnyVisualStudioInstallation( response, @@ -503,7 +503,7 @@ void main() { fixture.processManager, ); - final Map response = Map.from(_defaultResponse) + final Map response = Map.of(_defaultResponse) ..['isRebootRequired'] = true; setMockAnyVisualStudioInstallation( response, @@ -565,7 +565,7 @@ void main() { final VisualStudioFixture fixture = setUpVisualStudio(); final VisualStudio visualStudio = fixture.visualStudio; - final Map response = Map.from(_defaultResponse) + final Map response = Map.of(_defaultResponse) ..['isRebootRequired'] = true; setMockCompatibleVisualStudioInstallation( response, @@ -585,7 +585,7 @@ void main() { final VisualStudioFixture fixture = setUpVisualStudio(); final VisualStudio visualStudio = fixture.visualStudio; - final Map response = Map.from(_defaultResponse) + final Map response = Map.of(_defaultResponse) ..['isRebootRequired'] = true; setMockCompatibleVisualStudioInstallation( response, diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart index ecf413d07230d..298b5e7ce5134 100644 --- a/packages/flutter_tools/test/src/common.dart +++ b/packages/flutter_tools/test/src/common.dart @@ -5,10 +5,12 @@ import 'dart:async'; import 'package:args/command_runner.dart'; +import 'package:flutter_tools/src/convert.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/file_system.dart'; - import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; @@ -217,3 +219,109 @@ class NoContext implements AppContext { return body(); } } + +/// A fake implementation of a vm_service that mocks the JSON-RPC request +/// and response structure. +class FakeVmServiceHost { + FakeVmServiceHost({ + @required List requests, + }) : _requests = requests { + _vmService = vm_service.VmService( + _input.stream, + _output.add, + ); + _applyStreamListen(); + _output.stream.listen((String data) { + final Map request = json.decode(data) as Map; + if (_requests.isEmpty) { + throw Exception('Unexpected request: $request'); + } + final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest; + expect(request, isA>() + .having((Map request) => request['method'], 'method', fakeRequest.method) + .having((Map request) => request['id'], 'id', fakeRequest.id) + .having((Map request) => request['params'], 'args', fakeRequest.args) + ); + if (fakeRequest.errorCode == null) { + _input.add(json.encode({ + 'jsonrpc': '2.0', + 'id': fakeRequest.id, + 'result': fakeRequest.jsonResponse ?? {'type': 'Success'}, + })); + } else { + _input.add(json.encode({ + 'jsonrpc': '2.0', + 'id': fakeRequest.id, + 'error': { + 'code': fakeRequest.errorCode, + } + })); + } + _applyStreamListen(); + }); + } + + final List _requests; + final StreamController _input = StreamController(); + final StreamController _output = StreamController(); + + vm_service.VmService get vmService => _vmService; + vm_service.VmService _vmService; + + bool get hasRemainingExpectations => _requests.isNotEmpty; + + // remove FakeStreamResponse objects from _requests until it is empty + // or until we hit a FakeRequest + void _applyStreamListen() { + while (_requests.isNotEmpty && !_requests.first.isRequest) { + final FakeVmServiceStreamResponse response = _requests.removeAt(0) as FakeVmServiceStreamResponse; + _input.add(json.encode({ + 'jsonrpc': '2.0', + 'method': 'streamNotify', + 'params': { + 'streamId': response.streamId, + 'event': response.event.toJson(), + }, + })); + } + } +} + +abstract class VmServiceExpectation { + bool get isRequest; +} + +class FakeVmServiceRequest implements VmServiceExpectation { + const FakeVmServiceRequest({ + @required this.method, + @required this.id, + @required this.args, + this.jsonResponse, + this.errorCode, + }); + + final String method; + final String id; + + /// If non-null, the error code for a [vm_service.RPCError] in place of a + /// standard response. + final int errorCode; + final Map args; + final Map jsonResponse; + + @override + bool get isRequest => true; +} + +class FakeVmServiceStreamResponse implements VmServiceExpectation { + const FakeVmServiceStreamResponse({ + @required this.event, + @required this.streamId, + }); + + final vm_service.Event event; + final String streamId; + + @override + bool get isRequest => false; +} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index d5df280104512..4afbb3ee143f2 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -585,7 +585,7 @@ class BasicMock { final List messages = []; void expectMessages(List expectedMessages) { - final List actualMessages = List.from(messages); + final List actualMessages = List.of(messages); messages.clear(); expect(actualMessages, unorderedEquals(expectedMessages)); } diff --git a/packages/flutter_tools/test/src/throwing_pub.dart b/packages/flutter_tools/test/src/throwing_pub.dart index e2d4c61da582b..0e535cbacde2f 100644 --- a/packages/flutter_tools/test/src/throwing_pub.dart +++ b/packages/flutter_tools/test/src/throwing_pub.dart @@ -4,7 +4,9 @@ import 'dart:async'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/dart/pub.dart'; +import 'package:meta/meta.dart'; class ThrowingPub implements Pub { @override @@ -34,7 +36,7 @@ class ThrowingPub implements Pub { } @override - Future interactively(List arguments, {String directory}) { + Future interactively(List arguments, {String directory, @required Stdio stdio,}) { throw UnsupportedError('Attempted to invoke pub during test.'); } } diff --git a/packages/fuchsia_remote_debug_protocol/pubspec.yaml b/packages/fuchsia_remote_debug_protocol/pubspec.yaml index 655051a234178..1ad11fb31295c 100644 --- a/packages/fuchsia_remote_debug_protocol/pubspec.yaml +++ b/packages/fuchsia_remote_debug_protocol/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: ">=2.2.2 <3.0.0" dependencies: - json_rpc_2: 2.1.0 + json_rpc_2: 2.1.1 process: 3.0.12 web_socket_channel: 1.1.0 flutter_test: @@ -67,7 +67,7 @@ dev_dependencies: node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - package_config: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pedantic: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -78,8 +78,8 @@ dev_dependencies: source_maps: 0.10.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_core: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 0.5.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: f8cd +# PUBSPEC CHECKSUM: fdd1