change: make converted referenced pytest files always relative to project RootDir

This commit is contained in:
debugtalk
2020-06-20 23:53:45 +08:00
parent 6a6bb49c66
commit 4ff9770c64
15 changed files with 132 additions and 193 deletions

View File

@@ -6,6 +6,10 @@
- feat: integrate [locust](https://locust.io/) v1.0 - feat: integrate [locust](https://locust.io/) v1.0
**Changed**
- change: make converted referenced pytest files always relative to ProjectRootDir
**Fixed** **Fixed**
- change: do not raise error if failed to get client/server address info - change: do not raise error if failed to get client/server address info

View File

@@ -1,14 +1,14 @@
# NOTE: Generated By HttpRunner v3.1.0 # NOTE: Generated By HttpRunner v3.1.0
# FROM: request_methods/request_with_testcase_reference.yml # FROM: request_methods/request_with_testcase_reference.yml
import os
import sys import sys
from pathlib import Path
sys.path.insert(0, os.getcwd()) sys.path.insert(0, str(Path(__file__).parent.parent))
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import ( from request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions, TestCaseRequestWithFunctions as RequestWithFunctions,
) )

View File

@@ -1,14 +1,14 @@
# NOTE: Generated By HttpRunner v3.1.0 # NOTE: Generated By HttpRunner v3.1.0
# FROM: request_methods/request_with_testcase_reference.yml # FROM: request_methods/request_with_testcase_reference.yml
import os
import sys import sys
from pathlib import Path
sys.path.insert(0, os.getcwd()) sys.path.insert(0, str(Path(__file__).parent.parent))
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import ( from request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions, TestCaseRequestWithFunctions as RequestWithFunctions,
) )

View File

@@ -6,7 +6,7 @@ import sys
from typing import List, Dict, Text, Union, Any from typing import List, Dict, Text, Union, Any
from httprunner import exceptions from httprunner import exceptions
from httprunner.loader import load_project_meta from httprunner.loader import load_project_meta, convert_relative_project_root_dir
from httprunner.parser import parse_data from httprunner.parser import parse_data
from httprunner.utils import sort_dict_by_custom_order from httprunner.utils import sort_dict_by_custom_order
from loguru import logger from loguru import logger
@@ -340,7 +340,7 @@ def session_fixture(request):
test_path = os.path.abspath(test_path) test_path = os.path.abspath(test_path)
logs_dir_path = os.path.join(project_root_dir, "logs") logs_dir_path = os.path.join(project_root_dir, "logs")
test_path_relative_path = test_path[len(project_root_dir) + 1 :] test_path_relative_path = convert_relative_project_root_dir(test_path)
if os.path.isdir(test_path): if os.path.isdir(test_path):
file_foder_path = os.path.join(logs_dir_path, test_path_relative_path) file_foder_path = os.path.join(logs_dir_path, test_path_relative_path)

View File

@@ -432,3 +432,23 @@ def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
project_meta.debugtalk_path = debugtalk_path project_meta.debugtalk_path = debugtalk_path
return project_meta return project_meta
def convert_relative_project_root_dir(abs_path: Text) -> Text:
""" convert absolute path to relative path, based on project_meta.RootDir
Args:
abs_path: absolute path
Returns: relative path based on project_meta.RootDir
"""
_project_meta = load_project_meta(abs_path)
if not abs_path.startswith(_project_meta.RootDir):
raise exceptions.ParamsError(
f"failed to convert absolute path to relative path based on project_meta.RootDir\n"
f"abs_path: {abs_path}\n"
f"project_meta.RootDir: {_project_meta.RootDir}"
)
return abs_path[len(_project_meta.RootDir) + 1 :]

View File

