2020-08-19 12:40:10 +02:00
|
|
|
import logging
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from types import TracebackType
|
|
|
|
from typing import Iterable, Optional, Type, cast
|
|
|
|
|
|
|
|
|
|
|
|
class ExtraConsoleOutputInTestException(Exception):
|
|
|
|
pass
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-08-19 12:40:10 +02:00
|
|
|
class ExtraConsoleOutputFinder:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.latest_test_name = ""
|
|
|
|
valid_line_patterns = [
|
|
|
|
# Example: Running zerver.tests.test_attachments.AttachmentsTests.test_delete_unauthenticated
|
|
|
|
"^Running ",
|
|
|
|
# Example: ** Test is TOO slow: analytics.tests.test_counts.TestRealmActiveHumans.test_end_to_end (0.581 s)
|
|
|
|
"^\\*\\* Test is TOO slow: ",
|
|
|
|
"^----------------------------------------------------------------------",
|
|
|
|
# Example: INFO: URL coverage report is in var/url_coverage.txt
|
|
|
|
"^INFO: URL coverage report is in",
|
|
|
|
# Example: INFO: Try running: ./tools/create-test-api-docs
|
|
|
|
"^INFO: Try running:",
|
|
|
|
# Example: -- Running tests in parallel mode with 4 processes
|
|
|
|
"^-- Running tests in",
|
|
|
|
"^OK",
|
|
|
|
# Example: Ran 2139 tests in 115.659s
|
|
|
|
"^Ran [0-9]+ tests in",
|
|
|
|
# Destroying test database for alias 'default'...
|
|
|
|
"^Destroying test database for alias ",
|
|
|
|
"^Using existing clone",
|
|
|
|
"^\\*\\* Skipping ",
|
|
|
|
]
|
|
|
|
self.compiled_line_patterns = []
|
|
|
|
for pattern in valid_line_patterns:
|
|
|
|
self.compiled_line_patterns.append(re.compile(pattern))
|
|
|
|
self.full_extra_output = ""
|
|
|
|
|
|
|
|
def find_extra_output(self, data: str) -> None:
|
|
|
|
lines = data.split('\n')
|
|
|
|
for line in lines:
|
|
|
|
if not line:
|
|
|
|
continue
|
|
|
|
found_extra_output = True
|
|
|
|
for compiled_pattern in self.compiled_line_patterns:
|
|
|
|
if compiled_pattern.match(line):
|
|
|
|
found_extra_output = False
|
|
|
|
break
|
|
|
|
if found_extra_output:
|
|
|
|
self.full_extra_output += f'{line}\n'
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
class TeeStderrAndFindExtraConsoleOutput:
|
2020-08-19 12:40:10 +02:00
|
|
|
def __init__(self, extra_output_finder: ExtraConsoleOutputFinder) -> None:
|
|
|
|
self.stderr_stream = sys.stderr
|
|
|
|
|
|
|
|
# get shared console handler instance from any logger that have it
|
2021-02-12 08:19:30 +01:00
|
|
|
self.console_log_handler = cast(
|
|
|
|
logging.StreamHandler, logging.getLogger('django.server').handlers[0]
|
|
|
|
)
|
2020-08-19 12:40:10 +02:00
|
|
|
|
|
|
|
assert isinstance(self.console_log_handler, logging.StreamHandler)
|
|
|
|
assert self.console_log_handler.stream == sys.stderr
|
|
|
|
self.extra_output_finder = extra_output_finder
|
|
|
|
|
|
|
|
def __enter__(self) -> None:
|
|
|
|
sys.stderr = self # type: ignore[assignment] # Doing tee by swapping stderr stream with custom file like class
|
|
|
|
self.console_log_handler.stream = self # type: ignore[assignment] # Doing tee by swapping stderr stream with custom file like class
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
def __exit__(
|
|
|
|
self,
|
|
|
|
exc_type: Optional[Type[BaseException]],
|
|
|
|
exc_value: Optional[BaseException],
|
|
|
|
traceback: Optional[TracebackType],
|
|
|
|
) -> None:
|
2020-08-19 12:40:10 +02:00
|
|
|
sys.stderr = self.stderr_stream
|
|
|
|
self.console_log_handler.stream = sys.stderr
|
|
|
|
|
|
|
|
def write(self, data: str) -> None:
|
|
|
|
self.stderr_stream.write(data)
|
|
|
|
self.extra_output_finder.find_extra_output(data)
|
|
|
|
|
|
|
|
def writelines(self, data: Iterable[str]) -> None:
|
|
|
|
self.stderr_stream.writelines(data)
|
|
|
|
lines = "".join(data)
|
|
|
|
self.extra_output_finder.find_extra_output(lines)
|
|
|
|
|
|
|
|
def flush(self) -> None:
|
|
|
|
self.stderr_stream.flush()
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
class TeeStdoutAndFindExtraConsoleOutput:
|
2020-08-19 12:40:10 +02:00
|
|
|
def __init__(self, extra_output_finder: ExtraConsoleOutputFinder) -> None:
|
|
|
|
self.stdout_stream = sys.stdout
|
|
|
|
self.extra_output_finder = extra_output_finder
|
|
|
|
|
|
|
|
def __enter__(self) -> None:
|
|
|
|
sys.stdout = self # type: ignore[assignment] # Doing tee by swapping stderr stream with custom file like class
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
def __exit__(
|
|
|
|
self,
|
|
|
|
exc_type: Optional[Type[BaseException]],
|
|
|
|
exc_value: Optional[BaseException],
|
|
|
|
traceback: Optional[TracebackType],
|
|
|
|
) -> None:
|
2020-08-19 12:40:10 +02:00
|
|
|
sys.stdout = self.stdout_stream
|
|
|
|
|
|
|
|
def write(self, data: str) -> None:
|
|
|
|
self.stdout_stream.write(data)
|
|
|
|
self.extra_output_finder.find_extra_output(data)
|
|
|
|
|
|
|
|
def writelines(self, data: Iterable[str]) -> None:
|
|
|
|
self.stdout_stream.writelines(data)
|
|
|
|
lines = "".join(data)
|
|
|
|
self.extra_output_finder.find_extra_output(lines)
|
|
|
|
|
|
|
|
def flush(self) -> None:
|
|
|
|
self.stdout_stream.flush()
|