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-08-21 02:20:41 +02:00
import dorkbox.hex.toHexString
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
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-09-13 16:57:32 +02:00
import kotlinx.coroutines.DelicateCoroutinesApi
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
2023-10-26 21:19:50 +02:00
const val DEBUG = false
2023-09-07 01:01:55 +02:00
2022-08-02 21:15:02 +02:00
// wait minimum of 3 minutes before we automatically fail the unit test.
2023-09-07 01:01:55 +02:00
var AUTO _FAIL _TIMEOUT : Long = if ( DEBUG ) 9999999999L else 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 ( )
}
}
2023-09-04 00:47:46 +02:00
fun pause ( timeToSleep : Long ) {
Thread . sleep ( timeToSleep )
}
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 ( )
2023-07-11 15:51:28 +02:00
configuration . appId = " network_test "
2023-10-28 20:55:49 +02:00
configuration . tag = " **Client** "
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 ( )
2023-07-11 15:51:28 +02:00
configuration . appId = " network_test "
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 ( )
2023-11-22 20:38:46 +01:00
private var errors = mutableListOf < Throwable > ( )
2020-07-03 01:45:18 +02:00
@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 )
2023-10-24 13:46:28 +02:00
if ( DEBUG ) {
logger . error ( " ---- " + this . javaClass . simpleName + " :: DEBUG UNIT TESTS ENABLED " )
} else {
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
2023-06-29 19:26:27 +02:00
fun addEndPoint ( endPoint : EndPoint < * > , runCheck : Boolean = true ) {
2023-09-07 01:01:55 +02:00
if ( runCheck && ! endPoint . ensureStopped ( ) ) {
throw IllegalStateException ( " Unable to continue, AERON was unable to stop. " )
2023-06-29 19:26:27 +02:00
}
2023-09-13 16:57:32 +02:00
endPoint . onInit { logger . error ( " UNIT TEST: init $id ( ${uuid.toHexString()} ) " ) }
endPoint . onConnect { logger . error ( " UNIT TEST: connect $id ( ${uuid.toHexString()} ) " ) }
endPoint . onDisconnect { logger . error ( " UNIT TEST: disconnect $id ( ${uuid.toHexString()} ) " ) }
2023-05-28 18:41:46 +02:00
2023-07-15 13:12:25 +02:00
endPoint . onError {
2023-11-13 18:45:17 +01:00
logger . error ( " UNIT TEST: ERROR! $id ( ${uuid.toHexString()} ) " , it )
2023-11-22 20:38:46 +01:00
errors . add ( it )
}
endPoint . onErrorGlobal {
logger . error ( " UNIT TEST: GLOBAL ERROR! " , it )
errors . add ( it )
2023-07-15 13:12:25 +02:00
}
2023-05-28 18:41:46 +02:00
endPointConnections . add ( endPoint )
2020-07-03 01:45:18 +02:00
}
/ * *
2023-09-07 01:01:55 +02:00
* Immediately stop the endpoints . DOES NOT WAIT FOR THEM TO CLOSE !
2023-06-16 14:15:27 +02:00
*
* Can stop from inside different callbacks
2023-09-07 01:01:55 +02:00
* - message ( network event poller )
* - connect ( eventdispatch . connect )
* - disconnect ( eventdispatch . connect )
2023-05-28 18:41:46 +02:00
* /
2023-09-04 00:47:46 +02:00
fun stopEndPoints ( stopAfterMillis : Long = 0L ) {
2020-07-03 01:45:18 +02:00
if ( isStopping ) {
return
}
2023-06-25 17:25:29 +02:00
2023-09-07 01:01:55 +02:00
// if (EventDispatcher.isCurrentEvent()) {
// val mutex = Mutex(true)
//
// // 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)
// mutex.unlock()
// }
//
// runBlocking {
// mutex.withLock { }
// }
//
// return
// }
2023-06-25 17:25:29 +02:00
2020-07-03 01:45:18 +02:00
isStopping = true
2021-07-26 20:16:10 +02:00
if ( stopAfterMillis > 0L ) {
2023-09-04 00:47:46 +02:00
Thread . sleep ( 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
// shutdown clients first
2023-07-21 00:20:47 +02:00
logger . error ( " Closing clients... " )
2023-05-28 18:41:46 +02:00
clients . forEach { endPoint ->
2023-06-25 17:25:29 +02:00
endPoint . close ( )
2020-07-03 01:45:18 +02:00
}
2023-09-07 01:01:55 +02:00
logger . error ( " NOT WAITING FOR CLIENT CLOSE. " )
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-07-21 00:20:47 +02:00
logger . error ( " Closing servers... " )
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-09-07 01:01:55 +02:00
logger . error ( " NOT WAITING FOR SERVER CLOSE. " )
2023-07-01 11:42:29 +02:00
2023-09-07 01:01:55 +02:00
logger . error ( " Closed endpoints... " )
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-11-22 20:38:46 +01:00
fun waitForThreads ( stopAfterSeconds : Long = AUTO _FAIL _TIMEOUT , onShutdown : ( List < Throwable > ) -> List < Throwable > = { it } ) {
2023-06-25 17:25:29 +02:00
val clients = endPointConnections . filterIsInstance < Client < Connection > > ( )
val servers = endPointConnections . filterIsInstance < Server < Connection > > ( )
2023-07-01 11:42:29 +02:00
val timeoutMS = TimeUnit . SECONDS . toMillis ( stopAfterSeconds )
2023-07-21 00:20:47 +02:00
var successClients = true
var successServers = true
2023-06-25 17:25:29 +02:00
clients . forEach { endPoint ->
2023-07-21 00:20:47 +02:00
successClients = successClients && endPoint . waitForClose ( timeoutMS )
2023-06-25 17:25:29 +02:00
}
servers . forEach { endPoint ->
2023-07-21 00:20:47 +02:00
successServers = successServers && endPoint . waitForClose ( timeoutMS )
2023-07-01 11:42:29 +02:00
}
clients . forEach { endPoint ->
2023-06-25 17:25:29 +02:00
endPoint . stopDriver ( )
2022-07-18 05:01:46 +02:00
}
2023-07-01 11:42:29 +02:00
servers . forEach { endPoint ->
endPoint . stopDriver ( )
}
2022-07-18 05:01:46 +02:00
// run actions before we actually shutdown, but after we wait
2023-07-21 00:20:47 +02:00
if ( ! successClients || ! successServers ) {
2023-10-24 13:46:28 +02:00
Assert . fail ( " Shutdown latch not triggered ( $successClients | $successServers )! " )
2023-05-28 18:41:46 +02:00
}
2023-07-03 21:42:16 +02:00
// we must always make sure that aeron is shut-down before starting again.
clients . forEach { endPoint ->
endPoint . ensureStopped ( )
2023-12-04 13:36:47 +01:00
endPoint . shutdownEventDispatcher ( ) // once shutdown, it cannot be restarted!
2023-07-03 21:42:16 +02:00
if ( ! Client . ensureStopped ( endPoint . config . copy ( ) ) ) {
throw IllegalStateException ( " Unable to continue, AERON client was unable to stop. " )
}
2020-07-03 01:45:18 +02:00
}
2022-04-04 23:22:39 +02:00
2023-07-03 21:42:16 +02:00
servers . forEach { endPoint ->
endPoint . ensureStopped ( )
2023-12-04 13:36:47 +01:00
endPoint . shutdownEventDispatcher ( ) // once shutdown, it cannot be restarted!
2023-07-03 21:42:16 +02:00
if ( ! Client . ensureStopped ( endPoint . config . copy ( ) ) ) {
throw IllegalStateException ( " Unable to continue, AERON server was unable to stop. " )
}
}
if ( ! AeronDriver . areAllInstancesClosed ( logger ) ) {
throw RuntimeException ( " Unable to shutdown! There are still Aeron drivers loaded! " )
2023-05-28 18:41:46 +02:00
}
2023-07-21 00:20:47 +02:00
logger . error ( " UNIT TEST, checking driver and memory leaks " )
// 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-05-28 18:41:46 +02:00
2023-09-07 01:01:55 +02:00
endPointConnections . clear ( )
2023-07-21 00:20:47 +02:00
logger . error ( " Finished shutting down all endpoints... ( $successClients , $successServers ) " )
2023-11-22 20:38:46 +01:00
if ( errors . isNotEmpty ( ) ) {
val acceptableErrors = errors . filterNot { it . message ?. contains ( " Unable to send message. (Connection in non-connected state, aborted attempt! Not connected) " ) ?: false }
val filteredErrors = onShutdown ( acceptableErrors )
if ( filteredErrors . isNotEmpty ( ) ) {
filteredErrors . forEach {
it . printStackTrace ( )
}
Assert . fail ( " Exception caught, and it shouldn't have happened! " )
}
}
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-09-07 01:01:55 +02:00
stopEndPoints ( )
waitForThreads ( )
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
}
}
}