Skip to content

Commit

Permalink
refine import chain
Browse files Browse the repository at this point in the history
  • Loading branch information
Pyifan committed Jan 2, 2025
1 parent 84442ee commit 8f73da5
Show file tree
Hide file tree
Showing 18 changed files with 222 additions and 158 deletions.
2 changes: 1 addition & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
ignored-modules=testplan.exporters.testing

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
Expand Down
11 changes: 7 additions & 4 deletions testplan/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import traceback
import threading

from typing import Optional, Union, Type, List, Callable
from typing import Optional, Union, Type, List, Callable, TYPE_CHECKING
from types import ModuleType
from schema import And

Expand All @@ -22,7 +22,7 @@
from testplan.environment import Environments
from testplan.parser import TestplanParser
from testplan.runnable import TestRunner, TestRunnerConfig, TestRunnerResult
from testplan.runnable.interactive import TestRunnerIHandler

from testplan.runners.local import LocalRunner
from testplan.runners.base import Executor
from testplan.report.testing.styles import Style
Expand All @@ -32,6 +32,9 @@
from testplan.testing.multitest.test_metadata import TestPlanMetadata
from testplan.testing.ordering import BaseSorter

if TYPE_CHECKING:
from testplan.runnable.interactive.base import TestRunnerIHandler


def pdb_drop_handler(sig, frame):
"""
Expand Down Expand Up @@ -192,7 +195,7 @@ def __init__(
verbose: bool = False,
debug: bool = False,
timeout: int = defaults.TESTPLAN_TIMEOUT,
interactive_handler: Type[TestRunnerIHandler] = TestRunnerIHandler,
interactive_handler: Type["TestRunnerIHandler"] = None,
extra_deps: Optional[List[Union[str, ModuleType]]] = None,
label: Optional[str] = None,
driver_info: bool = False,
Expand Down Expand Up @@ -401,7 +404,7 @@ def main_wrapper(
verbose=False,
debug=False,
timeout=defaults.TESTPLAN_TIMEOUT,
interactive_handler=TestRunnerIHandler,
interactive_handler=None,
extra_deps=None,
label=None,
driver_info=False,
Expand Down
10 changes: 9 additions & 1 deletion testplan/common/entity/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1225,13 +1225,21 @@ def run(self):
"""
try:
if self.cfg.interactive_port is not None:

from testplan.runnable.interactive.base import (
TestRunnerIHandler,
)

if self._ihandler is not None:
raise RuntimeError(
f"{self} already has an active {self._ihandler}"
)

