• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:convert';
7import 'dart:io';
8import 'dart:typed_data';
9
10import 'package:file/memory.dart';
11import 'package:flutter_tools/src/base/context.dart';
12import 'package:flutter_tools/src/base/file_system.dart';
13import 'package:flutter_tools/src/base/io.dart';
14import 'package:flutter_tools/src/base/logger.dart';
15import 'package:flutter_tools/src/base/os.dart';
16import 'package:flutter_tools/src/base/platform.dart';
17import 'package:flutter_tools/src/base/terminal.dart';
18import 'package:flutter_tools/src/cache.dart';
19import 'package:flutter_tools/src/context_runner.dart';
20import 'package:flutter_tools/src/features.dart';
21import 'package:flutter_tools/src/reporting/reporting.dart';
22import 'package:flutter_tools/src/version.dart';
23
24import 'context.dart';
25
26export 'package:flutter_tools/src/base/context.dart' show Generator;
27
28// A default value should be provided if the vast majority of tests should use
29// this provider. For example, [BufferLogger], [MemoryFileSystem].
30final Map<Type, Generator> _testbedDefaults = <Type, Generator>{
31  // Keeps tests fast by avoiding the actual file system.
32  FileSystem: () => MemoryFileSystem(style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix),
33  Logger: () => BufferLogger(), // Allows reading logs and prevents stdout.
34  OperatingSystemUtils: () => FakeOperatingSystemUtils(),
35  OutputPreferences: () => OutputPreferences(showColor: false), // configures BufferLogger to avoid color codes.
36  Usage: () => NoOpUsage(), // prevent addition of analytics from burdening test mocks
37  FlutterVersion: () => FakeFlutterVersion() // prevent requirement to mock git for test runner.
38};
39
40/// Manages interaction with the tool injection and runner system.
41///
42/// The Testbed automatically injects reasonable defaults through the context
43/// DI system such as a [BufferLogger] and a [MemoryFileSytem].
44///
45/// Example:
46///
47/// Testing that a filesystem operation works as expected
48///
49///     void main() {
50///       group('Example', () {
51///         Testbed testbed;
52///
53///         setUp(() {
54///           testbed = Testbed(setUp: () {
55///             fs.file('foo').createSync()
56///           });
57///         })
58///
59///         test('Can delete a file', () => testBed.run(() {
60///           expect(fs.file('foo').existsSync(), true);
61///           fs.file('foo').deleteSync();
62///           expect(fs.file('foo').existsSync(), false);
63///         }));
64///       });
65///     }
66///
67/// For a more detailed example, see the code in test_compiler_test.dart.
68class Testbed {
69  /// Creates a new [TestBed]
70  ///
71  /// `overrides` provides more overrides in addition to the test defaults.
72  /// `setup` may be provided to apply mocks within the tool managed zone,
73  /// including any specified overrides.
74  Testbed({FutureOr<void> Function() setup, Map<Type, Generator> overrides})
75      : _setup = setup,
76        _overrides = overrides;
77
78  final FutureOr<void> Function() _setup;
79  final Map<Type, Generator> _overrides;
80
81  /// Runs `test` within a tool zone.
82  ///
83  /// `overrides` may be used to provide new context values for the single test
84  /// case or override any context values from the setup.
85  FutureOr<T> run<T>(FutureOr<T> Function() test, {Map<Type, Generator> overrides}) {
86    final Map<Type, Generator> testOverrides = <Type, Generator>{
87      ..._testbedDefaults,
88      // Add the initial setUp overrides
89      ...?_overrides,
90      // Add the test-specific overrides
91      ...?overrides,
92    };
93    // Cache the original flutter root to restore after the test case.
94    final String originalFlutterRoot = Cache.flutterRoot;
95    // Track pending timers to verify that they were correctly cleaned up.
96    final Map<Timer, StackTrace> timers = <Timer, StackTrace>{};
97
98    return HttpOverrides.runZoned(() {
99      return runInContext<T>(() {
100        return context.run<T>(
101          name: 'testbed',
102          overrides: testOverrides,
103          zoneSpecification: ZoneSpecification(
104            createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() timer) {
105              final Timer result = parent.createTimer(zone, duration, timer);
106              timers[result] = StackTrace.current;
107              return result;
108            },
109            createPeriodicTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration period, void Function(Timer) timer) {
110              final Timer result = parent.createPeriodicTimer(zone, period, timer);
111              timers[result] = StackTrace.current;
112              return result;
113            }
114          ),
115          body: () async {
116            Cache.flutterRoot = '';
117            if (_setup != null) {
118              await _setup();
119            }
120            await test();
121            Cache.flutterRoot = originalFlutterRoot;
122            for (MapEntry<Timer, StackTrace> entry in timers.entries) {
123              if (entry.key.isActive) {
124                throw StateError('A Timer was active at the end of a test: ${entry.value}');
125              }
126            }
127            return null;
128          });
129      });
130    }, createHttpClient: (SecurityContext c) => FakeHttpClient());
131  }
132}
133
134/// A no-op implementation of [Usage] for testing.
135class NoOpUsage implements Usage {
136  @override
137  bool enabled = false;
138
139  @override
140  bool suppressAnalytics = true;
141
142  @override
143  String get clientId => 'test';
144
145  @override
146  Future<void> ensureAnalyticsSent() {
147    return null;
148  }
149
150  @override
151  bool get isFirstRun => false;
152
153  @override
154  Stream<Map<String, Object>> get onSend => const Stream<Object>.empty();
155
156  @override
157  void printWelcome() {}
158
159  @override
160  void sendCommand(String command, {Map<String, String> parameters}) {}
161
162  @override
163  void sendEvent(String category, String parameter,{ Map<String, String> parameters }) {}
164
165  @override
166  void sendException(dynamic exception) {}
167
168  @override
169  void sendTiming(String category, String variableName, Duration duration, { String label }) {}
170}
171
172class FakeHttpClient implements HttpClient {
173  @override
174  bool autoUncompress;
175
176  @override
177  Duration connectionTimeout;
178
179  @override
180  Duration idleTimeout;
181
182  @override
183  int maxConnectionsPerHost;
184
185  @override
186  String userAgent;
187
188  @override
189  void addCredentials(
190      Uri url, String realm, HttpClientCredentials credentials) {}
191
192  @override
193  void addProxyCredentials(
194      String host, int port, String realm, HttpClientCredentials credentials) {}
195
196  @override
197  set authenticate(
198      Future<bool> Function(Uri url, String scheme, String realm) f) {}
199
200  @override
201  set authenticateProxy(
202      Future<bool> Function(String host, int port, String scheme, String realm)
203          f) {}
204
205  @override
206  set badCertificateCallback(
207      bool Function(X509Certificate cert, String host, int port) callback) {}
208
209  @override
210  void close({bool force = false}) {}
211
212  @override
213  Future<HttpClientRequest> delete(String host, int port, String path) async {
214    return FakeHttpClientRequest();
215  }
216
217  @override
218  Future<HttpClientRequest> deleteUrl(Uri url) async {
219    return FakeHttpClientRequest();
220  }
221
222  @override
223  set findProxy(String Function(Uri url) f) {}
224
225  @override
226  Future<HttpClientRequest> get(String host, int port, String path) async {
227    return FakeHttpClientRequest();
228  }
229
230  @override
231  Future<HttpClientRequest> getUrl(Uri url) async {
232    return FakeHttpClientRequest();
233  }
234
235  @override
236  Future<HttpClientRequest> head(String host, int port, String path) async {
237    return FakeHttpClientRequest();
238  }
239
240  @override
241  Future<HttpClientRequest> headUrl(Uri url) async {
242    return FakeHttpClientRequest();
243  }
244
245  @override
246  Future<HttpClientRequest> open(String method, String host, int port, String path) async {
247    return FakeHttpClientRequest();
248  }
249
250  @override
251  Future<HttpClientRequest> openUrl(String method, Uri url) async {
252    return FakeHttpClientRequest();
253  }
254
255  @override
256  Future<HttpClientRequest> patch(String host, int port, String path) async {
257    return FakeHttpClientRequest();
258  }
259
260  @override
261  Future<HttpClientRequest> patchUrl(Uri url) async {
262    return FakeHttpClientRequest();
263  }
264
265  @override
266  Future<HttpClientRequest> post(String host, int port, String path) async {
267    return FakeHttpClientRequest();
268  }
269
270  @override
271  Future<HttpClientRequest> postUrl(Uri url) async {
272    return FakeHttpClientRequest();
273  }
274
275  @override
276  Future<HttpClientRequest> put(String host, int port, String path) async {
277    return FakeHttpClientRequest();
278  }
279
280  @override
281  Future<HttpClientRequest> putUrl(Uri url) async {
282    return FakeHttpClientRequest();
283  }
284}
285
286class FakeHttpClientRequest implements HttpClientRequest {
287  FakeHttpClientRequest();
288
289  @override
290  bool bufferOutput;
291
292  @override
293  int contentLength;
294
295  @override
296  Encoding encoding;
297
298  @override
299  bool followRedirects;
300
301  @override
302  int maxRedirects;
303
304  @override
305  bool persistentConnection;
306
307  @override
308  void add(List<int> data) {}
309
310  @override
311  void addError(Object error, [StackTrace stackTrace]) {}
312
313  @override
314  Future<void> addStream(Stream<List<int>> stream) async {}
315
316  @override
317  Future<HttpClientResponse> close() async {
318    return FakeHttpClientResponse();
319  }
320
321  @override
322  HttpConnectionInfo get connectionInfo => null;
323
324  @override
325  List<Cookie> get cookies => <Cookie>[];
326
327  @override
328  Future<HttpClientResponse> get done => null;
329
330  @override
331  Future<void> flush() {
332    return Future<void>.value();
333  }
334
335  @override
336  HttpHeaders get headers => null;
337
338  @override
339  String get method => null;
340
341  @override
342  Uri get uri => null;
343
344  @override
345  void write(Object obj) {}
346
347  @override
348  void writeAll(Iterable<Object> objects, [String separator = '']) {}
349
350  @override
351  void writeCharCode(int charCode) {}
352
353  @override
354  void writeln([Object obj = '']) {}
355}
356
357class FakeHttpClientResponse implements HttpClientResponse {
358  final Stream<Uint8List> _delegate = Stream<Uint8List>.fromIterable(const Iterable<Uint8List>.empty());
359
360  @override
361  final HttpHeaders headers = FakeHttpHeaders();
362
363  @override
364  X509Certificate get certificate => null;
365
366  @override
367  HttpConnectionInfo get connectionInfo => null;
368
369  @override
370  int get contentLength => 0;
371
372  @override
373  HttpClientResponseCompressionState get compressionState {
374    return HttpClientResponseCompressionState.decompressed;
375  }
376
377  @override
378  List<Cookie> get cookies => null;
379
380  @override
381  Future<Socket> detachSocket() {
382    return Future<Socket>.error(UnsupportedError('Mocked response'));
383  }
384
385  @override
386  bool get isRedirect => false;
387
388  @override
389  StreamSubscription<Uint8List> listen(void Function(Uint8List event) onData, { Function onError, void Function() onDone, bool cancelOnError }) {
390    return const Stream<Uint8List>.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
391  }
392
393  @override
394  bool get persistentConnection => null;
395
396  @override
397  String get reasonPhrase => null;
398
399  @override
400  Future<HttpClientResponse> redirect([ String method, Uri url, bool followLoops ]) {
401    return Future<HttpClientResponse>.error(UnsupportedError('Mocked response'));
402  }
403
404  @override
405  List<RedirectInfo> get redirects => <RedirectInfo>[];
406
407  @override
408  int get statusCode => 400;
409
410  @override
411  Future<bool> any(bool Function(Uint8List element) test) {
412    return _delegate.any(test);
413  }
414
415  @override
416  Stream<Uint8List> asBroadcastStream({
417    void Function(StreamSubscription<Uint8List> subscription) onListen,
418    void Function(StreamSubscription<Uint8List> subscription) onCancel,
419  }) {
420    return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel);
421  }
422
423  @override
424  Stream<E> asyncExpand<E>(Stream<E> Function(Uint8List event) convert) {
425    return _delegate.asyncExpand<E>(convert);
426  }
427
428  @override
429  Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) {
430    return _delegate.asyncMap<E>(convert);
431  }
432
433  @override
434  Stream<R> cast<R>() {
435    return _delegate.cast<R>();
436  }
437
438  @override
439  Future<bool> contains(Object needle) {
440    return _delegate.contains(needle);
441  }
442
443  @override
444  Stream<Uint8List> distinct([bool Function(Uint8List previous, Uint8List next) equals]) {
445    return _delegate.distinct(equals);
446  }
447
448  @override
449  Future<E> drain<E>([E futureValue]) {
450    return _delegate.drain<E>(futureValue);
451  }
452
453  @override
454  Future<Uint8List> elementAt(int index) {
455    return _delegate.elementAt(index);
456  }
457
458  @override
459  Future<bool> every(bool Function(Uint8List element) test) {
460    return _delegate.every(test);
461  }
462
463  @override
464  Stream<S> expand<S>(Iterable<S> Function(Uint8List element) convert) {
465    return _delegate.expand(convert);
466  }
467
468  @override
469  Future<Uint8List> get first => _delegate.first;
470
471  @override
472  Future<Uint8List> firstWhere(
473    bool Function(Uint8List element) test, {
474    List<int> Function() orElse,
475  }) {
476    return _delegate.firstWhere(test, orElse: orElse);
477  }
478
479  @override
480  Future<S> fold<S>(S initialValue, S Function(S previous, Uint8List element) combine) {
481    return _delegate.fold<S>(initialValue, combine);
482  }
483
484  @override
485  Future<dynamic> forEach(void Function(Uint8List element) action) {
486    return _delegate.forEach(action);
487  }
488
489  @override
490  Stream<Uint8List> handleError(
491    Function onError, {
492    bool Function(dynamic error) test,
493  }) {
494    return _delegate.handleError(onError, test: test);
495  }
496
497  @override
498  bool get isBroadcast => _delegate.isBroadcast;
499
500  @override
501  Future<bool> get isEmpty => _delegate.isEmpty;
502
503  @override
504  Future<String> join([String separator = '']) {
505    return _delegate.join(separator);
506  }
507
508  @override
509  Future<Uint8List> get last => _delegate.last;
510
511  @override
512  Future<Uint8List> lastWhere(
513    bool Function(Uint8List element) test, {
514    List<int> Function() orElse,
515  }) {
516    return _delegate.lastWhere(test, orElse: orElse);
517  }
518
519  @override
520  Future<int> get length => _delegate.length;
521
522  @override
523  Stream<S> map<S>(S Function(Uint8List event) convert) {
524    return _delegate.map<S>(convert);
525  }
526
527  @override
528  Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) {
529    return _delegate.pipe(streamConsumer);
530  }
531
532  @override
533  Future<Uint8List> reduce(List<int> Function(Uint8List previous, Uint8List element) combine) {
534    return _delegate.reduce(combine);
535  }
536
537  @override
538  Future<Uint8List> get single => _delegate.single;
539
540  @override
541  Future<Uint8List> singleWhere(bool Function(Uint8List element) test, {List<int> Function() orElse}) {
542    return _delegate.singleWhere(test, orElse: orElse);
543  }
544
545  @override
546  Stream<Uint8List> skip(int count) {
547    return _delegate.skip(count);
548  }
549
550  @override
551  Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) {
552    return _delegate.skipWhile(test);
553  }
554
555  @override
556  Stream<Uint8List> take(int count) {
557    return _delegate.take(count);
558  }
559
560  @override
561  Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) {
562    return _delegate.takeWhile(test);
563  }
564
565  @override
566  Stream<Uint8List> timeout(
567    Duration timeLimit, {
568    void Function(EventSink<Uint8List> sink) onTimeout,
569  }) {
570    return _delegate.timeout(timeLimit, onTimeout: onTimeout);
571  }
572
573  @override
574  Future<List<Uint8List>> toList() {
575    return _delegate.toList();
576  }
577
578  @override
579  Future<Set<Uint8List>> toSet() {
580    return _delegate.toSet();
581  }
582
583  @override
584  Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {
585    return _delegate.transform<S>(streamTransformer);
586  }
587
588  @override
589  Stream<Uint8List> where(bool Function(Uint8List event) test) {
590    return _delegate.where(test);
591  }
592}
593
594/// A fake [HttpHeaders] that ignores all writes.
595class FakeHttpHeaders extends HttpHeaders {
596  @override
597  List<String> operator [](String name) => <String>[];
598
599  @override
600  void add(String name, Object value) { }
601
602  @override
603  void clear() { }
604
605  @override
606  void forEach(void Function(String name, List<String> values) f) { }
607
608  @override
609  void noFolding(String name) { }
610
611  @override
612  void remove(String name, Object value) { }
613
614  @override
615  void removeAll(String name) { }
616
617  @override
618  void set(String name, Object value) { }
619
620  @override
621  String value(String name) => null;
622}
623
624class FakeFlutterVersion implements FlutterVersion {
625  @override
626  String get channel => 'master';
627
628  @override
629  Future<void> checkFlutterVersionFreshness() async { }
630
631  @override
632  bool checkRevisionAncestry({String tentativeDescendantRevision, String tentativeAncestorRevision}) {
633    throw UnimplementedError();
634  }
635
636  @override
637  String get dartSdkVersion => '12';
638
639  @override
640  String get engineRevision => '42.2';
641
642  @override
643  String get engineRevisionShort => '42';
644
645  @override
646  Future<void> ensureVersionFile() async { }
647
648  @override
649  String get frameworkAge => null;
650
651  @override
652  String get frameworkCommitDate => null;
653
654  @override
655  String get frameworkDate => null;
656
657  @override
658  String get frameworkRevision => null;
659
660  @override
661  String get frameworkRevisionShort => null;
662
663  @override
664  String get frameworkVersion => null;
665
666  @override
667  String getBranchName({bool redactUnknownBranches = false}) {
668    return 'master';
669  }
670
671  @override
672  String getVersionString({bool redactUnknownBranches = false}) {
673    return 'v0.0.0';
674  }
675
676  @override
677  bool get isMaster => true;
678
679  @override
680  String get repositoryUrl => null;
681
682  @override
683  Map<String, Object> toJson() {
684    return null;
685  }
686}
687
688// A test implementation of [FeatureFlags] that allows enabling without reading
689// config. If not otherwise specified, all values default to false.
690class TestFeatureFlags implements FeatureFlags {
691  TestFeatureFlags({
692    this.isLinuxEnabled = false,
693    this.isMacOSEnabled = false,
694    this.isWebEnabled = false,
695    this.isWindowsEnabled = false,
696    this.isPluginAsAarEnabled = false,
697});
698
699  @override
700  final bool isLinuxEnabled;
701
702  @override
703  final bool isMacOSEnabled;
704
705  @override
706  final bool isWebEnabled;
707
708  @override
709  final bool isWindowsEnabled;
710
711  @override
712  final bool isPluginAsAarEnabled;
713}
714