from __future__ import annotations import copy import json import subprocess import sys import tempfile import unittest from pathlib import Path from unittest import mock import yaml REPO_ROOT = Path(__file__).resolve().parents[1] ACK_DIR = REPO_ROOT / "skills" / "ack" SCRIPTS_DIR = ACK_DIR / "scripts" SCHEMA_PATH = ACK_DIR / "templates" / "knowledge.schema.json" EXAMPLE_PATH = ACK_DIR / "examples" / "knowledge.example.yaml" TASKS_EXAMPLE_PATH = ACK_DIR / "examples" / "tasks.example.yaml" sys.path.insert(0, str(SCRIPTS_DIR)) import select_knowledge # noqa: E402 import validate_knowledge # noqa: E402 def example_data() -> dict: return yaml.safe_load(EXAMPLE_PATH.read_text(encoding="utf-8")) class AckKnowledgeTests(unittest.TestCase): def test_schema_template_and_example_are_aligned(self) -> None: schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8")) template = yaml.safe_load( (ACK_DIR / "templates" / "knowledge.template.yaml").read_text( encoding="utf-8" ) ) self.assertEqual( schema["definitions"]["entry"]["properties"]["kind"]["enum"], ["guardrail", "pitfall", "verification"], ) self.assertEqual( schema["definitions"]["entry"]["properties"]["status"]["enum"], ["active", "stale", "superseded", "archived"], ) self.assertEqual(template["entries"], []) self.assertEqual(template["verificationRegistry"], {}) errors, _ = validate_knowledge.validate_all( example_data(), SCHEMA_PATH, use_schema=False ) self.assertEqual(errors, []) self.assertEqual( validate_knowledge.stable_ref(example_data()["entries"][0]), "K-001@1" ) def test_semantics_run_even_when_schema_reports_no_errors(self) -> None: data = example_data() data["entries"].append(copy.deepcopy(data["entries"][0])) with mock.patch.object( validate_knowledge, "validate_with_schema", return_value=[] ): errors, mode = validate_knowledge.validate_all( data, SCHEMA_PATH, use_schema=True ) self.assertIn("内置语义", mode) self.assertTrue(any("稳定引用 K-001@1 重复" in error for error in errors)) def test_blank_required_text_fails_in_builtin_and_schema_modes(self) -> None: data = example_data() data["entries"][0]["title"] = " \t " builtin_errors, _ = validate_knowledge.validate_all( data, SCHEMA_PATH, use_schema=False ) with mock.patch.object( validate_knowledge, "validate_with_schema", return_value=[] ): schema_errors, _ = validate_knowledge.validate_all( data, SCHEMA_PATH, use_schema=True ) self.assertTrue(any("title: 必须是非空字符串" in e for e in builtin_errors)) self.assertTrue(any("title: 必须是非空字符串" in e for e in schema_errors)) schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8")) self.assertEqual( schema["definitions"]["entry"]["properties"]["title"]["pattern"], "\\S", ) def test_temporary_entry_requires_removal_condition_and_review_date(self) -> None: data = example_data() entry = data["entries"][0] entry["temporary"] = True entry["removalCondition"] = None entry["reviewAfter"] = None errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("removalCondition" in error for error in errors)) self.assertTrue(any("reviewAfter" in error for error in errors)) def test_overdue_active_entry_requires_revalidation(self) -> None: data = example_data() data["entries"][0]["reviewAfter"] = "2020-01-01T00:00:00+00:00" errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("必须重新验证或标记 stale" in error for error in errors)) def test_global_active_entry_requires_decision_owner_approval(self) -> None: data = example_data() data["entries"][0]["scope"] = { "all": True, "components": [], "paths": [], "dependencies": [], "versions": [], "tags": [], "symbols": [], "errorSignatures": [], } errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("Decision Owner approval" in error for error in errors)) def test_active_conflicts_and_duplicate_active_revision_are_rejected(self) -> None: data = example_data() conflicting = copy.deepcopy(data["entries"][0]) conflicting.update( { "id": "K-002", "subject": "service-process-alignment", "conflictsWith": ["K-001@1"], } ) data["entries"].append(conflicting) errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("active 条目不能与 active K-001@1 冲突" in e for e in errors)) conflicting["id"] = "K-001" conflicting["revision"] = 2 conflicting["conflictsWith"] = [] errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("多个 active revision" in error for error in errors)) def test_verification_ref_must_resolve_to_contained_registry_target(self) -> None: data = example_data() data["entries"][0]["verification"]["ref"] = "missing-check" errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("未在 verificationRegistry 注册" in e for e in errors)) data = example_data() data["verificationRegistry"]["service-worktree-alignment"]["path"] = "../run.sh" errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("必须是仓库内相对路径" in error for error in errors)) def test_verification_registry_rejects_any_symlink_component(self) -> None: data = example_data() data["verificationRegistry"]["service-worktree-alignment"]["path"] = ( "checks/verify.py" ) with tempfile.TemporaryDirectory() as temp_dir: base = Path(temp_dir) project = base / "project" outside = base / "outside" project.mkdir() outside.mkdir() (project / "checks").symlink_to(outside, target_is_directory=True) errors = validate_knowledge.validate_semantics( data, project_root=project ) self.assertTrue(any("symlink" in error for error in errors)) data = example_data() data["verificationRegistry"]["service-worktree-alignment"]["path"] = ( "checks/verify.py" ) with tempfile.TemporaryDirectory() as temp_dir: project = Path(temp_dir) / "project" real_checks = project / "real-checks" real_checks.mkdir(parents=True) target = real_checks / "verify.py" target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") target.chmod(target.stat().st_mode | 0o111) (project / "checks").symlink_to( real_checks, target_is_directory=True, ) errors = validate_knowledge.validate_semantics( data, project_root=project, ) self.assertTrue(any("symlink" in error for error in errors)) data = example_data() data["verificationRegistry"]["service-worktree-alignment"]["path"] = ( "tests/check.sh;touch" ) errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("必须是仓库内相对路径" in error for error in errors)) def test_verification_registry_requires_regular_executable_target(self) -> None: data = example_data() data["verificationRegistry"]["service-worktree-alignment"]["path"] = ( "checks/verify" ) with tempfile.TemporaryDirectory() as temp_dir: project = Path(temp_dir) / "project" project.mkdir() missing = validate_knowledge.validate_semantics( data, project_root=project, ) self.assertTrue(any("不存在" in error for error in missing)) target = project / "checks" / "verify" target.parent.mkdir() target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") not_executable = validate_knowledge.validate_semantics( data, project_root=project, ) self.assertTrue( any("不可执行" in error for error in not_executable) ) target.chmod(target.stat().st_mode | 0o111) valid = validate_knowledge.validate_semantics( data, project_root=project, ) self.assertFalse( any("verificationRegistry" in error for error in valid), valid, ) def test_free_command_fields_are_rejected_and_never_executed(self) -> None: data = example_data() data["entries"][0]["command"] = "echo unsafe" errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("不保存或执行自由命令" in error for error in errors)) with tempfile.TemporaryDirectory() as temp_dir: marker = Path(temp_dir) / "should-not-exist" safe = example_data() safe["entries"][0]["directive"] = f"touch {marker}" selected = select_knowledge.select_entries( safe, { "components": ["web"], "paths": ["web/app.py"], "dependencies": [], "versions": [], "tags": ["long-running-service"], "symbols": [], "errorSignatures": [], }, ) self.assertEqual(len(selected), 1) self.assertFalse(marker.exists()) def test_common_secret_material_is_rejected(self) -> None: data = example_data() data["entries"][0]["rationale"] = ( "debug token=sk-abcdefghijklmnopqrstuvwxyz123456" ) errors = validate_knowledge.validate_semantics(data) self.assertTrue(any("OpenAI-style token" in error for error in errors)) self.assertTrue(any("脱敏摘要" in error for error in errors)) def test_selection_is_active_deterministic_and_limited(self) -> None: data = example_data() stale = copy.deepcopy(data["entries"][0]) stale.update( { "id": "K-002", "status": "stale", "statusReason": "相关服务已移除", } ) global_entry = copy.deepcopy(data["entries"][0]) global_entry.update( { "id": "K-003", "subject": "global-release-check", "scope": { "all": True, "components": [], "paths": [], "dependencies": [], "versions": [], "tags": [], "symbols": [], "errorSignatures": [], }, } ) data["entries"].extend([stale, global_entry]) context = { "components": ["web"], "paths": ["web/app.py"], "dependencies": [], "versions": [], "tags": ["long-running-service"], "symbols": [], "errorSignatures": [], } selected = select_knowledge.select_entries(data, context, limit=1) self.assertEqual( [validate_knowledge.stable_ref(entry) for entry in selected], ["K-003@1"] ) self.assertFalse( select_knowledge.scope_matches( data["entries"][0]["scope"], {**context, "tags": []}, ) ) def test_global_entries_are_never_silently_dropped_by_limit(self) -> None: data = example_data() for number in range(2, 13): scoped = copy.deepcopy(data["entries"][0]) scoped.update( { "id": f"K-{number:03d}", "subject": f"scoped-check-{number}", } ) data["entries"].append(scoped) global_entry = copy.deepcopy(data["entries"][0]) global_entry.update( { "id": "K-999", "subject": "global-release-check", "scope": { "all": True, "components": [], "paths": [], "dependencies": [], "versions": [], "tags": [], "symbols": [], "errorSignatures": [], }, } ) data["entries"].append(global_entry) context = { "components": ["web"], "paths": ["web/app.py"], "dependencies": [], "versions": [], "tags": ["long-running-service"], "symbols": [], "errorSignatures": [], } selected = select_knowledge.select_entries(data, context, limit=10) refs = [validate_knowledge.stable_ref(entry) for entry in selected] self.assertEqual(len(refs), 10) self.assertIn("K-999@1", refs) second_global = copy.deepcopy(global_entry) second_global.update({"id": "K-998", "subject": "global-security-check"}) data["entries"].append(second_global) with self.assertRaisesRegex(ValueError, "不能静默丢弃"): select_knowledge.select_entries(data, context, limit=1) def test_path_glob_does_not_let_single_star_cross_directory(self) -> None: self.assertTrue(select_knowledge._path_glob_matches("web/app.py", "web/*")) self.assertFalse( select_knowledge._path_glob_matches("web/pages/app.py", "web/*") ) self.assertTrue( select_knowledge._path_glob_matches("web/pages/app.py", "web/**") ) def test_limit_prefers_narrow_scope_without_rewarding_or_patterns(self) -> None: data = example_data() narrow = copy.deepcopy(data["entries"][0]) narrow.update({"id": "K-002", "subject": "narrow-service-check"}) narrow["scope"]["paths"] = ["web/special/**"] broad_or = copy.deepcopy(data["entries"][0]) broad_or.update({"id": "K-003", "subject": "broad-or-service-check"}) broad_or["scope"]["paths"] = ["web/special/**", "api/**"] data["entries"].extend([narrow, broad_or]) context = { "components": ["web"], "paths": ["web/special/pages/app.py"], "dependencies": [], "versions": [], "tags": ["long-running-service"], "symbols": [], "errorSignatures": [], } selected = select_knowledge.select_entries(data, context, limit=1) self.assertEqual(validate_knowledge.stable_ref(selected[0]), "K-002@1") def test_task_cross_validation_requires_active_exact_ref_and_passed_check(self) -> None: data = example_data() task = { "id": "BUG-002", "status": "verified", "knowledgeRefs": ["K-001@1"], "knowledgeApplied": [ { "ref": "K-001@1", "result": "applied", "evidence": "developer report", } ], "knowledgeChecks": [ { "ref": "K-001@1", "result": "passed", "evidence": "test report", } ], "dispatch": { "rounds": [ { "round": 1, "attemptId": "BUG-002-A1", "result": "failed", }, { "round": 2, "attemptId": "BUG-002-A2", "result": "passed", } ] }, } tasks_data = { "project": { "name": "notes-web", "knowledgeFile": ".pouch/ack/knowledge.yaml", }, "tasks": [task], } self.assertEqual( validate_knowledge.validate_task_references(data, tasks_data), [] ) task["knowledgeChecks"][0]["result"] = "not_applicable" errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertTrue(any("缺少 passed knowledgeCheck" in error for error in errors)) data["entries"][0]["kind"] = "pitfall" errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertTrue(any("缺少 passed knowledgeCheck" in error for error in errors)) task["knowledgeRefs"] = ["K-001@2"] errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertTrue(any("找不到精确版本 K-001@2" in error for error in errors)) def test_task_cross_validation_rejects_another_project(self) -> None: data = example_data() tasks = { "project": { "name": "another-project", "knowledgeFile": ".pouch/ack/knowledge.yaml", }, "tasks": [], } errors = validate_knowledge.validate_task_references(data, tasks) self.assertTrue(any("与知识库项目" in error for error in errors)) def test_cross_validation_requires_traceable_explicit_attempt_id(self) -> None: data = example_data() task = { "id": "BUG-002", "status": "open", "dispatch": { "rounds": [ { "round": 1, "attemptId": "BUG-002-A1", "result": "failed", }, { "round": 2, "attemptId": "orca-dispatch-91", "result": "passed", } ] }, } tasks_data = { "project": { "name": "notes-web", "knowledgeFile": ".pouch/ack/knowledge.yaml", }, "tasks": [task], } errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertTrue(any("必须精确等于 'BUG-002-A2'" in e for e in errors)) self.assertTrue(any("未命中任务 'BUG-002'" in e for e in errors)) task["dispatch"]["rounds"][1]["attemptId"] = "BUG-002-A2" errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertFalse(any("provenance" in error for error in errors)) def test_cross_validation_rejects_non_contiguous_rounds(self) -> None: data = example_data() data["entries"][0]["provenance"]["attemptId"] = "BUG-002-A3" tasks_data = { "project": { "name": "notes-web", "knowledgeFile": ".pouch/ack/knowledge.yaml", }, "tasks": [ { "id": "BUG-002", "status": "open", "dispatch": { "rounds": [ { "round": 3, "attemptId": "BUG-002-A3", "result": "passed", } ] }, } ], } errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertTrue(any("round 必须按 1..N 连续" in error for error in errors)) def test_cross_validation_binds_configured_knowledge_file(self) -> None: data = example_data() task = { "id": "BUG-002", "status": "open", "dispatch": { "rounds": [ { "round": 1, "attemptId": "BUG-002-A1", "result": "failed", }, { "round": 2, "attemptId": "BUG-002-A2", "result": "passed", } ] }, } with tempfile.TemporaryDirectory() as temp_dir: project = Path(temp_dir) / "project" ack_dir = project / ".pouch" / "ack" ack_dir.mkdir(parents=True) knowledge_path = ack_dir / "knowledge.yaml" tasks_path = ack_dir / "tasks.yaml" knowledge_path.write_text( yaml.safe_dump(data, allow_unicode=True), encoding="utf-8" ) tasks_data = { "project": { "name": "notes-web", "repoPath": str(project), "knowledgeFile": ".pouch/ack/other.yaml", }, "tasks": [task], } errors = validate_knowledge.validate_task_references( data, tasks_data, knowledge_path=knowledge_path, tasks_path=tasks_path, ) self.assertTrue(any("与当前知识文件" in error for error in errors)) del tasks_data["project"]["knowledgeFile"] errors = validate_knowledge.validate_task_references(data, tasks_data) self.assertTrue(any("project.knowledgeFile 必填" in e for e in errors)) tasks_data["project"]["knowledgeFile"] = ".pouch/ack/knowledge.yaml" staging_root = Path(temp_dir) / "staging" staged = staging_root / ".pouch" / "ack" / "knowledge.yaml" staged.parent.mkdir(parents=True) staged.write_text( yaml.safe_dump(data, allow_unicode=True), encoding="utf-8" ) errors = validate_knowledge.validate_task_references( data, tasks_data, knowledge_path=staged, tasks_path=tasks_path, project_root=staging_root, project_root_is_explicit=True, ) self.assertFalse(any("knowledgeFile" in error for error in errors)) def test_cli_uses_tasks_layout_instead_of_stale_repo_path(self) -> None: data = example_data() task = { "id": "BUG-002", "title": "source task", "status": "open", "dispatch": { "rounds": [ { "round": 1, "attemptId": "BUG-002-A1", "result": "failed", }, { "round": 2, "attemptId": "BUG-002-A2", "result": "passed", } ] }, } with tempfile.TemporaryDirectory() as temp_dir: base = Path(temp_dir) project = base / "project" ack_dir = project / ".pouch" / "ack" outside = base / "outside" ack_dir.mkdir(parents=True) outside.mkdir() (project / "checks").symlink_to(outside, target_is_directory=True) data["verificationRegistry"]["service-worktree-alignment"]["path"] = ( "checks/verify.py" ) knowledge_path = ack_dir / "knowledge.yaml" knowledge_path.write_text( yaml.safe_dump(data, allow_unicode=True), encoding="utf-8" ) tasks_data = { "version": 1, "project": { "name": "notes-web", "repoPath": str(project / "missing"), "knowledgeFile": ".pouch/ack/not-the-current-file.yaml", }, "tasks": [task], } tasks_path = ack_dir / "tasks.yaml" tasks_path.write_text( yaml.safe_dump(tasks_data, allow_unicode=True), encoding="utf-8" ) command = [ sys.executable, str(SCRIPTS_DIR / "validate_knowledge.py"), str(knowledge_path), "--tasks", str(tasks_path), ] inferred = subprocess.run( command, capture_output=True, text=True, check=False, ) explicit = subprocess.run( [*command, "--project-root", str(project)], capture_output=True, text=True, check=False, ) self.assertEqual(inferred.returncode, 1) self.assertNotIn("project.repoPath", inferred.stderr) self.assertIn("与当前知识文件", inferred.stderr) self.assertIn("symlink", inferred.stderr) self.assertEqual(explicit.returncode, 1) self.assertIn("与当前知识文件", explicit.stderr) self.assertIn("symlink", explicit.stderr) def test_cli_requires_root_for_nonempty_registry_outside_project_layout(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: knowledge_path = Path(temp_dir) / "knowledge.yaml" knowledge_path.write_text( EXAMPLE_PATH.read_text(encoding="utf-8"), encoding="utf-8" ) result = subprocess.run( [ sys.executable, str(SCRIPTS_DIR / "validate_knowledge.py"), str(knowledge_path), ], capture_output=True, text=True, check=False, ) self.assertEqual(result.returncode, 1) self.assertIn("无法确定项目根目录", result.stderr) self.assertIn("--project-root", result.stderr) def test_terminal_task_keeps_historical_ref_after_entry_becomes_stale(self) -> None: data = example_data() data["entries"][0]["status"] = "stale" data["entries"][0]["statusReason"] = "service architecture changed" task = { "id": "BUG-002", "status": "verified", "knowledgeRefs": ["K-001@1"], "knowledgeApplied": [], "knowledgeChecks": [ { "ref": "K-001@1", "result": "passed", "evidence": "historical test evidence", } ], "dispatch": { "rounds": [ { "round": 1, "attemptId": "BUG-002-A1", "result": "failed", }, { "round": 2, "attemptId": "BUG-002-A2", "result": "passed", } ] }, } tasks_data = { "project": { "name": "notes-web", "knowledgeFile": ".pouch/ack/knowledge.yaml", }, "tasks": [task], } terminal_errors = validate_knowledge.validate_task_references( data, tasks_data ) self.assertFalse(any("必须 active" in error for error in terminal_errors)) task["status"] = "open" active_errors = validate_knowledge.validate_task_references( data, tasks_data ) self.assertTrue(any("必须 active" in error for error in active_errors)) def test_cli_validates_tasks_and_selector_outputs_stable_refs(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: project = Path(temp_dir) / "project" ack_dir = project / ".pouch" / "ack" ack_dir.mkdir(parents=True) knowledge_path = ack_dir / "knowledge.yaml" knowledge_path.write_text( EXAMPLE_PATH.read_text(encoding="utf-8"), encoding="utf-8" ) target = project / "tests" / "ack" / "check_service_worktree.py" target.parent.mkdir(parents=True) target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") target.chmod(target.stat().st_mode | 0o111) tasks = { "project": { "name": "notes-web", "repoPath": str(project), "knowledgeFile": ".pouch/ack/knowledge.yaml", }, "tasks": [ { "id": "BUG-002", "status": "verified", "knowledgeRefs": ["K-001@1"], "knowledgeApplied": [], "knowledgeChecks": [ { "ref": "K-001@1", "result": "passed", "evidence": "independent retest", } ], "dispatch": { "rounds": [ { "round": 1, "attemptId": "BUG-002-A1", "result": "failed", }, { "round": 2, "attemptId": "BUG-002-A2", "result": "passed", } ] }, } ], } tasks_path = ack_dir / "tasks.yaml" tasks_path.write_text( yaml.safe_dump(tasks, allow_unicode=True), encoding="utf-8" ) validated = subprocess.run( [ sys.executable, str(SCRIPTS_DIR / "validate_knowledge.py"), str(knowledge_path), "--tasks", str(tasks_path), ], capture_output=True, text=True, check=False, ) self.assertEqual(validated.returncode, 0, validated.stderr) selected = subprocess.run( [ sys.executable, str(SCRIPTS_DIR / "select_knowledge.py"), str(knowledge_path), "--component", "web", "--path", "web/app.py", "--tag", "long-running-service", "--limit", "1", "--format", "refs", ], capture_output=True, text=True, check=False, ) self.assertEqual(selected.returncode, 0, selected.stderr) self.assertEqual(selected.stdout.strip(), "K-001@1") selected_json = subprocess.run( [ sys.executable, str(SCRIPTS_DIR / "select_knowledge.py"), str(knowledge_path), "--component", "web", "--path", "web/app.py", "--tag", "long-running-service", ], capture_output=True, text=True, check=False, ) self.assertEqual(selected_json.returncode, 0, selected_json.stderr) payload = json.loads(selected_json.stdout) self.assertEqual( payload["entries"][0]["verificationTarget"]["path"], "tests/ack/check_service_worktree.py", ) def test_paired_examples_validate_when_installed_in_project_layout(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: project = Path(temp_dir) / "notes-web" ack_dir = project / ".pouch" / "ack" ack_dir.mkdir(parents=True) knowledge_path = ack_dir / "knowledge.yaml" tasks_path = ack_dir / "tasks.yaml" knowledge_path.write_text( EXAMPLE_PATH.read_text(encoding="utf-8"), encoding="utf-8", ) tasks = yaml.safe_load( TASKS_EXAMPLE_PATH.read_text(encoding="utf-8") ) tasks["project"]["repoPath"] = str(project) tasks["project"]["devWorktree"] = str(project) tasks_path.write_text( yaml.safe_dump(tasks, allow_unicode=True, sort_keys=False), encoding="utf-8", ) target = project / "tests" / "ack" / "check_service_worktree.py" target.parent.mkdir(parents=True) target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") target.chmod(target.stat().st_mode | 0o111) tasks_result = subprocess.run( [ sys.executable, str(SCRIPTS_DIR / "validate_tasks.py"), str(tasks_path), ], capture_output=True, text=True, check=False, ) knowledge_result = subprocess.run( [ sys.executable, str(SCRIPTS_DIR / "validate_knowledge.py"), str(knowledge_path), "--tasks", str(tasks_path), "--project-root", str(project), ], capture_output=True, text=True, check=False, ) self.assertEqual(tasks_result.returncode, 0, tasks_result.stderr) self.assertEqual( knowledge_result.returncode, 0, knowledge_result.stderr, ) if __name__ == "__main__": unittest.main()