2020-08-19 15:29:35 +02:00
|
|
|
/*
|
2023-03-01 00:22:11 +01:00
|
|
|
* Copyright 2023 dorkbox, llc
|
2020-08-19 15:29:35 +02:00
|
|
|
*
|
|
|
|
* Licensed 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.
|
2020-07-03 01:45:18 +02:00
|
|
|
*/
|
2020-08-12 23:38:56 +02:00
|
|
|
package dorkboxTest.network
|
2020-07-03 01:45:18 +02:00
|
|
|
|
|
|
|
import ch.qos.logback.classic.Level
|
|
|
|
import ch.qos.logback.classic.Logger
|
2022-08-02 12:11:36 +02:00
|
|
|
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
|
2020-07-03 01:45:18 +02:00
|
|
|
import ch.qos.logback.classic.joran.JoranConfigurator
|
2022-08-02 12:11:36 +02:00
|
|
|
import ch.qos.logback.classic.spi.ILoggingEvent
|
|
|
|
import ch.qos.logback.core.ConsoleAppender
|
2023-06-26 19:28:55 +02:00
|
|
|
import dorkbox.network.*
|
2023-05-28 18:41:46 +02:00
|
|
|
import dorkbox.network.aeron.AeronDriver
|
|
|
|
import dorkbox.network.connection.Connection
|
2020-07-03 01:45:18 +02:00
|
|
|
import dorkbox.network.connection.EndPoint
|
2023-06-25 17:25:29 +02:00
|
|
|
import dorkbox.network.connection.EventDispatcher
|
2020-08-12 23:38:56 +02:00
|
|
|
import dorkbox.os.OS
|
2021-08-23 08:39:55 +02:00
|
|
|
import dorkbox.storage.Storage
|
2020-07-03 01:45:18 +02:00
|
|
|
import dorkbox.util.entropy.Entropy
|
|
|
|
import dorkbox.util.entropy.SimpleEntropy
|
|
|
|
import dorkbox.util.exceptions.InitializationException
|
2023-06-26 19:28:55 +02:00
|
|
|
import kotlinx.coroutines.*
|
2020-07-03 01:45:18 +02:00
|
|
|
import org.junit.After
|
|
|
|
import org.junit.Assert
|
|
|
|
import org.junit.Before
|
|
|
|
import org.slf4j.LoggerFactory
|
|
|
|
import java.lang.reflect.Field
|
|
|
|
import java.lang.reflect.Method
|
2022-03-15 12:44:52 +01:00
|
|
|
import java.util.concurrent.*
|
2020-07-03 01:45:18 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
@OptIn(DelicateCoroutinesApi::class)
|
2020-07-03 01:45:18 +02:00
|
|
|
abstract class BaseTest {
|
|
|
|
companion object {
|
2022-03-15 12:44:52 +01:00
|
|
|
const val LOCALHOST = "localhost"
|
2022-05-30 02:45:50 +02:00
|
|
|
|
2022-08-02 21:15:02 +02:00
|
|
|
// wait minimum of 3 minutes before we automatically fail the unit test.
|
|
|
|
var AUTO_FAIL_TIMEOUT: Long = 180L
|
2022-05-30 02:45:50 +02:00
|
|
|
|
|
|
|
init {
|
|
|
|
if (OS.javaVersion >= 9) {
|
|
|
|
// disableAccessWarnings
|
|
|
|
try {
|
|
|
|
val unsafeClass = Class.forName("sun.misc.Unsafe")
|
|
|
|
val field: Field = unsafeClass.getDeclaredField("theUnsafe")
|
|
|
|
field.isAccessible = true
|
|
|
|
val unsafe: Any = field.get(null)
|
|
|
|
val putObjectVolatile: Method = unsafeClass.getDeclaredMethod("putObjectVolatile", Any::class.java, Long::class.javaPrimitiveType, Any::class.java)
|
|
|
|
val staticFieldOffset: Method = unsafeClass.getDeclaredMethod("staticFieldOffset", Field::class.java)
|
|
|
|
val loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger")
|
|
|
|
val loggerField: Field = loggerClass.getDeclaredField("logger")
|
|
|
|
val offset = staticFieldOffset.invoke(unsafe, loggerField) as Long
|
|
|
|
putObjectVolatile.invoke(unsafe, loggerClass, offset, null)
|
|
|
|
} catch (ignored: Exception) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (System.getProperty("logback.configurationFile") == null) {
|
|
|
|
// val file = File("logback.xml")
|
|
|
|
// if (file.canRead()) {
|
|
|
|
// System.setProperty("logback.configurationFile", file.toPath().toRealPath().toFile().toString())
|
|
|
|
// } else {
|
|
|
|
// System.setProperty("logback.configurationFile", "logback.xml")
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
2023-03-01 00:22:11 +01:00
|
|
|
// setLogLevel(Level.TRACE)
|
2022-05-30 02:45:50 +02:00
|
|
|
// setLogLevel(Level.ERROR)
|
2022-08-02 12:11:36 +02:00
|
|
|
// setLogLevel(Level.DEBUG)
|
2022-05-30 02:45:50 +02:00
|
|
|
|
|
|
|
// we want our entropy generation to be simple (ie, no user interaction to generate)
|
|
|
|
try {
|
|
|
|
Entropy.init(SimpleEntropy::class.java)
|
|
|
|
} catch (e: InitializationException) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 04:52:11 +02:00
|
|
|
fun clientConfig(block: Configuration.() -> Unit = {}): ClientConfiguration {
|
2021-04-30 18:22:38 +02:00
|
|
|
|
2022-07-29 04:52:11 +02:00
|
|
|
val configuration = ClientConfiguration()
|
2021-08-23 08:39:55 +02:00
|
|
|
configuration.settingsStore = Storage.Memory() // don't want to persist anything on disk!
|
2020-07-03 01:45:18 +02:00
|
|
|
|
2021-04-30 18:22:38 +02:00
|
|
|
configuration.enableIpc = false
|
2023-06-25 17:25:29 +02:00
|
|
|
configuration.enableIPv6 = false
|
2021-04-30 18:22:38 +02:00
|
|
|
|
2021-04-29 11:26:34 +02:00
|
|
|
block(configuration)
|
2020-07-03 01:45:18 +02:00
|
|
|
return configuration
|
|
|
|
}
|
|
|
|
|
2021-04-29 10:01:37 +02:00
|
|
|
fun serverConfig(block: ServerConfiguration.() -> Unit = {}): ServerConfiguration {
|
2020-07-03 01:45:18 +02:00
|
|
|
val configuration = ServerConfiguration()
|
2021-08-23 08:39:55 +02:00
|
|
|
configuration.settingsStore = Storage.Memory() // don't want to persist anything on disk!
|
2020-07-03 01:45:18 +02:00
|
|
|
|
2021-04-30 18:22:38 +02:00
|
|
|
configuration.enableIpc = false
|
2023-06-25 17:25:29 +02:00
|
|
|
configuration.enableIPv6 = false
|
2022-08-02 12:11:36 +02:00
|
|
|
|
|
|
|
configuration.maxClientCount = 50
|
|
|
|
configuration.maxConnectionsPerIpAddress = 50
|
2020-07-03 01:45:18 +02:00
|
|
|
|
2021-04-29 11:26:34 +02:00
|
|
|
block(configuration)
|
2020-07-03 01:45:18 +02:00
|
|
|
return configuration
|
|
|
|
}
|
|
|
|
|
2021-04-30 18:22:38 +02:00
|
|
|
fun setLogLevel(level: Level) {
|
2021-07-26 20:16:10 +02:00
|
|
|
println("Log level: $level")
|
|
|
|
|
2021-04-30 18:22:38 +02:00
|
|
|
// assume SLF4J is bound to logback in the current environment
|
|
|
|
val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger
|
2022-05-30 02:45:50 +02:00
|
|
|
rootLogger.detachAndStopAllAppenders()
|
2021-04-30 18:22:38 +02:00
|
|
|
|
|
|
|
val context = rootLogger.loggerContext
|
|
|
|
|
2022-05-30 02:45:50 +02:00
|
|
|
val jc = JoranConfigurator()
|
2021-04-30 18:22:38 +02:00
|
|
|
jc.context = context
|
2022-08-04 03:39:14 +02:00
|
|
|
// jc.doConfigure(File("logback.xml").absoluteFile)
|
2022-08-02 12:11:36 +02:00
|
|
|
context.reset() // override default configuration
|
|
|
|
|
|
|
|
val encoder = PatternLayoutEncoder()
|
|
|
|
encoder.context = context
|
2023-03-17 15:05:59 +01:00
|
|
|
encoder.pattern = "%date{HH:mm:ss.SSS} %-5level [%logger{35}] [%t] %msg%n"
|
2022-08-02 12:11:36 +02:00
|
|
|
encoder.start()
|
2023-02-08 22:27:53 +01:00
|
|
|
|
2022-08-02 12:11:36 +02:00
|
|
|
val consoleAppender = ConsoleAppender<ILoggingEvent>()
|
|
|
|
consoleAppender.context = context
|
|
|
|
consoleAppender.encoder = encoder
|
|
|
|
consoleAppender.start()
|
2023-02-08 22:27:53 +01:00
|
|
|
|
|
|
|
|
2022-08-02 12:11:36 +02:00
|
|
|
rootLogger.addAppender(consoleAppender)
|
2023-02-08 22:27:53 +01:00
|
|
|
|
|
|
|
// modify the level AFTER we setup the context!
|
|
|
|
|
|
|
|
rootLogger.level = level
|
|
|
|
|
|
|
|
// we only want error messages
|
|
|
|
val nettyLogger = LoggerFactory.getLogger("io.netty") as Logger
|
|
|
|
nettyLogger.level = Level.ERROR
|
|
|
|
|
|
|
|
// we only want error messages
|
|
|
|
val kryoLogger = LoggerFactory.getLogger("com.esotericsoftware") as Logger
|
|
|
|
kryoLogger.level = Level.ERROR
|
2021-04-30 18:22:38 +02:00
|
|
|
}
|
2022-05-30 02:45:50 +02:00
|
|
|
}
|
2021-04-30 18:22:38 +02:00
|
|
|
|
2022-05-30 02:45:50 +02:00
|
|
|
@Volatile
|
|
|
|
private var autoFailThread: Thread? = null
|
2020-07-03 01:45:18 +02:00
|
|
|
|
|
|
|
private val endPointConnections: MutableList<EndPoint<*>> = CopyOnWriteArrayList()
|
|
|
|
|
|
|
|
@Volatile
|
|
|
|
private var isStopping = false
|
|
|
|
|
2023-05-28 18:41:46 +02:00
|
|
|
private val logger: org.slf4j.Logger = LoggerFactory.getLogger(this.javaClass.simpleName)!!
|
|
|
|
|
2020-09-03 15:01:24 +02:00
|
|
|
init {
|
2023-05-28 18:41:46 +02:00
|
|
|
setLogLevel(Level.TRACE)
|
|
|
|
|
|
|
|
logger.error("---- " + this.javaClass.simpleName)
|
2021-07-06 15:38:53 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
AeronDriver.checkForMemoryLeaks()
|
2020-09-03 15:01:24 +02:00
|
|
|
}
|
|
|
|
|
2023-05-28 18:41:46 +02:00
|
|
|
|
|
|
|
fun addEndPoint(endPoint: EndPoint<*>) {
|
2023-06-25 17:25:29 +02:00
|
|
|
endPoint.onInit { logger.error { "UNIT TEST: init" } }
|
|
|
|
endPoint.onConnect { logger.error { "UNIT TEST: connect" } }
|
|
|
|
endPoint.onDisconnect { logger.error { "UNIT TEST: disconnect" } }
|
2023-05-28 18:41:46 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
endPoint.onError { logger.error(it) { "UNIT TEST: ERROR!" } }
|
2023-05-28 18:41:46 +02:00
|
|
|
|
|
|
|
endPointConnections.add(endPoint)
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Immediately stop the endpoints
|
2023-06-16 14:15:27 +02:00
|
|
|
*
|
|
|
|
* Can stop from inside different callbacks
|
|
|
|
* - message
|
|
|
|
* - connect
|
|
|
|
* - disconnect
|
2020-07-03 01:45:18 +02:00
|
|
|
*/
|
2023-06-16 14:15:27 +02:00
|
|
|
fun stopEndPointsBlocking(stopAfterMillis: Long = 0L) {
|
2023-05-28 18:41:46 +02:00
|
|
|
runBlocking {
|
2023-06-16 14:15:27 +02:00
|
|
|
stopEndPoints(stopAfterMillis)
|
2023-05-28 18:41:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Immediately stop the endpoints
|
2023-06-16 14:15:27 +02:00
|
|
|
*
|
|
|
|
* Can stop from inside different callbacks
|
|
|
|
* - message
|
|
|
|
* - connect
|
|
|
|
* - disconnect
|
2023-05-28 18:41:46 +02:00
|
|
|
*/
|
2023-06-16 14:15:27 +02:00
|
|
|
suspend fun stopEndPoints(stopAfterMillis: Long = 0L) {
|
2020-07-03 01:45:18 +02:00
|
|
|
if (isStopping) {
|
|
|
|
return
|
|
|
|
}
|
2023-06-25 17:25:29 +02:00
|
|
|
|
|
|
|
if (EventDispatcher.isCurrentEvent()) {
|
|
|
|
// we want to redispatch, in the event we are already running inside the event dispatch
|
|
|
|
// this gives us the chance to properly exit/close WITHOUT blocking currentEventDispatch
|
|
|
|
// during the `waitForClose()` call
|
|
|
|
GlobalScope.launch {
|
|
|
|
stopEndPoints(stopAfterMillis)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:45:18 +02:00
|
|
|
isStopping = true
|
|
|
|
|
2021-07-26 20:16:10 +02:00
|
|
|
if (stopAfterMillis > 0L) {
|
2023-05-28 18:41:46 +02:00
|
|
|
delay(stopAfterMillis)
|
2021-07-26 20:16:10 +02:00
|
|
|
}
|
|
|
|
|
2023-05-28 18:41:46 +02:00
|
|
|
val clients = endPointConnections.filterIsInstance<Client<Connection>>()
|
|
|
|
val servers = endPointConnections.filterIsInstance<Server<Connection>>()
|
|
|
|
|
|
|
|
logger.error("Unit test shutting down ${clients.size} clients...")
|
|
|
|
logger.error("Unit test shutting down ${servers.size} server...")
|
2020-07-03 01:45:18 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
val timeoutMS = 0L
|
|
|
|
// val timeoutMS = if (EndPoint.DEBUG_CONNECTIONS) {
|
|
|
|
// Long.MAX_VALUE
|
|
|
|
// } else {
|
|
|
|
// TimeUnit.SECONDS.toMillis(AUTO_FAIL_TIMEOUT)
|
|
|
|
// }
|
|
|
|
|
|
|
|
var success = true
|
2020-07-03 01:45:18 +02:00
|
|
|
|
|
|
|
// shutdown clients first
|
2023-05-28 18:41:46 +02:00
|
|
|
clients.forEach { endPoint ->
|
|
|
|
// we are ASYNC, so we must use callbacks to execute code
|
2023-06-25 17:25:29 +02:00
|
|
|
endPoint.close()
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
2023-05-28 18:41:46 +02:00
|
|
|
clients.forEach { endPoint ->
|
2023-06-25 17:25:29 +02:00
|
|
|
success = success && endPoint.waitForClose(timeoutMS)
|
|
|
|
endPoint.stopDriver()
|
2023-05-28 18:41:46 +02:00
|
|
|
}
|
|
|
|
|
2021-07-26 20:16:10 +02:00
|
|
|
|
|
|
|
// shutdown everything else (should only be servers) last
|
2023-05-28 18:41:46 +02:00
|
|
|
servers.forEach {
|
2023-06-25 17:25:29 +02:00
|
|
|
it.close()
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
2023-05-28 18:41:46 +02:00
|
|
|
servers.forEach { endPoint ->
|
2023-06-25 17:25:29 +02:00
|
|
|
success = success && endPoint.waitForClose(timeoutMS)
|
|
|
|
endPoint.stopDriver()
|
2023-05-28 18:41:46 +02:00
|
|
|
}
|
|
|
|
|
2020-07-03 01:45:18 +02:00
|
|
|
endPointConnections.clear()
|
2023-05-28 18:41:46 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
logger.error("UNIT TEST, checking driver and memory leaks")
|
2023-05-28 18:41:46 +02:00
|
|
|
|
2023-06-16 14:15:27 +02:00
|
|
|
// have to make sure that the aeron driver is CLOSED.
|
|
|
|
Assert.assertTrue("The aeron drivers are not fully closed!", AeronDriver.areAllInstancesClosed())
|
2023-06-25 17:25:29 +02:00
|
|
|
AeronDriver.checkForMemoryLeaks()
|
2023-06-16 14:15:27 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
logger.error("Shut down all endpoints... Success($success)")
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
|
|
|
/**
|
2023-05-28 18:41:46 +02:00
|
|
|
* Wait for network client/server threads to shut down for the specified time. 0 will wait forever
|
2020-07-03 01:45:18 +02:00
|
|
|
*
|
2020-09-23 15:59:09 +02:00
|
|
|
* it should close as close to naturally as possible, otherwise there are problems
|
|
|
|
*
|
2020-07-03 01:45:18 +02:00
|
|
|
* @param stopAfterSeconds how many seconds to wait, the default is 2 minutes.
|
|
|
|
*/
|
2023-06-25 17:25:29 +02:00
|
|
|
fun waitForThreads(stopAfterSeconds: Long = AUTO_FAIL_TIMEOUT) = runBlocking {
|
|
|
|
val clients = endPointConnections.filterIsInstance<Client<Connection>>()
|
|
|
|
val servers = endPointConnections.filterIsInstance<Server<Connection>>()
|
|
|
|
|
|
|
|
val timeoutMS = 0L
|
|
|
|
// val timeoutMS = if (stopAfterSeconds == 0L || EndPoint.DEBUG_CONNECTIONS) {
|
|
|
|
// Long.MAX_VALUE
|
|
|
|
// } else {
|
|
|
|
// TimeUnit.SECONDS.toMillis(stopAfterSeconds)
|
|
|
|
// }
|
|
|
|
|
|
|
|
var success = true
|
|
|
|
|
|
|
|
clients.forEach { endPoint ->
|
|
|
|
success = success && endPoint.waitForClose(timeoutMS)
|
|
|
|
endPoint.stopDriver()
|
|
|
|
}
|
|
|
|
servers.forEach { endPoint ->
|
|
|
|
success = success && endPoint.waitForClose(timeoutMS)
|
|
|
|
endPoint.stopDriver()
|
2022-07-18 05:01:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// run actions before we actually shutdown, but after we wait
|
2023-06-25 17:25:29 +02:00
|
|
|
if (!success) {
|
|
|
|
Assert.fail("Shutdown latch not triggered!")
|
2023-05-28 18:41:46 +02:00
|
|
|
}
|
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
if (!AeronDriver.areAllInstancesClosed(logger)) {
|
|
|
|
throw RuntimeException("Unable to shutdown! There are still Aeron drivers loaded!")
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
2022-04-04 23:22:39 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
// we must always make sure that aeron is shut-down before starting again.
|
|
|
|
if (!Server.ensureStopped(serverConfig()) || !Client.ensureStopped(clientConfig())) {
|
|
|
|
throw IllegalStateException("Unable to continue, AERON was unable to stop.")
|
2023-05-28 18:41:46 +02:00
|
|
|
}
|
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
AeronDriver.checkForMemoryLeaks()
|
2023-05-28 18:41:46 +02:00
|
|
|
|
2023-06-25 17:25:29 +02:00
|
|
|
logger.error("Finished shutting down all endpoints... ($success)")
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Before
|
|
|
|
fun setupFailureCheck() {
|
2023-05-28 18:41:46 +02:00
|
|
|
autoFailThread = Thread({
|
2020-07-03 01:45:18 +02:00
|
|
|
// not the best, but this works for our purposes. This is a TAD hacky, because we ALSO have to make sure that we
|
|
|
|
// ARE NOT in the same thread group as netty!
|
|
|
|
try {
|
|
|
|
Thread.sleep(AUTO_FAIL_TIMEOUT * 1000L)
|
|
|
|
|
|
|
|
// if the thread is interrupted, then it means we finished the test.
|
2023-05-28 18:41:46 +02:00
|
|
|
LoggerFactory.getLogger(this.javaClass.simpleName).error("Test did not complete in a timely manner...")
|
2023-06-16 14:15:27 +02:00
|
|
|
stopEndPointsBlocking()
|
2020-07-03 01:45:18 +02:00
|
|
|
Assert.fail("Test did not complete in a timely manner.")
|
|
|
|
} catch (ignored: InterruptedException) {
|
|
|
|
}
|
|
|
|
}, "UnitTest timeout fail condition")
|
|
|
|
autoFailThread!!.isDaemon = true
|
|
|
|
// autoFailThread.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
@After
|
|
|
|
fun cancelFailureCheck() {
|
|
|
|
if (autoFailThread != null) {
|
|
|
|
autoFailThread!!.interrupt()
|
|
|
|
autoFailThread = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|