feat: add summary to Action (#2469)
* chore: add summary to Action * refactor: new summary table * fix: fixup tests and formatting Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: pyodide missing some logging Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: nicer printout, nicer in-place summary Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: use summary for everything Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: support only one output wheel from repair Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Add new Github summary format * Remove a couple of humanize uses * fix: filter ANSI codes in summary Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: add sha256 Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: nicer wheel/wheels depending on how many are present Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
co-authored by
Joe Rickerby
parent
a2c263d4ab
commit
d0e4aded3e
@@ -29,6 +29,7 @@ repos:
|
||||
additional_dependencies: &mypy-dependencies
|
||||
- bracex
|
||||
- dependency-groups>=1.2
|
||||
- humanize
|
||||
- nox>=2025.2.9
|
||||
- orjson
|
||||
- packaging
|
||||
|
||||
@@ -7,10 +7,9 @@ import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import textwrap
|
||||
import time
|
||||
import traceback
|
||||
import typing
|
||||
from collections.abc import Generator, Iterable, Sequence
|
||||
from collections.abc import Iterable, Sequence
|
||||
from pathlib import Path
|
||||
from tempfile import mkdtemp
|
||||
from typing import Any, Literal, TextIO
|
||||
@@ -286,42 +285,6 @@ def _compute_platform(args: CommandLineArguments) -> PlatformName:
|
||||
return _compute_platform_auto()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def print_new_wheels(msg: str, output_dir: Path) -> Generator[None, None, None]:
|
||||
"""
|
||||
Prints the new items in a directory upon exiting. The message to display
|
||||
can include {n} for number of wheels, {s} for total number of seconds,
|
||||
and/or {m} for total number of minutes. Does not print anything if this
|
||||
exits via exception.
|
||||
"""
|
||||
|
||||
start_time = time.time()
|
||||
existing_contents = set(output_dir.iterdir())
|
||||
yield
|
||||
final_contents = set(output_dir.iterdir())
|
||||
|
||||
new_contents = [
|
||||
FileReport(wheel.name, f"{(wheel.stat().st_size + 1023) // 1024:,d}")
|
||||
for wheel in final_contents - existing_contents
|
||||
]
|
||||
|
||||
if not new_contents:
|
||||
return
|
||||
|
||||
max_name_len = max(len(f.name) for f in new_contents)
|
||||
max_size_len = max(len(f.size) for f in new_contents)
|
||||
n = len(new_contents)
|
||||
s = time.time() - start_time
|
||||
m = s / 60
|
||||
print(
|
||||
msg.format(n=n, s=s, m=m),
|
||||
*sorted(
|
||||
f" {f.name:<{max_name_len}s} {f.size:>{max_size_len}s} kB" for f in new_contents
|
||||
),
|
||||
sep="\n",
|
||||
)
|
||||
|
||||
|
||||
def build_in_directory(args: CommandLineArguments) -> None:
|
||||
platform: PlatformName = _compute_platform(args)
|
||||
if platform == "pyodide" and sys.platform == "win32":
|
||||
@@ -382,7 +345,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
|
||||
|
||||
tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True)
|
||||
try:
|
||||
with print_new_wheels("\n{n} wheels produced in {m:.0f} minutes:", output_dir):
|
||||
with log.print_summary(options=options):
|
||||
platform_module.build(options, tmp_path)
|
||||
finally:
|
||||
# avoid https://github.com/python/cpython/issues/86962 by performing
|
||||
|
||||
+12
-2
@@ -4,6 +4,8 @@ from enum import Enum
|
||||
|
||||
from .util.helpers import strtobool
|
||||
|
||||
ANSI_CODE_REGEX = re.compile(r"(\033\[[0-9;]*m)")
|
||||
|
||||
|
||||
class CIProvider(Enum):
|
||||
# official support
|
||||
@@ -46,7 +48,7 @@ def fix_ansi_codes_for_github_actions(text: str) -> str:
|
||||
Github Actions forgets the current ANSI style on every new line. This
|
||||
function repeats the current ANSI style on every new line.
|
||||
"""
|
||||
ansi_code_regex = re.compile(r"(\033\[[0-9;]*m)")
|
||||
|
||||
ansi_codes: list[str] = []
|
||||
output = ""
|
||||
|
||||
@@ -55,7 +57,7 @@ def fix_ansi_codes_for_github_actions(text: str) -> str:
|
||||
output += "".join(ansi_codes) + line
|
||||
|
||||
# split the line at each ANSI code
|
||||
parts = ansi_code_regex.split(line)
|
||||
parts = ANSI_CODE_REGEX.split(line)
|
||||
# if there are any ANSI codes, save them
|
||||
if len(parts) > 1:
|
||||
# iterate over the ANSI codes in this line
|
||||
@@ -67,3 +69,11 @@ def fix_ansi_codes_for_github_actions(text: str) -> str:
|
||||
ansi_codes.append(code)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def filter_ansi_codes(text: str, /) -> str:
|
||||
"""
|
||||
Remove ANSI codes from text.
|
||||
"""
|
||||
|
||||
return ANSI_CODE_REGEX.sub("", text)
|
||||
|
||||
+154
-20
@@ -1,11 +1,24 @@
|
||||
import codecs
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import functools
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import time
|
||||
from typing import IO, AnyStr, Final, Literal
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from typing import IO, TYPE_CHECKING, AnyStr, Final, Literal
|
||||
|
||||
from .ci import CIProvider, detect_ci_provider
|
||||
import humanize
|
||||
|
||||
from .ci import CIProvider, detect_ci_provider, filter_ansi_codes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .options import Options
|
||||
|
||||
FoldPattern = tuple[str, str]
|
||||
DEFAULT_FOLD_PATTERN: Final[FoldPattern] = ("{name}", "")
|
||||
@@ -69,6 +82,33 @@ class Symbols:
|
||||
self.error = "✕" if unicode else "failed"
|
||||
|
||||
|
||||
@dataclasses.dataclass(kw_only=True, frozen=True)
|
||||
class BuildInfo:
|
||||
identifier: str
|
||||
filename: Path | None
|
||||
duration: float
|
||||
|
||||
@functools.cached_property
|
||||
def size(self) -> str | None:
|
||||
if self.filename is None:
|
||||
return None
|
||||
return humanize.naturalsize(self.filename.stat().st_size)
|
||||
|
||||
@functools.cached_property
|
||||
def sha256(self) -> str | None:
|
||||
if self.filename is None:
|
||||
return None
|
||||
with self.filename.open("rb") as f:
|
||||
digest = hashlib.file_digest(f, "sha256")
|
||||
return digest.hexdigest()
|
||||
|
||||
def __str__(self) -> str:
|
||||
duration = humanize.naturaldelta(self.duration)
|
||||
if self.filename:
|
||||
return f"{self.identifier}: {self.filename.name} {self.size} in {duration}, SHA256={self.sha256}"
|
||||
return f"{self.identifier}: {duration} (test only)"
|
||||
|
||||
|
||||
class Logger:
|
||||
fold_mode: Literal["azure", "github", "travis", "disabled"]
|
||||
colors_enabled: bool
|
||||
@@ -77,6 +117,7 @@ class Logger:
|
||||
build_start_time: float | None = None
|
||||
step_start_time: float | None = None
|
||||
active_fold_group_name: str | None = None
|
||||
summary: list[BuildInfo]
|
||||
|
||||
def __init__(self) -> None:
|
||||
if sys.platform == "win32" and hasattr(sys.stdout, "reconfigure"):
|
||||
@@ -88,25 +129,28 @@ class Logger:
|
||||
|
||||
ci_provider = detect_ci_provider()
|
||||
|
||||
if ci_provider == CIProvider.azure_pipelines:
|
||||
self.fold_mode = "azure"
|
||||
self.colors_enabled = True
|
||||
match ci_provider:
|
||||
case CIProvider.azure_pipelines:
|
||||
self.fold_mode = "azure"
|
||||
self.colors_enabled = True
|
||||
|
||||
elif ci_provider == CIProvider.github_actions:
|
||||
self.fold_mode = "github"
|
||||
self.colors_enabled = True
|
||||
case CIProvider.github_actions:
|
||||
self.fold_mode = "github"
|
||||
self.colors_enabled = True
|
||||
|
||||
elif ci_provider == CIProvider.travis_ci:
|
||||
self.fold_mode = "travis"
|
||||
self.colors_enabled = True
|
||||
case CIProvider.travis_ci:
|
||||
self.fold_mode = "travis"
|
||||
self.colors_enabled = True
|
||||
|
||||
elif ci_provider == CIProvider.appveyor:
|
||||
self.fold_mode = "disabled"
|
||||
self.colors_enabled = True
|
||||
case CIProvider.appveyor:
|
||||
self.fold_mode = "disabled"
|
||||
self.colors_enabled = True
|
||||
|
||||
else:
|
||||
self.fold_mode = "disabled"
|
||||
self.colors_enabled = file_supports_color(sys.stdout)
|
||||
case _:
|
||||
self.fold_mode = "disabled"
|
||||
self.colors_enabled = file_supports_color(sys.stdout)
|
||||
|
||||
self.summary = []
|
||||
|
||||
def build_start(self, identifier: str) -> None:
|
||||
self.step_end()
|
||||
@@ -120,7 +164,7 @@ class Logger:
|
||||
self.build_start_time = time.time()
|
||||
self.active_build_identifier = identifier
|
||||
|
||||
def build_end(self) -> None:
|
||||
def build_end(self, filename: Path | None) -> None:
|
||||
assert self.build_start_time is not None
|
||||
assert self.active_build_identifier is not None
|
||||
self.step_end()
|
||||
@@ -128,11 +172,14 @@ class Logger:
|
||||
c = self.colors
|
||||
s = self.symbols
|
||||
duration = time.time() - self.build_start_time
|
||||
duration_str = humanize.naturaldelta(duration, minimum_unit="milliseconds")
|
||||
|
||||
print()
|
||||
print(
|
||||
f"{c.green}{s.done} {c.end}{self.active_build_identifier} finished in {duration:.2f}s"
|
||||
print(f"{c.green}{s.done} {c.end}{self.active_build_identifier} finished in {duration_str}")
|
||||
self.summary.append(
|
||||
BuildInfo(identifier=self.active_build_identifier, filename=filename, duration=duration)
|
||||
)
|
||||
|
||||
self.build_start_time = None
|
||||
self.active_build_identifier = None
|
||||
|
||||
@@ -147,6 +194,7 @@ class Logger:
|
||||
c = self.colors
|
||||
s = self.symbols
|
||||
duration = time.time() - self.step_start_time
|
||||
|
||||
if success:
|
||||
print(f"{c.green}{s.done} {c.end}{duration:.2f}s".rjust(78))
|
||||
else:
|
||||
@@ -183,6 +231,26 @@ class Logger:
|
||||
c = self.colors
|
||||
print(f"cibuildwheel: {c.bright_red}error{c.end}: {error}\n", file=sys.stderr)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def print_summary(self, *, options: "Options") -> Generator[None, None, None]:
|
||||
start = time.time()
|
||||
yield
|
||||
duration = time.time() - start
|
||||
if summary_path := os.environ.get("GITHUB_STEP_SUMMARY"):
|
||||
github_summary = self._github_step_summary(duration=duration, options=options)
|
||||
Path(summary_path).write_text(filter_ansi_codes(github_summary), encoding="utf-8")
|
||||
|
||||
n = len(self.summary)
|
||||
s = "s" if n > 1 else ""
|
||||
duration_str = humanize.naturaldelta(duration)
|
||||
print()
|
||||
self._start_fold_group(f"{n} wheel{s} produced in {duration_str}")
|
||||
for build_info in self.summary:
|
||||
print(" ", build_info)
|
||||
self._end_fold_group()
|
||||
|
||||
self.summary = []
|
||||
|
||||
@property
|
||||
def step_active(self) -> bool:
|
||||
return self.step_start_time is not None
|
||||
@@ -222,6 +290,72 @@ class Logger:
|
||||
# lowercase, shorten
|
||||
return identifier.lower()[:20]
|
||||
|
||||
def _github_step_summary(self, duration: float, options: "Options") -> str:
|
||||
"""
|
||||
Returns the GitHub step summary, in markdown format.
|
||||
"""
|
||||
out = io.StringIO()
|
||||
options_summary = options.summary(
|
||||
identifiers=[bi.identifier for bi in self.summary], skip_unset=True
|
||||
)
|
||||
out.write(
|
||||
textwrap.dedent("""\
|
||||
### 🎡 cibuildwheel
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Build options
|
||||
</summary>
|
||||
|
||||
```yaml
|
||||
{options_summary}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
""").format(options_summary=options_summary)
|
||||
)
|
||||
n_wheels = len([b for b in self.summary if b.filename])
|
||||
wheel_rows = "\n".join(
|
||||
"<tr>"
|
||||
f"<td nowrap>{'<samp>' + b.filename.name + '</samp>' if b.filename else '*Build only*'}</td>"
|
||||
f"<td nowrap>{b.size or 'N/A'}</td>"
|
||||
f"<td nowrap><samp>{b.identifier}</samp></td>"
|
||||
f"<td nowrap>{humanize.naturaldelta(b.duration)}</td>"
|
||||
f"<td nowrap><samp>{b.sha256 or 'N/A'}</samp></td>"
|
||||
"</tr>"
|
||||
for b in self.summary
|
||||
)
|
||||
out.write(
|
||||
textwrap.dedent("""\
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Wheel</th>
|
||||
<th align="left">Size</th>
|
||||
<th align="left">Build identifier</th>
|
||||
<th align="left">Time</th>
|
||||
<th align="left">SHA256</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{wheel_rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<div align="right"><sup>{n} wheel{s} created in {duration_str}</sup></div>
|
||||
""").format(
|
||||
wheel_rows=wheel_rows,
|
||||
n=n_wheels,
|
||||
duration_str=humanize.naturaldelta(duration),
|
||||
s="s" if n_wheels > 1 else "",
|
||||
)
|
||||
)
|
||||
|
||||
out.write("\n")
|
||||
out.write("---")
|
||||
out.write("\n")
|
||||
return out.getvalue()
|
||||
|
||||
@property
|
||||
def colors(self) -> Colors:
|
||||
return Colors(enabled=self.colors_enabled)
|
||||
|
||||
+20
-5
@@ -907,14 +907,18 @@ class Options:
|
||||
defaults=True,
|
||||
)
|
||||
|
||||
def summary(self, identifiers: Iterable[str]) -> str:
|
||||
def summary(self, identifiers: Iterable[str], skip_unset: bool = False) -> str:
|
||||
lines = []
|
||||
global_option_names = sorted(f.name for f in dataclasses.fields(self.globals))
|
||||
|
||||
for option_name in global_option_names:
|
||||
option_value = getattr(self.globals, option_name)
|
||||
default_value = getattr(self.defaults.globals, option_name)
|
||||
lines.append(self.option_summary(option_name, option_value, default_value))
|
||||
line = self.option_summary(
|
||||
option_name, option_value, default_value, skip_unset=skip_unset
|
||||
)
|
||||
if line is not None:
|
||||
lines.append(line)
|
||||
|
||||
build_options = self.build_options(identifier=None)
|
||||
build_options_defaults = self.defaults.build_options(identifier=None)
|
||||
@@ -934,9 +938,15 @@ class Options:
|
||||
i: getattr(build_options_for_identifier[i], option_name) for i in identifiers
|
||||
}
|
||||
|
||||
lines.append(
|
||||
self.option_summary(option_name, option_value, default_value, overrides=overrides)
|
||||
line = self.option_summary(
|
||||
option_name,
|
||||
option_value,
|
||||
default_value,
|
||||
overrides=overrides,
|
||||
skip_unset=skip_unset,
|
||||
)
|
||||
if line is not None:
|
||||
lines.append(line)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -946,7 +956,8 @@ class Options:
|
||||
option_value: Any,
|
||||
default_value: Any,
|
||||
overrides: Mapping[str, Any] | None = None,
|
||||
) -> str:
|
||||
skip_unset: bool = False,
|
||||
) -> str | None:
|
||||
"""
|
||||
Return a summary of the option value, including any overrides, with
|
||||
ANSI 'dim' color if it's the default.
|
||||
@@ -960,6 +971,10 @@ class Options:
|
||||
overrides_value_strs = {k: v for k, v in overrides_value_strs.items() if v != value_str}
|
||||
|
||||
has_been_set = (value_str != default_value_str) or overrides_value_strs
|
||||
|
||||
if skip_unset and not has_been_set:
|
||||
return None
|
||||
|
||||
c = log.colors
|
||||
|
||||
result = c.gray if not has_been_set else ""
|
||||
|
||||
@@ -673,6 +673,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
log.step_end()
|
||||
|
||||
# We're all done here; move it to output (overwrite existing)
|
||||
output_wheel: Path | None = None
|
||||
if compatible_wheel is None:
|
||||
output_wheel = build_options.output_dir.joinpath(built_wheel.name)
|
||||
moved_wheel = move_file(built_wheel, output_wheel)
|
||||
@@ -685,7 +686,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
# Clean up
|
||||
shutil.rmtree(identifier_tmp_dir)
|
||||
|
||||
log.build_end()
|
||||
log.build_end(output_wheel)
|
||||
except subprocess.CalledProcessError as error:
|
||||
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
|
||||
raise errors.FatalError(msg) from error
|
||||
|
||||
@@ -414,13 +414,15 @@ def build_in_container(
|
||||
# clean up test environment
|
||||
container.call(["rm", "-rf", testing_temp_dir])
|
||||
|
||||
# move repaired wheels to output
|
||||
# move repaired wheel to output
|
||||
output_wheel: Path | None = None
|
||||
if compatible_wheel is None:
|
||||
container.call(["mkdir", "-p", container_output_dir])
|
||||
container.call(["mv", repaired_wheel, container_output_dir])
|
||||
built_wheels.append(container_output_dir / repaired_wheel.name)
|
||||
output_wheel = options.globals.output_dir / repaired_wheel.name
|
||||
|
||||
log.build_end()
|
||||
log.build_end(output_wheel)
|
||||
|
||||
log.step("Copying wheels back to host...")
|
||||
# copy the output back into the host
|
||||
|
||||
@@ -732,6 +732,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
shell_with_arch(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
# we're all done here; move it to output (overwrite existing)
|
||||
output_wheel = None
|
||||
if compatible_wheel is None:
|
||||
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
|
||||
moved_wheel = move_file(repaired_wheel, output_wheel)
|
||||
@@ -744,7 +745,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
# clean up
|
||||
shutil.rmtree(identifier_tmp_dir)
|
||||
|
||||
log.build_end()
|
||||
log.build_end(output_wheel)
|
||||
except subprocess.CalledProcessError as error:
|
||||
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
|
||||
raise errors.FatalError(msg) from error
|
||||
|
||||
@@ -540,6 +540,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
# we're all done here; move it to output (overwrite existing)
|
||||
output_wheel: Path | None = None
|
||||
if compatible_wheel is None:
|
||||
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
|
||||
moved_wheel = move_file(repaired_wheel, output_wheel)
|
||||
@@ -548,7 +549,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
f"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
|
||||
)
|
||||
built_wheels.append(output_wheel)
|
||||
log.build_end()
|
||||
log.build_end(output_wheel)
|
||||
|
||||
except subprocess.CalledProcessError as error:
|
||||
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
|
||||
|
||||
@@ -613,6 +613,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
|
||||
|
||||
# we're all done here; move it to output (remove if already exists)
|
||||
output_wheel = None
|
||||
if compatible_wheel is None:
|
||||
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
|
||||
moved_wheel = move_file(repaired_wheel, output_wheel)
|
||||
@@ -627,7 +628,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
# don't want to abort a build because of that)
|
||||
shutil.rmtree(identifier_tmp_dir, ignore_errors=True)
|
||||
|
||||
log.build_end()
|
||||
log.build_end(output_wheel)
|
||||
except subprocess.CalledProcessError as error:
|
||||
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
|
||||
raise errors.FatalError(msg) from error
|
||||
|
||||
@@ -42,6 +42,7 @@ dependencies = [
|
||||
"certifi",
|
||||
"dependency-groups>=1.2",
|
||||
"filelock",
|
||||
"humanize",
|
||||
"packaging>=20.9",
|
||||
"platformdirs"
|
||||
]
|
||||
|
||||
@@ -6,7 +6,8 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from cibuildwheel import __main__, architecture
|
||||
from cibuildwheel import architecture
|
||||
from cibuildwheel.logger import Logger
|
||||
from cibuildwheel.platforms import linux, macos, pyodide, windows
|
||||
from cibuildwheel.util import file
|
||||
|
||||
@@ -58,7 +59,7 @@ def disable_print_wheels(monkeypatch):
|
||||
def empty_cm(*args, **kwargs):
|
||||
yield
|
||||
|
||||
monkeypatch.setattr(__main__, "print_new_wheels", empty_cm)
|
||||
monkeypatch.setattr(Logger, "print_summary", empty_cm)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -45,7 +45,7 @@ def mock_build_container(monkeypatch):
|
||||
"cibuildwheel.platforms.linux.build_in_container",
|
||||
mock.Mock(spec=platforms.linux.build_in_container),
|
||||
)
|
||||
monkeypatch.setattr("cibuildwheel.__main__.print_new_wheels", ignore_context_call)
|
||||
monkeypatch.setattr("cibuildwheel.logger.Logger.print_summary", ignore_context_call)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_build_container", "fake_package_dir")
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from cibuildwheel.__main__ import print_new_wheels
|
||||
from cibuildwheel.logger import BuildInfo, Logger
|
||||
from cibuildwheel.options import CommandLineArguments, Options
|
||||
|
||||
OPTIONS_DEFAULTS = Options("linux", CommandLineArguments.defaults(), {}, defaults=True)
|
||||
FILE = Path(__file__)
|
||||
|
||||
|
||||
def test_printout_wheels(tmp_path, capsys):
|
||||
tmp_path.joinpath("example.0").touch()
|
||||
with print_new_wheels("TEST_MSG: {n}", tmp_path):
|
||||
tmp_path.joinpath("example.1").write_bytes(b"0" * 1023)
|
||||
tmp_path.joinpath("example.2").write_bytes(b"0" * 1025)
|
||||
def test_printout_wheels(capsys):
|
||||
log = Logger()
|
||||
log.fold_mode = "disabled"
|
||||
log.colors_enabled = False
|
||||
|
||||
with log.print_summary(options=OPTIONS_DEFAULTS):
|
||||
log.summary = [
|
||||
BuildInfo(identifier="id1", filename=None, duration=3),
|
||||
BuildInfo(identifier="id2", filename=FILE, duration=2),
|
||||
]
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert captured.err == ""
|
||||
|
||||
assert "example.0" not in captured.out
|
||||
assert "example.1 1 kB\n" in captured.out
|
||||
assert "example.2 2 kB\n" in captured.out
|
||||
assert "TEST_MSG:" in captured.out
|
||||
assert "TEST_MSG: 2\n" in captured.out
|
||||
assert "id1" in captured.out
|
||||
assert "id2" in captured.out
|
||||
assert "wheels produced in" in captured.out
|
||||
assert "SHA256=" in captured.out
|
||||
|
||||
|
||||
def test_no_printout_on_error(tmp_path, capsys):
|
||||
tmp_path.joinpath("example.0").touch()
|
||||
with pytest.raises(RuntimeError), print_new_wheels("TEST_MSG: {n}", tmp_path): # noqa: PT012
|
||||
tmp_path.joinpath("example.1").touch()
|
||||
def test_no_printout_on_error(capsys):
|
||||
log = Logger()
|
||||
with pytest.raises(RuntimeError), log.print_summary(options=OPTIONS_DEFAULTS):
|
||||
raise RuntimeError()
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert captured.err == ""
|
||||
|
||||
assert "example.0" not in captured.out
|
||||
assert "example.1" not in captured.out
|
||||
assert "example.2" not in captured.out
|
||||
assert "TEST_MSG:" not in captured.out
|
||||
assert captured.out == ""
|
||||
|
||||
Reference in New Issue
Block a user