dart_test.dart 16.2 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2019 The Chromium 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/base/build.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
8
import 'package:flutter_tools/src/base/process.dart';
9 10 11 12
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/exceptions.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart';
13
import 'package:flutter_tools/src/build_system/targets/ios.dart';
14 15
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/compile.dart';
16
import 'package:flutter_tools/src/macos/xcode.dart';
17 18 19 20
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

21 22 23
import '../../../src/common.dart';
import '../../../src/mocks.dart';
import '../../../src/testbed.dart';
24 25

void main() {
26 27 28 29 30 31 32 33 34 35
  const BuildSystem buildSystem = BuildSystem();
  Testbed testbed;
  Environment androidEnvironment;
  Environment iosEnvironment;
  MockProcessManager mockProcessManager;
  MockXcode mockXcode;

  setUpAll(() {
    Cache.disableLocking();
  });
36

37 38 39 40 41
  setUp(() {
    mockXcode = MockXcode();
    mockProcessManager = MockProcessManager();
    testbed = Testbed(setup: () {
      androidEnvironment = Environment(
42
        outputDir: fs.currentDirectory,
43 44 45 46
        projectDir: fs.currentDirectory,
        defines: <String, String>{
          kBuildMode: getNameForBuildMode(BuildMode.profile),
          kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
47
        },
48 49
      );
      iosEnvironment = Environment(
50
        outputDir: fs.currentDirectory,
51 52 53 54
        projectDir: fs.currentDirectory,
        defines: <String, String>{
          kBuildMode: getNameForBuildMode(BuildMode.profile),
          kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
55
        },
56 57 58 59 60 61 62
      );
      HostPlatform hostPlatform;
      if (platform.isWindows) {
        hostPlatform = HostPlatform.windows_x64;
      } else if (platform.isLinux) {
        hostPlatform = HostPlatform.linux_x64;
      } else if (platform.isMacOS) {
63
        hostPlatform = HostPlatform.darwin_x64;
64 65 66
      } else {
        assert(false);
      }
67 68 69
      final String skyEngineLine = platform.isWindows
        ? r'sky_engine:file:///C:/bin/cache/pkg/sky_engine/lib/'
        : 'sky_engine:file:///bin/cache/pkg/sky_engine/lib/';
70 71 72
      fs.file('.packages')
        ..createSync()
        ..writeAsStringSync('''
73 74 75
# Generated
$skyEngineLine
flutter_tools:lib/''');
76 77 78 79 80 81 82 83
      final String engineArtifacts = fs.path.join('bin', 'cache',
          'artifacts', 'engine');
      final List<String> paths = <String>[
        fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui',
          'ui.dart'),
        fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'sdk_ext',
            'vmservice_io.dart'),
        fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
84
        fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart.exe'),
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
        fs.path.join(engineArtifacts, getNameForHostPlatform(hostPlatform),
            'frontend_server.dart.snapshot'),
        fs.path.join(engineArtifacts, 'android-arm-profile',
            getNameForHostPlatform(hostPlatform), 'gen_snapshot'),
        fs.path.join(engineArtifacts, 'ios-profile', 'gen_snapshot'),
        fs.path.join(engineArtifacts, 'common', 'flutter_patched_sdk',
            'platform_strong.dill'),
        fs.path.join('lib', 'foo.dart'),
        fs.path.join('lib', 'bar.dart'),
        fs.path.join('lib', 'fizz'),
        fs.path.join('packages', 'flutter_tools', 'lib', 'src', 'build_system', 'targets', 'dart.dart'),
        fs.path.join('packages', 'flutter_tools', 'lib', 'src', 'build_system', 'targets', 'ios.dart'),
      ];
      for (String path in paths) {
        fs.file(path).createSync(recursive: true);
      }
    }, overrides: <Type, Generator>{
      KernelCompilerFactory: () => FakeKernelCompilerFactory(),
      GenSnapshot: () => FakeGenSnapshot(),
104
    });
105
  });
106

107 108
  test('kernel_snapshot Produces correct output directory', () => testbed.run(() async {
    await buildSystem.build(const KernelSnapshot(), androidEnvironment);
109

110 111
    expect(fs.file(fs.path.join(androidEnvironment.buildDir.path,'app.dill')).existsSync(), true);
  }));
112