@@ -1,7 +1,7 @@
import os import os
import string
import subprocess import subprocess
import sys import sys
from shutil import copyfile
from typing import Text, List, Tuple, Dict, Set, NoReturn from typing import Text, List, Tuple, Dict, Set, NoReturn
import jinja2 import jinja2
@@ -21,9 +21,10 @@ from httprunner.loader import (
load_testcase, load_testcase,
load_testsuite, load_testsuite,
load_project_meta, load_project_meta,
convert_relative_project_root_dir,
) )
from httprunner.response import uniform_validator from httprunner.response import uniform_validator
from httprunner.utils import ensure_file_abs_path_valid, override_config_variables from httprunner.utils import override_config_variables
""" cache converted pytest files, avoid duplicate making """ cache converted pytest files, avoid duplicate making
""" """
@@ -36,11 +37,15 @@ pytest_files_run_set: Set = set()
__TEMPLATE__ = jinja2.Template( __TEMPLATE__ = jinja2.Template(
"""# NOTE: Generated By HttpRunner v{{ version }} """# NOTE: Generated By HttpRunner v{{ version }}
# FROM: {{ testcase_path }} # FROM: {{ testcase_path }}
{% if imports_list %} {% if imports_list and diff_levels > 0 %}
import os
import sys import sys
from pathlib import Path
sys.path.insert(0, os.getcwd()) sys.path.insert(0, str(Path(__file__)
{% for _ in range(diff_levels) %}
.parent
{% endfor %}
))
{% endif %} {% endif %}
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
{% for import_str in imports_list %} {% for import_str in imports_list %}
@@ -86,44 +91,45 @@ def __ensure_absolute(path: Text) -> Text:
return absolute_path return absolute_path
def __convert_relative_current_working_dir(abs_path: Text) -> Text: def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
""" convert absolute path to relative path, based on os.getcwd() """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
Args: Args:
abs_path: absolute path file_abs_path: absolute file path
Returns: relative path based on os.getcwd() Returns:
ensured valid absolute file path
""" """
cwd = os.getcwd() project_meta = load_project_meta(file_abs_path)
if not abs_path.startswith(cwd): raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path)
raise exceptions.ParamsError( file_suffix = file_suffix.lower()
f"failed to convert absolute path to relative path based on os.getcwd()\n"
f"abs_path: {abs_path}\n"
f"os.getcwd(): {cwd}"
)
return abs_path[len(cwd) + 1 :] 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):
def __convert_relative_project_root_dir(abs_path: Text) -> Text: if name[0] in string.digits:
""" convert absolute path to relative path, based on project_meta.RootDir # ensure file name not startswith digit
# 19 => T19, 2C => T2C
name = f"T{name}"
Args: if name.startswith("."):
abs_path: absolute path # avoid ".csv" been converted to "_csv"
pass
else:
# handle cases when directory name includes dot/hyphen/space
name = name.replace(" ", "_").replace(".", "_").replace("-", "_")
Returns: relative path based on project_meta.RootDir path_names.append(name)
""" new_file_path = os.path.join(
project_meta = load_project_meta(abs_path) project_meta.RootDir, f"{os.sep.join(path_names)}{file_suffix}"
if not abs_path.startswith(project_meta.RootDir): )
raise exceptions.ParamsError( return new_file_path
f"failed to convert absolute path to relative path based on project_meta.RootDir\n"
f"abs_path: {abs_path}\n"
f"project_meta.RootDir: {project_meta.RootDir}"
)
return abs_path[len(project_meta.RootDir) + 1 :]
def __ensure_testcase_module(path: Text) -> NoReturn: def __ensure_testcase_module(path: Text) -> NoReturn:
@@ -137,31 +143,6 @@ def __ensure_testcase_module(path: Text) -> NoReturn:
f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!\n") f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!\n")
def __ensure_project_meta_files(tests_path: Text) -> NoReturn:
""" ensure project meta files exist in generated pytest folder files
include debugtalk.py and .env
"""
project_meta = load_project_meta(tests_path)
# handle cases when generated pytest directory are different from original yaml/json testcases
debugtalk_path = project_meta.debugtalk_path
if debugtalk_path:
debugtalk_new_path = ensure_file_abs_path_valid(debugtalk_path)
if debugtalk_new_path != debugtalk_path:
logger.info(f"copy debugtalk.py to {debugtalk_new_path}")
copyfile(debugtalk_path, debugtalk_new_path)
global pytest_files_made_cache_mapping
pytest_files_made_cache_mapping[debugtalk_new_path] = ""
dot_csv_path = project_meta.dot_env_path
if dot_csv_path:
dot_csv_new_path = ensure_file_abs_path_valid(dot_csv_path)
if dot_csv_new_path != dot_csv_path:
logger.info(f"copy .env to {dot_csv_new_path}")
copyfile(dot_csv_path, dot_csv_new_path)
def convert_testcase_path(testcase_abs_path: Text) -> Tuple[Text, Text]: def convert_testcase_path(testcase_abs_path: Text) -> Tuple[Text, Text]:
"""convert single YAML/JSON testcase path to python file""" """convert single YAML/JSON testcase path to python file"""
testcase_new_path = ensure_file_abs_path_valid(testcase_abs_path) testcase_new_path = ensure_file_abs_path_valid(testcase_abs_path)
@@ -358,7 +339,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
return testcase_python_abs_path return testcase_python_abs_path
config = testcase["config"] config = testcase["config"]
config["path"] = __convert_relative_project_root_dir(testcase_python_abs_path) config["path"] = convert_relative_project_root_dir(testcase_python_abs_path)
config["variables"] = convert_variables( config["variables"] = convert_variables(
config.get("variables", {}), testcase_abs_path config.get("variables", {}), testcase_abs_path
) )
@@ -388,7 +369,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
teststep["testcase"] = ref_testcase_cls_name teststep["testcase"] = ref_testcase_cls_name
# prepare import ref testcase # prepare import ref testcase
ref_testcase_python_relative_path = __convert_relative_current_working_dir( ref_testcase_python_relative_path = convert_relative_project_root_dir(
ref_testcase_python_abs_path ref_testcase_python_abs_path
) )
ref_module_name, _ = os.path.splitext(ref_testcase_python_relative_path) ref_module_name, _ = os.path.splitext(ref_testcase_python_relative_path)
@@ -397,9 +378,14 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}" f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}"
) )
testcase_path = convert_relative_project_root_dir(testcase_abs_path)
# current file compared to ProjectRootDir
diff_levels = len(testcase_path.split(os.sep))
data = { data = {
"version": __version__, "version": __version__,
"testcase_path": __convert_relative_project_root_dir(testcase_abs_path), "testcase_path": testcase_path,
"diff_levels": diff_levels,
"class_name": f"TestCase{testcase_cls_name}", "class_name": f"TestCase{testcase_cls_name}",
"imports_list": imports_list, "imports_list": imports_list,
"config_chain_style": make_config_chain_style(config), "config_chain_style": make_config_chain_style(config),
@@ -564,8 +550,6 @@ def main_make(tests_paths: List[Text]) -> List[Text]:
logger.error(ex) logger.error(ex)
sys.exit(1) sys.exit(1)
__ensure_project_meta_files(tests_path)
# format pytest files # format pytest files
pytest_files_format_list = pytest_files_made_cache_mapping.keys() pytest_files_format_list = pytest_files_made_cache_mapping.keys()
format_pytest_with_black(*pytest_files_format_list) format_pytest_with_black(*pytest_files_format_list)

View File

@@ -3,16 +3,14 @@ import copy
import json import json
import os.path import os.path
import platform import platform
import string
import uuid import uuid
from typing import Dict, List, Any, Text from typing import Dict, List, Any
import sentry_sdk import sentry_sdk
from loguru import logger
from httprunner import __version__ from httprunner import __version__
from httprunner import exceptions from httprunner import exceptions
from httprunner.models import VariablesMapping from httprunner.models import VariablesMapping
from loguru import logger
def init_sentry_sdk(): def init_sentry_sdk():
@@ -181,45 +179,6 @@ def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List):
) )
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
"""
raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path)
file_suffix = file_suffix.lower()
raw_file_relative_name = raw_abs_file_name[len(os.getcwd()) + 1 :]
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(os.getcwd(), f"{os.sep.join(path_names)}{file_suffix}")
return new_file_path
class ExtendJSONEncoder(json.JSONEncoder): class ExtendJSONEncoder(json.JSONEncoder):
""" especially used to safely dump json data with python object, such as MultipartEncoder """ especially used to safely dump json data with python object, such as MultipartEncoder
""" """

View File

@@ -1,4 +1,5 @@
import io import io
import os
import sys import sys
import unittest import unittest
@@ -40,10 +41,12 @@ class TestCli(unittest.TestCase):
self.assertIn(__description__, self.captured_output.getvalue().strip()) self.assertIn(__description__, self.captured_output.getvalue().strip())
def test_debug_pytest(self): def test_debug_pytest(self):
exit_code = pytest.main( cwd = os.getcwd()
[ try:
"-s", os.chdir(os.path.join(cwd, "examples", "postman_echo"))
"examples/postman_echo/request_methods/request_with_testcase_reference_test.py", exit_code = pytest.main(
] ["-s", "request_methods/request_with_testcase_reference_test.py",]
) )
self.assertEqual(exit_code, 0) self.assertEqual(exit_code, 0)
finally:
os.chdir(cwd)

View File

@@ -6,7 +6,7 @@ config:
teststeps: teststeps:
- -
name: request with functions name: request with functions
testcase: 1.yml testcase: a-b.c/1.yml
export: export:
- session_foo2 - session_foo2
- -

View File

View File

@@ -8,6 +8,7 @@ from httprunner.make import (
make_config_chain_style, make_config_chain_style,
make_teststep_chain_style, make_teststep_chain_style,
pytest_files_run_set, pytest_files_run_set,
ensure_file_abs_path_valid,
) )
from httprunner import loader from httprunner import loader
@@ -64,7 +65,7 @@ class TestMake(unittest.TestCase):
content = f.read() content = f.read()
self.assertIn( self.assertIn(
""" """
from examples.postman_echo.request_methods.request_with_functions_test import ( from request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions, TestCaseRequestWithFunctions as RequestWithFunctions,
) )
""", """,
@@ -90,53 +91,57 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
testcase_python_list, testcase_python_list,
) )
def test_ensure_file_path_valid(self):
self.assertEqual(
ensure_file_abs_path_valid(
os.path.join(os.getcwd(), "tests", "data", "a-b.c", "2 3.yml")
),
os.path.join(os.getcwd(), "tests", "data", "a_b_c", "T2_3.yml"),
)
loader.project_meta = None
self.assertEqual(
ensure_file_abs_path_valid(
os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods")
),
os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods"),
)
loader.project_meta = None
self.assertEqual(
ensure_file_abs_path_valid(os.path.join(os.getcwd(), "README.md")),
os.path.join(os.getcwd(), "README.md"),
)
loader.project_meta = None
self.assertEqual(
ensure_file_abs_path_valid(os.getcwd()), os.getcwd(),
)
loader.project_meta = None
self.assertEqual(
ensure_file_abs_path_valid(
os.path.join(os.getcwd(), "tests", "data", ".csv")
),
os.path.join(os.getcwd(), "tests", "data", ".csv"),
)
def test_convert_testcase_path(self): def test_convert_testcase_path(self):
self.assertEqual(
convert_testcase_path(os.path.join(os.getcwd(), "mubu.login.yml")),
(os.path.join(os.getcwd(), "mubu_login_test.py"), "MubuLogin"),
)
self.assertEqual( self.assertEqual(
convert_testcase_path( convert_testcase_path(
os.path.join(os.getcwd(), os.path.join("path", "to", "mubu.login.yml")) os.path.join(os.getcwd(), "tests", "data", "a-b.c", "2 3.yml")
), ),
( (
os.path.join( os.path.join(os.getcwd(), "tests", "data", "a_b_c", "T2_3_test.py"),
os.getcwd(), os.path.join("path", "to", "mubu_login_test.py") "T23",
),
"MubuLogin",
), ),
) )
self.assertEqual( self.assertEqual(
convert_testcase_path( convert_testcase_path(
os.path.join(os.getcwd(), "path", "to 2", "mubu.login.yml") os.path.join(os.getcwd(), "tests", "data", "a-b.c", "中文case.yml")
), ),
( (
os.path.join( os.path.join(
os.getcwd(), os.path.join("path", "to_2", "mubu_login_test.py") os.getcwd(),
os.path.join("tests", "data", "a_b_c", "中文case_test.py"),
), ),
"MubuLogin", "中文Case",
),
)
self.assertEqual(
convert_testcase_path(
os.path.join(os.getcwd(), "path", "to-2", "mubu login.yml")
),
(
os.path.join(
os.getcwd(), os.path.join("path", "to_2", "mubu_login_test.py")
),
"MubuLogin",
),
)
self.assertEqual(
convert_testcase_path(
os.path.join(os.getcwd(), "path", "to.2", "幕布login.yml")
),
(
os.path.join(
os.getcwd(), os.path.join("path", "to_2", "幕布login_test.py")
),
"幕布Login",
), ),
) )

View File

@@ -35,6 +35,6 @@ class TestHttpRunner(unittest.TestCase):
exit_code = main_run(["tests/data/a-b.c/2 3.yml"]) exit_code = main_run(["tests/data/a-b.c/2 3.yml"])
self.assertEqual(exit_code, 0) self.assertEqual(exit_code, 0)
self.assertTrue(os.path.exists("tests/data/a_b_c/__init__.py")) self.assertTrue(os.path.exists("tests/data/a_b_c/__init__.py"))
self.assertTrue(os.path.exists("tests/data/a_b_c/debugtalk.py")) self.assertTrue(os.path.exists("tests/data/debugtalk.py"))
self.assertTrue(os.path.exists("tests/data/a_b_c/T1_test.py")) self.assertTrue(os.path.exists("tests/data/a_b_c/T1_test.py"))
self.assertTrue(os.path.exists("tests/data/a_b_c/T2_3_test.py")) self.assertTrue(os.path.exists("tests/data/a_b_c/T2_3_test.py"))

View File

@@ -5,7 +5,6 @@ import unittest
from httprunner import loader, utils from httprunner import loader, utils
from httprunner.utils import ( from httprunner.utils import (
ensure_file_abs_path_valid,
ExtendJSONEncoder, ExtendJSONEncoder,
override_config_variables, override_config_variables,
) )
@@ -105,41 +104,6 @@ class TestUtils(unittest.TestCase):
["A", "D", "C", "B"], ["A", "D", "C", "B"],
) )
def test_ensure_file_path_valid(self):
self.assertEqual(
ensure_file_abs_path_valid(
os.path.join(os.getcwd(), "examples", "a-b.c", "d f", "hardcode.yml")
),
os.path.join(os.getcwd(), "examples", "a_b_c", "d_f", "hardcode.yml"),
)
self.assertEqual(
ensure_file_abs_path_valid(os.path.join(os.getcwd(), "1", "2B", "3.yml")),
os.path.join(os.getcwd(), "T1", "T2B", "T3.yml"),
)
self.assertEqual(
ensure_file_abs_path_valid(
os.path.join(os.getcwd(), "examples", "a-b.c", "2B", "hardcode.yml")
),
os.path.join(os.getcwd(), "examples", "a_b_c", "T2B", "hardcode.yml"),
)
self.assertEqual(
ensure_file_abs_path_valid(
os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods")
),
os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods"),
)
self.assertEqual(
ensure_file_abs_path_valid(os.path.join(os.getcwd(), "test.yml")),
os.path.join(os.getcwd(), "test.yml"),
)
self.assertEqual(
ensure_file_abs_path_valid(os.getcwd()), os.getcwd(),
)
self.assertEqual(
ensure_file_abs_path_valid(os.path.join(os.getcwd(), "demo", ".csv")),
os.path.join(os.getcwd(), "demo", ".csv"),
)
def test_safe_dump_json(self): def test_safe_dump_json(self):
class A(object): class A(object):
pass pass