macos_test.dart 10.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter_tools/src/base/build.dart';
6 7
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
8
import 'package:flutter_tools/src/base/process.dart';
9
import 'package:flutter_tools/src/build_system/build_system.dart';
10
import 'package:flutter_tools/src/build_system/targets/dart.dart';
11
import 'package:flutter_tools/src/build_system/targets/macos.dart';
12
import 'package:flutter_tools/src/cache.dart';
13
import 'package:flutter_tools/src/macos/cocoapods.dart';
14
import 'package:flutter_tools/src/macos/xcode.dart';
15
import 'package:flutter_tools/src/globals.dart' as globals;
16 17
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
18
import 'package:platform/platform.dart';
19

20 21
import '../../../src/common.dart';
import '../../../src/testbed.dart';
22

23
const String _kInputPrefix = 'bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework';
24
const String _kOutputPrefix = 'FlutterMacOS.framework';
25 26

final List<File> inputs = <File>[
27
  globals.fs.file('$_kInputPrefix/FlutterMacOS'),
28
  // Headers
29 30 31 32 33 34 35 36 37 38
  globals.fs.file('$_kInputPrefix/Headers/FlutterDartProject.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterEngine.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterViewController.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterBinaryMessenger.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterChannels.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterCodecs.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterMacros.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterPluginMacOS.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterPluginRegistrarMacOS.h'),
  globals.fs.file('$_kInputPrefix/Headers/FlutterMacOS.h'),
39
  // Modules
40
  globals.fs.file('$_kInputPrefix/Modules/module.modulemap'),
41
  // Resources
42 43
  globals.fs.file('$_kInputPrefix/Resources/icudtl.dat'),
  globals.fs.file('$_kInputPrefix/Resources/Info.plist'),
44
  // Ignore Versions folder for now
45
  globals.fs.file('packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
46 47
];

48
void main() {
49 50 51
  Testbed testbed;
  Environment environment;
  MockPlatform mockPlatform;
52
  MockXcode mockXcode;
53

54 55 56 57 58 59
  setUpAll(() {
    Cache.disableLocking();
    Cache.flutterRoot = '';
  });

  setUp(() {
60
    mockXcode = MockXcode();
61 62 63 64
    mockPlatform = MockPlatform();
    when(mockPlatform.isWindows).thenReturn(false);
    when(mockPlatform.isMacOS).thenReturn(true);
    when(mockPlatform.isLinux).thenReturn(false);
65
    when(mockPlatform.environment).thenReturn(const <String, String>{});
66
    testbed = Testbed(setup: () {
67
      globals.fs.file(globals.fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui',
68
          'ui.dart')).createSync(recursive: true);
69
      globals.fs.file(globals.fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'sdk_ext',
70 71
          'vmservice_io.dart')).createSync(recursive: true);

72 73 74
    environment = Environment.test(
      globals.fs.currentDirectory,
      defines: <String, String>{
75 76
          kBuildMode: 'debug',
          kTargetPlatform: 'darwin-x64',
77
        },
78
      );
79 80 81
    }, overrides: <Type, Generator>{
      ProcessManager: () => MockProcessManager(),
      Platform: () => mockPlatform,
82
    });
83
  });
84

85
  test('Copies files to correct cache directory', () => testbed.run(() async {
86
    for (final File input in inputs) {
87 88
      input.createSync(recursive: true);
    }
89 90 91 92
    // Create output directory so we can test that it is deleted.
    environment.outputDir.childDirectory(_kOutputPrefix)
        .createSync(recursive: true);

93
    when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async {
94
      final List<String> arguments = invocation.positionalArguments.first as List<String>;
95 96
      final String sourcePath = arguments[arguments.length - 2];
      final String targetPath = arguments.last;
97 98
      final Directory source = globals.fs.directory(sourcePath);
      final Directory target = globals.fs.directory(targetPath);
99 100 101 102 103

      // verify directory was deleted by command.
      expect(target.existsSync(), false);
      target.createSync(recursive: true);

104
      for (final FileSystemEntity entity in source.listSync(recursive: true)) {
105
        if (entity is File) {
106 107 108 109
          final String relative = globals.fs.path.relative(entity.path, from: source.path);
          final String destination = globals.fs.path.join(target.path, relative);
          if (!globals.fs.file(destination).parent.existsSync()) {
            globals.fs.file(destination).parent.createSync();
110 111 112 113 114 115
          }
          entity.copySync(destination);
        }
      }
      return FakeProcessResult()..exitCode = 0;
    });
116
    await const DebugUnpackMacOS().build(environment);
117

118
    expect(globals.fs.directory(_kOutputPrefix).existsSync(), true);
119
    for (final File file in inputs) {
120
      expect(globals.fs.file(file.path.replaceFirst(_kInputPrefix, _kOutputPrefix)).existsSync(), true);
121
    }
122 123
  }));

124
  test('debug macOS application fails if App.framework missing', () => testbed.run(() async {
125 126
    final String inputKernel = globals.fs.path.join(environment.buildDir.path, 'app.dill');
    globals.fs.file(inputKernel)
127 128 129
      ..createSync(recursive: true)
      ..writeAsStringSync('testing');

130
    expect(() async => await const DebugMacOSBundleFlutterAssets().build(environment),
Dan Field's avatar
Dan Field committed
131
        throwsException);
132 133
  }));

