def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space Args: file_abs_path: absolute file path Returns: ensured valid absolute file path """ project_meta = load_project_meta(file_abs_path) raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path) file_suffix = file_suffix.lower() raw_file_relative_name = convert_relative_project_root_dir( raw_abs_file_name) if raw_file_relative_name == "": return file_abs_path path_names = [] for name in raw_file_relative_name.rstrip(os.sep).split(os.sep): if name[0] in string.digits: # ensure file name not startswith digit # 19 => T19, 2C => T2C name = f"T{name}" if name.startswith("."): # avoid ".csv" been converted to "_csv" pass else: # handle cases when directory name includes dot/hyphen/space name = name.replace(" ", "_").replace(".", "_").replace("-", "_") path_names.append(name) new_file_path = os.path.join(project_meta.RootDir, f"{os.sep.join(path_names)}{file_suffix}") return new_file_path
def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: """convert valid testcase dict to pytest file path""" # ensure compatibility with testcase format v2 testcase = ensure_testcase_v3(testcase) # validate testcase format load_testcase(testcase) testcase_abs_path = __ensure_absolute(testcase["config"]["path"]) logger.info(f"start to make testcase: {testcase_abs_path}") testcase_python_abs_path, testcase_cls_name = convert_testcase_path( testcase_abs_path) if dir_path: testcase_python_abs_path = os.path.join( dir_path, os.path.basename(testcase_python_abs_path)) global pytest_files_made_cache_mapping if testcase_python_abs_path in pytest_files_made_cache_mapping: return testcase_python_abs_path config = testcase["config"] config["path"] = convert_relative_project_root_dir( testcase_python_abs_path) config["variables"] = convert_variables(config.get("variables", {}), testcase_abs_path) # prepare reference testcase imports_list = [] teststeps = testcase["teststeps"] for teststep in teststeps: if not teststep.get("testcase"): continue # make ref testcase pytest file ref_testcase_path = __ensure_absolute(teststep["testcase"]) test_content = load_test_file(ref_testcase_path) # api in v2 format, convert to v3 testcase if "request" in test_content and "name" in test_content: test_content = ensure_testcase_v3_api(test_content) test_content.setdefault("config", {})["path"] = ref_testcase_path ref_testcase_python_abs_path = make_testcase(test_content) # override testcase export ref_testcase_export: List = test_content["config"].get("export", []) if ref_testcase_export: step_export: List = teststep.setdefault("export", []) step_export.extend(ref_testcase_export) teststep["export"] = list(set(step_export)) # prepare ref testcase class name ref_testcase_cls_name = pytest_files_made_cache_mapping[ ref_testcase_python_abs_path] teststep["testcase"] = ref_testcase_cls_name # prepare import ref testcase ref_testcase_python_relative_path = convert_relative_project_root_dir( ref_testcase_python_abs_path) ref_module_name, _ = os.path.splitext( ref_testcase_python_relative_path) ref_module_name = ref_module_name.replace(os.sep, ".") import_expr = f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}" if import_expr not in imports_list: imports_list.append(import_expr) testcase_path = convert_relative_project_root_dir(testcase_abs_path) # current file compared to ProjectRootDir diff_levels = len(testcase_path.split(os.sep)) data = { "version": __version__, "testcase_path": testcase_path, "diff_levels": diff_levels, "class_name": f"TestCase{testcase_cls_name}", "imports_list": imports_list, "config_chain_style": make_config_chain_style(config), "customization_test_start": make_test_start_style(config), "teststeps_chain_style": [make_teststep_chain_style(step) for step in teststeps], } content = __TEMPLATE__.render(data) # ensure new file's directory exists dir_path = os.path.dirname(testcase_python_abs_path) if not os.path.exists(dir_path): os.makedirs(dir_path) with open(testcase_python_abs_path, "w", encoding="utf-8") as f: f.write(content) pytest_files_made_cache_mapping[ testcase_python_abs_path] = testcase_cls_name __ensure_testcase_module(testcase_python_abs_path) logger.info(f"generated testcase: {testcase_python_abs_path}") return testcase_python_abs_path
def _generate_conftest_for_summary(args: List): for arg in args: if os.path.exists(arg): test_path = arg # FIXME: several test paths maybe specified break else: logger.error(f"No valid test path specified! \nargs: {args}") sys.exit(1) conftest_content = '''# NOTICE: Generated By HttpRunner. import json import os import time import pytest from loguru import logger from httprunner.utils import get_platform, ExtendJSONEncoder @pytest.fixture(scope="session", autouse=True) def session_fixture(request): """setup and teardown each task""" logger.info("start running testcases ...") start_at = time.time() yield logger.info("task finished, generate task summary for --save-tests") summary = { "success": True, "stat": { "testcases": {"total": 0, "success": 0, "fail": 0}, "teststeps": {"total": 0, "failures": 0, "successes": 0}, }, "time": {"start_at": start_at, "duration": time.time() - start_at}, "platform": get_platform(), "details": [], } for item in request.node.items: testcase_summary = item.instance.get_summary() summary["success"] &= testcase_summary.success summary["stat"]["testcases"]["total"] += 1 summary["stat"]["teststeps"]["total"] += len(testcase_summary.step_results) if testcase_summary.success: summary["stat"]["testcases"]["success"] += 1 summary["stat"]["teststeps"]["successes"] += len( testcase_summary.step_results ) else: summary["stat"]["testcases"]["fail"] += 1 summary["stat"]["teststeps"]["successes"] += ( len(testcase_summary.step_results) - 1 ) summary["stat"]["teststeps"]["failures"] += 1 testcase_summary_json = testcase_summary.dict() testcase_summary_json["records"] = testcase_summary_json.pop("step_results") summary["details"].append(testcase_summary_json) summary_path = r"{{SUMMARY_PATH_PLACEHOLDER}}" summary_dir = os.path.dirname(summary_path) os.makedirs(summary_dir, exist_ok=True) with open(summary_path, "w", encoding="utf-8") as f: json.dump(summary, f, indent=4, ensure_ascii=False, cls=ExtendJSONEncoder) logger.info(f"generated task summary: {summary_path}") ''' project_meta = load_project_meta(test_path) project_root_dir = project_meta.RootDir conftest_path = os.path.join(project_root_dir, "conftest.py") test_path = os.path.abspath(test_path) logs_dir_path = os.path.join(project_root_dir, "logs") test_path_relative_path = convert_relative_project_root_dir(test_path) if os.path.isdir(test_path): file_foder_path = os.path.join(logs_dir_path, test_path_relative_path) dump_file_name = "all.summary.json" else: file_relative_folder_path, test_file = os.path.split( test_path_relative_path) file_foder_path = os.path.join(logs_dir_path, file_relative_folder_path) test_file_name, _ = os.path.splitext(test_file) dump_file_name = f"{test_file_name}.summary.json" summary_path = os.path.join(file_foder_path, dump_file_name) conftest_content = conftest_content.replace("{{SUMMARY_PATH_PLACEHOLDER}}", summary_path) dir_path = os.path.dirname(conftest_path) if not os.path.exists(dir_path): os.makedirs(dir_path) with open(conftest_path, "w", encoding="utf-8") as f: f.write(conftest_content) logger.info("generated conftest.py to generate summary.json")