chrome_test.dart 9.51 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 'dart:async';

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

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

18
const List<String> kChromeArgs = <String>[
19 20 21 22 23 24 25 26 27 28 29
  '--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';
30 31

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

  setUp(() {
39 40
    operatingSystemUtils = MockOperatingSystemUtils();
    when(operatingSystemUtils.findFreePort())
41
        .thenAnswer((Invocation invocation) async {
42 43 44 45
      return 1234;
    });
    platform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{
      kChromeEnvironment: 'example_chrome',
46
    });
47 48
    fileSystem = MemoryFileSystem.test();
    processManager = FakeProcessManager.list(<FakeCommand>[]);
49
    chromeLauncher = ChromiumLauncher(
50 51 52 53
      fileSystem: fileSystem,
      platform: platform,
      processManager: processManager,
      operatingSystemUtils: operatingSystemUtils,
54
      browserFinder: findChromeExecutable,
55
      logger: BufferLogger.test(),
56
    );
57 58
  });

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

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

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

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

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

105 106 107 108 109 110 111 112 113 114 115 116 117 118 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
  testWithoutContext('does not crash if saving profile information fails due to a file system exception.', () async {
    final MockFileSystemUtils fileSystemUtils = MockFileSystemUtils();
    final BufferLogger logger = BufferLogger.test();
    chromeLauncher = ChromiumLauncher(
      fileSystem: fileSystem,
      platform: platform,
      processManager: processManager,
      operatingSystemUtils: operatingSystemUtils,
      browserFinder: findChromeExecutable,
      logger: logger,
      fileSystemUtils: fileSystemUtils,
    );
    when(fileSystemUtils.copyDirectorySync(any, any))
      .thenThrow(const FileSystemException());
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
        '--remote-debugging-port=1234',
        ...kChromeArgs,
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

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

    // 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 save Chrome preferences'));
  });

144
  testWithoutContext('can launch chrome with a custom debug port', () async {
145 146 147
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
148
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
149
        '--remote-debugging-port=10000',
150
        ...kChromeArgs,
151 152 153 154 155
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

156 157 158 159 160 161 162
    expect(
      () async => await chromeLauncher.launch(
        'example_url',
        skipCheck: true,
        debugPort: 10000,
      ),
      returnsNormally,
163 164
    );
  });
165

166
  testWithoutContext('can launch chrome headless', () async {
167 168 169
    processManager.addCommand(const FakeCommand(
      command: <String>[
        'example_chrome',
170
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
171
        '--remote-debugging-port=1234',
172
        ...kChromeArgs,
173 174 175 176 177 178 179 180 181
        '--headless',
        '--disable-gpu',
        '--no-sandbox',
        '--window-size=2400,1800',
        'example_url',
      ],
      stderr: kDevtoolsStderr,
    ));

182 183 184 185 186 187 188
    expect(
      () async => await chromeLauncher.launch(
        'example_url',
        skipCheck: true,
        headless: true,
      ),
      returnsNormally,
189 190
    );
  });
191

192
  testWithoutContext('can seed chrome temp directory with existing session data', () async {
193 194
    final Completer<void> exitCompleter = Completer<void>.sync();
    final Directory dataDir = fileSystem.directory('chrome-stuff');
195

196 197 198 199 200
    final File preferencesFile = dataDir
      .childDirectory('Default')
      .childFile('preferences');
    preferencesFile
      ..createSync(recursive: true)
201
      ..writeAsStringSync('"exit_type":"Crashed"');
202

203
    final Directory defaultContentDirectory = dataDir
204
        .childDirectory('Default')
205 206
        .childDirectory('Foo');
        defaultContentDirectory.createSync(recursive: true);
207

208 209 210 211 212 213 214 215 216 217 218
    processManager.addCommand(FakeCommand(
      command: const <String>[
        'example_chrome',
        '--user-data-dir=/.tmp_rand0/flutter_tools_chrome_device.rand0',
        '--remote-debugging-port=1234',
        ...kChromeArgs,
        'example_url',
      ],
      completer: exitCompleter,
      stderr: kDevtoolsStderr,
    ));
219 220 221 222

    await chromeLauncher.launch(
      'example_url',
      skipCheck: true,
223
      cacheDir: dataDir,
224 225 226
    );

    exitCompleter.complete();
227
    await Future<void>.delayed(const Duration(microseconds: 1));
228 229 230

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

232 233 234

    // validate any Default content is copied
    final Directory defaultContentDir = fileSystem
235
        .directory('.tmp_rand0/flutter_tools_chrome_device.rand0')
236
        .childDirectory('Default')
237
        .childDirectory('Foo');
238

239
    expect(defaultContentDir.existsSync(), true);
240
  });
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 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 304 305

  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',
      '--remote-debugging-port=1234',
      ...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(
      () async => await chromeLauncher.launch(
        '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',
        '--remote-debugging-port=1234',
        ...kChromeArgs,
        '--headless',
        '--disable-gpu',
        '--no-sandbox',
        '--window-size=2400,1800',
        'example_url',
      ],
      stderr: 'nothing in the std error indicating glibc error',
    ));

    expect(
      () async => await chromeLauncher.launch(
        'example_url',
        skipCheck: true,
        headless: true,
      ),
      throwsToolExit(message: 'Failed to launch browser.'),
    );
  });
306 307
}

308
class MockFileSystemUtils extends Mock implements FileSystemUtils {}
309
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
310

311
Future<Chromium> _testLaunchChrome(String userDataDir, FakeProcessManager processManager, ChromiumLauncher chromeLauncher) {
312 313 314 315 316
  processManager.addCommand(FakeCommand(
    command: <String>[
      'example_chrome',
      '--user-data-dir=$userDataDir',
      '--remote-debugging-port=1234',
317
      ...kChromeArgs,
318 319 320 321 322 323 324 325 326 327
      'example_url',
    ],
    stderr: kDevtoolsStderr,
  ));

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