diff --git a/packages/solaris/solaris/analyze/settings.py b/packages/solaris/solaris/analyze/settings.py index cf2d65c..d29b62e 100644 --- a/packages/solaris/solaris/analyze/settings.py +++ b/packages/solaris/solaris/analyze/settings.py @@ -20,8 +20,14 @@ class DataSourceDirSettings(BaseSettings): @model_validator(mode='after') def combine_dirs(self) -> Self: - self.HTML5_DIR = self.HTML5_DIR - self.PATCH_DIR = self.PATCH_DIR - self.UNITY_DIR = self.UNITY_DIR - self.FLASH_DIR = self.FLASH_DIR + # 分类目录未显式指定(环境变量或构造参数)时,一律从当前 BASE_DIR 派生, + # 保证 BASE_DIR 来自 -w/--source-dir 或环境变量时子目录跟随变化 + for field_name, sub_dir in ( + ('PATCH_DIR', 'patch'), + ('HTML5_DIR', 'html5'), + ('UNITY_DIR', 'unity'), + ('FLASH_DIR', 'flash'), + ): + if field_name not in self.model_fields_set: + setattr(self, field_name, self.BASE_DIR / sub_dir) return self diff --git a/packages/solaris/solaris/cli/analyze.py b/packages/solaris/solaris/cli/analyze.py index 129848b..ee11e90 100644 --- a/packages/solaris/solaris/cli/analyze.py +++ b/packages/solaris/solaris/cli/analyze.py @@ -76,9 +76,15 @@ def analyze( """分析赛尔号客户端数据并生成API数据或文档。""" ctx.ensure_object(dict) + # 不同的包可能导出同一个分析器类(例如同时指定父包与其子包), + # 重复的类会让拓扑排序的入度统计失配,误报循环依赖 analyzer_classes: list[type[BaseAnalyzer]] = [] + seen_analyzers: set[type[BaseAnalyzer]] = set() for name in package_name: - analyzer_classes.extend(import_analyzer_classes(name)) + for analyzer_class in import_analyzer_classes(name): + if analyzer_class not in seen_analyzers: + seen_analyzers.add(analyzer_class) + analyzer_classes.append(analyzer_class) ctx.obj['analyzer_classes'] = analyzer_classes @@ -91,9 +97,10 @@ def analyze( ctx.exit() # 设置数据源目录 - settings = DataSourceDirSettings() if source_dir: - settings.BASE_DIR = Path(source_dir) + settings = DataSourceDirSettings(BASE_DIR=source_dir) + else: + settings = DataSourceDirSettings() DataImportConfig.set_source_dir(settings) ctx.obj['data_source_settings'] = settings diff --git a/packages/solaris/solaris/cli/parse.py b/packages/solaris/solaris/cli/parse.py index 8da9eed..c80bc8b 100644 --- a/packages/solaris/solaris/cli/parse.py +++ b/packages/solaris/solaris/cli/parse.py @@ -87,9 +87,14 @@ def parse( ) -> None: """批量解析赛尔号 Unity 端二进制数据""" + # 同 analyze:重复的包会让同一个 parser 被导入并执行多次 parser_classes = [] + seen_parsers: set[type[BaseParser]] = set() for name in package_name: - parser_classes.extend(import_parser_classes(name)) + for parser_class in import_parser_classes(name): + if parser_class not in seen_parsers: + seen_parsers.add(parser_class) + parser_classes.append(parser_class) if parser_names: parser_classes, not_found = filter_parser_classes(parser_classes, parser_names) diff --git a/packages/solaris/tests/analyze/test_cli.py b/packages/solaris/tests/analyze/test_cli.py new file mode 100644 index 0000000..714157b --- /dev/null +++ b/packages/solaris/tests/analyze/test_cli.py @@ -0,0 +1,35 @@ +from click.testing import CliRunner + +from solaris.cli.analyze import analyze + + +def _listed_analyzer_names(output: str) -> list[str]: + names = [] + for line in output.splitlines(): + if line.startswith('分析器: '): + names.append(line.removeprefix('分析器: ').split(' |')[0]) + return names + + +def test_overlapping_packages_do_not_duplicate_analyzers(): + """pet 包会递归导入 skill 包也导出的分析器,重复类曾让拓扑排序误报循环依赖""" + runner = CliRunner() + result = runner.invoke( + analyze, + [ + '--package-name', + 'solaris.analyze.analyzers.pet', + '--package-name', + 'solaris.analyze.analyzers.skill', + '--list-analyzers', + 'data', + ], + catch_exceptions=False, + ) + + assert result.exit_code == 0 + analyzer_names = _listed_analyzer_names(result.output) + assert len(analyzer_names) == len(set(analyzer_names)) + # 两个包共有的分析器只保留一份 + assert analyzer_names.count('PetAdvanceAnalyzer') == 1 + assert 'SkillAnalyzer' in analyzer_names diff --git a/packages/solaris/tests/analyze/test_data_source_settings.py b/packages/solaris/tests/analyze/test_data_source_settings.py new file mode 100644 index 0000000..4f27701 --- /dev/null +++ b/packages/solaris/tests/analyze/test_data_source_settings.py @@ -0,0 +1,45 @@ +from pathlib import Path + +import pytest + +from solaris.analyze.settings import DataSourceDirSettings + + +@pytest.fixture(autouse=True) +def clear_solaris_data_env(monkeypatch): + """隔离开发机上可能存在的 SOLARIS_DATA_* 环境变量""" + for name in ( + 'SOLARIS_DATA_BASE_DIR', + 'SOLARIS_DATA_PATCH_DIR', + 'SOLARIS_DATA_HTML5_DIR', + 'SOLARIS_DATA_UNITY_DIR', + 'SOLARIS_DATA_FLASH_DIR', + ): + monkeypatch.delenv(name, raising=False) + + +def test_sub_dirs_derive_from_base_dir(): + settings = DataSourceDirSettings(BASE_DIR='custom') + + assert settings.PATCH_DIR == Path('custom') / 'patch' + assert settings.HTML5_DIR == Path('custom') / 'html5' + assert settings.UNITY_DIR == Path('custom') / 'unity' + assert settings.FLASH_DIR == Path('custom') / 'flash' + + +def test_default_base_dir_keeps_source_layout(): + settings = DataSourceDirSettings() + + assert settings.BASE_DIR == Path('./source') + assert settings.UNITY_DIR == Path('./source') / 'unity' + assert settings.PATCH_DIR == Path('./source') / 'patch' + + +def test_explicit_sub_dir_wins_over_base_dir(): + settings = DataSourceDirSettings( + BASE_DIR='custom', + UNITY_DIR='/elsewhere/unity', + ) + + assert settings.UNITY_DIR == Path('/elsewhere/unity') + assert settings.PATCH_DIR == Path('custom') / 'patch'