diff --git a/docs/changelog.rst b/docs/changelog.rst index b9a929bf..e1f52a22 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -17,6 +17,10 @@ Version History * Thanks to `@gnikonorov `_ for the PR +* Implement :code:`environment_table_redact_list` to allow for redaction of environment table values. (`#233 `_) + + * Thanks to `@fenchu `_ for reporting and `@gnikonorov `_ for the PR + 3.1.1 (2020-12-13) ~~~~~~~~~~~~~~~~~~ diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 5aedab57..57649250 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -80,8 +80,20 @@ Note that in the above example `@pytest.hookimpl(tryfirst=True)`_ is important, If this line is omitted, then the *Environment* table will **not** be updated since the :code:`pytest_sessionfinish` of the plugins will execute first, and thus not pick up your change. -The generated table will be sorted alphabetically unless the metadata is a -:code:`collections.OrderedDict`. +The generated table will be sorted alphabetically unless the metadata is a :code:`collections.OrderedDict`. + +It is possible to redact variables from the environment table. Redacted variables will have their names displayed, but their values grayed out. +This can be achieved by setting :code:`environment_table_redact_list` in your INI configuration file (e.g.: :code:`pytest.ini`). +:code:`environment_table_redact_list` is a :code:`linelist` of regexes. Any environment table variable that matches a regex in this list has its value redacted. + +For example, the following will redact all environment table variables that match the regexes :code:`^foo$`, :code:`.*redact.*`, or :code:`bar`: + +.. code-block:: ini + + [pytest] + environment_table_redact_list = ^foo$ + .*redact.* + bar Additional summary information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/pytest_html/html_report.py b/src/pytest_html/html_report.py index e1aa3973..66e10f07 100644 --- a/src/pytest_html/html_report.py +++ b/src/pytest_html/html_report.py @@ -2,6 +2,7 @@ import datetime import json import os +import re import time from collections import defaultdict from collections import OrderedDict @@ -226,6 +227,10 @@ def _generate_environment(self, config): for key in keys: value = metadata[key] + if self._is_redactable_environment_variable(key, config): + black_box_ascii_value = 0x2593 + value = "".join(chr(black_box_ascii_value) for char in str(value)) + if isinstance(value, str) and value.startswith("http"): value = html.a(value, href=value, target="_blank") elif isinstance(value, (list, tuple, set)): @@ -239,6 +244,14 @@ def _generate_environment(self, config): environment.append(html.table(rows, id="environment")) return environment + def _is_redactable_environment_variable(self, environment_variable, config): + redactable_regexes = config.getini("environment_table_redact_list") + for redactable_regex in redactable_regexes: + if re.match(redactable_regex, environment_variable): + return True + + return False + def _save_report(self, report_content): dir_name = os.path.dirname(self.logfile) assets_dir = os.path.join(dir_name, "assets") diff --git a/src/pytest_html/plugin.py b/src/pytest_html/plugin.py index 6a0ef7a4..0034da19 100644 --- a/src/pytest_html/plugin.py +++ b/src/pytest_html/plugin.py @@ -53,6 +53,12 @@ def pytest_addoption(parser): help="set the maximum filename length for assets " "attached to the html report.", ) + parser.addini( + "environment_table_redact_list", + type="linelist", + help="A list of regexes corresponding to environment " + "table variables whose values should be redacted from the report", + ) def pytest_configure(config): diff --git a/testing/test_pytest_html.py b/testing/test_pytest_html.py index b2d23af9..69341caf 100644 --- a/testing/test_pytest_html.py +++ b/testing/test_pytest_html.py @@ -1209,3 +1209,58 @@ def test_show_capture_no(): assert extra_log_div_regex.search(html) is not None else: assert extra_log_div_regex.search(html) is None + + def test_environment_table_redact_list(self, testdir): + testdir.makeini( + """ + [pytest] + environment_table_redact_list = ^foo$ + .*redact.* + bar + """ + ) + + testdir.makeconftest( + """ + def pytest_configure(config): + config._metadata["foo"] = "will not appear a" + config._metadata["afoo"] = "will appear" + config._metadata["foos"] = "will appear" + config._metadata["redact"] = "will not appear ab" + config._metadata["will_redact"] = "will not appear abc" + config._metadata["redacted_item"] = "will not appear abcd" + config._metadata["unrelated_item"] = "will appear" + config._metadata["bar"] = "will not appear abcde" + config._metadata["bars"] = "will not appear abcdef" + """ + ) + + testdir.makepyfile( + """ + def test_pass(): + assert True + """ + ) + + result, html = run(testdir) + assert result.ret == 0 + assert_results(html) + + black_box_ascii_value = 0x2593 + expected_environment_values = { + "foo": "".join(chr(black_box_ascii_value) for value in range(17)), + "afoo": "will appear", + "foos": "will appear", + "redact": "".join(chr(black_box_ascii_value) for value in range(18)), + "will_redact": "".join(chr(black_box_ascii_value) for value in range(19)), + "redacted_item": "".join(chr(black_box_ascii_value) for value in range(20)), + "unrelated_item": "will appear", + "bar": "".join(chr(black_box_ascii_value) for value in range(21)), + "bars": "".join(chr(black_box_ascii_value) for value in range(22)), + } + for variable in expected_environment_values: + variable_value = expected_environment_values[variable] + variable_value_regex = re.compile( + f"\n.*{variable}\n.*{variable_value}" + ) + assert variable_value_regex.search(html) is not None