Skip to content

Commit 0a2c42f

Browse files
authored
[Tests] Upload custom test artifacts (#572)
* make_reports * add test utils * style * style
1 parent 2a8477d commit 0a2c42f

File tree

4 files changed

+224
-2
lines changed

4 files changed

+224
-2
lines changed

.github/workflows/pr_tests.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,15 @@ jobs:
4141
4242
- name: Run all non-slow selected tests on CPU
4343
run: |
44-
python -m pytest -n 2 --max-worker-restart=0 --dist=loadfile -s tests/
44+
python -m pytest -n 2 --max-worker-restart=0 --dist=loadfile -s -v --make-reports=tests_torch_cpu tests/
45+
46+
- name: Failure short reports
47+
if: ${{ failure() }}
48+
run: cat reports/tests_torch_cpu_failures_short.txt
49+
50+
- name: Test suite reports artifacts
51+
if: ${{ always() }}
52+
uses: actions/upload-artifact@v2
53+
with:
54+
name: pr_torch_test_reports
55+
path: reports

.github/workflows/push_tests.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,15 @@ jobs:
4949
env:
5050
HUGGING_FACE_HUB_TOKEN: ${{ secrets.HUGGING_FACE_HUB_TOKEN }}
5151
run: |
52-
python -m pytest -n 1 --max-worker-restart=0 --dist=loadfile -s tests/
52+
python -m pytest -n 1 --max-worker-restart=0 --dist=loadfile -s -v --make-reports=tests_torch_gpu tests/
53+
54+
- name: Failure short reports
55+
if: ${{ failure() }}
56+
run: cat reports/tests_torch_gpu_failures_short.txt
57+
58+
- name: Test suite reports artifacts
59+
if: ${{ always() }}
60+
uses: actions/upload-artifact@v2
61+
with:
62+
name: push_torch_test_reports
63+
path: reports

src/diffusers/testing_utils.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
import random
3+
import re
34
import unittest
45
from distutils.util import strtobool
6+
from pathlib import Path
57
from typing import Union
68

79
import torch
@@ -92,3 +94,157 @@ def load_image(image: Union[str, PIL.Image.Image]) -> PIL.Image.Image:
9294
image = PIL.ImageOps.exif_transpose(image)
9395
image = image.convert("RGB")
9496
return image
97+
98+
99+
# --- pytest conf functions --- #
100+
101+
# to avoid multiple invocation from tests/conftest.py and examples/conftest.py - make sure it's called only once
102+
pytest_opt_registered = {}
103+
104+
105+
def pytest_addoption_shared(parser):
106+
"""
107+
This function is to be called from `conftest.py` via `pytest_addoption` wrapper that has to be defined there.
108+
109+
It allows loading both `conftest.py` files at once without causing a failure due to adding the same `pytest`
110+
option.
111+
112+
"""
113+
option = "--make-reports"
114+
if option not in pytest_opt_registered:
115+
parser.addoption(
116+
option,
117+
action="store",
118+
default=False,
119+
help="generate report files. The value of this option is used as a prefix to report names",
120+
)
121+
pytest_opt_registered[option] = 1
122+
123+
124+
def pytest_terminal_summary_main(tr, id):
125+
"""
126+
Generate multiple reports at the end of test suite run - each report goes into a dedicated file in the current
127+
directory. The report files are prefixed with the test suite name.
128+
129+
This function emulates --duration and -rA pytest arguments.
130+
131+
This function is to be called from `conftest.py` via `pytest_terminal_summary` wrapper that has to be defined
132+
there.
133+
134+
Args:
135+
- tr: `terminalreporter` passed from `conftest.py`
136+
- id: unique id like `tests` or `examples` that will be incorporated into the final reports filenames - this is
137+
needed as some jobs have multiple runs of pytest, so we can't have them overwrite each other.
138+
139+
NB: this functions taps into a private _pytest API and while unlikely, it could break should
140+
pytest do internal changes - also it calls default internal methods of terminalreporter which
141+
can be hijacked by various `pytest-` plugins and interfere.
142+
143+
"""
144+
from _pytest.config import create_terminal_writer
145+
146+
if not len(id):
147+
id = "tests"
148+
149+
config = tr.config
150+
orig_writer = config.get_terminal_writer()
151+
orig_tbstyle = config.option.tbstyle
152+
orig_reportchars = tr.reportchars
153+
154+
dir = "reports"
155+
Path(dir).mkdir(parents=True, exist_ok=True)
156+
report_files = {
157+
k: f"{dir}/{id}_{k}.txt"
158+
for k in [
159+
"durations",
160+
"errors",
161+
"failures_long",
162+
"failures_short",
163+
"failures_line",
164+
"passes",
165+
"stats",
166+
"summary_short",
167+
"warnings",
168+
]
169+
}
170+
171+
# custom durations report
172+
# note: there is no need to call pytest --durations=XX to get this separate report
173+
# adapted from https://github.com/pytest-dev/pytest/blob/897f151e/src/_pytest/runner.py#L66
174+
dlist = []
175+
for replist in tr.stats.values():
176+
for rep in replist:
177+
if hasattr(rep, "duration"):
178+
dlist.append(rep)
179+
if dlist:
180+
dlist.sort(key=lambda x: x.duration, reverse=True)
181+
with open(report_files["durations"], "w") as f:
182+
durations_min = 0.05 # sec
183+
f.write("slowest durations\n")
184+
for i, rep in enumerate(dlist):
185+
if rep.duration < durations_min:
186+
f.write(f"{len(dlist)-i} durations < {durations_min} secs were omitted")
187+
break
188+
f.write(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}\n")
189+
190+
def summary_failures_short(tr):
191+
# expecting that the reports were --tb=long (default) so we chop them off here to the last frame
192+
reports = tr.getreports("failed")
193+
if not reports:
194+
return
195+
tr.write_sep("=", "FAILURES SHORT STACK")
196+
for rep in reports:
197+
msg = tr._getfailureheadline(rep)
198+
tr.write_sep("_", msg, red=True, bold=True)
199+
# chop off the optional leading extra frames, leaving only the last one
200+
longrepr = re.sub(r".*_ _ _ (_ ){10,}_ _ ", "", rep.longreprtext, 0, re.M | re.S)
201+
tr._tw.line(longrepr)
202+
# note: not printing out any rep.sections to keep the report short
203+
204+
# use ready-made report funcs, we are just hijacking the filehandle to log to a dedicated file each
205+
# adapted from https://github.com/pytest-dev/pytest/blob/897f151e/src/_pytest/terminal.py#L814
206+
# note: some pytest plugins may interfere by hijacking the default `terminalreporter` (e.g.
207+
# pytest-instafail does that)
208+
209+
# report failures with line/short/long styles
210+
config.option.tbstyle = "auto" # full tb
211+
with open(report_files["failures_long"], "w") as f:
212+
tr._tw = create_terminal_writer(config, f)
213+
tr.summary_failures()
214+
215+
# config.option.tbstyle = "short" # short tb
216+
with open(report_files["failures_short"], "w") as f:
217+
tr._tw = create_terminal_writer(config, f)
218+
summary_failures_short(tr)
219+
220+
config.option.tbstyle = "line" # one line per error
221+
with open(report_files["failures_line"], "w") as f:
222+
tr._tw = create_terminal_writer(config, f)
223+
tr.summary_failures()
224+
225+
with open(report_files["errors"], "w") as f:
226+
tr._tw = create_terminal_writer(config, f)
227+
tr.summary_errors()
228+
229+
with open(report_files["warnings"], "w") as f:
230+
tr._tw = create_terminal_writer(config, f)
231+
tr.summary_warnings() # normal warnings
232+
tr.summary_warnings() # final warnings
233+
234+
tr.reportchars = "wPpsxXEf" # emulate -rA (used in summary_passes() and short_test_summary())
235+
with open(report_files["passes"], "w") as f:
236+
tr._tw = create_terminal_writer(config, f)
237+
tr.summary_passes()
238+
239+
with open(report_files["summary_short"], "w") as f:
240+
tr._tw = create_terminal_writer(config, f)
241+
tr.short_test_summary()
242+
243+
with open(report_files["stats"], "w") as f:
244+
tr._tw = create_terminal_writer(config, f)
245+
tr.summary_stats()
246+
247+
# restore:
248+
tr._tw = orig_writer
249+
tr.reportchars = orig_reportchars
250+
config.option.tbstyle = orig_tbstyle

tests/conftest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2022 The HuggingFace Team. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# tests directory-specific settings - this file is run automatically
16+
# by pytest before any tests are run
17+
18+
import sys
19+
import warnings
20+
from os.path import abspath, dirname, join
21+
22+
23+
# allow having multiple repository checkouts and not needing to remember to rerun
24+
# 'pip install -e .[dev]' when switching between checkouts and running tests.
25+
git_repo_path = abspath(join(dirname(dirname(__file__)), "src"))
26+
sys.path.insert(1, git_repo_path)
27+
28+
# silence FutureWarning warnings in tests since often we can't act on them until
29+
# they become normal warnings - i.e. the tests still need to test the current functionality
30+
warnings.simplefilter(action="ignore", category=FutureWarning)
31+
32+
33+
def pytest_addoption(parser):
34+
from diffusers.testing_utils import pytest_addoption_shared
35+
36+
pytest_addoption_shared(parser)
37+
38+
39+
def pytest_terminal_summary(terminalreporter):
40+
from diffusers.testing_utils import pytest_terminal_summary_main
41+
42+
make_reports = terminalreporter.config.getoption("--make-reports")
43+
if make_reports:
44+
pytest_terminal_summary_main(terminalreporter, id=make_reports)

0 commit comments

Comments
 (0)