11/*
22 * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33 */
4- @file:OptIn( ExperimentalForeignApi :: class , ExperimentalStdlibApi :: class , ExperimentalNativeApi :: class )
4+ package kotlinx.rpc.grpc.test
55
6- package kotlinx.rpc.grpc.internal
7-
8- import HelloReply
9- import HelloReplyInternal
10- import HelloRequest
11- import HelloRequestInternal
12- import invoke
13- import kotlinx.cinterop.ExperimentalForeignApi
146import kotlinx.coroutines.CompletableDeferred
157import kotlinx.coroutines.delay
168import kotlinx.coroutines.runBlocking
9+ import kotlinx.coroutines.test.runTest
1710import kotlinx.coroutines.withTimeout
1811import kotlinx.rpc.grpc.*
19- import kotlin.experimental.ExperimentalNativeApi
12+ import kotlinx.rpc.grpc.internal.*
13+ import kotlinx.rpc.registerService
2014import kotlin.test.Test
2115import kotlin.test.assertEquals
16+ import kotlin.test.assertFails
2217import kotlin.test.assertFailsWith
23- import kotlin.test.assertTrue
18+ import kotlin.time.Duration
2419
20+ private const val PORT = 50051
2521
22+ /* *
23+ * Client tests that use lower level API directly to test that it behaves correctly.
24+ * Before executing the tests run [GreeterServiceImpl.runServer] on JVM.
25+ */
2626// TODO: Start external service server automatically (KRPC-208)
27- class GrpcCoreTest {
27+ class GrpcCoreClientTest {
2828
29- private fun descriptorFor (fullName : String = "helloworld. Greeter /SayHello "): MethodDescriptor <HelloRequest , HelloReply > =
29+ private fun descriptorFor (fullName : String = "kotlinx.rpc.grpc.test. GreeterService /SayHello "): MethodDescriptor <HelloRequest , HelloReply > =
3030 methodDescriptor(
3131 fullMethodName = fullName,
3232 requestCodec = HelloRequestInternal .CODEC ,
@@ -38,10 +38,10 @@ class GrpcCoreTest {
3838 sampledToLocalTracing = true ,
3939 )
4040
41- private fun ManagedChannel.newHelloCall (fullName : String = "helloworld. Greeter /SayHello "): ClientCall <HelloRequest , HelloReply > =
42- platformApi.newCall(descriptorFor(fullName), GrpcCallOptions () )
41+ private fun ManagedChannel.newHelloCall (fullName : String = "kotlinx.rpc.grpc.test. GreeterService /SayHello "): ClientCall <HelloRequest , HelloReply > =
42+ platformApi.newCall(descriptorFor(fullName), GrpcDefaultCallOptions )
4343
44- private fun createChannel (): ManagedChannel = ManagedChannelBuilder (" localhost:50051 " )
44+ private fun createChannel (): ManagedChannel = ManagedChannelBuilder (" localhost:$PORT " )
4545 .usePlaintext()
4646 .buildChannel()
4747
@@ -64,15 +64,10 @@ class GrpcCoreTest {
6464
6565 val statusDeferred = CompletableDeferred <Status >()
6666 val replyDeferred = CompletableDeferred <HelloReply >()
67- val listener = object : ClientCall .Listener <HelloReply >() {
68- override fun onMessage (message : HelloReply ) {
69- replyDeferred.complete(message)
70- }
71-
72- override fun onClose (status : Status , trailers : GrpcTrailers ) {
73- statusDeferred.complete(status)
74- }
75- }
67+ val listener = createClientCallListener<HelloReply >(
68+ onMessage = { replyDeferred.complete(it) },
69+ onClose = { status, _ -> statusDeferred.complete(status) }
70+ )
7671
7772 call.start(listener, GrpcTrailers ())
7873 call.sendMessage(req)
@@ -82,41 +77,22 @@ class GrpcCoreTest {
8277 runBlocking {
8378 withTimeout(10000 ) {
8479 val status = statusDeferred.await()
85- val reply = replyDeferred.await()
8680 assertEquals(StatusCode .OK , status.statusCode)
81+ val reply = replyDeferred.await()
8782 assertEquals(" Hello world" , reply.message)
8883 }
8984 }
9085 shutdownAndWait(channel)
9186 }
9287
93- @Test
94- fun sendMessage_beforeStart_throws () {
95- val channel = createChannel()
96- val call = channel.newHelloCall()
97- val req = helloReq()
98- assertFailsWith<IllegalStateException > { call.sendMessage(req) }
99- shutdownAndWait(channel)
100- }
101-
102- @Test
103- fun request_beforeStart_throws () {
104- val channel = createChannel()
105- val call = channel.newHelloCall()
106- assertFailsWith<IllegalStateException > { call.request(1 ) }
107- shutdownAndWait(channel)
108- }
109-
11088 @Test
11189 fun start_twice_throws () {
11290 val channel = createChannel()
11391 val call = channel.newHelloCall()
11492 val statusDeferred = CompletableDeferred <Status >()
115- val listener = object : ClientCall .Listener <HelloReply >() {
116- override fun onClose (status : Status , trailers : GrpcTrailers ) {
117- statusDeferred.complete(status)
118- }
119- }
93+ val listener = createClientCallListener<HelloReply >(
94+ onClose = { status, _ -> statusDeferred.complete(status) }
95+ )
12096 call.start(listener, GrpcTrailers ())
12197 assertFailsWith<IllegalStateException > { call.start(listener, GrpcTrailers ()) }
12298 // cancel to finish the call quickly
@@ -131,11 +107,9 @@ class GrpcCoreTest {
131107 val call = channel.newHelloCall()
132108 val req = helloReq()
133109 val statusDeferred = CompletableDeferred <Status >()
134- val listener = object : ClientCall .Listener <HelloReply >() {
135- override fun onClose (status : Status , trailers : GrpcTrailers ) {
136- statusDeferred.complete(status)
137- }
138- }
110+ val listener = createClientCallListener<HelloReply >(
111+ onClose = { status, _ -> statusDeferred.complete(status) }
112+ )
139113 call.start(listener, GrpcTrailers ())
140114 call.halfClose()
141115 assertFailsWith<IllegalStateException > { call.sendMessage(req) }
@@ -146,17 +120,15 @@ class GrpcCoreTest {
146120 }
147121
148122 @Test
149- fun request_zero_throws () {
123+ fun request_negative_throws () {
150124 val channel = createChannel()
151125 val call = channel.newHelloCall()
152126 val statusDeferred = CompletableDeferred <Status >()
153- val listener = object : ClientCall .Listener <HelloReply >() {
154- override fun onClose (status : Status , trailers : GrpcTrailers ) {
155- statusDeferred.complete(status)
156- }
157- }
127+ val listener = createClientCallListener<HelloReply >(
128+ onClose = { status, _ -> statusDeferred.complete(status) }
129+ )
158130 call.start(listener, GrpcTrailers ())
159- assertFailsWith< IllegalStateException > { call.request(0 ) }
131+ assertFails { call.request(- 1 ) }
160132 call.cancel(" cleanup" , null )
161133 runBlocking { withTimeout(5000 ) { statusDeferred.await() } }
162134 shutdownAndWait(channel)
@@ -167,11 +139,9 @@ class GrpcCoreTest {
167139 val channel = createChannel()
168140 val call = channel.newHelloCall()
169141 val statusDeferred = CompletableDeferred <Status >()
170- val listener = object : ClientCall .Listener <HelloReply >() {
171- override fun onClose (status : Status , trailers : GrpcTrailers ) {
172- statusDeferred.complete(status)
173- }
174- }
142+ val listener = createClientCallListener<HelloReply >(
143+ onClose = { status, _ -> statusDeferred.complete(status) }
144+ )
175145 call.start(listener, GrpcTrailers ())
176146 call.cancel(" user cancel" , null )
177147 runBlocking {
@@ -186,13 +156,11 @@ class GrpcCoreTest {
186156 @Test
187157 fun invalid_method_returnsNonOkStatus () {
188158 val channel = createChannel()
189- val call = channel.newHelloCall(" /helloworld .Greeter/NoSuchMethod" )
159+ val call = channel.newHelloCall(" kotlinx.rpc.grpc.test .Greeter/NoSuchMethod" )
190160 val statusDeferred = CompletableDeferred <Status >()
191- val listener = object : ClientCall .Listener <HelloReply >() {
192- override fun onClose (status : Status , trailers : GrpcTrailers ) {
193- statusDeferred.complete(status)
194- }
195- }
161+ val listener = createClientCallListener<HelloReply >(
162+ onClose = { status, _ -> statusDeferred.complete(status) }
163+ )
196164
197165 call.start(listener, GrpcTrailers ())
198166 call.sendMessage(helloReq())
@@ -201,7 +169,7 @@ class GrpcCoreTest {
201169 runBlocking {
202170 withTimeout(10000 ) {
203171 val status = statusDeferred.await()
204- assertTrue(status.statusCode != StatusCode . OK )
172+ assertEquals( StatusCode . UNIMPLEMENTED , status.statusCode )
205173 }
206174 }
207175 shutdownAndWait(channel)
@@ -213,11 +181,9 @@ class GrpcCoreTest {
213181 val channel = createChannel()
214182 val call = channel.newHelloCall()
215183 val statusDeferred = CompletableDeferred <Status >()
216- val listener = object : ClientCall .Listener <HelloReply >() {
217- override fun onClose (status : Status , trailers : GrpcTrailers ) {
218- statusDeferred.complete(status)
219- }
220- }
184+ val listener = createClientCallListener<HelloReply >(
185+ onClose = { status, _ -> statusDeferred.complete(status) }
186+ )
221187 assertFailsWith<IllegalStateException > {
222188 try {
223189 call.start(listener, GrpcTrailers ())
@@ -234,13 +200,12 @@ class GrpcCoreTest {
234200 val channel = createChannel()
235201 val call = channel.newHelloCall()
236202 val statusDeferred = CompletableDeferred <Status >()
237- val listener = object : ClientCall .Listener <HelloReply >() {
238- override fun onClose (status : Status , trailers : GrpcTrailers ) {
239- statusDeferred.complete(status)
240- }
241- }
203+ val listener = createClientCallListener<HelloReply >(
204+ onClose = { status, _ -> statusDeferred.complete(status) }
205+ )
242206
243207 channel.shutdown()
208+ runBlocking { delay(100 ) }
244209 call.start(listener, GrpcTrailers ())
245210 call.sendMessage(helloReq())
246211 call.halfClose()
@@ -259,11 +224,9 @@ class GrpcCoreTest {
259224 val channel = createChannel()
260225 val call = channel.newHelloCall()
261226 val statusDeferred = CompletableDeferred <Status >()
262- val listener = object : ClientCall .Listener <HelloReply >() {
263- override fun onClose (status : Status , trailers : GrpcTrailers ) {
264- statusDeferred.complete(status)
265- }
266- }
227+ val listener = createClientCallListener<HelloReply >(
228+ onClose = { status, _ -> statusDeferred.complete(status) }
229+ )
267230
268231 call.start(listener, GrpcTrailers ())
269232 // set timeout on the server to 1000 ms, to simulate a long-running call
@@ -276,19 +239,53 @@ class GrpcCoreTest {
276239 channel.shutdownNow()
277240 withTimeout(10000 ) {
278241 val status = statusDeferred.await()
279- assertEquals(StatusCode .CANCELLED , status.statusCode)
242+ assertEquals(StatusCode .UNAVAILABLE , status.statusCode)
280243 }
281244 }
282245 }
246+ }
247+
248+ class GreeterServiceImpl : GreeterService {
249+
250+ override suspend fun SayHello (message : HelloRequest ): HelloReply {
251+ delay(message.timeout?.toLong() ? : 0 )
252+ return HelloReply {
253+ this .message = " Hello ${message.name} "
254+ }
255+ }
283256
257+
258+ /* *
259+ * Run this on JVM before executing tests.
260+ */
284261 @Test
285- fun unaryCallTest () = runBlocking {
286- val ch = createChannel()
287- val desc = descriptorFor()
288- val req = helloReq()
289- repeat(1000 ) {
290- val res = unaryRpc(ch.platformApi, desc, req)
291- assertEquals(" Hello world" , res.message)
262+ fun runServer () = runTest(timeout = Duration .INFINITE ) {
263+ val server = GrpcServer (
264+ port = PORT ,
265+ builder = { registerService<GreeterService > { GreeterServiceImpl () } }
266+ )
267+
268+ try {
269+ server.start()
270+ println (" Server started" )
271+ server.awaitTermination()
272+ } finally {
273+ server.shutdown()
274+ server.awaitTermination()
292275 }
293276 }
294- }
277+
278+ }
279+
280+
281+ private fun <T > createClientCallListener (
282+ onHeaders : (headers: GrpcTrailers ) -> Unit = {},
283+ onMessage : (message: T ) -> Unit = {},
284+ onClose : (status: Status , trailers: GrpcTrailers ) -> Unit = { _, _ -> },
285+ onReady : () -> Unit = {},
286+ ) = clientCallListener(
287+ onHeaders = onHeaders,
288+ onMessage = onMessage,
289+ onClose = onClose,
290+ onReady = onReady,
291+ )
0 commit comments