diff --git a/pkgs/cronet_http/example/integration_test/stress.dart b/pkgs/cronet_http/example/integration_test/stress.dart new file mode 100644 index 0000000000..09b7a67c85 --- /dev/null +++ b/pkgs/cronet_http/example/integration_test/stress.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2025, 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 'package:cronet_http/cronet_http.dart'; +import 'package:http_client_conformance_tests/http_client_conformance_tests.dart'; +import 'package:integration_test/integration_test.dart'; + +/// Stress tests not run as part of `flutter test integration_test`. +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + stressTestConcurrentRequests(CronetClient.defaultCronetEngine()); +} diff --git a/pkgs/cupertino_http/example/integration_test/main.dart b/pkgs/cupertino_http/example/integration_test/main.dart index 14a7ba9bcf..35c00d5360 100644 --- a/pkgs/cupertino_http/example/integration_test/main.dart +++ b/pkgs/cupertino_http/example/integration_test/main.dart @@ -24,6 +24,8 @@ import 'web_socket_conformance_test.dart' as web_socket_conformance_test; /// This is faster than running each test individually using /// `flutter test integration_test/` because only one compilation step and /// application launch is required. +/// +/// Does **not** run the stress tests. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); diff --git a/pkgs/cupertino_http/example/integration_test/stress.dart b/pkgs/cupertino_http/example/integration_test/stress.dart new file mode 100644 index 0000000000..2031655c17 --- /dev/null +++ b/pkgs/cupertino_http/example/integration_test/stress.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2025, 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 'package:cupertino_http/cupertino_http.dart'; +import 'package:http_client_conformance_tests/http_client_conformance_tests.dart'; +import 'package:integration_test/integration_test.dart'; + +/// Stress tests not run as part of `main.dart` or +/// `flutter test integration_test`. +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + stressTestConcurrentRequests(CupertinoClient.defaultSessionConfiguration()); +} 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 0686c9d09c..a37f838a41 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 @@ -4,23 +4,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/multipart_tests.dart'; -import 'src/multiple_clients_tests.dart'; -import 'src/redirect_tests.dart'; -import 'src/request_body_streamed_tests.dart'; -import 'src/request_body_tests.dart'; -import 'src/request_cookies_test.dart'; -import 'src/request_headers_tests.dart'; -import 'src/request_methods_tests.dart'; -import 'src/response_body_streamed_test.dart'; -import 'src/response_body_tests.dart'; -import 'src/response_cookies_test.dart'; -import 'src/response_headers_tests.dart'; -import 'src/response_status_line_tests.dart'; -import 'src/server_errors_test.dart'; +import 'http_client_conformance_tests.dart'; export 'src/close_tests.dart' show testClose; export 'src/compressed_response_body_tests.dart' @@ -40,6 +24,7 @@ export 'src/response_cookies_test.dart' show testResponseCookies; export 'src/response_headers_tests.dart' show testResponseHeaders; export 'src/response_status_line_tests.dart' show testResponseStatusLine; export 'src/server_errors_test.dart' show testServerErrors; +export 'src/stress_test.dart' show stressTestConcurrentRequests; /// Runs the entire test suite against the given [Client]. /// diff --git a/pkgs/http_client_conformance_tests/lib/src/stress_test.dart b/pkgs/http_client_conformance_tests/lib/src/stress_test.dart new file mode 100644 index 0000000000..80632211cc --- /dev/null +++ b/pkgs/http_client_conformance_tests/lib/src/stress_test.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2025, 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:async'; + +import 'package:async/async.dart'; +import 'package:http/http.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:test/test.dart'; + +import 'response_body_server_vm.dart' + if (dart.library.js_interop) 'response_body_server_web.dart'; + +/// Test that the [Client] can perform many concurrent HTTP requests without +/// error. +/// +/// `concurrentRequests` controls the number of requests that will be made +/// simultaneously. If set too large, this may overload the HTTP server. +/// +/// NOTE: These tests are not run by `testAll`. +void stressTestConcurrentRequests(Client client, + {int numRequests = 100000, int concurrentRequests = 10}) async { + group('stress concurrent requests', () { + 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.nextAsInt}'; + }); + tearDownAll(() => httpServerChannel.sink.add(null)); + + test('small response', () async { + var requestCount = 0; + var completeCount = 0; + final c = Completer(); + + void request() { + client.get(Uri.http(host, '/$requestCount')).then((response) { + expect(response.statusCode, 200); + ++completeCount; + if (requestCount < numRequests) { + ++requestCount; + request(); + } + if (completeCount == numRequests) { + c.complete(); + } + }, + onError: (Object e, _) => + c.completeError(e, StackTrace.empty)).ignore(); + } + + for (requestCount = 0; + requestCount < concurrentRequests; + ++requestCount) { + request(); + } + await c.future; + }); + }); +}