mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			162 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import contextlib
 | |
| import dataclasses
 | |
| import json
 | |
| import os
 | |
| import subprocess
 | |
| from typing import Any
 | |
| 
 | |
| from test import support
 | |
| 
 | |
| from .utils import (
 | |
|     StrPath, StrJSON, TestTuple, FilterTuple, FilterDict)
 | |
| 
 | |
| 
 | |
| class JsonFileType:
 | |
|     UNIX_FD = "UNIX_FD"
 | |
|     WINDOWS_HANDLE = "WINDOWS_HANDLE"
 | |
|     STDOUT = "STDOUT"
 | |
| 
 | |
| 
 | |
| @dataclasses.dataclass(slots=True, frozen=True)
 | |
| class JsonFile:
 | |
|     # file type depends on file_type:
 | |
|     # - UNIX_FD: file descriptor (int)
 | |
|     # - WINDOWS_HANDLE: handle (int)
 | |
|     # - STDOUT: use process stdout (None)
 | |
|     file: int | None
 | |
|     file_type: str
 | |
| 
 | |
|     def configure_subprocess(self, popen_kwargs: dict) -> None:
 | |
|         match self.file_type:
 | |
|             case JsonFileType.UNIX_FD:
 | |
|                 # Unix file descriptor
 | |
|                 popen_kwargs['pass_fds'] = [self.file]
 | |
|             case JsonFileType.WINDOWS_HANDLE:
 | |
|                 # Windows handle
 | |
|                 startupinfo = subprocess.STARTUPINFO()
 | |
|                 startupinfo.lpAttributeList = {"handle_list": [self.file]}
 | |
|                 popen_kwargs['startupinfo'] = startupinfo
 | |
| 
 | |
|     @contextlib.contextmanager
 | |
|     def inherit_subprocess(self):
 | |
|         if self.file_type == JsonFileType.WINDOWS_HANDLE:
 | |
|             os.set_handle_inheritable(self.file, True)
 | |
|             try:
 | |
|                 yield
 | |
|             finally:
 | |
|                 os.set_handle_inheritable(self.file, False)
 | |
|         else:
 | |
|             yield
 | |
| 
 | |
|     def open(self, mode='r', *, encoding):
 | |
|         if self.file_type == JsonFileType.STDOUT:
 | |
|             raise ValueError("for STDOUT file type, just use sys.stdout")
 | |
| 
 | |
|         file = self.file
 | |
|         if self.file_type == JsonFileType.WINDOWS_HANDLE:
 | |
|             import msvcrt
 | |
|             # Create a file descriptor from the handle
 | |
|             file = msvcrt.open_osfhandle(file, os.O_WRONLY)
 | |
|         return open(file, mode, encoding=encoding)
 | |
| 
 | |
| 
 | |
| @dataclasses.dataclass(slots=True, frozen=True)
 | |
| class HuntRefleak:
 | |
|     warmups: int
 | |
|     runs: int
 | |
|     filename: StrPath
 | |
| 
 | |
| 
 | |
| @dataclasses.dataclass(slots=True, frozen=True)
 | |
| class RunTests:
 | |
|     tests: TestTuple
 | |
|     fail_fast: bool
 | |
|     fail_env_changed: bool
 | |
|     match_tests: FilterTuple | None
 | |
|     ignore_tests: FilterTuple | None
 | |
|     match_tests_dict: FilterDict | None
 | |
|     rerun: bool
 | |
|     forever: bool
 | |
|     pgo: bool
 | |
|     pgo_extended: bool
 | |
|     output_on_failure: bool
 | |
|     timeout: float | None
 | |
|     verbose: int
 | |
|     quiet: bool
 | |
|     hunt_refleak: HuntRefleak | None
 | |
|     test_dir: StrPath | None
 | |
|     use_junit: bool
 | |
|     memory_limit: str | None
 | |
|     gc_threshold: int | None
 | |
|     use_resources: tuple[str, ...]
 | |
|     python_cmd: tuple[str, ...] | None
 | |
|     randomize: bool
 | |
|     random_seed: int | None
 | |
|     json_file: JsonFile | None
 | |
| 
 | |
|     def copy(self, **override):
 | |
|         state = dataclasses.asdict(self)
 | |
|         state.update(override)
 | |
|         return RunTests(**state)
 | |
| 
 | |
|     def get_match_tests(self, test_name) -> FilterTuple | None:
 | |
|         if self.match_tests_dict is not None:
 | |
|             return self.match_tests_dict.get(test_name, None)
 | |
|         else:
 | |
|             return None
 | |
| 
 | |
|     def get_jobs(self):
 | |
|         # Number of run_single_test() calls needed to run all tests.
 | |
|         # None means that there is not bound limit (--forever option).
 | |
|         if self.forever:
 | |
|             return None
 | |
|         return len(self.tests)
 | |
| 
 | |
|     def iter_tests(self):
 | |
|         if self.forever:
 | |
|             while True:
 | |
|                 yield from self.tests
 | |
|         else:
 | |
|             yield from self.tests
 | |
| 
 | |
|     def as_json(self) -> StrJSON:
 | |
|         return json.dumps(self, cls=_EncodeRunTests)
 | |
| 
 | |
|     @staticmethod
 | |
|     def from_json(worker_json: StrJSON) -> 'RunTests':
 | |
|         return json.loads(worker_json, object_hook=_decode_runtests)
 | |
| 
 | |
|     def json_file_use_stdout(self) -> bool:
 | |
|         # Use STDOUT in two cases:
 | |
|         #
 | |
|         # - If --python command line option is used;
 | |
|         # - On Emscripten and WASI.
 | |
|         #
 | |
|         # On other platforms, UNIX_FD or WINDOWS_HANDLE can be used.
 | |
|         return (
 | |
|             bool(self.python_cmd)
 | |
|             or support.is_emscripten
 | |
|             or support.is_wasi
 | |
|         )
 | |
| 
 | |
| 
 | |
| class _EncodeRunTests(json.JSONEncoder):
 | |
|     def default(self, o: Any) -> dict[str, Any]:
 | |
|         if isinstance(o, RunTests):
 | |
|             result = dataclasses.asdict(o)
 | |
|             result["__runtests__"] = True
 | |
|             return result
 | |
|         else:
 | |
|             return super().default(o)
 | |
| 
 | |
| 
 | |
| def _decode_runtests(data: dict[str, Any]) -> RunTests | dict[str, Any]:
 | |
|     if "__runtests__" in data:
 | |
|         data.pop('__runtests__')
 | |
|         if data['hunt_refleak']:
 | |
|             data['hunt_refleak'] = HuntRefleak(**data['hunt_refleak'])
 | |
|         if data['json_file']:
 | |
|             data['json_file'] = JsonFile(**data['json_file'])
 | |
|         return RunTests(**data)
 | |
|     else:
 | |
|         return data
 | 
