This is an automated email from the ASF dual-hosted git repository.
chia7712 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git
The following commit(s) were added to refs/heads/trunk by this push:
new 190fd8946a4 KAFKA-20553 Remove Scala dependency from
streams:integration-tests (#22675)
190fd8946a4 is described below
commit 190fd8946a48f11865011c91797e7a618e0d6547
Author: PoAn Yang <[email protected]>
AuthorDate: Fri Jun 26 04:14:43 2026 +0900
KAFKA-20553 Remove Scala dependency from streams:integration-tests (#22675)
The `streams:integration-tests` module only kept the Scala plugin,
`streams-scala` and `scala-library` for four old Scala test files.
Removing it makes the module pure Java and simplifies the follow-up
testFixtures migration in KAFKA-20553.
Reviewers: Chia-Ping Tsai <[email protected]>
---
build.gradle | 24 --
...bleJoinScalaIntegrationTestImplicitSerdes.scala | 175 ---------------
.../kafka/streams/integration/WordCountTest.scala | 250 ---------------------
...StreamToTableJoinScalaIntegrationTestBase.scala | 144 ------------
.../utils/StreamToTableJoinTestData.scala | 60 -----
.../streams/scala/StreamToTableJoinTest.scala | 106 +++++++++
6 files changed, 106 insertions(+), 653 deletions(-)
diff --git a/build.gradle b/build.gradle
index 5b8c8e8c874..4d44740d9e6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3039,15 +3039,12 @@ project(':streams:streams-scala') {
}
project(':streams:integration-tests') {
- apply plugin: 'scala'
-
base {
archivesName = "kafka-streams-integration-tests"
}
dependencies {
implementation libs.slf4jApi
- implementation libs.scalaLibrary
testImplementation testFixtures(project(':clients'))
testImplementation project(':group-coordinator')
@@ -3056,7 +3053,6 @@ project(':streams:integration-tests') {
testImplementation testFixtures(project(':server-common'))
testImplementation project(':storage')
testImplementation project(':streams').sourceSets.test.output
- testImplementation project(':streams:streams-scala')
testImplementation project(':test-common:test-common-runtime')
testImplementation project(':tools')
testImplementation project(':transaction-coordinator')
@@ -3070,26 +3066,6 @@ project(':streams:integration-tests') {
testRuntimeOnly runtimeTestLibs
}
-
- sourceSets {
- // Set java/scala source folders in the `scala` block to enable joint
compilation
- main {
- java {
- srcDirs = []
- }
- scala {
- srcDirs = []
- }
- }
- test {
- java {
- srcDirs = []
- }
- scala {
- srcDirs = ["src/test/java", "src/test/scala"]
- }
- }
- }
}
project(':streams:test-utils') {
diff --git
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/StreamToTableJoinScalaIntegrationTestImplicitSerdes.scala
b/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/StreamToTableJoinScalaIntegrationTestImplicitSerdes.scala
deleted file mode 100644
index 4cfc811728e..00000000000
---
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/StreamToTableJoinScalaIntegrationTestImplicitSerdes.scala
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.kafka.streams.integration
-
-import
org.apache.kafka.streams.integration.utils.StreamToTableJoinScalaIntegrationTestBase
-import org.apache.kafka.streams.scala.ImplicitConversions._
-import org.apache.kafka.streams.scala.StreamsBuilder
-import org.apache.kafka.streams.scala.kstream._
-import org.apache.kafka.streams.scala.serialization.{Serdes => NewSerdes}
-import org.apache.kafka.streams.{KafkaStreams, KeyValue, StreamsConfig}
-import org.junit.jupiter.api.Assertions._
-import org.junit.jupiter.api._
-
-import java.util.Properties
-
-/**
- * Test suite that does an example to demonstrate stream-table joins in Kafka
Streams
- * <p>
- * The suite contains the test case using Scala APIs
`testShouldCountClicksPerRegion` and the same test case using the
- * Java APIs `testShouldCountClicksPerRegionJava`. The idea is to demonstrate
that both generate the same result.
- */
-@Tag("integration")
-class StreamToTableJoinScalaIntegrationTestImplicitSerdes extends
StreamToTableJoinScalaIntegrationTestBase {
-
- @Test def testShouldCountClicksPerRegion(): Unit = {
-
- // DefaultSerdes brings into scope implicit serdes (mostly for primitives)
that will set up all Grouped, Produced,
- // Consumed and Joined instances. So all APIs below that accept Grouped,
Produced, Consumed or Joined will
- // get these instances automatically
- import org.apache.kafka.streams.scala.serialization.Serdes._
-
- val streamsConfiguration: Properties = getStreamsConfiguration()
-
- val builder = new StreamsBuilder()
-
- val userClicksStream: KStream[String, Long] =
builder.stream(userClicksTopic)
-
- val userRegionsTable: KTable[String, String] =
builder.table(userRegionsTopic)
-
- // Compute the total per region by summing the individual click counts per
region.
- val clicksPerRegion: KTable[String, Long] =
- userClicksStream
-
- // Join the stream against the table.
- .leftJoin(userRegionsTable)((clicks, region) => (if (region == null)
"UNKNOWN" else region, clicks))
-
- // Change the stream from <user> -> <region, clicks> to <region> ->
<clicks>
- .map((_, regionWithClicks) => regionWithClicks)
-
- // Compute the total per region by summing the individual click counts
per region.
- .groupByKey
- .reduce(_ + _)
-
- // Write the (continuously updating) results to the output topic.
- clicksPerRegion.toStream.to(outputTopic)
-
- val streams: KafkaStreams = new KafkaStreams(builder.build(),
streamsConfiguration)
- streams.start()
-
- val actualClicksPerRegion: java.util.List[KeyValue[String, Long]] =
- produceNConsume(userClicksTopic, userRegionsTopic, outputTopic)
-
- assertTrue(!actualClicksPerRegion.isEmpty, "Expected to process some data")
-
- streams.close()
- }
-
- @Test
- def testShouldCountClicksPerRegionWithNamedRepartitionTopic(): Unit = {
-
- // DefaultSerdes brings into scope implicit serdes (mostly for primitives)
that will set up all Grouped, Produced,
- // Consumed and Joined instances. So all APIs below that accept Grouped,
Produced, Consumed or Joined will
- // get these instances automatically
- import org.apache.kafka.streams.scala.serialization.Serdes._
-
- val streamsConfiguration: Properties = getStreamsConfiguration()
-
- val builder = new StreamsBuilder()
-
- val userClicksStream: KStream[String, Long] =
builder.stream(userClicksTopic)
-
- val userRegionsTable: KTable[String, String] =
builder.table(userRegionsTopic)
-
- // Compute the total per region by summing the individual click counts per
region.
- val clicksPerRegion: KTable[String, Long] =
- userClicksStream
-
- // Join the stream against the table.
- .leftJoin(userRegionsTable)((clicks, region) => (if (region == null)
"UNKNOWN" else region, clicks))
-
- // Change the stream from <user> -> <region, clicks> to <region> ->
<clicks>
- .map((_, regionWithClicks) => regionWithClicks)
-
- // Compute the total per region by summing the individual click counts
per region.
- .groupByKey
- .reduce(_ + _)
-
- // Write the (continuously updating) results to the output topic.
- clicksPerRegion.toStream.to(outputTopic)
-
- val streams: KafkaStreams = new KafkaStreams(builder.build(),
streamsConfiguration)
- streams.start()
-
- val actualClicksPerRegion: java.util.List[KeyValue[String, Long]] =
- produceNConsume(userClicksTopic, userRegionsTopic, outputTopic)
-
- assertTrue(!actualClicksPerRegion.isEmpty, "Expected to process some data")
-
- streams.close()
- }
-
- @Test
- def testShouldCountClicksPerRegionJava(): Unit = {
-
- import org.apache.kafka.streams.kstream.{KStream => KStreamJ, KTable =>
KTableJ, _}
- import org.apache.kafka.streams.{KafkaStreams => KafkaStreamsJ,
StreamsBuilder => StreamsBuilderJ}
-
- import java.lang.{Long => JLong}
-
- val streamsConfiguration: Properties = getStreamsConfiguration()
-
- streamsConfiguration.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG,
NewSerdes.stringSerde.getClass.getName)
- streamsConfiguration.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,
NewSerdes.stringSerde.getClass.getName)
-
- val builder: StreamsBuilderJ = new StreamsBuilderJ()
-
- val userClicksStream: KStreamJ[String, JLong] =
- builder.stream[String, JLong](userClicksTopicJ,
Consumed.`with`(NewSerdes.stringSerde, NewSerdes.javaLongSerde))
-
- val userRegionsTable: KTableJ[String, String] =
- builder.table[String, String](userRegionsTopicJ,
Consumed.`with`(NewSerdes.stringSerde, NewSerdes.stringSerde))
-
- // Join the stream against the table.
- val valueJoinerJ: ValueJoiner[JLong, String, (String, JLong)] =
- (clicks: JLong, region: String) => (if (region == null) "UNKNOWN" else
region, clicks)
- val userClicksJoinRegion: KStreamJ[String, (String, JLong)] =
userClicksStream.leftJoin(
- userRegionsTable,
- valueJoinerJ,
- Joined.`with`[String, JLong, String](NewSerdes.stringSerde,
NewSerdes.javaLongSerde, NewSerdes.stringSerde)
- )
-
- // Change the stream from <user> -> <region, clicks> to <region> ->
<clicks>
- val clicksByRegion: KStreamJ[String, JLong] = userClicksJoinRegion.map {
(_, regionWithClicks) =>
- new KeyValue(regionWithClicks._1, regionWithClicks._2)
- }
-
- // Compute the total per region by summing the individual click counts per
region.
- val clicksPerRegion: KTableJ[String, JLong] = clicksByRegion
- .groupByKey(Grouped.`with`(NewSerdes.stringSerde,
NewSerdes.javaLongSerde))
- .reduce((v1, v2) => v1 + v2)
-
- // Write the (continuously updating) results to the output topic.
- clicksPerRegion.toStream.to(outputTopicJ,
Produced.`with`(NewSerdes.stringSerde, NewSerdes.javaLongSerde))
-
- val streams = new KafkaStreamsJ(builder.build(), streamsConfiguration)
-
- streams.start()
- produceNConsume(userClicksTopicJ, userRegionsTopicJ, outputTopicJ)
- streams.close()
- }
-}
diff --git
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/WordCountTest.scala
b/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/WordCountTest.scala
deleted file mode 100644
index 3e9813dda24..00000000000
---
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/WordCountTest.scala
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.kafka.streams.integration
-
-import java.util.Properties
-import java.util.regex.Pattern
-import org.junit.jupiter.api.Assertions._
-import org.junit.jupiter.api._
-import org.apache.kafka.streams.scala.serialization.{Serdes => NewSerdes}
-import org.apache.kafka.streams.scala.ImplicitConversions._
-import org.apache.kafka.streams.scala.StreamsBuilder
-import org.apache.kafka.streams.{KafkaStreams, KeyValue, StreamsConfig}
-import org.apache.kafka.streams.scala.kstream._
-import org.apache.kafka.streams.integration.utils.{EmbeddedKafkaCluster,
IntegrationTestUtils}
-import org.apache.kafka.clients.consumer.ConsumerConfig
-import org.apache.kafka.clients.producer.ProducerConfig
-import org.apache.kafka.common.utils.{MockTime, Utils}
-import org.apache.kafka.common.serialization.{LongDeserializer,
StringDeserializer, StringSerializer}
-import org.apache.kafka.test.TestUtils
-import org.junit.jupiter.api.Tag
-
-import java.io.File
-
-/**
- * Test suite that does a classic word count example.
- * <p>
- * The suite contains the test case using Scala APIs `testShouldCountWords`
and the same test case using the
- * Java APIs `testShouldCountWordsJava`. The idea is to demonstrate that both
generate the same result.
- */
-@Tag("integration")
-class WordCountTest extends WordCountTestData {
-
- private val cluster: EmbeddedKafkaCluster = new EmbeddedKafkaCluster(1)
-
- final private val alignedTime = (System.currentTimeMillis() / 1000 + 1) *
1000
- private val mockTime: MockTime = cluster.time
- mockTime.setCurrentTimeMs(alignedTime)
-
- private val testFolder: File = TestUtils.tempDirectory()
-
- @BeforeEach
- def startKafkaCluster(): Unit = {
- cluster.start()
- cluster.createTopic(inputTopic)
- cluster.createTopic(outputTopic)
- cluster.createTopic(inputTopicJ)
- cluster.createTopic(outputTopicJ)
- }
-
- @AfterEach
- def stopKafkaCluster(): Unit = {
- cluster.stop()
- Utils.delete(testFolder)
- }
-
- @Test
- def testShouldCountWords(): Unit = {
- import org.apache.kafka.streams.scala.serialization.Serdes._
-
- val streamsConfiguration = getStreamsConfiguration()
-
- val streamBuilder = new StreamsBuilder
- val textLines = streamBuilder.stream[String, String](inputTopic)
-
- val pattern = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS)
-
- // generate word counts
- val wordCounts: KTable[String, Long] =
- textLines
- .flatMapValues(v => pattern.split(v.toLowerCase))
- .groupBy((_, v) => v)
- .count()
-
- // write to output topic
- wordCounts.toStream.to(outputTopic)
-
- val streams = new KafkaStreams(streamBuilder.build(), streamsConfiguration)
- streams.start()
-
- // produce and consume synchronously
- val actualWordCounts: java.util.List[KeyValue[String, Long]] =
produceNConsume(inputTopic, outputTopic)
-
- streams.close()
-
- import scala.jdk.CollectionConverters._
-
assertEquals(actualWordCounts.asScala.take(expectedWordCounts.size).sortBy(_.key),
expectedWordCounts.sortBy(_.key))
- }
-
- @Test
- def testShouldCountWordsMaterialized(): Unit = {
- import org.apache.kafka.streams.scala.serialization.Serdes._
-
- val streamsConfiguration = getStreamsConfiguration()
-
- val streamBuilder = new StreamsBuilder
- val textLines = streamBuilder.stream[String, String](inputTopic)
-
- val pattern = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS)
-
- // generate word counts
- val wordCounts: KTable[String, Long] =
- textLines
- .flatMapValues(v => pattern.split(v.toLowerCase))
- .groupBy((k, v) => v)
- .count()(Materialized.as("word-count"))
-
- // write to output topic
- wordCounts.toStream.to(outputTopic)
-
- val streams = new KafkaStreams(streamBuilder.build(), streamsConfiguration)
- streams.start()
-
- // produce and consume synchronously
- val actualWordCounts: java.util.List[KeyValue[String, Long]] =
produceNConsume(inputTopic, outputTopic)
-
- streams.close()
-
- import scala.jdk.CollectionConverters._
-
assertEquals(actualWordCounts.asScala.take(expectedWordCounts.size).sortBy(_.key),
expectedWordCounts.sortBy(_.key))
- }
-
- @Test
- def testShouldCountWordsJava(): Unit = {
-
- import org.apache.kafka.streams.{KafkaStreams => KafkaStreamsJ,
StreamsBuilder => StreamsBuilderJ}
- import org.apache.kafka.streams.kstream.{
- KTable => KTableJ,
- KStream => KStreamJ,
- KGroupedStream => KGroupedStreamJ,
- _
- }
- import scala.jdk.CollectionConverters._
-
- val streamsConfiguration = getStreamsConfiguration()
- streamsConfiguration.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG,
NewSerdes.stringSerde.getClass.getName)
- streamsConfiguration.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,
NewSerdes.stringSerde.getClass.getName)
-
- val streamBuilder = new StreamsBuilderJ
- val textLines: KStreamJ[String, String] = streamBuilder.stream[String,
String](inputTopicJ)
-
- val pattern = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS)
-
- val splits: KStreamJ[String, String] = textLines.flatMapValues { line =>
- pattern.split(line.toLowerCase).toBuffer.asJava
- }
-
- val grouped: KGroupedStreamJ[String, String] = splits.groupBy { (_, v) =>
- v
- }
-
- val wordCounts: KTableJ[String, java.lang.Long] = grouped.count()
-
- wordCounts.toStream.to(outputTopicJ,
Produced.`with`(NewSerdes.stringSerde, NewSerdes.javaLongSerde))
-
- val streams: KafkaStreamsJ = new KafkaStreamsJ(streamBuilder.build(),
streamsConfiguration)
- streams.start()
-
- val actualWordCounts: java.util.List[KeyValue[String, Long]] =
produceNConsume(inputTopicJ, outputTopicJ)
-
- streams.close()
-
-
assertEquals(actualWordCounts.asScala.take(expectedWordCounts.size).sortBy(_.key),
expectedWordCounts.sortBy(_.key))
- }
-
- private def getStreamsConfiguration(): Properties = {
- val streamsConfiguration: Properties = new Properties()
-
- streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG,
"wordcount-test")
- streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,
cluster.bootstrapServers())
- streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, "10000")
- streamsConfiguration.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,
"earliest")
- streamsConfiguration.put(StreamsConfig.STATE_DIR_CONFIG,
testFolder.getPath)
- streamsConfiguration
- }
-
- private def getProducerConfig(): Properties = {
- val p = new Properties()
- p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, cluster.bootstrapServers())
- p.put(ProducerConfig.ACKS_CONFIG, "all")
- p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
classOf[StringSerializer])
- p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
classOf[StringSerializer])
- p
- }
-
- private def getConsumerConfig(): Properties = {
- val p = new Properties()
- p.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, cluster.bootstrapServers())
- p.put(ConsumerConfig.GROUP_ID_CONFIG,
"wordcount-scala-integration-test-standard-consumer")
- p.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
- p.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
classOf[StringDeserializer])
- p.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
classOf[LongDeserializer])
- p
- }
-
- private def produceNConsume(inputTopic: String, outputTopic: String):
java.util.List[KeyValue[String, Long]] = {
-
- val linesProducerConfig: Properties = getProducerConfig()
-
- import scala.jdk.CollectionConverters._
- IntegrationTestUtils.produceValuesSynchronously(inputTopic,
inputValues.asJava, linesProducerConfig, mockTime)
-
- val consumerConfig = getConsumerConfig()
-
- IntegrationTestUtils.waitUntilMinKeyValueRecordsReceived(consumerConfig,
outputTopic, expectedWordCounts.size)
- }
-}
-
-trait WordCountTestData {
- val inputTopic = s"inputTopic"
- val outputTopic = s"outputTopic"
- val inputTopicJ = s"inputTopicJ"
- val outputTopicJ = s"outputTopicJ"
-
- val inputValues = List(
- "Hello Kafka Streams",
- "All streams lead to Kafka",
- "Join Kafka Summit",
- "И теперь пошли русские слова"
- )
-
- val expectedWordCounts: List[KeyValue[String, Long]] = List(
- new KeyValue("hello", 1L),
- new KeyValue("all", 1L),
- new KeyValue("streams", 2L),
- new KeyValue("lead", 1L),
- new KeyValue("to", 1L),
- new KeyValue("join", 1L),
- new KeyValue("kafka", 3L),
- new KeyValue("summit", 1L),
- new KeyValue("и", 1L),
- new KeyValue("теперь", 1L),
- new KeyValue("пошли", 1L),
- new KeyValue("русские", 1L),
- new KeyValue("слова", 1L)
- )
-}
diff --git
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/utils/StreamToTableJoinScalaIntegrationTestBase.scala
b/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/utils/StreamToTableJoinScalaIntegrationTestBase.scala
deleted file mode 100644
index f3aec5784cd..00000000000
---
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/utils/StreamToTableJoinScalaIntegrationTestBase.scala
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.kafka.streams.integration.utils
-
-import org.apache.kafka.clients.consumer.ConsumerConfig
-import org.apache.kafka.clients.producer.ProducerConfig
-import org.apache.kafka.common.serialization._
-import org.apache.kafka.common.utils.{MockTime, Utils}
-import org.apache.kafka.streams._
-import org.apache.kafka.test.TestUtils
-import org.junit.jupiter.api._
-
-import java.io.File
-import java.util.Properties
-
-/**
- * Test suite base that prepares Kafka cluster for stream-table joins in Kafka
Streams
- * <p>
- */
-@Tag("integration")
-class StreamToTableJoinScalaIntegrationTestBase extends
StreamToTableJoinTestData {
-
- private val cluster: EmbeddedKafkaCluster = new EmbeddedKafkaCluster(1)
-
- final private val alignedTime = (System.currentTimeMillis() / 1000 + 1) *
1000
- private val mockTime: MockTime = cluster.time
- mockTime.setCurrentTimeMs(alignedTime)
-
- private val testFolder: File = TestUtils.tempDirectory()
-
- @BeforeEach
- def startKafkaCluster(): Unit = {
- cluster.start()
- cluster.createTopic(userClicksTopic)
- cluster.createTopic(userRegionsTopic)
- cluster.createTopic(outputTopic)
- cluster.createTopic(userClicksTopicJ)
- cluster.createTopic(userRegionsTopicJ)
- cluster.createTopic(outputTopicJ)
- }
-
- @AfterEach
- def stopKafkaCluster(): Unit = {
- cluster.stop()
- Utils.delete(testFolder)
- }
-
- def getStreamsConfiguration(): Properties = {
- val streamsConfiguration: Properties = new Properties()
-
- streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG,
"stream-table-join-scala-integration-test")
- streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,
cluster.bootstrapServers())
- streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, "1000")
- streamsConfiguration.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,
"earliest")
- streamsConfiguration.put(StreamsConfig.STATE_DIR_CONFIG,
testFolder.getPath)
-
- streamsConfiguration
- }
-
- private def getUserRegionsProducerConfig(): Properties = {
- val p = new Properties()
- p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, cluster.bootstrapServers())
- p.put(ProducerConfig.ACKS_CONFIG, "all")
- p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
classOf[StringSerializer])
- p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
classOf[StringSerializer])
- p
- }
-
- private def getUserClicksProducerConfig(): Properties = {
- val p = new Properties()
- p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, cluster.bootstrapServers())
- p.put(ProducerConfig.ACKS_CONFIG, "all")
- p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
classOf[StringSerializer])
- p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
classOf[LongSerializer])
- p
- }
-
- private def getConsumerConfig(): Properties = {
- val p = new Properties()
- p.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, cluster.bootstrapServers())
- p.put(ConsumerConfig.GROUP_ID_CONFIG,
"join-scala-integration-test-standard-consumer")
- p.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
- p.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
classOf[StringDeserializer])
- p.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
classOf[LongDeserializer])
- p
- }
-
- def produceNConsume(
- userClicksTopic: String,
- userRegionsTopic: String,
- outputTopic: String,
- waitTillRecordsReceived: Boolean = true
- ): java.util.List[KeyValue[String, Long]] = {
-
- import _root_.scala.jdk.CollectionConverters._
-
- // Publish user-region information.
- val userRegionsProducerConfig: Properties = getUserRegionsProducerConfig()
- IntegrationTestUtils.produceKeyValuesSynchronously(
- userRegionsTopic,
- userRegions.asJava,
- userRegionsProducerConfig,
- mockTime,
- false
- )
-
- // Publish user-click information.
- val userClicksProducerConfig: Properties = getUserClicksProducerConfig()
- IntegrationTestUtils.produceKeyValuesSynchronously(
- userClicksTopic,
- userClicks.asJava,
- userClicksProducerConfig,
- mockTime,
- false
- )
-
- if (waitTillRecordsReceived) {
- // consume and verify result
- val consumerConfig = getConsumerConfig()
-
- IntegrationTestUtils.waitUntilFinalKeyValueRecordsReceived(
- consumerConfig,
- outputTopic,
- expectedClicksPerRegion.asJava
- )
- } else {
- java.util.Collections.emptyList()
- }
- }
-}
diff --git
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/utils/StreamToTableJoinTestData.scala
b/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/utils/StreamToTableJoinTestData.scala
deleted file mode 100644
index 4e8a2f024a7..00000000000
---
a/streams/integration-tests/src/test/scala/org/apache/kafka/streams/integration/utils/StreamToTableJoinTestData.scala
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.kafka.streams.integration.utils
-
-import org.apache.kafka.streams.KeyValue
-
-trait StreamToTableJoinTestData {
- val brokers = "localhost:9092"
-
- val userClicksTopic = s"user-clicks"
- val userRegionsTopic = s"user-regions"
- val outputTopic = s"output-topic"
-
- val userClicksTopicJ = s"user-clicks-j"
- val userRegionsTopicJ = s"user-regions-j"
- val outputTopicJ = s"output-topic-j"
-
- // Input 1: Clicks per user (multiple records allowed per user).
- val userClicks: Seq[KeyValue[String, Long]] = Seq(
- new KeyValue("alice", 13L),
- new KeyValue("bob", 4L),
- new KeyValue("chao", 25L),
- new KeyValue("bob", 19L),
- new KeyValue("dave", 56L),
- new KeyValue("eve", 78L),
- new KeyValue("alice", 40L),
- new KeyValue("fang", 99L)
- )
-
- // Input 2: Region per user (multiple records allowed per user).
- val userRegions: Seq[KeyValue[String, String]] = Seq(
- new KeyValue("alice", "asia"), /* Alice lived in Asia originally... */
- new KeyValue("bob", "americas"),
- new KeyValue("chao", "asia"),
- new KeyValue("dave", "europe"),
- new KeyValue("alice", "europe"), /* ...but moved to Europe some time
later. */
- new KeyValue("eve", "americas"),
- new KeyValue("fang", "asia")
- )
-
- val expectedClicksPerRegion: Seq[KeyValue[String, Long]] = Seq(
- new KeyValue("americas", 101L),
- new KeyValue("europe", 109L),
- new KeyValue("asia", 124L)
- )
-}
diff --git
a/streams/streams-scala/src/test/scala/org/apache/kafka/streams/scala/StreamToTableJoinTest.scala
b/streams/streams-scala/src/test/scala/org/apache/kafka/streams/scala/StreamToTableJoinTest.scala
new file mode 100644
index 00000000000..a104b6cf3f4
--- /dev/null
+++
b/streams/streams-scala/src/test/scala/org/apache/kafka/streams/scala/StreamToTableJoinTest.scala
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.streams.scala
+
+import org.apache.kafka.streams.scala.ImplicitConversions._
+import org.apache.kafka.streams.scala.kstream._
+import org.apache.kafka.streams.scala.serialization.Serdes._
+import org.apache.kafka.streams.scala.utils.TestDriver
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+import scala.jdk.CollectionConverters._
+
+/**
+ * Test suite that demonstrates a stream-table join in Kafka Streams using the
Scala API with implicit serdes.
+ */
+class StreamToTableJoinTest extends TestDriver {
+
+ private val userClicksTopic = "user-clicks"
+ private val userRegionsTopic = "user-regions"
+ private val outputTopic = "output-topic"
+
+ // Input 1: Clicks per user (multiple records allowed per user).
+ private val userClicks: Seq[(String, Long)] = Seq(
+ "alice" -> 13L,
+ "bob" -> 4L,
+ "chao" -> 25L,
+ "bob" -> 19L,
+ "dave" -> 56L,
+ "eve" -> 78L,
+ "alice" -> 40L,
+ "fang" -> 99L
+ )
+
+ // Input 2: Region per user (multiple records allowed per user).
+ private val userRegions: Seq[(String, String)] = Seq(
+ "alice" -> "asia",
+ "bob" -> "americas",
+ "chao" -> "asia",
+ "dave" -> "europe",
+ "alice" -> "europe",
+ "eve" -> "americas",
+ "fang" -> "asia"
+ )
+
+ private val expectedClicksPerRegion: Map[String, Long] = Map(
+ "americas" -> 101L,
+ "europe" -> 109L,
+ "asia" -> 124L
+ )
+
+ @Test
+ def testShouldCountClicksPerRegionWithImplicitSerdes(): Unit = {
+ // DefaultSerdes brings into scope implicit serdes (mostly for primitives)
that will set up all Grouped, Produced,
+ // Consumed and Joined instances. So all APIs below that accept Grouped,
Produced, Consumed or Joined will
+ // get these instances automatically.
+ val builder = new StreamsBuilder()
+
+ val userClicksStream: KStream[String, Long] =
builder.stream(userClicksTopic)
+
+ val userRegionsTable: KTable[String, String] =
builder.table(userRegionsTopic)
+
+ // Compute the total per region by summing the individual click counts per
region.
+ val clicksPerRegion: KTable[String, Long] =
+ userClicksStream
+
+ // Join the stream against the table.
+ .leftJoin(userRegionsTable)((clicks, region) => (if (region == null)
"UNKNOWN" else region, clicks))
+
+ // Change the stream from <user> -> <region, clicks> to <region> ->
<clicks>
+ .map((_, regionWithClicks) => regionWithClicks)
+
+ // Compute the total per region by summing the individual click counts
per region.
+ .groupByKey
+ .reduce(_ + _)
+
+ // Write the (continuously updating) results to the output topic.
+ clicksPerRegion.toStream.to(outputTopic)
+
+ val testDriver = createTestDriver(builder)
+ val userRegionsInput = testDriver.createInput[String,
String](userRegionsTopic)
+ val userClicksInput = testDriver.createInput[String, Long](userClicksTopic)
+ val output = testDriver.createOutput[String, Long](outputTopic)
+
+ userRegions.foreach { case (user, region) =>
userRegionsInput.pipeInput(user, region) }
+ userClicks.foreach { case (user, clicks) =>
userClicksInput.pipeInput(user, clicks) }
+
+ assertEquals(expectedClicksPerRegion.asJava, output.readKeyValuesToMap())
+
+ testDriver.close()
+ }
+}