Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ jobs:

steps:
- name: "Checkout Branch"
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: "Setup Java"
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'

- name: Setup Gradle cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('build.gradle') }}
Expand Down Expand Up @@ -59,16 +59,16 @@ jobs:

steps:
- name: "Checkout Branch"
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: "Setup Java"
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'

- name: Setup Gradle cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('build.gradle') }}
Expand All @@ -81,7 +81,7 @@ jobs:
RUN_LONG_INTEGRATION_TESTS: false

- name: Upload Gradle reports
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: ${{ failure() }}
with:
name: Test Backend Reports
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/inspect-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v6

# Gets the download URL associated with the $DETEKT_RELEASE_TAG
- name: Get Detekt download URL
Expand All @@ -62,7 +62,8 @@ jobs:
}
' | \
jq --raw-output '.data.repository.release.releaseAssets.nodes[0].downloadUrl' )
echo "::set-output name=download_url::$DETEKT_DOWNLOAD_URL"
echo "download_url=$DETEKT_DOWNLOAD_URL" >> $GITHUB_OUTPUT
echo "Detekt download URL: $DETEKT_DOWNLOAD_URL"

# Sets up the detekt cli
- name: Setup Detekt
Expand Down Expand Up @@ -96,7 +97,7 @@ jobs:
)" > ${{ github.workspace }}/detekt.sarif.json

# Uploads results to GitHub repository using the upload-sarif action
- uses: github/codeql-action/upload-sarif@v1
- uses: github/codeql-action/upload-sarif@v4
with:
# Path to SARIF file relative to the root of the repository
sarif_file: ${{ github.workspace }}/detekt.sarif.json
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/publish-jars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ jobs:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'adopt'

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v3.5.0
uses: gradle/actions/wrapper-validation@v6

