convert spring batch tests to java (#12004)

Co-authored-by: Steve Rao <raozihao.rzh@alibaba-inc.com>
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
Gregor Zeitlinger 2024-09-13 16:50:30 +02:00 committed by GitHub
parent 68e844e129
commit e36be9c3f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 862 additions and 1823 deletions

View File

@ -1,42 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import org.springframework.batch.core.Job
import org.springframework.batch.core.JobParameter
import org.springframework.batch.core.JobParameters
import org.springframework.batch.core.launch.JobLauncher
import org.springframework.context.ConfigurableApplicationContext
trait ApplicationConfigTrait {
static ConfigurableApplicationContext applicationContext
static JobLauncher jobLauncher
abstract ConfigurableApplicationContext createApplicationContext()
def setupSpec() {
applicationContext = createApplicationContext()
applicationContext.start()
jobLauncher = applicationContext.getBean(JobLauncher)
}
def cleanupSpec() {
applicationContext.stop()
applicationContext.close()
additionalCleanup()
}
def additionalCleanup() {}
def runJob(String jobName, Map<String, JobParameter> params) {
def job = applicationContext.getBean(jobName, Job)
postProcessJob(jobName, job)
jobLauncher.run(job, new JobParameters(params))
}
def postProcessJob(String jobName, Job job) {
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import org.springframework.batch.core.JobParameter
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static java.util.Collections.emptyMap
abstract class ChunkRootSpanTest extends AgentInstrumentationSpecification {
abstract runJob(String jobName, Map<String, JobParameter> params = emptyMap())
def "should create separate traces for each chunk"() {
when:
runJob("itemsAndTaskletJob")
then:
assertTraces(5) {
def itemStepSpan = null
def taskletStepSpan = null
trace(0, 3) {
itemStepSpan = span(1)
taskletStepSpan = span(2)
span(0) {
name "BatchJob itemsAndTaskletJob"
kind INTERNAL
}
span(1) {
name "BatchJob itemsAndTaskletJob.itemStep"
kind INTERNAL
childOf span(0)
}
span(2) {
name "BatchJob itemsAndTaskletJob.taskletStep"
kind INTERNAL
childOf span(0)
}
}
trace(1, 1) {
span(0) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
hasLink itemStepSpan
}
}
trace(2, 1) {
span(0) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
hasLink itemStepSpan
}
}
trace(3, 1) {
span(0) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
hasLink itemStepSpan
}
}
trace(4, 1) {
span(0) {
name "BatchJob itemsAndTaskletJob.taskletStep.Tasklet"
kind INTERNAL
hasLink taskletStepSpan
}
}
}
}
}
class JavaConfigChunkRootSpanTest extends ChunkRootSpanTest implements ApplicationConfigTrait {
@Override
ConfigurableApplicationContext createApplicationContext() {
new AnnotationConfigApplicationContext(SpringBatchApplication)
}
}
class XmlConfigChunkRootSpanTest extends ChunkRootSpanTest implements ApplicationConfigTrait {
@Override
ConfigurableApplicationContext createApplicationContext() {
new ClassPathXmlApplicationContext("spring-batch.xml")
}
}
class JsrConfigChunkRootSpanTest extends ChunkRootSpanTest implements JavaxBatchConfigTrait {
}

View File

@ -1,219 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import org.springframework.batch.core.JobParameter
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static java.util.Collections.emptyMap
abstract class CustomSpanEventTest extends AgentInstrumentationSpecification {
static final boolean VERSION_GREATER_THAN_4_0 = Boolean.getBoolean("testLatestDeps")
abstract runJob(String jobName, Map<String, JobParameter> params = emptyMap())
def "should be able to call Span.current() and add custom info to spans"() {
when:
runJob("customSpanEventsItemsJob")
then:
assertTraces(1) {
trace(0, 7) {
span(0) {
name "BatchJob customSpanEventsItemsJob"
kind INTERNAL
events(2)
event(0) {
eventName "job.before"
}
event(1) {
eventName "job.after"
}
}
span(1) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep"
kind INTERNAL
childOf span(0)
// CompositeChunkListener has broken ordering that causes listeners that do not override order() to appear first at all times
// because of that a custom ChunkListener will always see a Step span when using spring-batch versions [3, 4)
// that bug was fixed in 4.0
if (VERSION_GREATER_THAN_4_0) {
events(2)
event(0) {
eventName "step.before"
}
event(1) {
eventName "step.after"
}
} else {
events(4)
event(0) {
eventName "step.before"
}
event(1) {
eventName "chunk.before"
}
event(2) {
eventName "chunk.after"
}
event(3) {
eventName "step.after"
}
}
}
span(2) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.Chunk"
kind INTERNAL
childOf span(1)
// CompositeChunkListener has broken ordering that causes listeners that do not override order() to appear first at all times
// because of that a custom ChunkListener will always see a Step span when using spring-batch versions [3, 4)
// that bug was fixed in 4.0
if (VERSION_GREATER_THAN_4_0) {
events(2)
event(0) {
eventName "chunk.before"
}
event(1) {
eventName "chunk.after"
}
} else {
events(0)
}
}
itemSpans(it)
}
}
}
// Spring Batch Java & XML configs have slightly different ordering from JSR config
protected void itemSpans(TraceAssert trace) {
trace.with {
span(3) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
kind INTERNAL
childOf span(2)
events(2)
event(0) {
eventName "item.read.before"
}
event(1) {
eventName "item.read.after"
}
}
// second read that returns null and signifies end of stream
span(4) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
kind INTERNAL
childOf span(2)
// spring batch does not call ItemReadListener after() methods when read() returns end-of-stream
events(1)
event(0) {
eventName "item.read.before"
}
}
span(5) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemProcess"
kind INTERNAL
childOf span(2)
events(2)
event(0) {
eventName "item.process.before"
}
event(1) {
eventName "item.process.after"
}
}
span(6) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemWrite"
kind INTERNAL
childOf span(2)
events(2)
event(0) {
eventName "item.write.before"
}
event(1) {
eventName "item.write.after"
}
}
}
}
}
class JavaConfigCustomSpanEventTest extends CustomSpanEventTest implements ApplicationConfigTrait {
@Override
ConfigurableApplicationContext createApplicationContext() {
new AnnotationConfigApplicationContext(SpringBatchApplication)
}
}
class XmlConfigCustomSpanEventTest extends CustomSpanEventTest implements ApplicationConfigTrait {
@Override
ConfigurableApplicationContext createApplicationContext() {
new ClassPathXmlApplicationContext("spring-batch.xml")
}
}
class JsrConfigCustomSpanEventTest extends CustomSpanEventTest implements JavaxBatchConfigTrait {
// JSR config has different item span ordering
protected void itemSpans(TraceAssert trace) {
trace.with {
span(3) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
kind INTERNAL
childOf span(2)
events(2)
event(0) {
eventName "item.read.before"
}
event(1) {
eventName "item.read.after"
}
}
span(4) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemProcess"
kind INTERNAL
childOf span(2)
events(2)
event(0) {
eventName "item.process.before"
}
event(1) {
eventName "item.process.after"
}
}
// second read that returns null and signifies end of stream
span(5) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead"
kind INTERNAL
childOf span(2)
// spring batch does not call ItemReadListener after() methods when read() returns end-of-stream
events(1)
event(0) {
eventName "item.read.before"
}
}
span(6) {
name "BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemWrite"
kind INTERNAL
childOf span(2)
events(2)
event(0) {
eventName "item.write.before"
}
event(1) {
eventName "item.write.after"
}
}
}
}
}

