@@ -386,12 +386,12 @@ def test_load_plugins_index_rejects_non_dict_values(self):
386386
387387
388388class ValidationProgressTests (unittest .TestCase ):
389- def test_build_parser_defaults_max_workers_to_eight (self ):
389+ def test_build_parser_defaults_max_workers_to_sixteen (self ):
390390 module = load_validator_module ()
391391
392392 args = module .build_parser ().parse_args (["--astrbot-path" , "/tmp/AstrBot" ])
393393
394- self .assertEqual (args .max_workers , 8 )
394+ self .assertEqual (args .max_workers , 16 )
395395
396396 def test_build_parser_rejects_non_positive_worker_and_timeout_values (self ):
397397 module = load_validator_module ()
@@ -748,6 +748,131 @@ async def load(self, specified_dir_name: str):
748748 self .assertEqual (result ["message" ], "{'reason': 'boom'}" )
749749
750750
751+ class RunWorkerIsolationTests (unittest .TestCase ):
752+ def _assert_worker_isolated (self , * , temp_root : Path , args , observed : dict ) -> None :
753+ self .assertIn ("astrbot_root" , observed )
754+ astrbot_root = Path (observed ["astrbot_root" ]).resolve ()
755+ shared_astrbot_path = Path (args .astrbot_path ).resolve ()
756+
757+ self .assertNotEqual (astrbot_root , shared_astrbot_path )
758+ self .assertTrue (
759+ astrbot_root .is_relative_to (temp_root .resolve ()),
760+ f"worker astrbot_root { astrbot_root } should be under { temp_root } " ,
761+ )
762+
763+ current = astrbot_root
764+ found_worker_prefix = False
765+ while True :
766+ if current .name .startswith ("astrbot-plugin-worker-" ):
767+ found_worker_prefix = True
768+ break
769+ if current .parent == current :
770+ break
771+ current = current .parent
772+
773+ self .assertTrue (found_worker_prefix )
774+ self .assertTrue ((astrbot_root / "data" / "plugins" ).is_dir ())
775+ self .assertTrue ((astrbot_root / "data" / "config" ).is_dir ())
776+
777+ def test_configure_worker_install_target_deduplicates_process_paths (self ):
778+ module = load_validator_module ()
779+ original_sys_path = list (sys .path )
780+
781+ with tempfile .TemporaryDirectory () as tmp_dir :
782+ temp_root = Path (tmp_dir )
783+ site_packages = (temp_root / "site-packages" ).resolve ()
784+ site_packages_alias = os .path .join (str (site_packages .parent ), "." , site_packages .name )
785+ extra_path = (temp_root / "extra-path" ).resolve ()
786+ extra_path .mkdir ()
787+ observed = {}
788+
789+ sys .path [:0 ] = [str (site_packages ), site_packages_alias ]
790+ with mock .patch .dict (
791+ os .environ ,
792+ {"PYTHONPATH" : os .pathsep .join ([site_packages_alias , str (extra_path )])},
793+ clear = True ,
794+ ):
795+ module .configure_worker_install_target (temp_root = temp_root )
796+ module .configure_worker_install_target (temp_root = temp_root )
797+
798+ observed ["pip_target" ] = os .environ ["PIP_TARGET" ]
799+ observed ["pythonpath_entries" ] = os .environ ["PYTHONPATH" ].split (os .pathsep )
800+ observed ["resolved_pythonpath_count" ] = sum (
801+ 1
802+ for entry in observed ["pythonpath_entries" ]
803+ if Path (entry ).resolve () == site_packages
804+ )
805+ observed ["resolved_sys_path_count" ] = sum (
806+ 1 for entry in sys .path if Path (entry ).resolve () == site_packages
807+ )
808+
809+ self .assertEqual (observed ["pip_target" ], str (site_packages ))
810+ self .assertEqual (observed ["resolved_pythonpath_count" ], 1 )
811+ self .assertEqual (observed ["resolved_sys_path_count" ], 1 )
812+ self .assertIn (str (extra_path ), observed ["pythonpath_entries" ])
813+
814+ sys .path [:] = original_sys_path
815+
816+ def test_run_worker_isolates_plugin_installs_under_temp_root (self ):
817+ module = load_validator_module ()
818+ observed = {}
819+ original_sys_path = list (sys .path )
820+
821+ async def fake_run_worker_load_check (plugin_dir_name : str , normalized_repo_url : str ):
822+ observed ["plugin_dir_name" ] = plugin_dir_name
823+ observed ["normalized_repo_url" ] = normalized_repo_url
824+ observed ["astrbot_root" ] = os .environ .get ("ASTRBOT_ROOT" )
825+ observed ["pip_target" ] = os .environ .get ("PIP_TARGET" )
826+ observed ["sys_path" ] = list (sys .path )
827+ return module .build_result (
828+ plugin = plugin_dir_name ,
829+ repo = normalized_repo_url ,
830+ normalized_repo_url = normalized_repo_url ,
831+ ok = True ,
832+ stage = "load" ,
833+ message = "plugin loaded successfully" ,
834+ plugin_dir_name = plugin_dir_name ,
835+ )
836+
837+ with tempfile .TemporaryDirectory () as tmp_dir :
838+ worker_temp_root = Path (tmp_dir ) / "astrbot-plugin-worker-test-root"
839+ worker_temp_root .mkdir ()
840+ plugin_source_dir = Path (tmp_dir ) / "plugin-src"
841+ plugin_source_dir .mkdir ()
842+ (plugin_source_dir / "main.py" ).write_text ("print('hello')\n " , encoding = "utf-8" )
843+ args = module .argparse .Namespace (
844+ astrbot_path = "/tmp/AstrBot" ,
845+ plugin_source_dir = str (plugin_source_dir ),
846+ plugin_dir_name = "demo_plugin" ,
847+ normalized_repo_url = "https://github.com/example/demo-plugin" ,
848+ )
849+
850+ with mock .patch .dict (os .environ , {}, clear = True ):
851+ with mock .patch .object (module , "run_worker_load_check" , side_effect = fake_run_worker_load_check ):
852+ with mock .patch .object (module .tempfile , "mkdtemp" , return_value = str (worker_temp_root )):
853+ with mock .patch .object (module .shutil , "rmtree" ) as rmtree_mock :
854+ exit_code = module .run_worker (args )
855+
856+ self ._assert_worker_isolated (temp_root = worker_temp_root , args = args , observed = observed )
857+ rmtree_mock .assert_called_once_with (worker_temp_root , ignore_errors = True )
858+
859+ sys .path [:] = original_sys_path
860+
861+ self .assertEqual (exit_code , 0 )
862+ self .assertEqual (observed ["plugin_dir_name" ], "demo_plugin" )
863+ self .assertEqual (
864+ observed ["normalized_repo_url" ],
865+ "https://github.com/example/demo-plugin" ,
866+ )
867+ self .assertIsNotNone (observed ["astrbot_root" ])
868+ self .assertIsNotNone (observed ["pip_target" ])
869+ self .assertEqual (
870+ Path (observed ["pip_target" ]).resolve (),
871+ (Path (observed ["astrbot_root" ]).parent / "site-packages" ).resolve (),
872+ )
873+ self .assertIn (observed ["pip_target" ], observed ["sys_path" ])
874+
875+
751876class ReportBuilderTests (unittest .TestCase ):
752877 def test_build_report_counts_passed_warned_and_failed_results (self ):
753878 module = load_validator_module ()
0 commit comments