From 74f9d3df83545f83338794d1decc28d10a94d290 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 9 Mar 2023 15:42:28 -0800 Subject: [PATCH] Add conformances test that verify that the Client works in Isolates (#889) --- .../example/integration_test/client_test.dart | 2 +- .../cronet_configuration_test.dart | 3 -- pkgs/cronet_http/lib/src/messages.dart | 24 ++++++--- .../client_conformance_test.dart | 2 +- .../test/html/client_conformance_test.dart | 3 +- .../lib/http_client_conformance_tests.dart | 9 +++- .../lib/src/dummy_isolate.dart | 10 ++++ .../lib/src/isolate_test.dart | 51 +++++++++++++++++++ 8 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 pkgs/http_client_conformance_tests/lib/src/dummy_isolate.dart create mode 100644 pkgs/http_client_conformance_tests/lib/src/isolate_test.dart diff --git a/pkgs/cronet_http/example/integration_test/client_test.dart b/pkgs/cronet_http/example/integration_test/client_test.dart index ea85a5837c..4c279d7618 100644 --- a/pkgs/cronet_http/example/integration_test/client_test.dart +++ b/pkgs/cronet_http/example/integration_test/client_test.dart @@ -9,7 +9,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:test/test.dart'; void testClientConformance(CronetClient Function() clientFactory) { - testAll(clientFactory, canStreamRequestBody: false); + testAll(clientFactory, canStreamRequestBody: false, canWorkInIsolates: false); } Future testConformance() async { diff --git a/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart b/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart index b6e7205489..91f85cd797 100644 --- a/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart +++ b/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart @@ -2,9 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -/// Tests various [CronetEngine] configurations. -library; - import 'dart:io'; import 'package:cronet_http/cronet_http.dart'; diff --git a/pkgs/cronet_http/lib/src/messages.dart b/pkgs/cronet_http/lib/src/messages.dart index 8b6c3910f7..47e0a920e9 100644 --- a/pkgs/cronet_http/lib/src/messages.dart +++ b/pkgs/cronet_http/lib/src/messages.dart @@ -298,21 +298,29 @@ class _HttpApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: return CreateEngineRequest.decode(readValue(buffer)!); + case 128: + return CreateEngineRequest.decode(readValue(buffer)!); - case 129: return CreateEngineResponse.decode(readValue(buffer)!); + case 129: + return CreateEngineResponse.decode(readValue(buffer)!); - case 130: return EventMessage.decode(readValue(buffer)!); + case 130: + return EventMessage.decode(readValue(buffer)!); - case 131: return ReadCompleted.decode(readValue(buffer)!); + case 131: + return ReadCompleted.decode(readValue(buffer)!); - case 132: return ResponseStarted.decode(readValue(buffer)!); + case 132: + return ResponseStarted.decode(readValue(buffer)!); - case 133: return StartRequest.decode(readValue(buffer)!); + case 133: + return StartRequest.decode(readValue(buffer)!); - case 134: return StartResponse.decode(readValue(buffer)!); + case 134: + return StartResponse.decode(readValue(buffer)!); - default: return super.readValueOfType(type, buffer); + default: + return super.readValueOfType(type, buffer); } } } diff --git a/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart b/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart index a86243c9dc..28d9bbc891 100644 --- a/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart +++ b/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart @@ -17,6 +17,6 @@ void main() { group('fromSessionConfiguration', () { final config = URLSessionConfiguration.ephemeralSessionConfiguration(); testAll(() => CupertinoClient.fromSessionConfiguration(config), - canStreamRequestBody: false); + canStreamRequestBody: false, canWorkInIsolates: false); }); } diff --git a/pkgs/http/test/html/client_conformance_test.dart b/pkgs/http/test/html/client_conformance_test.dart index 1a94a551c2..b4f567dfa7 100644 --- a/pkgs/http/test/html/client_conformance_test.dart +++ b/pkgs/http/test/html/client_conformance_test.dart @@ -13,5 +13,6 @@ void main() { testAll(BrowserClient.new, redirectAlwaysAllowed: true, canStreamRequestBody: false, - canStreamResponseBody: false); + canStreamResponseBody: false, + canWorkInIsolates: false); } diff --git a/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart b/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart index 4f492fdbde..3500bf8df2 100644 --- a/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart +++ b/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart @@ -6,6 +6,7 @@ import 'package:http/http.dart'; import 'src/close_tests.dart'; import 'src/compressed_response_body_tests.dart'; +import 'src/isolate_test.dart'; import 'src/multiple_clients_tests.dart'; import 'src/redirect_tests.dart'; import 'src/request_body_streamed_tests.dart'; @@ -19,6 +20,7 @@ import 'src/server_errors_test.dart'; export 'src/close_tests.dart' show testClose; export 'src/compressed_response_body_tests.dart' show testCompressedResponseBody; +export 'src/isolate_test.dart' show testIsolate; export 'src/multiple_clients_tests.dart' show testMultipleClients; export 'src/redirect_tests.dart' show testRedirect; export 'src/request_body_streamed_tests.dart' show testRequestBodyStreamed; @@ -42,13 +44,17 @@ export 'src/server_errors_test.dart' show testServerErrors; /// If [redirectAlwaysAllowed] is `true` then tests that require the [Client] /// to limit redirects will be skipped. /// +/// If [canWorkInIsolates] is `false` then tests that require that the [Client] +/// work in Isolates other than the main isolate will be skipped. +/// /// The tests are run against a series of HTTP servers that are started by the /// tests. If the tests are run in the browser, then the test servers are /// started in another process. Otherwise, the test servers are run in-process. void testAll(Client Function() clientFactory, {bool canStreamRequestBody = true, bool canStreamResponseBody = true, - bool redirectAlwaysAllowed = false}) { + bool redirectAlwaysAllowed = false, + bool canWorkInIsolates = true}) { testRequestBody(clientFactory()); testRequestBodyStreamed(clientFactory(), canStreamRequestBody: canStreamRequestBody); @@ -63,4 +69,5 @@ void testAll(Client Function() clientFactory, testCompressedResponseBody(clientFactory()); testMultipleClients(clientFactory); testClose(clientFactory); + testIsolate(clientFactory, canWorkInIsolates: canWorkInIsolates); } diff --git a/pkgs/http_client_conformance_tests/lib/src/dummy_isolate.dart b/pkgs/http_client_conformance_tests/lib/src/dummy_isolate.dart new file mode 100644 index 0000000000..0f592220f1 --- /dev/null +++ b/pkgs/http_client_conformance_tests/lib/src/dummy_isolate.dart @@ -0,0 +1,10 @@ +import 'dart:async'; + +// ignore: avoid_classes_with_only_static_members +/// An Isolate implementation for the web that throws when used. +abstract class Isolate { + static Future run(FutureOr Function() computation, + {String? debugName}) => + throw ArgumentError.value('true', 'canWorkInIsolates', + 'isolate tests are not supported on the web'); +} diff --git a/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart b/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart new file mode 100644 index 0000000000..b4ac8b2393 --- /dev/null +++ b/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. 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:isolate' if (dart.library.html) 'dummy_isolate.dart'; + +import 'package:async/async.dart'; +import 'package:http/http.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; + +import 'request_body_server_vm.dart' + if (dart.library.html) 'request_body_server_web.dart'; + +Future _testPost(Client Function() clientFactory, String host) async { + await Isolate.run( + () => clientFactory().post(Uri.http(host, ''), body: 'Hello World!')); +} + +/// Tests that the [Client] is useable from Isolates other than the main +/// isolate. +/// +/// If [canWorkInIsolates] is `false` then the tests will be skipped. +void testIsolate(Client Function() clientFactory, + {bool canWorkInIsolates = true}) { + group('test isolate', () { + late final String host; + late final StreamChannel httpServerChannel; + late final StreamQueue httpServerQueue; + + setUpAll(() async { + httpServerChannel = await startServer(); + httpServerQueue = StreamQueue(httpServerChannel.stream); + host = 'localhost:${await httpServerQueue.next}'; + }); + tearDownAll(() => httpServerChannel.sink.add(null)); + + test('client.post() with string body', () async { + await _testPost(clientFactory, host); + + final serverReceivedContentType = await httpServerQueue.next; + final serverReceivedBody = await httpServerQueue.next; + + expect(serverReceivedContentType, ['text/plain; charset=utf-8']); + expect(serverReceivedBody, 'Hello World!'); + }); + }, + skip: canWorkInIsolates + ? false + : 'does not work outside of the main isolate'); +}