From 51fd192889dadf973f3a34e897617b7a1bfab97f Mon Sep 17 00:00:00 2001 From: Mateus Azis Date: Wed, 2 Jul 2025 11:55:11 -0700 Subject: [PATCH 1/5] move away from mocking servercall --- .../internal/PendingAuthListenerTest.java | 167 ++++++++++++++++-- 1 file changed, 152 insertions(+), 15 deletions(-) diff --git a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java index 9cdf123033b..f477c3857da 100644 --- a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java @@ -1,15 +1,40 @@ package io.grpc.binder.internal; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static java.nio.charset.StandardCharsets.UTF_8; + +import androidx.annotation.Nullable; + +import com.google.common.io.ByteStreams; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ManagedChannel; import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Server; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.ServerServiceDefinition; +import io.grpc.ServiceDescriptor; import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.ClientCalls; +import io.grpc.stub.ServerCalls; +import io.grpc.testing.GrpcCleanupRule; + +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -23,15 +48,26 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; + @RunWith(JUnit4.class) public final class PendingAuthListenerTest { - @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule + public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); - @Mock ServerCallHandler next; - @Mock ServerCall call; - @Mock ServerCall.Listener delegate; - @Captor ArgumentCaptor statusCaptor; + @Mock + ServerCallHandler next; + @Mock + ServerCall call; + @Mock + ServerCall.Listener delegate; private final Metadata headers = new Metadata(); private final PendingAuthListener listener = new PendingAuthListener<>(); @@ -86,16 +122,117 @@ public void onCallbacks_withCancellation_runsPendingCallbacksAfterStartCall() { } @Test - public void whenStartCallFails_closesTheCallWithInternalStatus() { - IllegalStateException exception = new IllegalStateException("oops"); - when(next.startCall(any(), any())).thenThrow(exception); - - listener.onReady(); - listener.startCall(call, headers, next); + public void whenStartCallFails_closesTheCallWithInternalStatus() throws Exception { + // Arrange + String name = "test_server"; + AtomicBoolean closed = new AtomicBoolean(false); + MethodDescriptor method = + MethodDescriptor + .newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE) + .setFullMethodName("test_server/method") + .setType(MethodDescriptor.MethodType.UNARY) + .build(); + ServerCallHandler callHandler = + ServerCalls.asyncUnaryCall((req, respObserver) -> { + throw new IllegalStateException("ooops"); + }); + ServerServiceDefinition serviceDef = + ServerServiceDefinition.builder(name).addMethod(method, callHandler).build(); + Server server = + InProcessServerBuilder.forName(name).addService(serviceDef).intercept( + new ServerInterceptor() { + @SuppressWarnings("unchecked") + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + + listener.startCall((ServerCall) call, headers, (ServerCallHandler) next); + return (ServerCall.Listener) listener; + } + }).build().start(); + ManagedChannel channel = + InProcessChannelBuilder.forName(name).intercept(new ClientInterceptor() { + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + ClientCall delegate = next.newCall(method, callOptions); + return new ClientCall() { + @Override + public void start(Listener responseListener, Metadata headers) { + Listener listenerWrapper = new Listener() { + @Override + public void onHeaders(Metadata headers) { + responseListener.onHeaders(headers); + } + + @Override + public void onMessage(RespT message) { + responseListener.onMessage(message); + } + + @Override + public void onReady() { + responseListener.onReady(); + } + + @Override + public void onClose(Status status, Metadata trailers) { + responseListener.onClose(status, trailers); + closed.set(true); + } + }; + delegate.start(listenerWrapper, headers); + } + + @Override + public void request(int numMessages) { + delegate.request(numMessages); + } + + @Override + public void cancel(@Nullable String message, @Nullable Throwable cause) { + delegate.cancel(message, cause); + } + + @Override + public void halfClose() { + delegate.halfClose(); + } + + @Override + public void sendMessage(ReqT message) { + delegate.sendMessage(message); + } + }; + } + }).build(); + grpcCleanupRule.register(server); + grpcCleanupRule.register(channel); + + // Act + assertThrows(StatusRuntimeException.class, () -> ClientCalls.blockingUnaryCall(channel, + method, CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), "foo")); + + // Assert + assertThat(closed.get()).isTrue(); + } - verify(call).close(statusCaptor.capture(), any()); - Status status = statusCaptor.getValue(); - assertThat(status.getCode()).isEqualTo(Status.Code.INTERNAL); - assertThat(status.getCause()).isSameInstanceAs(exception); + private static class StringMarshaller implements MethodDescriptor.Marshaller { + public static final StringMarshaller INSTANCE = new StringMarshaller(); + + @Override + public InputStream stream(String value) { + return new ByteArrayInputStream(value.getBytes(UTF_8)); + } + + @Override + public String parse(InputStream stream) { + try { + return new String(ByteStreams.toByteArray(stream), UTF_8); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } } } From bbd5983d142458ec31d521b2ea0c5fec59e92d1e Mon Sep 17 00:00:00 2001 From: Mateus Azis Date: Tue, 15 Jul 2025 17:49:50 -0700 Subject: [PATCH 2/5] use SimpleForwardingClientCallListener --- .../internal/PendingAuthListenerTest.java | 58 +++++-------------- 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java index f477c3857da..df9fb92af1b 100644 --- a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java @@ -17,6 +17,8 @@ import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.MethodDescriptor; @@ -157,52 +159,19 @@ public ServerCall.Listener interceptCall( public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { ClientCall delegate = next.newCall(method, callOptions); - return new ClientCall() { + return new ForwardingClientCall.SimpleForwardingClientCall(delegate){ @Override public void start(Listener responseListener, Metadata headers) { - Listener listenerWrapper = new Listener() { - @Override - public void onHeaders(Metadata headers) { - responseListener.onHeaders(headers); - } - - @Override - public void onMessage(RespT message) { - responseListener.onMessage(message); - } - - @Override - public void onReady() { - responseListener.onReady(); - } - - @Override - public void onClose(Status status, Metadata trailers) { - responseListener.onClose(status, trailers); - closed.set(true); - } - }; - delegate.start(listenerWrapper, headers); - } - - @Override - public void request(int numMessages) { - delegate.request(numMessages); - } - - @Override - public void cancel(@Nullable String message, @Nullable Throwable cause) { - delegate.cancel(message, cause); - } - - @Override - public void halfClose() { - delegate.halfClose(); - } - - @Override - public void sendMessage(ReqT message) { - delegate.sendMessage(message); + ClientCall.Listener wrappedListener = + new ForwardingClientCallListener.SimpleForwardingClientCallListener( + responseListener){ + @Override + public void onClose(Status status, Metadata trailers) { + super.onClose(status, trailers); + closed.set(true); + } + }; + super.start(wrappedListener, headers); } }; } @@ -218,6 +187,7 @@ public void sendMessage(ReqT message) { assertThat(closed.get()).isTrue(); } + private static class StringMarshaller implements MethodDescriptor.Marshaller { public static final StringMarshaller INSTANCE = new StringMarshaller(); From 29dd39be857d9bae310b244841cd96cdb1f82ce8 Mon Sep 17 00:00:00 2001 From: Mateus Azis Date: Tue, 15 Jul 2025 17:55:07 -0700 Subject: [PATCH 3/5] use voidMethod methoddescriptor --- .../internal/PendingAuthListenerTest.java | 33 +++---------------- .../grpc/testing/TestMethodDescriptors.java | 7 ++-- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java index df9fb92af1b..167d0889ea4 100644 --- a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java @@ -35,6 +35,7 @@ import io.grpc.stub.ClientCalls; import io.grpc.stub.ServerCalls; import io.grpc.testing.GrpcCleanupRule; +import io.grpc.testing.TestMethodDescriptors; import org.junit.Assert; import org.junit.Before; @@ -126,15 +127,10 @@ public void onCallbacks_withCancellation_runsPendingCallbacksAfterStartCall() { @Test public void whenStartCallFails_closesTheCallWithInternalStatus() throws Exception { // Arrange - String name = "test_server"; + String name = TestMethodDescriptors.SERVICE_NAME; AtomicBoolean closed = new AtomicBoolean(false); - MethodDescriptor method = - MethodDescriptor - .newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE) - .setFullMethodName("test_server/method") - .setType(MethodDescriptor.MethodType.UNARY) - .build(); - ServerCallHandler callHandler = + MethodDescriptor method = TestMethodDescriptors.voidMethod(); + ServerCallHandler callHandler = ServerCalls.asyncUnaryCall((req, respObserver) -> { throw new IllegalStateException("ooops"); }); @@ -181,28 +177,9 @@ public void onClose(Status status, Metadata trailers) { // Act assertThrows(StatusRuntimeException.class, () -> ClientCalls.blockingUnaryCall(channel, - method, CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), "foo")); + method, CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), /* request= */ (Void)null)); // Assert assertThat(closed.get()).isTrue(); } - - - private static class StringMarshaller implements MethodDescriptor.Marshaller { - public static final StringMarshaller INSTANCE = new StringMarshaller(); - - @Override - public InputStream stream(String value) { - return new ByteArrayInputStream(value.getBytes(UTF_8)); - } - - @Override - public String parse(InputStream stream) { - try { - return new String(ByteStreams.toByteArray(stream), UTF_8); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } } diff --git a/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java b/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java index 5ffc60e369b..c53d872c49f 100644 --- a/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java +++ b/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java @@ -30,16 +30,19 @@ public final class TestMethodDescriptors { private TestMethodDescriptors() {} + /** The name of the service that the method returned by {@link #voidMethod()} uses. */ + public static final String SERVICE_NAME = "service_foo"; + /** * Creates a new method descriptor that always creates zero length messages, and always parses to - * null objects. + * null objects. It is part of the service named {@link #SERVICE_NAME}. * * @since 1.1.0 */ public static MethodDescriptor voidMethod() { return MethodDescriptor.newBuilder() .setType(MethodType.UNARY) - .setFullMethodName(MethodDescriptor.generateFullMethodName("service_foo", "method_bar")) + .setFullMethodName(MethodDescriptor.generateFullMethodName(SERVICE_NAME, "method_bar")) .setRequestMarshaller(TestMethodDescriptors.voidMarshaller()) .setResponseMarshaller(TestMethodDescriptors.voidMarshaller()) .build(); From 33f8227022b6d9d08c2ac14c3f6e026a9198d274 Mon Sep 17 00:00:00 2001 From: Mateus Azis Date: Tue, 15 Jul 2025 18:01:02 -0700 Subject: [PATCH 4/5] format and check status --- .../internal/PendingAuthListenerTest.java | 129 +++++++++--------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java index 167d0889ea4..172e8d2bb61 100644 --- a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java @@ -2,17 +2,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import static java.nio.charset.StandardCharsets.UTF_8; - -import androidx.annotation.Nullable; - -import com.google.common.io.ByteStreams; - import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; @@ -27,7 +19,6 @@ import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.ServerServiceDefinition; -import io.grpc.ServiceDescriptor; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.inprocess.InProcessChannelBuilder; @@ -36,41 +27,28 @@ import io.grpc.stub.ServerCalls; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.TestMethodDescriptors; - -import org.junit.Assert; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; - @RunWith(JUnit4.class) public final class PendingAuthListenerTest { - @Rule - public final MockitoRule mocks = MockitoJUnit.rule(); - @Rule - public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); - @Mock - ServerCallHandler next; - @Mock - ServerCall call; - @Mock - ServerCall.Listener delegate; + @Mock ServerCallHandler next; + @Mock ServerCall call; + @Mock ServerCall.Listener delegate; private final Metadata headers = new Metadata(); private final PendingAuthListener listener = new PendingAuthListener<>(); @@ -131,55 +109,76 @@ public void whenStartCallFails_closesTheCallWithInternalStatus() throws Exceptio AtomicBoolean closed = new AtomicBoolean(false); MethodDescriptor method = TestMethodDescriptors.voidMethod(); ServerCallHandler callHandler = - ServerCalls.asyncUnaryCall((req, respObserver) -> { - throw new IllegalStateException("ooops"); - }); + ServerCalls.asyncUnaryCall( + (req, respObserver) -> { + throw new IllegalStateException("ooops"); + }); ServerServiceDefinition serviceDef = ServerServiceDefinition.builder(name).addMethod(method, callHandler).build(); Server server = - InProcessServerBuilder.forName(name).addService(serviceDef).intercept( - new ServerInterceptor() { - @SuppressWarnings("unchecked") - @Override - public ServerCall.Listener interceptCall( - ServerCall call, Metadata headers, ServerCallHandler next) { - - listener.startCall((ServerCall) call, headers, (ServerCallHandler) next); - return (ServerCall.Listener) listener; - } - }).build().start(); + InProcessServerBuilder.forName(name) + .addService(serviceDef) + .intercept( + new ServerInterceptor() { + @SuppressWarnings("unchecked") + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next) { + + listener.startCall( + (ServerCall) call, + headers, + (ServerCallHandler) next); + return (ServerCall.Listener) listener; + } + }) + .build() + .start(); ManagedChannel channel = - InProcessChannelBuilder.forName(name).intercept(new ClientInterceptor() { - @Override - public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, Channel next) { - ClientCall delegate = next.newCall(method, callOptions); - return new ForwardingClientCall.SimpleForwardingClientCall(delegate){ - @Override - public void start(Listener responseListener, Metadata headers) { - ClientCall.Listener wrappedListener = - new ForwardingClientCallListener.SimpleForwardingClientCallListener( - responseListener){ + InProcessChannelBuilder.forName(name) + .intercept( + new ClientInterceptor() { + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + ClientCall delegate = next.newCall(method, callOptions); + return new ForwardingClientCall.SimpleForwardingClientCall( + delegate) { @Override - public void onClose(Status status, Metadata trailers) { - super.onClose(status, trailers); - closed.set(true); + public void start(Listener responseListener, Metadata headers) { + ClientCall.Listener wrappedListener = + new ForwardingClientCallListener.SimpleForwardingClientCallListener< + RespT>(responseListener) { + @Override + public void onClose(Status status, Metadata trailers) { + super.onClose(status, trailers); + closed.set(true); + } + }; + super.start(wrappedListener, headers); } }; - super.start(wrappedListener, headers); - } - }; - } - }).build(); + } + }) + .build(); grpcCleanupRule.register(server); grpcCleanupRule.register(channel); // Act - assertThrows(StatusRuntimeException.class, () -> ClientCalls.blockingUnaryCall(channel, - method, CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), /* request= */ (Void)null)); + StatusRuntimeException ex = + assertThrows( + StatusRuntimeException.class, + () -> + ClientCalls.blockingUnaryCall( + channel, + method, + CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), + /* request= */ null)); // Assert + assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.INTERNAL); assertThat(closed.get()).isTrue(); } } From fca403a6581f2d5a26573bfc40658675e52e83a6 Mon Sep 17 00:00:00 2001 From: Mateus Azis Date: Tue, 15 Jul 2025 18:10:55 -0700 Subject: [PATCH 5/5] format --- .../internal/PendingAuthListenerTest.java | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java index 172e8d2bb61..868a774689c 100644 --- a/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/PendingAuthListenerTest.java @@ -27,8 +27,8 @@ import io.grpc.stub.ServerCalls; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.TestMethodDescriptors; +import java.io.IOException; import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -43,6 +43,9 @@ @RunWith(JUnit4.class) public final class PendingAuthListenerTest { + private static final MethodDescriptor TEST_METHOD = + TestMethodDescriptors.voidMethod(); + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); @@ -105,16 +108,32 @@ public void onCallbacks_withCancellation_runsPendingCallbacksAfterStartCall() { @Test public void whenStartCallFails_closesTheCallWithInternalStatus() throws Exception { // Arrange - String name = TestMethodDescriptors.SERVICE_NAME; - AtomicBoolean closed = new AtomicBoolean(false); - MethodDescriptor method = TestMethodDescriptors.voidMethod(); ServerCallHandler callHandler = ServerCalls.asyncUnaryCall( (req, respObserver) -> { throw new IllegalStateException("ooops"); }); + ManagedChannel channel = startServer(callHandler); + + // Act + StatusRuntimeException ex = + assertThrows( + StatusRuntimeException.class, + () -> + ClientCalls.blockingUnaryCall( + channel, + TEST_METHOD, + CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), + /* request= */ null)); + + // Assert + assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.INTERNAL); + } + + private ManagedChannel startServer(ServerCallHandler callHandler) throws IOException { + String name = TestMethodDescriptors.SERVICE_NAME; ServerServiceDefinition serviceDef = - ServerServiceDefinition.builder(name).addMethod(method, callHandler).build(); + ServerServiceDefinition.builder(name).addMethod(TEST_METHOD, callHandler).build(); Server server = InProcessServerBuilder.forName(name) .addService(serviceDef) @@ -150,35 +169,17 @@ public ClientCall interceptCall( public void start(Listener responseListener, Metadata headers) { ClientCall.Listener wrappedListener = new ForwardingClientCallListener.SimpleForwardingClientCallListener< - RespT>(responseListener) { - @Override - public void onClose(Status status, Metadata trailers) { - super.onClose(status, trailers); - closed.set(true); - } - }; + RespT>(responseListener) {}; super.start(wrappedListener, headers); } }; } }) .build(); + grpcCleanupRule.register(server); grpcCleanupRule.register(channel); - // Act - StatusRuntimeException ex = - assertThrows( - StatusRuntimeException.class, - () -> - ClientCalls.blockingUnaryCall( - channel, - method, - CallOptions.DEFAULT.withDeadlineAfter(Duration.ofSeconds(5)), - /* request= */ null)); - - // Assert - assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.INTERNAL); - assertThat(closed.get()).isTrue(); + return channel; } }