diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6fb2d697..8ed660b9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,23 @@ # Release History +## 2.4.9 (2019-12-29) + +**Added** + +- test: add unittest for cli + +**Changed** + +- change: html report name defaults to be in UTC ISO 8601 format + +**Fixed** + +- fix: display validators in report when validate raised exception +- fix: eval validator python script before validating +- fix: do not strip string content when preparing lazy data +- fix: catch ApiNotFound exception when loading testcases +- fix: print exception string with exception stage + ## 2.4.8 (2019-12-25) **Added** diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 1766552f..13d3adf4 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.4.8" +__version__ = "2.4.9" __description__ = "One-stop solution for HTTP(S) testing." __all__ = ["__version__", "__description__"] diff --git a/httprunner/cli.py b/httprunner/cli.py index 50e365d5..8258ba4f 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -105,6 +105,7 @@ def main(): err_code |= (0 if summary and summary["success"] else 1) except Exception as ex: color_print("!!!!!!!!!! exception stage: {} !!!!!!!!!!".format(runner.exception_stage), "YELLOW") + color_print(str(ex), "RED") capture_exception(ex) err_code = 1 diff --git a/httprunner/compat.py b/httprunner/compat.py index e7e92ca3..aa2a16ec 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -47,6 +47,7 @@ if is_py2: integer_types = (int, long) FileNotFoundError = IOError + import StringIO as io elif is_py3: builtin_str = str @@ -57,3 +58,4 @@ elif is_py3: integer_types = (int,) FileNotFoundError = FileNotFoundError + import io as io diff --git a/httprunner/loader/buildup.py b/httprunner/loader/buildup.py index e92739ea..b6839b93 100644 --- a/httprunner/loader/buildup.py +++ b/httprunner/loader/buildup.py @@ -480,6 +480,8 @@ def load_cases(path, dot_env_path=None): loaded_content = None try: loaded_content = load_test_file(path) + except exceptions.ApiNotFound as ex: + logger.log_warning("Invalid api reference in {}: {}".format(path, ex)) except exceptions.FileFormatError: logger.log_warning("Invalid test file format: {}".format(path)) diff --git a/httprunner/parser.py b/httprunner/parser.py index d5ef8214..8fc49d8e 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -776,7 +776,6 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None, functions_mapping = functions_mapping or {} check_variables_set = check_variables_set or set() - content = content.strip() content = LazyString(content, functions_mapping, check_variables_set, cached) return content diff --git a/httprunner/report/html/gen_report.py b/httprunner/report/html/gen_report.py index 265acdea..303abd63 100644 --- a/httprunner/report/html/gen_report.py +++ b/httprunner/report/html/gen_report.py @@ -33,15 +33,16 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file= logger.log_info("Start to render Html report ...") - start_at_timestamp = int(summary["time"]["start_at"]) - summary["time"]["start_datetime"] = datetime.fromtimestamp(start_at_timestamp).strftime('%Y-%m-%d %H:%M:%S') + start_at_timestamp = summary["time"]["start_at"] + utc_time_iso_8601_str = datetime.utcfromtimestamp(start_at_timestamp).isoformat() + summary["time"]["start_datetime"] = utc_time_iso_8601_str if report_file: report_dir = os.path.dirname(report_file) report_file_name = os.path.basename(report_file) else: report_dir = report_dir or os.path.join(os.getcwd(), "reports") - report_file_name = "{}.html".format(int(start_at_timestamp * 1000)) + report_file_name = "{}.html".format(utc_time_iso_8601_str) if not os.path.isdir(report_dir): os.makedirs(report_dir) diff --git a/httprunner/runner.py b/httprunner/runner.py index 86804722..b2d1b2f3 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -303,8 +303,8 @@ class Runner(object): except exceptions.ValidationFailure: log_req_resp_details() raise - - return validator.validation_results + finally: + self.validation_results = validator.validation_results def _run_testcase(self, testcase_dict): """ run single testcase. @@ -382,9 +382,9 @@ class Runner(object): self._run_testcase(test_dict) else: # api - validation_results = {} + self.validation_results = {} try: - validation_results = self._run_test(test_dict) + self._run_test(test_dict) except Exception: # log exception request_type and name for locust stat self.exception_request_type = test_dict["request"]["method"] @@ -393,7 +393,7 @@ class Runner(object): finally: # get request/response data and validate results self.meta_datas = getattr(self.http_client_session, "meta_data", {}) - self.meta_datas["validators"] = validation_results + self.meta_datas["validators"] = self.validation_results def export_variables(self, output_variables_list): """ export current testcase variables diff --git a/httprunner/validator.py b/httprunner/validator.py index 3a821047..8dad5ac6 100644 --- a/httprunner/validator.py +++ b/httprunner/validator.py @@ -124,7 +124,8 @@ except Exception as ex: for validator in validators: if isinstance(validator, dict) and validator.get("type") == "python_script": - validator_dict, ex = self.validate_script(validator["script"]) + script = self.session_context.eval_content(validator["script"]) + validator_dict, ex = self.validate_script(script) if ex: validate_pass = False failures.append(ex) diff --git a/pyproject.toml b/pyproject.toml index e0b6f378..2dfa8519 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "2.4.8" +version = "2.4.9" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..abf2a714 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,37 @@ +import sys +import unittest + +from httprunner.cli import main +from httprunner.compat import io + + +class TestCli(unittest.TestCase): + + def setUp(self): + self.captured_output = io.StringIO() + sys.stdout = self.captured_output + + def tearDown(self): + sys.stdout = sys.__stdout__ # Reset redirect. + + def test_show_version(self): + sys.argv = ["hrun", "-V"] + + with self.assertRaises(SystemExit) as cm: + main() + + self.assertEqual(cm.exception.code, 0) + + from httprunner import __version__ + self.assertIn(__version__, self.captured_output.getvalue().strip()) + + def test_show_help(self): + sys.argv = ["hrun", "-h"] + + with self.assertRaises(SystemExit) as cm: + main() + + self.assertEqual(cm.exception.code, 0) + + from httprunner import __description__ + self.assertIn(__description__, self.captured_output.getvalue().strip())