From 1b1918394bfae213898b4f2257a63efe1f348053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Br=C3=A4nnlund?= Date: Wed, 10 Jul 2019 22:07:46 +0200 Subject: [PATCH 1/4] Migrate to 'black' for consistent styling and formatting --- .gitignore | 2 +- Pipfile | 13 + pytest_html/__init__.py | 4 +- pytest_html/extras.py | 43 +-- pytest_html/plugin.py | 452 +++++++++++++++------------ setup.py | 64 ++-- testing/test_pytest_html.py | 593 +++++++++++++++++++++--------------- tox.ini | 9 +- 8 files changed, 683 insertions(+), 497 deletions(-) create mode 100644 Pipfile diff --git a/.gitignore b/.gitignore index b89f6565..023c4e99 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ local.properties ##Tests JS node_modules/ -Pipfile +Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..53fddcaa --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +pytest = "*" +tox = "*" +flake8 = "*" +black = "*" + +[packages] +pytest-html = {editable = true,path = "."} diff --git a/pytest_html/__init__.py b/pytest_html/__init__.py index 02c2adc2..2fd250de 100644 --- a/pytest_html/__init__.py +++ b/pytest_html/__init__.py @@ -5,6 +5,6 @@ __version__ = get_distribution(__name__).version except DistributionNotFound: # package is not installed - __version__ = 'unknown' + __version__ = "unknown" -__pypi_url__ = 'https://pypi.python.org/pypi/pytest-html' +__pypi_url__ = "https://pypi.python.org/pypi/pytest-html" diff --git a/pytest_html/extras.py b/pytest_html/extras.py index 8f25e9e3..28a26518 100644 --- a/pytest_html/extras.py +++ b/pytest_html/extras.py @@ -2,45 +2,50 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -FORMAT_HTML = 'html' -FORMAT_IMAGE = 'image' -FORMAT_JSON = 'json' -FORMAT_TEXT = 'text' -FORMAT_URL = 'url' +FORMAT_HTML = "html" +FORMAT_IMAGE = "image" +FORMAT_JSON = "json" +FORMAT_TEXT = "text" +FORMAT_URL = "url" def extra(content, format, name=None, mime_type=None, extension=None): - return {'name': name, 'format': format, 'content': content, - 'mime_type': mime_type, 'extension': extension} + return { + "name": name, + "format": format, + "content": content, + "mime_type": mime_type, + "extension": extension, + } def html(content): return extra(content, FORMAT_HTML) -def image(content, name='Image', mime_type='image/png', extension='png'): +def image(content, name="Image", mime_type="image/png", extension="png"): return extra(content, FORMAT_IMAGE, name, mime_type, extension) -def png(content, name='Image'): - return image(content, name, mime_type='image/png', extension='png') +def png(content, name="Image"): + return image(content, name, mime_type="image/png", extension="png") -def jpg(content, name='Image'): - return image(content, name, mime_type='image/jpeg', extension='jpg') +def jpg(content, name="Image"): + return image(content, name, mime_type="image/jpeg", extension="jpg") -def svg(content, name='Image'): - return image(content, name, mime_type='image/svg+xml', extension='svg') +def svg(content, name="Image"): + return image(content, name, mime_type="image/svg+xml", extension="svg") -def json(content, name='JSON'): - return extra(content, FORMAT_JSON, name, 'application/json', 'json') +def json(content, name="JSON"): + return extra(content, FORMAT_JSON, name, "application/json", "json") -def text(content, name='Text'): - return extra(content, FORMAT_TEXT, name, 'text/plain', 'txt') +def text(content, name="Text"): + return extra(content, FORMAT_TEXT, name, "text/plain", "txt") -def url(content, name='URL'): +def url(content, name="URL"): return extra(content, FORMAT_URL, name) diff --git a/pytest_html/plugin.py b/pytest_html/plugin.py index 3903c1cf..074d2f71 100644 --- a/pytest_html/plugin.py +++ b/pytest_html/plugin.py @@ -19,6 +19,7 @@ try: from ansi2html import Ansi2HTMLConverter, style + ANSI = True except ImportError: # ansi2html is not installed @@ -42,49 +43,62 @@ def pytest_addhooks(pluginmanager): from . import hooks + pluginmanager.add_hookspecs(hooks) def pytest_addoption(parser): - group = parser.getgroup('terminal reporting') - group.addoption('--html', action='store', dest='htmlpath', - metavar='path', default=None, - help='create html report file at given path.') - group.addoption('--self-contained-html', action='store_true', - help='create a self-contained html file containing all ' - 'necessary styles, scripts, and images - this means ' - 'that the report may not render or function where CSP ' - 'restrictions are in place (see ' - 'https://developer.mozilla.org/docs/Web/Security/CSP)') - group.addoption('--css', action='append', metavar='path', default=[], - help='append given css file content to report style file.') + group = parser.getgroup("terminal reporting") + group.addoption( + "--html", + action="store", + dest="htmlpath", + metavar="path", + default=None, + help="create html report file at given path.", + ) + group.addoption( + "--self-contained-html", + action="store_true", + help="create a self-contained html file containing all " + "necessary styles, scripts, and images - this means " + "that the report may not render or function where CSP " + "restrictions are in place (see " + "https://developer.mozilla.org/docs/Web/Security/CSP)", + ) + group.addoption( + "--css", + action="append", + metavar="path", + default=[], + help="append given css file content to report style file.", + ) def pytest_configure(config): - htmlpath = config.getoption('htmlpath') + htmlpath = config.getoption("htmlpath") if htmlpath: - for csspath in config.getoption('css'): + for csspath in config.getoption("css"): open(csspath) - if not hasattr(config, 'slaveinput'): + if not hasattr(config, "slaveinput"): # prevent opening htmlpath on slave nodes (xdist) config._html = HTMLReport(htmlpath, config) config.pluginmanager.register(config._html) def pytest_unconfigure(config): - html = getattr(config, '_html', None) + html = getattr(config, "_html", None) if html: del config._html config.pluginmanager.unregister(html) -def data_uri(content, mime_type='text/plain', charset='utf-8'): - data = b64encode(content.encode(charset)).decode('ascii') - return 'data:{0};charset={1};base64,{2}'.format(mime_type, charset, data) +def data_uri(content, mime_type="text/plain", charset="utf-8"): + data = b64encode(content.encode(charset)).decode("ascii") + return "data:{0};charset={1};base64,{2}".format(mime_type, charset, data) class HTMLReport(object): - def __init__(self, logfile, config): logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.abspath(logfile) @@ -93,171 +107,180 @@ def __init__(self, logfile, config): self.errors = self.failed = 0 self.passed = self.skipped = 0 self.xfailed = self.xpassed = 0 - has_rerun = config.pluginmanager.hasplugin('rerunfailures') + has_rerun = config.pluginmanager.hasplugin("rerunfailures") self.rerun = 0 if has_rerun else None - self.self_contained = config.getoption('self_contained_html') + self.self_contained = config.getoption("self_contained_html") self.config = config class TestResult: - def __init__(self, outcome, report, logfile, config): self.test_id = report.nodeid - if getattr(report, 'when', 'call') != 'call': - self.test_id = '::'.join([report.nodeid, report.when]) - self.time = getattr(report, 'duration', 0.0) + if getattr(report, "when", "call") != "call": + self.test_id = "::".join([report.nodeid, report.when]) + self.time = getattr(report, "duration", 0.0) self.outcome = outcome self.additional_html = [] self.links_html = [] - self.self_contained = config.getoption('self_contained_html') + self.self_contained = config.getoption("self_contained_html") self.logfile = logfile self.config = config self.row_table = self.row_extra = None - test_index = hasattr(report, 'rerun') and report.rerun + 1 or 0 + test_index = hasattr(report, "rerun") and report.rerun + 1 or 0 - for extra_index, extra in enumerate(getattr(report, 'extra', [])): + for extra_index, extra in enumerate(getattr(report, "extra", [])): self.append_extra_html(extra, extra_index, test_index) self.append_log_html(report, self.additional_html) cells = [ - html.td(self.outcome, class_='col-result'), - html.td(self.test_id, class_='col-name'), - html.td('{0:.2f}'.format(self.time), class_='col-duration'), - html.td(self.links_html, class_='col-links')] + html.td(self.outcome, class_="col-result"), + html.td(self.test_id, class_="col-name"), + html.td("{0:.2f}".format(self.time), class_="col-duration"), + html.td(self.links_html, class_="col-links"), + ] - self.config.hook.pytest_html_results_table_row( - report=report, cells=cells) + self.config.hook.pytest_html_results_table_row(report=report, cells=cells) self.config.hook.pytest_html_results_table_html( - report=report, data=self.additional_html) + report=report, data=self.additional_html + ) if len(cells) > 0: self.row_table = html.tr(cells) - self.row_extra = html.tr(html.td(self.additional_html, - class_='extra', - colspan=len(cells))) + self.row_extra = html.tr( + html.td(self.additional_html, class_="extra", colspan=len(cells)) + ) def __lt__(self, other): - order = ('Error', 'Failed', 'Rerun', 'XFailed', - 'XPassed', 'Skipped', 'Passed') + order = ( + "Error", + "Failed", + "Rerun", + "XFailed", + "XPassed", + "Skipped", + "Passed", + ) return order.index(self.outcome) < order.index(other.outcome) - def create_asset(self, content, extra_index, - test_index, file_extension, mode='w'): + def create_asset( + self, content, extra_index, test_index, file_extension, mode="w" + ): - hash_key = ''.join([self.test_id, str(extra_index), - str(test_index)]) + hash_key = "".join([self.test_id, str(extra_index), str(test_index)]) hash_generator = hashlib.md5() - hash_generator.update(hash_key.encode('utf-8')) + hash_generator.update(hash_key.encode("utf-8")) hex_digest = hash_generator.hexdigest() # 255 is the common max filename length on various filesystems, # we subtract hash length, file extension length and 2 more # characters for the underscore and dot max_length = 255 - len(hex_digest) - len(file_extension) - 2 - asset_file_name = '{0}_{1}.{2}'.format(hash_key[:max_length], - hex_digest, - file_extension) - asset_path = os.path.join(os.path.dirname(self.logfile), - 'assets', asset_file_name) + asset_file_name = "{0}_{1}.{2}".format( + hash_key[:max_length], hex_digest, file_extension + ) + asset_path = os.path.join( + os.path.dirname(self.logfile), "assets", asset_file_name + ) if not os.path.exists(os.path.dirname(asset_path)): os.makedirs(os.path.dirname(asset_path)) - relative_path = '{0}/{1}'.format('assets', asset_file_name) + relative_path = "{0}/{1}".format("assets", asset_file_name) - kwargs = {'encoding': 'utf-8'} if 'b' not in mode else {} + kwargs = {"encoding": "utf-8"} if "b" not in mode else {} with open(asset_path, mode, **kwargs) as f: f.write(content) return relative_path def append_extra_html(self, extra, extra_index, test_index): href = None - if extra.get('format') == extras.FORMAT_IMAGE: - content = extra.get('content') + if extra.get("format") == extras.FORMAT_IMAGE: + content = extra.get("content") try: - is_uri_or_path = (content.startswith(('file', 'http')) or - isfile(content)) + is_uri_or_path = content.startswith(("file", "http")) or isfile( + content + ) except ValueError: # On Windows, os.path.isfile throws this exception when # passed a b64 encoded image. is_uri_or_path = False if is_uri_or_path: if self.self_contained: - warnings.warn('Self-contained HTML report ' - 'includes link to external ' - 'resource: {}'.format(content)) + warnings.warn( + "Self-contained HTML report " + "includes link to external " + "resource: {}".format(content) + ) html_div = html.a(html.img(src=content), href=content) elif self.self_contained: - src = 'data:{0};base64,{1}'.format( - extra.get('mime_type'), - content) + src = "data:{0};base64,{1}".format(extra.get("mime_type"), content) html_div = html.img(src=src) else: if PY3: - content = b64decode(content.encode('utf-8')) + content = b64decode(content.encode("utf-8")) else: content = b64decode(content) href = src = self.create_asset( - content, extra_index, test_index, - extra.get('extension'), 'wb') + content, extra_index, test_index, extra.get("extension"), "wb" + ) html_div = html.a(html.img(src=src), href=href) - self.additional_html.append(html.div(html_div, class_='image')) + self.additional_html.append(html.div(html_div, class_="image")) - elif extra.get('format') == extras.FORMAT_HTML: - self.additional_html.append(html.div( - raw(extra.get('content')))) + elif extra.get("format") == extras.FORMAT_HTML: + self.additional_html.append(html.div(raw(extra.get("content")))) - elif extra.get('format') == extras.FORMAT_JSON: - content = json.dumps(extra.get('content')) + elif extra.get("format") == extras.FORMAT_JSON: + content = json.dumps(extra.get("content")) if self.self_contained: - href = data_uri(content, - mime_type=extra.get('mime_type')) + href = data_uri(content, mime_type=extra.get("mime_type")) else: - href = self.create_asset(content, extra_index, - test_index, - extra.get('extension')) + href = self.create_asset( + content, extra_index, test_index, extra.get("extension") + ) - elif extra.get('format') == extras.FORMAT_TEXT: - content = extra.get('content') + elif extra.get("format") == extras.FORMAT_TEXT: + content = extra.get("content") if isinstance(content, bytes): - content = content.decode('utf-8') + content = content.decode("utf-8") if self.self_contained: href = data_uri(content) else: - href = self.create_asset(content, extra_index, - test_index, - extra.get('extension')) + href = self.create_asset( + content, extra_index, test_index, extra.get("extension") + ) - elif extra.get('format') == extras.FORMAT_URL: - href = extra.get('content') + elif extra.get("format") == extras.FORMAT_URL: + href = extra.get("content") if href is not None: - self.links_html.append(html.a( - extra.get('name'), - class_=extra.get('format'), - href=href, - target='_blank')) - self.links_html.append(' ') + self.links_html.append( + html.a( + extra.get("name"), + class_=extra.get("format"), + href=href, + target="_blank", + ) + ) + self.links_html.append(" ") def append_log_html(self, report, additional_html): - log = html.div(class_='log') + log = html.div(class_="log") if report.longrepr: for line in report.longreprtext.splitlines(): - separator = line.startswith('_ ' * 10) + separator = line.startswith("_ " * 10) if separator: log.append(line[:80]) else: exception = line.startswith("E ") if exception: - log.append(html.span(raw(escape(line)), - class_='error')) + log.append(html.span(raw(escape(line)), class_="error")) else: log.append(raw(escape(line))) log.append(html.br()) for section in report.sections: header, content = map(escape, section) - log.append(' {0} '.format(header).center(80, '-')) + log.append(" {0} ".format(header).center(80, "-")) log.append(html.br()) if ANSI: converter = Ansi2HTMLConverter(inline=False, escaped=False) @@ -265,8 +288,8 @@ def append_log_html(self, report, additional_html): log.append(raw(content)) if len(log) == 0: - log = html.div(class_='empty log') - log.append('No log output captured.') + log = html.div(class_="empty log") + log.append("No log output captured.") additional_html.append(log) def _appendrow(self, outcome, report): @@ -276,45 +299,46 @@ def _appendrow(self, outcome, report): self.results.insert(index, result) tbody = html.tbody( result.row_table, - class_='{0} results-table-row'.format(result.outcome.lower())) + class_="{0} results-table-row".format(result.outcome.lower()), + ) if result.row_extra is not None: tbody.append(result.row_extra) self.test_logs.insert(index, tbody) def append_passed(self, report): - if report.when == 'call': + if report.when == "call": if hasattr(report, "wasxfail"): self.xpassed += 1 - self._appendrow('XPassed', report) + self._appendrow("XPassed", report) else: self.passed += 1 - self._appendrow('Passed', report) + self._appendrow("Passed", report) def append_failed(self, report): - if getattr(report, 'when', None) == "call": + if getattr(report, "when", None) == "call": if hasattr(report, "wasxfail"): # pytest < 3.0 marked xpasses as failures self.xpassed += 1 - self._appendrow('XPassed', report) + self._appendrow("XPassed", report) else: self.failed += 1 - self._appendrow('Failed', report) + self._appendrow("Failed", report) else: self.errors += 1 - self._appendrow('Error', report) + self._appendrow("Error", report) def append_skipped(self, report): if hasattr(report, "wasxfail"): self.xfailed += 1 - self._appendrow('XFailed', report) + self._appendrow("XFailed", report) else: self.skipped += 1 - self._appendrow('Skipped', report) + self._appendrow("Skipped", report) def append_other(self, report): # For now, the only "other" the plugin give support is rerun self.rerun += 1 - self._appendrow('Rerun', report) + self._appendrow("Rerun", report) def _generate_report(self, session): suite_stop_time = time.time() @@ -323,42 +347,42 @@ def _generate_report(self, session): generated = datetime.datetime.now() self.style_css = pkg_resources.resource_string( - __name__, os.path.join('resources', 'style.css')) + __name__, os.path.join("resources", "style.css") + ) if PY3: - self.style_css = self.style_css.decode('utf-8') + self.style_css = self.style_css.decode("utf-8") if ANSI: ansi_css = [ - '\n/******************************', - ' * ANSI2HTML STYLES', - ' ******************************/\n'] + "\n/******************************", + " * ANSI2HTML STYLES", + " ******************************/\n", + ] ansi_css.extend([str(r) for r in style.get_styles()]) - self.style_css += '\n'.join(ansi_css) + self.style_css += "\n".join(ansi_css) # Add user-provided CSS - for path in self.config.getoption('css'): - self.style_css += '\n/******************************' - self.style_css += '\n * CUSTOM CSS' - self.style_css += '\n * {}'.format(path) - self.style_css += '\n ******************************/\n\n' - with open(path, 'r') as f: + for path in self.config.getoption("css"): + self.style_css += "\n/******************************" + self.style_css += "\n * CUSTOM CSS" + self.style_css += "\n * {}".format(path) + self.style_css += "\n ******************************/\n\n" + with open(path, "r") as f: self.style_css += f.read() - css_href = '{0}/{1}'.format('assets', 'style.css') - html_css = html.link(href=css_href, rel='stylesheet', - type='text/css') + css_href = "{0}/{1}".format("assets", "style.css") + html_css = html.link(href=css_href, rel="stylesheet", type="text/css") if self.self_contained: html_css = html.style(raw(self.style_css)) head = html.head( - html.meta(charset='utf-8'), - html.title('Test Report'), - html_css) + html.meta(charset="utf-8"), html.title("Test Report"), html_css + ) class Outcome: - - def __init__(self, outcome, total=0, label=None, - test_result=None, class_html=None): + def __init__( + self, outcome, total=0, label=None, test_result=None, class_html=None + ): self.outcome = outcome self.label = label or outcome self.class_html = class_html or outcome @@ -369,108 +393,130 @@ def __init__(self, outcome, total=0, label=None, self.generate_summary_item() def generate_checkbox(self): - checkbox_kwargs = {'data-test-result': - self.test_result.lower()} + checkbox_kwargs = {"data-test-result": self.test_result.lower()} if self.total == 0: - checkbox_kwargs['disabled'] = 'true' - - self.checkbox = html.input(type='checkbox', - checked='true', - onChange='filter_table(this)', - name='filter_checkbox', - class_='filter', - hidden='true', - **checkbox_kwargs) + checkbox_kwargs["disabled"] = "true" + + self.checkbox = html.input( + type="checkbox", + checked="true", + onChange="filter_table(this)", + name="filter_checkbox", + class_="filter", + hidden="true", + **checkbox_kwargs + ) def generate_summary_item(self): - self.summary_item = html.span('{0} {1}'. - format(self.total, self.label), - class_=self.class_html) - - outcomes = [Outcome('passed', self.passed), - Outcome('skipped', self.skipped), - Outcome('failed', self.failed), - Outcome('error', self.errors, label='errors'), - Outcome('xfailed', self.xfailed, - label='expected failures'), - Outcome('xpassed', self.xpassed, - label='unexpected passes')] + self.summary_item = html.span( + "{0} {1}".format(self.total, self.label), class_=self.class_html + ) + + outcomes = [ + Outcome("passed", self.passed), + Outcome("skipped", self.skipped), + Outcome("failed", self.failed), + Outcome("error", self.errors, label="errors"), + Outcome("xfailed", self.xfailed, label="expected failures"), + Outcome("xpassed", self.xpassed, label="unexpected passes"), + ] if self.rerun is not None: - outcomes.append(Outcome('rerun', self.rerun)) - - summary = [html.p( - '{0} tests ran in {1:.2f} seconds. '.format( - numtests, suite_time_delta)), - html.p('(Un)check the boxes to filter the results.', - class_='filter', - hidden='true')] + outcomes.append(Outcome("rerun", self.rerun)) + + summary = [ + html.p( + "{0} tests ran in {1:.2f} seconds. ".format(numtests, suite_time_delta) + ), + html.p( + "(Un)check the boxes to filter the results.", + class_="filter", + hidden="true", + ), + ] for i, outcome in enumerate(outcomes, start=1): summary.append(outcome.checkbox) summary.append(outcome.summary_item) if i < len(outcomes): - summary.append(', ') + summary.append(", ") cells = [ - html.th('Result', - class_='sortable result initial-sort', - col='result'), - html.th('Test', class_='sortable', col='name'), - html.th('Duration', class_='sortable numeric', col='duration'), - html.th('Links')] + html.th("Result", class_="sortable result initial-sort", col="result"), + html.th("Test", class_="sortable", col="name"), + html.th("Duration", class_="sortable numeric", col="duration"), + html.th("Links"), + ] session.config.hook.pytest_html_results_table_header(cells=cells) - results = [html.h2('Results'), html.table([html.thead( - html.tr(cells), - html.tr([ - html.th('No results found. Try to check the filters', - colspan=len(cells))], - id='not-found-message', hidden='true'), - id='results-table-head'), - self.test_logs], id='results-table')] + results = [ + html.h2("Results"), + html.table( + [ + html.thead( + html.tr(cells), + html.tr( + [ + html.th( + "No results found. Try to check the filters", + colspan=len(cells), + ) + ], + id="not-found-message", + hidden="true", + ), + id="results-table-head", + ), + self.test_logs, + ], + id="results-table", + ), + ] main_js = pkg_resources.resource_string( - __name__, os.path.join('resources', 'main.js')) + __name__, os.path.join("resources", "main.js") + ) if PY3: - main_js = main_js.decode('utf-8') + main_js = main_js.decode("utf-8") body = html.body( html.script(raw(main_js)), html.h1(os.path.basename(self.logfile)), - html.p('Report generated on {0} at {1} by '.format( - generated.strftime('%d-%b-%Y'), - generated.strftime('%H:%M:%S')), - html.a('pytest-html', href=__pypi_url__), - ' v{0}'.format(__version__)), - onLoad='init()') + html.p( + "Report generated on {0} at {1} by ".format( + generated.strftime("%d-%b-%Y"), generated.strftime("%H:%M:%S") + ), + html.a("pytest-html", href=__pypi_url__), + " v{0}".format(__version__), + ), + onLoad="init()", + ) body.extend(self._generate_environment(session.config)) summary_prefix, summary_postfix = [], [] session.config.hook.pytest_html_results_summary( - prefix=summary_prefix, summary=summary, postfix=summary_postfix) - body.extend([html.h2('Summary')] + summary_prefix - + summary + summary_postfix) + prefix=summary_prefix, summary=summary, postfix=summary_postfix + ) + body.extend([html.h2("Summary")] + summary_prefix + summary + summary_postfix) body.extend(results) doc = html.html(head, body) - unicode_doc = u'\n{0}'.format(doc.unicode(indent=2)) + unicode_doc = u"\n{0}".format(doc.unicode(indent=2)) if PY3: # Fix encoding issues, e.g. with surrogates - unicode_doc = unicode_doc.encode('utf-8', - errors='xmlcharrefreplace') - unicode_doc = unicode_doc.decode('utf-8') + unicode_doc = unicode_doc.encode("utf-8", errors="xmlcharrefreplace") + unicode_doc = unicode_doc.decode("utf-8") return unicode_doc def _generate_environment(self, config): - if not hasattr(config, '_metadata') or config._metadata is None: + if not hasattr(config, "_metadata") or config._metadata is None: return [] metadata = config._metadata - environment = [html.h2('Environment')] + environment = [html.h2("Environment")] rows = [] keys = [k for k in metadata.keys()] @@ -479,29 +525,29 @@ def _generate_environment(self, config): for key in keys: value = metadata[key] - if isinstance(value, basestring) and value.startswith('http'): - value = html.a(value, href=value, target='_blank') + if isinstance(value, basestring) and value.startswith("http"): + value = html.a(value, href=value, target="_blank") elif isinstance(value, (list, tuple, set)): - value = ', '.join((str(i) for i in value)) + value = ", ".join((str(i) for i in value)) rows.append(html.tr(html.td(key), html.td(value))) - environment.append(html.table(rows, id='environment')) + environment.append(html.table(rows, id="environment")) return environment def _save_report(self, report_content): dir_name = os.path.dirname(self.logfile) - assets_dir = os.path.join(dir_name, 'assets') + assets_dir = os.path.join(dir_name, "assets") if not os.path.exists(dir_name): os.makedirs(dir_name) if not self.self_contained and not os.path.exists(assets_dir): os.makedirs(assets_dir) - with open(self.logfile, 'w', encoding='utf-8') as f: + with open(self.logfile, "w", encoding="utf-8") as f: f.write(report_content) if not self.self_contained: - style_path = os.path.join(assets_dir, 'style.css') - with open(style_path, 'w', encoding='utf-8') as f: + style_path = os.path.join(assets_dir, "style.css") + with open(style_path, "w", encoding="utf-8") as f: f.write(self.style_css) def pytest_runtest_logreport(self, report): @@ -527,5 +573,5 @@ def pytest_sessionfinish(self, session): def pytest_terminal_summary(self, terminalreporter): terminalreporter.write_sep( - '-', - 'generated html file: file://{0}'.format(self.logfile)) + "-", "generated html file: file://{0}".format(self.logfile) + ) diff --git a/setup.py b/setup.py index 5116702c..1f4922c3 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,34 @@ from setuptools import setup -setup(name='pytest-html', - use_scm_version=True, - description='pytest plugin for generating HTML reports', - long_description=open('README.rst').read(), - author='Dave Hunt', - author_email='dhunt@mozilla.com', - url='https://github.com/pytest-dev/pytest-html', - packages=['pytest_html'], - package_data={'pytest_html': ['resources/*']}, - entry_points={'pytest11': ['html = pytest_html.plugin']}, - setup_requires=['setuptools_scm'], - install_requires=[ - 'pytest>=3.0', - 'pytest-metadata'], - license='Mozilla Public License 2.0 (MPL 2.0)', - keywords='py.test pytest html report', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Topic :: Software Development :: Quality Assurance', - 'Topic :: Software Development :: Testing', - 'Topic :: Utilities', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - ]) +setup( + name="pytest-html", + use_scm_version=True, + description="pytest plugin for generating HTML reports", + long_description=open("README.rst").read(), + author="Dave Hunt", + author_email="dhunt@mozilla.com", + url="https://github.com/pytest-dev/pytest-html", + packages=["pytest_html"], + package_data={"pytest_html": ["resources/*"]}, + entry_points={"pytest11": ["html = pytest_html.plugin"]}, + setup_requires=["setuptools_scm"], + install_requires=["pytest>=3.0", "pytest-metadata"], + license="Mozilla Public License 2.0 (MPL 2.0)", + keywords="py.test pytest html report", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: Pytest", + "Intended Audience :: Developers", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", + "Topic :: Utilities", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], +) diff --git a/testing/test_pytest_html.py b/testing/test_pytest_html.py index 32fc8f6d..95299310 100644 --- a/testing/test_pytest_html.py +++ b/testing/test_pytest_html.py @@ -15,12 +15,12 @@ import pytest PY3 = sys.version_info[0] == 3 -pytest_plugins = "pytester", +pytest_plugins = ("pytester",) -def run(testdir, path='report.html', *args): +def run(testdir, path="report.html", *args): path = testdir.tmpdir.join(path) - result = testdir.runpytest('--html', path, *args) + result = testdir.runpytest("--html", path, *args) return result, read_html(path) @@ -29,53 +29,68 @@ def read_html(path): return f.read() -def assert_results_by_outcome(html, test_outcome, test_outcome_number, - label=None): +def assert_results_by_outcome(html, test_outcome, test_outcome_number, label=None): # Asserts if the test number of this outcome in the summary is correct - regex_summary = r'(\d)+ {0}'.format(label or test_outcome) + regex_summary = r"(\d)+ {0}".format(label or test_outcome) assert int(re.search(regex_summary, html).group(1)) == test_outcome_number # Asserts if the generated checkbox of this outcome is correct - regex_checkbox = ('= float(duration) # Asserts by outcome - assert_results_by_outcome(html, 'passed', passed) - assert_results_by_outcome(html, 'skipped', skipped) - assert_results_by_outcome(html, 'failed', failed) - assert_results_by_outcome(html, 'error', errors, 'errors') - assert_results_by_outcome(html, 'xfailed', xfailed, 'expected failures') - assert_results_by_outcome(html, 'xpassed', xpassed, 'unexpected passes') - assert_results_by_outcome(html, 'rerun', rerun) + assert_results_by_outcome(html, "passed", passed) + assert_results_by_outcome(html, "skipped", skipped) + assert_results_by_outcome(html, "failed", failed) + assert_results_by_outcome(html, "error", errors, "errors") + assert_results_by_outcome(html, "xfailed", xfailed, "expected failures") + assert_results_by_outcome(html, "xpassed", xpassed, "unexpected passes") + assert_results_by_outcome(html, "rerun", rerun) class TestHTML: def test_durations(self, testdir): sleep = float(0.2) - testdir.makepyfile(""" + testdir.makepyfile( + """ import time def test_sleep(): time.sleep({0:f}) - """.format(sleep * 2)) + """.format( + sleep * 2 + ) + ) result, html = run(testdir) assert result.ret == 0 assert_results(html, duration=sleep) @@ -84,49 +99,56 @@ def test_sleep(): assert float(m.group(1)) >= sleep def test_pass(self, testdir): - testdir.makepyfile('def test_pass(): pass') + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 assert_results(html) def test_skip(self, testdir): reason = str(random.random()) - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest def test_skip(): pytest.skip('{0}') - """.format(reason)) + """.format( + reason + ) + ) result, html = run(testdir) assert result.ret == 0 assert_results(html, tests=0, passed=0, skipped=1) - assert 'Skipped: {0}'.format(reason) in html + assert "Skipped: {0}".format(reason) in html def test_fail(self, testdir): - testdir.makepyfile('def test_fail(): assert False') + testdir.makepyfile("def test_fail(): assert False") result, html = run(testdir) assert result.ret assert_results(html, passed=0, failed=1) - assert 'AssertionError' in html + assert "AssertionError" in html def test_rerun(self, testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.mark.flaky(reruns=5) def test_example(): assert False - """) + """ + ) result, html = run(testdir) assert result.ret assert_results(html, passed=0, failed=1, rerun=5) def test_no_rerun(self, testdir): - testdir.makepyfile('def test_pass(): pass') - result, html = run(testdir, 'report.html', '-p', 'no:rerunfailures') + testdir.makepyfile("def test_pass(): pass") + result, html = run(testdir, "report.html", "-p", "no:rerunfailures") assert result.ret == 0 assert re.search('data-test-result="rerun"', html) is None def test_conditional_xfails(self, testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.mark.xfail(False, reason='reason') def test_fail(): assert False @@ -136,60 +158,69 @@ def test_pass(): pass def test_xfail(): assert False @pytest.mark.xfail(True, reason='reason') def test_xpass(): pass - """) + """ + ) result, html = run(testdir) assert result.ret assert_results(html, tests=4, passed=1, failed=1, xfailed=1, xpassed=1) def test_setup_error(self, testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.fixture def arg(request): raise ValueError() def test_function(arg): pass - """) + """ + ) result, html = run(testdir) assert result.ret assert_results(html, tests=0, passed=0, errors=1) - assert '::setup' in html - assert 'ValueError' in html + assert "::setup" in html + assert "ValueError" in html def test_xfail(self, testdir): reason = str(random.random()) - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest def test_xfail(): pytest.xfail('{0}') - """.format(reason)) + """.format( + reason + ) + ) result, html = run(testdir) assert result.ret == 0 assert_results(html, passed=0, xfailed=1) - assert 'XFailed: {0}'.format(reason) in html + assert "XFailed: {0}".format(reason) in html def test_xpass(self, testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.mark.xfail() def test_xpass(): pass - """) + """ + ) result, html = run(testdir) assert result.ret == 0 assert_results(html, passed=0, xpassed=1) def test_create_report_path(self, testdir): - testdir.makepyfile('def test_pass(): pass') - path = os.path.join('directory', 'report.html') + testdir.makepyfile("def test_pass(): pass") + path = os.path.join("directory", "report.html") result, html = run(testdir, path) assert result.ret == 0 assert_results(html) - @pytest.mark.parametrize('path', ['', 'directory']) + @pytest.mark.parametrize("path", ["", "directory"]) def test_report_title(self, testdir, path): - testdir.makepyfile('def test_pass(): pass') - report_name = 'report.html' + testdir.makepyfile("def test_pass(): pass") + report_name = "report.html" path = os.path.join(path, report_name) result, html = run(testdir, path) assert result.ret == 0 @@ -209,46 +240,52 @@ def test_report_title_addopts_env_var(self, testdir, monkeypatch): report_location ), ) - testdir.makepyfile('def test_pass(): pass') + testdir.makepyfile("def test_pass(): pass") result = testdir.runpytest() assert result.ret == 0 report_title = "