113 114 115
  test('kernel_snapshot throws error if missing build mode', () => testbed.run(() async {
    final BuildResult result = await buildSystem.build(const KernelSnapshot(),
        androidEnvironment..defines.remove(kBuildMode));
116

117 118
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
119

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
  test('kernel_snapshot handles null result from kernel compilation', () => testbed.run(() async {
    final FakeKernelCompilerFactory fakeKernelCompilerFactory = kernelCompilerFactory;
    fakeKernelCompilerFactory.kernelCompiler = MockKernelCompiler();
    when(fakeKernelCompilerFactory.kernelCompiler.compile(
      sdkRoot: anyNamed('sdkRoot'),
      mainPath: anyNamed('mainPath'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      targetModel: anyNamed('targetModel'),
      linkPlatformKernelIn: anyNamed('linkPlatformKernelIn'),
      aot: anyNamed('aot'),
      trackWidgetCreation: anyNamed('trackWidgetCreation'),
      extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
      packagesPath: anyNamed('packagesPath'),
      fileSystemRoots: anyNamed('fileSystemRoots'),
      fileSystemScheme: anyNamed('fileSystemScheme'),
      targetProductVm: anyNamed('targetProductVm'),
      platformDill: anyNamed('platformDill'),
      initializeFromDill: anyNamed('initializeFromDill'),
    )).thenAnswer((Invocation invocation) async {
      return null;
    });
    final BuildResult result = await buildSystem.build(const KernelSnapshot(), androidEnvironment);

    expect(result.exceptions.values.single.exception, isInstanceOf<Exception>());
  }));

147 148 149 150 151 152 153 154 155 156 157 158 159 160
  test('kernel_snapshot does not use track widget creation on profile builds', () => testbed.run(() async {
    final MockKernelCompiler mockKernelCompiler = MockKernelCompiler();
    when(kernelCompilerFactory.create(any)).thenAnswer((Invocation _) async {
      return mockKernelCompiler;
    });
    when(mockKernelCompiler.compile(
      sdkRoot: anyNamed('sdkRoot'),
      aot: anyNamed('aot'),
      trackWidgetCreation: false,
      targetModel: anyNamed('targetModel'),
      targetProductVm: anyNamed('targetProductVm'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      packagesPath: anyNamed('packagesPath'),
161
      mainPath: anyNamed('mainPath'),
162 163 164 165
    )).thenAnswer((Invocation _) async {
      return const CompilerOutput('example', 0, <Uri>[]);
    });

166
    await const KernelSnapshot().build(androidEnvironment);
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
  }, overrides: <Type, Generator>{
    KernelCompilerFactory: () => MockKernelCompilerFactory(),
  }));

  test('kernel_snapshot does use track widget creation on debug builds', () => testbed.run(() async {
    final MockKernelCompiler mockKernelCompiler = MockKernelCompiler();
    when(kernelCompilerFactory.create(any)).thenAnswer((Invocation _) async {
      return mockKernelCompiler;
    });
    when(mockKernelCompiler.compile(
      sdkRoot: anyNamed('sdkRoot'),
      aot: anyNamed('aot'),
      trackWidgetCreation: true,
      targetModel: anyNamed('targetModel'),
      targetProductVm: anyNamed('targetProductVm'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      packagesPath: anyNamed('packagesPath'),
185
      mainPath: anyNamed('mainPath'),
186 187 188 189
    )).thenAnswer((Invocation _) async {
      return const CompilerOutput('example', 0, <Uri>[]);
    });

190
    await const KernelSnapshot().build(Environment(
191
        outputDir: fs.currentDirectory,
192 193 194 195 196 197 198 199 200
        projectDir: fs.currentDirectory,
        defines: <String, String>{
      kBuildMode: 'debug',
      kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
    }));
  }, overrides: <Type, Generator>{
    KernelCompilerFactory: () => MockKernelCompilerFactory(),
  }));

201 202
  test('aot_elf_profile Produces correct output directory', () => testbed.run(() async {
    await buildSystem.build(const AotElfProfile(), androidEnvironment);
203

204 205 206
    expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'app.dill')).existsSync(), true);
    expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'app.so')).existsSync(), true);
  }));
207

