dart_test.dart 20.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// 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';
7
import 'package:flutter_tools/src/base/process.dart';
8 9 10 11
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';
12
import 'package:flutter_tools/src/build_system/targets/ios.dart';
13 14
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/compile.dart';
15
import 'package:flutter_tools/src/macos/xcode.dart';
16
import 'package:flutter_tools/src/project.dart';
17 18
import 'package:flutter_tools/src/globals.dart' as globals;

19 20 21
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

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

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

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

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

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

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

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

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

121
  test('kernel_snapshot handles null result from kernel compilation', () => testbed.run(() async {
122
    final FakeKernelCompilerFactory fakeKernelCompilerFactory = kernelCompilerFactory as FakeKernelCompilerFactory;
123 124 125 126 127 128 129 130 131
    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'),
132
      buildMode: anyNamed('buildMode'),
133 134 135 136 137 138 139
      trackWidgetCreation: anyNamed('trackWidgetCreation'),
      extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
      packagesPath: anyNamed('packagesPath'),
      fileSystemRoots: anyNamed('fileSystemRoots'),
      fileSystemScheme: anyNamed('fileSystemScheme'),
      platformDill: anyNamed('platformDill'),
      initializeFromDill: anyNamed('initializeFromDill'),
140
      dartDefines: anyNamed('dartDefines'),
141 142 143 144 145 146 147 148
    )).thenAnswer((Invocation invocation) async {
      return null;
    });
    final BuildResult result = await buildSystem.build(const KernelSnapshot(), androidEnvironment);

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

149 150 151 152 153 154 155 156
  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'),
157
      buildMode: anyNamed('buildMode'),
158 159 160 161 162
      trackWidgetCreation: false,
      targetModel: anyNamed('targetModel'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      packagesPath: anyNamed('packagesPath'),
163
      mainPath: anyNamed('mainPath'),
164 165 166
      extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
      fileSystemRoots: anyNamed('fileSystemRoots'),
      fileSystemScheme: anyNamed('fileSystemScheme'),
167
      linkPlatformKernelIn: anyNamed('linkPlatformKernelIn'),
168
      dartDefines: anyNamed('dartDefines'),
169 170 171 172
    )).thenAnswer((Invocation _) async {
      return const CompilerOutput('example', 0, <Uri>[]);
    });

173
    await const KernelSnapshot().build(androidEnvironment);
174 175 176 177
  }, overrides: <Type, Generator>{
    KernelCompilerFactory: () => MockKernelCompilerFactory(),
  }));

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
  test('kernel_snapshot can disable 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'),
      buildMode: anyNamed('buildMode'),
      trackWidgetCreation: false,
      targetModel: anyNamed('targetModel'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      packagesPath: anyNamed('packagesPath'),
      mainPath: anyNamed('mainPath'),
193 194 195
      extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
      fileSystemRoots: anyNamed('fileSystemRoots'),
      fileSystemScheme: anyNamed('fileSystemScheme'),
196
      linkPlatformKernelIn: false,
197
      dartDefines: anyNamed('dartDefines'),
198 199 200 201
    )).thenAnswer((Invocation _) async {
      return const CompilerOutput('example', 0, <Uri>[]);
    });

202 203 204
    await const KernelSnapshot().build(androidEnvironment
      ..defines[kBuildMode] = 'debug'
      ..defines[kTrackWidgetCreation] = 'false');
205 206 207 208
  }, overrides: <Type, Generator>{
    KernelCompilerFactory: () => MockKernelCompilerFactory(),
  }));

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
  test('kernel_snapshot forces platform linking on debug for darwin target platforms', () => 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'),
      buildMode: anyNamed('buildMode'),
      trackWidgetCreation: anyNamed('trackWidgetCreation'),
      targetModel: anyNamed('targetModel'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      packagesPath: anyNamed('packagesPath'),
      mainPath: anyNamed('mainPath'),
      extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
      fileSystemRoots: anyNamed('fileSystemRoots'),
      fileSystemScheme: anyNamed('fileSystemScheme'),
      linkPlatformKernelIn: true,
      dartDefines: anyNamed('dartDefines'),
    )).thenAnswer((Invocation _) async {
      return const CompilerOutput('example', 0, <Uri>[]);
    });

    await const KernelSnapshot().build(androidEnvironment
      ..defines[kTargetPlatform]  = 'darwin-x64'
      ..defines[kBuildMode] = 'debug'
236
      ..defines[kTrackWidgetCreation] = 'false'
237 238 239 240 241 242
    );
  }, overrides: <Type, Generator>{
    KernelCompilerFactory: () => MockKernelCompilerFactory(),
  }));


243 244 245 246 247 248 249 250
  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'),
251
      buildMode: anyNamed('buildMode'),
252 253 254 255 256
      trackWidgetCreation: true,
      targetModel: anyNamed('targetModel'),
      outputFilePath: anyNamed('outputFilePath'),
      depFilePath: anyNamed('depFilePath'),
      packagesPath: anyNamed('packagesPath'),
257
      mainPath: anyNamed('mainPath'),
258 259 260
      extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
      fileSystemRoots: anyNamed('fileSystemRoots'),
      fileSystemScheme: anyNamed('fileSystemScheme'),
