diff --git a/mirai-core-api/src/commonMain/kotlin/contact/ContactList.kt b/mirai-core-api/src/commonMain/kotlin/contact/ContactList.kt index c29f31b91..32cb82a99 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/ContactList.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/ContactList.kt @@ -22,10 +22,13 @@ import java.util.concurrent.ConcurrentLinkedQueue */ @Suppress("unused") public class ContactList<C : Contact> -internal constructor(@JvmField @MiraiInternalApi public val delegate: ConcurrentLinkedQueue<C>) : +@MiraiInternalApi public constructor(@JvmField @MiraiInternalApi public val delegate: ConcurrentLinkedQueue<C>) : Collection<C> by delegate { - internal constructor(collection: Collection<C>) : this(ConcurrentLinkedQueue(collection)) - internal constructor() : this(ConcurrentLinkedQueue()) + @MiraiInternalApi + public constructor(collection: Collection<C>) : this(ConcurrentLinkedQueue(collection)) + + @MiraiInternalApi + public constructor() : this(ConcurrentLinkedQueue()) /** * 获取一个 [Contact.id] 为 [id] 的元素. 在不存在时返回 `null`. diff --git a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt index 415052fb9..aac8cd3fa 100644 --- a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt +++ b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt @@ -52,19 +52,14 @@ public inline fun CoroutineScope.launchWithPermit( */ public fun CoroutineScope.childScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, -): CoroutineScope { - val ctx = this.coroutineContext + coroutineContext - return CoroutineScope(ctx + SupervisorJob(ctx.job)) -} +): CoroutineScope = this.coroutineContext.childScope(coroutineContext) /** * Creates a child scope of the receiver context scope. */ public fun CoroutineContext.childScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, -): CoroutineScope { - return CoroutineScope(this.childScopeContext(coroutineContext)) -} +): CoroutineScope = CoroutineScope(this.childScopeContext(coroutineContext)) /** * Creates a child scope of the receiver context scope. @@ -73,12 +68,11 @@ public fun CoroutineContext.childScopeContext( coroutineContext: CoroutineContext = EmptyCoroutineContext, ): CoroutineContext { val ctx = this + coroutineContext - return ctx + SupervisorJob(ctx.job) + val job = ctx[Job] ?: return ctx + SupervisorJob() + return ctx + SupervisorJob(job) } public inline fun <E : U, U : CoroutineContext.Element> CoroutineContext.getOrElse( key: CoroutineContext.Key<E>, default: () -> U -): U { - return this[key] ?: default() -} \ No newline at end of file +): U = this[key] ?: default() \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt index 9bf0564a5..161f1442f 100644 --- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt +++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt @@ -7,13 +7,6 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress( - "EXPERIMENTAL_API_USAGE", - "DEPRECATION_ERROR", - "OverridingDeprecatedMember", - "INVISIBLE_REFERENCE", - "INVISIBLE_MEMBER" -) package net.mamoe.mirai.internal @@ -29,8 +22,13 @@ import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.handler.NetworkHandler +import net.mamoe.mirai.internal.network.impl.netty.asCoroutineExceptionHandler import net.mamoe.mirai.supervisorJob -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.BotConfiguration +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.childScopeContext +import net.mamoe.mirai.utils.info +import kotlin.collections.set import kotlin.coroutines.CoroutineContext /** @@ -45,30 +43,45 @@ internal abstract class AbstractBot constructor( /////////////////////////////////////////////////////////////////////////// // FASTEST INIT - final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) } + @Suppress("LeakingThis") + final override val logger: MiraiLogger = configuration.botLoggerSupplier(this) - final override val coroutineContext: CoroutineContext = configuration.parentCoroutineContext.childScopeContext( - configuration.parentCoroutineContext.getOrElse(CoroutineExceptionHandler) { - CoroutineExceptionHandler { _, e -> - logger.error("An exception was thrown under a coroutine of Bot", e) + final override val coroutineContext: CoroutineContext = + CoroutineName("Bot.$id") + .plus(logger.asCoroutineExceptionHandler()) + .childScopeContext(configuration.parentCoroutineContext) + .apply { + job.invokeOnCompletion { throwable -> + logger.info { "Bot cancelled" + throwable?.message?.let { ": $it" }.orEmpty() } + + kotlin.runCatching { + network.close(throwable) + }.onFailure { + if (it !is CancellationException) logger.error(it) + } + + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + Bot._instances.remove(id) + + // help GC release instances + groups.forEach { it.members.delegate.clear() } + groups.delegate.clear() // job is cancelled, so child jobs are to be cancelled + friends.delegate.clear() + strangers.delegate.clear() + } } - } + CoroutineName("Mirai Bot") - ) - - abstract val components: ConcurrentComponentStorage init { - @Suppress("LeakingThis") + @Suppress("LeakingThis", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") Bot._instances[this.id] = this - supervisorJob.invokeOnCompletion { - Bot._instances.remove(id) - } } /////////////////////////////////////////////////////////////////////////// // overrides /////////////////////////////////////////////////////////////////////////// + abstract val components: ConcurrentComponentStorage + final override val isOnline: Boolean get() = network.isOk() final override val eventChannel: EventChannel<BotEvent> = GlobalEventChannel.filterIsInstance<BotEvent>().filter { it.bot === this@AbstractBot } @@ -79,7 +92,19 @@ internal abstract class AbstractBot constructor( final override val strangers: ContactList<Stranger> = ContactList() final override val asFriend: Friend by lazy { Mirai.newFriend(this, FriendInfoImpl(uin, nick, "")) } - final override val asStranger: Stranger by lazy { Mirai.newStranger(bot, StrangerInfoImpl(bot.id, bot.nick)) } + final override val asStranger: Stranger by lazy { Mirai.newStranger(this, StrangerInfoImpl(bot.id, bot.nick)) } + + override fun close(cause: Throwable?) { + if (!this.isActive) return + + if (cause == null) { + supervisorJob.cancel() + } else { + supervisorJob.cancel(CancellationException("Bot closed", cause)) + } + } + + final override fun toString(): String = "Bot($id)" /////////////////////////////////////////////////////////////////////////// // network @@ -93,56 +118,4 @@ internal abstract class AbstractBot constructor( } protected abstract fun createNetworkHandler(): NetworkHandler - protected abstract suspend fun sendLogout() - - // endregion - - - init { - coroutineContext[Job]!!.invokeOnCompletion { throwable -> - logger.info { "Bot cancelled" + throwable?.message?.let { ": $it" }.orEmpty() } - - kotlin.runCatching { - network.close(throwable) - } - - // help GC release instances - groups.forEach { - it.members.delegate.clear() - } - groups.delegate.clear() // job is cancelled, so child jobs are to be cancelled - friends.delegate.clear() - strangers.delegate.clear() - } - } - - override fun close(cause: Throwable?) { - if (!this.isActive) { - // already cancelled - return - } - - this.network.close(cause) - - if (supervisorJob.isActive) { - if (cause == null) { - supervisorJob.cancel() - } else { - supervisorJob.cancel(CancellationException("Bot closed", cause)) - } - } - } - - final override fun toString(): String = "Bot($id)" -} - -private val Throwable.rootCause: Throwable - get() { - var depth = 0 - var rootCause: Throwable? = this - while (rootCause?.cause != null) { - rootCause = rootCause.cause - if (depth++ == 20) break - } - return rootCause ?: this - } \ No newline at end of file +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index 80f7fa06d..bc5771876 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.internal import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai @@ -29,7 +30,6 @@ import net.mamoe.mirai.internal.network.context.SsoProcessorContextImpl import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl -import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.internal.network.handler.selector.FactoryKeepAliveNetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler import net.mamoe.mirai.internal.network.handler.state.* @@ -77,12 +77,12 @@ internal open class QQAndroidBot constructor( // TODO: 2021/4/14 bdhSyncer.loadFromCache() when login - // IDE error, don't move into lazy + // also called by tests. fun ComponentStorage.stateObserverChain(): StateObserver { val components = this return StateObserver.chainOfNotNull( components[BotInitProcessor].asObserver(), - StateChangedObserver(State.OK) { new -> + StateChangedObserver(to = State.OK) { new -> bot.launch(logger.asCoroutineExceptionHandler()) { BotOnlineEvent(bot).broadcast() if (bot.firstLoginSucceed) { // TODO: 2021/4/21 actually no use @@ -90,31 +90,27 @@ internal open class QQAndroidBot constructor( } } }, - object : StateObserver { - override fun stateChanged( - networkHandler: NetworkHandlerSupport, - previous: NetworkHandlerSupport.BaseStateImpl, - new: NetworkHandlerSupport.BaseStateImpl - ) { - val p = previous.correspondingState - val n = new.correspondingState - when { - p == State.OK && n == State.CONNECTING -> { - bot.launch(logger.asCoroutineExceptionHandler()) { - BotOfflineEvent.Dropped(bot, new.getCause()).broadcast() - } - } - p == State.OK && n == State.CLOSED -> { - bot.launch(logger.asCoroutineExceptionHandler()) { - BotOfflineEvent.Active(bot, new.getCause()).broadcast() - } - } - } + StateChangedObserver(State.OK, State.CONNECTING) { new -> + bot.launch(logger.asCoroutineExceptionHandler()) { + BotOfflineEvent.Dropped(bot, new.getCause()).broadcast() } }, - StateChangedObserver(State.OK) { new -> + StateChangedObserver(State.OK, State.CLOSED) { new -> + bot.launch(logger.asCoroutineExceptionHandler()) { + BotOfflineEvent.Active(bot, new.getCause()).broadcast() + } + }, + StateChangedObserver(to = State.OK) { new -> components[BotOfflineEventMonitor].attachJob(bot, new) }, + StateChangedObserver(State.OK, State.CLOSED) { + runBlocking { + try { + components[SsoProcessor].logout(network) + } catch (ignored: Exception) { + } + } + }, debugConfiguration.stateObserver ).safe(logger) } @@ -156,10 +152,6 @@ internal open class QQAndroidBot constructor( val client get() = components[SsoProcessor].client - override suspend fun sendLogout() { - components[SsoProcessor].logout(network) - } - override fun createNetworkHandler(): NetworkHandler { val context = NetworkHandlerContextImpl( this, diff --git a/mirai-core/src/commonMain/kotlin/network/handler/state/StateChangedObserver.kt b/mirai-core/src/commonMain/kotlin/network/handler/state/StateChangedObserver.kt index 6c3394413..97f3ee261 100644 --- a/mirai-core/src/commonMain/kotlin/network/handler/state/StateChangedObserver.kt +++ b/mirai-core/src/commonMain/kotlin/network/handler/state/StateChangedObserver.kt @@ -12,11 +12,12 @@ package net.mamoe.mirai.internal.network.handler.state import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport +@Suppress("FunctionName") internal fun StateChangedObserver( - state: State, + to: State, action: (new: NetworkHandlerSupport.BaseStateImpl) -> Unit -): StateChangedObserver { - return object : StateChangedObserver(state) { +): StateObserver { + return object : StateChangedObserver(to) { override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, @@ -27,6 +28,25 @@ internal fun StateChangedObserver( } } +@Suppress("FunctionName") +internal fun StateChangedObserver( + from: State, + to: State, + action: (new: NetworkHandlerSupport.BaseStateImpl) -> Unit +): StateObserver { + return object : StateObserver { + override fun stateChanged( + networkHandler: NetworkHandlerSupport, + previous: NetworkHandlerSupport.BaseStateImpl, + new: NetworkHandlerSupport.BaseStateImpl + ) { + if (previous.correspondingState == from && new.correspondingState == to) { + action(new) + } + } + } +} + internal abstract class StateChangedObserver( val state: State, ) : StateObserver { diff --git a/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt index 9b90287f7..bef60b1ce 100644 --- a/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt @@ -42,6 +42,7 @@ internal open class NettyNetworkHandler( ) : NetworkHandlerSupport(context) { override fun close(cause: Throwable?) { setState { StateClosed(CancellationException("Closed manually.", cause)) } + super.close(cause) // wrap an exception, more stacktrace information } diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt index a3b833570..c39031538 100644 --- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.internal.network.framework +import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient @@ -20,17 +21,23 @@ import net.mamoe.mirai.internal.network.context.SsoProcessorContext import net.mamoe.mirai.internal.network.context.SsoProcessorContextImpl import net.mamoe.mirai.internal.network.context.SsoSession import net.mamoe.mirai.internal.network.handler.NetworkHandler +import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug +import net.mamoe.mirai.utils.lateinitMutableProperty +import org.junit.jupiter.api.TestInstance import java.net.InetSocketAddress +import java.util.concurrent.ConcurrentLinkedQueue +import kotlin.test.assertEquals /** * With real factory and components as in [QQAndroidBot.components]. */ +@TestInstance(TestInstance.Lifecycle.PER_METHOD) internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : AbstractTest() { init { System.setProperty("mirai.debug.network.state.observer.logging", "true") @@ -38,14 +45,25 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs } protected abstract val factory: NetworkHandlerFactory<H> + protected abstract val network: NetworkHandler - protected open val bot: QQAndroidBot by lazy { + protected open var bot: QQAndroidBot by lateinitMutableProperty { MockBot { networkHandlerProvider { createHandler() } } } + protected open val networkLogger = MiraiLogger.TopLevel + protected sealed class NHEvent { + object Login : NHEvent() + object Logout : NHEvent() + object DoHeartbeatNow : NHEvent() + object Init : NHEvent() + } + + protected val nhEvents = ConcurrentLinkedQueue<NHEvent>() + protected open val defaultComponents = ConcurrentComponentStorage().apply { val components = this val configuration = bot.configuration @@ -55,15 +73,18 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs override val ssoSession: SsoSession get() = bot.client override fun createObserverChain(): StateObserver = get(StateObserver) override suspend fun login(handler: NetworkHandler) { + nhEvents.add(NHEvent.Login) networkLogger.debug { "SsoProcessor.login" } } override suspend fun logout(handler: NetworkHandler) { + nhEvents.add(NHEvent.Logout) networkLogger.debug { "SsoProcessor.logout" } } }) set(HeartbeatProcessor, object : HeartbeatProcessor { override suspend fun doHeartbeatNow(networkHandler: NetworkHandler) { + nhEvents.add(NHEvent.DoHeartbeatNow) networkLogger.debug { "HeartbeatProcessor.doHeartbeatNow" } } }) @@ -77,6 +98,7 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs set(BotInitProcessor, object : BotInitProcessor { override suspend fun init() { + nhEvents.add(NHEvent.Init) networkLogger.debug { "BotInitProcessor.init" } } }) @@ -89,6 +111,10 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs set(OtherClientUpdater, OtherClientUpdaterImpl(bot, components, bot.logger)) set(ConfigPushSyncer, ConfigPushSyncerImpl()) + set(BotOfflineEventMonitor, object : BotOfflineEventMonitor { + override fun attachJob(bot: QQAndroidBot, scope: CoroutineScope) { + } + }) set(StateObserver, bot.run { stateObserverChain() }) } @@ -102,4 +128,13 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs InetSocketAddress.createUnresolved("localhost", 123) ) } + + /////////////////////////////////////////////////////////////////////////// + // Assertions + /////////////////////////////////////////////////////////////////////////// + + protected fun assertState(state: State) { + assertEquals(state, network.state) + } + } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/network/handler/StateObserverTest.kt b/mirai-core/src/commonTest/kotlin/network/handler/StateObserverTest.kt index 1bfece691..d28b0f2b0 100644 --- a/mirai-core/src/commonTest/kotlin/network/handler/StateObserverTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/handler/StateObserverTest.kt @@ -67,6 +67,21 @@ internal class StateObserverTest : AbstractMockNetworkHandlerTest() { assertEquals(CONNECTING, called[0].second.correspondingState) } + @Test + fun `test StateChangedObserver2`() { + val called = ArrayList<NetworkHandlerSupport.BaseStateImpl>() + components[StateObserver] = StateChangedObserver(INITIALIZED, CONNECTING) { new -> + called.add(new) + } + val handler = createNetworkHandler() + assertEquals(0, called.size) + handler.setState(INITIALIZED) + assertEquals(0, called.size) + handler.setState(CONNECTING) + assertEquals(1, called.size) + assertEquals(CONNECTING, called[0].correspondingState) + } + @Test fun `can combine`() { val called = ArrayList<Pair<NetworkHandlerSupport.BaseStateImpl, NetworkHandlerSupport.BaseStateImpl>>() diff --git a/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt b/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt new file mode 100644 index 000000000..e543adbd3 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.internal.network.impl.netty + +import io.netty.channel.Channel +import io.netty.channel.embedded.EmbeddedChannel +import net.mamoe.mirai.internal.network.framework.AbstractRealNetworkHandlerTest +import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext +import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory +import net.mamoe.mirai.utils.ExceptionCollector +import java.net.SocketAddress + +internal open class TestNettyNH( + context: NetworkHandlerContext, + address: SocketAddress +) : NettyNetworkHandler(context, address) { + + fun setStateClosed(exception: Throwable? = null) { + setState { StateClosed(exception) } + } + + fun setStateConnecting(exception: Throwable? = null) { + setState { StateConnecting(ExceptionCollector(exception), false) } + } + +} + +internal abstract class AbstractNettyNHTest : AbstractRealNetworkHandlerTest<TestNettyNH>() { + val channel = EmbeddedChannel() + override val network: TestNettyNH get() = bot.network as TestNettyNH + + override val factory: NetworkHandlerFactory<TestNettyNH> = + object : NetworkHandlerFactory<TestNettyNH> { + override fun create(context: NetworkHandlerContext, address: SocketAddress): TestNettyNH { + return object : TestNettyNH(context, address) { + override suspend fun createConnection(decodePipeline: PacketDecodePipeline): Channel = + channel.apply { setupChannelPipeline(pipeline(), decodePipeline) } + } + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyBotLifecycleTest.kt b/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyBotLifecycleTest.kt new file mode 100644 index 000000000..8e345d998 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyBotLifecycleTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.internal.network.impl.netty + +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import net.mamoe.mirai.internal.MockBot +import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* +import net.mamoe.mirai.internal.test.runBlockingUnit +import net.mamoe.mirai.supervisorJob +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +internal class NettyBotLifecycleTest : AbstractNettyNHTest() { + + @Test + fun `send logout on exit`() = runBlockingUnit { + assertState(INITIALIZED) + bot.login() + assertState(OK) + bot.close() // send logout blocking + delay(1000) + assertState(CLOSED) + assertTrue { nhEvents.any { it is NHEvent.Logout } } + } + + @Test + fun `can override context`() = runBlockingUnit { + bot = MockBot { + conf { + parentCoroutineContext = CoroutineName("Overrode") + } + networkHandlerProvider { createHandler() } + } + assertEquals("Overrode", bot.coroutineContext[CoroutineName]!!.name) + } + + @Test + fun `job attached`() = runBlockingUnit { + val parentJob = SupervisorJob() + bot = MockBot { + conf { + parentCoroutineContext = parentJob + } + networkHandlerProvider { createHandler() } + } + assertEquals(1, parentJob.children.count()) + assertEquals(bot.supervisorJob, parentJob.children.first()) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyHandlerEventTest.kt b/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyHandlerEventTest.kt index a6a71ec34..05a9f8ba6 100644 --- a/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyHandlerEventTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/impl/netty/NettyHandlerEventTest.kt @@ -9,54 +9,19 @@ package net.mamoe.mirai.internal.network.impl.netty -import io.netty.channel.Channel -import io.netty.channel.embedded.EmbeddedChannel import kotlinx.coroutines.delay import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent -import net.mamoe.mirai.internal.network.framework.AbstractRealNetworkHandlerTest import net.mamoe.mirai.internal.network.handler.NetworkHandler.State -import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext -import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.test.assertEventBroadcasts import net.mamoe.mirai.internal.test.runBlockingUnit -import net.mamoe.mirai.utils.ExceptionCollector -import java.net.SocketAddress import kotlin.test.Test import kotlin.test.assertEquals import kotlin.time.seconds -internal open class TestNettyNH( - context: NetworkHandlerContext, - address: SocketAddress -) : NettyNetworkHandler(context, address) { - - fun setStateClosed(exception: Throwable? = null) { - setState { StateClosed(exception) } - } - - fun setStateConnecting(exception: Throwable? = null) { - setState { StateConnecting(ExceptionCollector(exception), false) } - } - -} - -internal class NettyHandlerEventTest : AbstractRealNetworkHandlerTest<TestNettyNH>() { - val channel = EmbeddedChannel() - val network get() = bot.network as TestNettyNH - - override val factory: NetworkHandlerFactory<TestNettyNH> = - object : NetworkHandlerFactory<TestNettyNH> { - override fun create(context: NetworkHandlerContext, address: SocketAddress): TestNettyNH { - return object : TestNettyNH(context, address) { - override suspend fun createConnection(decodePipeline: PacketDecodePipeline): Channel = - channel.apply { setupChannelPipeline(pipeline(), decodePipeline) } - } - } - } - +internal class NettyHandlerEventTest : AbstractNettyNHTest() { @Test fun `BotOnlineEvent after successful logon`() = runBlockingUnit { assertEventBroadcasts<BotOnlineEvent> {