208 209 210
  test('aot_elf_profile throws error if missing build mode', () => testbed.run(() async {
    final BuildResult result = await buildSystem.build(const AotElfProfile(),
        androidEnvironment..defines.remove(kBuildMode));
211

212 213
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
214 215


216 217 218
  test('aot_elf_profile throws error if missing target platform', () => testbed.run(() async {
    final BuildResult result = await buildSystem.build(const AotElfProfile(),
        androidEnvironment..defines.remove(kTargetPlatform));
219

220 221
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
222 223


224 225 226
  test('aot_assembly_profile throws error if missing build mode', () => testbed.run(() async {
    final BuildResult result = await buildSystem.build(const AotAssemblyProfile(),
        iosEnvironment..defines.remove(kBuildMode));
227

228 229
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
230

231 232 233
  test('aot_assembly_profile throws error if missing target platform', () => testbed.run(() async {
    final BuildResult result = await buildSystem.build(const AotAssemblyProfile(),
        iosEnvironment..defines.remove(kTargetPlatform));
234

235 236
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
237

238 239 240
  test('aot_assembly_profile throws error if built for non-iOS platform', () => testbed.run(() async {
    final BuildResult result = await buildSystem
        .build(const AotAssemblyProfile(), androidEnvironment);
241

242 243
    expect(result.exceptions.values.single.exception, isInstanceOf<Exception>());
  }));
244

245 246 247 248 249 250
  test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async {
    iosEnvironment.defines[kIosArchs] ='armv7,arm64';
    when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
      fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
          .createSync(recursive: true);
      return FakeProcessResult(
251 252 253
        stdout: '',
        stderr: '',
      );
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
    });
    final BuildResult result = await buildSystem
        .build(const AotAssemblyProfile(), iosEnvironment);
    expect(result.success, true);
  }, overrides: <Type, Generator>{
    ProcessManager: () => mockProcessManager,
  }));

  test('aot_assembly_profile with bitcode sends correct argument to snapshotter (one arch)', () => testbed.run(() async {
    iosEnvironment.defines[kIosArchs] = 'arm64';
    iosEnvironment.defines[kBitcodeFlag] = 'true';

    final FakeProcessResult fakeProcessResult = FakeProcessResult(
      stdout: '',
      stderr: '',
    );
    final RunResult fakeRunResult = RunResult(fakeProcessResult, const <String>['foo']);
    when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
      fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
          .createSync(recursive: true);
      return fakeProcessResult;
    });
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
    when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));

    final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);

    expect(result.success, true);
    verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(1);
    verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(1);
  }, overrides: <Type, Generator>{
    ProcessManager: () => mockProcessManager,
    Xcode: () => mockXcode,
  }));

  test('aot_assembly_profile with bitcode sends correct argument to snapshotter (mutli arch)', () => testbed.run(() async {
    iosEnvironment.defines[kIosArchs] = 'armv7,arm64';
    iosEnvironment.defines[kBitcodeFlag] = 'true';

    final FakeProcessResult fakeProcessResult = FakeProcessResult(
      stdout: '',
      stderr: '',
    );
    final RunResult fakeRunResult = RunResult(fakeProcessResult, const <String>['foo']);
    when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
      fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
          .createSync(recursive: true);
      return fakeProcessResult;
    });
304

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
    when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));

    final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);

    expect(result.success, true);
    verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2);
    verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2);
  }, overrides: <Type, Generator>{
    ProcessManager: () => mockProcessManager,
    Xcode: () => mockXcode,
  }));

  test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async {
    iosEnvironment.defines[kIosArchs] = 'armv7,arm64';
    when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
      fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
          .createSync(recursive: true);
      return FakeProcessResult(
324 325 326
        stdout: '',
        stderr: '',
      );
327 328 329 330 331 332 333
    });
    final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);

    expect(result.success, true);
  }, overrides: <Type, Generator>{
    ProcessManager: () => mockProcessManager,
  }));
334 335 336 337 338 339 340 341 342 343

  test('list dart sources handles packages without lib directories', () => testbed.run(() {
    fs.file('.packages')
      ..createSync()
      ..writeAsStringSync('''
# Generated
example:fiz/lib/''');
    fs.directory('fiz').createSync();
    expect(listDartSources(androidEnvironment), <File>[]);
  }));
344 345 346 347
}

class MockProcessManager extends Mock implements ProcessManager {}

348 349
class MockXcode extends Mock implements Xcode {}

350
class FakeGenSnapshot implements GenSnapshot {
351
  List<String> lastCallAdditionalArgs;
352
  @override
353
  Future<int> run({SnapshotType snapshotType, DarwinArch darwinArch, Iterable<String> additionalArgs = const <String>[]}) async {
354 355
    lastCallAdditionalArgs = additionalArgs.toList();
    final Directory out = fs.file(lastCallAdditionalArgs.last).parent;
356
    if (darwinArch == null) {
357 358 359 360 361
      out.childFile('app.so').createSync();
      out.childFile('gen_snapshot.d').createSync();
      return 0;
    }
    out.childDirectory('App.framework').childFile('App').createSync(recursive: true);
362 363 364 365 366 367

    final String assembly = lastCallAdditionalArgs
        .firstWhere((String arg) => arg.startsWith('--assembly'))
        .substring('--assembly='.length);
    fs.file(assembly).createSync();
    fs.file(assembly.replaceAll('.S', '.o')).createSync();
368 369 370 371 372
    return 0;
  }
}

class FakeKernelCompilerFactory implements KernelCompilerFactory {
373
  KernelCompiler kernelCompiler = FakeKernelCompiler();
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396

  @override
  Future<KernelCompiler> create(FlutterProject flutterProject) async {
    return kernelCompiler;
  }
}

class FakeKernelCompiler implements KernelCompiler {
  @override
  Future<CompilerOutput> compile({
    String sdkRoot,
    String mainPath,
    String outputFilePath,
    String depFilePath,
    TargetModel targetModel = TargetModel.flutter,
    bool linkPlatformKernelIn = false,
    bool aot = false,
    bool trackWidgetCreation,
    List<String> extraFrontEndOptions,
    String packagesPath,
    List<String> fileSystemRoots,
    String fileSystemScheme,
    bool targetProductVm = false,
397
    String platformDill,
398 399 400 401
    String initializeFromDill,
  }) async {
    fs.file(outputFilePath).createSync(recursive: true);
    return CompilerOutput(outputFilePath, 0, null);
402 403
  }
}
404 405 406

class MockKernelCompilerFactory extends Mock implements KernelCompilerFactory {}
class MockKernelCompiler extends Mock implements KernelCompiler {}