View File

@ -1,421 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.sdk.trace.data.SpanData
import org.springframework.batch.core.Job
import org.springframework.batch.core.JobParameter
import org.springframework.batch.core.Step
import org.springframework.batch.core.job.AbstractJob
import org.springframework.batch.core.step.tasklet.TaskletStep
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy
import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static java.util.Collections.emptyMap
abstract class ItemLevelSpanTest extends AgentInstrumentationSpecification {
abstract runJob(String jobName, Map<String, JobParameter> params = emptyMap())
def "should trace item read, process and write calls"() {
when:
runJob("itemsAndTaskletJob")
then:
assertTraces(1) {
trace(0, 37) {
span(0) {
name "BatchJob itemsAndTaskletJob"
kind INTERNAL
}
// item step
span(1) {
name "BatchJob itemsAndTaskletJob.itemStep"
kind INTERNAL
childOf span(0)
}
// chunk 1, items 0-5
span(2) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
childOf span(1)
}
(3..7).forEach {
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(2)
}
}
(8..12).forEach {
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemProcess"
kind INTERNAL
childOf span(2)
}
}
span(13) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemWrite"
kind INTERNAL
childOf span(2)
}
// chunk 2, items 5-10
span(14) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
childOf span(1)
}
(15..19).forEach {
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(14)
}
}
(20..24).forEach {
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemProcess"
kind INTERNAL
childOf span(14)
}
}
span(25) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemWrite"
kind INTERNAL
childOf span(14)
}
// chunk 3, items 10-13
span(26) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
childOf span(1)
}
// +1 for last read returning end of stream marker
(27..30).forEach {
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(26)
}
}
(31..33).forEach {
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemProcess"
kind INTERNAL
childOf span(26)
}
}
span(34) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemWrite"
kind INTERNAL
childOf span(26)
}
// tasklet step
span(35) {
name "BatchJob itemsAndTaskletJob.taskletStep"
kind INTERNAL
childOf span(0)
}
span(36) {
name "BatchJob itemsAndTaskletJob.taskletStep.Tasklet"
kind INTERNAL
childOf span(35)
}
}
}
}
def "should trace all item operations on a parallel items job"() {
when:
runJob("parallelItemsJob")
then:
assertTraces(1) {
trace(0, 19) {
// as chunks are processed in parallel we need to sort them to guarantee that they are
// in the expected order
// firstly compute child span count for each chunk, we'll sort chunks from larger to smaller
// based on child count
def childCount = new HashMap<SpanData, Number>()
spans.forEach { span ->
if (span.name == "BatchJob parallelItemsJob.parallelItemsStep.Chunk") {
childCount.put(span, spans.count { it.parentSpanId == span.spanId })
}
}
// sort spans with a ranking function
spans.sort({
// job span is first
if (it.name == "BatchJob parallelItemsJob") {
return 0
}
// step span is second
if (it.name == "BatchJob parallelItemsJob.parallelItemsStep") {
return 1
}
// find the chunk this span belongs to
def chunkSpan = it
while (chunkSpan != null && chunkSpan.name != "BatchJob parallelItemsJob.parallelItemsStep.Chunk") {
chunkSpan = spans.find { it.spanId == chunkSpan.parentSpanId }
}
if (chunkSpan != null) {
// sort larger chunks first
return 100 - childCount.get(chunkSpan)
}
throw new IllegalStateException("item spans should have a parent chunk span")
})
span(0) {
name "BatchJob parallelItemsJob"
kind INTERNAL
}
span(1) {
name "BatchJob parallelItemsJob.parallelItemsStep"
kind INTERNAL
childOf span(0)
}
// chunk 1, first two items; thread 1
span(2) {
name "BatchJob parallelItemsJob.parallelItemsStep.Chunk"
kind INTERNAL
childOf span(1)
}
[3, 4].forEach {
span(it) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemRead"
kind INTERNAL
childOf span(2)
}
}
[5, 6].forEach {
span(it) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemProcess"
kind INTERNAL
childOf span(2)
}
}
span(7) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemWrite"
kind INTERNAL
childOf span(2)
}
// chunk 2, items 3 & 4; thread 2
span(8) {
name "BatchJob parallelItemsJob.parallelItemsStep.Chunk"
kind INTERNAL
childOf span(1)
}
[9, 10].forEach {
span(it) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemRead"
kind INTERNAL
childOf span(8)
}
}
[11, 12].forEach {
span(it) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemProcess"
kind INTERNAL
childOf span(8)
}
}
span(13) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemWrite"
kind INTERNAL
childOf span(8)
}
// chunk 3, 5th item; thread 1
span(14) {
name "BatchJob parallelItemsJob.parallelItemsStep.Chunk"
kind INTERNAL
childOf span(1)
}
// +1 for last read returning end of stream marker
[15, 16].forEach {
span(it) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemRead"
kind INTERNAL
childOf span(14)
}
}
span(17) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemProcess"
kind INTERNAL
childOf span(14)
}
span(18) {
name "BatchJob parallelItemsJob.parallelItemsStep.ItemWrite"
kind INTERNAL
childOf span(14)
}
}
}
}
def postProcessParallelItemsJob(String jobName, Job job) {
if ("parallelItemsJob" == jobName) {
Step step = ((AbstractJob) job).getStep("parallelItemsStep")
TaskletStep taskletStep = (TaskletStep) step
// explicitly set the number of chunks we expect from this test to ensure we always get
// the same number of spans
((TaskExecutorRepeatTemplate) taskletStep.stepOperations).completionPolicy = new SimpleCompletionPolicy(3)
}
}
}
class JavaConfigItemLevelSpanTest extends ItemLevelSpanTest implements ApplicationConfigTrait {
@Override
def postProcessJob(String jobName, Job job) {
postProcessParallelItemsJob(jobName, job)
}
@Override
ConfigurableApplicationContext createApplicationContext() {
new AnnotationConfigApplicationContext(SpringBatchApplication)
}
}
class XmlConfigItemLevelSpanTest extends ItemLevelSpanTest implements ApplicationConfigTrait {
@Override
def postProcessJob(String jobName, Job job) {
postProcessParallelItemsJob(jobName, job)
}
@Override
ConfigurableApplicationContext createApplicationContext() {
new ClassPathXmlApplicationContext("spring-batch.xml")
}
}
// JsrChunkProcessor works a bit differently than the "standard" one and does not read the whole
// chunk at once, it reads every item separately; it results in a different span ordering, that's
// why it has a completely separate test class
class JsrConfigItemLevelSpanTest extends AgentInstrumentationSpecification implements JavaxBatchConfigTrait {
def "should trace item read, process and write calls"() {
when:
runJob("itemsAndTaskletJob", [:])
then:
assertTraces(1) {
trace(0, 37) {
span(0) {
name "BatchJob itemsAndTaskletJob"
kind INTERNAL
}
// item step
span(1) {
name "BatchJob itemsAndTaskletJob.itemStep"
kind INTERNAL
childOf span(0)
}
// chunk 1, items 0-5
span(2) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
childOf span(1)
}
(3..11).step(2) {
println it
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(2)
}
span(it + 1) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemProcess"
kind INTERNAL
childOf span(2)
}
}
span(13) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemWrite"
kind INTERNAL
childOf span(2)
}
// chunk 2, items 5-10
span(14) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
childOf span(1)
}
(15..23).step(2) {
println it
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(14)
}
span(it + 1) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemProcess"
kind INTERNAL
childOf span(14)
}
}
span(25) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemWrite"
kind INTERNAL
childOf span(14)
}
// chunk 3, items 10-13
span(26) {
name "BatchJob itemsAndTaskletJob.itemStep.Chunk"
kind INTERNAL
childOf span(1)
}
(27..32).step(2) {
println it
span(it) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(26)
}
span(it + 1) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemProcess"
kind INTERNAL
childOf span(26)
}
}
// last read returning end of stream marker
span(33) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemRead"
kind INTERNAL
childOf span(26)
}
span(34) {
name "BatchJob itemsAndTaskletJob.itemStep.ItemWrite"
kind INTERNAL
childOf span(26)
}
// tasklet step
span(35) {
name "BatchJob itemsAndTaskletJob.taskletStep"
kind INTERNAL
childOf span(0)
}
span(36) {
name "BatchJob itemsAndTaskletJob.taskletStep.Tasklet"
kind INTERNAL
childOf span(35)
}
}
}
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import org.springframework.batch.core.JobParameter
import javax.batch.operations.JobOperator
import javax.batch.runtime.BatchRuntime
import java.util.concurrent.atomic.AtomicInteger
trait JavaxBatchConfigTrait {
static JobOperator jobOperator
static AtomicInteger counter = new AtomicInteger()
def setupSpec() {
jobOperator = BatchRuntime.jobOperator
}
// just for consistency with ApplicationConfigTrait
def cleanupSpec() {
additionalCleanup()
}
def additionalCleanup() {}
def runJob(String jobName, Map<String, JobParameter> params) {
def jobParams = new Properties()
params.forEach({ k, v ->
jobParams.setProperty(k, v.toString())
})
// each job instance with the same name needs to be unique
jobParams.setProperty("uniqueJobIdCounter", counter.getAndIncrement().toString())
jobOperator.start(jobName, jobParams)
}
}