- name: Publish Packages
uses: gradle/actions/setup-gradle@v3.5.0
uses: gradle/actions/setup-gradle@v6
with:
arguments: publishToSonatype closeAndReleaseSonatypeStagingRepository
env:
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=3.7.2
version=3.7.3
org.gradle.caching=true
org.gradle.parallel=true
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ public void testReinit() throws Exception {
*
* @throws Exception if any error occurs
*/
@Timeout(5)
@Timeout(20)
@Test
public void testParallelMethodCall() throws Exception {
final String scope = "/test/parallel";
Expand Down
1 change: 1 addition & 0 deletions module/communication/mqtt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
api(project(":jul.extension.type.processing"))
api("com.hivemq:hivemq-mqtt-client:_")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:_")
testImplementation(project(":jul.communication.mqtt.test"))
testImplementation("org.testcontainers:junit-jupiter:_") {
exclude(group = "junit", module = "junit")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ object SharedMqttClient : Shutdownable {
sharedClients.values
.filter { (it as Mqtt5ClientWrapper).isConnected() }
.map { it.disconnect() }
.map { it.get() }
.map {
runCatching { it.get() }
.getOrElse { t -> check(t.message == "com.hivemq.client.mqtt.exceptions.MqttClientStateException: MQTT client is not connected.") {
Comment thread
lhuxohl marked this conversation as resolved.
Dismissed
"Could not disconnect client connection: ${t.message}" }
null
}
}
.run { sharedClients.clear() }

@Synchronized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,77 +4,31 @@ import com.hivemq.client.mqtt.mqtt5.datatypes.Mqtt5UserProperties
import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Timeout
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.GenericContainer
import org.openbase.jul.communication.config.CommunicatorConfig
import org.openbase.jul.communication.mqtt.test.MqttBrokerManager
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName
import java.time.Duration
import kotlin.io.path.absolute
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeLines

@Testcontainers
abstract class AbstractIntegrationTest {
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
open class AbstractIntegrationTest {

// the companion object makes sure that the container is started once before all tests instead of restarting for every test
companion object {

private const val port: Int = 1883

private val mosquittoConfig = kotlin.io.path.createTempFile(prefix = "mosquitto_", suffix = ".conf")
private lateinit var broker: MqttBrokerContainer

private var usageCounter = 0
private val lock = Any()

@JvmStatic
@BeforeAll
@Timeout(30)
fun setup() {
synchronized(lock) {
if (usageCounter == 0) {
mosquittoConfig.writeLines(
listOf(
"allow_anonymous true",
"listener 1883"
)
)

broker = MqttBrokerContainer()
.withExposedPorts(port)
.withFileSystemBind(
mosquittoConfig.absolute().toString(),
"/mosquitto/config/mosquitto.conf",
BindMode.READ_ONLY
)
broker.withStartupTimeout(Duration.ofSeconds(30)).start()
}
usageCounter++
}
}

@JvmStatic
@AfterAll
@Timeout(30)
fun cleanup() {
synchronized(lock) {
usageCounter--
if (usageCounter == 0) {
SharedMqttClient.waitForShutdown()
broker.stop()
mosquittoConfig.deleteIfExists()
}
}
}
@BeforeAll
@Timeout(30)
fun setupMqtt() {
MqttBrokerManager.setupMqtt(this::class.java.simpleName)
}

protected val brokerHost: String get() = broker.host
protected val brokerHost: String? get() = MqttBrokerManager.broker?.host

protected val brokerPort: Int get() = broker.firstMappedPort
}
protected val brokerPort: Int? get() = MqttBrokerManager.broker?.firstMappedPort

class MqttBrokerContainer : GenericContainer<MqttBrokerContainer>(DockerImageName.parse("eclipse-mosquitto"))
protected val config
get() = CommunicatorConfig(
hostname = brokerHost ?: error("Host not defined!"),
port = brokerPort ?: error("Port not defined!"),
)
}

fun Mqtt5Publish.clearTimestamp() = let {
this.extend().userProperties(Mqtt5UserProperties.of()).build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import kotlin.concurrent.withLock
class IntegrationTest : AbstractIntegrationTest() {

private val scope = ScopeProcessor.generateScope("/test/integration")
private val config = CommunicatorConfig(brokerHost, brokerPort)

internal class MethodMock {
val lock = ReentrantLock()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.openbase.jul.communication.mqtt

import com.hivemq.client.internal.mqtt.util.MqttChecks.unsubscribe
import com.hivemq.client.mqtt.datatypes.MqttQos
import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish
import com.hivemq.client.mqtt.mqtt5.message.subscribe.Mqtt5Subscribe
import com.hivemq.client.mqtt.mqtt5.message.unsubscribe.Mqtt5Unsubscribe
import org.junit.jupiter.api.Test
Expand All @@ -15,7 +13,6 @@ internal class SharedMqttClientTest : AbstractIntegrationTest() {
@Test
@Timeout(value = 30)
fun `shutdown should be work as expected`() {
val config = CommunicatorConfig(brokerHost, brokerPort)
val client = SharedMqttClient
client.get(config).apply {
disconnect()
Expand All @@ -27,7 +24,6 @@ internal class SharedMqttClientTest : AbstractIntegrationTest() {
@Test
@Timeout(value = 30)
fun `subscription should be work as expected`() {
val config = CommunicatorConfig(brokerHost, brokerPort)
val topic = "/a/b/c"
val client = SharedMqttClient

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.openbase.jul.communication.mqtt.test

import org.openbase.jps.core.JPService
import org.openbase.jps.exception.JPServiceException
import org.openbase.jul.communication.jp.JPComHost
import org.openbase.jul.communication.jp.JPComPort
import org.openbase.jul.communication.mqtt.SharedMqttClient
import org.testcontainers.containers.GenericContainer
import org.testcontainers.utility.DockerImageName
import org.testcontainers.utility.MountableFile
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import kotlin.concurrent.Volatile
import kotlin.io.path.deleteIfExists

object MqttBrokerManager {

val STARTUP_TIMEOUT: Duration = Duration.ofSeconds(30)

var broker: GenericContainer<*>? = null
val lock = Any()

fun setupMqtt(testClassName: String, port: Int = 1883) {
synchronized(lock) {
if (broker == null) {
val mosquittoConfig: Path = Files.createTempFile("${testClassName}_mosquitto_", ".conf")
Files.write(
mosquittoConfig, listOf(
"allow_anonymous true",
"listener $port"
)
)
MqttBrokerContainer()
.withExposedPorts(port)
.withCopyFileToContainer(
MountableFile.forHostPath(mosquittoConfig.toString()),
"/mosquitto/config/mosquitto.conf"
)
.apply { withStartupTimeout(STARTUP_TIMEOUT).start() }
.also { broker = it }
.also { setupProperties() }
.also {
// Add shutdown hook to stop broker at JVM exit
Runtime.getRuntime().addShutdownHook(Thread {
synchronized(lock) {
broker?.stop()
}
})
}
mosquittoConfig.deleteIfExists()
}
}
}

@Throws(JPServiceException::class)
private fun setupProperties() {
JPService.reset()
JPService.registerProperty(JPComPort::class.java, broker!!.firstMappedPort)
JPService.registerProperty(JPComHost::class.java, broker!!.host)
JPService.setupJUnitTestMode()
}

class MqttBrokerContainer : GenericContainer<MqttBrokerContainer>(DockerImageName.parse("eclipse-mosquitto"))
}
Loading
Loading