Skip to content

Commit

Permalink
Merge pull request #32 from zzhuncle/comments
Browse files Browse the repository at this point in the history
Added code comments for static test files and functional document for project
  • Loading branch information
AxingguChen authored Sep 22, 2023
2 parents 07232ca + cdbb030 commit 8a9f8b0
Show file tree
Hide file tree
Showing 9 changed files with 461 additions and 2 deletions.
Binary file added assets/overflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions doc/zh/func_doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 功能文档

## 代码框架

源代码编译运行后会生成三个可执行文件,分别为静态测试文件 `3ts_dbtest`、动态测试 文件 `3ts_dbtest_v2` 和键值数据库测试文件 `3ts_kvtest`,因此可以据此划分并阅读源代码。

除核心源代码外,`src` 文件夹还包含一些脚本文件、配置文件和测试用例文件。其中脚 本文件 `auto_test_all.sh` 用于自动化全部测试用例,而 `auto_test.sh` 用于自动化某些测试 用例;配置文件 `CMakeLists.txt` 用于 `CMake` 项目构建;`do_test_list.txt` 包含一些测试用 例文件的列表;`t` 目录包含所有静态测试用例文件。

## 总体流程图

以静态/动态测试为例,给出如下总体流程图。

![image-20230921143844936](../../assets/overflow.png)

## 静态测试

静态测试包含 `33` 个测试用例,其测试文件在 `p` 文件夹下,不需要生成测试文件。源文件包括:`common.cc``case_cntl.cc``sql_cntl.cc``sqltest.cc` 以及对应的头文件,生成的可执行文件名为 `3ts_dbtest`

### 源文件分析:

- `common.cc` 包含常用的函数和操作,比如将给定的字符串按照分隔符进行分割以及移 除空白字符或者引号字符等;
- `case_cntl.cc` 实现了测试用例控制功能,包含从测试文件中读取数据库配置、`sql` 语句 以及预期结果,验证处理实际结果和预期结果的区别,输出比对结果到指定文件夹等功能;
- `sql_cntl.cc` 涉及 `SQL` 控制或操作的代码。即实现了与 `ODBC` 数据库的接口功能。包 含设置数据库隔离级别、开始事务、执行增删改查等功能、处理 `SQL` 返回值并获取错 误信息、结束事务或回滚事务等功能。
- `sqltest.cc` 涉及 `SQL` 测试的代码,包含项目的主函数。该文件利用 `gflags` 库来实现命 令行参数解析,采用多线程编程,每个线程负责一组数据库语句的执行,并且通过子 线程不同的休眠时间实现程序代码按指定顺序运行。

### 重点类分析:

`CaseReader` 用于读取和解析测试用例;`TestResultSet` 用于表示测试结果集;`TxnSql` 与事务 `SQL` 操作相关;`TestSequence` 表示测试序列;`ResultHandler` 用 于处理测试结果;`Outputter` 用于格式化和输出测试结果。

### 重点函数分析:

`ExecTestSequence` 用于执行一个指定的测试序列;`ExecAllTestSequence` 利用多线程执行所有测试序列。

## 动态测试

动态测试需要通过 `python` 脚本文件动态生成测试文件。源文件包括:`common.cc``case_cntl_v2.cc``sql_cntl_v2.cc``sqltest_v2.cc`,生成的可执行文件名为 `3ts_dbtest_v2`。其中 `*_v2.cc` 为对应静态测试文件的另一个版本,大致功能相似。

较大的不同为动态测试中测试样例由 `python` 脚本文件生成,包含 `random_do_list.py``mda_detect.py``mda_generate.py` 文件。其中 `random_do_list.py` 用于随机生成测试文件 列表;`mda_generate.py` 用于生成指定格式测试样例;`mda_detect.py`: 可能用于检测测试 结果是否出现异常现象。

### 源文件分析:

- `random_do_list.py` 基于预定义的操作集随机生成一系列数据库操作,此外还要确保 生成的操作列表不含任何非法模式。此脚本文件会生成 `do_test_list.txt` 文件,该文件包含不同的测试模式,即具体测试文件列表。
- `mda_generate.py``do_test_list.txt` 文件中读取所需的操作模式(例如 `RW-RR` 等), 针对每种操作模式生成一个测试用例文件,该文件包含初始化表、执行事务和验证的 `SQL` 语句。主要目的是为不同的操作模式自动生成数据库测试用例。
- `mda_detect.py` 主要用于数据库事务的序列化调度检查。即读取事务,并构建一个图, 该图表示事务之间的依赖关系。然后,程序会检查这个图是否存在循环,从而判断事 务调度是否是序列化的。

