fakes.dart 16.4 KB
Newer Older
1 2 3 4 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 'dart:async';
6 7
import 'dart:io' as io show IOSink, ProcessSignal, Stdout, StdoutException;

8
import 'package:flutter_tools/src/android/android_sdk.dart';
9
import 'package:flutter_tools/src/android/android_studio.dart';
10
import 'package:flutter_tools/src/android/java.dart';
11
import 'package:flutter_tools/src/base/bot_detector.dart';
12
import 'package:flutter_tools/src/base/file_system.dart';
13
import 'package:flutter_tools/src/base/io.dart';
14 15
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
16
import 'package:flutter_tools/src/base/time.dart';
17
import 'package:flutter_tools/src/base/version.dart';
18
import 'package:flutter_tools/src/cache.dart';
19
import 'package:flutter_tools/src/convert.dart';
20
import 'package:flutter_tools/src/features.dart';
21
import 'package:flutter_tools/src/ios/plist_parser.dart';
22
import 'package:flutter_tools/src/project.dart';
23
import 'package:flutter_tools/src/version.dart';
24
import 'package:test/fake.dart';
25 26 27 28 29 30

/// Environment with DYLD_LIBRARY_PATH=/path/to/libraries
class FakeDyldEnvironmentArtifact extends ArtifactSet {
  FakeDyldEnvironmentArtifact() : super(DevelopmentArtifact.iOS);
  @override
  Map<String, String> get environment => <String, String>{
31
    'DYLD_LIBRARY_PATH': '/path/to/libraries',
32 33 34
  };

  @override
35
  Future<bool> isUpToDate(FileSystem fileSystem) => Future<bool>.value(true);
36 37 38 39 40

  @override
  String get name => 'fake';

  @override
41
  Future<void> update(ArtifactUpdater artifactUpdater, Logger logger, FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils, {bool offline = false}) async {
42 43
  }
}
44 45 46 47 48

/// A fake process implementation which can be provided all necessary values.
class FakeProcess implements Process {
  FakeProcess({
    this.pid = 1,
49 50
    Future<int>? exitCode,
    IOSink? stdin,
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
    this.stdout = const Stream<List<int>>.empty(),
    this.stderr = const Stream<List<int>>.empty(),
  }) : exitCode = exitCode ?? Future<int>.value(0),
       stdin = stdin ?? MemoryIOSink();

  @override
  final int pid;

  @override
  final Future<int> exitCode;

  @override
  final io.IOSink stdin;

  @override
  final Stream<List<int>> stdout;

  @override
  final Stream<List<int>> stderr;

  @override
  bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
    return true;
  }
}

/// An IOSink that completes a future with the first line written to it.
class CompleterIOSink extends MemoryIOSink {
  CompleterIOSink({
    this.throwOnAdd = false,
  });

  final bool throwOnAdd;

  final Completer<List<int>> _completer = Completer<List<int>>();

  Future<List<int>> get future => _completer.future;

  @override
  void add(List<int> data) {
    if (!_completer.isCompleted) {
      // When throwOnAdd is true, complete with empty so any expected output
      // doesn't appear.
      _completer.complete(throwOnAdd ? <int>[] : data);
    }
    if (throwOnAdd) {
      throw Exception('CompleterIOSink Error');
    }
    super.add(data);
  }
}

/// An IOSink that collects whatever is written to it.
class MemoryIOSink implements IOSink {
  @override
  Encoding encoding = utf8;

  final List<List<int>> writes = <List<int>>[];

  @override
  void add(List<int> data) {
    writes.add(data);
  }

  @override
  Future<void> addStream(Stream<List<int>> stream) {
    final Completer<void> completer = Completer<void>();
118
    late StreamSubscription<List<int>> sub;
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    sub = stream.listen(
      (List<int> data) {
        try {
          add(data);
        // Catches all exceptions to propagate them to the completer.
        } catch (err, stack) { // ignore: avoid_catches_without_on_clauses
          sub.cancel();
          completer.completeError(err, stack);
        }
      },
      onError: completer.completeError,
      onDone: completer.complete,
      cancelOnError: true,
    );
    return completer.future;
  }

