Skip to content

Commit

Permalink
Mock Testing Framework (mamoe#1521)
Browse files Browse the repository at this point in the history
Co-authored-by: Eritque arcus <[email protected]>
Co-authored-by: Him188 <[email protected]>
  • Loading branch information
3 people authored Sep 10, 2022
1 parent 2eb2c3a commit 2db9804
Show file tree
Hide file tree
Showing 85 changed files with 8,018 additions and 0 deletions.
9 changes: 9 additions & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ object Versions {
const val yamlkt = "0.12.0"
const val intellijGradlePlugin = "1.7.0"

// https://github.com/google/jimfs
// Java In Memory File System
const val jimfs = "1.2"


// don't update easily unless you want your disk space -= 1000 MB
// (700 MB for IDEA, 150 MB for sources, 150 MB for JBR)
const val intellij = "222.3345-EAP-CANDIDATE-SNAPSHOT"
Expand Down Expand Up @@ -115,6 +120,10 @@ val `ktor-client-logging` = ktor("client-logging", Versions.ktor)
val `ktor-network` = ktor("network-jvm", Versions.ktor)
val `ktor-client-serialization` = ktor("client-serialization", Versions.ktor)

val `ktor-server-core` = ktor("server-core", Versions.ktor)
val `ktor-server-netty` = ktor("server-netty", Versions.ktor)
const val `java-in-memory-file-system` = "com.google.jimfs:jimfs:" + Versions.jimfs

const val `logback-classic` = "ch.qos.logback:logback-classic:" + Versions.logback

const val `slf4j-api` = "org.slf4j:slf4j-api:" + Versions.slf4j
Expand Down
1 change: 1 addition & 0 deletions docs/.conf/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = {
{text: "事件列表", link: "/EventList.html"},
{text: "Debugging Network", link: "/DebuggingNetwork.html"},
{text: "Using Dev Snapshots", link: "/UsingSnapshots.html"},
{text: "mirai 模拟测试框架", link: "/mocking/Mocking.md"},
]
},
],
Expand Down
5 changes: 5 additions & 0 deletions docs/Bots.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [处理滑动验证码](#处理滑动验证码)
- [常见登录失败原因](#常见登录失败原因)
- [附录: 调试网络层](#附录-调试网络层)
- [附录: 模拟测试框架](#附录-模拟测试框架)

## 1. 创建和配置 `Bot`

Expand Down Expand Up @@ -284,6 +285,10 @@ contactListCache.setSaveIntervalMillis(60000) // 可选设置有更新时的保

参阅 [DebuggingNetwork.md](DebuggingNetwork.md)

## 附录: 模拟测试框架

参阅 [Mocking.md](mocking/Mocking.md)

> 下一步,[Contacts](Contacts.md)
>
> [回到 Mirai 文档索引](CoreAPI.md)
Expand Down
49 changes: 49 additions & 0 deletions docs/mocking/Mocking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Mirai - Mocking

本章节介绍 mirai 模拟环境

> mirai 模拟环境从 `2.13` 开始支持
>
> 注:
> - **不支持**同时运行模拟环境和真实环境
> - **不支持**从模拟环境切换回真实环境
-----------------------------------

# 在非 console 中进行模拟

## 环境准备

要使用 mirai 模拟环境测试框架, 首先需要额外添加一项依赖

```kotlin
dependencies {
testImplementation("net.mamoe:mirai-core-mock:$VERSION")
}
```

并在本地的测试入口添加以下的代码

```kotlin
internal fun main() {
MockBotFactory.initialize()
// .....
}
```

## 创建 Bot

对于创建 `MockBot`, 更好的方法是使用 `MockBotFactory.newMockBotBuilder()`

也可以使用原始的 `BotFactory` 来创建一个新的 `MockBot`, 系统会使用默认值填充相关的信息

## 使用

关于 `MockBot` 可以在 [这里](https://github.com/mamoe/mirai/tree/dev/mirai-core-mock/test/mock)
找到 mirai-core-mock 的相关用法

----------------

# 在 console 中进行模拟

Work In Progress...
21 changes: 21 additions & 0 deletions mirai-core-mock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# mirai-core-mock

mirai 模拟环境测试框架

> 模拟环境目前仅支持 JVM
--------------

# src 架构

- `contact` - 与 `mirai-core-api` 架构一致
- `database` - 数据库, 用于存储一些临时的零碎数据
- `resserver` - 资源服务
- `userprofile` - 与 `UserProfile` 相关的一些服务
- `utils` - 工具类

# test 架构

- `<toplevel>` 与 mirai-core-api 关系不大或者一些独立的组件的测试
- `.mock` 模拟的各个部分的测试, 每个测试都继承 `MockBotTestBase`

37 changes: 37 additions & 0 deletions mirai-core-mock/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2019-2022 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/dev/LICENSE
*/


plugins {
kotlin("jvm")
kotlin("plugin.serialization")
`maven-publish`
id("me.him188.kotlin-jvm-blocking-bridge")
}

version = Versions.project
description = "Mirai core mock testing framework"

kotlin {
explicitApiWarning()
}

dependencies {
api(project(":mirai-core-api"))
implementation(project(":mirai-core-utils"))
implementation(project(":mirai-core"))

implementation(`ktor-server-core`)
implementation(`ktor-server-netty`)
implementation(`java-in-memory-file-system`)

}

configurePublishing("mirai-core-mock")
tasks.named("shadowJar") { enabled = false }
187 changes: 187 additions & 0 deletions mirai-core-mock/src/MockActions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright 2019-2022 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/dev/LICENSE
*/

package net.mamoe.mirai.mock

import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.mock.contact.MockFriend
import net.mamoe.mirai.mock.contact.MockNormalMember
import net.mamoe.mirai.mock.contact.MockStranger
import net.mamoe.mirai.mock.contact.MockUserOrBot
import net.mamoe.mirai.mock.database.removeMessageInfo
import net.mamoe.mirai.mock.utils.NudgeDsl
import net.mamoe.mirai.mock.utils.mock
import net.mamoe.mirai.mock.utils.nudged0
import net.mamoe.mirai.utils.cast

@JvmBlockingBridge
public object MockActions {

/**
* 修改 [MockUserOrBot.nick] 并广播相关事件 (如 [FriendNickChangedEvent])
*/
@JvmStatic
public suspend fun fireNickChanged(target: MockUserOrBot, value: String) {
when (target) {
is MockFriend -> {
val ov = target.nick
target.mockApi.nick = value
FriendNickChangedEvent(target, ov, target.nick).broadcast()
}

is MockStranger -> {
target.mockApi.nick = value
// TODO: StrangerNickChangedEvent
}

is MockNormalMember -> {
val friend0 = target.bot.getFriend(target.id)
if (friend0 != null) {
return fireNickChanged(friend0, value)
}
target.mockApi.nick = value
}

is MockBot -> {
target.nick = value
}
}
}

/**
* 修改 [MockNormalMember.nameCard] 并广播 [MemberCardChangeEvent]
*/
@JvmStatic
public suspend fun fireNameCardChanged(member: MockNormalMember, value: String) {
val ov = member.nameCard
member.mockApi.nameCard = value
MemberCardChangeEvent(ov, value, member).broadcast()
}

/**
* 修改 [MockNormalMember.specialTitle] 并广播 [MemberSpecialTitleChangeEvent]
*/
@JvmStatic
public suspend fun fireSpecialTitleChanged(member: MockNormalMember, value: String) {
val ov = member.specialTitle
member.mockApi.specialTitle = value
MemberSpecialTitleChangeEvent(
ov,
value,
member,
operator = member.group.owner.takeIf { it.id != member.bot.id },
).broadcast()
}

/**
* 修改一名成员的权限并广播 [MemberPermissionChangeEvent]
*/
@JvmStatic
public suspend fun firePermissionChanged(member: MockNormalMember, perm: MemberPermission) {
if (perm == MemberPermission.OWNER || member == member.group.owner) {
error("Use group.changeOwner to modify group owner")
}
val ov = member.permission
member.mockApi.permission = perm
if (member.id == member.bot.id) {
BotGroupPermissionChangeEvent(member.group, ov, perm)
} else {
MemberPermissionChangeEvent(member, ov, perm)
}.broadcast()
}

/**
* 令 [operator] 撤回一条消息
*
* @param operator 当 [operator] 为 null 时代表是发送者自己撤回
*/
@JvmStatic
public suspend fun fireMessageRecalled(chain: MessageChain, operator: User? = null) {
return fireMessageRecalled(chain.source, operator)
}

/**
* 令 [operator] 撤回一条消息
*
* @param operator 当 [operator] 为 null 时代表是发送者自己撤回
*/
@JvmStatic
public suspend fun fireMessageRecalled(source: MessageSource, operator: User? = null) {
if (source is OnlineMessageSource) {
val from = source.sender
when (val target = source.target) {
is Group -> {
from.bot.mock().msgDatabase.removeMessageInfo(source)
MessageRecallEvent.GroupRecall(
source.bot,
from.id,
source.ids,
source.internalIds,
source.time,
operator?.cast(),
target,
when (from) {
is Bot -> target.botAsMember
else -> from.cast()
}
).broadcast()
return
}

is Friend -> {
from.bot.mock().msgDatabase.removeMessageInfo(source)
MessageRecallEvent.FriendRecall(
source.bot,
source.ids,
source.internalIds,
source.time,
from.id,
from.cast()
).broadcast()
return
}
}
}
error("Unsupported message source type: ${source.javaClass}")
}

/**
* 令 [operator] 撤回一条消息
*
* @param operator 当 [operator] 为 null 时代表是发送者自己撤回
*/
@JvmStatic
public suspend fun mockFireRecalled(receipt: MessageReceipt<*>, operator: User? = null) {
return fireMessageRecalled(receipt.source, operator)
}

/**
* 令 [actor] 戳一下 [actee]
*
* @param actor 发起戳一戳的人
* @param actee 被戳的人
*/
@JvmStatic
public suspend fun fireNudge(actor: MockUserOrBot, actee: MockUserOrBot, dsl: NudgeDsl) {
actor.nudged0(actee, dsl)
}

}
Loading

0 comments on commit 2db9804

Please sign in to comment.