import sys
import os
import argparse
import configparser
from pathlib import Path
from src.scheduler import Config
from src.log import init_logger
from src.utils.data_utils import parse_json_param
project_root = Path(__file__).parent.resolve()
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--testcase_dir", required=True)
parser.add_argument("--log_dir", required=True)
parser.add_argument(
"--pytest_testkit_whl_path",
required=False,
help=argparse.SUPPRESS,
)
parser.add_argument(
"--pytest_testkit_path",
required=False,
help=argparse.SUPPRESS,
)
parser.add_argument("--testcase_collector_path", required=False)
parser.add_argument("--obs", required=True)
parser.add_argument("--task_id", required=True)
parser.add_argument("--archive_log_dir", required=False)
parser.add_argument("--env_device_resource", required=False)
parser.add_argument("--pipeline_id", required=True)
parser.add_argument("--pipeline_run_id", required=True)
parser.add_argument("--job_run_id", required=True)
parser.add_argument("--log_conf", required=False)
parser.add_argument(
"--scheduler_secret",
required=False,
default=None,
help=(
"Fallback when SCHEDULER_SECRET env var is absent. "
"Prefer env var to avoid exposing the secret in argv."
),
)
parser.add_argument("--testcase_config_file", required=False)
parser.add_argument(
"--collect_config_file",
required=False,
default=None,
help=(
"Optional pytest config file (relative to testcase_dir) used for "
"test case metadata collection; defaults to the original flow when omitted"
),
)
parser.add_argument("--max_workers", required=False)
parser.add_argument("--image_label", required=False)
parser.add_argument(
"--env_deploy_model",
required=False,
default="Dislocated",
help="Environment deployment model: Co-located or Dislocated",
)
parser.add_argument(
"--platform",
required=False,
default="codearts",
help="Platform name prefix for environment naming, default: codearts",
)
parser.add_argument(
"--fail_case",
required=False,
type=str,
help="Path to JSON file containing failed test cases result",
)
parser.add_argument(
"--ci_env_info",
required=False,
type=str,
help=(
"Grouped JSON of CI runtime metadata under the ci_env_info key, "
"injected into test cases as the ci_env_info environment variable"
),
)
parser.add_argument(
"--debug_keep_env",
required=False,
action="store_true",
help=argparse.SUPPRESS,
)
return parser.parse_args()
def _init_logger(log_dir: str):
log_dir = Path(log_dir)
log_dir.mkdir(parents=True, exist_ok=True)
return init_logger(log_dir)
_SENSITIVE_ENV_KEYWORDS = {
"password",
"pwd",
"secret",
"token",
"access_token",
"ak",
"sk",
"api_key",
"api_secret",
"private_key",
"ssh_key",
"rsa_key",
"pem",
"credential",
"cred",
"auth",
"username",
"user",
"gitcode",
"obs",
}
def _sanitize_env_vars():
for key in list(os.environ.keys()):
if key == "SCHEDULER_SECRET":
continue
key_lower = key.lower()
if any(keyword in key_lower for keyword in _SENSITIVE_ENV_KEYWORDS):
os.environ[key] = "***"
def _parse_ci_env_info(raw):
"""解析并校验 --ci_env_info,输出规范的分组结构。
Args:
raw: 插件传入的 JSON 字符串,形如 {"ci_env_info": {...}},可能为空
Returns:
dict: {"ci_env_info": {字段名: 字符串值}},白名单字段恒在
"""
return Config.validate_ci_env_info(parse_json_param(raw, "ci_env_info"))
def _init_config(args):
Config.set("obs_info", args.obs)
Config.set("task_id", args.task_id)
Config.set(
"env_device_resource",
parse_json_param(args.env_device_resource, "env_device_resource"),
)
Config.set("log_conf", parse_json_param(args.log_conf, "log_conf"))
Config.set(
"pytest_testkit_path",
Path(args.pytest_testkit_whl_path) if args.pytest_testkit_whl_path else None,
)
Config.set(
"testcase_collector_path",
Path(args.testcase_collector_path) if args.testcase_collector_path else None,
)
Config.set("test_dir", Path(args.testcase_dir))
Config.set("max_workers", int(args.max_workers))
Config.set("output_json_path", Path(args.log_dir) / "output.json")
job_info = {
"pipeline_id": args.pipeline_id,
"pipeline_run_id": args.pipeline_run_id,
"job_id": args.job_run_id,
}
Config.set("job_info", job_info)
job_unique_id = f"{args.pipeline_id}-{args.pipeline_run_id}-{args.job_run_id}"
Config.set("job_unique_id", job_unique_id)
Config.set("ci_env_info", _parse_ci_env_info(args.ci_env_info))
scheduler_secret_raw = (
os.environ.pop("SCHEDULER_SECRET", None) or args.scheduler_secret
)
if not scheduler_secret_raw:
raise ValueError(
"scheduler_secret must be provided via SCHEDULER_SECRET env var "
"or --scheduler_secret argument"
)
scheduler_secret = parse_json_param(scheduler_secret_raw, "scheduler_secret")
Config.set("scheduler_secret", scheduler_secret)
Config.set("testcase_config_file", args.testcase_config_file)
Config.set("collect_config_file", args.collect_config_file)
only_fail = False
if args.testcase_config_file:
test_dir = Path(args.testcase_dir)
testcase_config_path = test_dir / args.testcase_config_file
if testcase_config_path.exists():
config_parser = configparser.ConfigParser()
config_parser.read(testcase_config_path, encoding="utf-8")
if (
"only-fail" in config_parser
and "run_only_fail_case" in config_parser["only-fail"]
):
only_fail_value = config_parser["only-fail"]["run_only_fail_case"]
only_fail = only_fail_value.lower() in ("true", "1", "yes")
image_label = args.image_label or ""
Config.set("image_label", image_label)
archive_log_dir = (
Path(args.archive_log_dir)
if args.archive_log_dir
else project_root / "case-logs"
)
Config.set("archive_log_dir", archive_log_dir)
archive_log_dir.mkdir(parents=True, exist_ok=True)
Config.set("env_deploy_model", args.env_deploy_model)
Config.set("platform", args.platform)
Config.set("only_fail", only_fail)
Config.set("fail_case", args.fail_case)
Config.set("debug_keep_env", args.debug_keep_env)
Config.set(
"lease_health_ttl_seconds",
-1 if args.debug_keep_env else 600,
)
def main():
args = parse_args()
_sanitize_env_vars()
_init_logger(args.log_dir)
_init_config(args)
from src.run_scheduler import SchedulerRunner
scheduler_runner = SchedulerRunner()
return scheduler_runner.run()
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)