  @override
  void writeCharCode(int charCode) {
    add(<int>[charCode]);
  }

  @override
142
  void write(Object? obj) {
143 144 145 146
    add(encoding.encode('$obj'));
  }

  @override
147
  void writeln([ Object? obj = '' ]) {
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    add(encoding.encode('$obj\n'));
  }

  @override
  void writeAll(Iterable<dynamic> objects, [ String separator = '' ]) {
    bool addSeparator = false;
    for (final dynamic object in objects) {
      if (addSeparator) {
        write(separator);
      }
      write(object);
      addSeparator = true;
    }
  }

  @override
164
  void addError(dynamic error, [ StackTrace? stackTrace ]) {
165 166 167 168 169 170 171 172 173 174 175
    throw UnimplementedError();
  }

  @override
  Future<void> get done => close();

  @override
  Future<void> close() async { }

  @override
  Future<void> flush() async { }
176 177 178 179 180 181 182 183 184 185

  void clear() {
    writes.clear();
  }

  String getAndClear() {
    final String result = utf8.decode(writes.expand((List<int> l) => l).toList());
    clear();
    return result;
  }
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
}

class MemoryStdout extends MemoryIOSink implements io.Stdout {
  @override
  bool get hasTerminal => _hasTerminal;
  set hasTerminal(bool value) {
    _hasTerminal = value;
  }
  bool _hasTerminal = true;

  @override
  io.IOSink get nonBlocking => this;

  @override
  bool get supportsAnsiEscapes => _supportsAnsiEscapes;
  set supportsAnsiEscapes(bool value) {
    _supportsAnsiEscapes = value;
  }
  bool _supportsAnsiEscapes = true;

  @override
  int get terminalColumns {
    if (_terminalColumns != null) {
209
      return _terminalColumns!;
210 211 212 213
    }
    throw const io.StdoutException('unspecified mock value');
  }
  set terminalColumns(int value) => _terminalColumns = value;
214
  int? _terminalColumns;
215 216 217 218

  @override
  int get terminalLines {
    if (_terminalLines != null) {
219
      return _terminalLines!;
220 221 222 223
    }
    throw const io.StdoutException('unspecified mock value');
  }
  set terminalLines(int value) => _terminalLines = value;
224
  int? _terminalLines;
225 226 227 228
}

/// A Stdio that collects stdout and supports simulated stdin.
class FakeStdio extends Stdio {
229
  final MemoryStdout _stdout = MemoryStdout()..terminalColumns = 80;
230
  final MemoryIOSink _stderr = MemoryIOSink();
231
  final FakeStdin _stdin = FakeStdin();
232 233 234 235 236 237 238 239

  @override
  MemoryStdout get stdout => _stdout;

  @override
  MemoryIOSink get stderr => _stderr;

  @override
240
  Stream<List<int>> get stdin => _stdin;
241 242

  void simulateStdin(String line) {
243
    _stdin.controller.add(utf8.encode('$line\n'));
244 245
  }

246 247 248
  @override
  bool hasTerminal = true;

249 250 251 252
  List<String> get writtenToStdout => _stdout.writes.map<String>(_stdout.encoding.decode).toList();
  List<String> get writtenToStderr => _stderr.writes.map<String>(_stderr.encoding.decode).toList();
}

253 254 255
class FakeStdin extends Fake implements Stdin {
  final StreamController<List<int>> controller = StreamController<List<int>>();

256 257 258 259 260 261 262
  void Function(bool mode)? echoModeCallback;

  bool _echoMode = true;

  @override
  bool get echoMode => _echoMode;

263
  @override
264 265 266 267 268 269
  set echoMode(bool mode) {
    _echoMode = mode;
    if (echoModeCallback != null) {
      echoModeCallback!(mode);
    }
  }
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294

