Skip to content

Commit 530087e

Browse files
Add integration tests for SOFA RPC Bolt: happy path, distributed trace, server error
1 parent 2711638 commit 530087e

1 file changed

Lines changed: 126 additions & 7 deletions

File tree

  • dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc

dd-java-agent/instrumentation/sofarpc/sofarpc-5.0/src/test/groovy/datadog/trace/instrumentation/sofarpc/SofaRpcTest.groovy

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,33 @@ import datadog.trace.agent.test.InstrumentationSpecification
99
import datadog.trace.bootstrap.instrumentation.api.Tags
1010
import spock.lang.Shared
1111

12+
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
13+
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
14+
1215
class SofaRpcTest extends InstrumentationSpecification {
1316

1417
@Shared
1518
int port = 12201
1619

20+
@Shared
21+
int errorPort = 12202
22+
1723
@Shared
1824
ProviderBootstrap providerBootstrap
1925

26+
@Shared
27+
ProviderBootstrap errorProviderBootstrap
28+
2029
@Shared
2130
GreeterService greeterService
2231

32+
@Shared
33+
FaultyService faultyService
34+
2335
def setupSpec() {
2436
ApplicationConfig appConfig = new ApplicationConfig().setAppName("test-server")
2537

38+
// Happy-path server: Bolt on port 12201
2639
ServerConfig serverConfig =
2740
new ServerConfig()
2841
.setProtocol("bolt")
@@ -39,61 +52,154 @@ class SofaRpcTest extends InstrumentationSpecification {
3952

4053
providerBootstrap = providerConfig.export()
4154

42-
ConsumerConfig<GreeterService> consumerConfig =
55+
greeterService =
4356
new ConsumerConfig<GreeterService>()
4457
.setApplication(new ApplicationConfig().setAppName("test-client"))
4558
.setInterfaceId(GreeterService.name)
4659
.setDirectUrl("bolt://127.0.0.1:${port}")
4760
.setProtocol("bolt")
4861
.setRegister(false)
4962
.setSubscribe(false)
63+
.refer()
64+
65+
// Error-path server: Bolt on port 12202, separate interface to avoid registry conflict
66+
ServerConfig errorServerConfig =
67+
new ServerConfig()
68+
.setProtocol("bolt")
69+
.setHost("127.0.0.1")
70+
.setPort(errorPort)
71+
72+
ProviderConfig<FaultyService> errorProviderConfig =
73+
new ProviderConfig<FaultyService>()
74+
.setApplication(appConfig)
75+
.setInterfaceId(FaultyService.name)
76+
.setRef(new FaultyServiceImpl())
77+
.setServer(errorServerConfig)
78+
.setRegister(false)
5079

51-
greeterService = consumerConfig.refer()
80+
errorProviderBootstrap = errorProviderConfig.export()
81+
82+
faultyService =
83+
new ConsumerConfig<FaultyService>()
84+
.setApplication(new ApplicationConfig().setAppName("test-client"))
85+
.setInterfaceId(FaultyService.name)
86+
.setDirectUrl("bolt://127.0.0.1:${errorPort}")
87+
.setProtocol("bolt")
88+
.setRegister(false)
89+
.setSubscribe(false)
90+
.refer()
5291
}
5392

5493
def cleanupSpec() {
5594
providerBootstrap?.unExport()
95+
errorProviderBootstrap?.unExport()
5696
}
5797

5898
def "client and server spans created for synchronous Bolt RPC call"() {
5999
setup:
60100
String serviceUniqueName = GreeterService.name + ":1.0"
61101

62102
when:
63-
String reply = greeterService.sayHello("World")
103+
// runUnderTrace gives the client trace 2 spans (caller + sofarpc.request), making it
104+
// unambiguously distinguishable from the 1-span server trace in assertTraces below.
105+
String reply = runUnderTrace("caller") { greeterService.sayHello("World") }
64106

65107
then:
66108
reply == "Hello, World"
67109

68110
and:
69-
// Client and server are in the same JVM but use real TCP sockets (Bolt),
70-
// so each side produces its own local trace entry.
71111
assertTraces(2) {
72-
trace(1) {
112+
// trace(0): client side — 2 spans [caller, sofarpc.request(client)]
113+
trace(2) {
114+
basicSpan(it, "caller")
73115
span {
74116
operationName "sofarpc.request"
75117
resourceName "${serviceUniqueName}/sayHello"
76118
spanType "rpc"
77119
errored false
120+
childOf span(0)
78121
tags {
79122
"$Tags.RPC_SERVICE" serviceUniqueName
123+
"rpc.system" "sofarpc"
124+
"sofarpc.protocol" "bolt"
80125
"component" "sofarpc-client"
81126
"span.kind" "client"
127+
peerServiceFrom(Tags.RPC_SERVICE)
82128
defaultTags()
83129
}
84130
}
85131
}
132+
// trace(1): server side — 1 span [sofarpc.request(server)], child of client span
86133
trace(1) {
87134
span {
88135
operationName "sofarpc.request"
89136
resourceName "${serviceUniqueName}/sayHello"
90137
spanType "rpc"
91138
errored false
139+
childOf trace(0).get(1)
92140
tags {
93141
"$Tags.RPC_SERVICE" serviceUniqueName
142+
"rpc.system" "sofarpc"
143+
"sofarpc.protocol" "bolt"
94144
"component" "sofarpc-server"
95145
"span.kind" "server"
96-
defaultTags(true) // distributed root span — parent context propagated via Bolt headers
146+
defaultTags(true)
147+
}
148+
}
149+
}
150+
}
151+
}
152+
153+
def "server error is marked on server span"() {
154+
setup:
155+
String serviceUniqueName = FaultyService.name + ":1.0"
156+
157+
when:
158+
// SOFA RPC Bolt propagates server exceptions back to the client as a SofaRpcException.
159+
// The client-side AbstractCluster.invoke() returns the SofaResponse to the proxy layer,
160+
// which then throws — after our instrumentation's OnMethodExit has already closed the scope.
161+
// So the CLIENT span is not errored; only the SERVER span reflects the error.
162+
faultyService.fail()
163+
164+
then:
165+
thrown(Exception)
166+
167+
and:
168+
assertTraces(2) {
169+
// Traces sorted by root-span start time. Client span starts first (initiates the call),
170+
// so the client trace is trace(0) and the server trace is trace(1).
171+
trace(1) {
172+
span {
173+
operationName "sofarpc.request"
174+
resourceName "${serviceUniqueName}/fail"
175+
spanType "rpc"
176+
errored false
177+
tags {
178+
"$Tags.RPC_SERVICE" serviceUniqueName
179+
"rpc.system" "sofarpc"
180+
"sofarpc.protocol" "bolt"
181+
"component" "sofarpc-client"
182+
"span.kind" "client"
183+
peerServiceFrom(Tags.RPC_SERVICE)
184+
defaultTags()
185+
}
186+
}
187+
}
188+
trace(1) {
189+
span {
190+
operationName "sofarpc.request"
191+
resourceName "${serviceUniqueName}/fail"
192+
spanType "rpc"
193+
errored true
194+
childOf trace(0).get(0)
195+
tags {
196+
"$Tags.RPC_SERVICE" serviceUniqueName
197+
"rpc.system" "sofarpc"
198+
"sofarpc.protocol" "bolt"
199+
"component" "sofarpc-server"
200+
"span.kind" "server"
201+
"error.message" { String }
202+
defaultTags(true)
97203
}
98204
}
99205
}
@@ -110,4 +216,17 @@ class SofaRpcTest extends InstrumentationSpecification {
110216
return "Hello, ${name}"
111217
}
112218
}
219+
220+
interface FaultyService {
221+
// Non-void return type: SOFA RPC Bolt throws SofaRpcException on client side
222+
// when the server returns an error response, which is what we verify in the test.
223+
String fail()
224+
}
225+
226+
static class FaultyServiceImpl implements FaultyService {
227+
@Override
228+
String fail() {
229+
throw new IllegalStateException("something went wrong")
230+
}
231+
}
113232
}

0 commit comments

Comments
 (0)