Skip to content

Commit 6b3e3f3

Browse files
authored
Use spawnHybrid for the stub server (#700)
Move the in-process stub server from `test/io/utils.dart` to `test/stub_server.dart` as a `hybridMain`. This will allow spawning the server from the browser tests as well. The browser tests still do not work against this server. Separate out the change in how the server is started from the (potential) other changes with fixes. Update tests to use the new pattern - they call `startServer` and wait for the `serverUrl`. No explicit teardown is needed because the `spawnHybrid` call gets an implicit `addTearDown` which kills the isolate. Remove `test/io/utils.dart` entirely. Inline the definition of `throwsSocketException` at the single use site. Make the `message` argument to the other copy of `throwsClientException` optional and use it in place of the version that had been in `test/io/utils.dart`.
1 parent 74a4371 commit 6b3e3f3

File tree

9 files changed

+142
-148
lines changed

9 files changed

+142
-148
lines changed

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ dev_dependencies:
1616
fake_async: ^1.2.0
1717
lints: ^1.0.0
1818
shelf: ^1.1.0
19+
stream_channel: ^2.1.0
1920
test: ^1.16.0

test/io/client_test.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import 'package:http/http.dart' as http;
1010
import 'package:http/io_client.dart' as http_io;
1111
import 'package:test/test.dart';
1212

13-
import 'utils.dart';
13+
import '../utils.dart';
1414

1515
void main() {
16-
setUp(startServer);
17-
18-
tearDown(stopServer);
16+
late Uri serverUrl;
17+
setUpAll(() async {
18+
serverUrl = await startServer();
19+
});
1920

2021
test('#send a StreamedRequest', () async {
2122
var client = http.Client();
@@ -101,7 +102,7 @@ void main() {
101102
request.headers[HttpHeaders.contentTypeHeader] =
102103
'application/json; charset=utf-8';
103104

104-
expect(client.send(request), throwsSocketException);
105+
expect(client.send(request), throwsA(isA<SocketException>()));
105106

106107
request.sink.add('{"hello": "world"}'.codeUnits);
107108
request.sink.close();

test/io/http_test.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import 'package:http/http.dart' as http;
88
import 'package:test/test.dart';
99

10-
import 'utils.dart';
10+
import '../utils.dart';
1111

1212
void main() {
13-
group('http.', () {
14-
setUp(startServer);
15-
16-
tearDown(stopServer);
13+
late Uri serverUrl;
14+
setUpAll(() async {
15+
serverUrl = await startServer();
16+
});
1717

18+
group('http.', () {
1819
test('head', () async {
1920
var response = await http.head(serverUrl);
2021
expect(response.statusCode, equals(200));
@@ -408,7 +409,7 @@ void main() {
408409
});
409410

410411
test('read throws an error for a 4** status code', () {
411-
expect(http.read(serverUrl.resolve('/error')), throwsClientException);
412+
expect(http.read(serverUrl.resolve('/error')), throwsClientException());
412413
});
413414

414415
test('readBytes', () async {
@@ -434,7 +435,7 @@ void main() {
434435

435436
test('readBytes throws an error for a 4** status code', () {
436437
expect(
437-
http.readBytes(serverUrl.resolve('/error')), throwsClientException);
438+
http.readBytes(serverUrl.resolve('/error')), throwsClientException());
438439
});
439440
});
440441
}

test/io/multipart_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'package:http/http.dart' as http;
1010
import 'package:path/path.dart' as path;
1111
import 'package:test/test.dart';
1212

13-
import 'utils.dart';
13+
import '../utils.dart';
1414

1515
void main() {
1616
late Directory tempDir;

test/io/request_test.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
import 'package:http/http.dart' as http;
88
import 'package:test/test.dart';
99

10-
import 'utils.dart';
10+
import '../utils.dart';
1111

1212
void main() {
13-
setUp(startServer);
14-
15-
tearDown(stopServer);
13+
late Uri serverUrl;
14+
setUpAll(() async {
15+
serverUrl = await startServer();
16+
});
1617

1718
test('send happy case', () async {
1819
final request = http.Request('GET', serverUrl)

test/io/streamed_request_test.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import 'dart:convert';
99
import 'package:http/http.dart' as http;
1010
import 'package:test/test.dart';
1111

12-
import 'utils.dart';
12+
import '../utils.dart';
1313

1414
void main() {
15-
setUp(startServer);
16-
17-
tearDown(stopServer);
15+
late Uri serverUrl;
16+
setUpAll(() async {
17+
serverUrl = await startServer();
18+
});
1819

1920
group('contentLength', () {
2021
test('controls the Content-Length header', () async {

test/io/utils.dart

Lines changed: 0 additions & 125 deletions
This file was deleted.

test/stub_server.dart

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
import 'dart:io';
8+
9+
import 'package:http/http.dart';
10+
import 'package:http/src/utils.dart';
11+
import 'package:stream_channel/stream_channel.dart';
12+
13+
void hybridMain(StreamChannel<dynamic> channel) async {
14+
final server = await HttpServer.bind('localhost', 0);
15+
final url = Uri.http('localhost:${server.port}', '');
16+
server.listen((request) async {
17+
var path = request.uri.path;
18+
var response = request.response;
19+
20+
if (path == '/error') {
21+
response
22+
..statusCode = 400
23+
..contentLength = 0;
24+
unawaited(response.close());
25+
return;
26+
}
27+
28+
if (path == '/loop') {
29+
var n = int.parse(request.uri.query);
30+
response
31+
..statusCode = 302
32+
..headers.set('location', url.resolve('/loop?${n + 1}').toString())
33+
..contentLength = 0;
34+
unawaited(response.close());
35+
return;
36+
}
37+
38+
if (path == '/redirect') {
39+
response
40+
..statusCode = 302
41+
..headers.set('location', url.resolve('/').toString())
42+
..contentLength = 0;
43+
unawaited(response.close());
44+
return;
45+
}
46+
47+
if (path == '/no-content-length') {
48+
response
49+
..statusCode = 200
50+
..contentLength = -1
51+
..write('body');
52+
unawaited(response.close());
53+
return;
54+
}
55+
56+
var requestBodyBytes = await ByteStream(request).toBytes();
57+
var encodingName = request.uri.queryParameters['response-encoding'];
58+
var outputEncoding =
59+
encodingName == null ? ascii : requiredEncodingForCharset(encodingName);
60+
61+
response.headers.contentType =
62+
ContentType('application', 'json', charset: outputEncoding.name);
63+
response.headers.set('single', 'value');
64+
65+
dynamic requestBody;
66+
if (requestBodyBytes.isEmpty) {
67+
requestBody = null;
68+
} else if (request.headers.contentType?.charset != null) {
69+
var encoding =
70+
requiredEncodingForCharset(request.headers.contentType!.charset!);
71+
requestBody = encoding.decode(requestBodyBytes);
72+
} else {
73+
requestBody = requestBodyBytes;
74+
}
75+
76+
final headers = <String, List<String>>{};
77+
78+
request.headers.forEach((name, values) {
79+
// These headers are automatically generated by dart:io, so we don't
80+
// want to test them here.
81+
if (name == 'cookie' || name == 'host') return;
82+
83+
headers[name] = values;
84+
});
85+
86+
var content = <String, dynamic>{
87+
'method': request.method,
88+
'path': request.uri.path,
89+
if (requestBody != null) 'body': requestBody,
90+
'headers': headers,
91+
};
92+
93+
var body = json.encode(content);
94+
response
95+
..contentLength = body.length
96+
..write(body);
97+
unawaited(response.close());
98+
});
99+
channel.sink.add(server.port);
100+
}

test/utils.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,19 @@ class _BodyMatches extends Matcher {
110110
/// [http.ClientException] with the given [message].
111111
///
112112
/// [message] can be a String or a [Matcher].
113-
Matcher throwsClientException(String message) => throwsA(
114-
isA<http.ClientException>().having((e) => e.message, 'message', message));
113+
Matcher throwsClientException([String? message]) {
114+
var exception = isA<http.ClientException>();
115+
if (message != null) {
116+
exception = exception.having((e) => e.message, 'message', message);
117+
}
118+
return throwsA(exception);
119+
}
120+
121+
/// Spawn an isolate in the test runner with an http server.
122+
///
123+
/// The server isolate will be killed on teardown.
124+
Future<Uri> startServer() async {
125+
final channel = spawnHybridUri(Uri(path: '/test/stub_server.dart'));
126+
final port = await channel.stream.first as int;
127+
return Uri.http('localhost:$port', '');
128+
}

0 commit comments

Comments
 (0)