From 300882ea18def1be55e9b7ec327d516610476132 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Tue, 8 Jun 2021 17:13:15 +0800
Subject: [PATCH] Redesign `ComponentStorage` initialization for clearer tests

---
 .../src/commonMain/kotlin/QQAndroidBot.kt     | 13 ++---
 .../component/ComponentStorageDelegate.kt     | 20 +++++++
 .../component/MutableComponentStorage.kt      | 11 ++++
 mirai-core/src/commonTest/kotlin/MockBot.kt   | 19 ++++---
 .../AbstractMockNetworkHandlerTest.kt         |  2 +-
 .../AbstractRealNetworkHandlerTest.kt         | 53 +++++--------------
 .../handler/SelectorNetworkHandlerTest.kt     |  2 +-
 .../network/impl/netty/AbstractNettyNHTest.kt |  2 +-
 8 files changed, 66 insertions(+), 56 deletions(-)
 create mode 100644 mirai-core/src/commonMain/kotlin/network/component/ComponentStorageDelegate.kt

diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
index a9534522d..ef10cd6e8 100644
--- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
+++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
@@ -20,6 +20,7 @@ import net.mamoe.mirai.event.events.BotOnlineEvent
 import net.mamoe.mirai.event.events.BotReloginEvent
 import net.mamoe.mirai.internal.contact.checkIsGroupImpl
 import net.mamoe.mirai.internal.network.component.ComponentStorage
+import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate
 import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
 import net.mamoe.mirai.internal.network.components.*
 import net.mamoe.mirai.internal.network.context.SsoProcessorContext
