chrome_test.dart 10.9 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 6
// @dart = 2.8

7 8
import 'dart:async';

9
import 'package:file/memory.dart';
10
import 'package:flutter_tools/src/base/file_system.dart';
11
import 'package:flutter_tools/src/base/logger.dart';
12
import 'package:flutter_tools/src/base/os.dart';
13
import 'package:flutter_tools/src/base/platform.dart';
14
import 'package:flutter_tools/src/web/chrome.dart';
15

16
import '../../src/common.dart';
17 18
import '../../src/context.dart';

19
const List<String> kChromeArgs = <String>[
20 21 22 23 24 25 26 27 28 29 30
  '--disable-background-timer-throttling',
  '--disable-extensions',
  '--disable-popup-blocking',
  '--bwsi',
  '--no-first-run',
  '--no-default-browser-check',
  '--disable-default-apps',
  '--disable-translate',
];

const String kDevtoolsStderr = '\n\nDevTools listening\n\n';
31 32

void main() {
33
  FileExceptionHandler exceptionHandler;
34
  ChromiumLauncher chromeLauncher;
35 36 37 38
  FileSystem fileSystem;
  Platform platform;
  FakeProcessManager processManager;
  OperatingSystemUtils operatingSystemUtils;
39 40

  setUp(() {
41
    exceptionHandler = FileExceptionHandler();
42
    operatingSystemUtils = FakeOperatingSystemUtils();
43 44
    platform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{
      kChromeEnvironment: 'example_chrome',
45
    });
46
    fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle);
47
    processManager = FakeProcessManager.list(<FakeCommand>[]);
48
    chromeLauncher = ChromiumLauncher(
49 50 51 52
      fileSystem: fileSystem,
      platform: platform,
      processManager: processManager,
      operatingSystemUtils: operatingSystemUtils,
53
      browserFinder: findChromeExecutable,
54
      logger: BufferLogger.test(),
55
    );
56 57
  });

58 59
  testWithoutContext('can launch chrome and connect to the devtools', () async {
    expect(
60
      () async => _testLaunchChrome(
61 62 63 64 65 66
        '/.tmp_rand0/flutter_tools_chrome_device.rand0',
        processManager,
        chromeLauncher,
      ),
      returnsNormally,
    );
67 68
  });

69
  testWithoutContext('cannot have two concurrent instances of chrome', () async {
70
    await _testLaunchChrome(
71 72 73 74
      '/.tmp_rand0/flutter_tools_chrome_device.rand0',
      processManager,
      chromeLauncher,
    );
75

76
    expect(
77
      () async => _testLaunchChrome(
78 79 80 81
        '/.tmp_rand0/flutter_tools_chrome_device.rand1',
        processManager,
        chromeLauncher,
      ),
82
      throwsToolExit(message: 'Only one instance of chrome can be started'),
83
    );
84 85
  });

86
  testWithoutContext('can launch new chrome after stopping a previous chrome', () async {
87
    final Chromium chrome = await _testLaunchChrome(
88 89 90 91
      '/.tmp_rand0/flutter_tools_chrome_device.rand0',
      processManager,
      chromeLauncher,
    );
92
    await chrome.close();
93 94

    expect(
95
      () async => _testLaunchChrome(
96 97 98 99 100 101
        '/.tmp_rand0/flutter_tools_chrome_device.rand1',
        processManager,
        chromeLauncher,
      ),
      returnsNormally,
    );
102
  });
103

