docs: 为全部13个源码模块和5个测试文件添加简体中文注释
- 每个文件补充模块级 docstring、类/函数 docstring、关键逻辑行内注释 - 注释风格:"中文说明" 或 # 中文说明 - 代码结构和逻辑完全不变
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
"""测试活动打印板成员(Active Plate Member)的 3MF 输出行为。
|
||||
|
||||
验证内容:
|
||||
- write_output_3mf 正确保留配置中指定的打印板 G-code 成员
|
||||
- ZIP 压缩级别参数生效并正确裁剪
|
||||
- 预览图拼合时仅使用前 9 个唯一输入文件
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -23,13 +30,17 @@ except Exception: # pragma: no cover
|
||||
|
||||
|
||||
class ActivePlateMemberTest(unittest.TestCase):
|
||||
"""测试写入输出 3MF 时活动打印板成员的正确保留与压缩行为。"""
|
||||
|
||||
def test_write_output_preserves_configured_plate_member(self) -> None:
|
||||
"""验证 write_output_3mf 保留配置中指定打印板对应的 G-code 成员。"""
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
source_3mf = root / "plate2.3mf"
|
||||
output_3mf = root / "out.3mf"
|
||||
self.write_plate2_archive(source_3mf)
|
||||
self.write_plate2_archive(source_3mf) # 创建含 plate_2 的测试 3MF
|
||||
|
||||
# 打开源归档,校验 plate_2 被正确识别
|
||||
with zipfile.ZipFile(source_3mf, "r") as archive:
|
||||
self.assertEqual(read_model_settings_gcode_members(archive), ["Metadata/plate_2.gcode"])
|
||||
self.assertEqual(resolve_output_gcode_member(archive, "Metadata/plate_2.gcode"), "Metadata/plate_2.gcode")
|
||||
@@ -38,7 +49,7 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
{"Metadata/plate_2.png", "Metadata/plate_2_small.png"},
|
||||
)
|
||||
|
||||
gcode_bytes = b"G1 X1\n"
|
||||
gcode_bytes = b"G1 X1\n" # 替换用的 G-code 内容
|
||||
sources = [
|
||||
PlateSource(
|
||||
source_3mf=source_3mf,
|
||||
@@ -52,8 +63,9 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
add_preview_label=False,
|
||||
apply_gcode_patches=False,
|
||||
)
|
||||
md5 = write_output_3mf(source_3mf, output_3mf, gcode_bytes, sources, options)
|
||||
md5 = write_output_3mf(source_3mf, output_3mf, gcode_bytes, sources, options) # 执行打包输出
|
||||
|
||||
# 校验输出归档中仅包含 plate_2 的 G-code,且 MD5 正确
|
||||
with zipfile.ZipFile(output_3mf, "r") as archive:
|
||||
names = set(archive.namelist())
|
||||
self.assertIn("Metadata/plate_2.gcode", names)
|
||||
@@ -64,6 +76,7 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
self.assertEqual(md5, hashlib.md5(gcode_bytes).hexdigest())
|
||||
|
||||
def test_write_output_honors_zip_compression_level(self) -> None:
|
||||
"""验证 ZIP 压缩级别参数生效:压缩级别越高,压缩后文件越小。"""
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
source_3mf = root / "plate2.3mf"
|
||||
@@ -77,8 +90,10 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
gcode_text="G1 X0\n",
|
||||
)
|
||||
]
|
||||
# 大量重复 G-code 以便观察压缩效果
|
||||
gcode_bytes = (b"G1 X123.456 Y789.012 E0.03456 ; repeated movement\n" * 20000)
|
||||
|
||||
# 分别以压缩级别 1 和 7 输出
|
||||
for output_3mf, zip_level in ((level_1_output, 1), (level_7_output, 7)):
|
||||
options = BuildOptions(
|
||||
swap_gcode="",
|
||||
@@ -89,6 +104,7 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
)
|
||||
write_output_3mf(source_3mf, output_3mf, gcode_bytes, sources, options)
|
||||
|
||||
# 比较两个输出文件的压缩后大小:级别 7 应更小
|
||||
with zipfile.ZipFile(level_1_output, "r") as level_1_archive, zipfile.ZipFile(level_7_output, "r") as level_7_archive:
|
||||
level_1_info = level_1_archive.getinfo("Metadata/plate_2.gcode")
|
||||
level_7_info = level_7_archive.getinfo("Metadata/plate_2.gcode")
|
||||
@@ -97,14 +113,17 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
self.assertLess(level_7_info.compress_size, level_1_info.compress_size)
|
||||
|
||||
def test_zip_compression_level_is_clamped(self) -> None:
|
||||
self.assertEqual(normalized_zip_compress_level(None), 7)
|
||||
self.assertEqual(normalized_zip_compress_level(0), 1)
|
||||
self.assertEqual(normalized_zip_compress_level(10), 9)
|
||||
"""验证压缩级别参数被正确裁剪到合法范围 [1, 9]。"""
|
||||
self.assertEqual(normalized_zip_compress_level(None), 7) # 默认值 → 7
|
||||
self.assertEqual(normalized_zip_compress_level(0), 1) # 低于下限 → 1
|
||||
self.assertEqual(normalized_zip_compress_level(10), 9) # 超出上限 → 9
|
||||
|
||||
def test_preview_composite_uses_first_nine_unique_input_files(self) -> None:
|
||||
"""验证预览图拼合仅使用前 9 个不重复的输入文件,第 10 个被排除。"""
|
||||
if Image is None:
|
||||
self.skipTest("Pillow is not installed")
|
||||
|
||||
# 10 种不同颜色,用于验证拼合结果
|
||||
colors = [
|
||||
(255, 0, 0, 255),
|
||||
(0, 0, 255, 255),
|
||||
@@ -121,6 +140,7 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
sources: list[PlateSource] = []
|
||||
# 为每种颜色创建一个独立的 3MF 源文件
|
||||
for index, color in enumerate(colors):
|
||||
source_3mf = root / f"source_{index}.3mf"
|
||||
self.write_plate2_archive(source_3mf, image_color=color, image_size=300)
|
||||
@@ -132,6 +152,7 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
# 验证 select_preview_sources 返回恰好 9 个源
|
||||
selected = select_preview_sources(sources)
|
||||
self.assertEqual(len(selected), 9)
|
||||
self.assertEqual(selected[-1].source_3mf.name, "source_8.3mf")
|
||||
@@ -140,16 +161,18 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
options = BuildOptions(
|
||||
swap_gcode="",
|
||||
output_3mf=output_3mf,
|
||||
add_preview_label=True,
|
||||
add_preview_label=True, # 启用预览标签生成
|
||||
apply_gcode_patches=False,
|
||||
)
|
||||
write_output_3mf(sources[0].source_3mf, output_3mf, b"G1 X1\n", sources, options)
|
||||
|
||||
# 读取拼合后的预览图
|
||||
with zipfile.ZipFile(output_3mf, "r") as archive:
|
||||
image_bytes = archive.read("Metadata/plate_2.png")
|
||||
|
||||
import io
|
||||
|
||||
# 在拼合图像的 9 个采样点处检测颜色
|
||||
image = Image.open(io.BytesIO(image_bytes)).convert("RGBA")
|
||||
sampled_colors = {
|
||||
image.getpixel((50, 50)),
|
||||
@@ -162,11 +185,19 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
image.getpixel((150, 250)),
|
||||
image.getpixel((250, 250)),
|
||||
}
|
||||
# 前 9 种颜色全部出现,第 10 种不出现
|
||||
self.assertTrue(set(colors[:9]).issubset(sampled_colors))
|
||||
self.assertNotIn(colors[9], sampled_colors)
|
||||
|
||||
@staticmethod
|
||||
def write_plate2_archive(path: Path, image_color: tuple[int, int, int, int] | None = None, image_size: int = 128) -> None:
|
||||
"""创建仅含 plate_2 有效 G-code 成员及其预览图的 3MF 测试归档。
|
||||
|
||||
Args:
|
||||
path: 输出 3MF 文件路径
|
||||
image_color: 预览图填充颜色(RGBA),None 则写入占位字节
|
||||
image_size: 预览图尺寸(正方形)
|
||||
"""
|
||||
model_settings = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<plate>
|
||||
@@ -192,11 +223,13 @@ class ActivePlateMemberTest(unittest.TestCase):
|
||||
archive.writestr("Metadata/plate_2.gcode", "G1 X0\n")
|
||||
archive.writestr("Metadata/plate_2.gcode.md5", "old")
|
||||
if image_color is None or Image is None:
|
||||
# 无 Pillow 或不指定颜色 → 写入占位字节
|
||||
archive.writestr("Metadata/plate_2.png", b"png")
|
||||
archive.writestr("Metadata/plate_2_small.png", b"png")
|
||||
else:
|
||||
import io
|
||||
|
||||
# 生成纯色 PNG 预览图及其缩略图
|
||||
image = Image.new("RGBA", (image_size, image_size), image_color)
|
||||
output = io.BytesIO()
|
||||
image.save(output, format="PNG")
|
||||
|
||||
+30
-3
@@ -1,3 +1,9 @@
|
||||
"""测试独立批量构建(Individual Batch Build)的任务调度与工作器计数。
|
||||
|
||||
验证内容:
|
||||
- 工作器数量根据任务数和 CPU 核心数合理计算
|
||||
- 串行执行器保持成功任务顺序,同时收集失败任务信息
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
@@ -9,16 +15,23 @@ from a1_swap_mod_packer.models import BuildOptions, BuildResult, PlateJob
|
||||
|
||||
|
||||
class IndividualBatchBuildTest(unittest.TestCase):
|
||||
"""测试独立批量构建的工作器计数和运行器行为。"""
|
||||
|
||||
def test_worker_count_uses_task_count_and_caps_cpu_count(self) -> None:
|
||||
self.assertEqual(batch.individual_batch_worker_count(0), 0)
|
||||
self.assertEqual(batch.individual_batch_worker_count(3, max_workers=99), 3)
|
||||
self.assertEqual(batch.individual_batch_worker_count(3, max_workers=0), 1)
|
||||
"""验证工作器数量 = min(任务数, CPU 核心数),且受上下限约束。"""
|
||||
self.assertEqual(batch.individual_batch_worker_count(0), 0) # 无任务 → 0 个工作器
|
||||
self.assertEqual(batch.individual_batch_worker_count(3, max_workers=99), 3) # 任务数 < 上限 → 按任务数
|
||||
self.assertEqual(batch.individual_batch_worker_count(3, max_workers=0), 1) # 上限为 0 → 退化为 1
|
||||
with patch.object(batch.os, "cpu_count", return_value=32):
|
||||
# 任务数 20,CPU 32 核,上限 = INDIVIDUAL_BATCH_MAX_WORKERS
|
||||
self.assertEqual(batch.individual_batch_worker_count(20), batch.INDIVIDUAL_BATCH_MAX_WORKERS)
|
||||
with patch.object(batch.os, "cpu_count", return_value=None):
|
||||
# cpu_count 返回 None → 回退为 1
|
||||
self.assertEqual(batch.individual_batch_worker_count(20), 1)
|
||||
|
||||
def test_serial_runner_keeps_success_order_and_collects_failures(self) -> None:
|
||||
"""验证串行执行器按提交顺序返回成功结果,并收集失败任务。"""
|
||||
# 准备 3 个任务,其中第 2 个计划失败
|
||||
tasks = [
|
||||
self.task("one.3mf", "one.out.3mf", copies=2),
|
||||
self.task("fail.3mf", "fail.out.3mf", copies=3),
|
||||
@@ -26,6 +39,7 @@ class IndividualBatchBuildTest(unittest.TestCase):
|
||||
]
|
||||
|
||||
def fake_build(jobs: list[PlateJob], options: BuildOptions) -> BuildResult:
|
||||
"""模拟构建:fail.3mf 触发 ValueError,其余正常返回。"""
|
||||
job = jobs[0]
|
||||
if job.source_3mf.name == "fail.3mf":
|
||||
raise ValueError("planned failure")
|
||||
@@ -37,15 +51,18 @@ class IndividualBatchBuildTest(unittest.TestCase):
|
||||
gcode_md5=job.source_3mf.stem,
|
||||
)
|
||||
|
||||
# 使用单工作器串行运行,验证顺序与失败收集
|
||||
with patch.object(batch, "build_packed_3mf", side_effect=fake_build):
|
||||
result = batch.run_individual_batch_builds(tasks, max_workers=1)
|
||||
|
||||
self.assertEqual(result.worker_count, 1)
|
||||
# 成功结果按原始顺序排列,跳过失败项
|
||||
self.assertEqual(
|
||||
[item.output_3mf.name for item in result.results],
|
||||
["one.out.3mf", "two.out.3mf"],
|
||||
)
|
||||
self.assertEqual([item.plate_count for item in result.results], [2, 4])
|
||||
# 失败详情:索引、任务源文件名、错误信息
|
||||
self.assertEqual(len(result.failures), 1)
|
||||
self.assertEqual(result.failures[0].index, 1)
|
||||
self.assertEqual(result.failures[0].job.source_3mf.name, "fail.3mf")
|
||||
@@ -53,6 +70,16 @@ class IndividualBatchBuildTest(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def task(source_name: str, output_name: str, copies: int) -> batch.IndividualBuildTask:
|
||||
"""构造独立构建任务辅助方法。
|
||||
|
||||
Args:
|
||||
source_name: 源 3MF 文件名
|
||||
output_name: 输出 3MF 文件名
|
||||
copies: 打印板份数
|
||||
|
||||
Returns:
|
||||
包装好的 IndividualBuildTask
|
||||
"""
|
||||
return batch.IndividualBuildTask(
|
||||
PlateJob(Path(source_name), copies),
|
||||
BuildOptions(swap_gcode=Path("swap.gcode"), output_3mf=Path(output_name)),
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
"""测试 G-code 准备阶段缓存行为,确保重复副本不会重复执行预处理。
|
||||
|
||||
验证内容:
|
||||
- 同一 G-code 成员多次复制时,补丁只应用一次
|
||||
- 同一 G-code 成员多次复制时,M73 偏移模板只计算一次
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -12,15 +18,19 @@ from a1_swap_mod_packer.models import BuildOptions, GcodePatchConfig, GcodePatch
|
||||
|
||||
|
||||
class GcodePrepareCacheTest(unittest.TestCase):
|
||||
"""测试 G-code 准备阶段的缓存:补丁和 M73 模板应每成员仅执行一次。"""
|
||||
|
||||
def test_repeated_copies_prepare_gcode_once_per_member(self) -> None:
|
||||
"""验证同一 G-code 成员多次复制时,补丁仅在首次准备时应用一次。"""
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
source_3mf = root / "source.3mf"
|
||||
output_3mf = root / "out.3mf"
|
||||
swap_gcode = root / "swap.gcode"
|
||||
self.write_archive(source_3mf)
|
||||
self.write_archive(source_3mf) # 创建测试用 3MF 归档
|
||||
swap_gcode.write_text("; swap\nG1 X5\n", encoding="utf-8")
|
||||
|
||||
# 配置一条简单的替换规则
|
||||
patch_config = GcodePatchConfig(
|
||||
rules=(
|
||||
GcodePatchRule(
|
||||
@@ -30,10 +40,11 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
),
|
||||
)
|
||||
)
|
||||
patch_calls = 0
|
||||
captured_gcode = ""
|
||||
patch_calls = 0 # 记录补丁被调用次数
|
||||
captured_gcode = "" # 捕获最终 G-code 内容
|
||||
|
||||
def count_patch_calls(text: str, config: GcodePatchConfig) -> str:
|
||||
"""模拟补丁应用并累计调用次数。"""
|
||||
nonlocal patch_calls
|
||||
patch_calls += 1
|
||||
return text.replace("G0 Y254 F3000", "G0 Y250 F3000 ;Patched", 1)
|
||||
@@ -45,6 +56,7 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
sources: list[builder.PlateSource],
|
||||
options: BuildOptions,
|
||||
) -> str:
|
||||
"""截获最终写入的 G-code 字节以便断言。"""
|
||||
nonlocal captured_gcode
|
||||
captured_gcode = gcode_bytes.decode("utf-8")
|
||||
return hashlib.md5(gcode_bytes).hexdigest()
|
||||
@@ -59,6 +71,7 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
add_preview_label=False,
|
||||
)
|
||||
|
||||
# 构建 3 份副本,验证补丁仅调用 1 次但 3 份均被替换
|
||||
with patch.object(builder, "parse_patch_config", return_value=patch_config), patch.object(
|
||||
builder,
|
||||
"apply_gcode_patches",
|
||||
@@ -66,12 +79,13 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
), patch.object(builder, "write_output_3mf", side_effect=capture_gcode):
|
||||
result = builder.build_packed_3mf([PlateJob(source_3mf, 3)], options)
|
||||
|
||||
self.assertEqual(result.plate_count, 3)
|
||||
self.assertEqual(patch_calls, 1)
|
||||
self.assertEqual(captured_gcode.count("G0 Y250 F3000 ;Patched"), 3)
|
||||
self.assertNotIn("G0 Y254 F3000", captured_gcode)
|
||||
self.assertEqual(result.plate_count, 3) # 3 份副本
|
||||
self.assertEqual(patch_calls, 1) # 补丁仅调用 1 次(缓存生效)
|
||||
self.assertEqual(captured_gcode.count("G0 Y250 F3000 ;Patched"), 3) # 但 3 份都已替换
|
||||
self.assertNotIn("G0 Y254 F3000", captured_gcode) # 原始行已不存在
|
||||
|
||||
def test_repeated_copies_prepare_m73_template_once_per_member(self) -> None:
|
||||
"""验证同一 G-code 成员多次复制时,M73 偏移模板仅计算一次。"""
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
source_3mf = root / "source.3mf"
|
||||
@@ -80,11 +94,12 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
self.write_archive(source_3mf)
|
||||
swap_gcode.write_text("; swap\nG1 X5\n", encoding="utf-8")
|
||||
|
||||
m73_template_calls = 0
|
||||
m73_template_calls = 0 # 记录模板准备调用次数
|
||||
captured_gcode = ""
|
||||
original_prepare_m73 = builder.prepare_m73_offset_template
|
||||
original_prepare_m73 = builder.prepare_m73_offset_template # 保存原始函数引用
|
||||
|
||||
def count_m73_template_calls(text: str):
|
||||
"""模拟 M73 模板准备并累计调用次数。"""
|
||||
nonlocal m73_template_calls
|
||||
m73_template_calls += 1
|
||||
return original_prepare_m73(text)
|
||||
@@ -96,6 +111,7 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
sources: list[builder.PlateSource],
|
||||
options: BuildOptions,
|
||||
) -> str:
|
||||
"""截获最终写入的 G-code 字节以便断言。"""
|
||||
nonlocal captured_gcode
|
||||
captured_gcode = gcode_bytes.decode("utf-8")
|
||||
return hashlib.md5(gcode_bytes).hexdigest()
|
||||
@@ -104,12 +120,13 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
swap_gcode=swap_gcode,
|
||||
output_3mf=output_3mf,
|
||||
apply_gcode_patches=False,
|
||||
show_plate_number=True,
|
||||
show_plate_number=True, # 启用板号显示,触发 M73 偏移
|
||||
swap_after_final=False,
|
||||
line_ending="lf",
|
||||
add_preview_label=False,
|
||||
)
|
||||
|
||||
# 构建 3 份副本,验证模板仅准备 1 次,但每份各自偏移
|
||||
with (
|
||||
patch.object(builder, "prepare_m73_offset_template", side_effect=count_m73_template_calls),
|
||||
patch.object(
|
||||
@@ -121,13 +138,15 @@ class GcodePrepareCacheTest(unittest.TestCase):
|
||||
result = builder.build_packed_3mf([PlateJob(source_3mf, 3)], options)
|
||||
|
||||
self.assertEqual(result.plate_count, 3)
|
||||
self.assertEqual(m73_template_calls, 1)
|
||||
self.assertEqual(m73_template_calls, 1) # 模板仅准备 1 次
|
||||
# 3 份副本的 M73 剩余时间已分别偏移
|
||||
self.assertIn("M73 P0 R6010", captured_gcode)
|
||||
self.assertIn("M73 P0 R12010", captured_gcode)
|
||||
self.assertIn("M73 P0 R18010", captured_gcode)
|
||||
|
||||
@staticmethod
|
||||
def write_archive(path: Path) -> None:
|
||||
"""创建包含带 M73 指令和待替换行的 plate_1 G-code 的 3MF 测试归档。"""
|
||||
slice_info = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<plate>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
"""测试 M73 偏移模板(M73 Offset Template)的生成与应用。
|
||||
|
||||
验证内容:
|
||||
- 模板应用结果与先前正则替换实现的语义一致
|
||||
- 不含 M73 指令的 G-code 返回原始文本不做修改
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
@@ -6,18 +12,22 @@ from a1_swap_mod_packer.gcode import M73_RE, apply_plate_number_offset, prepare_
|
||||
|
||||
|
||||
class M73OffsetTemplateTest(unittest.TestCase):
|
||||
"""测试 M73 剩余时间偏移模板的正确性。"""
|
||||
|
||||
def test_template_matches_previous_regex_replacement_semantics(self) -> None:
|
||||
"""验证新模板方式生成的偏移结果与旧正则替换方式完全一致。"""
|
||||
gcode = (
|
||||
"M73 P0 R10\n"
|
||||
"; M73 P0 R20\n"
|
||||
" M73 P0 R30\n"
|
||||
"M73 P50 R5999 ; comment\n"
|
||||
"G1 X1\n"
|
||||
"M73 P100 R0\n"
|
||||
"; M73 P0 R20\n" # 被注释的 M73 行不应匹配
|
||||
" M73 P0 R30\n" # 带前导空格的 M73 行
|
||||
"M73 P50 R5999 ; comment\n" # 带注释的 M73 行
|
||||
"G1 X1\n" # 非 M73 行
|
||||
"M73 P100 R0\n" # 剩余时间为 0 的 M73 行
|
||||
)
|
||||
|
||||
def previous_apply(text: str, plate_number: int) -> str:
|
||||
minute_offset = plate_number * 100 * 60
|
||||
"""旧版正则替换实现:对每个匹配的 M73 行增加时间偏移。"""
|
||||
minute_offset = plate_number * 100 * 60 # 每板偏移 100 分钟
|
||||
|
||||
def replace(match) -> str:
|
||||
prefix = match.group(1)
|
||||
@@ -27,13 +37,15 @@ class M73OffsetTemplateTest(unittest.TestCase):
|
||||
|
||||
return M73_RE.sub(replace, text)
|
||||
|
||||
# 生成模板并与旧实现逐板对比
|
||||
template = prepare_m73_offset_template(gcode)
|
||||
for plate_number in (1, 2, 10):
|
||||
self.assertEqual(template.apply(plate_number), previous_apply(gcode, plate_number))
|
||||
self.assertEqual(apply_plate_number_offset(gcode, plate_number), previous_apply(gcode, plate_number))
|
||||
|
||||
def test_template_without_m73_matches_returns_original_text(self) -> None:
|
||||
gcode = "G1 X1\n; M73 P0 R10\n"
|
||||
"""验证不含 M73 指令的 G-code 应用模板后保持原样。"""
|
||||
gcode = "G1 X1\n; M73 P0 R10\n" # M73 在注释中,不应匹配
|
||||
template = prepare_m73_offset_template(gcode)
|
||||
self.assertEqual(template.apply(3), gcode)
|
||||
|
||||
|
||||
+14
-2
@@ -1,3 +1,9 @@
|
||||
"""测试应用程序版本号的显示一致性。
|
||||
|
||||
验证内容:
|
||||
- APP_TITLE 使用统一的 __version__ 构建
|
||||
- CLI 的 --help 和 --version 输出均使用共享的版本号
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
@@ -9,19 +15,25 @@ from a1_swap_mod_packer.cli import create_parser
|
||||
|
||||
|
||||
class VersionDisplayTest(unittest.TestCase):
|
||||
"""测试版本信息在应用标题和 CLI 中的一致性。"""
|
||||
|
||||
def test_app_title_uses_shared_version(self) -> None:
|
||||
"""验证 APP_TITLE 由 APP_NAME 与 __version__ 拼接而成。"""
|
||||
self.assertEqual(APP_TITLE, f"{APP_NAME} v{__version__}")
|
||||
|
||||
def test_cli_help_and_version_use_shared_version(self) -> None:
|
||||
"""验证 CLI 帮助信息和 --version 输出中均包含统一版本号。"""
|
||||
parser = create_parser()
|
||||
# 帮助信息应包含 APP_TITLE
|
||||
self.assertIn(APP_TITLE, parser.format_help())
|
||||
|
||||
stdout = io.StringIO()
|
||||
# --version 参数触发 SystemExit(0) 并打印版本信息
|
||||
with self.assertRaises(SystemExit) as caught, contextlib.redirect_stdout(stdout):
|
||||
parser.parse_args(["--version"])
|
||||
|
||||
self.assertEqual(caught.exception.code, 0)
|
||||
self.assertEqual(stdout.getvalue().strip(), f"{APP_NAME} {__version__}")
|
||||
self.assertEqual(caught.exception.code, 0) # 正常退出码
|
||||
self.assertEqual(stdout.getvalue().strip(), f"{APP_NAME} {__version__}") # 版本信息匹配
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user