self.logger.user_info("Starting %s in interactive mode", self)
self._ihandler = self.cfg.interactive_handler(
handler_class = (
self.cfg.interactive_handler or TestRunnerIHandler
)
self._ihandler = handler_class(
target=self,
http_port=self.cfg.interactive_port,
pre_start_environments=self.cfg.pre_start_environments,
Expand Down
39 changes: 39 additions & 0 deletions testplan/common/exporters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from testplan.common.config import Config, Configurable
from testplan.common.utils import strings
from testplan.common.utils.comparison import is_regex
from testplan.common.utils.timing import utcnow
from testplan.report import TestReport

Expand Down Expand Up @@ -172,3 +173,41 @@ def run_exporter(
exp_result.result = result
export_context.results.append(exp_result)
return exp_result


def format_cell_data(data, limit):
"""
Change the str representation of values in data if they represent regex or
lambda functions. Also limit the length of these strings.
:param data: List of values to be formatted.
:type data: ``list``
:param limit: The number of characters allowed in each string.
:type limit: ``int``
:return: List of formatted and limited strings.
:rtype: ``list``
"""
for i, value in enumerate(data):
if is_regex(value):
data[i] = "REGEX('{}')".format(value.pattern)
elif "lambda" in str(value):
data[i] = "<lambda>"

return _limit_cell_length(data, limit)


def _limit_cell_length(iterable, limit):
"""
Limit the length of each string in the iterable.
:param iterable: iterable object containing string values.
:type iterable: ``list`` or ``tuple`` etc.
:param limit: The number of characters allowed in each string
:type limit: ``int``
:return: The list of limited strings.
:rtype: ``list`` of ``str``
"""
return [
val if len(str(val)) < limit else "{}...".format(str(val)[: limit - 3])
for val in iterable
]
117 changes: 75 additions & 42 deletions testplan/common/exporters/pdf.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"""
Utilities for generating pdf files via Reportlab.
"""

import re
import os
import itertools

from reportlab.platypus import Table
from reportlab.lib import colors
from reportlab.pdfbase.pdfmetrics import stringWidth

from testplan.common.utils.comparison import is_regex
from testplan.common.exporters import constants

from testplan.common.exporters import constants, _limit_cell_length

# If you increase this too much Reportlab starts having
# performance issues and takes exponentially longer to render the PDF
Expand Down Expand Up @@ -112,44 +112,6 @@ def create_base_tables(data, style, col_widths, max_rows=MAX_TABLE_ROWS):
]


def format_cell_data(data, limit):
"""
Change the str representation of values in data if they represent regex or
lambda functions. Also limit the length of these strings.
:param data: List of values to be formatted.
:type data: ``list``
:param limit: The number of characters allowed in each string.
:type limit: ``int``
:return: List of formatted and limited strings.
:rtype: ``list``
"""
for i, value in enumerate(data):
if is_regex(value):
data[i] = "REGEX('{}')".format(value.pattern)
elif "lambda" in str(value):
data[i] = "<lambda>"

return _limit_cell_length(data, limit)


def _limit_cell_length(iterable, limit):
"""
Limit the length of each string in the iterable.
:param iterable: iterable object containing string values.
:type iterable: ``list`` or ``tuple`` etc.
:param limit: The number of characters allowed in each string
:type limit: ``int``
:return: The list of limited strings.
:rtype: ``list`` of ``str``
"""
return [
val if len(str(val)) < limit else "{}...".format(str(val)[: limit - 3])
for val in iterable
]


def _add_row_index(columns, rows, indices):
"""
Add row indices as the first column to the columns and rows data.
Expand Down Expand Up @@ -771,3 +733,74 @@ def append(self, content, style=None):

# This changes `self.end`, so needs to happen last
self.content.extend(content)


def split_line(line, max_width, get_width_func=None):
"""
Split `line` into multi-lines if width exceeds `max_width`.
:param line: Line to be split.
:param max_width: Maximum length of each line (unit: px).
:param get_width_func: A function which computes width of string
according to font and font size.
:return: list of lines
"""
result = []
total_width = 0
tmp_str = ""
get_text_width = (
get_width_func
if get_width_func
else lambda text: stringWidth(text, "Helvetica", 9)
)

for ch in line:
char_width = get_text_width(ch)
if total_width + char_width <= max_width or not tmp_str:
tmp_str += ch
total_width += char_width
else:
result.append(tmp_str)
tmp_str = ch
total_width = char_width

if tmp_str:
result.append(tmp_str)

return result


def split_text(
text, font_name, font_size, max_width, keep_leading_whitespace=False
):
"""
Wraps `text` within given `max_width` limit (measured in px), keeping
initial indentation of each line (and generated lines) if
`keep_leading_whitespace` is True.
:param text: Text to be split.
:param font_name: Font name.
:param font_size: Font size.
:param max_width: Maximum length of each line (unit: px).
:param keep_leading_whitespace: each split line keeps the leading
whitespace.
:return: list of lines
"""

def get_text_width(text, name=font_name, size=font_size):
return stringWidth(text, name, size)

result = []
lines = [line for line in re.split(r"[\r\n]+", text) if line]
cutoff_regex = re.compile(r"^(\s|\t)+")
for line in lines:
line_list = split_line(line, max_width, get_text_width)
if keep_leading_whitespace and len(line_list) > 1:
first, rest = line_list[0], line_list[1:]
indent_match = cutoff_regex.match(first)
if indent_match:
prefix = first[indent_match.start() : indent_match.end()]
line_list = [first] + ["{}{}".format(prefix, s) for s in rest]
result.extend(line_list)

return os.linesep.join(result)
3 changes: 3 additions & 0 deletions testplan/common/utils/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Test progress information (e.g. Pass / Fail status)
- Exporter statuses
"""

import logging
import os
import sys
Expand Down Expand Up @@ -113,6 +114,7 @@ def _initial_setup():
:return: root logger object and stdout logging handler
:type: ``tuple``
"""

logging.setLoggerClass(TestplanLogger)
root_logger = logging.getLogger(LOGGER_NAME)

Expand All @@ -139,6 +141,7 @@ def _initial_setup():
# self.logger. However, for classes that don't inherit from Loggable or for
# code outside of a class we provide the root testplan.common.utils.logger
# object here as TESTPLAN_LOGGER.

TESTPLAN_LOGGER, STDOUT_HANDLER = _initial_setup()


Expand Down
79 changes: 2 additions & 77 deletions testplan/common/utils/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
colorama.init()
from termcolor import colored

from reportlab.pdfbase.pdfmetrics import stringWidth


_DESCRIPTION_CUTOFF_REGEX = re.compile(r"^(\s|\t)+")


def map_to_str(value):
"""
Expand Down Expand Up @@ -91,8 +86,9 @@ def format_description(description):
..Foo bar
1 2 3 4
"""
cutoff_regex = re.compile(r"^(\s|\t)+")
lines = [line for line in description.split(os.linesep) if line.strip()]
matches = [_DESCRIPTION_CUTOFF_REGEX.match(line) for line in lines]
matches = [cutoff_regex.match(line) for line in lines]
if matches:
min_offset = min(
match.end() if match is not None else 0 for match in matches
Expand Down Expand Up @@ -187,77 +183,6 @@ def wrap(text, width=150):
return os.linesep.join(result)


def split_line(line, max_width, get_width_func=None):
"""
Split `line` into multi-lines if width exceeds `max_width`.
:param line: Line to be split.
:param max_width: Maximum length of each line (unit: px).
:param get_width_func: A function which computes width of string
according to font and font size.
:return: list of lines
"""
result = []
total_width = 0
tmp_str = ""
get_text_width = (
get_width_func
if get_width_func
else lambda text: stringWidth(text, "Helvetica", 9)
)

for ch in line:
char_width = get_text_width(ch)
if total_width + char_width <= max_width or not tmp_str:
tmp_str += ch
total_width += char_width
else:
result.append(tmp_str)
tmp_str = ch
total_width = char_width

if tmp_str:
result.append(tmp_str)

return result


def split_text(
text, font_name, font_size, max_width, keep_leading_whitespace=False
):
"""
Wraps `text` within given `max_width` limit (measured in px), keeping
initial indentation of each line (and generated lines) if
`keep_leading_whitespace` is True.
:param text: Text to be split.
:param font_name: Font name.
:param font_size: Font size.
:param max_width: Maximum length of each line (unit: px).
:param keep_leading_whitespace: each split line keeps the leading
whitespace.
:return: list of lines
"""

def get_text_width(text, name=font_name, size=font_size):
return stringWidth(text, name, size)

result = []
lines = [line for line in re.split(r"[\r\n]+", text) if line]

for line in lines:
line_list = split_line(line, max_width, get_text_width)
if keep_leading_whitespace and len(line_list) > 1:
first, rest = line_list[0], line_list[1:]
indent_match = _DESCRIPTION_CUTOFF_REGEX.match(first)
if indent_match:
prefix = first[indent_match.start() : indent_match.end()]
line_list = [first] + ["{}{}".format(prefix, s) for s in rest]
result.extend(line_list)

return os.linesep.join(result)


def indent(lines_str, indent_size=2):
"""
Indent a multi-line string with a common indent.
Expand Down
Loading

0 comments on commit 8f73da5

Please sign in to comment.