  @override
  bool lineMode = true;

  @override
  Stream<S> transform<S>(StreamTransformer<List<int>, S> transformer) {
    return controller.stream.transform(transformer);
  }

  @override
  StreamSubscription<List<int>> listen(
    void Function(List<int> event)? onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) {
    return controller.stream.listen(
      onData,
      onError: onError,
      onDone: onDone,
      cancelOnError: cancelOnError,
    );
  }
}

295
class FakePlistParser implements PlistParser {
296 297
  FakePlistParser([Map<String, Object>? underlyingValues]):
    _underlyingValues = underlyingValues ?? <String, Object>{};
298

299 300 301
  final Map<String, Object> _underlyingValues;

  void setProperty(String key, Object value) {
302 303 304 305
    _underlyingValues[key] = value;
  }

  @override
306 307 308 309
  String? plistXmlContent(String plistFilePath) => throw UnimplementedError();

  @override
  Map<String, Object> parseFile(String plistFilePath) {
310 311 312 313
    return _underlyingValues;
  }

  @override
314 315
  T? getValueFromFile<T>(String plistFilePath, String key) {
    return _underlyingValues[key] as T?;
316
  }
317 318 319 320 321 322 323 324 325 326

  @override
  bool replaceKey(String plistFilePath, {required String key, String? value}) {
    if (value == null) {
      _underlyingValues.remove(key);
      return true;
    }
    setProperty(key, value);
    return true;
  }
327
}
328 329 330 331 332 333 334 335 336 337

class FakeBotDetector implements BotDetector {
  const FakeBotDetector(bool isRunningOnBot)
      : _isRunningOnBot = isRunningOnBot;

  @override
  Future<bool> get isRunningOnBot async => _isRunningOnBot;

  final bool _isRunningOnBot;
}
338

339
class FakeFlutterVersion implements FlutterVersion {
340
  FakeFlutterVersion({
341
    this.branch = 'master',
342
    this.dartSdkVersion = '12',
343
    this.devToolsVersion = '2.8.0',
344 345 346 347 348 349 350
    this.engineRevision = 'abcdefghijklmnopqrstuvwxyz',
    this.engineRevisionShort = 'abcde',
    this.repositoryUrl = 'https://github.com/flutter/flutter.git',
    this.frameworkVersion = '0.0.0',
    this.frameworkRevision = '11111111111111111111',
    this.frameworkRevisionShort = '11111',
    this.frameworkAge = '0 hours ago',
351 352
    this.frameworkCommitDate = '12/01/01',
    this.gitTagVersion = const GitTagVersion.unknown(),
353 354
    this.flutterRoot = '/path/to/flutter',
    this.nextFlutterVersion,
355 356
  });

357 358
  final String branch;

359 360 361
  bool get didFetchTagsAndUpdate => _didFetchTagsAndUpdate;
  bool _didFetchTagsAndUpdate = false;

362 363 364 365 366 367 368 369 370 371 372
  /// Will be returned by [fetchTagsAndGetVersion] if not null.
  final FlutterVersion? nextFlutterVersion;

  @override
    FlutterVersion fetchTagsAndGetVersion({
      SystemClock clock = const SystemClock(),
    }) {
    _didFetchTagsAndUpdate = true;
    return nextFlutterVersion ?? this;
  }

373 374 375
  bool get didCheckFlutterVersionFreshness => _didCheckFlutterVersionFreshness;
  bool _didCheckFlutterVersionFreshness = false;

376
  @override
377 378 379 380 381 382
  String get channel {
    if (kOfficialChannels.contains(branch) || kObsoleteBranches.containsKey(branch)) {
      return branch;
    }
    return kUserBranch;
  }
383

384 385 386
  @override
  final String flutterRoot;

387 388 389
  @override
  final String devToolsVersion;

390
  @override
391
  final String dartSdkVersion;
392 393

