Allow for redacting of environment table values (#431)

* Add untested change

* Add tests

* Add documentation

* Add the changelog entry

* remove debug code from test

* Change wording of documentation
This commit is contained in:
Gleb Nikonorov 2021-01-22 22:26:17 -05:00 committed by Jim Brännlund
parent b7fd443917
commit 256688a167
5 changed files with 92 additions and 2 deletions

View File

@ -17,6 +17,10 @@ Version History
* Thanks to `@gnikonorov <https://github.com/gnikonorov>`_ for the PR * Thanks to `@gnikonorov <https://github.com/gnikonorov>`_ for the PR
* Implement :code:`environment_table_redact_list` to allow for redaction of environment table values. (`#233 <https://github.com/pytest-dev/pytest-html/issues/233>`_)
* Thanks to `@fenchu <https://github.com/fenchu>`_ for reporting and `@gnikonorov <https://github.com/gnikonorov>`_ for the PR
3.1.1 (2020-12-13) 3.1.1 (2020-12-13)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~

View File

@ -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, 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. and thus not pick up your change.
The generated table will be sorted alphabetically unless the metadata is a The generated table will be sorted alphabetically unless the metadata is a :code:`collections.OrderedDict`.
: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 Additional summary information
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -2,6 +2,7 @@ import bisect
import datetime import datetime
import json import json
import os import os
import re
import time import time
from collections import defaultdict from collections import defaultdict
from collections import OrderedDict from collections import OrderedDict
@ -226,6 +227,10 @@ class HTMLReport:
for key in keys: for key in keys:
value = metadata[key] 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"): if isinstance(value, str) and value.startswith("http"):
value = html.a(value, href=value, target="_blank") value = html.a(value, href=value, target="_blank")
elif isinstance(value, (list, tuple, set)): elif isinstance(value, (list, tuple, set)):
@ -239,6 +244,14 @@ class HTMLReport:
environment.append(html.table(rows, id="environment")) environment.append(html.table(rows, id="environment"))
return 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): def _save_report(self, report_content):
dir_name = os.path.dirname(self.logfile) dir_name = os.path.dirname(self.logfile)
assets_dir = os.path.join(dir_name, "assets") assets_dir = os.path.join(dir_name, "assets")

View File

@ -53,6 +53,12 @@ def pytest_addoption(parser):
help="set the maximum filename length for assets " help="set the maximum filename length for assets "
"attached to the html report.", "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): def pytest_configure(config):

View File

@ -1209,3 +1209,58 @@ class TestHTML:
assert extra_log_div_regex.search(html) is not None assert extra_log_div_regex.search(html) is not None
else: else:
assert extra_log_div_regex.search(html) is None 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"<tr>\n.*<td>{variable}</td>\n.*<td>{variable_value}</td></tr>"
)
assert variable_value_regex.search(html) is not None