devfs_web_test.dart 22.1 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 'dart:async';
6 7
import 'dart:io';

8 9 10 11 12
import 'package:dwds/data/build_result.dart';
import 'package:dwds/dwds.dart';
import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/readers/asset_reader.dart';
import 'package:dwds/src/services/expression_compiler.dart';
13 14
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
15
import 'package:flutter_tools/src/base/platform.dart';
16
import 'package:flutter_tools/src/build_info.dart';
17
import 'package:flutter_tools/src/build_runner/devfs_web.dart';
18
import 'package:flutter_tools/src/compile.dart';
19
import 'package:flutter_tools/src/convert.dart';
20
import 'package:flutter_tools/src/globals.dart' as globals;
21
import 'package:logging/logging.dart';
22
import 'package:mockito/mockito.dart';
23
import 'package:package_config/package_config.dart';
24
import 'package:shelf/shelf.dart';
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

import '../../src/common.dart';
import '../../src/testbed.dart';

const List<int> kTransparentImage = <int>[
  0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49,
  0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
  0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44,
  0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D,
  0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
];

void main() {
  Testbed testbed;
  WebAssetServer webAssetServer;
40
  Platform linux;
41
  PackageConfig packages;
42 43
  Platform windows;
  MockHttpServer mockHttpServer;
44 45

  setUpAll(() async {
46
    packages = await loadPackageConfigUri(Uri.base.resolve('.packages'));
47
  });
48 49

  setUp(() {
50 51 52
    mockHttpServer = MockHttpServer();
    linux = FakePlatform(operatingSystem: 'linux', environment: <String, String>{});
    windows = FakePlatform(operatingSystem: 'windows', environment: <String, String>{});
53
    testbed = Testbed(setup: () {
54 55 56 57 58 59 60
      webAssetServer = WebAssetServer(
        mockHttpServer,
        packages,
        InternetAddress.loopbackIPv4,
        null,
        null,
      );
61 62 63 64
    });
  });

  test('Handles against malformed manifest', () => testbed.run(() async {
65
    final File source = globals.fs.file('source')
66
      ..writeAsStringSync('main() {}');
67
    final File sourcemap = globals.fs.file('sourcemap')
68
      ..writeAsStringSync('{}');
69 70

    // Missing ending offset.
71
    final File manifestMissingOffset = globals.fs.file('manifestA')
72 73 74 75
      ..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
        'code': <int>[0],
        'sourcemap': <int>[0],
      }}));
76
    final File manifestOutOfBounds = globals.fs.file('manifest')
77 78 79 80
      ..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
        'code': <int>[0, 100],
        'sourcemap': <int>[0],
      }}));
81

82 83
    expect(webAssetServer.write(source, manifestMissingOffset, sourcemap), isEmpty);
    expect(webAssetServer.write(source, manifestOutOfBounds, sourcemap), isEmpty);
84 85 86
  }));

  test('serves JavaScript files from in memory cache', () => testbed.run(() async {
87
    final File source = globals.fs.file('source')
88
      ..writeAsStringSync('main() {}');
89
    final File sourcemap = globals.fs.file('sourcemap')
90
      ..writeAsStringSync('{}');
91
    final File manifest = globals.fs.file('manifest')
92 93 94 95 96
      ..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
        'code': <int>[0, source.lengthSync()],
        'sourcemap': <int>[0, 2],
      }}));
    webAssetServer.write(source, manifest, sourcemap);
97

98 99
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.js')));
100

101
    expect(response.headers, allOf(<Matcher>[
102 103 104
      containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
      containsPair(HttpHeaders.contentTypeHeader, 'application/javascript'),
      containsPair(HttpHeaders.etagHeader, isNotNull)
105 106
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
107 108 109 110
  }, overrides: <Type, Generator>{
    Platform: () => linux,
  }));