134
  test('debug macOS application creates correctly structured framework', () => testbed.run(() async {
135
    globals.fs.file(globals.fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64',
136
        'vm_isolate_snapshot.bin')).createSync(recursive: true);
137
    globals.fs.file(globals.fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64',
138
        'isolate_snapshot.bin')).createSync(recursive: true);
139
    globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'App.framework', 'App'))
140
        .createSync(recursive: true);
141

142 143
    final String inputKernel = globals.fs.path.join(environment.buildDir.path, 'app.dill');
    final String outputKernel = globals.fs.path.join('App.framework', 'Versions', 'A', 'Resources',
144
        'flutter_assets', 'kernel_blob.bin');
145
    final String outputPlist = globals.fs.path.join('App.framework', 'Versions', 'A', 'Resources',
146
        'Info.plist');
147
    globals.fs.file(inputKernel)
148 149 150
      ..createSync(recursive: true)
      ..writeAsStringSync('testing');

151
    await const DebugMacOSBundleFlutterAssets().build(environment);
152

153 154
    expect(globals.fs.file(outputKernel).readAsStringSync(), 'testing');
    expect(globals.fs.file(outputPlist).readAsStringSync(), contains('io.flutter.flutter.app'));
155
  }));
156 157

  test('release/profile macOS application has no blob or precompiled runtime', () => testbed.run(() async {
158
    globals.fs.file(globals.fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64',
159
        'vm_isolate_snapshot.bin')).createSync(recursive: true);
160
    globals.fs.file(globals.fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64',
161
        'isolate_snapshot.bin')).createSync(recursive: true);
162
    globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'App.framework', 'App'))
163
        .createSync(recursive: true);
164
    final String outputKernel = globals.fs.path.join('App.framework', 'Resources',
165
        'flutter_assets', 'kernel_blob.bin');
166
    final String precompiledVm = globals.fs.path.join('App.framework', 'Resources',
167
        'flutter_assets', 'vm_snapshot_data');
168
    final String precompiledIsolate = globals.fs.path.join('App.framework', 'Resources',
169
        'flutter_assets', 'isolate_snapshot_data');
170
    await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
171

172 173 174
    expect(globals.fs.file(outputKernel).existsSync(), false);
    expect(globals.fs.file(precompiledVm).existsSync(), false);
    expect(globals.fs.file(precompiledIsolate).existsSync(), false);
175 176
  }));

177
  test('release/profile macOS application updates when App.framework updates', () => testbed.run(() async {
178
    globals.fs.file(globals.fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64',
179
        'vm_isolate_snapshot.bin')).createSync(recursive: true);
180
    globals.fs.file(globals.fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64',
181
        'isolate_snapshot.bin')).createSync(recursive: true);
182
    final File inputFramework = globals.fs.file(globals.fs.path.join(environment.buildDir.path, 'App.framework', 'App'))
183 184 185 186
        ..createSync(recursive: true)
        ..writeAsStringSync('ABC');

    await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
187
    final File outputFramework = globals.fs.file(globals.fs.path.join(environment.outputDir.path, 'App.framework', 'App'));
188 189 190 191 192 193 194 195 196

    expect(outputFramework.readAsStringSync(), 'ABC');

    inputFramework.writeAsStringSync('DEF');
    await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');

    expect(outputFramework.readAsStringSync(), 'DEF');
  }));

197 198 199 200 201 202 203 204 205 206
  test('release/profile macOS compilation uses correct gen_snapshot', () => testbed.run(() async {
    when(genSnapshot.run(
      snapshotType: anyNamed('snapshotType'),
      additionalArgs: anyNamed('additionalArgs'),
      darwinArch: anyNamed('darwinArch'),
    )).thenAnswer((Invocation invocation) {
      environment.buildDir.childFile('snapshot_assembly.o').createSync();
      environment.buildDir.childFile('snapshot_assembly.S').createSync();
      return Future<int>.value(0);
    });
207
    when(mockXcode.cc(any)).thenAnswer((Invocation invocation) {
208 209
      return Future<RunResult>.value(RunResult(FakeProcessResult()..exitCode = 0, <String>['test']));
    });
210
    when(mockXcode.clang(any)).thenAnswer((Invocation invocation) {
211 212 213
      return Future<RunResult>.value(RunResult(FakeProcessResult()..exitCode = 0, <String>['test']));
    });
    environment.buildDir.childFile('app.dill').createSync(recursive: true);
214
    globals.fs.file('.packages')
215 216 217 218 219
      ..createSync()
      ..writeAsStringSync('''
# Generated
sky_engine:file:///bin/cache/pkg/sky_engine/lib/
flutter_tools:lib/''');
220
    await const CompileMacOSFramework().build(environment..defines[kBuildMode] = 'release');
221 222
  }, overrides: <Type, Generator>{
    GenSnapshot: () => MockGenSnapshot(),
223
    Xcode: () => mockXcode,
224
  }));
225 226 227
}

class MockPlatform extends Mock implements Platform {}
228
class MockCocoaPods extends Mock implements CocoaPods {}
229
class MockProcessManager extends Mock implements ProcessManager {}
230
class MockGenSnapshot extends Mock implements GenSnapshot {}
231
class MockXcode extends Mock implements Xcode {}
232 233 234 235 236 237 238 239 240 241 242 243 244
class FakeProcessResult implements ProcessResult {
  @override
  int exitCode;

  @override
  int pid = 0;

  @override
  String stderr = '';

  @override
  String stdout = '';
}