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