aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tests
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2025-10-25 00:30:48 +0800
committer简律纯 <i@jyunko.cn>2025-10-25 00:30:48 +0800
commitcbc653ffd0ea9abf4360623dc7a7651e1a49cc61 (patch)
treeea3c396148158077bae3e77eaa9341f8c1990636 /tests
parent08299b37dfda86e56e4f2b442f68ccd2da7a82e3 (diff)
downloadconventional_role_play-cbc653ffd0ea9abf4360623dc7a7651e1a49cc61.tar.gz
conventional_role_play-cbc653ffd0ea9abf4360623dc7a7651e1a49cc61.zip
feat: Implement plugin system with combat tracker and dice analyzer
- Added `plugin_system_demo.py` to demonstrate basic plugin usage, processing, and analysis. - Created `CombatTrackerPlugin` for tracking combat statistics including damage and healing. - Developed `DiceAnalyzerPlugin` for analyzing dice rolls and calculating success rates. - Introduced `renderer_demo.py` for rendering output in HTML, Markdown, and JSON formats. - Implemented `rule_system_demo.py` to showcase rule engine capabilities with various examples. - Established core rule engine functionality in `rules.py` with support for conditions and actions. - Enhanced base plugin structure in `base.py` to support different plugin types (Processor, Renderer, Analyzer). - Added custom exception handling in `exceptions.py` for better error management. - Configured logging setup in `logging_config.py` for improved logging capabilities. - Created unit tests in `test_rust_core.py` to validate core functionalities and performance.
Diffstat (limited to 'tests')
-rw-r--r--tests/README.md63
-rw-r--r--tests/run_tests.py12
-rw-r--r--tests/test_parser.py20
-rw-r--r--tests/test_processor.py22
-rw-r--r--tests/test_pyo3.py4
-rw-r--r--tests/test_renderers.py25
-rw-r--r--tests/test_rule_extractor.py17
-rw-r--r--tests/test_rust_core.py99
8 files changed, 106 insertions, 156 deletions
diff --git a/tests/README.md b/tests/README.md
deleted file mode 100644
index 97a8028..0000000
--- a/tests/README.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# ConventionalRP 测试套件
-
-本目录包含 ConventionalRP SDK 的所有单元测试。
-
-## 测试文件
-
-- `test_parser.py` - Parser 解析器测试
-- `test_processor.py` - Processor 处理器测试
-- `test_rule_extractor.py` - RuleExtractor 规则提取器测试
-- `test_renderers.py` - 渲染器测试(HTML/JSON/Markdown)
-- `test_pyo3.py` - PyO3 Rust 扩展测试
-
-## 运行测试
-
-### 运行所有测试
-
-```bash
-python tests/run_tests.py
-```
-
-### 运行单个测试文件
-
-```bash
-python -m unittest tests/test_parser.py
-python -m unittest tests/test_processor.py
-```
-
-### 运行特定测试类
-
-```bash
-python -m unittest tests.test_parser.TestParser
-```
-
-### 运行特定测试方法
-
-```bash
-python -m unittest tests.test_parser.TestParser.test_load_rules_success
-```
-
-## 测试覆盖率
-
-要查看测试覆盖率,请安装 `coverage` 并运行:
-
-```bash
-pip install coverage
-coverage run -m unittest discover -s tests -p "test_*.py"
-coverage report
-coverage html # 生成 HTML 报告
-```
-
-## 测试数据
-
-测试使用临时文件来模拟规则文件和日志文件,测试完成后会自动清理。
-
-## 添加新测试
-
-创建新的测试文件时,请遵循以下约定:
-
-1. 文件名以 `test_` 开头
-2. 测试类继承自 `unittest.TestCase`
-3. 测试方法以 `test_` 开头
-4. 使用 `setUp()` 和 `tearDown()` 方法管理测试状态
-5. 添加清晰的文档字符串说明测试目的
diff --git a/tests/run_tests.py b/tests/run_tests.py
index 4cfc2d4..b776062 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -1,34 +1,22 @@
-#!/usr/bin/env python3
-"""
-测试套件运行器
-运行所有单元测试
-"""
-
import sys
import unittest
from pathlib import Path
-# 添加 src 目录到路径
src_path = Path(__file__).parent.parent / "src"
sys.path.insert(0, str(src_path))
def run_all_tests():
- """运行所有测试"""
- # 创建测试加载器
loader = unittest.TestLoader()
- # 从当前目录加载所有测试
suite = loader.discover(
start_dir=Path(__file__).parent,
pattern='test_*.py'
)
- # 运行测试
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
- # 返回结果
return 0 if result.wasSuccessful() else 1
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 595d0b4..1d41a1e 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -1,8 +1,3 @@
-#!/usr/bin/env python3
-"""
-Parser 模块单元测试
-"""
-
import unittest
import tempfile
from pathlib import Path
@@ -10,13 +5,9 @@ from conventionalrp.core.parser import Parser
class TestParser(unittest.TestCase):
- """Parser 类的单元测试"""
-
def setUp(self):
- """设置测试环境"""
self.parser = Parser()
- # 创建临时规则文件
self.temp_rules = tempfile.NamedTemporaryFile(
mode='w',
suffix='.json5',
@@ -56,7 +47,6 @@ class TestParser(unittest.TestCase):
}''')
self.temp_rules.close()
- # 创建临时日志文件
self.temp_log = tempfile.NamedTemporaryFile(
mode='w',
suffix='.txt',
@@ -70,30 +60,25 @@ class TestParser(unittest.TestCase):
self.temp_log.close()
def tearDown(self):
- """清理测试环境"""
Path(self.temp_rules.name).unlink(missing_ok=True)
Path(self.temp_log.name).unlink(missing_ok=True)
def test_load_rules_success(self):
- """测试成功加载规则文件"""
self.parser.load_rules(self.temp_rules.name)
self.assertIn("metadata", self.parser.rules)
self.assertIn("content", self.parser.rules)
def test_load_rules_file_not_found(self):
- """测试加载不存在的规则文件"""
with self.assertRaises(FileNotFoundError):
self.parser.load_rules("nonexistent_file.json5")
def test_parse_log_success(self):
- """测试成功解析日志"""
self.parser.load_rules(self.temp_rules.name)
result = self.parser.parse_log(self.temp_log.name)
self.assertIsInstance(result, list)
self.assertGreater(len(result), 0)
- # 检查第一条记录
first_entry = result[0]
self.assertIn("timestamp", first_entry)
self.assertIn("speaker", first_entry)
@@ -101,13 +86,11 @@ class TestParser(unittest.TestCase):
self.assertEqual(first_entry["speaker"], "艾莉娅")
def test_parse_log_file_not_found(self):
- """测试解析不存在的日志文件"""
self.parser.load_rules(self.temp_rules.name)
with self.assertRaises(FileNotFoundError):
self.parser.parse_log("nonexistent_log.txt")
def test_match_metadata(self):
- """测试元数据匹配"""
self.parser.load_rules(self.temp_rules.name)
line = "[2025-10-24 14:30:01] <艾莉娅> 测试内容"
result = self.parser._match_metadata(line)
@@ -118,7 +101,6 @@ class TestParser(unittest.TestCase):
self.assertEqual(result["speaker"], "艾莉娅")
def test_parse_line_content_dialogue(self):
- """测试解析对话内容"""
self.parser.load_rules(self.temp_rules.name)
line = "「这是一段对话」"
result = self.parser._parse_line_content(line)
@@ -128,12 +110,10 @@ class TestParser(unittest.TestCase):
self.assertEqual(result[0]["type"], "dialogue")
def test_parse_line_content_dice_roll(self):
- """测试解析骰子投掷"""
self.parser.load_rules(self.temp_rules.name)
line = "检定结果: [d20 = 18]"
result = self.parser._parse_line_content(line)
- # 应该包含文本和骰子投掷
dice_tokens = [t for t in result if t["type"] == "dice_roll"]
self.assertGreater(len(dice_tokens), 0)
diff --git a/tests/test_processor.py b/tests/test_processor.py
index c08fc52..adaf2db 100644
--- a/tests/test_processor.py
+++ b/tests/test_processor.py
@@ -1,17 +1,9 @@
-#!/usr/bin/env python3
-"""
-Processor 模块单元测试
-"""
-
import unittest
from conventionalrp.core.processor import Processor
class TestProcessor(unittest.TestCase):
- """Processor 类的单元测试"""
-
def setUp(self):
- """设置测试环境"""
self.processor = Processor()
self.sample_tokens = [
{
@@ -33,30 +25,26 @@ class TestProcessor(unittest.TestCase):
]
def test_init_without_rules(self):
- """测试无规则初始化"""
processor = Processor()
self.assertEqual(processor.rules, {})
def test_init_with_rules(self):
- """测试带规则初始化"""
rules = {"test_rule": "value"}
processor = Processor(rules)
self.assertEqual(processor.rules, rules)
def test_process_tokens(self):
- """测试处理 token 列表"""
result = self.processor.process_tokens(self.sample_tokens)
self.assertIsInstance(result, list)
self.assertEqual(len(result), len(self.sample_tokens))
- # 检查处理标记
+ # Check processing marks
for token in result:
if "timestamp" in token:
self.assertTrue(token.get("processed"))
def test_apply_rules(self):
- """测试应用规则到单个 token"""
token = self.sample_tokens[0]
result = self.processor.apply_rules(token)
@@ -65,7 +53,6 @@ class TestProcessor(unittest.TestCase):
self.assertTrue(result.get("processed"))
def test_generate_json_output(self):
- """测试生成 JSON 输出"""
output = self.processor.generate_json_output(self.sample_tokens)
self.assertIsInstance(output, str)
@@ -73,7 +60,6 @@ class TestProcessor(unittest.TestCase):
self.assertIn("speaker", output)
def test_generate_html_output(self):
- """测试生成 HTML 输出"""
output = self.processor.generate_html_output(self.sample_tokens)
self.assertIsInstance(output, str)
@@ -81,29 +67,25 @@ class TestProcessor(unittest.TestCase):
self.assertIn("</html>", output)
def test_generate_markdown_output(self):
- """测试生成 Markdown 输出"""
output = self.processor.generate_markdown_output(self.sample_tokens)
self.assertIsInstance(output, str)
self.assertIn("-", output)
def test_generate_output_json(self):
- """测试生成输出 - JSON 格式"""
output = self.processor.generate_output(self.sample_tokens, "json")
self.assertIsInstance(output, str)
def test_generate_output_html(self):
- """测试生成输出 - HTML 格式"""
+ output = self.processor.generate_output(self.sample_tokens, "html")
output = self.processor.generate_output(self.sample_tokens, "html")
self.assertIsInstance(output, str)
def test_generate_output_markdown(self):
- """测试生成输出 - Markdown 格式"""
output = self.processor.generate_output(self.sample_tokens, "markdown")
self.assertIsInstance(output, str)
def test_generate_output_unsupported_format(self):
- """测试生成输出 - 不支持的格式"""
with self.assertRaises(ValueError) as context:
self.processor.generate_output(self.sample_tokens, "pdf")
diff --git a/tests/test_pyo3.py b/tests/test_pyo3.py
deleted file mode 100644
index 9668519..0000000
--- a/tests/test_pyo3.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from conventionalrp._core import sum_as_string
-
-if __name__ == "__main__":
- print(sum_as_string(1, 2))
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 13e4540..8095550 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -1,8 +1,3 @@
-#!/usr/bin/env python3
-"""
-Renderers 模块单元测试
-"""
-
import unittest
import json
from conventionalrp.renderers.html_renderer import HTMLRenderer
@@ -11,10 +6,7 @@ from conventionalrp.renderers.markdown_renderer import MarkdownRenderer
class TestRenderers(unittest.TestCase):
- """测试所有渲染器"""
-
def setUp(self):
- """设置测试数据"""
self.sample_data = [
{
"type": "metadata",
@@ -40,7 +32,6 @@ class TestRenderers(unittest.TestCase):
}
def test_html_renderer_basic(self):
- """测试 HTML 渲染器基本功能"""
renderer = HTMLRenderer()
output = renderer.render(self.sample_data)
@@ -50,51 +41,45 @@ class TestRenderers(unittest.TestCase):
self.assertIn("<title>", output)
def test_html_renderer_set_style(self):
- """测试 HTML 渲染器设置样式"""
renderer = HTMLRenderer()
renderer.set_style("custom_style")
- # 当前实现为占位符,仅测试不抛出异常
+ # now style is set, just ensure no exceptions
self.assertIsNotNone(renderer)
def test_json_renderer_basic(self):
- """测试 JSON 渲染器基本功能"""
renderer = JSONRenderer()
output = renderer.render(self.sample_data)
self.assertIsInstance(output, str)
- # 验证输出是有效的 JSON
+ # Output should be valid JSON
parsed = json.loads(output)
self.assertIsInstance(parsed, list)
self.assertEqual(len(parsed), len(self.sample_data))
def test_json_renderer_unicode(self):
- """测试 JSON 渲染器处理 Unicode"""
renderer = JSONRenderer()
output = renderer.render(self.sample_data)
- # 应该保留中文字符
+ # should preserve Chinese characters
self.assertIn("艾莉娅", output)
self.assertIn("测试", output)
def test_markdown_renderer_basic(self):
- """测试 Markdown 渲染器基本功能"""
renderer = MarkdownRenderer()
output = renderer.render(self.dict_data)
self.assertIsInstance(output, str)
- self.assertIn("##", output) # 应该有标题标记
- self.assertIn("测试标题", output)
+ self.assertIn("##", output)
+ self.assertIn("test content", output)
def test_markdown_renderer_set_style(self):
- """测试 Markdown 渲染器设置样式"""
renderer = MarkdownRenderer()
style = {"heading_level": 2}
renderer.set_style(style)
self.assertEqual(renderer.style, style)
def test_all_renderers_empty_data(self):
- """测试所有渲染器处理空数据"""
empty_data = []
html_renderer = HTMLRenderer()
diff --git a/tests/test_rule_extractor.py b/tests/test_rule_extractor.py
index 6c4d585..2c2815c 100644
--- a/tests/test_rule_extractor.py
+++ b/tests/test_rule_extractor.py
@@ -1,8 +1,3 @@
-#!/usr/bin/env python3
-"""
-RuleExtractor 模块单元测试
-"""
-
import unittest
import tempfile
import json5
@@ -11,11 +6,7 @@ from conventionalrp.extractors.rule_extractor import RuleExtractor
class TestRuleExtractor(unittest.TestCase):
- """RuleExtractor 类的单元测试"""
-
def setUp(self):
- """设置测试环境"""
- # 创建临时规则文件
self.temp_rules = tempfile.NamedTemporaryFile(
mode='w',
suffix='.json5',
@@ -30,23 +21,19 @@ class TestRuleExtractor(unittest.TestCase):
self.temp_rules.close()
def tearDown(self):
- """清理测试环境"""
Path(self.temp_rules.name).unlink(missing_ok=True)
def test_init_without_file(self):
- """测试不带配置文件的初始化"""
extractor = RuleExtractor()
self.assertEqual(extractor.rules, {})
self.assertIsNone(extractor.config_file)
def test_init_with_file(self):
- """测试带配置文件的初始化"""
extractor = RuleExtractor(self.temp_rules.name)
self.assertIsNotNone(extractor.rules)
self.assertIn("test_rule", extractor.rules)
def test_load_rules_from_file_success(self):
- """测试成功加载规则文件"""
extractor = RuleExtractor()
rules = extractor.load_rules_from_file(self.temp_rules.name)
@@ -55,13 +42,11 @@ class TestRuleExtractor(unittest.TestCase):
self.assertEqual(rules["test_rule"], "test_value")
def test_load_rules_from_file_not_found(self):
- """测试加载不存在的文件"""
extractor = RuleExtractor()
with self.assertRaises(FileNotFoundError):
extractor.load_rules_from_file("nonexistent.json5")
def test_load_rules_empty_file(self):
- """测试加载空文件"""
empty_file = tempfile.NamedTemporaryFile(
mode='w',
suffix='.json5',
@@ -79,7 +64,6 @@ class TestRuleExtractor(unittest.TestCase):
Path(empty_file.name).unlink(missing_ok=True)
def test_load_rules_method(self):
- """测试 load_rules 方法"""
extractor = RuleExtractor()
rules = extractor.load_rules(self.temp_rules.name)
@@ -87,7 +71,6 @@ class TestRuleExtractor(unittest.TestCase):
self.assertEqual(extractor.rules, rules)
def test_extract_method(self):
- """测试 extract 方法"""
extractor = RuleExtractor(self.temp_rules.name)
extracted = extractor.extract()
diff --git a/tests/test_rust_core.py b/tests/test_rust_core.py
new file mode 100644
index 0000000..1848e68
--- /dev/null
+++ b/tests/test_rust_core.py
@@ -0,0 +1,99 @@
+import sys
+from pathlib import Path
+
+project_root = Path(__file__).parent.parent
+sys.path.insert(0, str(project_root / "src"))
+
+from conventionalrp import _core
+
+def test_token():
+ token = _core.Token("dialogue", "「你好世界」")
+ token.add_metadata("speaker", "艾莉娅")
+ token.add_metadata("timestamp", "2025-10-24 14:30:01")
+
+ print(f"Token: {token}")
+ print(f"Type: {token.token_type}")
+ print(f"Content: {token.content}")
+ print(f"Speaker: {token.get_metadata('speaker')}")
+ print(f"Dict: {token.to_dict()}")
+
+
+def test_regex_rule():
+ rule = _core.RegexRule(r"\[d(\d+)\s*=\s*(\d+)\]", "dice_roll", 90)
+
+ text = "检定结果: [d20 = 18]"
+ print(f"Text: {text}")
+ print(f"Matches: {rule.matches(text)}")
+ print(f"Extracted: {rule.extract(text)}")
+ print(f"Find all: {rule.find_all(text)}")
+
+
+def test_text_parser():
+ parser = _core.TextParser()
+
+ parser.add_rule(r"\[d(\d+)\s*=\s*(\d+)\]", "dice_roll", 90)
+ parser.add_rule(r"「(.+?)」", "dialogue", 80)
+ parser.add_rule(r"\*\*(.+?)\*\*", "action", 70)
+
+ print(f"规则数量: {parser.rule_count()}")
+
+ line = "艾莉娅说「我要投掷」然后 **投掷骰子** 结果是 [d20 = 18]"
+ print(f"\n解析文本: {line}")
+ result = parser.parse_line(line)
+ print(f"解析结果: {result}")
+
+ lines = [
+ "「你好」",
+ "**挥剑** [d20 = 15]",
+ "普通文本"
+ ]
+ results = parser.parse_lines(lines)
+ for i, line_result in enumerate(results):
+ print(f" 行 {i+1}: {line_result}")
+
+
+def test_fast_matcher():
+ patterns = ["骰子", "投掷", "检定"]
+ matcher = _core.FastMatcher(patterns)
+
+ text = "艾莉娅进行投掷骰子检定"
+ print(f"Text: {text}")
+ print(f"Contains any: {matcher.contains_any(text)}")
+ print(f"Find matches: {matcher.find_matches(text)}")
+ print(f"Count matches: {matcher.count_matches(text)}")
+
+
+def benchmark_parser():
+ import time
+
+ parser = _core.TextParser()
+ parser.add_rule(r"\[d(\d+)\s*=\s*(\d+)\]", "dice_roll", 90)
+ parser.add_rule(r"「(.+?)」", "dialogue", 80)
+ parser.add_rule(r"\*\*(.+?)\*\*", "action", 70)
+
+ test_lines = [
+ "「你好」**挥剑** [d20 = 15]" for _ in range(1000)
+ ]
+
+ start = time.time()
+ results = parser.parse_lines(test_lines)
+ elapsed = time.time() - start
+
+ print(f"Process {len(test_lines)} lines")
+ print(f"time: {elapsed:.4f} s")
+ print(f"speed: {len(test_lines)/elapsed:.0f} lines/s")
+
+
+if __name__ == "__main__":
+ try:
+ test_token()
+ test_regex_rule()
+ test_text_parser()
+ test_fast_matcher()
+ benchmark_parser()
+
+ except Exception as e:
+ print(f"\nTest failed: {e}")
+ import traceback
+ traceback.print_exc()
+ sys.exit(1)