Sikuwa first commit
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

This commit is contained in:
so陈
2026-02-20 23:53:48 +08:00
commit 13a1072c6f
57 changed files with 13519 additions and 0 deletions

View File

@@ -0,0 +1,360 @@
# 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)