104 105 106
  testWithoutContext('does not crash if saving profile information fails due to a file system exception.', () async {
    final BufferLogger logger = BufferLogger.test();
    chromeLauncher = ChromiumLauncher(
107
      fileSystem: fileSystem,
108 109 110 111 112 113 114 115 116 117
      platform: platform,
      processManager: processManager,
      operatingSystemUtils: operatingSystemUtils,
      browserFinder: findChromeExecutable,
      logger: logger,
    );
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
118
        '--remote-debugging-port=12345',
119 120 121 122 123 124 125 126 127 128 129 130
        ...kChromeArgs,
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

    final Chromium chrome = await chromeLauncher.launch(
      'example_url',
      skipCheck: true,
      cacheDir: fileSystem.currentDirectory,
    );

131 132 133 134
    // Create cache dir that the Chrome launcher will atttempt to persist, and a file
    // that will thrown an exception when it is read.
    const String directoryPrefix = '/.tmp_rand0/flutter_tools_chrome_device.rand0/Default';
    fileSystem.directory('$directoryPrefix/Local Storage')
135
      .createSync(recursive: true);
136 137 138 139 140 141 142
    final File file = fileSystem.file('$directoryPrefix/Local Storage/foo')
      ..createSync(recursive: true);
    exceptionHandler.addError(
      file,
      FileSystemOp.read,
      const FileSystemException(),
    );
143 144 145 146 147

    await chrome.close(); // does not exit with error.
    expect(logger.errorText, contains('Failed to save Chrome preferences'));
  });

148 149
  testWithoutContext('does not crash if restoring profile information fails due to a file system exception.', () async {
    final BufferLogger logger = BufferLogger.test();
150 151 152 153 154 155 156
    final File file = fileSystem.file('/Default/foo')
      ..createSync(recursive: true);
    exceptionHandler.addError(
      file,
      FileSystemOp.read,
      const FileSystemException(),
    );
157
    chromeLauncher = ChromiumLauncher(
158
      fileSystem: fileSystem,
159 160 161 162 163 164
      platform: platform,
      processManager: processManager,
      operatingSystemUtils: operatingSystemUtils,
      browserFinder: findChromeExecutable,
      logger: logger,
    );
165

166 167 168 169
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
170
        '--remote-debugging-port=12345',
171 172 173 174 175 176 177 178 179 180
        ...kChromeArgs,
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

    fileSystem.currentDirectory.childDirectory('Default').createSync();
    final Chromium chrome = await chromeLauncher.launch(
      'example_url',
      skipCheck: true,
181
      cacheDir: fileSystem.currentDirectory,
182 183 184 185 186 187 188 189 190 191
    );

    // Create cache dir that the Chrome launcher will atttempt to persist.
    fileSystem.directory('/.tmp_rand0/flutter_tools_chrome_device.rand0/Default/Local Storage')
      .createSync(recursive: true);

    await chrome.close(); // does not exit with error.
    expect(logger.errorText, contains('Failed to restore Chrome preferences'));
  });

192
  testWithoutContext('can launch chrome with a custom debug port', () async {
193 194 195
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
196
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
197
        '--remote-debugging-port=10000',
198
        ...kChromeArgs,
199 200 201 202 203
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

204
    expect(
205
      () async => chromeLauncher.launch(
206 207 208 209 210
        'example_url',
        skipCheck: true,
        debugPort: 10000,
      ),
      returnsNormally,
211 212
    );
  });
213

214
  testWithoutContext('can launch chrome headless', () async {
215 216 217
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
218
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
219
        '--remote-debugging-port=12345',
220
        ...kChromeArgs,
221 222 223 224 225 226 227 228 229
        '--headless',
        '--disable-gpu',
        '--no-sandbox',
        '--window-size=2400,1800',
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

230
    expect(
231
      () async => chromeLauncher.launch(
232 233 234 235 236
        'example_url',
        skipCheck: true,
        headless: true,
      ),
      returnsNormally,
237 238
    );
  });
239

