diff --git a/bazel/kotlin_lib.bzl b/bazel/kotlin_lib.bzl index 2a3de87274..e21a360b37 100644 --- a/bazel/kotlin_lib.bzl +++ b/bazel/kotlin_lib.bzl @@ -46,12 +46,13 @@ def envoy_mobile_kt_library(name, visibility = None, srcs = [], deps = []): # name = "java_jni_lib.jnilib", # native_dep = "libjava_jni_lib.so", # ) -def envoy_mobile_so_to_jni_lib(name, native_dep): +def envoy_mobile_so_to_jni_lib(name, native_dep, testonly = False): lib_name = native_lib_name(native_dep) output = "{}.jnilib".format(lib_name) return native.genrule( name = name, + testonly = testonly, outs = [output], srcs = [native_dep], cmd = """ diff --git a/bazel/kotlin_test.bzl b/bazel/kotlin_test.bzl index edfb79c3cd..3a2f909649 100644 --- a/bazel/kotlin_test.bzl +++ b/bazel/kotlin_test.bzl @@ -74,6 +74,7 @@ def envoy_mobile_android_test(name, srcs, deps = [], native_deps = [], repositor visibility = ["//visibility:public"], data = native_deps, exports = deps, + testonly = True, ) native.android_local_test( diff --git a/test/common/integration/BUILD b/test/common/integration/BUILD index 41a2180478..5dafd395dc 100644 --- a/test/common/integration/BUILD +++ b/test/common/integration/BUILD @@ -1,4 +1,4 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_package") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_cc_test_library", "envoy_package") licenses(["notice"]) # Apache 2 @@ -21,5 +21,35 @@ envoy_cc_test( "@envoy//test/common/http:common_lib", "@envoy//test/integration:http_integration_lib", "@envoy//test/server:utility_lib", + "@envoy//test/test_common:environment_lib", ], ) + +# interface libs for quic test server's jni implementation +envoy_cc_test_library( + name = "quic_test_server_interface_lib", + srcs = [ + "quic_test_server.cc", + "quic_test_server_interface.cc", + ], + hdrs = [ + "quic_test_server.h", + "quic_test_server_interface.h", + ], + data = [ + "@envoy//test/config/integration/certs", + ], + repository = "@envoy", + deps = [ + "@envoy//source/exe:process_wide_lib", + "@envoy//test/config:utility_lib", + "@envoy//test/integration:autonomous_upstream_lib", + "@envoy//test/mocks/server:transport_socket_factory_context_mocks", + "@envoy//test/test_common:environment_lib", + ] + select({ + "@envoy//bazel:disable_signal_trace": [], + "//conditions:default": [ + "@envoy//source/common/signal:sigaction_lib", + ], + }), +) diff --git a/test/common/integration/quic_test_server.cc b/test/common/integration/quic_test_server.cc new file mode 100644 index 0000000000..c145501ffe --- /dev/null +++ b/test/common/integration/quic_test_server.cc @@ -0,0 +1,68 @@ +#include "quic_test_server.h" + +#include "test/test_common/environment.h" + +namespace Envoy { + +Network::TransportSocketFactoryPtr QuicTestServer::createUpstreamTlsContext( + testing::NiceMock& factory_context) { + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; + Extensions::TransportSockets::Tls::ContextManagerImpl context_manager{time_system_}; + const std::string yaml = absl::StrFormat( + R"EOF( +common_tls_context: + alpn_protocols: h3 + tls_certificates: + - certificate_chain: + filename: ../envoy/test/config/integration/certs/upstreamcert.pem + private_key: + filename: ../envoy/test/config/integration/certs/upstreamkey.pem +)EOF"); + TestUtility::loadFromYaml(yaml, tls_context); + envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport quic_config; + quic_config.mutable_downstream_tls_context()->MergeFrom(tls_context); + + std::vector server_names; + auto& config_factory = Config::Utility::getAndCheckFactoryByName< + Server::Configuration::DownstreamTransportSocketConfigFactory>( + "envoy.transport_sockets.quic"); + + return config_factory.createTransportSocketFactory(quic_config, factory_context, server_names); +} + +QuicTestServer::QuicTestServer() + : api_(Api::createApiForTest(stats_store_, time_system_)), + version_(Network::Address::IpVersion::v4), upstream_config_(time_system_), port_(0) { + ON_CALL(factory_context_, api()).WillByDefault(testing::ReturnRef(*api_)); + ON_CALL(factory_context_, scope()).WillByDefault(testing::ReturnRef(stats_store_)); + upstream_config_.udp_fake_upstream_ = FakeUpstreamConfig::UdpConfig(); +} + +void QuicTestServer::startQuicTestServer() { + ASSERT(!upstream_); + // pre-setup: see https://github.com/envoyproxy/envoy/blob/main/test/test_runner.cc + Logger::Context logging_state(spdlog::level::level_enum::err, + "[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v", lock, false, false); + // end pre-setup + + upstream_config_.upstream_protocol_ = Http::CodecType::HTTP3; + + Network::TransportSocketFactoryPtr factory = createUpstreamTlsContext(factory_context_); + + upstream_ = std::make_unique(std::move(factory), port_, version_, + upstream_config_, false); + + // see upstream address + ENVOY_LOG_MISC(debug, "Upstream now listening on {}", upstream_->localAddress()->asString()); +} + +void QuicTestServer::shutdownQuicTestServer() { + ASSERT(upstream_); + upstream_.reset(); +} + +int QuicTestServer::getServerPort() { + ASSERT(upstream_); + return upstream_->localAddress()->ip()->port(); +} +} // namespace Envoy diff --git a/test/common/integration/quic_test_server.h b/test/common/integration/quic_test_server.h new file mode 100644 index 0000000000..261624a68b --- /dev/null +++ b/test/common/integration/quic_test_server.h @@ -0,0 +1,52 @@ +#pragma once + +#include "source/extensions/transport_sockets/tls/ssl_socket.h" + +// test_runner setups +#include "source/exe/process_wide.h" + +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" +#include "test/mocks/server/transport_socket_factory_context.h" +#include "test/integration/autonomous_upstream.h" +#include "test/config/utility.h" + +namespace Envoy { +class QuicTestServer { +private: + testing::NiceMock factory_context_; + Stats::IsolatedStoreImpl stats_store_; + Event::GlobalTimeSystem time_system_; + Api::ApiPtr api_; + Network::Address::IpVersion version_; + std::unique_ptr upstream_; + FakeUpstreamConfig upstream_config_; + int port_; + Thread::SkipAsserts skip_asserts_; + ProcessWide process_wide; + Thread::MutexBasicLockable lock; + + Network::TransportSocketFactoryPtr createUpstreamTlsContext( + testing::NiceMock&); + +public: + QuicTestServer(); + + /** + * Starts the server. Can only have one server active per JVM. This is blocking until the port can + * start accepting requests. + */ + void startQuicTestServer(); + + /** + * Shutdowns the server. Can be restarted later. This is blocking until the server has freed all + * resources. + */ + void shutdownQuicTestServer(); + + /** + * Returns the port that got attributed. Can only be called once the server has been started. + */ + int getServerPort(); +}; + +} // namespace Envoy diff --git a/test/common/integration/quic_test_server_interface.cc b/test/common/integration/quic_test_server_interface.cc new file mode 100644 index 0000000000..4fd695f337 --- /dev/null +++ b/test/common/integration/quic_test_server_interface.cc @@ -0,0 +1,34 @@ +#include "test/common/integration/quic_test_server_interface.h" + +// NOLINT(namespace-envoy) + +static std::shared_ptr strong_quic_test_server_; +static std::weak_ptr weak_quic_test_server_; + +static std::shared_ptr quic_test_server() { + return weak_quic_test_server_.lock(); +} + +void start_server() { + strong_quic_test_server_ = std::make_shared(); + weak_quic_test_server_ = strong_quic_test_server_; + + if (auto e = quic_test_server()) { + e->startQuicTestServer(); + } +} + +void shutdown_server() { + // Reset the primary handle to the test_server, + // but retain it long enough to synchronously shutdown. + auto e = strong_quic_test_server_; + strong_quic_test_server_.reset(); + e->shutdownQuicTestServer(); +} + +int get_server_port() { + if (auto e = quic_test_server()) { + return e->getServerPort(); + } + return -1; // failure +} diff --git a/test/common/integration/quic_test_server_interface.h b/test/common/integration/quic_test_server_interface.h new file mode 100644 index 0000000000..2691caff24 --- /dev/null +++ b/test/common/integration/quic_test_server_interface.h @@ -0,0 +1,30 @@ +#pragma once + +#include "test/common/integration/quic_test_server.h" + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { // functions +#endif + +/** + * Starts the server. Can only have one server active per JVM. This is blocking until the port can + * start accepting requests. + */ +void start_server(); + +/** + * Shutdowns the server. Can be restarted later. This is blocking until the server has freed all + * resources. + */ +void shutdown_server(); + +/** + * Returns the port that got attributed. Can only be called once the server has been started. + */ +int get_server_port(); + +#ifdef __cplusplus +} // functions +#endif diff --git a/test/common/jni/BUILD b/test/common/jni/BUILD new file mode 100644 index 0000000000..3a167db040 --- /dev/null +++ b/test/common/jni/BUILD @@ -0,0 +1,32 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") +load("@rules_cc//cc:defs.bzl", "cc_binary") +load("//bazel:kotlin_lib.bzl", "envoy_mobile_so_to_jni_lib") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +# OS X binary (.jnilib) for Quic Test Server +envoy_mobile_so_to_jni_lib( + name = "libquic_test_server_jni.jnilib", + testonly = True, + native_dep = "libquic_test_server_jni.so", +) + +# Binary for Quic Test Server +cc_binary( + name = "libquic_test_server_jni.so", + testonly = True, + srcs = [ + "quic_test_server_jni_interface.cc", + "//library/common/jni:android_test_jni_interface.cc", + "//library/common/jni:jni_interface.cc", + "@local_jdk//:jni_header", + ], + copts = ["-std=c++17"], + linkshared = True, + deps = [ + "//library/common/jni:base_java_jni_lib", + "//test/common/integration:quic_test_server_interface_lib", + ], +) diff --git a/test/common/jni/quic_test_server_jni_interface.cc b/test/common/jni/quic_test_server_jni_interface.cc new file mode 100644 index 0000000000..f1c5581f2f --- /dev/null +++ b/test/common/jni/quic_test_server_jni_interface.cc @@ -0,0 +1,32 @@ +#include + +#include "test/common/integration/quic_test_server_interface.h" + +#include "library/common/jni/jni_support.h" +#include "library/common/jni/jni_utility.h" +#include "library/common/jni/jni_version.h" + +// NOLINT(namespace-envoy) + +// Quic Test ServerJniLibrary + +extern "C" JNIEXPORT void JNICALL +Java_io_envoyproxy_envoymobile_engine_testing_QuicTestServer_nativeStartQuicTestServer( + JNIEnv* env, jclass clazz) { + jni_log("[QTS]", "starting server"); + start_server(); +} + +extern "C" JNIEXPORT jint JNICALL +Java_io_envoyproxy_envoymobile_engine_testing_QuicTestServer_nativeGetServerPort(JNIEnv* env, + jclass clazz) { + jni_log("[QTS]", "getting server port"); + return get_server_port(); +} + +extern "C" JNIEXPORT void JNICALL +Java_io_envoyproxy_envoymobile_engine_testing_QuicTestServer_nativeShutdownQuicTestServer( + JNIEnv* env, jclass clazz) { + jni_log("[QTS]", "shutting down server"); + shutdown_server(); +} diff --git a/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD b/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD new file mode 100644 index 0000000000..fcdd93891c --- /dev/null +++ b/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD @@ -0,0 +1,35 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") +load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_android_test") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +android_library( + name = "testing", + testonly = True, + srcs = [ + "QuicTestServer.java", + ], + visibility = ["//test:__subpackages__"], +) + +envoy_mobile_android_test( + name = "quic_test_server_test", + srcs = [ + "QuicTestServerTest.java", + ], + exec_properties = { + # TODO(lfpino): Remove this once the sandboxNetwork=off works for ipv4 localhost addresses. + "sandboxNetwork": "standard", + }, + library_path = "test/common/jni", + native_deps = [ + "//test/common/jni:libquic_test_server_jni.so", + "//test/common/jni:libquic_test_server_jni.jnilib", + ], + deps = [ + ":testing", + "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", + ], +) diff --git a/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServer.java b/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServer.java new file mode 100644 index 0000000000..db57b7f28b --- /dev/null +++ b/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServer.java @@ -0,0 +1,55 @@ +package io.envoyproxy.envoymobile.engine.testing; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Wrapper class to start a Quic test server. + */ +public final class QuicTestServer { + + private static final AtomicBoolean sServerRunning = new AtomicBoolean(); + + /* + * Starts the server. Throws an {@link IllegalStateException} if already started. + */ + public static void startQuicTestServer() { + if (!sServerRunning.compareAndSet(false, true)) { + throw new IllegalStateException("Quic server is already running"); + } + nativeStartQuicTestServer(); + } + + /** + * Shutdowns the server. No-op if the server is already shutdown. + */ + public static void shutdownQuicTestServer() { + if (!sServerRunning.compareAndSet(true, false)) { + return; + } + nativeShutdownQuicTestServer(); + } + + public static String getServerURL() { + return "https://" + getServerHost() + ":" + getServerPort() + "/"; + } + + public static String getServerHost() { return "test.example.com"; } + + /** + * Returns the server attributed port. Throws an {@link IllegalStateException} if not started. + */ + public static int getServerPort() { + if (!sServerRunning.get()) { + throw new IllegalStateException("Quic server not started."); + } + return nativeGetServerPort(); + } + + private static native void nativeStartQuicTestServer(); + + private static native void nativeShutdownQuicTestServer(); + + private static native int nativeGetServerPort(); + + private QuicTestServer() {} +} diff --git a/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java b/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java new file mode 100644 index 0000000000..982b4f5a21 --- /dev/null +++ b/test/java/io/envoyproxy/envoymobile/engine/testing/QuicTestServerTest.java @@ -0,0 +1,282 @@ +package io.envoyproxy.envoymobile.engine.testing; + +import static org.assertj.core.api.Assertions.assertThat; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import io.envoyproxy.envoymobile.AndroidEngineBuilder; +import io.envoyproxy.envoymobile.Custom; +import io.envoyproxy.envoymobile.Engine; +import io.envoyproxy.envoymobile.EnvoyError; +import io.envoyproxy.envoymobile.LogLevel; +import io.envoyproxy.envoymobile.RequestHeaders; +import io.envoyproxy.envoymobile.RequestHeadersBuilder; +import io.envoyproxy.envoymobile.RequestMethod; +import io.envoyproxy.envoymobile.ResponseHeaders; +import io.envoyproxy.envoymobile.ResponseTrailers; +import io.envoyproxy.envoymobile.Stream; +import io.envoyproxy.envoymobile.engine.AndroidJniLibrary; +import io.envoyproxy.envoymobile.engine.JniLibrary; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class QuicTestServerTest { + + private static final String HCM_TYPE = + "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; + + private static final String QUIC_UPSTREAM_TYPE = + "type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport"; + + private static final String config = + "static_resources:\n" + + " listeners:\n" + + " - name: base_api_listener\n" + + " address:\n" + + " socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 }\n" + + " api_listener:\n" + + " api_listener:\n" + + " \"@type\": " + HCM_TYPE + "\n" + + " stat_prefix: api_hcm\n" + + " route_config:\n" + + " name: api_router\n" + + " virtual_hosts:\n" + + " - name: api\n" + + " domains: [\"*\"]\n" + + " routes:\n" + + " - match: { prefix: \"/\" }\n" + + " route: { cluster: h3_remote }\n" + + " http_filters:\n" + + " - name: envoy.router\n" + + " typed_config:\n" + + " \"@type\": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router\n" + + " clusters:\n" + + " - name: h3_remote\n" + + " connect_timeout: 10s\n" + + " type: STATIC\n" + + " dns_lookup_family: V4_ONLY\n" + + " lb_policy: ROUND_ROBIN\n" + + " load_assignment:\n" + + " cluster_name: h3_remote\n" + + " endpoints:\n" + + " - lb_endpoints:\n" + + " - endpoint:\n" + + " address:\n" + + " socket_address: { address: 127.0.0.1, port_value: %s }\n" + + " typed_extension_protocol_options:\n" + + " envoy.extensions.upstreams.http.v3.HttpProtocolOptions:\n" + + + " \"@type\": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions\n" + + " explicit_http_config:\n" + + " http3_protocol_options: {}\n" + + " common_http_protocol_options:\n" + + " idle_timeout: 1s\n" + + " transport_socket:\n" + + " name: envoy.transport_sockets.quic\n" + + " typed_config:\n" + + " \"@type\": " + QUIC_UPSTREAM_TYPE + "\n" + + " upstream_tls_context:\n" + + " sni: www.lyft.com"; + + private final Context appContext = ApplicationProvider.getApplicationContext(); + private Engine engine; + + @BeforeClass + public static void loadJniLibrary() { + AndroidJniLibrary.loadTestLibrary(); + JniLibrary.load(); + } + + @Before + public void setUpEngine() throws Exception { + QuicTestServer.startQuicTestServer(); + CountDownLatch latch = new CountDownLatch(1); + engine = new AndroidEngineBuilder( + appContext, new Custom(String.format(config, QuicTestServer.getServerPort()))) + .addLogLevel(LogLevel.OFF) + .setOnEngineRunning(() -> { + latch.countDown(); + return null; + }) + .build(); + latch.await(); // Don't launch a request before initialization has completed. + } + + @After + public void shutdownEngine() { + engine.terminate(); + QuicTestServer.shutdownQuicTestServer(); + } + + @Test + public void get_simple() throws Exception { + QuicTestServerTest.RequestScenario requestScenario = new QuicTestServerTest.RequestScenario() + .setHttpMethod(RequestMethod.GET) + .setUrl(QuicTestServer.getServerURL()); + + QuicTestServerTest.Response response = sendRequest(requestScenario); + + assertThat(response.getHeaders().getHttpStatus()).isEqualTo(200); + assertThat(response.getBodyAsString()).isEqualTo("aaaaaaaaaa"); + assertThat(response.getEnvoyError()).isNull(); + } + + private QuicTestServerTest.Response sendRequest(RequestScenario requestScenario) + throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference response = + new AtomicReference<>(new QuicTestServerTest.Response()); + + Stream stream = engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders((responseHeaders, endStream, ignored) -> { + response.get().setHeaders(responseHeaders); + return null; + }) + .setOnResponseData((data, endStream, ignored) -> { + response.get().addBody(data); + return null; + }) + .setOnResponseTrailers((trailers, ignored) -> { + response.get().setTrailers(trailers); + return null; + }) + .setOnError((error, ignored1, ignored2) -> { + response.get().setEnvoyError(error); + latch.countDown(); + return null; + }) + .setOnCancel((ignored1, ignored2) -> { + response.get().setCancelled(); + latch.countDown(); + return null; + }) + .setOnComplete((ignored1, ignored2) -> { + latch.countDown(); + return null; + }) + .start(Executors.newSingleThreadExecutor()) + .sendHeaders(requestScenario.getHeaders(), !requestScenario.hasBody()); + requestScenario.getBodyChunks().forEach(stream::sendData); + requestScenario.getClosingBodyChunk().ifPresent(stream::close); + + latch.await(); + response.get().throwAssertionErrorIfAny(); + return response.get(); + } + + private static class RequestScenario { + + private URL url; + private RequestMethod method = null; + private final List bodyChunks = new ArrayList<>(); + private final boolean closeBodyStream = false; + + RequestHeaders getHeaders() { + RequestHeadersBuilder requestHeadersBuilder = + new RequestHeadersBuilder(method, url.getProtocol(), url.getAuthority(), url.getPath()); + requestHeadersBuilder.add("no_trailers", "0"); + return requestHeadersBuilder.build(); + } + + List getBodyChunks() { + return closeBodyStream + ? Collections.unmodifiableList(bodyChunks.subList(0, bodyChunks.size() - 1)) + : Collections.unmodifiableList(bodyChunks); + } + + Optional getClosingBodyChunk() { + return closeBodyStream ? Optional.of(bodyChunks.get(bodyChunks.size() - 1)) + : Optional.empty(); + } + + boolean hasBody() { return !bodyChunks.isEmpty(); } + + QuicTestServerTest.RequestScenario setHttpMethod(RequestMethod requestMethod) { + this.method = requestMethod; + return this; + } + + QuicTestServerTest.RequestScenario setUrl(String url) throws MalformedURLException { + this.url = new URL(url); + return this; + } + } + + private static class Response { + + private final AtomicReference headers = new AtomicReference<>(); + private final AtomicReference trailers = new AtomicReference<>(); + private final AtomicReference envoyError = new AtomicReference<>(); + private final List bodies = new ArrayList<>(); + private final AtomicBoolean cancelled = new AtomicBoolean(false); + private final AtomicReference assertionError = new AtomicReference<>(); + + void setHeaders(ResponseHeaders headers) { + if (!this.headers.compareAndSet(null, headers)) { + assertionError.compareAndSet( + null, new AssertionError("setOnResponseHeaders called more than once.")); + } + } + + void addBody(ByteBuffer body) { bodies.add(body); } + + void setTrailers(ResponseTrailers trailers) { + if (!this.trailers.compareAndSet(null, trailers)) { + assertionError.compareAndSet( + null, new AssertionError("setOnResponseTrailers called more than once.")); + } + } + + void setEnvoyError(EnvoyError envoyError) { + if (!this.envoyError.compareAndSet(null, envoyError)) { + assertionError.compareAndSet(null, new AssertionError("setOnError called more than once.")); + } + } + + void setCancelled() { + if (!cancelled.compareAndSet(false, true)) { + assertionError.compareAndSet(null, + new AssertionError("setOnCancel called more than once.")); + } + } + + EnvoyError getEnvoyError() { return envoyError.get(); } + + ResponseHeaders getHeaders() { return headers.get(); } + + String getBodyAsString() { + int totalSize = bodies.stream().mapToInt(ByteBuffer::limit).sum(); + byte[] body = new byte[totalSize]; + int pos = 0; + for (ByteBuffer buffer : bodies) { + int bytesToRead = buffer.limit(); + buffer.get(body, pos, bytesToRead); + pos += bytesToRead; + } + return new String(body); + } + + void throwAssertionErrorIfAny() { + if (assertionError.get() != null) { + throw assertionError.get(); + } + } + } +} diff --git a/test/java/org/chromium/net/testing/BUILD b/test/java/org/chromium/net/testing/BUILD index 6209f0e41d..249d0e76c7 100644 --- a/test/java/org/chromium/net/testing/BUILD +++ b/test/java/org/chromium/net/testing/BUILD @@ -21,6 +21,7 @@ android_library( "NativeTestServer.java", "PathUtils.java", "StrictModeContext.java", + "TestFilesInstaller.java", "TestUploadDataProvider.java", "TestUrlRequestCallback.java", "UrlUtils.java",