Files
Sikuwa/incremental/tests/test_incremental.py
so陈 13a1072c6f
Some checks are pending
CI / Test (Python 3.10 on macos-latest) (push) Waiting to run
CI / Test (Python 3.11 on macos-latest) (push) Waiting to run
CI / Test (Python 3.12 on macos-latest) (push) Waiting to run
CI / Test (Python 3.8 on macos-latest) (push) Waiting to run
CI / Test (Python 3.9 on macos-latest) (push) Waiting to run
CI / Test (Python 3.10 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.11 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.12 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.8 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.9 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.10 on windows-latest) (push) Waiting to run
CI / Test (Python 3.11 on windows-latest) (push) Waiting to run
CI / Test (Python 3.12 on windows-latest) (push) Waiting to run
CI / Test (Python 3.8 on windows-latest) (push) Waiting to run
CI / Test (Python 3.9 on windows-latest) (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Release (push) Blocked by required conditions
Documentation / Build Documentation (push) Waiting to run
Sikuwa first commit
2026-02-20 23:53:48 +08:00

361 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sikuwa/incremental/tests/test_incremental.py
"""
减量编译系统测试
"""
import sys
import os
import tempfile
import unittest
from pathlib import Path
# 添加父目录到路径
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from incremental.core import (
IncrementalCompiler,
CompilationUnit,
Snapshot,
ChangeDetector,
CompilationCache,
UnitType,
UnitState
)
from incremental.analyzer import PythonAnalyzer, BlockType
class TestPythonAnalyzer(unittest.TestCase):
"""测试 Python 分析器"""
def setUp(self):
self.analyzer = PythonAnalyzer()
def test_analyze_function(self):
"""测试函数分析"""
code = '''
def hello(name):
"""Say hello"""
print(f"Hello, {name}!")
'''
blocks = self.analyzer.analyze(code, "test.py")
# 应该检测到函数块
func_blocks = [b for b in blocks if b.type == BlockType.FUNCTION]
self.assertEqual(len(func_blocks), 1)
self.assertEqual(func_blocks[0].name, "hello")
def test_analyze_class(self):
"""测试类分析"""
code = '''
class MyClass:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
'''
blocks = self.analyzer.analyze(code, "test.py")
# 应该检测到类块
class_blocks = [b for b in blocks if b.type == BlockType.CLASS]
self.assertEqual(len(class_blocks), 1)
self.assertEqual(class_blocks[0].name, "MyClass")
def test_analyze_import(self):
"""测试导入分析"""
code = '''
import os
from sys import path
from pathlib import Path
'''
blocks = self.analyzer.analyze(code, "test.py")
import_blocks = [b for b in blocks if b.type == BlockType.IMPORT]
self.assertEqual(len(import_blocks), 3)
def test_dependency_extraction(self):
"""测试依赖提取"""
code = '''
def outer():
def inner():
return x
return inner()
'''
blocks = self.analyzer.analyze(code, "test.py")
func_blocks = [b for b in blocks if b.type == BlockType.FUNCTION]
# outer 函数应该依赖 x
self.assertEqual(len(func_blocks), 1)
self.assertIn('x', func_blocks[0].references)
class TestChangeDetector(unittest.TestCase):
"""测试变更检测器"""
def setUp(self):
self.detector = ChangeDetector()
def test_detect_addition(self):
"""测试新增检测"""
old = Snapshot()
old.units = {}
new_unit = CompilationUnit(
id="u1", content="def foo(): pass",
start_line=1, end_line=1, file_path="test.py"
)
new_unit.compute_hash()
new = Snapshot()
new.units = {"u1": new_unit}
changes = self.detector.detect_changes(old, new)
self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].unit_id, "u1")
self.assertEqual(changes[0].change_type, UnitState.ADDED)
def test_detect_modification(self):
"""测试修改检测"""
old_unit = CompilationUnit(
id="u1", content="def foo(): pass",
start_line=1, end_line=1, file_path="test.py"
)
old_unit.compute_hash()
old = Snapshot()
old.units = {"u1": old_unit}
new_unit = CompilationUnit(
id="u1", content="def foo(): return 1",
start_line=1, end_line=1, file_path="test.py"
)
new_unit.compute_hash()
new = Snapshot()
new.units = {"u1": new_unit}
changes = self.detector.detect_changes(old, new)
self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].unit_id, "u1")
self.assertEqual(changes[0].change_type, UnitState.MODIFIED)
def test_detect_deletion(self):
"""测试删除检测"""
old_unit = CompilationUnit(
id="u1", content="def foo(): pass",
start_line=1, end_line=1, file_path="test.py"
)
old_unit.compute_hash()
old = Snapshot()
old.units = {"u1": old_unit}
new = Snapshot()
new.units = {}
changes = self.detector.detect_changes(old, new)
self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].change_type, UnitState.DELETED)
class TestCompilationCache(unittest.TestCase):
"""测试编译缓存"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.cache = CompilationCache(self.temp_dir)
def tearDown(self):
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_put_get(self):
"""测试缓存存取"""
self.cache.put("key1", "value1", "hash1")
result = self.cache.get("key1")
self.assertEqual(result, "value1")
def test_get_nonexistent(self):
"""测试获取不存在的键"""
result = self.cache.get("nonexistent")
self.assertEqual(result, "") # 返回空字符串
def test_persistence(self):
"""测试持久化"""
self.cache.put("key1", "value1", "hash1")
self.cache.save()
# 创建新缓存实例
cache2 = CompilationCache(self.temp_dir)
result = cache2.get("key1")
self.assertEqual(result, "value1")
class TestIncrementalCompiler(unittest.TestCase):
"""测试减量编译器"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.compiler = IncrementalCompiler(self.temp_dir)
# 设置简单的编译器(返回大写代码)
self.compiler.set_compiler(lambda unit: unit.content.upper())
def tearDown(self):
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_initial_compile(self):
"""测试初始编译"""
code = '''
def hello():
print("Hello")
def world():
print("World")
'''
self.compiler.analyze_source("test.py", code)
changes = self.compiler.update_source("test.py", code)
# 首次编译,所有单元都应该是新的
self.assertGreater(len(changes), 0)
# 编译
outputs = self.compiler.compile_all_pending()
self.assertGreater(len(outputs), 0)
def test_incremental_compile(self):
"""测试增量编译"""
# 初始代码
code1 = '''
def hello():
print("Hello")
def world():
print("World")
'''
self.compiler.analyze_source("test.py", code1)
self.compiler.update_source("test.py", code1)
outputs1 = self.compiler.compile_all_pending()
# 修改一个函数
code2 = '''
def hello():
print("Hello Modified")
def world():
print("World")
'''
changes = self.compiler.update_source("test.py", code2)
# 应该有变更
self.assertGreater(len(changes), 0)
# 再次编译
outputs2 = self.compiler.compile_all_pending()
# 验证有输出
self.assertGreater(len(outputs1) + len(outputs2), 0)
def test_dependency_propagation(self):
"""测试依赖传播"""
code = '''
x = 10
def get_x():
return x
def double_x():
return get_x() * 2
'''
self.compiler.analyze_source("test.py", code)
self.compiler.update_source("test.py", code)
self.compiler.compile_all_pending()
# 修改 x 的值
code2 = '''
x = 20
def get_x():
return x
def double_x():
return get_x() * 2
'''
changes = self.compiler.update_source("test.py", code2)
# 应该检测到变更x 变了,依赖它的也应该被标记)
self.assertGreater(len(changes), 0)
def test_combined_output(self):
"""测试合并输出"""
code = '''
import os
def hello():
print("Hello")
def world():
print("World")
'''
self.compiler.analyze_source("test.py", code)
self.compiler.update_source("test.py", code)
self.compiler.compile_all_pending()
combined = self.compiler.get_combined_output("test.py")
# 合并输出应该包含所有编译产物
self.assertGreater(len(combined), 0)
class TestBlockBoundary(unittest.TestCase):
"""测试边界触发器"""
def setUp(self):
self.analyzer = PythonAnalyzer()
def test_class_contains_methods(self):
"""测试类包含其方法"""
code = '''
class MyClass:
def method1(self):
pass
def method2(self):
pass
'''
blocks = self.analyzer.analyze(code, "test.py")
class_blocks = [b for b in blocks if b.type == BlockType.CLASS]
self.assertEqual(len(class_blocks), 1)
# 类块应该包含整个类定义
class_block = class_blocks[0]
self.assertIn("method1", class_block.content)
self.assertIn("method2", class_block.content)
def run_tests():
"""运行所有测试"""
loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromTestCase(TestPythonAnalyzer))
suite.addTests(loader.loadTestsFromTestCase(TestChangeDetector))
suite.addTests(loader.loadTestsFromTestCase(TestCompilationCache))
suite.addTests(loader.loadTestsFromTestCase(TestIncrementalCompiler))
suite.addTests(loader.loadTestsFromTestCase(TestBlockBoundary))
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result.wasSuccessful()
if __name__ == '__main__':
success = run_tests()
sys.exit(0 if success else 1)