261
      linkPlatformKernelIn: false,
262
      dartDefines: anyNamed('dartDefines'),
263 264 265 266
    )).thenAnswer((Invocation _) async {
      return const CompilerOutput('example', 0, <Uri>[]);
    });

267
    await const KernelSnapshot().build(Environment(
268 269
      outputDir: globals.fs.currentDirectory,
      projectDir: globals.fs.currentDirectory,
270 271 272 273
      defines: <String, String>{
        kBuildMode: 'debug',
        kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
      }));
274 275 276 277
  }, overrides: <Type, Generator>{
    KernelCompilerFactory: () => MockKernelCompilerFactory(),
  }));

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

281 282
    expect(globals.fs.file(globals.fs.path.join(androidEnvironment.buildDir.path, 'app.dill')).existsSync(), true);
    expect(globals.fs.file(globals.fs.path.join(androidEnvironment.buildDir.path, 'app.so')).existsSync(), true);
283
  }));
284

285 286 287
  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));
288

289 290
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
291

292 293 294
  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));
295

296 297
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
298

299 300 301
  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));
302

303 304
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
305

306 307 308
  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));
309

310 311
    expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
  }));
312

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

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

320 321 322
  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 {
323
      globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
324 325
          .createSync(recursive: true);
      return FakeProcessResult(
326 327 328
        stdout: '',
        stderr: '',
      );
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    });
    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 {
347
      globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
348 349 350
          .createSync(recursive: true);
      return fakeProcessResult;
    });
351

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
    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 {
375
      globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
376 377 378
          .createSync(recursive: true);
      return fakeProcessResult;
    });
379

380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
    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 {
396
      globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
397 398
          .createSync(recursive: true);
      return FakeProcessResult(
399 400 401
        stdout: '',
        stderr: '',
      );
402 403 404 405 406 407 408
    });
    final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);

    expect(result.success, true);
  }, overrides: <Type, Generator>{
    ProcessManager: () => mockProcessManager,
  }));
409

410 411 412 413 414 415 416
  test('Profile/ReleaseCopyFlutterAotBundle copies .so to correct output directory', () => testbed.run(() async {
    androidEnvironment.buildDir.createSync(recursive: true);
    androidEnvironment.buildDir.childFile('app.so').createSync();
    await const ProfileCopyFlutterAotBundle().build(androidEnvironment);

    expect(androidEnvironment.outputDir.childFile('app.so').existsSync(), true);
  }));
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438

  test('kExtraGenSnapshotOptions passes values to gen_snapshot', () => testbed.run(() async {
    androidEnvironment.defines[kExtraGenSnapshotOptions] = 'foo,bar,baz=2';

    when(genSnapshot.run(
      snapshotType: anyNamed('snapshotType'),
      darwinArch: anyNamed('darwinArch'),
      additionalArgs: captureAnyNamed('additionalArgs'),
    )).thenAnswer((Invocation invocation) async {
      expect(invocation.namedArguments[#additionalArgs], containsAll(<String>[
        'foo',
        'bar',
        'baz=2',
      ]));
      return 0;
    });


    await const AotElfRelease().build(androidEnvironment);
  }, overrides: <Type, Generator>{
    GenSnapshot: () => MockGenSnapshot(),
  }));
439 440 441
}

class MockProcessManager extends Mock implements ProcessManager {}
442
class MockGenSnapshot extends Mock implements GenSnapshot {}
443 444
class MockXcode extends Mock implements Xcode {}

445
class FakeGenSnapshot implements GenSnapshot {
446
  List<String> lastCallAdditionalArgs;
447
  @override
448
  Future<int> run({SnapshotType snapshotType, DarwinArch darwinArch, Iterable<String> additionalArgs = const <String>[]}) async {
449
    lastCallAdditionalArgs = additionalArgs.toList();
450
    final Directory out = globals.fs.file(lastCallAdditionalArgs.last).parent;
451
    if (darwinArch == null) {
452 453 454 455 456
      out.childFile('app.so').createSync();
      out.childFile('gen_snapshot.d').createSync();
      return 0;
    }
    out.childDirectory('App.framework').childFile('App').createSync(recursive: true);
457 458 459 460

    final String assembly = lastCallAdditionalArgs
        .firstWhere((String arg) => arg.startsWith('--assembly'))
        .substring('--assembly='.length);
461 462
    globals.fs.file(assembly).createSync();
    globals.fs.file(assembly.replaceAll('.S', '.o')).createSync();
463 464 465 466 467
    return 0;
  }
}

class FakeKernelCompilerFactory implements KernelCompilerFactory {
468
  KernelCompiler kernelCompiler = FakeKernelCompiler();
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485

  @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,
486
    BuildMode buildMode,
487
    bool causalAsyncStacks = true,
488 489 490 491 492
    bool trackWidgetCreation,
    List<String> extraFrontEndOptions,
    String packagesPath,
    List<String> fileSystemRoots,
    String fileSystemScheme,
493
    String platformDill,
494
    String initializeFromDill,
495
    List<String> dartDefines,
496
  }) async {
497
    globals.fs.file(outputFilePath).createSync(recursive: true);
498
    return CompilerOutput(outputFilePath, 0, null);
499 500
  }
}
501 502 503

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