111
  test('serves JavaScript files from in memory cache not from manifest', () => testbed.run(() async {
112
    webAssetServer.writeFile('foo.js', 'main() {}');
113

114 115
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.js')));
116

117
    expect(response.headers, allOf(<Matcher>[
118 119 120 121
      containsPair(HttpHeaders.contentLengthHeader, '9'),
      containsPair(HttpHeaders.contentTypeHeader, 'application/javascript'),
      containsPair(HttpHeaders.etagHeader, isNotNull),
      containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
122 123
    ]));
    expect((await response.read().toList()).first, utf8.encode('main() {}'));
124 125
  }));

126
  test('Returns notModified when the ifNoneMatch header matches the etag', () => testbed.run(() async {
127
    webAssetServer.writeFile('foo.js', 'main() {}');
128 129 130 131 132 133 134 135 136 137 138 139 140 141

    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.js')));
    final String etag = response.headers[HttpHeaders.etagHeader];

    final Response cachedResponse = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.js'), headers: <String, String>{
        HttpHeaders.ifNoneMatchHeader: etag
      }));

    expect(cachedResponse.statusCode, HttpStatus.notModified);
    expect(await cachedResponse.read().toList(), isEmpty);
  }));

142
  test('handles missing JavaScript files from in memory cache', () => testbed.run(() async {
143
    final File source = globals.fs.file('source')
144
      ..writeAsStringSync('main() {}');
145
    final File sourcemap = globals.fs.file('sourcemap')
146
      ..writeAsStringSync('{}');
147
    final File manifest = globals.fs.file('manifest')
148 149 150 151 152
      ..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
        'code': <int>[0, source.lengthSync()],
        'sourcemap': <int>[0, 2],
      }}));
    webAssetServer.write(source, manifest, sourcemap);
153

154 155
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/bar.js')));
156

157
    expect(response.statusCode, HttpStatus.notFound);
158 159
  }));

160 161 162 163 164 165 166 167 168
  test('serves default index.html', () => testbed.run(() async {
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/')));

    expect(response.statusCode, HttpStatus.ok);
    expect((await response.read().toList()).first,
      containsAllInOrder(utf8.encode('<html>')));
  }));

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
  test('handles web server paths without .lib extension', () => testbed.run(() async {
    final File source = globals.fs.file('source')
      ..writeAsStringSync('main() {}');
    final File sourcemap = globals.fs.file('sourcemap')
      ..writeAsStringSync('{}');
    final File manifest = globals.fs.file('manifest')
      ..writeAsStringSync(json.encode(<String, Object>{'/foo.dart.lib.js': <String, Object>{
        'code': <int>[0, source.lengthSync()],
        'sourcemap': <int>[0, 2],
      }}));
    webAssetServer.write(source, manifest, sourcemap);

    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart.js')));

    expect(response.statusCode, HttpStatus.ok);
  }));

187 188 189 190 191 192
  test('serves JavaScript files from in memory cache on Windows', () => testbed.run(() async {
    final File source = globals.fs.file('source')
      ..writeAsStringSync('main() {}');
    final File sourcemap = globals.fs.file('sourcemap')
      ..writeAsStringSync('{}');
    final File manifest = globals.fs.file('manifest')
193
      ..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
194 195 196 197
        'code': <int>[0, source.lengthSync()],
        'sourcemap': <int>[0, 2],
      }}));
    webAssetServer.write(source, manifest, sourcemap);
198 199 200 201
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://localhost/foo.js')));

    expect(response.headers, allOf(<Matcher>[
202 203 204 205
      containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
      containsPair(HttpHeaders.contentTypeHeader, 'application/javascript'),
      containsPair(HttpHeaders.etagHeader, isNotNull),
      containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
206 207
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
208 209 210 211
  }, overrides: <Type, Generator>{
    Platform: () => windows,
  }));