## 键值数据库测试

键值数据库测试源文件包括:`common.cc``case_cntl.cc``kv_cntl.cc``kvtest.cc` 文件, 生成的可执行文件名为 `3ts_kvtest`,其中 `kvtest.cc` 涉及键值数据库测试的代码。

## 功能扩展

即添加其他数据库测试结果,以 `MariaDB` 数据库为例。

**数据库连接**`DBConnector` 类中添加 `MariaDB` 的连接代码,需要使用 `MariaDB``C` `API` 或其他库来实现。此外还需要安装和配置必要的 `MariaDB` 客户端库和头文件,确保它 们能在项目中被正确引用。

**配置文件** 重新配置文件来指定数据库连接信息(例如,数据库主机、用户名、密码等),确保添加对 `MariaDB` 相关配置的支持。

**`SQL` 语法** 虽然 `MariaDB` 基本上与 `MySQL`兼容,但还是可能存在一些语法上的差异。因此需要确保 `SQL` 语句和查询对 `MariaDB` 兼容。

**功能测试** 根据项目的结构和代码,需要在 `CaseReader``ResultHandler` 类中添加或修改 代码,以处理 `MariaDB` 的特定测试和结果。并且需要考虑添加 `MariaDB` 特有的功能和事务测试。

**错误处理** 更新错误处理代码以处理 `MariaDB` 特定的错误和异常。

**文档说明** 更新项目的 `README` 或其他文档,添加关于如何配置和运行针对 `MariaDB` 的 测试的说明。
147 changes: 147 additions & 0 deletions src/dbtest/src/case_cntl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@
Outputter outputter;
ResultHandler result_handler;