  @override
394
  final String engineRevision;
395 396

  @override
397
  final String engineRevisionShort;
398 399

  @override
400
  final String? repositoryUrl;
401 402

  @override
403
  final String frameworkVersion;
404 405

  @override
406
  final String frameworkRevision;
407 408

  @override
409 410 411 412
  final String frameworkRevisionShort;

  @override
  final String frameworkAge;
413 414

  @override
415
  final String frameworkCommitDate;
416

417 418 419
  @override
  final GitTagVersion gitTagVersion;

420
  @override
421
  FileSystem get fs => throw UnimplementedError('FakeFlutterVersion.fs is not implemented');
422 423

  @override
424 425 426
  Future<void> checkFlutterVersionFreshness() async {
    _didCheckFlutterVersionFreshness = true;
  }
427 428

  @override
429
  Future<void> ensureVersionFile() async { }
430 431 432

  @override
  String getBranchName({bool redactUnknownBranches = false}) {
433 434 435 436
    if (!redactUnknownBranches || kOfficialChannels.contains(branch) || kObsoleteBranches.containsKey(branch)) {
      return branch;
    }
    return kUserBranch;
437 438 439 440
  }

  @override
  String getVersionString({bool redactUnknownBranches = false}) {
441
    return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkRevision';
442 443 444 445
  }

  @override
  Map<String, Object> toJson() {
446
    return <String, Object>{};
447 448 449 450 451 452 453 454 455 456 457 458 459 460
  }
}

// A test implementation of [FeatureFlags] that allows enabling without reading
// config. If not otherwise specified, all values default to false.
class TestFeatureFlags implements FeatureFlags {
  TestFeatureFlags({
    this.isLinuxEnabled = false,
    this.isMacOSEnabled = false,
    this.isWebEnabled = false,
    this.isWindowsEnabled = false,
    this.isAndroidEnabled = true,
    this.isIOSEnabled = true,
    this.isFuchsiaEnabled = false,
461
    this.areCustomDevicesEnabled = false,
462
    this.isFlutterWebWasmEnabled = false,
463
    this.isCliAnimationEnabled = true,
464
    this.isNativeAssetsEnabled = false,
465
    this.isPreviewDeviceEnabled = false,
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  });

  @override
  final bool isLinuxEnabled;

  @override
  final bool isMacOSEnabled;

  @override
  final bool isWebEnabled;

  @override
  final bool isWindowsEnabled;

  @override
  final bool isAndroidEnabled;

  @override
  final bool isIOSEnabled;

  @override
  final bool isFuchsiaEnabled;

489 490 491
  @override
  final bool areCustomDevicesEnabled;

492 493 494
  @override
  final bool isFlutterWebWasmEnabled;

495 496 497
  @override
  final bool isCliAnimationEnabled;

498 499 500
  @override
  final bool isNativeAssetsEnabled;

501 502 503
  @override
  final bool isPreviewDeviceEnabled;

504 505
  @override
  bool isEnabled(Feature feature) {
506 507 508 509 510 511 512 513 514 515 516 517 518
    return switch (feature) {
      flutterWebFeature => isWebEnabled,
      flutterLinuxDesktopFeature => isLinuxEnabled,
      flutterMacOSDesktopFeature => isMacOSEnabled,
      flutterWindowsDesktopFeature => isWindowsEnabled,
      flutterAndroidFeature => isAndroidEnabled,
      flutterIOSFeature => isIOSEnabled,
      flutterFuchsiaFeature => isFuchsiaEnabled,
      flutterCustomDevicesFeature => areCustomDevicesEnabled,
      cliAnimation => isCliAnimationEnabled,
      nativeAssets => isNativeAssetsEnabled,
      _ => false,
    };
519 520 521
  }
}

522 523 524
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
  FakeOperatingSystemUtils({this.hostPlatform = HostPlatform.linux_x64});

