mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-09 07:42:28 +08:00
Add NetworkHandlerFactory and tests for NetworkHandler
This commit is contained in:
parent
4f77abca87
commit
b91bbfd2b8
@ -88,6 +88,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
|
|||||||
* 因 returnCode = -10008 等原因掉线
|
* 因 returnCode = -10008 等原因掉线
|
||||||
*/
|
*/
|
||||||
@MiraiInternalApi("This is very experimental and might be changed")
|
@MiraiInternalApi("This is very experimental and might be changed")
|
||||||
|
@Deprecated("Deprecated with no replacement", level = DeprecationLevel.ERROR)
|
||||||
public data class PacketFactoryErrorCode @MiraiInternalApi public constructor(
|
public data class PacketFactoryErrorCode @MiraiInternalApi public constructor(
|
||||||
val returnCode: Int,
|
val returnCode: Int,
|
||||||
public override val bot: Bot,
|
public override val bot: Bot,
|
||||||
|
@ -12,7 +12,10 @@ package net.mamoe.mirai.utils
|
|||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
public class ExceptionCollector {
|
public class ExceptionCollector : Sequence<Throwable> {
|
||||||
|
|
||||||
|
// TODO: 2021/4/20 drop last
|
||||||
|
|
||||||
public constructor()
|
public constructor()
|
||||||
public constructor(initial: Throwable?) {
|
public constructor(initial: Throwable?) {
|
||||||
collect(initial)
|
collect(initial)
|
||||||
@ -32,6 +35,12 @@ public class ExceptionCollector {
|
|||||||
if (e == null) return
|
if (e == null) return
|
||||||
val last = last
|
val last = last
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
|
last.itr().forEach { suppressed ->
|
||||||
|
if (suppressed.stackTrace.contentEquals(e.stackTrace)) {
|
||||||
|
// filter out useless duplicates.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
e.addSuppressed(last)
|
e.addSuppressed(last)
|
||||||
}
|
}
|
||||||
this.last = e
|
this.last = e
|
||||||
@ -57,6 +66,15 @@ public class ExceptionCollector {
|
|||||||
|
|
||||||
@DslMarker
|
@DslMarker
|
||||||
private annotation class TerminalOperation
|
private annotation class TerminalOperation
|
||||||
|
|
||||||
|
private fun Throwable.itr(): Iterator<Throwable> {
|
||||||
|
return (sequenceOf(this) + this.suppressed.asSequence().flatMap { it.itr().asSequence() }).iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<Throwable> {
|
||||||
|
val last = getLast() ?: return emptyList<Throwable>().iterator()
|
||||||
|
return last.itr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,14 +10,36 @@
|
|||||||
package net.mamoe.mirai.internal.test
|
package net.mamoe.mirai.internal.test
|
||||||
|
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.security.Security
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
internal actual fun initPlatform() {
|
internal actual fun initPlatform() {
|
||||||
init
|
init
|
||||||
}
|
}
|
||||||
|
|
||||||
private val init by lazy {
|
private val init: Unit by lazy {
|
||||||
MiraiLogger.setDefaultLoggerCreator {
|
MiraiLogger.setDefaultLoggerCreator {
|
||||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
net.mamoe.mirai.internal.utils.StdoutLogger(it)
|
net.mamoe.mirai.internal.utils.StdoutLogger(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) != null) {
|
||||||
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
}
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual class PlatformInitializationTest : AbstractTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
actual fun test() {
|
||||||
|
assertTrue {
|
||||||
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
|
MiraiLogger.create("1") is net.mamoe.mirai.internal.utils.StdoutLogger
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -27,7 +27,9 @@ import net.mamoe.mirai.internal.network.components.*
|
|||||||
import net.mamoe.mirai.internal.network.context.SsoProcessorContext
|
import net.mamoe.mirai.internal.network.context.SsoProcessorContext
|
||||||
import net.mamoe.mirai.internal.network.context.SsoProcessorContextImpl
|
import net.mamoe.mirai.internal.network.context.SsoProcessorContextImpl
|
||||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
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.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.FactoryKeepAliveNetworkHandlerSelector
|
||||||
import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
|
import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
|
||||||
import net.mamoe.mirai.internal.network.handler.state.*
|
import net.mamoe.mirai.internal.network.handler.state.*
|
||||||
@ -35,7 +37,6 @@ import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandlerFactory
|
|||||||
import net.mamoe.mirai.internal.network.impl.netty.asCoroutineExceptionHandler
|
import net.mamoe.mirai.internal.network.impl.netty.asCoroutineExceptionHandler
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
|
||||||
import net.mamoe.mirai.utils.BotConfiguration
|
import net.mamoe.mirai.utils.BotConfiguration
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import net.mamoe.mirai.utils.systemProp
|
import net.mamoe.mirai.utils.systemProp
|
||||||
@ -61,7 +62,7 @@ internal class BotDebugConfiguration(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember")
|
@Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember")
|
||||||
internal class QQAndroidBot constructor(
|
internal open class QQAndroidBot constructor(
|
||||||
internal val account: BotAccount,
|
internal val account: BotAccount,
|
||||||
configuration: BotConfiguration,
|
configuration: BotConfiguration,
|
||||||
private val debugConfiguration: BotDebugConfiguration = BotDebugConfiguration(),
|
private val debugConfiguration: BotDebugConfiguration = BotDebugConfiguration(),
|
||||||
@ -77,25 +78,42 @@ internal class QQAndroidBot constructor(
|
|||||||
// TODO: 2021/4/14 bdhSyncer.loadFromCache() when login
|
// TODO: 2021/4/14 bdhSyncer.loadFromCache() when login
|
||||||
|
|
||||||
// IDE error, don't move into lazy
|
// IDE error, don't move into lazy
|
||||||
private fun ComponentStorage.stateObserverChain(): StateObserver {
|
fun ComponentStorage.stateObserverChain(): StateObserver {
|
||||||
val components = this
|
val components = this
|
||||||
return StateObserver.chainOfNotNull(
|
return StateObserver.chainOfNotNull(
|
||||||
components[BotInitProcessor].asObserver().safe(networkLogger),
|
components[BotInitProcessor].asObserver(),
|
||||||
StateChangedObserver(NetworkHandler.State.OK) { new ->
|
StateChangedObserver(State.OK) { new ->
|
||||||
new.launch(logger.asCoroutineExceptionHandler()) {
|
bot.launch(logger.asCoroutineExceptionHandler()) {
|
||||||
BotOnlineEvent(bot).broadcast()
|
BotOnlineEvent(bot).broadcast()
|
||||||
if (bot.firstLoginSucceed) { // TODO: 2021/4/21 actually no use
|
if (bot.firstLoginSucceed) { // TODO: 2021/4/21 actually no use
|
||||||
BotReloginEvent(bot, new.getCause()).broadcast()
|
BotReloginEvent(bot, new.getCause()).broadcast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
StateChangedObserver(NetworkHandler.State.CLOSED) { new ->
|
object : StateObserver {
|
||||||
new.launch(logger.asCoroutineExceptionHandler()) {
|
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()
|
BotOfflineEvent.Dropped(bot, new.getCause()).broadcast()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
p == State.OK && n == State.CLOSED -> {
|
||||||
|
bot.launch(logger.asCoroutineExceptionHandler()) {
|
||||||
|
BotOfflineEvent.Active(bot, new.getCause()).broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
debugConfiguration.stateObserver
|
debugConfiguration.stateObserver
|
||||||
)
|
).safe(logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -135,7 +153,7 @@ internal class QQAndroidBot constructor(
|
|||||||
val client get() = components[SsoProcessor].client
|
val client get() = components[SsoProcessor].client
|
||||||
|
|
||||||
override suspend fun sendLogout() {
|
override suspend fun sendLogout() {
|
||||||
network.sendWithoutExpect(StatSvc.Register.offline(client))
|
components[SsoProcessor].logout(network)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createNetworkHandler(): NetworkHandler {
|
override fun createNetworkHandler(): NetworkHandler {
|
||||||
|
@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.network.component
|
|||||||
|
|
||||||
import net.mamoe.mirai.utils.systemProp
|
import net.mamoe.mirai.utils.systemProp
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread-safe implementation of [MutableComponentStorage]
|
* A thread-safe implementation of [MutableComponentStorage]
|
||||||
@ -55,4 +56,4 @@ internal class ConcurrentComponentStorage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val SHOW_ALL_COMPONENTS = systemProp("mirai.debug.network.show.all.components", false)
|
private val SHOW_ALL_COMPONENTS: Boolean by lazy(NONE) { systemProp("mirai.debug.network.show.all.components", false) }
|
@ -34,7 +34,7 @@ import net.mamoe.mirai.utils.info
|
|||||||
* Facade of [ContactUpdater], [OtherClientUpdater], [ConfigPushSyncer].
|
* Facade of [ContactUpdater], [OtherClientUpdater], [ConfigPushSyncer].
|
||||||
* Handles initialization jobs after successful logon.
|
* Handles initialization jobs after successful logon.
|
||||||
*
|
*
|
||||||
* Attached to handler state [NetworkHandler.State.LOADING] [as state observer][asObserver].
|
* Attached to handler state [NetworkHandler.State.LOADING] [as state observer][asObserver] in [QQAndroidBot.stateObserverChain].
|
||||||
*/
|
*/
|
||||||
internal interface BotInitProcessor {
|
internal interface BotInitProcessor {
|
||||||
suspend fun init()
|
suspend fun init()
|
||||||
|
@ -24,6 +24,7 @@ import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver
|
|||||||
import net.mamoe.mirai.internal.network.handler.state.StateObserver
|
import net.mamoe.mirai.internal.network.handler.state.StateObserver
|
||||||
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler
|
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||||
|
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
|
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
|
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
|
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
|
||||||
@ -57,6 +58,8 @@ internal interface SsoProcessor {
|
|||||||
@Throws(LoginFailedException::class)
|
@Throws(LoginFailedException::class)
|
||||||
suspend fun login(handler: NetworkHandler)
|
suspend fun login(handler: NetworkHandler)
|
||||||
|
|
||||||
|
suspend fun logout(handler: NetworkHandler)
|
||||||
|
|
||||||
companion object : ComponentKey<SsoProcessor>
|
companion object : ComponentKey<SsoProcessor>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +113,10 @@ internal class SsoProcessorImpl(
|
|||||||
ssoContext.accountSecretsManager.saveSecrets(ssoContext.account, AccountSecretsImpl(client))
|
ssoContext.accountSecretsManager.saveSecrets(ssoContext.account, AccountSecretsImpl(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun logout(handler: NetworkHandler) {
|
||||||
|
handler.sendWithoutExpect(StatSvc.Register.offline(client))
|
||||||
|
}
|
||||||
|
|
||||||
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
|
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
|
||||||
val device = ssoContext.device
|
val device = ssoContext.device
|
||||||
return QQAndroidClient(
|
return QQAndroidClient(
|
||||||
|
@ -10,22 +10,25 @@
|
|||||||
package net.mamoe.mirai.internal.network.handler
|
package net.mamoe.mirai.internal.network.handler
|
||||||
|
|
||||||
import kotlinx.coroutines.selects.SelectClause1
|
import kotlinx.coroutines.selects.SelectClause1
|
||||||
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.internal.network.Packet
|
import net.mamoe.mirai.internal.network.Packet
|
||||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
|
import net.mamoe.mirai.internal.network.components.BotInitProcessor
|
||||||
|
import net.mamoe.mirai.internal.network.components.SsoProcessor
|
||||||
|
import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
|
||||||
|
import net.mamoe.mirai.internal.network.handler.state.StateObserver
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.net.SocketAddress
|
|
||||||
import java.util.concurrent.CancellationException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic interface available to application. Usually wrapped with [SelectorNetworkHandler].
|
* Basic interface available to application. Usually wrapped with [SelectorNetworkHandler].
|
||||||
*
|
*
|
||||||
* Implementation is usually subclass of [NetworkHandlerSupport].
|
* Implementation is usually subclass of [NetworkHandlerSupport].
|
||||||
*
|
*
|
||||||
|
* Instances are often created by [NetworkHandlerFactory].
|
||||||
|
*
|
||||||
* @see NetworkHandlerSupport
|
* @see NetworkHandlerSupport
|
||||||
|
* @see NetworkHandlerFactory
|
||||||
*/
|
*/
|
||||||
internal interface NetworkHandler {
|
internal interface NetworkHandler {
|
||||||
val context: NetworkHandlerContext
|
val context: NetworkHandlerContext
|
||||||
@ -33,7 +36,7 @@ internal interface NetworkHandler {
|
|||||||
fun isOk() = state == State.OK
|
fun isOk() = state == State.OK
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State of this handler.
|
* Current state of this handler. This is volatile.
|
||||||
*/
|
*/
|
||||||
val state: State
|
val state: State
|
||||||
|
|
||||||
@ -42,6 +45,28 @@ internal interface NetworkHandler {
|
|||||||
*/
|
*/
|
||||||
val onStateChanged: SelectClause1<State>
|
val onStateChanged: SelectClause1<State>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State of this handler.
|
||||||
|
*
|
||||||
|
* ## States transition overview
|
||||||
|
*
|
||||||
|
* There are 5 [State]s, each of which encapsulates the state of the network connection.
|
||||||
|
*
|
||||||
|
* Initial state is [State.INITIALIZED], at which no packets can be send before [resumeConnection], which transmits state into [State.CONNECTING].
|
||||||
|
* On [State.CONNECTING], [NetworkHandler] establishes a connection with the server while [SsoProcessor] takes responsibility in the single-sign-on process.
|
||||||
|
*
|
||||||
|
* Successful logon turns state to [State.LOADING], an **open state**, which does nothing by default. Jobs can be *attached* by [StateObserver]s.
|
||||||
|
* For example, attaching a [BotInitProcessor] to handle session-relevant jobs to the [Bot].
|
||||||
|
*
|
||||||
|
* Failure during [State.CONNECTING] and [State.LOADING] switches state to [State.CLOSED], on which [NetworkHandler] is considered permanently dead.
|
||||||
|
*
|
||||||
|
* The state after finish of [State.LOADING] is [State.OK]. This state lasts for the majority of time.
|
||||||
|
*
|
||||||
|
* When connection is lost (e.g. due to Internet unavailability), it returns to [State.CONNECTING] and repeatedly attempts to reconnect.
|
||||||
|
* Immediately after successful recovery, [State.OK] will be set.
|
||||||
|
*
|
||||||
|
* @see state
|
||||||
|
*/
|
||||||
enum class State {
|
enum class State {
|
||||||
/**
|
/**
|
||||||
* Just created and no connection has been made.
|
* Just created and no connection has been made.
|
||||||
@ -73,18 +98,18 @@ internal interface NetworkHandler {
|
|||||||
OK,
|
OK,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cannot resume anymore. Both [resumeConnection] and [sendAndExpect] throw a [CancellationException].
|
* The terminal state. Cannot resume anymore. Both [resumeConnection] and [sendAndExpect] throw a [IllegalStateException].
|
||||||
*
|
*
|
||||||
* When a handler reached [CLOSED] state, it is finalized and cannot be restored to any other states.
|
* When a handler reached [CLOSED] state, it is finalized and cannot be restored to any other states.
|
||||||
*
|
*
|
||||||
* At this state [resumeConnection] throws the exception caught from underlying socket implementation (i.e netty).
|
* At this state [resumeConnection] throws the exception caught from underlying socket implementation (i.e netty).
|
||||||
* [sendAndExpect] throws [IllegalStateException]
|
* [sendAndExpect] throws [IllegalStateException].
|
||||||
*/
|
*/
|
||||||
CLOSED,
|
CLOSED,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to resume the connection.
|
* Suspends the coroutine until [sendAndExpect] can be executed without suspension.
|
||||||
*
|
*
|
||||||
* May throw exception that had caused current state to fail.
|
* May throw exception that had caused current state to fail.
|
||||||
* @see State
|
* @see State
|
||||||
@ -95,17 +120,23 @@ internal interface NetworkHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends [packet] and expects to receive a response from the server.
|
* Sends [packet] and expects to receive a response from the server.
|
||||||
|
*
|
||||||
|
* Coroutine suspension may happen if connection if not yet available however, [IllegalStateException] is thrown if [NetworkHandler] is already in [State.CLOSED]
|
||||||
|
*
|
||||||
* @param attempts ranges `1..INFINITY`
|
* @param attempts ranges `1..INFINITY`
|
||||||
*/
|
*/
|
||||||
suspend fun sendAndExpect(packet: OutgoingPacket, timeout: Long = 5000, attempts: Int = 2): Packet?
|
suspend fun sendAndExpect(packet: OutgoingPacket, timeout: Long = 5000, attempts: Int = 2): Packet?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends [packet] and does not expect any response. (Response is still processed but not passed as a return value of this function.)
|
* Sends [packet] and does not expect any response.
|
||||||
|
*
|
||||||
|
* Response is still being processed but not passed as a return value of this function, so it does not suspends this function.
|
||||||
|
* However, coroutine is still suspended if connection if not yet available, and [IllegalStateException] is thrown if [NetworkHandler] is already in [State.CLOSED]
|
||||||
*/
|
*/
|
||||||
suspend fun sendWithoutExpect(packet: OutgoingPacket)
|
suspend fun sendWithoutExpect(packet: OutgoingPacket)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this handler gracefully.
|
* Closes this handler gracefully (i.e. asynchronously).
|
||||||
*/
|
*/
|
||||||
fun close(cause: Throwable?)
|
fun close(cause: Throwable?)
|
||||||
|
|
||||||
@ -142,18 +173,3 @@ internal interface NetworkHandler {
|
|||||||
|
|
||||||
internal val NetworkHandler.logger: MiraiLogger get() = context.logger
|
internal val NetworkHandler.logger: MiraiLogger get() = context.logger
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for a specific [NetworkHandler] implementation.
|
|
||||||
*/
|
|
||||||
internal interface NetworkHandlerFactory<H : NetworkHandler> {
|
|
||||||
fun create(context: NetworkHandlerContext, host: String, port: Int): H =
|
|
||||||
create(context, InetSocketAddress.createUnresolved(host, port))
|
|
||||||
|
|
||||||
fun create(context: NetworkHandlerContext, host: InetAddress, port: Int): H =
|
|
||||||
create(context, InetSocketAddress(host, port))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED]
|
|
||||||
*/
|
|
||||||
fun create(context: NetworkHandlerContext, address: SocketAddress): H
|
|
||||||
}
|
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.handler
|
||||||
|
|
||||||
|
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.SocketAddress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for a specific [NetworkHandler] implementation.
|
||||||
|
*/
|
||||||
|
internal interface NetworkHandlerFactory<H : NetworkHandler> {
|
||||||
|
fun create(context: NetworkHandlerContext, host: String, port: Int): H =
|
||||||
|
create(context, InetSocketAddress.createUnresolved(host, port))
|
||||||
|
|
||||||
|
fun create(context: NetworkHandlerContext, host: InetAddress, port: Int): H =
|
||||||
|
create(context, InetSocketAddress(host, port))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED]
|
||||||
|
*/
|
||||||
|
fun create(context: NetworkHandlerContext, address: SocketAddress): H
|
||||||
|
}
|
@ -36,7 +36,7 @@ import java.net.SocketAddress
|
|||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import io.netty.channel.Channel as NettyChannel
|
import io.netty.channel.Channel as NettyChannel
|
||||||
|
|
||||||
internal class NettyNetworkHandler(
|
internal open class NettyNetworkHandler(
|
||||||
context: NetworkHandlerContext,
|
context: NetworkHandlerContext,
|
||||||
private val address: SocketAddress,
|
private val address: SocketAddress,
|
||||||
) : NetworkHandlerSupport(context) {
|
) : NetworkHandlerSupport(context) {
|
||||||
@ -86,7 +86,16 @@ internal class NettyNetworkHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createConnection(decodePipeline: PacketDecodePipeline): NettyChannel {
|
protected open fun setupChannelPipeline(pipeline: ChannelPipeline, decodePipeline: PacketDecodePipeline) {
|
||||||
|
pipeline
|
||||||
|
.addLast(OutgoingPacketEncoder())
|
||||||
|
.addLast(LengthFieldBasedFrameDecoder(Int.MAX_VALUE, 0, 4, -4, 4))
|
||||||
|
.addLast(ByteBufToIncomingPacketDecoder())
|
||||||
|
.addLast(RawIncomingPacketCollector(decodePipeline))
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be overridden for tests
|
||||||
|
protected open suspend fun createConnection(decodePipeline: PacketDecodePipeline): NettyChannel {
|
||||||
val contextResult = CompletableDeferred<NettyChannel>()
|
val contextResult = CompletableDeferred<NettyChannel>()
|
||||||
val eventLoopGroup = NioEventLoopGroup()
|
val eventLoopGroup = NioEventLoopGroup()
|
||||||
|
|
||||||
@ -101,10 +110,8 @@ internal class NettyNetworkHandler(
|
|||||||
eventLoopGroup.shutdownGracefully()
|
eventLoopGroup.shutdownGracefully()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.addLast(OutgoingPacketEncoder())
|
|
||||||
.addLast(LengthFieldBasedFrameDecoder(Int.MAX_VALUE, 0, 4, -4, 4))
|
setupChannelPipeline(ch.pipeline(), decodePipeline)
|
||||||
.addLast(ByteBufToIncomingPacketDecoder())
|
|
||||||
.addLast(RawIncomingPacketCollector(decodePipeline))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.connect(address)
|
.connect(address)
|
||||||
@ -125,9 +132,9 @@ internal class NettyNetworkHandler(
|
|||||||
return contextResult.await()
|
return contextResult.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val decodePipeline = PacketDecodePipeline(this@NettyNetworkHandler.coroutineContext)
|
protected val decodePipeline = PacketDecodePipeline(this@NettyNetworkHandler.coroutineContext)
|
||||||
|
|
||||||
private inner class PacketDecodePipeline(parentContext: CoroutineContext) :
|
protected inner class PacketDecodePipeline(parentContext: CoroutineContext) :
|
||||||
CoroutineScope by parentContext.childScope() {
|
CoroutineScope by parentContext.childScope() {
|
||||||
private val channel: Channel<RawIncomingPacket> = Channel(Channel.BUFFERED)
|
private val channel: Channel<RawIncomingPacket> = Channel(Channel.BUFFERED)
|
||||||
private val packetCodec: PacketCodec by lazy { context[PacketCodec] }
|
private val packetCodec: PacketCodec by lazy { context[PacketCodec] }
|
||||||
@ -159,13 +166,13 @@ internal class NettyNetworkHandler(
|
|||||||
*
|
*
|
||||||
* @see StateObserver
|
* @see StateObserver
|
||||||
*/
|
*/
|
||||||
private abstract inner class NettyState(
|
protected abstract inner class NettyState(
|
||||||
correspondingState: State
|
correspondingState: State
|
||||||
) : BaseStateImpl(correspondingState) {
|
) : BaseStateImpl(correspondingState) {
|
||||||
abstract suspend fun sendPacketImpl(packet: OutgoingPacket)
|
abstract suspend fun sendPacketImpl(packet: OutgoingPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class StateInitialized : NettyState(State.INITIALIZED) {
|
protected inner class StateInitialized : NettyState(State.INITIALIZED) {
|
||||||
override suspend fun sendPacketImpl(packet: OutgoingPacket) {
|
override suspend fun sendPacketImpl(packet: OutgoingPacket) {
|
||||||
error("Cannot send packet when connection is not set. (resumeConnection not called.)")
|
error("Cannot send packet when connection is not set. (resumeConnection not called.)")
|
||||||
}
|
}
|
||||||
@ -181,16 +188,20 @@ internal class NettyNetworkHandler(
|
|||||||
/**
|
/**
|
||||||
* 1. Connect to server.
|
* 1. Connect to server.
|
||||||
* 2. Perform SSO login with [SsoProcessor]
|
* 2. Perform SSO login with [SsoProcessor]
|
||||||
* 3. If failure, set state to [StateClosed]
|
*
|
||||||
* 4. If success, set state to [StateOK]
|
* If failure, set state to [StateClosed]
|
||||||
|
* If success, set state to [StateOK]
|
||||||
*/
|
*/
|
||||||
private inner class StateConnecting(
|
protected inner class StateConnecting(
|
||||||
/**
|
/**
|
||||||
* Collected (suppressed) exceptions that have led this state.
|
* Collected (suppressed) exceptions that have led this state.
|
||||||
*
|
*
|
||||||
* Dropped when state becomes [StateOK].
|
* Dropped when state becomes [StateOK].
|
||||||
*/
|
*/
|
||||||
private val collectiveExceptions: ExceptionCollector,
|
private val collectiveExceptions: ExceptionCollector,
|
||||||
|
/**
|
||||||
|
* If `true`, [delay] 5 seconds before connecting.
|
||||||
|
*/
|
||||||
wait: Boolean = false
|
wait: Boolean = false
|
||||||
) : NettyState(State.CONNECTING) {
|
) : NettyState(State.CONNECTING) {
|
||||||
private val connection = async {
|
private val connection = async {
|
||||||
@ -238,7 +249,7 @@ internal class NettyNetworkHandler(
|
|||||||
* @see BotInitProcessor
|
* @see BotInitProcessor
|
||||||
* @see StateObserver
|
* @see StateObserver
|
||||||
*/
|
*/
|
||||||
private inner class StateLoading(
|
protected inner class StateLoading(
|
||||||
private val connection: NettyChannel
|
private val connection: NettyChannel
|
||||||
) : NettyState(State.LOADING) {
|
) : NettyState(State.LOADING) {
|
||||||
override suspend fun sendPacketImpl(packet: OutgoingPacket) {
|
override suspend fun sendPacketImpl(packet: OutgoingPacket) {
|
||||||
@ -256,7 +267,7 @@ internal class NettyNetworkHandler(
|
|||||||
override fun toString(): String = "StateLoading"
|
override fun toString(): String = "StateLoading"
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class StateOK(
|
protected inner class StateOK(
|
||||||
private val connection: NettyChannel
|
private val connection: NettyChannel
|
||||||
) : NettyState(State.OK) {
|
) : NettyState(State.OK) {
|
||||||
init {
|
init {
|
||||||
@ -317,7 +328,7 @@ internal class NettyNetworkHandler(
|
|||||||
override fun toString(): String = "StateOK"
|
override fun toString(): String = "StateOK"
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class StateClosed(
|
protected inner class StateClosed(
|
||||||
val exception: Throwable?
|
val exception: Throwable?
|
||||||
) : NettyState(State.CLOSED) {
|
) : NettyState(State.CLOSED) {
|
||||||
init {
|
init {
|
||||||
|
@ -12,34 +12,46 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal
|
package net.mamoe.mirai.internal
|
||||||
|
|
||||||
|
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||||
import net.mamoe.mirai.utils.BotConfiguration
|
import net.mamoe.mirai.utils.BotConfiguration
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
internal val MockAccount = BotAccount(1, "pwd")
|
internal val MockAccount = BotAccount(1, "pwd")
|
||||||
|
|
||||||
internal val MockConfiguration = BotConfiguration {
|
internal val MockConfiguration = BotConfiguration {
|
||||||
|
randomDeviceInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MockBotBuilder(
|
internal class MockBotBuilder(
|
||||||
val conf: BotConfiguration = BotConfiguration(),
|
val conf: BotConfiguration = BotConfiguration(),
|
||||||
val debugConf: BotDebugConfiguration = BotDebugConfiguration()
|
val debugConf: BotDebugConfiguration = BotDebugConfiguration(),
|
||||||
) {
|
) {
|
||||||
|
var nhProvider: (QQAndroidBot.(bot: QQAndroidBot) -> NetworkHandler)? = null
|
||||||
|
|
||||||
fun conf(action: BotConfiguration.() -> Unit): MockBotBuilder {
|
fun conf(action: BotConfiguration.() -> Unit): MockBotBuilder {
|
||||||
|
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
|
||||||
conf.apply(action)
|
conf.apply(action)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun debugConf(action: BotDebugConfiguration.() -> Unit): MockBotBuilder {
|
fun debugConf(action: BotDebugConfiguration.() -> Unit): MockBotBuilder {
|
||||||
|
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
|
||||||
debugConf.apply(action)
|
debugConf.apply(action)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun networkHandlerProvider(provider: QQAndroidBot.(bot: QQAndroidBot) -> NetworkHandler): MockBotBuilder {
|
||||||
|
this.nhProvider = provider
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
internal fun MockBot(conf: MockBotBuilder.() -> Unit) =
|
internal fun MockBot(conf: MockBotBuilder.() -> Unit = {}) =
|
||||||
MockBotBuilder(MockConfiguration.copy()).apply(conf).run {
|
MockBotBuilder(MockConfiguration.copy()).apply(conf).run {
|
||||||
QQAndroidBot(MockAccount, this.conf, debugConf)
|
object : QQAndroidBot(MockAccount, this.conf, debugConf) {
|
||||||
|
override fun createNetworkHandler(): NetworkHandler =
|
||||||
|
nhProvider?.invoke(this, this) ?: super.createNetworkHandler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("TestFunctionName")
|
|
||||||
internal fun MockBot() =
|
|
||||||
QQAndroidBot(MockAccount, MockConfiguration.copy())
|
|
@ -31,11 +31,20 @@ import java.net.InetSocketAddress
|
|||||||
/**
|
/**
|
||||||
* With real factory and components as in [QQAndroidBot.components].
|
* With real factory and components as in [QQAndroidBot.components].
|
||||||
*/
|
*/
|
||||||
internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler>(
|
internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : AbstractTest() {
|
||||||
private val factory: NetworkHandlerFactory<H>,
|
init {
|
||||||
) : AbstractTest() {
|
System.setProperty("mirai.debug.network.state.observer.logging", "true")
|
||||||
val bot = MockBot()
|
System.setProperty("mirai.debug.network.show.all.components", "true")
|
||||||
val networkLogger = MiraiLogger.TopLevel
|
}
|
||||||
|
|
||||||
|
protected abstract val factory: NetworkHandlerFactory<H>
|
||||||
|
|
||||||
|
protected open val bot: QQAndroidBot by lazy {
|
||||||
|
MockBot {
|
||||||
|
networkHandlerProvider { createHandler() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected open val networkLogger = MiraiLogger.TopLevel
|
||||||
|
|
||||||
protected open val defaultComponents = ConcurrentComponentStorage().apply {
|
protected open val defaultComponents = ConcurrentComponentStorage().apply {
|
||||||
val components = this
|
val components = this
|
||||||
@ -44,18 +53,27 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler>(
|
|||||||
set(SsoProcessor, object : SsoProcessor {
|
set(SsoProcessor, object : SsoProcessor {
|
||||||
override val client: QQAndroidClient get() = bot.client
|
override val client: QQAndroidClient get() = bot.client
|
||||||
override val ssoSession: SsoSession get() = bot.client
|
override val ssoSession: SsoSession get() = bot.client
|
||||||
override fun createObserverChain(): StateObserver = StateObserver.NOP
|
override fun createObserverChain(): StateObserver = get(StateObserver)
|
||||||
override suspend fun login(handler: NetworkHandler) {
|
override suspend fun login(handler: NetworkHandler) {
|
||||||
networkLogger.debug { "SsoProcessor.login" }
|
networkLogger.debug { "SsoProcessor.login" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun logout(handler: NetworkHandler) {
|
||||||
|
networkLogger.debug { "SsoProcessor.logout" }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
set(HeartbeatProcessor, object : HeartbeatProcessor {
|
set(HeartbeatProcessor, object : HeartbeatProcessor {
|
||||||
override suspend fun doHeartbeatNow(networkHandler: NetworkHandler) {
|
override suspend fun doHeartbeatNow(networkHandler: NetworkHandler) {
|
||||||
networkLogger.debug { "HeartbeatProcessor.doHeartbeatNow" }
|
networkLogger.debug { "HeartbeatProcessor.doHeartbeatNow" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
set(KeyRefreshProcessor, KeyRefreshProcessorImpl(networkLogger))
|
set(KeyRefreshProcessor, object : KeyRefreshProcessor {
|
||||||
set(ConfigPushProcessor, ConfigPushProcessorImpl(networkLogger))
|
override suspend fun keyRefreshLoop(handler: NetworkHandler) {}
|
||||||
|
override suspend fun refreshKeysNow(handler: NetworkHandler) {}
|
||||||
|
})
|
||||||
|
set(ConfigPushProcessor, object : ConfigPushProcessor {
|
||||||
|
override suspend fun syncConfigPush(network: NetworkHandler) {}
|
||||||
|
})
|
||||||
|
|
||||||
set(BotInitProcessor, object : BotInitProcessor {
|
set(BotInitProcessor, object : BotInitProcessor {
|
||||||
override suspend fun init() {
|
override suspend fun init() {
|
||||||
@ -71,11 +89,10 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler>(
|
|||||||
set(OtherClientUpdater, OtherClientUpdaterImpl(bot, components, bot.logger))
|
set(OtherClientUpdater, OtherClientUpdaterImpl(bot, components, bot.logger))
|
||||||
set(ConfigPushSyncer, ConfigPushSyncerImpl())
|
set(ConfigPushSyncer, ConfigPushSyncerImpl())
|
||||||
|
|
||||||
set(StateObserver, StateObserver.NOP)
|
set(StateObserver, bot.run { stateObserverChain() })
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createHandler(additionalComponents: ComponentStorage? = null): NetworkHandler {
|
protected open fun createHandler(additionalComponents: ComponentStorage? = null): H {
|
||||||
return factory.create(
|
return factory.create(
|
||||||
NetworkHandlerContextImpl(
|
NetworkHandlerContextImpl(
|
||||||
bot,
|
bot,
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework.test
|
||||||
|
|
||||||
|
import net.mamoe.mirai.event.AbstractEvent
|
||||||
|
import net.mamoe.mirai.event.broadcast
|
||||||
|
import net.mamoe.mirai.internal.test.AbstractTest
|
||||||
|
import net.mamoe.mirai.internal.test.assertEventBroadcasts
|
||||||
|
import net.mamoe.mirai.internal.test.runBlockingUnit
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertFails
|
||||||
|
|
||||||
|
internal class FrameworkEventTest : AbstractTest() {
|
||||||
|
|
||||||
|
class TestEvent : AbstractEvent()
|
||||||
|
class TestEvent2 : AbstractEvent()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can observe event`() = runBlockingUnit {
|
||||||
|
assertEventBroadcasts<TestEvent> {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `observes expected event`() = runBlockingUnit {
|
||||||
|
assertEventBroadcasts<TestEvent>(1) {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
TestEvent2().broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can observe event multiple times`() = runBlockingUnit {
|
||||||
|
assertEventBroadcasts<TestEvent>(2) {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
TestEvent().broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can observe event only in block`() = runBlockingUnit {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
assertEventBroadcasts<TestEvent>(1) {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `fails if times not match`() = runBlockingUnit {
|
||||||
|
assertFails {
|
||||||
|
assertEventBroadcasts<TestEvent>(2) {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFails {
|
||||||
|
assertEventBroadcasts<TestEvent>(0) {
|
||||||
|
TestEvent().broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.handler
|
|
||||||
|
|
||||||
import net.mamoe.mirai.internal.network.framework.AbstractRealNetworkHandlerTest
|
|
||||||
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler
|
|
||||||
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandlerFactory
|
|
||||||
import net.mamoe.mirai.internal.test.runBlockingUnit
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
internal class HandlerEventTest : AbstractRealNetworkHandlerTest<NettyNetworkHandler>(NettyNetworkHandlerFactory) {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `BotOnlineEvent after successful logon`() = runBlockingUnit {
|
|
||||||
bot.login()
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,7 @@ package net.mamoe.mirai.internal.network.handler
|
|||||||
import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest
|
import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest
|
||||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.CONNECTING
|
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.CONNECTING
|
||||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.INITIALIZED
|
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.INITIALIZED
|
||||||
|
import net.mamoe.mirai.internal.network.handler.state.CombinedStateObserver.Companion.plus
|
||||||
import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver
|
import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver
|
||||||
import net.mamoe.mirai.internal.network.handler.state.StateObserver
|
import net.mamoe.mirai.internal.network.handler.state.StateObserver
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@ -65,4 +66,36 @@ internal class StateObserverTest : AbstractMockNetworkHandlerTest() {
|
|||||||
assertEquals(INITIALIZED, called[0].first.correspondingState)
|
assertEquals(INITIALIZED, called[0].first.correspondingState)
|
||||||
assertEquals(CONNECTING, called[0].second.correspondingState)
|
assertEquals(CONNECTING, called[0].second.correspondingState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can combine`() {
|
||||||
|
val called = ArrayList<Pair<NetworkHandlerSupport.BaseStateImpl, NetworkHandlerSupport.BaseStateImpl>>()
|
||||||
|
components[StateObserver] = object : StateChangedObserver(CONNECTING) {
|
||||||
|
override fun stateChanged0(
|
||||||
|
networkHandler: NetworkHandlerSupport,
|
||||||
|
previous: NetworkHandlerSupport.BaseStateImpl,
|
||||||
|
new: NetworkHandlerSupport.BaseStateImpl
|
||||||
|
) {
|
||||||
|
called.add(previous to new)
|
||||||
|
}
|
||||||
|
} + object : StateChangedObserver(CONNECTING) {
|
||||||
|
override fun stateChanged0(
|
||||||
|
networkHandler: NetworkHandlerSupport,
|
||||||
|
previous: NetworkHandlerSupport.BaseStateImpl,
|
||||||
|
new: NetworkHandlerSupport.BaseStateImpl
|
||||||
|
) {
|
||||||
|
called.add(previous to new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val handler = createNetworkHandler()
|
||||||
|
assertEquals(0, called.size)
|
||||||
|
handler.setState(INITIALIZED)
|
||||||
|
assertEquals(0, called.size)
|
||||||
|
handler.setState(CONNECTING)
|
||||||
|
assertEquals(2, called.size)
|
||||||
|
assertEquals(INITIALIZED, called[0].first.correspondingState)
|
||||||
|
assertEquals(CONNECTING, called[0].second.correspondingState)
|
||||||
|
assertEquals(INITIALIZED, called[1].first.correspondingState)
|
||||||
|
assertEquals(CONNECTING, called[1].second.correspondingState)
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BotOnlineEvent after successful logon`() = runBlockingUnit {
|
||||||
|
assertEventBroadcasts<BotOnlineEvent> {
|
||||||
|
assertEquals(State.INITIALIZED, network.state)
|
||||||
|
bot.login() // launches a job which broadcasts the event
|
||||||
|
delay(3.seconds)
|
||||||
|
assertEquals(State.OK, network.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BotReloginEvent after successful reconnection`() = runBlockingUnit {
|
||||||
|
assertEventBroadcasts<BotReloginEvent> {
|
||||||
|
assertEquals(State.INITIALIZED, network.state)
|
||||||
|
bot.login()
|
||||||
|
bot.firstLoginSucceed = true
|
||||||
|
network.setStateConnecting()
|
||||||
|
network.resumeConnection()
|
||||||
|
delay(3.seconds) // `login` launches a job which broadcasts the event
|
||||||
|
assertEquals(State.OK, network.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BotOnlineEvent after successful reconnection`() = runBlockingUnit {
|
||||||
|
assertEquals(State.INITIALIZED, network.state)
|
||||||
|
bot.login()
|
||||||
|
bot.firstLoginSucceed = true
|
||||||
|
delay(3.seconds) // `login` launches a job which broadcasts the event
|
||||||
|
assertEventBroadcasts<BotOnlineEvent>(1) {
|
||||||
|
network.setStateConnecting()
|
||||||
|
network.resumeConnection()
|
||||||
|
delay(3.seconds)
|
||||||
|
assertEquals(State.OK, network.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BotOfflineEvent after successful reconnection`() = runBlockingUnit {
|
||||||
|
assertEquals(State.INITIALIZED, network.state)
|
||||||
|
bot.login()
|
||||||
|
bot.firstLoginSucceed = true
|
||||||
|
assertEquals(State.OK, network.state)
|
||||||
|
delay(3.seconds) // `login` launches a job which broadcasts the event
|
||||||
|
assertEventBroadcasts<BotOfflineEvent>(1) {
|
||||||
|
network.setStateClosed()
|
||||||
|
delay(3.seconds)
|
||||||
|
assertEquals(State.CLOSED, network.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,41 +12,37 @@ package net.mamoe.mirai.internal.test
|
|||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import net.mamoe.mirai.event.Event
|
import net.mamoe.mirai.event.Event
|
||||||
import net.mamoe.mirai.event.GlobalEventChannel
|
import net.mamoe.mirai.event.GlobalEventChannel
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal inline fun <reified T : Event, R> assertEventBroadcasts(times: Int = 1, block: () -> R): R {
|
internal inline fun <reified T : Event, R> assertEventBroadcasts(times: Int = 1, block: () -> R): R {
|
||||||
val receivedEvents = AtomicInteger(0)
|
assertEventBroadcasts<T>(times) {
|
||||||
val listener = GlobalEventChannel.subscribeAlways<T> {
|
|
||||||
receivedEvents.incrementAndGet()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return block()
|
return block()
|
||||||
} finally {
|
|
||||||
listener.complete()
|
|
||||||
assertEquals(
|
|
||||||
times,
|
|
||||||
receivedEvents.get(),
|
|
||||||
"Expected event ${T::class.simpleName} broadcast $times time(s). But actual is ${receivedEvents.get()}."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal inline fun <reified T : Event> assertEventBroadcasts(times: Int = 1, block: () -> Unit) {
|
internal inline fun <reified T : Event> assertEventBroadcasts(times: Int = 1, block: () -> Unit) {
|
||||||
val receivedEvents = AtomicInteger(0)
|
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||||
val listener = GlobalEventChannel.subscribeAlways<T> {
|
|
||||||
receivedEvents.incrementAndGet()
|
val receivedEvents = ConcurrentLinkedQueue<Event>()
|
||||||
|
val listener = GlobalEventChannel.subscribeAlways<Event> { event ->
|
||||||
|
receivedEvents.add(event)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return block()
|
return block()
|
||||||
} finally {
|
} finally {
|
||||||
|
val actual = receivedEvents.filterIsInstance<T>().count()
|
||||||
listener.complete()
|
listener.complete()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
times,
|
times,
|
||||||
receivedEvents.get(),
|
actual,
|
||||||
"Expected event ${T::class.simpleName} broadcast $times time(s). But actual is ${receivedEvents.get()}."
|
"Expected event ${T::class.simpleName} broadcast $times time(s). " +
|
||||||
|
"But actual count is ${actual}. " +
|
||||||
|
"\nAll received events: ${receivedEvents.joinToString(", ", "[", "]")}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal.test
|
package net.mamoe.mirai.internal.test
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
internal expect fun initPlatform()
|
internal expect fun initPlatform()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,3 +21,8 @@ abstract class AbstractTest {
|
|||||||
initPlatform()
|
initPlatform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal expect class PlatformInitializationTest() : AbstractTest {
|
||||||
|
@Test
|
||||||
|
fun test()
|
||||||
|
}
|
@ -9,6 +9,16 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal.test
|
package net.mamoe.mirai.internal.test
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
internal actual fun initPlatform() {
|
internal actual fun initPlatform() {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal actual class PlatformInitializationTest : AbstractTest() {
|
||||||
|
@Test
|
||||||
|
actual fun test() {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user