• Christopher Fujino's avatar
    Flutter preview device (#135639) · 8a31a3a2
    Christopher Fujino authored
    Fixes https://github.com/flutter/flutter/issues/130277
    
    This PR does two things:
    
    1. introduce a hidden `flutter build _preview` command, that will build a debug windows desktop app and copy it into the SDK's binary cache. This command is only intended to be run during packaging.
    2. introduce a new device type, called `PreviewDevice`, which relies on the prebuilt desktop debug app from step 1, copies it into the target app's assets build folder, and then hot reloads their dart code into it.
    8a31a3a2
build_test.dart 6.54 KB
// 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:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';

import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
import '../../src/test_build_system.dart';
import '../../src/test_flutter_command_runner.dart';

void main() {
  testUsingContext('obfuscate requires split-debug-info', () {
    final FakeBuildInfoCommand command = FakeBuildInfoCommand();
    final CommandRunner<void> commandRunner = createTestCommandRunner(command);

    expect(() => commandRunner.run(<String>[
      'fake',
      '--obfuscate',
    ]), throwsToolExit(message: '"--${FlutterOptions.kDartObfuscationOption}" can only be used in '
        'combination with "--${FlutterOptions.kSplitDebugInfoOption}"'));
  });

  group('Fatal Logs', () {
    late FakeBuildCommand command;
    late MemoryFileSystem fs;
    late BufferLogger logger;
    late ProcessManager processManager;
    late ProcessUtils processUtils;
    late Artifacts artifacts;

    setUp(() {
      fs = MemoryFileSystem.test();
      artifacts = Artifacts.test(fileSystem: fs);
      fs.file('/package/pubspec.yaml').createSync(recursive: true);
      fs.currentDirectory = '/package';
      Cache.disableLocking();
      logger = BufferLogger.test();
      processManager = FakeProcessManager.empty();
      processUtils = ProcessUtils(
        logger: logger,
        processManager: processManager,
      );
    });

    testUsingContext("doesn't fail if --fatal-warnings specified and no warnings occur", () async {
      command = FakeBuildCommand(
        artifacts: artifacts,
        androidSdk: FakeAndroidSdk(),
        buildSystem: TestBuildSystem.all(BuildResult(success: true)),
        fileSystem: fs,
        logger: logger,
        processUtils: processUtils,
        osUtils: FakeOperatingSystemUtils(),
      );
      try {
        await createTestCommandRunner(command).run(<String>[
          'build',
          'test',
          '--${FlutterOptions.kFatalWarnings}',
        ]);
      } on Exception {
        fail('Unexpected exception thrown');
      }
    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
      ProcessManager: () => processManager,
    });

    testUsingContext("doesn't fail if --fatal-warnings not specified", () async {
      command = FakeBuildCommand(
        artifacts: artifacts,
        androidSdk: FakeAndroidSdk(),
        buildSystem: TestBuildSystem.all(BuildResult(success: true)),
        fileSystem: fs,
        logger: logger,
        processUtils: processUtils,
        osUtils: FakeOperatingSystemUtils(),
      );
      testLogger.printWarning('Warning: Mild annoyance Will Robinson!');
      try {
        await createTestCommandRunner(command).run(<String>[
          'build',
          'test',
        ]);
      } on Exception {
        fail('Unexpected exception thrown');
      }
    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
      ProcessManager: () => processManager,
    });

    testUsingContext('fails if --fatal-warnings specified and warnings emitted', () async {
      command = FakeBuildCommand(
        artifacts: artifacts,
        androidSdk: FakeAndroidSdk(),
        buildSystem: TestBuildSystem.all(BuildResult(success: true)),
        fileSystem: fs,
        logger: logger,
        processUtils: processUtils,
        osUtils: FakeOperatingSystemUtils(),
      );
      testLogger.printWarning('Warning: Mild annoyance Will Robinson!');
      await expectLater(createTestCommandRunner(command).run(<String>[
        'build',
        'test',
        '--${FlutterOptions.kFatalWarnings}',
      ]), throwsToolExit(message: 'Logger received warning output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
      ProcessManager: () => processManager,
    });

    testUsingContext('fails if --fatal-warnings specified and errors emitted', () async {
      command = FakeBuildCommand(
        artifacts: artifacts,
        androidSdk: FakeAndroidSdk(),
        buildSystem: TestBuildSystem.all(BuildResult(success: true)),
        fileSystem: fs,
        logger: logger,
        processUtils: processUtils,
        osUtils: FakeOperatingSystemUtils(),
      );
      testLogger.printError('Error: Danger Will Robinson!');
      await expectLater(createTestCommandRunner(command).run(<String>[
        'build',
        'test',
        '--${FlutterOptions.kFatalWarnings}',
      ]), throwsToolExit(message: 'Logger received error output during the run, and "--${FlutterOptions.kFatalWarnings}" is enabled.'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
      ProcessManager: () => processManager,
    });
  });
}

class FakeBuildInfoCommand extends FlutterCommand {
  FakeBuildInfoCommand() : super() {
    addSplitDebugInfoOption();
    addDartObfuscationOption();
  }

  @override
  String get description => '';

  @override
  String get name => 'fake';

  @override
  Future<FlutterCommandResult> runCommand() async {
    await getBuildInfo();
    return FlutterCommandResult.success();
  }
}

class FakeBuildCommand extends BuildCommand {
  FakeBuildCommand({
    required super.fileSystem,
    required super.buildSystem,
    required super.osUtils,
    required Logger logger,
    required super.androidSdk,
    required super.processUtils,
    required super.artifacts,
    bool verboseHelp = false,
  }) : super(logger: logger) {
    addSubcommand(FakeBuildSubcommand(logger: logger, verboseHelp: verboseHelp));
  }

  @override
  String get description => '';

  @override
  String get name => 'build';

  @override
  Future<FlutterCommandResult> runCommand() async {
    return FlutterCommandResult.success();
  }
}

class FakeBuildSubcommand extends BuildSubCommand {
  FakeBuildSubcommand({required super.logger, required super.verboseHelp});

  @override
  String get description => '';

  @override
  String get name => 'test';

  @override
  Future<FlutterCommandResult> runCommand() async {
    return FlutterCommandResult.success();
  }
}