525 526
  final List<List<String>> chmods = <List<String>>[];

527 528 529 530 531 532 533
  @override
  void makeExecutable(File file) { }

  @override
  HostPlatform hostPlatform = HostPlatform.linux_x64;

  @override
534 535 536
  void chmod(FileSystemEntity entity, String mode) {
    chmods.add(<String>[entity.path, mode]);
  }
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561

  @override
  File? which(String execName) => null;

  @override
  List<File> whichAll(String execName) => <File>[];

  @override
  void unzip(File file, Directory targetDirectory) { }

  @override
  void unpack(File gzippedTarFile, Directory targetDirectory) { }

  @override
  Stream<List<int>> gzipLevel1Stream(Stream<List<int>> stream) => stream;

  @override
  String get name => 'fake OS name and version';

  @override
  String get pathVarSeparator => ';';

  @override
  Future<int> findFreePort({bool ipv6 = false}) async => 12345;
}
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614

class FakeStopwatch implements Stopwatch {
  @override
  bool get isRunning => _isRunning;
  bool _isRunning = false;

  @override
  void start() => _isRunning = true;

  @override
  void stop() => _isRunning = false;

  @override
  Duration elapsed = Duration.zero;

  @override
  int get elapsedMicroseconds => elapsed.inMicroseconds;

  @override
  int get elapsedMilliseconds => elapsed.inMilliseconds;

  @override
  int get elapsedTicks => elapsed.inMilliseconds;

  @override
  int get frequency => 1000;

  @override
  void reset() {
    _isRunning = false;
    elapsed = Duration.zero;
  }

  @override
  String toString() => '$runtimeType $elapsed $isRunning';
}

class FakeStopwatchFactory implements StopwatchFactory {
  FakeStopwatchFactory({
    Stopwatch? stopwatch,
    Map<String, Stopwatch>? stopwatches
  }) : stopwatches = <String, Stopwatch>{
         if (stopwatches != null) ...stopwatches,
         if (stopwatch != null) '': stopwatch,
       };

  Map<String, Stopwatch> stopwatches;

  @override
  Stopwatch createStopwatch([String name = '']) {
    return stopwatches[name] ?? FakeStopwatch();
  }
}
615 616 617 618 619 620 621 622 623 624

class FakeFlutterProjectFactory implements FlutterProjectFactory {
  @override
  FlutterProject fromDirectory(Directory directory) {
    return FlutterProject.fromDirectoryTest(directory);
  }

  @override
  Map<String, FlutterProject> get projects => throw UnimplementedError();
}
625 626

class FakeAndroidSdk extends Fake implements AndroidSdk {
627

628 629 630 631 632 633 634 635 636
  @override
  late bool platformToolsAvailable;

  @override
  late bool licensesAvailable;

  @override
  AndroidSdkVersion? latestVersion;
}
637 638 639 640 641

class FakeAndroidStudio extends Fake implements AndroidStudio {
  @override
  String get javaPath => 'java';
}
642 643 644 645 646

class FakeJava extends Fake implements Java {
  FakeJava({
    this.javaHome = '/android-studio/jbr',
    String binary = '/android-studio/jbr/bin/java',
647
    Version? version,
648 649
    bool canRun = true,
  }): binaryPath = binary,
650
      version = version ?? const Version.withText(19, 0, 2, 'openjdk 19.0.2 2023-01-17'),
651
      _environment = <String, String>{
652
        if (javaHome != null) Java.javaHomeEnvironmentVariable: javaHome,
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
        'PATH': '/android-studio/jbr/bin',
      },
      _canRun = canRun;

  @override
  String? javaHome;

  @override
  String binaryPath;

  final Map<String, String> _environment;
  final bool _canRun;

  @override
  Map<String, String> get environment => _environment;

  @override
670
  Version? version;
671 672 673 674 675 676

  @override
  bool canRun() {
    return _canRun;
  }
}