212 213 214 215 216 217 218 219
   test('serves asset files from in filesystem with url-encoded paths', () => testbed.run(() async {
    final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', Uri.encodeFull('abcd象形字.png')))
      ..createSync(recursive: true)
      ..writeAsBytesSync(kTransparentImage);
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/abcd%25E8%25B1%25A1%25E5%25BD%25A2%25E5%25AD%2597.png')));

    expect(response.headers, allOf(<Matcher>[
220 221 222 223
      containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
      containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
      containsPair(HttpHeaders.etagHeader, isNotNull),
      containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
224 225 226
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
  }));
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
  test('serves files from web directory', () => testbed.run(() async {
    final File source = globals.fs.file(globals.fs.path.join('web', 'foo.png'))
      ..createSync(recursive: true)
      ..writeAsBytesSync(kTransparentImage);
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.png')));

    expect(response.headers, allOf(<Matcher>[
      containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
      containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
      containsPair(HttpHeaders.etagHeader, isNotNull),
      containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
  }));
242

243 244 245 246
   test('serves asset files from in filesystem with known mime type on Windows', () => testbed.run(() async {
    final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo.png'))
      ..createSync(recursive: true)
      ..writeAsBytesSync(kTransparentImage);
247 248 249 250
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo.png')));

    expect(response.headers, allOf(<Matcher>[
251 252 253 254
      containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
      containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
      containsPair(HttpHeaders.etagHeader, isNotNull),
      containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
255 256
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
257 258 259 260
  }, overrides: <Type,  Generator>{
    Platform: () => windows,
  }));

261
  test('serves Dart files from in filesystem on Linux/macOS', () => testbed.run(() async {
262
    final File source = globals.fs.file('foo.dart').absolute
263 264 265
      ..createSync(recursive: true)
      ..writeAsStringSync('void main() {}');

266 267
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart')));
268

269
    expect(response.headers, containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()));
270
    expect((await response.read().toList()).first, source.readAsBytesSync());
271 272 273 274 275
  }, overrides: <Type,  Generator>{
    Platform: () => linux,
  }));

  test('Handles missing Dart files from filesystem', () => testbed.run(() async {
276 277
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart')));
278

279
    expect(response.statusCode, HttpStatus.notFound);
280 281 282
  }));

  test('serves asset files from in filesystem with known mime type', () => testbed.run(() async {
283
    final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo.png'))
284 285 286
      ..createSync(recursive: true)
      ..writeAsBytesSync(kTransparentImage);

287 288
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo.png')));
289

290
    expect(response.headers, allOf(<Matcher>[
291 292
      containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
      containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
293 294
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
295 296 297
  }));

  test('serves asset files files from in filesystem with unknown mime type and length > 12', () => testbed.run(() async {
298
    final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo'))
299 300 301
      ..createSync(recursive: true)
      ..writeAsBytesSync(List<int>.filled(100, 0));

302 303
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo')));
304

305
    expect(response.headers, allOf(<Matcher>[
306 307
      containsPair(HttpHeaders.contentLengthHeader, '100'),
      containsPair(HttpHeaders.contentTypeHeader, 'application/octet-stream'),
308 309
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
310 311 312
  }));

  test('serves asset files files from in filesystem with unknown mime type and length < 12', () => testbed.run(() async {
313
    final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo'))
314 315 316
      ..createSync(recursive: true)
      ..writeAsBytesSync(<int>[1, 2, 3]);

317 318
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo')));
319

320
    expect(response.headers, allOf(<Matcher>[
321 322
      containsPair(HttpHeaders.contentLengthHeader, '3'),
      containsPair(HttpHeaders.contentTypeHeader, 'application/octet-stream'),
323 324
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
325 326
  }));

327 328 329 330 331 332 333 334 335 336 337 338
  test('serves valid etag header for asset files with non-ascii chracters', () => testbed.run(() async {
    globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'fooπ'))
      ..createSync(recursive: true)
      ..writeAsBytesSync(<int>[1, 2, 3]);

    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/fooπ')));
    final String etag = response.headers[HttpHeaders.etagHeader];

    expect(etag.runes, everyElement(predicate((int char) => char < 255)));
  }));

339
  test('handles serving missing asset file', () => testbed.run(() async {
340 341
    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo')));
342

343
    expect(response.statusCode, HttpStatus.notFound);
344 345
  }));

346 347 348 349 350 351 352
  test('serves /packages/<package>/<path> files as if they were '
       'package:<package>/<path> uris', () => testbed.run(() async {
    final Uri expectedUri = packages.resolve(
        Uri.parse('package:flutter_tools/foo.dart'));
    final File source = globals.fs.file(globals.fs.path.fromUri(expectedUri))
      ..createSync(recursive: true)
      ..writeAsBytesSync(<int>[1, 2, 3]);
353 354 355 356 357

    final Response response = await webAssetServer
      .handleRequest(Request('GET', Uri.parse('http:///packages/flutter_tools/foo.dart')));

    expect(response.headers, allOf(<Matcher>[
358 359
      containsPair(HttpHeaders.contentLengthHeader, '3'),
      containsPair(HttpHeaders.contentTypeHeader, 'application/octet-stream'),
360 361
    ]));
    expect((await response.read().toList()).first, source.readAsBytesSync());
362 363
  }));

364 365 366 367 368
  test('calling dispose closes the http server', () => testbed.run(() async {
    await webAssetServer.dispose();

    verify(mockHttpServer.close()).called(1);
  }));
369 370

  test('Can start web server with specified assets', () => testbed.run(() async {
371
    globals.fs.file('.packages').writeAsStringSync('\n');
372 373 374 375 376 377 378 379 380 381 382 383
    final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      ..createSync(recursive: true);
    outputFile.parent.childFile('a.sources').writeAsStringSync('');
    outputFile.parent.childFile('a.json').writeAsStringSync('{}');
    outputFile.parent.childFile('a.map').writeAsStringSync('{}');
    outputFile.parent.childFile('.packages').writeAsStringSync('\n');

    final ResidentCompiler residentCompiler = MockResidentCompiler();
    when(residentCompiler.recompile(
      any,
      any,
      outputPath: anyNamed('outputPath'),
384
      packageConfig: anyNamed('packageConfig'),
385 386 387 388 389 390 391 392 393
    )).thenAnswer((Invocation invocation) async {
      return const CompilerOutput('a', 0, <Uri>[]);
    });

    final WebDevFS webDevFS = WebDevFS(
      hostname: 'localhost',
      port: 0,
      packagesFilePath: '.packages',
      urlTunneller: null,
394
      useSseForDebugProxy: true,
395 396 397 398
      buildMode: BuildMode.debug,
      enableDwds: false,
      entrypoint: Uri.base,
      testMode: true,
399
      expressionCompiler: null,
400
      chromiumLauncher: null,
401 402 403 404
    );
    webDevFS.requireJS.createSync(recursive: true);
    webDevFS.stackTraceMapper.createSync(recursive: true);

405
    final Uri uri = await webDevFS.create();
406 407 408 409 410 411
    webDevFS.webAssetServer.entrypointCacheDirectory = globals.fs.currentDirectory;
    globals.fs.currentDirectory
      .childDirectory('lib')
      .childFile('web_entrypoint.dart')
      ..createSync(recursive: true)
      ..writeAsStringSync('GENERATED');
412 413 414
    webDevFS.webAssetServer.dartSdk
      ..createSync(recursive: true)
      ..writeAsStringSync('HELLO');
415 416 417 418 419 420 421 422 423
    webDevFS.webAssetServer.dartSdkSourcemap
      ..createSync(recursive: true)
      ..writeAsStringSync('THERE');
    webDevFS.webAssetServer.canvasKitDartSdk
      ..createSync(recursive: true)
      ..writeAsStringSync('OL');
    webDevFS.webAssetServer.canvasKitDartSdkSourcemap
      ..createSync(recursive: true)
      ..writeAsStringSync('CHUM');
424 425 426
    webDevFS.webAssetServer.dartSdkSourcemap.createSync(recursive: true);

    await webDevFS.update(
427
      mainUri: globals.fs.file(globals.fs.path.join('lib', 'main.dart')).uri,
428 429 430 431
      generator: residentCompiler,
      trackWidgetCreation: true,
      bundleFirstUpload: true,
      invalidatedFiles: <Uri>[],
432
      packageConfig: PackageConfig.empty,
433 434
    );

435 436 437 438 439 440 441
    expect(webDevFS.webAssetServer.getFile('require.js'), isNotNull);
    expect(webDevFS.webAssetServer.getFile('stack_trace_mapper.js'), isNotNull);
    expect(webDevFS.webAssetServer.getFile('main.dart'), isNotNull);
    expect(webDevFS.webAssetServer.getFile('manifest.json'), isNotNull);
    expect(webDevFS.webAssetServer.getFile('flutter_service_worker.js'), isNotNull);
    expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'HELLO');
    expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'THERE');
442 443 444 445 446

    // Update to the SDK.
    webDevFS.webAssetServer.dartSdk.writeAsStringSync('BELLOW');

    // New SDK should be visible..
447
    expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW');
448

449 450
    // Toggle CanvasKit
    webDevFS.webAssetServer.canvasKitRendering = true;
451 452 453 454
    expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL');
    expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM');

    // Generated entrypoint.
455 456
    expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'),
      contains('GENERATED'));
457

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
    // served on localhost
    expect(uri, Uri.http('localhost:0', ''));

    await webDevFS.destroy();
  }));

  test('Can start web server with hostname any', () => testbed.run(() async {
    globals.fs.file('.packages').writeAsStringSync('\n');
    final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
      ..createSync(recursive: true);
    outputFile.parent.childFile('a.sources').writeAsStringSync('');
    outputFile.parent.childFile('a.json').writeAsStringSync('{}');
    outputFile.parent.childFile('a.map').writeAsStringSync('{}');
    outputFile.parent.childFile('.packages').writeAsStringSync('\n');

    final ResidentCompiler residentCompiler = MockResidentCompiler();
    when(residentCompiler.recompile(
      any,
      any,
      outputPath: anyNamed('outputPath'),
478
      packageConfig: anyNamed('packageConfig'),
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    )).thenAnswer((Invocation invocation) async {
      return const CompilerOutput('a', 0, <Uri>[]);
    });

    final WebDevFS webDevFS = WebDevFS(
      hostname: 'any',
      port: 0,
      packagesFilePath: '.packages',
      urlTunneller: null,
      useSseForDebugProxy: true,
      buildMode: BuildMode.debug,
      enableDwds: false,
      entrypoint: Uri.base,
      testMode: true,
      expressionCompiler: null,
494
      chromiumLauncher: null,
495 496 497 498 499 500 501
    );
    webDevFS.requireJS.createSync(recursive: true);
    webDevFS.stackTraceMapper.createSync(recursive: true);

    final Uri uri = await webDevFS.create();

    expect(uri, Uri.http('localhost:0', ''));
502 503
    await webDevFS.destroy();
  }));
504 505

  test('Launches DWDS with the correct arguments', () => testbed.run(() async {
506
    globals.fs.file('.packages').writeAsStringSync('\n');
507
    final WebAssetServer server = await WebAssetServer.start(
508
      null,
509
      'any',
510 511
      8123,
      (String url) => null,
512
      true,
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
      BuildMode.debug,
      true,
      Uri.file('test.dart'),
      null,
      dwdsLauncher: ({
        AssetReader assetReader,
        Stream<BuildResult> buildResults,
        ConnectionProvider chromeConnection,
        bool enableDebugExtension,
        bool enableDebugging,
        ExpressionCompiler expressionCompiler,
        String hostname,
        LoadStrategy loadStrategy,
        void Function(Level, String) logWriter,
        bool serveDevTools,
        UrlEncoder urlEncoder,
        bool useSseForDebugProxy,
        bool verbose,
      }) async {
        expect(serveDevTools, false);
        expect(verbose, null);
        expect(enableDebugging, true);
        expect(enableDebugExtension, true);
536
        expect(useSseForDebugProxy, true);
537
        expect(hostname, 'any');
538 539 540 541

        return MockDwds();
      });

542
    await server.dispose();
543
  }));
544 545 546
}

class MockHttpServer extends Mock implements HttpServer {}
547
class MockResidentCompiler extends Mock implements ResidentCompiler {}
548
class MockDwds extends Mock implements Dwds {}