@@ -109,13 +110,15 @@ internal open class QQAndroidBot constructor(
 
 
     private val networkLogger: MiraiLogger by lazy { configuration.networkLoggerSupplier(this) }
-    override val components: ComponentStorage by lazy {
-        createDefaultComponents()
+    final override val components: ComponentStorage by lazy {
+        createDefaultComponents().apply {
+            set(StateObserver, stateObserverChain())
+        }
     }
 
-    fun createDefaultComponents(): ConcurrentComponentStorage {
+    open fun createDefaultComponents(): ConcurrentComponentStorage {
         return ConcurrentComponentStorage().apply {
-            val components = this // avoid mistakes
+            val components = ComponentStorageDelegate { this@QQAndroidBot.components }
 
             // There's no need to interrupt a broadcasting event when network handler closed.
             set(EventDispatcher, EventDispatcherImpl(bot.coroutineContext, logger.subLogger("EventDispatcher")))
@@ -154,8 +157,6 @@ internal open class QQAndroidBot constructor(
                 OtherClientUpdaterImpl(bot, components, networkLogger.subLogger("OtherClientUpdater"))
             )
             set(ConfigPushSyncer, ConfigPushSyncerImpl())
-
-            set(StateObserver, stateObserverChain())
         }
     }
 
diff --git a/mirai-core/src/commonMain/kotlin/network/component/ComponentStorageDelegate.kt b/mirai-core/src/commonMain/kotlin/network/component/ComponentStorageDelegate.kt
new file mode 100644
index 000000000..8617a13b6
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/network/component/ComponentStorageDelegate.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.component
+
+internal class ComponentStorageDelegate(
+    private val instance: () -> ComponentStorage
+) : ComponentStorage {
+    override val size: Int get() = instance().size
+    override fun <T : Any> get(key: ComponentKey<T>): T = instance()[key]
+    override fun <T : Any> getOrNull(key: ComponentKey<T>): T? = instance().getOrNull(key)
+    override val keys: Set<ComponentKey<*>> get() = instance().keys
+    override fun toString(): String = instance().toString()
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/network/component/MutableComponentStorage.kt b/mirai-core/src/commonMain/kotlin/network/component/MutableComponentStorage.kt
index 8e8b51614..0c600d763 100644
--- a/mirai-core/src/commonMain/kotlin/network/component/MutableComponentStorage.kt
+++ b/mirai-core/src/commonMain/kotlin/network/component/MutableComponentStorage.kt
@@ -9,10 +9,21 @@
 
 package net.mamoe.mirai.internal.network.component
 
+import net.mamoe.mirai.utils.TestOnly
+import net.mamoe.mirai.utils.cast
+
 internal interface MutableComponentStorage : ComponentStorage {
     override operator fun <T : Any> get(key: ComponentKey<T>): T
     operator fun <T : Any> set(key: ComponentKey<T>, value: T)
     fun <T : Any> remove(key: ComponentKey<T>): T?
+
+}
+
+@TestOnly
+internal fun MutableComponentStorage.setAll(other: ComponentStorage) {
+    for (key in other.keys) {
+        set(key.cast(), other[key])
+    }
 }
 
 internal operator fun <T : Any> MutableComponentStorage.set(key: ComponentKey<T>, value: T?) {
diff --git a/mirai-core/src/commonTest/kotlin/MockBot.kt b/mirai-core/src/commonTest/kotlin/MockBot.kt
index 7a918d43a..41650bc29 100644
--- a/mirai-core/src/commonTest/kotlin/MockBot.kt
+++ b/mirai-core/src/commonTest/kotlin/MockBot.kt
@@ -14,6 +14,7 @@ package net.mamoe.mirai.internal
 
 import net.mamoe.mirai.internal.network.component.ComponentStorage
 import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
+import net.mamoe.mirai.internal.network.component.setAll
 import net.mamoe.mirai.internal.network.handler.NetworkHandler
 import net.mamoe.mirai.utils.BotConfiguration
 import kotlin.contracts.InvocationKind
@@ -29,7 +30,7 @@ internal class MockBotBuilder(
     val conf: BotConfiguration = BotConfiguration(),
 ) {
     var nhProvider: (QQAndroidBot.(bot: QQAndroidBot) -> NetworkHandler)? = null
-    var componentsProvider: (QQAndroidBot.(bot: QQAndroidBot) -> ComponentStorage)? = null
+    var additionalComponentsProvider: (QQAndroidBot.(bot: QQAndroidBot) -> ComponentStorage)? = null
 
     fun conf(action: BotConfiguration.() -> Unit): MockBotBuilder {
         contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
@@ -44,16 +45,20 @@ internal class MockBotBuilder(
 }
 
 @Suppress("TestFunctionName")
-internal fun MockBot(conf: MockBotBuilder.() -> Unit = {}) =
-    MockBotBuilder(MockConfiguration.copy()).apply(conf).run {
+internal fun MockBot(conf: MockBotBuilder.() -> Unit = {}): QQAndroidBot {
+    return MockBotBuilder(MockConfiguration.copy()).apply(conf).run {
         object : QQAndroidBot(MockAccount, this.conf) {
-            override val components: ComponentStorage by lazy {
-                componentsProvider?.invoke(this, this) ?: EMPTY_COMPONENT_STORAGE
+            override fun createDefaultComponents(): ConcurrentComponentStorage {
+                return super.createDefaultComponents().apply {
+                    val componentsProvider = additionalComponentsProvider
+                    if (componentsProvider != null) {
+                        setAll(componentsProvider(bot, bot))
+                    }
+                }
             }
 
             override fun createNetworkHandler(): NetworkHandler =
                 nhProvider?.invoke(this, this) ?: super.createNetworkHandler()
         }
     }
-
-private val EMPTY_COMPONENT_STORAGE = ConcurrentComponentStorage()
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt
index 7a9dc9b59..998a71846 100644
--- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt
+++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt
@@ -35,7 +35,7 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractTest() {
 
     protected val bot: QQAndroidBot = MockBot {
         nhProvider = { createNetworkHandler() }
-        componentsProvider = { this@AbstractMockNetworkHandlerTest.components }
+        additionalComponentsProvider = { this@AbstractMockNetworkHandlerTest.components }
     }
     protected val logger = MiraiLogger.create("test")
     protected val components = ConcurrentComponentStorage().apply {
diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt
index 1c1d4367d..78eea477e 100644
--- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt
+++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt
@@ -11,11 +11,11 @@ package net.mamoe.mirai.internal.network.framework
 
 import kotlinx.coroutines.CoroutineScope
 import net.mamoe.mirai.internal.AbstractBot
-import net.mamoe.mirai.internal.MockBot
+import net.mamoe.mirai.internal.MockAccount
+import net.mamoe.mirai.internal.MockConfiguration
 import net.mamoe.mirai.internal.QQAndroidBot
-import net.mamoe.mirai.internal.network.component.ComponentStorage
 import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
-import net.mamoe.mirai.internal.network.component.withFallback
+import net.mamoe.mirai.internal.network.component.setAll
 import net.mamoe.mirai.internal.network.components.*
 import net.mamoe.mirai.internal.network.context.SsoProcessorContext
 import net.mamoe.mirai.internal.network.context.SsoProcessorContextImpl
@@ -24,7 +24,6 @@ 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.internal.utils.subLogger
 import net.mamoe.mirai.utils.MiraiLogger
@@ -45,9 +44,12 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
     abstract val network: NetworkHandler
 
     var bot: QQAndroidBot by lateinitMutableProperty {
-        MockBot {
-            networkHandlerProvider { createHandler() }
-            componentsProvider = { network.context } // initialized in [createHandler]
+        object : QQAndroidBot(MockAccount, MockConfiguration.copy()) {
+            override fun createDefaultComponents(): ConcurrentComponentStorage =
+                super.createDefaultComponents().apply { setAll(overrideComponents) }
+
+            override fun createNetworkHandler(): NetworkHandler =
+                this@AbstractRealNetworkHandlerTest.createHandler()
         }
     }
 
@@ -65,7 +67,7 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
     /**
      * This overrides [QQAndroidBot.components]
      */
-    open val defaultComponents = ConcurrentComponentStorage().apply {
+    open val overrideComponents = ConcurrentComponentStorage().apply {
         set(SsoProcessorContext, SsoProcessorContextImpl(bot))
         set(SsoProcessor, object : TestSsoProcessor(bot) {
             override suspend fun login(handler: NetworkHandler) {
@@ -117,38 +119,9 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
         // set(StateObserver, bot.run { stateObserverChain() })
     }
 
-    /**
-     * [additionalComponents] overrides [defaultComponents] and [QQAndroidBot.components]
-     */
-    open fun createHandler(additionalComponents: ComponentStorage? = null): H {
-        return factory.create(
-            createContext(additionalComponents),
-            address
-        )
-    }
-
-    val address = InetSocketAddress.createUnresolved("localhost", 123)
-
-    open fun createContext(additionalComponents: ComponentStorage? = null): NetworkHandlerContextImpl {
-        // StateObserver
-        val components =
-            additionalComponents.withFallback(defaultComponents).withFallback(bot.createDefaultComponents())
-        val observerComponents = if (
-            additionalComponents?.getOrNull(StateObserver)
-            ?: defaultComponents.getOrNull(StateObserver)
-            == null
-        ) {
-            ConcurrentComponentStorage().apply {
-                set(StateObserver, bot.run { components.stateObserverChain() })
-            }
-        } else null
-
-        return NetworkHandlerContextImpl(
-            bot,
-            networkLogger,
-            observerComponents.withFallback(components)
-        )
-    }
+    open fun createHandler(): H = factory.create(createContext(), address)
+    open fun createContext(): NetworkHandlerContextImpl = NetworkHandlerContextImpl(bot, networkLogger, bot.components)
+    val address: InetSocketAddress = InetSocketAddress.createUnresolved("localhost", 123)
 
     ///////////////////////////////////////////////////////////////////////////
     // Assertions
diff --git a/mirai-core/src/commonTest/kotlin/network/handler/SelectorNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/handler/SelectorNetworkHandlerTest.kt
index a0c399fb7..69bc148e9 100644
--- a/mirai-core/src/commonTest/kotlin/network/handler/SelectorNetworkHandlerTest.kt
+++ b/mirai-core/src/commonTest/kotlin/network/handler/SelectorNetworkHandlerTest.kt
@@ -80,7 +80,7 @@ internal class SelectorNetworkHandlerTest : AbstractRealNetworkHandlerTest<Selec
                 return listOf(Job())
             }
         }
-        defaultComponents[HeartbeatScheduler] = heartbeatScheduler
+        overrideComponents[HeartbeatScheduler] = heartbeatScheduler
 
         bot.login()
         bot.network.context[EventDispatcher].joinBroadcast()
diff --git a/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt b/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt
index 4ecd8ff5b..3d468b40c 100644
--- a/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt
+++ b/mirai-core/src/commonTest/kotlin/network/impl/netty/AbstractNettyNHTest.kt
@@ -95,7 +95,7 @@ internal abstract class AbstractNettyNHTest : AbstractRealNetworkHandlerTest<Tes
 }
 
 internal fun AbstractNettyNHTest.setSsoProcessor(action: suspend SsoProcessor.(handler: NetworkHandler) -> Unit) {
-    defaultComponents[SsoProcessor] = object : SsoProcessor by defaultComponents[SsoProcessor] {
+    overrideComponents[SsoProcessor] = object : SsoProcessor by overrideComponents[SsoProcessor] {
         override suspend fun login(handler: NetworkHandler) = action(handler)
     }
 }
\ No newline at end of file