240
  testWithoutContext('can seed chrome temp directory with existing session data', () async {
241 242
    final Completer<void> exitCompleter = Completer<void>.sync();
    final Directory dataDir = fileSystem.directory('chrome-stuff');
243 244 245 246 247
    final File preferencesFile = dataDir
      .childDirectory('Default')
      .childFile('preferences');
    preferencesFile
      ..createSync(recursive: true)
248
      ..writeAsStringSync('"exit_type":"Crashed"');
249

250
    final Directory defaultContentDirectory = dataDir
251
        .childDirectory('Default')
252 253
        .childDirectory('Foo');
        defaultContentDirectory.createSync(recursive: true);
254

255 256 257 258
    processManager.addCommand(FakeCommand(
      command: const <String>[
        'example_chrome',
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
259
        '--remote-debugging-port=12345',
260 261 262 263 264 265
        ...kChromeArgs,
        'example_url',
      ],
      completer: exitCompleter,
      stderr: kDevtoolsStderr,
    ));
266 267 268 269

    await chromeLauncher.launch(
      'example_url',
      skipCheck: true,
270
      cacheDir: dataDir,
271 272 273
    );

    exitCompleter.complete();
274
    await Future<void>.delayed(const Duration(milliseconds: 1));
275 276 277

    // writes non-crash back to dart_tool
    expect(preferencesFile.readAsStringSync(), '"exit_type":"Normal"');
278

279 280 281

    // validate any Default content is copied
    final Directory defaultContentDir = fileSystem
282
        .directory('.tmp_rand0/flutter_tools_chrome_device.rand0')
283
        .childDirectory('Default')
284
        .childDirectory('Foo');
285

286
    expect(defaultContentDir.existsSync(), true);
287
  });
288 289 290 291 292

  testWithoutContext('can retry launch when glibc bug happens', () async {
    const List<String> args = <String>[
      'example_chrome',
      '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
293
      '--remote-debugging-port=12345',
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
      ...kChromeArgs,
      '--headless',
      '--disable-gpu',
      '--no-sandbox',
      '--window-size=2400,1800',
      'example_url',
    ];

    // Pretend to hit glibc bug 3 times.
    for (int i = 0; i < 3; i++) {
      processManager.addCommand(const FakeCommand(
        command: args,
        stderr: 'Inconsistency detected by ld.so: ../elf/dl-tls.c: 493: '
                '_dl_allocate_tls_init: Assertion `listp->slotinfo[cnt].gen '
                '<= GL(dl_tls_generation)\' failed!',
      ));
    }

    // Succeed on the 4th try.
    processManager.addCommand(const FakeCommand(
      command: args,
      stderr: kDevtoolsStderr,
    ));

    expect(
319
      () async => chromeLauncher.launch(
320 321 322 323 324 325 326 327 328 329 330 331 332
        'example_url',
        skipCheck: true,
        headless: true,
      ),
      returnsNormally,
    );
  });

  testWithoutContext('gives up retrying when a non-glibc error happens', () async {
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
333
        '--remote-debugging-port=12345',
334 335 336 337 338 339 340 341 342 343 344
        ...kChromeArgs,
        '--headless',
        '--disable-gpu',
        '--no-sandbox',
        '--window-size=2400,1800',
        'example_url',
      ],
      stderr: 'nothing in the std error indicating glibc error',
    ));

    expect(
345
      () async => chromeLauncher.launch(
346 347 348 349 350 351 352
        'example_url',
        skipCheck: true,
        headless: true,
      ),
      throwsToolExit(message: 'Failed to launch browser.'),
    );
  });
353 354
}

355
Future<Chromium> _testLaunchChrome(String userDataDir, FakeProcessManager processManager, ChromiumLauncher chromeLauncher) {
356 357 358 359
  processManager.addCommand(FakeCommand(
    command: <String>[
      'example_chrome',
      '--user-data-dir=$userDataDir',
360
      '--remote-debugging-port=12345',
361
      ...kChromeArgs,
362 363 364 365 366 367 368 369 370 371
      'example_url',
    ],
    stderr: kDevtoolsStderr,
  ));

  return chromeLauncher.launch(
    'example_url',
    skipCheck: true,
  );
}