View File

@ -1,304 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory
import org.springframework.batch.core.job.builder.FlowBuilder
import org.springframework.batch.core.job.flow.Flow
import org.springframework.batch.core.job.flow.support.SimpleFlow
import org.springframework.batch.core.launch.JobLauncher
import org.springframework.batch.core.launch.support.SimpleJobLauncher
import org.springframework.batch.core.partition.support.Partitioner
import org.springframework.batch.core.repository.JobRepository
import org.springframework.batch.item.ItemProcessor
import org.springframework.batch.item.ItemReader
import org.springframework.batch.item.ItemWriter
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.task.AsyncTaskExecutor
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import springbatch.CustomEventChunkListener
import springbatch.CustomEventItemProcessListener
import springbatch.CustomEventItemReadListener
import springbatch.CustomEventItemWriteListener
import springbatch.CustomEventJobListener
import springbatch.CustomEventStepListener
import springbatch.SingleItemReader
import springbatch.TestDecider
import springbatch.TestItemProcessor
import springbatch.TestItemReader
import springbatch.TestItemWriter
import springbatch.TestPartitionedItemReader
import springbatch.TestPartitioner
import springbatch.TestSyncItemReader
import springbatch.TestTasklet
@Configuration
@EnableBatchProcessing
class SpringBatchApplication {
@Autowired
JobBuilderFactory jobs
@Autowired
StepBuilderFactory steps
@Autowired
JobRepository jobRepository
@Bean
AsyncTaskExecutor asyncTaskExecutor() {
def executor = new ThreadPoolTaskExecutor()
executor.corePoolSize = 10
executor.maxPoolSize = 10
executor
}
@Bean
JobLauncher jobLauncher() {
def launcher = new SimpleJobLauncher()
launcher.jobRepository = jobRepository
launcher.taskExecutor = asyncTaskExecutor()
launcher
}
// common
@Bean
ItemReader<String> itemReader() {
new TestItemReader()
}
@Bean
ItemProcessor<String, Integer> itemProcessor() {
new TestItemProcessor()
}
@Bean
ItemWriter<Integer> itemWriter() {
new TestItemWriter()
}
// simple tasklet job
@Bean
Job taskletJob() {
jobs.get("taskletJob")
.start(step())
.build()
}
@Bean
Step step() {
steps.get("step")
.tasklet(new TestTasklet())
.build()
}
// 2-step tasklet + chunked items job
@Bean
Job itemsAndTaskletJob() {
jobs.get("itemsAndTaskletJob")
.start(itemStep())
.next(taskletStep())
.build()
}
@Bean
Step taskletStep() {
steps.get("taskletStep")
.tasklet(new TestTasklet())
.build()
}
@Bean
Step itemStep() {
steps.get("itemStep")
.chunk(5)
.reader(itemReader())
.processor(itemProcessor())
.writer(itemWriter())
.build()
}
// parallel items job
@Bean
Job parallelItemsJob() {
jobs.get("parallelItemsJob")
.start(parallelItemsStep())
.build()
}
@Bean
Step parallelItemsStep() {
steps.get("parallelItemsStep")
.chunk(2)
.reader(new TestSyncItemReader(5))
.processor(itemProcessor())
.writer(itemWriter())
.taskExecutor(asyncTaskExecutor())
.throttleLimit(2)
.build()
}
// job using a flow
@Bean
Job flowJob() {
jobs.get("flowJob")
.start(flow())
.build()
.build()
}
@Bean
Flow flow() {
new FlowBuilder<SimpleFlow>("flow")
.start(flowStep1())
.on("*")
.to(flowStep2())
.build()
}
@Bean
Step flowStep1() {
steps.get("flowStep1")
.tasklet(new TestTasklet())
.build()
}
@Bean
Step flowStep2() {
steps.get("flowStep2")
.tasklet(new TestTasklet())
.build()
}
// split job
@Bean
Job splitJob() {
jobs.get("splitJob")
.start(splitFlowStep1())
.split(asyncTaskExecutor())
.add(splitFlow2())
.build()
.build()
}
@Bean
Step splitFlowStep1() {
steps.get("splitFlowStep1")
.tasklet(new TestTasklet())
.build()
}
@Bean
Flow splitFlow2() {
new FlowBuilder<SimpleFlow>("splitFlow2")
.start(splitFlowStep2())
.build()
}
@Bean
Step splitFlowStep2() {
steps.get("splitFlowStep2")
.tasklet(new TestTasklet())
.build()
}
// job with decisions
@Bean
Job decisionJob() {
jobs.get("decisionJob")
.start(decisionStepStart())
.next(new TestDecider())
.on("LEFT").to(decisionStepLeft())
.on("RIGHT").to(decisionStepRight())
.end()
.build()
}
@Bean
Step decisionStepStart() {
steps.get("decisionStepStart")
.tasklet(new TestTasklet())
.build()
}
@Bean
Step decisionStepLeft() {
steps.get("decisionStepLeft")
.tasklet(new TestTasklet())
.build()
}
@Bean
Step decisionStepRight() {
steps.get("decisionStepRight")
.tasklet(new TestTasklet())
.build()
}
// partitioned job
@Bean
Job partitionedJob() {
jobs.get("partitionedJob")
.start(partitionManagerStep())
.build()
}
@Bean
Step partitionManagerStep() {
steps.get("partitionManagerStep")
.partitioner("partitionWorkerStep", partitioner())
.step(partitionWorkerStep())
.gridSize(2)
.taskExecutor(asyncTaskExecutor())
.build()
}
@Bean
Partitioner partitioner() {
new TestPartitioner()
}
@Bean
Step partitionWorkerStep() {
steps.get("partitionWorkerStep")
.chunk(5)
.reader(partitionedItemReader())
.processor(itemProcessor())
.writer(itemWriter())
.build()
}
@Bean
ItemReader<String> partitionedItemReader() {
new TestPartitionedItemReader()
}
// custom span events items job
@Bean
Job customSpanEventsItemsJob() {
jobs.get("customSpanEventsItemsJob")
.start(customSpanEventsItemStep())
.listener(new CustomEventJobListener())
.build()
}
@Bean
Step customSpanEventsItemStep() {
steps.get("customSpanEventsItemStep")
.chunk(5)
.reader(new SingleItemReader())
.processor(itemProcessor())
.writer(itemWriter())
.listener(new CustomEventStepListener())
.listener(new CustomEventChunkListener())
.listener(new CustomEventItemReadListener())
.listener(new CustomEventItemProcessListener())
.listener(new CustomEventItemWriteListener())
.build()
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import io.opentelemetry.api.trace.Span
import javax.batch.api.chunk.listener.ChunkListener
class CustomEventChunkListener implements ChunkListener {
@Override
void beforeChunk() throws Exception {
Span.current().addEvent("chunk.before")
}
@Override
void onError(Exception e) throws Exception {
Span.current().addEvent("chunk.error")
}
@Override
void afterChunk() throws Exception {
Span.current().addEvent("chunk.after")
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import io.opentelemetry.api.trace.Span
import javax.batch.api.chunk.listener.ItemProcessListener
class CustomEventItemProcessListener implements ItemProcessListener {
@Override
void beforeProcess(Object o) throws Exception {
Span.current().addEvent("item.process.before")
}
@Override
void afterProcess(Object o, Object o1) throws Exception {
Span.current().addEvent("item.process.after")
}
@Override
void onProcessError(Object o, Exception e) throws Exception {
Span.current().addEvent("item.process.error")
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import io.opentelemetry.api.trace.Span
import javax.batch.api.chunk.listener.ItemReadListener
class CustomEventItemReadListener implements ItemReadListener {
@Override
void beforeRead() throws Exception {
Span.current().addEvent("item.read.before")
}
@Override
void afterRead(Object o) throws Exception {
Span.current().addEvent("item.read.after")
}
@Override
void onReadError(Exception e) throws Exception {
Span.current().addEvent("item.read.error")
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import io.opentelemetry.api.trace.Span
import javax.batch.api.chunk.listener.ItemWriteListener
class CustomEventItemWriteListener implements ItemWriteListener {
@Override
void beforeWrite(List<Object> list) throws Exception {
Span.current().addEvent("item.write.before")
}
@Override
void afterWrite(List<Object> list) throws Exception {
Span.current().addEvent("item.write.after")
}
@Override
void onWriteError(List<Object> list, Exception e) throws Exception {
Span.current().addEvent("item.write.error")
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import io.opentelemetry.api.trace.Span
import javax.batch.api.listener.JobListener
class CustomEventJobListener implements JobListener {
@Override
void beforeJob() throws Exception {
Span.current().addEvent("job.before")
}
@Override
void afterJob() throws Exception {
Span.current().addEvent("job.after")
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import io.opentelemetry.api.trace.Span
import javax.batch.api.listener.StepListener
class CustomEventStepListener implements StepListener {
@Override
void beforeStep() throws Exception {
Span.current().addEvent("step.before")
}
@Override
void afterStep() throws Exception {
Span.current().addEvent("step.after")
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.chunk.ItemReader
import java.util.concurrent.atomic.AtomicReference
class SingleItemReader implements ItemReader {
final AtomicReference<String> item = new AtomicReference<>("42")
@Override
void open(Serializable serializable) throws Exception {
}
@Override
void close() throws Exception {
}
@Override
Object readItem() throws Exception {
return item.getAndSet(null)
}
@Override
Serializable checkpointInfo() throws Exception {
return null
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.BatchProperty
import javax.batch.api.Batchlet
import javax.inject.Inject
class TestBatchlet implements Batchlet {
@Inject
@BatchProperty(name = "fail")
String fail
@Override
String process() throws Exception {
if (fail != null && Integer.valueOf(fail) == 1) {
throw new IllegalStateException("fail")
}
return "FINISHED"
}
@Override
void stop() throws Exception {
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.Decider
import javax.batch.runtime.StepExecution
class TestDecider implements Decider {
@Override
String decide(StepExecution[] stepExecutions) throws Exception {
"LEFT"
}
}

View File

@ -1,15 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.chunk.ItemProcessor
class TestItemProcessor implements ItemProcessor {
@Override
Object processItem(Object item) throws Exception {
Integer.parseInt(item as String)
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.chunk.ItemReader
import java.util.stream.Collectors
import java.util.stream.IntStream
class TestItemReader implements ItemReader {
private final List<String> items = IntStream.range(0, 13).mapToObj(String.&valueOf).collect(Collectors.toList())
private Iterator<String> itemsIt
@Override
void open(Serializable serializable) throws Exception {
itemsIt = items.iterator()
}
@Override
void close() throws Exception {
itemsIt = null
}
@Override
Object readItem() throws Exception {
if (itemsIt == null) {
return null
}
return itemsIt.hasNext() ? itemsIt.next() : null
}
@Override
Serializable checkpointInfo() throws Exception {
return null
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.chunk.ItemWriter
class TestItemWriter implements ItemWriter {
final List<Integer> items = new ArrayList()
@Override
void open(Serializable checkpoint) throws Exception {
}
@Override
void close() throws Exception {
}
@Override
void writeItems(List<Object> items) throws Exception {
for (item in items) {
this.items.add(item as Integer)
}
}
@Override
Serializable checkpointInfo() throws Exception {
return null
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package jsr
import javax.batch.api.BatchProperty
import javax.batch.api.chunk.ItemReader
import javax.inject.Inject
class TestPartitionedItemReader implements ItemReader {
@Inject
@BatchProperty(name = "start")
String startStr
@Inject
@BatchProperty(name = "end")
String endStr
int start
int end
@Override
void open(Serializable checkpoint) throws Exception {
start = Integer.parseInt(startStr)
end = Integer.parseInt(endStr)
}
@Override
void close() throws Exception {
}
@Override
Object readItem() throws Exception {
if (start >= end) {
return null
}
return String.valueOf(start++)
}
@Override
Serializable checkpointInfo() throws Exception {
return null
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import io.opentelemetry.api.trace.Span
import org.springframework.batch.core.ChunkListener
import org.springframework.batch.core.scope.context.ChunkContext
class CustomEventChunkListener implements ChunkListener {
@Override
void beforeChunk(ChunkContext context) {
Span.current().addEvent("chunk.before")
}
@Override
void afterChunk(ChunkContext context) {
Span.current().addEvent("chunk.after")
}
@Override
void afterChunkError(ChunkContext context) {
Span.current().addEvent("chunk.error")
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import io.opentelemetry.api.trace.Span
import org.springframework.batch.core.ItemProcessListener
class CustomEventItemProcessListener implements ItemProcessListener<String, Integer> {
@Override
void beforeProcess(String item) {
Span.current().addEvent("item.process.before")
}
@Override
void afterProcess(String item, Integer result) {
Span.current().addEvent("item.process.after")
}
@Override
void onProcessError(String item, Exception e) {
Span.current().addEvent("item.process.error")
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import io.opentelemetry.api.trace.Span
import org.springframework.batch.core.ItemReadListener
class CustomEventItemReadListener implements ItemReadListener<String> {
@Override
void beforeRead() {
Span.current().addEvent("item.read.before")
}
@Override
void afterRead(String item) {
Span.current().addEvent("item.read.after")
}
@Override
void onReadError(Exception ex) {
Span.current().addEvent("item.read.error")
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import io.opentelemetry.api.trace.Span
import org.springframework.batch.core.ItemWriteListener
class CustomEventItemWriteListener implements ItemWriteListener<Integer> {
@Override
void beforeWrite(List<? extends Integer> items) {
Span.current().addEvent("item.write.before")
}
@Override
void afterWrite(List<? extends Integer> items) {
Span.current().addEvent("item.write.after")
}
@Override
void onWriteError(Exception exception, List<? extends Integer> items) {
Span.current().addEvent("item.write.error")
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import io.opentelemetry.api.trace.Span
import org.springframework.batch.core.JobExecution
import org.springframework.batch.core.JobExecutionListener
class CustomEventJobListener implements JobExecutionListener {
@Override
void beforeJob(JobExecution jobExecution) {
Span.current().addEvent("job.before")
}
@Override
void afterJob(JobExecution jobExecution) {
Span.current().addEvent("job.after")
}
}

View File

@ -1,24 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import io.opentelemetry.api.trace.Span
import org.springframework.batch.core.ExitStatus
import org.springframework.batch.core.StepExecution
import org.springframework.batch.core.StepExecutionListener
class CustomEventStepListener implements StepExecutionListener {
@Override
void beforeStep(StepExecution stepExecution) {
Span.current().addEvent("step.before")
}
@Override
ExitStatus afterStep(StepExecution stepExecution) {
Span.current().addEvent("step.after")
return null
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.item.ItemReader
import java.util.concurrent.atomic.AtomicReference
class SingleItemReader implements ItemReader<String> {
final AtomicReference<String> item = new AtomicReference<>("42")
@Override
String read() {
return item.getAndSet(null)
}
}

View File

@ -1,18 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.core.JobExecution
import org.springframework.batch.core.StepExecution
import org.springframework.batch.core.job.flow.FlowExecutionStatus
import org.springframework.batch.core.job.flow.JobExecutionDecider
class TestDecider implements JobExecutionDecider {
@Override
FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
new FlowExecutionStatus("LEFT")
}
}

View File

@ -1,15 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.item.ItemProcessor
class TestItemProcessor implements ItemProcessor<String, Integer> {
@Override
Integer process(String item) throws Exception {
Integer.parseInt(item)
}
}

View File

@ -1,17 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.item.support.ListItemReader
import java.util.stream.Collectors
import java.util.stream.IntStream
class TestItemReader extends ListItemReader<String> {
TestItemReader() {
super(IntStream.range(0, 13).mapToObj(String.&valueOf).collect(Collectors.toList()) as List<String>)
}
}

View File

@ -1,17 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.item.ItemWriter
class TestItemWriter implements ItemWriter<Integer> {
final List<Integer> items = Collections.synchronizedList(new ArrayList())
@Override
void write(List<? extends Integer> items) throws Exception {
this.items.addAll(items)
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.item.ExecutionContext
import org.springframework.batch.item.ItemReader
import org.springframework.batch.item.ItemStream
import org.springframework.batch.item.ItemStreamException
import org.springframework.batch.item.NonTransientResourceException
import org.springframework.batch.item.ParseException
import org.springframework.batch.item.UnexpectedInputException
class TestPartitionedItemReader implements ItemReader<String>, ItemStream {
ThreadLocal<Integer> start = new ThreadLocal<>()
ThreadLocal<Integer> end = new ThreadLocal<>()
@Override
String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
if (start.get() >= end.get()) {
return null
}
def value = start.get()
start.set(value + 1)
return String.valueOf(value)
}
@Override
void open(ExecutionContext executionContext) throws ItemStreamException {
start.set(executionContext.getInt("start"))
end.set(executionContext.getInt("end"))
}
@Override
void update(ExecutionContext executionContext) throws ItemStreamException {
}
@Override
void close() throws ItemStreamException {
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.core.partition.support.Partitioner
import org.springframework.batch.item.ExecutionContext
class TestPartitioner implements Partitioner {
@Override
Map<String, ExecutionContext> partition(int gridSize) {
return [
"partition0": new ExecutionContext([
"start": 0, "end": 8
]),
"partition1": new ExecutionContext([
"start": 8, "end": 13
])
]
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.item.ItemReader
import java.util.stream.Collectors
import java.util.stream.IntStream
class TestSyncItemReader implements ItemReader<String> {
private final Iterator<String> items
TestSyncItemReader(int max) {
items = IntStream.range(0, max).mapToObj(String.&valueOf).collect(Collectors.toList()).iterator()
}
synchronized String read() {
if (items.hasNext()) {
return items.next()
}
return null
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package springbatch
import org.springframework.batch.core.StepContribution
import org.springframework.batch.core.scope.context.ChunkContext
import org.springframework.batch.core.step.tasklet.Tasklet
import org.springframework.batch.repeat.RepeatStatus
class TestTasklet implements Tasklet {
@Override
RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
if (chunkContext.stepContext.stepExecution.jobParameters.getLong("fail") == 1) {
throw new IllegalStateException("fail")
}
RepeatStatus.FINISHED
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.chunk;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.JobRunner;
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
abstract class AbstractChunkRootSpanTest {
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
private final JobRunner jobRunner;
AbstractChunkRootSpanTest(JobRunner jobRunner) {
this.jobRunner = jobRunner;
}
@Test
void shouldCreateSeparateTracesForEachChunk() {
jobRunner.runJob("itemsAndTaskletJob");
AtomicReference<SpanData> itemStepSpan = new AtomicReference<>();
AtomicReference<SpanData> taskletStepSpan = new AtomicReference<>();
Consumer<TraceAssert> chunk =
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasLinks(LinkData.create(itemStepSpan.get().getSpanContext())));
testing.waitAndAssertTraces(
trace -> {
itemStepSpan.set(trace.getSpan(1));
taskletStepSpan.set(trace.getSpan(2));
trace.hasSpansSatisfyingExactly(
span -> span.hasName("BatchJob itemsAndTaskletJob").hasKind(SpanKind.INTERNAL),
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)),
span ->
span.hasName("BatchJob itemsAndTaskletJob.taskletStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
},
chunk,
chunk,
chunk,
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("BatchJob itemsAndTaskletJob.taskletStep.Tasklet")
.hasKind(SpanKind.INTERNAL)
.hasLinks(LinkData.create(taskletStepSpan.get().getSpanContext()))));
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.chunk;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.ApplicationConfigRunner;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.SpringBatchApplication;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
class JavaConfigChunkRootSpanTest extends AbstractChunkRootSpanTest {
@RegisterExtension
static final ApplicationConfigRunner runner =
new ApplicationConfigRunner(
() -> new AnnotationConfigApplicationContext(SpringBatchApplication.class));
JavaConfigChunkRootSpanTest() {
super(runner);
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.chunk;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.JavaxBatchConfigRunner;
import org.junit.jupiter.api.extension.RegisterExtension;
class JsrConfigChunkRootSpanTest extends AbstractChunkRootSpanTest {
@RegisterExtension static final JavaxBatchConfigRunner runner = new JavaxBatchConfigRunner();
JsrConfigChunkRootSpanTest() {
super(runner);
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.chunk;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.ApplicationConfigRunner;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class XmlConfigChunkRootSpanTest extends AbstractChunkRootSpanTest {
@RegisterExtension
static final ApplicationConfigRunner runner =
new ApplicationConfigRunner(() -> new ClassPathXmlApplicationContext("spring-batch.xml"));
XmlConfigChunkRootSpanTest() {
super(runner);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.event;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.JobRunner;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
abstract class CustomSpanEventTest {
private static final boolean VERSION_GREATER_THAN_4_0 = Boolean.getBoolean("testLatestDeps");
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
private final JobRunner runner;
CustomSpanEventTest(JobRunner runner) {
this.runner = runner;
}
@Test
void shouldBeAbleToCallSpanCurrentAndAddCustomInfoToSpans() {
runner.runJob("customSpanEventsItemsJob");
testing.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> assertions =
new ArrayList<>(
Arrays.asList(
span ->
span.hasName("BatchJob customSpanEventsItemsJob")
.hasKind(SpanKind.INTERNAL)
.hasEventsSatisfyingExactly(
event -> event.hasName("job.before"),
event -> event.hasName("job.after")),
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0))
.satisfies(
spanData -> {
// CompositeChunkListener has broken ordering that causes
// listeners that do not override order() to appear first at all
// times because of that a custom ChunkListener will always see
// a Step span when using spring-batch versions [3, 4)
// that bug was fixed in 4.0
if (VERSION_GREATER_THAN_4_0) {
assertThat(spanData)
.hasEventsSatisfyingExactly(
event -> event.hasName("step.before"),
event -> event.hasName("step.after"));
} else {
assertThat(spanData)
.hasEventsSatisfyingExactly(
event -> event.hasName("step.before"),
event -> event.hasName("chunk.before"),
event -> event.hasName("chunk.after"),
event -> event.hasName("step.after"));
}
}),
span ->
span.hasName(
"BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1))
.satisfies(
spanData -> {
// CompositeChunkListener has broken ordering that causes
// listeners that do not override order() to appear first at all
// times because of that a custom ChunkListener will always see
// a Step span when using spring-batch versions [3, 4)
// that bug was fixed in 4.0
if (VERSION_GREATER_THAN_4_0) {
assertThat(spanData)
.hasEventsSatisfyingExactly(
event -> event.hasName("chunk.before"),
event -> event.hasName("chunk.after"));
} else {
assertThat(spanData.getEvents()).isEmpty();
}
})));
itemSpans(trace, assertions);
trace.hasSpansSatisfyingExactly(assertions);
});
}
protected void itemSpans(TraceAssert trace, List<Consumer<SpanDataAssert>> assertions) {
assertions.addAll(
Arrays.asList(
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)),
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)),
span ->
span.hasName(
"BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)),
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2))));
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.event;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.ApplicationConfigRunner;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.SpringBatchApplication;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
class JavaConfigCustomSpanEventTest extends CustomSpanEventTest {
@RegisterExtension
static final ApplicationConfigRunner runner =
new ApplicationConfigRunner(
() -> new AnnotationConfigApplicationContext(SpringBatchApplication.class));
JavaConfigCustomSpanEventTest() {
super(runner);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.event;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.JavaxBatchConfigRunner;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.extension.RegisterExtension;
class JsrConfigCustomSpanEventTest extends CustomSpanEventTest {
@RegisterExtension static final JavaxBatchConfigRunner runner = new JavaxBatchConfigRunner();
JsrConfigCustomSpanEventTest() {
super(runner);
}
@Override
protected void itemSpans(TraceAssert trace, List<Consumer<SpanDataAssert>> assertions) {
assertions.addAll(
Arrays.asList(
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2))
.hasEventsSatisfyingExactly(
event -> event.hasName("item.read.before"),
event -> event.hasName("item.read.after")),
span ->
span.hasName(
"BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2))
.hasEventsSatisfyingExactly(
event -> event.hasName("item.process.before"),
event -> event.hasName("item.process.after")),
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2))
.hasEventsSatisfyingExactly(event -> event.hasName("item.read.before")),
span ->
span.hasName("BatchJob customSpanEventsItemsJob.customSpanEventsItemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2))
.hasEventsSatisfyingExactly(
event -> event.hasName("item.write.before"),
event -> event.hasName("item.write.after"))));
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.event;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.ApplicationConfigRunner;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class XmlConfigCustomSpanEventTest extends CustomSpanEventTest {
@RegisterExtension
static final ApplicationConfigRunner runner =
new ApplicationConfigRunner(() -> new ClassPathXmlApplicationContext("spring-batch.xml"));
XmlConfigCustomSpanEventTest() {
super(runner);
}
}

View File

@ -0,0 +1,320 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.item;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.JobRunner;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.assertj.core.api.AssertAccess;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
abstract class ItemLevelSpanTest {
private final JobRunner runner;
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
ItemLevelSpanTest(JobRunner runner) {
this.runner = runner;
}
@Test
void shouldTraceItemReadProcessAndWriteCalls() {
runner.runJob("itemsAndTaskletJob");
testing.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> assertions = new ArrayList<>();
assertions.add(
span -> span.hasName("BatchJob itemsAndTaskletJob").hasKind(SpanKind.INTERNAL));
// item step
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
// chunk 1, items 0-5
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 3; i <= 7; i++) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
}
for (int i = 8; i <= 12; i++) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
}
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
// chunk 2, items 5-10
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 15; i <= 19; i++) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
}
for (int i = 20; i <= 24; i++) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
}
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
// chunk 3, items 10-13
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
// +1 for last read returning end of stream marker
for (int i = 27; i <= 30; i++) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(26)));
}
for (int i = 31; i <= 33; i++) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(26)));
}
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(26)));
// tasklet step
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.taskletStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.taskletStep.Tasklet")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(35)));
trace.hasSpansSatisfyingExactly(assertions);
});
}
@Test
void shouldTraceAllItemOperationsOnAparallelItemsJob() {
runner.runJob("parallelItemsJob");
testing.waitAndAssertTraces(
trace -> {
// as chunks are processed in parallel we need to sort them to guarantee that they are
// in the expected order
// firstly compute child span count for each chunk, we'll sort chunks from larger to
// smaller based on child count
List<SpanData> spans = AssertAccess.getActual(trace);
Map<SpanData, Long> childCount = new HashMap<>();
for (SpanData span : spans) {
if (span.getName().equals("BatchJob parallelItemsJob.parallelItemsStep.Chunk")) {
childCount.put(
span,
spans.stream()
.filter(it -> it.getParentSpanId().equals(span.getSpanId()))
.count());
}
}
spans.sort(
Comparator.comparingLong(
it -> {
// job span is first
if (it.getName().equals("BatchJob parallelItemsJob")) {
return 0;
}
// step span is second
if (it.getName().equals("BatchJob parallelItemsJob.parallelItemsStep")) {
return 1;
}
// find the chunk this span belongs to
SpanData chunkSpan = it;
while (chunkSpan != null
&& !chunkSpan
.getName()
.equals("BatchJob parallelItemsJob.parallelItemsStep.Chunk")) {
SpanData currentChunkSpan = chunkSpan;
chunkSpan =
spans.stream()
.filter(
candidate ->
candidate
.getSpanId()
.equals(currentChunkSpan.getParentSpanId()))
.findFirst()
.orElse(null);
}
if (chunkSpan != null) {
// sort larger chunks first
return 100 - childCount.get(chunkSpan);
}
throw new IllegalStateException("item spans should have a parent chunk span");
}));
List<Consumer<SpanDataAssert>> assertions = new ArrayList<>();
assertions.add(
span -> span.hasName("BatchJob parallelItemsJob").hasKind(SpanKind.INTERNAL));
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
// chunk 1, first two items; thread 1
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 3; i <= 4; i++) {
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
}
for (int i = 5; i <= 6; i++) {
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
}
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
// chunk 2, items 3 & 4; thread 2
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 9; i <= 10; i++) {
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(8)));
}
for (int i = 11; i <= 12; i++) {
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(8)));
}
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(8)));
// chunk 3, 5th item; thread 1
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
// +1 for last read returning end of stream marker
for (int i = 15; i <= 16; i++) {
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
}
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
assertions.add(
span ->
span.hasName("BatchJob parallelItemsJob.parallelItemsStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
trace.hasSpansSatisfyingExactly(assertions);
});
}
protected void postProcessParallelItemsJob(String jobName, Job job) {
if ("parallelItemsJob".equals(jobName)) {
Step step = ((AbstractJob) job).getStep("parallelItemsStep");
TaskletStep taskletStep = (TaskletStep) step;
// explicitly set the number of chunks we expect from this test to ensure we always get
// the same number of spans
try {
Field field = taskletStep.getClass().getDeclaredField("stepOperations");
field.setAccessible(true);
TaskExecutorRepeatTemplate stepOperations =
(TaskExecutorRepeatTemplate) field.get(taskletStep);
stepOperations.setCompletionPolicy(new SimpleCompletionPolicy(3));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.item;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.ApplicationConfigRunner;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class JavaConfigItemLevelSpanTest extends ItemLevelSpanTest {
static JavaConfigItemLevelSpanTest instance;
@RegisterExtension
static final ApplicationConfigRunner runner =
new ApplicationConfigRunner(
() -> new ClassPathXmlApplicationContext("spring-batch.xml"),
(jobName, job) -> instance.postProcessParallelItemsJob(jobName, job));
JavaConfigItemLevelSpanTest() {
super(runner);
instance = this;
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.item;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.JavaxBatchConfigRunner;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class JsrConfigItemLevelSpanTest {
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@RegisterExtension static final JavaxBatchConfigRunner runner = new JavaxBatchConfigRunner();
@Test
void shouldTraceItemReadProcessAndWriteCalls() {
runner.runJob("itemsAndTaskletJob");
testing.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> assertions = new ArrayList<>();
assertions.add(
span -> span.hasName("BatchJob itemsAndTaskletJob").hasKind(SpanKind.INTERNAL));
// item step
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
// chunk 1, items 0-5
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 3; i <= 11; i += 2) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
}
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(2)));
// chunk 2, items 6-10
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 15; i <= 23; i += 2) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
}
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(14)));
// chunk 3, items 11-13
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.Chunk")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(1)));
for (int i = 27; i <= 32; i += 2) {
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(26)));
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemProcess")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(26)));
}
// last read returning end of stream marker
assertions.add(span -> span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemRead"));
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.itemStep.ItemWrite")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(26)));
// tasklet step
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.taskletStep")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
assertions.add(
span ->
span.hasName("BatchJob itemsAndTaskletJob.taskletStep.Tasklet")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(35)));
trace.hasSpansSatisfyingExactly(assertions);
});
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.item;
import io.opentelemetry.javaagent.instrumentation.spring.batch.v3_0.runner.ApplicationConfigRunner;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class XmlConfigItemLevelSpanTest extends ItemLevelSpanTest {
static XmlConfigItemLevelSpanTest instance;
@RegisterExtension
static final ApplicationConfigRunner runner =
new ApplicationConfigRunner(
() -> new ClassPathXmlApplicationContext("spring-batch.xml"),
(jobName, job) -> instance.postProcessParallelItemsJob(jobName, job));
XmlConfigItemLevelSpanTest() {
super(runner);
instance = this;
}
}