{0}

".format(report_name) assert report_title in read_html(report_name) def test_resources_inline_css(self, testdir): - testdir.makepyfile('def test_pass(): pass') - result, html = run(testdir, 'report.html', '--self-contained-html') + testdir.makepyfile("def test_pass(): pass") + result, html = run(testdir, "report.html", "--self-contained-html") assert result.ret == 0 content = pkg_resources.resource_string( - 'pytest_html', os.path.join('resources', 'style.css')) + "pytest_html", os.path.join("resources", "style.css") + ) if PY3: - content = content.decode('utf-8') + content = content.decode("utf-8") assert content assert content in html def test_resources(self, testdir): - testdir.makepyfile('def test_pass(): pass') + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 content = pkg_resources.resource_string( - 'pytest_html', os.path.join('resources', 'main.js')) + "pytest_html", os.path.join("resources", "main.js") + ) if PY3: - content = content.decode('utf-8') + content = content.decode("utf-8") assert content assert content in html regex_css_link = '{0}')] - """.format(content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 assert content in html - @pytest.mark.parametrize('content, encoded', [ - ("u'\u0081'", 'woE='), - ("'foo'", 'Zm9v'), - ("b'\\xe2\\x80\\x93'", '4oCT')]) + @pytest.mark.parametrize( + "content, encoded", + [("u'\u0081'", "woE="), ("'foo'", "Zm9v"), ("b'\\xe2\\x80\\x93'", "4oCT")], + ) def test_extra_text(self, testdir, content, encoded): - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -305,18 +351,21 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.text({0})] - """.format(content)) - testdir.makepyfile('def test_pass(): pass') - result, html = run(testdir, 'report.html', '--self-contained-html') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") + result, html = run(testdir, "report.html", "--self-contained-html") assert result.ret == 0 - href = 'data:text/plain;charset=utf-8;base64,{0}'.format(encoded) - link = 'Text'.format( - href) + href = "data:text/plain;charset=utf-8;base64,{0}".format(encoded) + link = 'Text'.format(href) assert link in html def test_extra_json(self, testdir): content = {str(random.random()): str(random.random())} - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -325,23 +374,26 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.json({0})] - """.format(content)) - testdir.makepyfile('def test_pass(): pass') - result, html = run(testdir, 'report.html', '--self-contained-html') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") + result, html = run(testdir, "report.html", "--self-contained-html") assert result.ret == 0 content_str = json.dumps(content) if PY3: - data = b64encode(content_str.encode('utf-8')).decode('ascii') + data = b64encode(content_str.encode("utf-8")).decode("ascii") else: data = b64encode(content_str) - href = 'data:application/json;charset=utf-8;base64,{0}'.format(data) - link = 'JSON'.format( - href) + href = "data:application/json;charset=utf-8;base64,{0}".format(data) + link = 'JSON'.format(href) assert link in html def test_extra_url(self, testdir): content = str(random.random()) - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -350,22 +402,29 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.url('{0}')] - """.format(content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 - link = 'URL'.format( - content) + link = 'URL'.format(content) assert link in html - @pytest.mark.parametrize('mime_type, extension', - [('image/png', 'png'), - ('image/png', 'image'), - ('image/jpeg', 'jpg'), - ('image/svg+xml', 'svg')]) + @pytest.mark.parametrize( + "mime_type, extension", + [ + ("image/png", "png"), + ("image/png", "image"), + ("image/jpeg", "jpg"), + ("image/svg+xml", "svg"), + ], + ) def test_extra_image(self, testdir, mime_type, extension): content = str(random.random()) - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -374,25 +433,28 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.{0}('{1}')] - """.format(extension, content)) - testdir.makepyfile('def test_pass(): pass') - result, html = run(testdir, 'report.html', '--self-contained-html') + """.format( + extension, content + ) + ) + testdir.makepyfile("def test_pass(): pass") + result, html = run(testdir, "report.html", "--self-contained-html") assert result.ret == 0 - src = 'data:{0};base64,{1}'.format(mime_type, content) + src = "data:{0};base64,{1}".format(mime_type, content) assert ''.format(src) in html def test_extra_image_windows(self, mocker, testdir): - mock_isfile = mocker.patch('pytest_html.plugin.isfile') - mock_isfile.side_effect = ValueError('stat: path too long for Windows') - self.test_extra_image(testdir, 'image/png', 'png') + mock_isfile = mocker.patch("pytest_html.plugin.isfile") + mock_isfile.side_effect = ValueError("stat: path too long for Windows") + self.test_extra_image(testdir, "image/png", "png") assert mock_isfile.call_count == 1 - @pytest.mark.parametrize('content', [ - ("u'\u0081'"), - ("'foo'"), - ("b'\\xe2\\x80\\x93'")]) + @pytest.mark.parametrize( + "content", [("u'\u0081'"), ("'foo'"), ("b'\\xe2\\x80\\x93'")] + ) def test_extra_text_separated(self, testdir, content): - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -401,28 +463,31 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.text({0})] - """.format(content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) - hash_key = ('test_extra_text_separated.py::' - 'test_pass00') + hash_key = "test_extra_text_separated.py::" "test_pass00" hash_generator = hashlib.md5() - hash_generator.update(hash_key.encode('utf-8')) + hash_generator.update(hash_key.encode("utf-8")) assert result.ret == 0 - src = '{0}/{1}'.format('assets', '{0}_{1}.txt'. - format(hash_key, hash_generator.hexdigest())) - link = (''.format(src)) + src = "{0}/{1}".format( + "assets", "{0}_{1}.txt".format(hash_key, hash_generator.hexdigest()) + ) + link = ''.format(src) assert link in html assert os.path.exists(src) - @pytest.mark.parametrize('file_extension, extra_type', [ - ('png', 'image'), - ('png', 'png'), - ('svg', 'svg'), - ('jpg', 'jpg')]) + @pytest.mark.parametrize( + "file_extension, extra_type", + [("png", "image"), ("png", "png"), ("svg", "svg"), ("jpg", "jpg")], + ) def test_extra_image_separated(self, testdir, file_extension, extra_type): - content = b64encode('foo'.encode('utf-8')).decode('ascii') - testdir.makeconftest(""" + content = b64encode("foo".encode("utf-8")).decode("ascii") + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -431,29 +496,32 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.{0}('{1}')] - """.format(extra_type, content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + extra_type, content + ) + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) - hash_key = 'test_extra_image_separated.py::test_pass00' + hash_key = "test_extra_image_separated.py::test_pass00" hash_generator = hashlib.md5() - hash_generator.update(hash_key.encode('utf-8')) + hash_generator.update(hash_key.encode("utf-8")) assert result.ret == 0 - src = '{0}/{1}'.format('assets', '{0}_{1}.{2}'. - format(hash_key, hash_generator.hexdigest(), - file_extension)) - link = (''.format(src)) + src = "{0}/{1}".format( + "assets", + "{0}_{1}.{2}".format(hash_key, hash_generator.hexdigest(), file_extension), + ) + link = ''.format(src) assert link in html assert os.path.exists(src) - @pytest.mark.parametrize('file_extension, extra_type', [ - ('png', 'image'), - ('png', 'png'), - ('svg', 'svg'), - ('jpg', 'jpg')]) - def test_extra_image_separated_rerun(self, testdir, file_extension, - extra_type): - content = b64encode('foo'.encode('utf-8')).decode('ascii') - testdir.makeconftest(""" + @pytest.mark.parametrize( + "file_extension, extra_type", + [("png", "image"), ("png", "png"), ("svg", "svg"), ("jpg", "jpg")], + ) + def test_extra_image_separated_rerun(self, testdir, file_extension, extra_type): + content = b64encode("foo".encode("utf-8")).decode("ascii") + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -462,31 +530,36 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.{0}('{1}')] - """.format(extra_type, content)) - testdir.makepyfile(""" + """.format( + extra_type, content + ) + ) + testdir.makepyfile( + """ import pytest @pytest.mark.flaky(reruns=2) def test_fail(): - assert False""") + assert False""" + ) result, html = run(testdir) for i in range(1, 4): - hash_key = ('test_extra_image_separated_rerun.py::' - 'test_fail0{0}'.format(i)) + hash_key = "test_extra_image_separated_rerun.py::" "test_fail0{0}".format(i) hash_generator = hashlib.md5() - hash_generator.update(hash_key.encode('utf-8')) - src = 'assets/{0}_{1}.{2}'.format(hash_key, - hash_generator.hexdigest(), - file_extension) - link = (''.format(src)) + hash_generator.update(hash_key.encode("utf-8")) + src = "assets/{0}_{1}.{2}".format( + hash_key, hash_generator.hexdigest(), file_extension + ) + link = ''.format(src) assert result.ret assert link in html assert os.path.exists(src) - @pytest.mark.parametrize('src_type', ["https://", "file://", "image.png"]) + @pytest.mark.parametrize("src_type", ["https://", "file://", "image.png"]) def test_extra_image_non_b64(self, testdir, src_type): content = src_type - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -495,16 +568,20 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.image('{0}')] - """.format(content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") if src_type == "image.png": - testdir.makefile('.png', image='pretty picture') - result, html = run(testdir, 'report.html') + testdir.makefile(".png", image="pretty picture") + result, html = run(testdir, "report.html") assert result.ret == 0 assert ''.format(content) in html def test_very_long_test_name(self, testdir): - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -513,126 +590,153 @@ def pytest_runtest_makereport(item, call): if report.when == 'call': from pytest_html import extras report.extra = [extras.image('image.png')] - """) + """ + ) # This will get truncated - test_name = 'test_{}'.format('a' * 300) - testdir.makepyfile(""" + test_name = "test_{}".format("a" * 300) + testdir.makepyfile( + """ def {0}(): assert False - """.format(test_name)) + """.format( + test_name + ) + ) result, html = run(testdir) - hash_key = 'test_very_long_test_name.py::{}00'.format(test_name) + hash_key = "test_very_long_test_name.py::{}00".format(test_name) hash_generator = hashlib.md5() - hash_generator.update(hash_key.encode('utf-8')) - src = 'assets/{0}_{1}.png'.format(hash_key[:218], - hash_generator.hexdigest()) - link = (''.format(src)) + hash_generator.update(hash_key.encode("utf-8")) + src = "assets/{0}_{1}.png".format(hash_key[:218], hash_generator.hexdigest()) + link = ''.format(src) assert result.ret assert link in html assert os.path.exists(src) def test_no_environment(self, testdir): - testdir.makeconftest(""" + testdir.makeconftest( + """ def pytest_configure(config): config._metadata = None - """) - testdir.makepyfile('def test_pass(): pass') + """ + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 - assert 'Environment' not in html + assert "Environment" not in html def test_environment(self, testdir): content = str(random.random()) - testdir.makeconftest(""" + testdir.makeconftest( + """ def pytest_configure(config): config._metadata['content'] = '{0}' - """.format(content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 - assert 'Environment' in html + assert "Environment" in html assert len(re.findall(content, html)) == 1 def test_environment_xdist(self, testdir): content = str(random.random()) - testdir.makeconftest(""" + testdir.makeconftest( + """ def pytest_configure(config): for i in range(2): config._metadata['content'] = '{0}' - """.format(content)) - testdir.makepyfile('def test_pass(): pass') - result, html = run(testdir, 'report.html', '-n', '1') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") + result, html = run(testdir, "report.html", "-n", "1") assert result.ret == 0 - assert 'Environment' in html + assert "Environment" in html assert len(re.findall(content, html)) == 1 def test_environment_xdist_reruns(self, testdir): content = str(random.random()) - testdir.makeconftest(""" + testdir.makeconftest( + """ def pytest_configure(config): for i in range(2): config._metadata['content'] = '{0}' - """.format(content)) - testdir.makepyfile('def test_fail(): assert False') - result, html = run(testdir, 'report.html', '-n', '1', '--reruns', '1') + """.format( + content + ) + ) + testdir.makepyfile("def test_fail(): assert False") + result, html = run(testdir, "report.html", "-n", "1", "--reruns", "1") assert result.ret - assert 'Environment' in html + assert "Environment" in html assert len(re.findall(content, html)) == 1 def test_environment_list_value(self, testdir): content = tuple(str(random.random()) for i in range(10)) content += tuple(random.random() for i in range(10)) - expected_content = ', '.join((str(i) for i in content)) - expected_html_re = r'content\n\s+{}'.format( - expected_content - ) - testdir.makeconftest(""" + expected_content = ", ".join((str(i) for i in content)) + expected_html_re = r"content\n\s+{}".format(expected_content) + testdir.makeconftest( + """ def pytest_configure(config): for i in range(2): config._metadata['content'] = {0} - """.format(content)) - testdir.makepyfile('def test_pass(): pass') + """.format( + content + ) + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 - assert 'Environment' in html + assert "Environment" in html assert len(re.findall(expected_html_re, html)) == 1 def test_environment_ordered(self, testdir): - testdir.makeconftest(""" + testdir.makeconftest( + """ from collections import OrderedDict def pytest_configure(config): config._metadata = OrderedDict([('ZZZ', 1), ('AAA', 2)]) - """) - testdir.makepyfile('def test_pass(): pass') + """ + ) + testdir.makepyfile("def test_pass(): pass") result, html = run(testdir) assert result.ret == 0 - assert 'Environment' in html - assert len(re.findall('ZZZ.+AAA', html, re.DOTALL)) == 1 + assert "Environment" in html + assert len(re.findall("ZZZ.+AAA", html, re.DOTALL)) == 1 @pytest.mark.xfail( - sys.version_info < (3, 2) and - LooseVersion(pytest.__version__) >= LooseVersion('2.8.0'), - reason='Fails on earlier versions of Python and pytest', - run=False) + sys.version_info < (3, 2) + and LooseVersion(pytest.__version__) >= LooseVersion("2.8.0"), + reason="Fails on earlier versions of Python and pytest", + run=False, + ) def test_xdist_crashing_slave(self, testdir): """https://github.com/pytest-dev/pytest-html/issues/21""" - testdir.makepyfile(""" + testdir.makepyfile( + """ import os def test_exit(): os._exit(0) - """) - result, html = run(testdir, 'report.html', '-n', '1') - assert 'INTERNALERROR>' not in result.stdout.str() + """ + ) + result, html = run(testdir, "report.html", "-n", "1") + assert "INTERNALERROR>" not in result.stdout.str() def test_utf8_surrogate(self, testdir): - testdir.makepyfile(r""" + testdir.makepyfile( + r""" import pytest @pytest.mark.parametrize('val', ['\ud800']) def test_foo(val): pass - """) + """ + ) result, html = run(testdir) assert result.ret == 0 assert_results(html, passed=1) @@ -640,21 +744,26 @@ def test_foo(val): def test_ansi_color(self, testdir): try: import ansi2html # NOQA + ANSI = True except ImportError: # ansi2html is not installed ANSI = False - pass_content = ["RCOLOR", - "GCOLOR", - "YCOLOR"] - testdir.makepyfile(r""" + pass_content = [ + 'RCOLOR', + 'GCOLOR', + 'YCOLOR', + ] + testdir.makepyfile( + r""" def test_ansi(): colors = ['\033[31mRCOLOR\033[0m', '\033[32mGCOLOR\033[0m', '\033[33mYCOLOR\033[0m'] for color in colors: print(color) - """) - result, html = run(testdir, 'report.html', '--self-contained-html') + """ + ) + result, html = run(testdir, "report.html", "--self-contained-html") assert result.ret == 0 for content in pass_content: if ANSI: @@ -662,9 +771,10 @@ def test_ansi(): else: assert content not in html - @pytest.mark.parametrize('content', [("'foo'"), ("u'\u0081'")]) + @pytest.mark.parametrize("content", [("'foo'"), ("u'\u0081'")]) def test_utf8_longrepr(self, testdir, content): - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item, call): @@ -672,52 +782,57 @@ def pytest_runtest_makereport(item, call): report = outcome.get_result() if report.when == 'call': report.longrepr = 'utf8 longrepr: ' + {0} - """.format(content)) - testdir.makepyfile(""" + """.format( + content + ) + ) + testdir.makepyfile( + """ def test_fail(): testtext = 'utf8 longrepr: ' assert False - """) - result, html = run(testdir, 'report.html', '--self-contained-html') + """ + ) + result, html = run(testdir, "report.html", "--self-contained-html") assert result.ret - assert 'utf8 longrepr' in html + assert "utf8 longrepr" in html def test_collect_error(self, testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import xyz def test_pass(): pass - """) + """ + ) result, html = run(testdir) assert result.ret assert_results(html, tests=0, passed=0, errors=1) - regex_error = '(Import|ModuleNotFound)Error: No module named .*xyz' + regex_error = "(Import|ModuleNotFound)Error: No module named .*xyz" assert re.search(regex_error, html) is not None - @pytest.mark.parametrize('colors', [(['red']), (['green', 'blue'])]) + @pytest.mark.parametrize("colors", [(["red"]), (["green", "blue"])]) def test_css(self, testdir, colors): - testdir.makepyfile('def test_pass(): pass') + testdir.makepyfile("def test_pass(): pass") css = {} cssargs = [] for color in colors: - style = '* {{color: {}}}'.format(color) - path = testdir.makefile('.css', **{color: style}) - css[color] = {'style': style, 'path': path} - cssargs.extend(['--css', path]) - result, html = run(testdir, 'report.html', '--self-contained-html', - *cssargs) + style = "* {{color: {}}}".format(color) + path = testdir.makefile(".css", **{color: style}) + css[color] = {"style": style, "path": path} + cssargs.extend(["--css", path]) + result, html = run(testdir, "report.html", "--self-contained-html", *cssargs) assert result.ret == 0 for k, v in css.items(): - assert str(v['path']) in html - assert v['style'] in html + assert str(v["path"]) in html + assert v["style"] in html def test_css_invalid(self, testdir): - testdir.makepyfile('def test_pass(): pass') - result = testdir.runpytest('--html', 'report.html', - '--css', 'style.css') + testdir.makepyfile("def test_pass(): pass") + result = testdir.runpytest("--html", "report.html", "--css", "style.css") assert result.ret assert "No such file or directory: 'style.css'" in result.stderr.str() def test_css_invalid_no_html(self, testdir): - testdir.makepyfile('def test_pass(): pass') - result = testdir.runpytest('--css', 'style.css') + testdir.makepyfile("def test_pass(): pass") + result = testdir.runpytest("--css", "style.css") assert result.ret == 0 diff --git a/tox.ini b/tox.ini index 82ac61fa..6d501878 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py{27,36,37,py,py3}{,-ansi2html}, flake8 [testenv] -commands = pytest -v -r a {posargs} +commands = pytest -n auto -v -r a {posargs} deps = pytest-xdist pytest-rerunfailures @@ -19,3 +19,10 @@ skip_install = true basepython = python deps = flake8 commands = flake8 {posargs:.} + +[flake8] +max-line-length = 88 +exclude = .eggs,.tox + +[pytest] +testpaths = testing From e8bb0c28e2140880bdd810042a944e1ecba11e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Br=C3=A4nnlund?= Date: Wed, 10 Jul 2019 22:25:20 +0200 Subject: [PATCH 2/4] Try running without xdist --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6d501878..854db3d3 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py{27,36,37,py,py3}{,-ansi2html}, flake8 [testenv] -commands = pytest -n auto -v -r a {posargs} +commands = pytest -v -r a {posargs} deps = pytest-xdist pytest-rerunfailures From 4d09f20071884e4a005565d6de7207cca71d9ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Br=C3=A4nnlund?= Date: Wed, 10 Jul 2019 22:41:16 +0200 Subject: [PATCH 3/4] Cleaned up travis config --- .travis.yml | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index e2143388..f7f31504 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,50 +1,62 @@ language: python jobs: include: - - stage: + - stage: tests language: node_js node_js: node install: npm install phantomjs-prebuilt@2.1.15 grunt-cli grunt grunt-contrib-qunit script: grunt test - - stage: + + - python: 3.7 dist: xenial sudo: required env: TOXENV=flake8 - - stage: + + - python: 2.7 env: TOXENV=py27 - - stage: + + - python: 2.7 env: TOXENV=py27-ansi2html - - stage: + + - python: 3.6 env: TOXENV=py36 - - stage: + + - python: 3.6 env: TOXENV=py36-ansi2html - - stage: + + - python: 3.7 dist: xenial sudo: required env: TOXENV=py37 - - stage: + + - python: 3.7 dist: xenial sudo: required env: TOXENV=py37-ansi2html - - stage: + + - python: pypy env: TOXENV=pypy - - stage: + + - python: pypy env: TOXENV=pypy-ansi2html - - stage: + + - python: pypy3 env: TOXENV=pypy3 - - stage: + + - python: pypy3 env: TOXENV=pypy3-ansi2html + - stage: deploy python: 3.7 dist: xenial @@ -60,7 +72,9 @@ jobs: on: tags: true repo: pytest-dev/pytest-html -cache: - pip: true + +cache: pip + install: pip install tox + script: tox From 4357b33366aa2e75943845bdba33ac03f2594663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Br=C3=A4nnlund?= Date: Sat, 13 Jul 2019 12:58:16 +0200 Subject: [PATCH 4/4] Add black to tox and travis --- .travis.yml | 6 ++++++ tox.ini | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f7f31504..45feb263 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,12 @@ jobs: sudo: required env: TOXENV=flake8 + - + python: 3.7 + dist: xenial + sudo: required + env: TOXENV=black + - python: 2.7 env: TOXENV=py27 diff --git a/tox.ini b/tox.ini index 854db3d3..1489252b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py{27,36,37,py,py3}{,-ansi2html}, flake8 +envlist = py{27,36,37,py,py3}{,-ansi2html}, flake8, black [testenv] commands = pytest -v -r a {posargs} @@ -20,6 +20,12 @@ basepython = python deps = flake8 commands = flake8 {posargs:.} +[testenv:black] +skip_install = true +basepython = python +deps = black +commands = black --check {posargs:.} + [flake8] max-line-length = 88 exclude = .eggs,.tox