mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
331 lines
13 KiB
Dart
331 lines
13 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_tools/src/base/common.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/convert.dart';
|
|
import 'package:flutter_tools/src/web/devfs_web.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:package_config/discovery.dart';
|
|
import 'package:package_config/packages.dart';
|
|
import 'package:platform/platform.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
|
|
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() {
|
|
MockHttpServer mockHttpServer;
|
|
StreamController<HttpRequest> requestController;
|
|
Testbed testbed;
|
|
MockHttpRequest request;
|
|
MockHttpResponse response;
|
|
MockHttpHeaders headers;
|
|
Completer<void> closeCompleter;
|
|
WebAssetServer webAssetServer;
|
|
MockPlatform windows;
|
|
MockPlatform linux;
|
|
Packages packages;
|
|
|
|
setUpAll(() async {
|
|
packages = await loadPackagesFile(Uri.base.resolve('.packages'));
|
|
});
|
|
|
|
setUp(() {
|
|
windows = MockPlatform();
|
|
linux = MockPlatform();
|
|
when(windows.environment).thenReturn(const <String, String>{});
|
|
when(windows.isWindows).thenReturn(true);
|
|
when(linux.isWindows).thenReturn(false);
|
|
when(linux.environment).thenReturn(const <String, String>{});
|
|
testbed = Testbed(setup: () {
|
|
mockHttpServer = MockHttpServer();
|
|
requestController = StreamController<HttpRequest>.broadcast();
|
|
request = MockHttpRequest();
|
|
response = MockHttpResponse();
|
|
headers = MockHttpHeaders();
|
|
closeCompleter = Completer<void>();
|
|
when(mockHttpServer.listen(any, onError: anyNamed('onError'))).thenAnswer((Invocation invocation) {
|
|
final void Function(HttpRequest) callback = invocation.positionalArguments.first as void Function(HttpRequest);
|
|
return requestController.stream.listen(callback);
|
|
});
|
|
when(request.response).thenReturn(response);
|
|
when(response.headers).thenReturn(headers);
|
|
when(response.close()).thenAnswer((Invocation invocation) async {
|
|
closeCompleter.complete();
|
|
});
|
|
webAssetServer = WebAssetServer(
|
|
mockHttpServer, packages, onError: (dynamic error, StackTrace stackTrace) {
|
|
closeCompleter.completeError(error, stackTrace);
|
|
});
|
|
});
|
|
});
|
|
|
|
tearDown(() async {
|
|
await webAssetServer.dispose();
|
|
await requestController.close();
|
|
});
|
|
|
|
test('Throws a tool exit if bind fails with a SocketException', () => testbed.run(() async {
|
|
expect(WebAssetServer.start('hello', 1234), throwsA(isInstanceOf<ToolExit>()));
|
|
}));
|
|
|
|
test('Can catch exceptions through the onError callback', () => testbed.run(() async {
|
|
when(response.close()).thenAnswer((Invocation invocation) {
|
|
throw StateError('Something bad');
|
|
});
|
|
webAssetServer.writeFile('/foo.js', 'main() {}');
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js'));
|
|
requestController.add(request);
|
|
|
|
expect(closeCompleter.future, throwsA(isInstanceOf<StateError>()));
|
|
}));
|
|
|
|
test('Handles against malformed manifest', () => testbed.run(() async {
|
|
final File source = globals.fs.file('source')
|
|
..writeAsStringSync('main() {}');
|
|
final File sourcemap = globals.fs.file('sourcemap')
|
|
..writeAsStringSync('{}');
|
|
|
|
// Missing ending offset.
|
|
final File manifestMissingOffset = globals.fs.file('manifestA')
|
|
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
|
|
'code': <int>[0],
|
|
'sourcemap': <int>[0],
|
|
}}));
|
|
final File manifestOutOfBounds = globals.fs.file('manifest')
|
|
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
|
|
'code': <int>[0, 100],
|
|
'sourcemap': <int>[0],
|
|
}}));
|
|
|
|
expect(webAssetServer.write(source, manifestMissingOffset, sourcemap), isEmpty);
|
|
expect(webAssetServer.write(source, manifestOutOfBounds, sourcemap), isEmpty);
|
|
}));
|
|
|
|
test('serves JavaScript files from in memory cache', () => 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.js': <String, Object>{
|
|
'code': <int>[0, source.lengthSync()],
|
|
'sourcemap': <int>[0, 2],
|
|
}}));
|
|
webAssetServer.write(source, manifest, sourcemap);
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'application/javascript')).called(1);
|
|
verify(response.add(source.readAsBytesSync())).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => linux,
|
|
}));
|
|
|
|
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')
|
|
..writeAsStringSync(json.encode(<String, Object>{'/C:/foo.js': <String, Object>{
|
|
'code': <int>[0, source.lengthSync()],
|
|
'sourcemap': <int>[0, 2],
|
|
}}));
|
|
webAssetServer.write(source, manifest, sourcemap);
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/C:/foo.js'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'application/javascript')).called(1);
|
|
verify(response.add(source.readAsBytesSync())).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windows,
|
|
}));
|
|
|
|
test('serves JavaScript files from in memory cache not from manifest', () => testbed.run(() async {
|
|
webAssetServer.writeFile('/foo.js', 'main() {}');
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', 9)).called(1);
|
|
verify(headers.add('Content-Type', 'application/javascript')).called(1);
|
|
verify(response.add(any)).called(1);
|
|
}));
|
|
|
|
test('handles missing JavaScript files from in memory cache', () => 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.js': <String, Object>{
|
|
'code': <int>[0, source.lengthSync()],
|
|
'sourcemap': <int>[0, 2],
|
|
}}));
|
|
webAssetServer.write(source, manifest, sourcemap);
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/bar.js'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(response.statusCode = 404).called(1);
|
|
}));
|
|
|
|
test('serves Dart files from in filesystem on Windows', () => testbed.run(() async {
|
|
final File source = globals.fs.file('foo.dart').absolute
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('void main() {}');
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/C:/foo.dart'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windows,
|
|
}));
|
|
|
|
test('serves Dart files from in filesystem on Linux/macOS', () => testbed.run(() async {
|
|
final File source = globals.fs.file('foo.dart').absolute
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('void main() {}');
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.dart'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => linux,
|
|
}));
|
|
|
|
test('Handles missing Dart files from filesystem', () => testbed.run(() async {
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/foo.dart'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(response.statusCode = 404).called(1);
|
|
}));
|
|
|
|
test('serves asset files from in filesystem with known mime type', () => testbed.run(() async {
|
|
final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo.png'))
|
|
..createSync(recursive: true)
|
|
..writeAsBytesSync(kTransparentImage);
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo.png'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'image/png')).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}));
|
|
|
|
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);
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo.png'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'image/png')).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windows,
|
|
}));
|
|
|
|
|
|
test('serves asset files files from in filesystem with unknown mime type and length > 12', () => testbed.run(() async {
|
|
final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo'))
|
|
..createSync(recursive: true)
|
|
..writeAsBytesSync(List<int>.filled(100, 0));
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'application/octet-stream')).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}));
|
|
|
|
test('serves asset files files from in filesystem with unknown mime type and length < 12', () => testbed.run(() async {
|
|
final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo'))
|
|
..createSync(recursive: true)
|
|
..writeAsBytesSync(<int>[1, 2, 3]);
|
|
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'application/octet-stream')).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}));
|
|
|
|
test('handles serving missing asset file', () => testbed.run(() async {
|
|
when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(response.statusCode = HttpStatus.notFound).called(1);
|
|
}));
|
|
|
|
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]);
|
|
when(request.uri).thenReturn(
|
|
Uri.parse('http:///packages/flutter_tools/foo.dart'));
|
|
requestController.add(request);
|
|
await closeCompleter.future;
|
|
|
|
verify(headers.add('Content-Length', source.lengthSync())).called(1);
|
|
verify(headers.add('Content-Type', 'application/octet-stream')).called(1);
|
|
verify(response.addStream(any)).called(1);
|
|
}));
|
|
|
|
test('calling dispose closes the http server', () => testbed.run(() async {
|
|
await webAssetServer.dispose();
|
|
|
|
verify(mockHttpServer.close()).called(1);
|
|
}));
|
|
}
|
|
|
|
class MockHttpServer extends Mock implements HttpServer {}
|
|
class MockHttpRequest extends Mock implements HttpRequest {}
|
|
class MockHttpResponse extends Mock implements HttpResponse {}
|
|
class MockHttpHeaders extends Mock implements HttpHeaders {}
|
|
class MockPlatform extends Mock implements Platform {}
|