375 lines
11 KiB
Groovy
375 lines
11 KiB
Groovy
package datadog.trace.tracer
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper
|
|
import datadog.trace.api.DDTags
|
|
import spock.lang.Specification
|
|
|
|
class SpanImplTest extends Specification {
|
|
|
|
private static final String SERVICE_NAME = "service.name"
|
|
private static final String PARENT_TRACE_ID = "trace id"
|
|
private static final String PARENT_SPAN_ID = "span id"
|
|
private static final long START_TIME = 100
|
|
private static final long DURATION = 321
|
|
|
|
def interceptors = [Mock(name: "interceptor-1", Interceptor), Mock(name: "interceptor-2", Interceptor)]
|
|
def tracer = Mock(Tracer) {
|
|
getDefaultServiceName() >> SERVICE_NAME
|
|
getInterceptors() >> interceptors
|
|
}
|
|
def parentContext = Mock(SpanContextImpl) {
|
|
getTraceId() >> PARENT_TRACE_ID
|
|
getSpanId() >> PARENT_SPAN_ID
|
|
}
|
|
def startTimestamp = Mock(Timestamp) {
|
|
getTime() >> START_TIME
|
|
getDuration() >> DURATION
|
|
getDuration(_) >> { args -> args[0] + DURATION }
|
|
}
|
|
def trace = Mock(TraceImpl) {
|
|
getTracer() >> tracer
|
|
}
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper()
|
|
|
|
def "test setters and default values"() {
|
|
when: "create span"
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
then: "span got created"
|
|
span.getTrace() == trace
|
|
span.getStartTimestamp() == startTimestamp
|
|
span.getDuration() == null
|
|
span.getContext().getTraceId() == PARENT_TRACE_ID
|
|
span.getContext().getParentId() == PARENT_SPAN_ID
|
|
span.getContext().getSpanId() ==~ /\d+/
|
|
span.getTraceId() == PARENT_TRACE_ID
|
|
span.getParentId() == PARENT_SPAN_ID
|
|
span.getSpanId() == span.getContext().getSpanId()
|
|
span.getService() == SERVICE_NAME
|
|
span.getResource() == null
|
|
span.getType() == null
|
|
span.getName() == null
|
|
!span.isErrored()
|
|
|
|
when: "span settings changes"
|
|
span.setService("new.service.name")
|
|
span.setResource("resource")
|
|
span.setType("type")
|
|
span.setName("name")
|
|
span.setErrored(true)
|
|
|
|
then: "span fields get updated"
|
|
span.getService() == "new.service.name"
|
|
span.getResource() == "resource"
|
|
span.getType() == "type"
|
|
span.getName() == "name"
|
|
span.isErrored()
|
|
}
|
|
|
|
def "test setter #setter on finished span"() {
|
|
setup: "create span"
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
span.finish()
|
|
|
|
when: "call setter on finished span"
|
|
span."$setter"(newValue)
|
|
|
|
then: "error reported"
|
|
1 * tracer.reportError(_, {
|
|
it[0] == fieldName
|
|
it[1] == span
|
|
})
|
|
and: "value unchanged"
|
|
span."$getter"() == oldValue
|
|
|
|
where:
|
|
fieldName | setter | getter | newValue | oldValue
|
|
"service" | "setService" | "getService" | "new service" | SERVICE_NAME
|
|
"resource" | "setResource" | "getResource" | "new resource" | null
|
|
"type" | "setType" | "getType" | "new type" | null
|
|
"name" | "setName" | "getName" | "new name" | null
|
|
"errored" | "setErrored" | "isErrored" | true | false
|
|
}
|
|
|
|
def "test meta set and remove for #key"() {
|
|
when:
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
then:
|
|
span.getMeta(key) == null
|
|
|
|
when:
|
|
span.setMeta(key, value)
|
|
|
|
then:
|
|
span.getMeta(key) == value
|
|
|
|
when:
|
|
span.setMeta(key, value.class.cast(null))
|
|
|
|
then:
|
|
span.getMeta(key) == null
|
|
|
|
where:
|
|
key | value
|
|
"string.key" | "string"
|
|
"boolean.key" | true
|
|
"number.key" | 123
|
|
}
|
|
|
|
def "test getMeta"() {
|
|
setup:
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
when:
|
|
span.setMeta("number.key", 123)
|
|
span.setMeta("string.key", "meta string")
|
|
span.setMeta("boolean.key", true)
|
|
|
|
then:
|
|
span.getMeta() == ["number.key": "123", "string.key": "meta string", "boolean.key": "true"]
|
|
}
|
|
|
|
def "test meta setter on finished span for #key"() {
|
|
setup: "create span"
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
span.finish()
|
|
|
|
when: "call setter on finished span"
|
|
span.setMeta(key, value)
|
|
|
|
then: "error reported"
|
|
1 * tracer.reportError(_, {
|
|
it[0] == "meta value " + key
|
|
it[1] == span
|
|
})
|
|
and: "value unchanged"
|
|
span.getMeta(key) == null
|
|
|
|
where:
|
|
key | value
|
|
"string.key" | "string"
|
|
"boolean.key" | true
|
|
"number.key" | 123
|
|
}
|
|
|
|
def "test attachThrowable"() {
|
|
setup:
|
|
def exception = new RuntimeException("test message")
|
|
when:
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
then:
|
|
!span.isErrored()
|
|
span.getMeta(DDTags.ERROR_MSG) == null
|
|
span.getMeta(DDTags.ERROR_TYPE) == null
|
|
span.getMeta(DDTags.ERROR_STACK) == null
|
|
|
|
when:
|
|
span.attachThrowable(exception)
|
|
|
|
then:
|
|
span.isErrored()
|
|
span.getMeta(DDTags.ERROR_MSG) == "test message"
|
|
span.getMeta(DDTags.ERROR_TYPE) == RuntimeException.getName()
|
|
span.getMeta(DDTags.ERROR_STACK) != null
|
|
}
|
|
|
|
def "test attachThrowable on finished span"() {
|
|
setup: "create span"
|
|
def exception = new RuntimeException("test message")
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
span.finish()
|
|
|
|
when: "attach throwable"
|
|
span.attachThrowable(exception)
|
|
|
|
then: "error reported"
|
|
1 * tracer.reportError(_, {
|
|
it[0] == "throwable"
|
|
it[1] == span
|
|
})
|
|
and: "span unchanged"
|
|
!span.isErrored()
|
|
span.getMeta(DDTags.ERROR_MSG) == null
|
|
span.getMeta(DDTags.ERROR_TYPE) == null
|
|
span.getMeta(DDTags.ERROR_STACK) == null
|
|
}
|
|
|
|
def "test no parent"() {
|
|
when:
|
|
def span = new SpanImpl(trace, null, startTimestamp)
|
|
|
|
then:
|
|
span.getContext().getTraceId() ==~ /\d+/
|
|
span.getContext().getParentId() == SpanContextImpl.ZERO
|
|
span.getContext().getSpanId() ==~ /\d+/
|
|
}
|
|
|
|
def "test lifecycle '#name'"() {
|
|
when: "create span"
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
then: "interceptors called"
|
|
interceptors.each({ interceptor ->
|
|
then:
|
|
1 * interceptor.afterSpanStarted({
|
|
// Apparently invocation verification has to know expected value before 'when' section
|
|
// To work around this we just check parent span id
|
|
it.getContext().getParentId() == parentContext.getSpanId()
|
|
})
|
|
0 * interceptor._
|
|
})
|
|
then:
|
|
!span.isFinished()
|
|
|
|
when: "finish/finalize span"
|
|
span."$method"(*methodArgs)
|
|
|
|
then: "warning is reported"
|
|
_ * trace.getTracer() >> tracer
|
|
if (finalizeErrorReported) {
|
|
1 * tracer.reportWarning(_, span)
|
|
}
|
|
then: "interceptors called"
|
|
interceptors.reverseEach({ interceptor ->
|
|
then:
|
|
1 * interceptor.beforeSpanFinished({
|
|
it == span
|
|
it.getDuration() == null // Make sure duration is not set yet
|
|
})
|
|
0 * interceptor._
|
|
})
|
|
then: "trace is informed that span is closed"
|
|
1 * trace.finishSpan({
|
|
it == span
|
|
it.getDuration() == expectedDuration
|
|
}, finalizeErrorReported)
|
|
0 * trace._
|
|
span.isFinished()
|
|
|
|
when: "try to finish span again"
|
|
span.finish(*secondFinishCallArgs)
|
|
|
|
then: "interceptors are not called"
|
|
interceptors.each({ interceptor ->
|
|
0 * interceptor._
|
|
})
|
|
and: "trace is not informed"
|
|
0 * trace.finishSpan(_, _)
|
|
and: "error is reported"
|
|
1 * tracer.reportError(_, span)
|
|
|
|
where:
|
|
name | method | methodArgs | expectedDuration | finalizeErrorReported | secondFinishCallArgs
|
|
"happy" | "finish" | [] | DURATION | false | []
|
|
"happy" | "finish" | [] | DURATION | false | [222]
|
|
"happy with timestamp" | "finish" | [111] | DURATION + 111 | false | [222]
|
|
"happy with timestamp" | "finish" | [111] | DURATION + 111 | false | []
|
|
// Note: doing GC tests with mocks is nearly impossible because mocks hold all sort of references
|
|
// We will do 'real' GC test as part of integration testing, this is more of a unit test
|
|
"finalized" | "finalize" | [] | DURATION | true | []
|
|
"finalized" | "finalize" | [] | DURATION | true | [222]
|
|
}
|
|
|
|
def "finished span GCed without errors"() {
|
|
setup: "create span"
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
when: "finish span"
|
|
span.finish()
|
|
|
|
then:
|
|
span.isFinished()
|
|
0 * tracer.reportError(_, *_)
|
|
|
|
when: "finalize span"
|
|
span.finalize()
|
|
|
|
then:
|
|
0 * tracer.reportError(_, *_)
|
|
}
|
|
|
|
def "finalize catches all exceptions"() {
|
|
setup:
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
|
|
when:
|
|
span.finalize()
|
|
|
|
then:
|
|
1 * startTimestamp.getDuration() >> { throw new Throwable() }
|
|
noExceptionThrown()
|
|
}
|
|
|
|
def "span without timestamp"() {
|
|
when:
|
|
new SpanImpl(trace, parentContext, null)
|
|
|
|
then:
|
|
thrown TraceException
|
|
}
|
|
|
|
def "test JSON rendering"() {
|
|
setup: "create span"
|
|
def parentContext = new SpanContextImpl("123", "456", "789")
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
span.setResource("test resource")
|
|
span.setType("test type")
|
|
span.setName("test name")
|
|
span.setMeta("number.key", 123)
|
|
span.setMeta("string.key", "meta string")
|
|
span.setMeta("boolean.key", true)
|
|
span.finish()
|
|
|
|
when: "convert to JSON"
|
|
def string = objectMapper.writeValueAsString(span)
|
|
def parsedSpan = objectMapper.readerFor(JsonSpan).readValue(string)
|
|
|
|
then:
|
|
parsedSpan == new JsonSpan(span)
|
|
}
|
|
|
|
def "test JSON rendering with throwable"() {
|
|
setup: "create span"
|
|
def parentContext = new SpanContextImpl("123", "456", "789")
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
span.attachThrowable(new RuntimeException("test"))
|
|
span.finish()
|
|
|
|
when: "convert to JSON"
|
|
def string = objectMapper.writeValueAsString(span)
|
|
def parsedSpan = objectMapper.readerFor(JsonSpan).readValue(string)
|
|
|
|
then:
|
|
parsedSpan == new JsonSpan(span)
|
|
}
|
|
|
|
def "test JSON rendering with big ID values"() {
|
|
setup: "create span"
|
|
def parentContext = new SpanContextImpl(
|
|
new BigInteger(2).pow(64).subtract(1).toString(),
|
|
"123",
|
|
new BigInteger(2).pow(64).subtract(2).toString())
|
|
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
|
span.finish()
|
|
|
|
when: "convert to JSON"
|
|
def string = objectMapper.writeValueAsString(span)
|
|
def parsedSpan = objectMapper.readValue(string, JsonSpan)
|
|
|
|
then:
|
|
parsedSpan == new JsonSpan(span)
|
|
|
|
when:
|
|
def json = objectMapper.readTree(string)
|
|
|
|
then: "make sure ids rendered as number"
|
|
json.get("trace_id").isNumber()
|
|
json.get("parent_id").isNumber()
|
|
json.get("span_id").isNumber()
|
|
}
|
|
}
|