Skip to content

Commit 136b59d

Browse files
authored
integration test (#545)
### What problem were solved in this pull request? Issue Number: close #xxx Problem: ### What is changed and how it works? ### Other information
1 parent f9e3e2e commit 136b59d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+6672
-5
lines changed

.github/workflows/test.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
- name: start server
5252
shell: bash
5353
run: |
54-
nohup ./build_release/bin/observer -T ${{ matrix.thread_model }} -s /tmp/miniob.sock -f etc/observer.ini -P mysql -t mvcc -d disk &
54+
nohup ./build_release/bin/observer -T ${{ matrix.thread_model }} -s /tmp/miniob.sock -f etc/observer.ini -P mysql -t mvcc -d disk 2>&1 &
5555
sleep 10 && echo "wake up"
5656
mysql --version
5757
mysql -S /tmp/miniob.sock -e "show tables"
@@ -63,7 +63,7 @@ jobs:
6363
run: |
6464
cd test/sysbench
6565
sysbench --mysql-socket=/tmp/miniob.sock --mysql-ignore-errors=41 --threads=10 ${{ matrix.test_case }} prepare
66-
sysbench --mysql-socket=/tmp/miniob.sock --mysql-ignore-errors=41 --threads=10 ${{ matrix.test_case }} run || { cat ../../observer.log*; exit 1; }
66+
sysbench --mysql-socket=/tmp/miniob.sock --mysql-ignore-errors=41 --threads=10 ${{ matrix.test_case }} run || { cat ../../nohup.txt*; exit 1; }
6767
6868
- name: stop server
6969
shell: bash
@@ -75,15 +75,15 @@ jobs:
7575
- name: restart server
7676
shell: bash
7777
run: |
78-
nohup ./build_release/bin/observer -T ${{ matrix.thread_model }} -s /tmp/miniob.sock -f etc/observer.ini -P mysql -t mvcc -d disk &
78+
nohup ./build_release/bin/observer -T ${{ matrix.thread_model }} -s /tmp/miniob.sock -f etc/observer.ini -P mysql -t mvcc -d disk 2>&1 &
7979
sleep 10 && echo "wake up"
8080
mysql -S /tmp/miniob.sock -e "show tables"
8181
8282
- name: sysbench test again
8383
shell: bash
8484
run: |
8585
cd test/sysbench
86-
sysbench --mysql-socket=/tmp/miniob.sock --mysql-ignore-errors=41 --threads=10 ${{ matrix.test_case }} run || { cat ../../observer.log*; exit 1; }
86+
sysbench --mysql-socket=/tmp/miniob.sock --mysql-ignore-errors=41 --threads=10 ${{ matrix.test_case }} run || { cat ../../nohup.txt*; exit 1; }
8787
8888
benchmark-test:
8989
runs-on: ubuntu-latest
@@ -145,5 +145,26 @@ jobs:
145145
nohup ./build_release/bin/observer -T one-thread-per-connection -s /tmp/miniob.sock -f etc/observer.ini -P mysql -t mvcc -d disk &
146146
sleep 10 && echo "wake up"
147147
mysql -S /tmp/miniob.sock -e "show tables"
148-
148+
integration-test:
149+
runs-on: ubuntu-latest
150+
steps:
151+
- name: Checkout repository and submodules
152+
uses: actions/checkout@v4
153+
154+
- name: init mysql
155+
uses: shogo82148/actions-setup-mysql@v1
156+
with:
157+
mysql-version: "8.0"
158+
- name: init
159+
shell: bash
160+
run: |
161+
sudo bash build.sh init
162+
sudo apt -y install pip python3-pymysql python3-psutil
163+
164+
- name: integration test
165+
shell: bash
166+
run: |
167+
cd test/integration_test/
168+
bash ./miniob_test_docker_entry.sh
169+
python3 ./libminiob_test.py -c conf.ini
149170

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ GRTAGS
2222
GPATH
2323
GTAGS
2424
docs/book
25+
**/__pycache__
2526
#*#
2627

2728
# ignore generated files
266 KB
Loading
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
title: MiniOB 集成测试使用文档
3+
---
4+
5+
# MiniOB 集成测试使用文档
6+
MiniOB 集成测试的代码位于: `test/integration_test` 下,[训练营](http://open.oceanbase.com/train/) 中的测试与该集成测试基本一致。因此,大家可以通过该集成测试快速测试自己的 MiniOB 实现,自助排查训练营中未通过的测试用例。
7+
8+
# 如何运行集成测试
9+
10+
## 本地运行
11+
12+
### 前置依赖
13+
1. 建议使用 MiniOB 提供的官方镜像进行开发,关于如何使用可参考:[开发环境](./introduction.md)
14+
2. 安装集成测试运行所需依赖
15+
```bash
16+
sudo bash build.sh init
17+
sudo apt -y install pip python3-pymysql python3-psutil mysql-server
18+
```
19+
20+
### 执行集成测试
21+
```
22+
cd test/integration_test
23+
bash ./miniob_test_docker_entry.sh
24+
python3 libminiob_test.py -c conf.ini
25+
```
26+
## GitHub 中运行
27+
28+
目前,当你在 GitHub 上提交代码时,并向 main 分支提交 Pull Request 时,会自动触发测试用例的运行。你可以在 Pull Request 界面查看集成测试的运行结果。
29+
![](images/github_integration_test.png)
30+
31+
关于如何使用 Github Action 以自定义集成测试的执行方式可以参考:[Github Action](https://docs.github.com/en/actions)
32+
33+
## 查看执行结果
34+
当执行完一次集成测试后,最后的一条执行日志会展示执行结果,示例如下:
35+
36+
```
37+
2025-04-22 16:52:55.849 [INFO ] >>> miniob test done. result=case:aggregation-func, passed: False, result_groups:[{name:init data},{name:count, results:SELECT count(*) FROM aggregation_func;
38+
- 4}],case:basic, passed: True, result_groups:[{name:create table},{name:init data},{name:basic delete},{name:basic select},{name:create index}],case:date, passed: False, result_groups:[{name:init data, results:CREATE TABLE date_table(id int, u_date date);
39+
- SUCCESS
40+
+ SQL_SYNTAX > Failed to parse sql}],case:drop-table, passed: False, result_groups:[{name:drop empty table, results:create table Drop_table_1(id int);
41+
drop table Drop_table_1;
42+
- SUCCESS
43+
+ FAILURE}],case:update, passed: False, result_groups:[{name:init data},{name:update one row, results:UPDATE Update_table_1 SET t_name='N01' WHERE id=1;
44+
- SUCCESS
45+
+ FAILURE}] <<< libminiob_test [_test@libminiob_test.py:165] [MainThread] P:20180 T:139725989607232
46+
2025-04-22 16:52:55.849 [INFO ] >>> result = rc=0:success,outputs=,errors=,body=task_id=default,return_code=0,
47+
branch=main,commit_id=,
48+
message=,case_result=case:aggregation-func, passed: False, result_groups:[{name:init data},{name:count, results:SELECT count(*) FROM aggregation_func;
49+
- 4}],case:basic, passed: True, result_groups:[{name:create table},{name:init data},{name:basic delete},{name:basic select},{name:create index}],case:date, passed: False, result_groups:[{name:init data, results:CREATE TABLE date_table(id int, u_date date);
50+
- SUCCESS
51+
+ SQL_SYNTAX > Failed to parse sql}],case:drop-table, passed: False, result_groups:[{name:drop empty table, results:create table Drop_table_1(id int);
52+
drop table Drop_table_1;
53+
- SUCCESS
54+
+ FAILURE}],case:update, passed: False, result_groups:[{name:init data},{name:update one row, results:UPDATE Update_table_1 SET t_name='N01' WHERE id=1;
55+
- SUCCESS
56+
+ FAILURE}]
57+
<<< libminiob_test [<module>@libminiob_test.py:317] [MainThread] P:20180 T:139725989607232
58+
```
59+
60+
其中,`case:` 后面为测试用例名称,`passed:` 后面为该用例是否通过,`result_groups:` 后面为每个测试步骤的执行结果。对于未通过的测试用例,会在其`result_groups` 中展示失败的原因。例如对于上述示例中的 drop_table,其失败的原因为:
61+
62+
```
63+
result_groups:[{name:drop empty table, results:create table Drop_table_1(id int);
64+
drop table Drop_table_1;
65+
- SUCCESS
66+
+ FAILURE}]
67+
```
68+
其中, `-` 符号之后的为预期输出,`+` 符号之后的为你实现的 MiniOB 的实际输出。
69+
70+
# 如何添加集成测试测试用例
71+
添加测试用例请参考:`test/integration_test/test_cases/MiniOB/python/` 中相关的测试用例,目前已公开几道简单题目(basic/drop_table/update等)的测试用例,帮助同学们快速入门。
72+
73+
下面通过一个示例来介绍如何添加一个测试用例(MiniOB 运行结果与 MySQL 运行结果对比)。
74+
75+
```python
76+
def create_test_cases() -> TestCase:
77+
# 创建一个测试用例
78+
aggregation_test = TestCase()
79+
# 设置测试用例名称
80+
aggregation_test.name = 'aggregation-func'
81+
82+
# 创建一个执行组
83+
init_group = aggregation_test.add_execution_group('init data')
84+
# 执行组中添加 DDL 语句,注意这里需要使用 add_runtime_ddl_instruction 方法,才会增加与 MySQL 对比的测试用例。
85+
init_group.add_runtime_ddl_instruction('CREATE TABLE aggregation_func(id int, num int, price float, addr char(4));')
86+
87+
# 执行组中添加 DML 语句
88+
insert_sqls = [
89+
"INSERT INTO aggregation_func VALUES (1, 18, 10.0, 'abc');",
90+
"INSERT INTO aggregation_func VALUES (2, 15, 20.0, 'abc');",
91+
"INSERT INTO aggregation_func VALUES (3, 12, 30.0, 'def');",
92+
"INSERT INTO aggregation_func VALUES (4, 15, 30.0, 'dei');"
93+
]
94+
for sql in insert_sqls:
95+
init_group.add_runtime_dml_instruction(sql)
96+
97+
# 创建一个执行组,该执行组依赖于 init_group,通过定义依赖关系,可以在测试用例执行失败时,展示依赖的group 的测试用例,方便定位问题。
98+
count_group = aggregation_test.add_execution_group('count', [init_group])
99+
100+
# 执行组中添加 DQL 语句,注意这里需要使用 add_runtime_dql_instruction 方法,才会增加与 MySQL 对比的测试用例。
101+
count_sqls = [
102+
'SELECT count(*) FROM aggregation_func;',
103+
'SELECT count(num) FROM aggregation_func;'
104+
]
105+
106+
for sql in count_sqls:
107+
count_group.add_runtime_dql_instruction(sql)
108+
109+
return aggregation_test
110+
```
111+
112+
# 常见问题
113+
114+
Q: 我在本地运行的集成测试和训练营中的测试是完全一样的吗还是有什么区别?
115+
A: 可以认为是基本一样的,区别在于训练营中的集成测试是在内存受限(1GB)的容器内运行的。测试运行的代码基本一致,如果遇到在本地/Github 上运行的结果与训练营的结果不一致,欢迎反馈。
116+
117+
Q: 我用该集成测试可以复现出训练营中的问题,但是不知道如何进一步定位问题?
118+
A: 你可以在集成测试的关键位置增加更多自定义的日志输出。默认情况下,集成测试的日志会输出到标准输出.

docs/mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ nav:
2323
- dev-env/how_to_dev_miniob_by_vscode.md
2424
- dev-env/miniob-how-to-debug.md
2525
- dev-env/how_to_submit_for_testing.md
26+
- dev-env/integration_test.md
27+
2628
- 设计文档:
2729
- design/miniob-architecture.md
2830
- design/miniob-buffer-pool.md

test/integration_test/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# integration_test 介绍
2+
3+
MiniOB 对应的集成测试,支持运行单测,性能测试,功能性测试(同时在 MiniOB 和 MySQL 上运行相同的 SQL 测试用例以对比其结果)等。

test/integration_test/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import os
2+
import sys
3+
4+
'''
5+
'''
6+
7+
__this_file = os.path.abspath(__file__)
8+
__root_dir = os.path.dirname(os.path.dirname(__this_file))
9+
10+
if not __root_dir in sys.path:
11+
sys.path.insert(0, __root_dir)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-#
3+
4+
import json
5+
import logging
6+
7+
_logger = logging.getLogger('case_name_mapper')
8+
9+
class CaseNameMapper:
10+
'''
11+
因为本地上使用的测试名称,与前端配置的可能会不同,前端展示配置的名字也可能会不太容易操作,
12+
所以这里做一个后端case name到前端case name的映射。
13+
'''
14+
15+
def __init__(self):
16+
self.__mapper = {}
17+
18+
def use_json_file(self, filename:str):
19+
try:
20+
f = open(filename, 'r')
21+
s = f.read()
22+
self.use_json_string(s)
23+
except Exception as ex:
24+
_logger.error('failed to use json file %s. ex=%s', filename, str(ex))
25+
26+
_logger.info('case name mapper use file: %s', filename)
27+
28+
def use_json_string(self, json_string:str):
29+
try:
30+
json_data = json.loads(json_string)
31+
if type(json_data) != dict:
32+
_logger.error('invalid json. should be dict data: %s', json_string)
33+
34+
self.__mapper = json_data
35+
except Exception as ex:
36+
_logger.error('failed to parse json data: %s', json_string)
37+
38+
def map_name(self, case_name: str) -> str:
39+
value: str = self.__mapper.get(case_name)
40+
if not value:
41+
return case_name
42+
return value
43+
44+
if __name__ == '__main__':
45+
filename = '/root/miniob_test/etc/case_name_map.json'
46+
mapper = CaseNameMapper()
47+
mapper.use_json_file(filename)
48+
print(mapper.map_name('primary-complex-sub-query'))
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/env python3
2+
3+
import os
4+
import sys
5+
import logging
6+
import threading
7+
8+
from configparser import ConfigParser
9+
10+
from common import mylog
11+
12+
_logger = logging.getLogger('configuration_reader')
13+
14+
class ConfigurationReader:
15+
def __init__(self, filename: str):
16+
self.__lock = threading.Lock()
17+
self.__filename = filename
18+
19+
self.__config_items = self.__parse()
20+
21+
def __parse(self) -> ConfigParser:
22+
with self.__lock:
23+
cp = ConfigParser()
24+
try:
25+
cp.read(self.__filename)
26+
_logger.debug('read config from file: %s', self.__filename)
27+
except Exception as ex:
28+
_logger.error('failed to read configuration from file %s', self.__filename)
29+
raise
30+
31+
return cp
32+
33+
def get(self, section, key, default=None, reparse=False):
34+
config_items = self.__config_items
35+
if reparse:
36+
try:
37+
config_items = self.__parse()
38+
except Exception as ex:
39+
_logger.error('failed to read config')
40+
41+
with self.__lock:
42+
value = config_items.get(section, key, fallback=default)
43+
if value is None:
44+
return default
45+
return value
46+
47+
def get_int(self, section, key, default=None, reparse=False):
48+
value = self.get(section, key, default, reparse)
49+
try:
50+
return int(value)
51+
except Exception as ex:
52+
_logger.warn('failed to convert %s to integer', str(value))
53+
return default
54+
55+
def get_bool(self, section, key, default=None, reparse=False):
56+
value = self.get(section, key, default, reparse)
57+
try:
58+
value = value.upper()
59+
if value == '0':
60+
return False
61+
if value == 'FALSE' or value == 'NO' or value == 'OFF':
62+
return False
63+
64+
return True
65+
except Exception as ex:
66+
_logger.warn('failed to convert %s to boolean', str(value))
67+
return default
68+
69+
if __name__ == '__main__':
70+
pass

0 commit comments

Comments
 (0)