/**
* Parses a given line to extract the execution order ID, transaction ID, and SQL statement.
*
* The function expects the input line to be formatted with dashes ("-") separating the
* execution order ID, the transaction ID, and the SQL statement. If the format is not met,
* an error message is displayed. The function then returns a vector containing the extracted elements.
*
* @param line The input string containing the execution order ID, transaction ID, and SQL statement.
* @return A vector of strings where the first element is the execution order ID,
* the second is the transaction ID, and the third is the SQL statement.
*/
std::vector<std::string> CaseReader::TxnIdAndSql(const std::string& line) {
std::vector<std::string> txn_id_and_sql;
const auto index_first = line.find("-");
Expand All @@ -28,6 +39,23 @@ std::vector<std::string> CaseReader::TxnIdAndSql(const std::string& line) {
return txn_id_and_sql;
}

/**
* Parses a given line to extract the SQL ID and its expected result.
*
* The function expects the input line to be formatted with a dash ("-") separating the
* SQL ID from the result. The result can be a space-separated list of values. The function
* will then return a pair where the first element is the SQL ID (as an integer) and the
* second element is the enhanced result string. Each value in the result is enclosed within
* parentheses, except for 'null' values.
*
* For example, given a line "9-0,1 1,1", the function will return a pair with 9 as the SQL ID
* and "(0,1) (1,1)" as the result.
*
* If the format is not met or there's an issue with the SQL ID, an appropriate message is displayed.
*
* @param line The input string containing the SQL ID and its expected result.
* @return A pair where the first element is the SQL ID and the second is the enhanced result string.
*/
std::pair<int, std::string> CaseReader::SqlIdAndResult(const std::string& line) {
std::pair<int, std::string> sql_id_and_result;
const auto index = line.find("-");
Expand Down Expand Up @@ -61,11 +89,43 @@ std::pair<int, std::string> CaseReader::SqlIdAndResult(const std::string& line)
return sql_id_and_result;
}

/**
* Extracts the isolation level from the given line.
*
* The function expects the input line to contain an isolation level followed by
* a curly brace "{" character. It will extract and return everything before the
* curly brace as the isolation level.
*
* For example, given a line "serializable{...}", the function will return "serializable".
*
* @param line The input string containing the isolation level and subsequent data enclosed within curly braces.
* @return The extracted isolation level from the input string.
*/
std::string CaseReader::Isolation(const std::string& line) {
const auto index = line.find("{");
return line.substr(0, index - 1);
}

/**
* Parses the provided test file to extract both test sequences and their corresponding expected results.
*
* The function reads through the test file line by line to identify SQL transactions, their expected results,
* and additional information like the isolation level and parameter numbers. It constructs two main data structures:
* a TestSequence which represents the sequence of transactions, and a TestResultSet which represents the set
* of expected results for each transaction in the sequence.
*
* @param test_file Path to the test file to be read.
* @param db_type Database type identifier.
*
* Expected Format:
* - The file may contain lines starting with "#" which are treated as comments and ignored.
* - A line with "ParamNum" is used to set the number of parameters for the test sequence.
* - The isolation level is expected to be present in a line that contains a "{" character.
* - Each transaction's SQL and its expected result are parsed from lines without "{" or "}".
* - Each result row is defined between lines with "{" and "}".
*
* @return A pair containing the extracted TestSequence and TestResultSet from the test file.
*/
std::pair<TestSequence, TestResultSet> CaseReader::TestSequenceAndTestResultSetFromFile(const std::string& test_file, const std::string& db_type) {
std::pair<TestSequence, TestResultSet> test_sequence_and_result_set;
std::ifstream test_stream(test_file);
Expand All @@ -87,9 +147,11 @@ std::pair<TestSequence, TestResultSet> CaseReader::TestSequenceAndTestResultSetF
auto index_left = line.find("{");
auto index_right = line.find("}");
auto index_anno = line.find("#");
// Skip comment lines (those starting with #)
if (index_anno != line.npos) {
continue;
}
// Retrieve and set the parameter count (if the line contains "ParamNum")
if (index_table_type != line.npos) {
auto start = line.find(":");
std::string param_num_str = "";
Expand Down Expand Up @@ -158,6 +220,20 @@ std::pair<TestSequence, TestResultSet> CaseReader::TestSequenceAndTestResultSetF
return test_sequence_and_result_set;
}

/**
* Initializes the lists of TestSequence and TestResultSet List based on a provided test path and database type.
*
* The function starts by reading the "do_test_list.txt" file. Each line in this file should represent
* a test case. The function will then, for each test case, retrieve the respective test sequence and
* test result set by reading the corresponding file named "<test_case>.txt" located in the provided test path.
*
* Lines in the "do_test_list.txt" file starting with a '#' are considered comments and are skipped.
* If the required test file for a test case is successfully read and parsed, a success message is printed.
*
* @param test_path The path to the directory containing the test files.
* @param db_type The type of the database being tested (e.g., "MySQL").
* @return true if the initialization is successful, false if the "do_test_list.txt" file is not found.
*/
bool CaseReader::InitTestSequenceAndTestResultSetList(const std::string& test_path, const std::string& db_type) {
std::cout << dash + "read test sequence and test result set start" + dash << std::endl;
std::ifstream do_test_stream("./do_test_list.txt");
Expand Down Expand Up @@ -188,6 +264,19 @@ bool CaseReader::InitTestSequenceAndTestResultSetList(const std::string& test_pa
}
}

/**
* Compares the current SQL result with the expected SQL result.
*
* The function checks if the given current result (cur_result) matches the expected result (expected_result) for a specific SQL query.
* The comparison is done element-wise for each entry in the result vectors. If any entry is different, or the sizes of the two vectors
* differ, the function returns false, indicating the results are not as expected. Otherwise, it returns true.
*
* @param cur_result A vector containing the current SQL query result.
* @param expected_result A vector containing the expected SQL query result.
* @param sql_id The ID of the SQL statement being compared.
* @param sql The actual SQL statement string (currently not used in the function).
* @return true if the current result matches the expected result, false otherwise.
*/
bool ResultHandler::IsSqlExpectedResult(std::vector<std::string> cur_result, std::vector<std::string> expected_result, const int sql_id, const std::string& sql) {
if (cur_result.size() != expected_result.size()) {
//std::cout << "stmt_id:" << sql_id << " The number of data is different, cur_result:" << cur_result.size() << " expected_result: " << expected_result.size() << std::endl;
Expand All @@ -204,6 +293,21 @@ bool ResultHandler::IsSqlExpectedResult(std::vector<std::string> cur_result, std
return true;
}

/**
* Compares the current test result with a set of expected test results.
*
* The function checks if the given current test result (cur_result) matches any of the expected results in the provided list of expected result sets.
* For each expected result set, it iteratively compares the results of individual SQL queries. If all queries' results in the current result match those
* in one of the expected result sets, the function returns true. If no such matching expected result set is found, it returns false.
*
* The function also appends the matching result information to a file specified by test_process_file.
*
* @param cur_result An unordered_map containing the current test results, where the key is the SQL ID and the value is the corresponding result vector.
* @param expected_result_set_list A vector containing multiple unordered_maps. Each unordered_map represents expected results for various SQL queries.
* @param sql_map An unordered_map associating each SQL ID with its actual SQL statement string.
* @param test_process_file The name of the file where matching result information will be appended.
* @return true if the current test result matches any of the expected result sets, false otherwise.
*/
bool ResultHandler::IsTestExpectedResult(std::unordered_map<int, std::vector<std::string>>& cur_result,
std::vector<std::unordered_map<int, std::vector<std::string>>> expected_result_set_list,
std::unordered_map<int, std::string> sql_map, const std::string& test_process_file) {
Expand Down Expand Up @@ -233,6 +337,16 @@ bool ResultHandler::IsTestExpectedResult(std::unordered_map<int, std::vector<std
return false;
}

/**
* Writes the summarized test results to a specified file.
*
* This function iterates through each TestResultSet in the given list and extracts the summarized test result for each set.
* It then writes each test case type and its corresponding result to the provided output file.
* Note that the detailed reasons for each result are stripped; only the core result is written.
*
* @param test_result_set_list A vector of TestResultSet objects, each representing the results of a test case.
* @param ret_file The path and filename where the summarized results should be written.
*/
void Outputter::WriteResultTotal(std::vector<TestResultSet> test_result_set_list, const std::string& ret_file) {
std::ofstream out(ret_file);
for (auto& test_result_set : test_result_set_list) {
Expand All @@ -244,9 +358,26 @@ void Outputter::WriteResultTotal(std::vector<TestResultSet> test_result_set_list
out.close();
}

/**
* Compares the current SQL result to a set of expected results, and outputs the comparison to both the console and a file.
*
* For each transaction or session, the function uses session_id to create a string "blank" which acts as an indentation for the output.
* The current SQL result and its corresponding expected results are then printed and written to the specified file,
* with the results that match being indicated with an asterisk (*) prefix.
*
* @param cur_result A vector containing the current SQL result.
* @param expected_result_set_list A vector of unordered maps, each representing a set of expected results indexed by SQL ID.
* @param sql_id The unique identifier for the SQL statement.
* @param sql The actual SQL statement string.
* @param session_id The identifier for the current session or transaction, used for output formatting.
* @param test_process_file The path to the file where results should be written.
*
* @return True if the results are successfully written to the file, otherwise false (e.g., if the file is not found).
*/
bool Outputter::PrintAndWriteTxnSqlResult(std::vector<std::string> cur_result,
std::vector<std::unordered_map<int, std::vector<std::string>>> expected_result_set_list,
const int sql_id, const std::string& sql, const int session_id, const std::string& test_process_file) {
// blank ensures that each transaction or session has its specific indentation.
std::string blank(blank_base*(session_id - 1), ' ');
std::ofstream test_process(test_process_file, std::ios::app);
if (test_process) {
Expand Down Expand Up @@ -295,6 +426,14 @@ bool Outputter::PrintAndWriteTxnSqlResult(std::vector<std::string> cur_result,
}
}

/**
* Appends the given test case type to a specified file.
* The test case type is formatted with dashes before and after it.
*
* @param test_case_type The type of the test case to be written.
* @param test_process_file The file to which the test case type will be appended.
* @return Returns true if the write operation is successful; otherwise returns false.
*/
bool Outputter::WriteTestCaseTypeToFile(const std::string& test_case_type, const std::string& test_process_file) {
std::ofstream test_process(test_process_file, std::ios::app);
if (test_process) {
Expand All @@ -306,6 +445,14 @@ bool Outputter::WriteTestCaseTypeToFile(const std::string& test_case_type, const
}
}

/**
* Appends the given result type to a specified file.
* The result type is prefixed with "Test Result:" for clarity.
*
* @param result_type The result type to be written.
* @param test_process_file The file to which the result type will be appended.
* @return Returns true if the write operation is successful; otherwise returns false.
*/
bool Outputter::WriteResultType(const std::string& result_type, const std::string& test_process_file) {
std::ofstream test_process(test_process_file, std::ios::app);
if (test_process) {
Expand Down
Loading

0 comments on commit 8a9f8b0

Please sign in to comment.