Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3.12.1 start time #284

Merged
merged 4 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
fail-fast: false

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
Expand All @@ -35,11 +35,11 @@ jobs:

- name: Format
run: black --check --diff green example
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
if: matrix.python-version == '3.12.0' && matrix.os == 'ubuntu-latest'

- name: Mypy
run: mypy .
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
run: mypy green example
if: matrix.python-version == '3.12.0' && matrix.os == 'ubuntu-latest'

- name: Test
run: |
Expand All @@ -50,10 +50,10 @@ jobs:
run: |
pip install --upgrade coveralls
green -tvvvvr green
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
if: matrix.python-version == '3.12.0' && matrix.os == 'ubuntu-latest'

- name: Coveralls
run: coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
if: matrix.python-version == '3.12.0' && matrix.os == 'ubuntu-latest'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ tags
# virtual environments
venv*
env*
.python-version

*.sublime-workspace

Expand Down
25 changes: 15 additions & 10 deletions green/loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from collections import OrderedDict
from doctest import DocTestSuite
from fnmatch import fnmatch
Expand All @@ -12,7 +14,7 @@
import traceback

from green.output import debug
from green.result import proto_test
from green import result
from green.suite import GreenTestSuite

python_file_pattern = re.compile(r"^[_a-z]\w*?\.py$", re.IGNORECASE)
Expand Down Expand Up @@ -293,7 +295,9 @@ def testFailure(self):
return None


def toProtoTestList(suite, test_list=None, doing_completions=False):
def toProtoTestList(
suite, test_list=None, doing_completions: bool = False
) -> list[result.ProtoTest]:
"""
Take a test suite and turn it into a list of ProtoTests.

Expand All @@ -314,22 +318,22 @@ def toProtoTestList(suite, test_list=None, doing_completions=False):
if isinstance(suite, unittest.TestCase):
# Skip actual blank TestCase objects that twisted inserts
if str(type(suite)) != "<class 'twisted.trial.unittest.TestCase'>":
test_list.append(proto_test(suite))
test_list.append(result.proto_test(suite))
else:
for i in suite:
toProtoTestList(i, test_list, doing_completions)
return test_list


def toParallelTargets(suite, targets):
def toParallelTargets(suite, targets: list[str]) -> list[str]:
"""
Produce a list of targets which should be tested in parallel.

For the most part this will be a list of test modules. The exception is
when a dotted name representing something more granular than a module
was input (like an individal test case or test method)
For the most part, this will be a list of test modules.
The exception is when a dotted name representing something more granular
than a module was input (like an individual test case or test method).
"""
targets = filter(lambda x: x != ".", targets)
targets = [x for x in targets if x != "."]
CleanCut marked this conversation as resolved.
Show resolved Hide resolved
# First, convert the suite to a proto test list - proto tests nicely
# parse things like the fully dotted name of the test and the
# finest-grained module it belongs to, which simplifies our job.
Expand All @@ -338,11 +342,12 @@ def toParallelTargets(suite, targets):
modules = {x.module for x in proto_test_list}
# Get the list of user-specified targets that are NOT modules
non_module_targets = []
target: str
for target in targets:
if not list(filter(None, [target in x for x in modules])):
if not list(filter(None, (target in x for x in modules))):
Copy link
Collaborator Author

@sodul sodul Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this can probably be replaced with something like:
if not any((target in x for x in modules)):

But I need to fully grasp the original condition first, this can be an optimization for the future PR.

non_module_targets.append(target)
# Main loop -- iterating through all loaded test methods
parallel_targets = []
parallel_targets: list[str] = []
for test in proto_test_list:
found = False
for target in non_module_targets:
Expand Down
101 changes: 54 additions & 47 deletions green/output.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from __future__ import annotations

from typing import Iterable, TYPE_CHECKING

from colorama import Fore, Style
from colorama.ansi import Cursor
from colorama.initialise import wrap_stream
Expand All @@ -8,14 +12,18 @@
import sys
from unidecode import unidecode

if TYPE_CHECKING:
from colorama.ansitowin32 import StreamWrapper
from colorama.initialise import _TextIOT

global debug_level
debug_level = 0

text_type = str
unicode = None # so pyflakes stops complaining


def debug(message, level=1):
def debug(message: str, level: int = 1):
"""
So we can tune how much debug output we get when we turn it on.
"""
Expand All @@ -28,73 +36,72 @@ class Colors:
A class to centralize wrapping strings in terminal colors.
"""

def __init__(self, termcolor=None):
"""
termcolor - If None, attempt to autodetect whether we are in a terminal
and turn on terminal colors if we think we are. If True, force
terminal colors on. If False, force terminal colors off.
def __init__(self, termcolor: bool | None = None):
"""Initialize the Colors object.

Args:
termcolor: If None, attempt to autodetect whether we are in a
terminal and turn on terminal colors if we think we are.
If True, force terminal colors on.
If False, force terminal colors off.
"""
if termcolor is None:
self.termcolor = sys.stdout.isatty()
else:
self.termcolor = termcolor
self.termcolor = sys.stdout.isatty() if termcolor is None else termcolor

def wrap(self, text, style):
def wrap(self, text: str, style: str) -> str:
if self.termcolor:
return f"{style}{text}{Style.RESET_ALL}"
else:
return text
return text

# Movement
def start_of_line(self):
def start_of_line(self) -> str:
return "\r"

def up(self, lines=1):
def up(self, lines: int = 1) -> str:
return Cursor.UP(lines)

# Real colors and styles
def bold(self, text):
def bold(self, text: str) -> str:
return self.wrap(text, Style.BRIGHT)

def blue(self, text):
def blue(self, text: str) -> str:
if platform.system() == "Windows": # pragma: no cover
# Default blue in windows is unreadable (such awful defaults...)
return self.wrap(text, Fore.CYAN)
else:
return self.wrap(text, Fore.BLUE)

def green(self, text):
def green(self, text: str) -> str:
return self.wrap(text, Fore.GREEN)

def red(self, text):
def red(self, text: str) -> str:
return self.wrap(text, Fore.RED)

def yellow(self, text):
def yellow(self, text: str) -> str:
return self.wrap(text, Fore.YELLOW)

# Abstracted colors and styles
def passing(self, text):
def passing(self, text: str) -> str:
return self.green(text)

def failing(self, text):
def failing(self, text: str) -> str:
return self.red(text)

def error(self, text):
def error(self, text: str) -> str:
return self.red(text)

def skipped(self, text):
def skipped(self, text: str) -> str:
return self.blue(text)

def unexpectedSuccess(self, text):
def unexpectedSuccess(self, text: str) -> str:
return self.yellow(text)

def expectedFailure(self, text):
def expectedFailure(self, text: str) -> str:
return self.yellow(text)

def moduleName(self, text):
def moduleName(self, text: str) -> str:
return self.bold(text)

def className(self, text):
def className(self, text: str) -> str:
return text


Expand All @@ -111,19 +118,19 @@ class GreenStream:
writelines(lines)
"""

indent_spaces = 2
_ascii_only_output = False # default to printing output in unicode
indent_spaces: int = 2
_ascii_only_output: bool = False # default to printing output in unicode
coverage_pattern = re.compile(r"TOTAL\s+\d+\s+\d+\s+(?P<percent>\d+)%")

def __init__(
self,
stream,
override_appveyor=False,
disable_windows=False,
disable_unidecode=False,
):
stream: _TextIOT,
override_appveyor: bool = False,
disable_windows: bool = False,
disable_unidecode: bool = False,
) -> None:
self.disable_unidecode = disable_unidecode
self.stream = stream
self.stream: _TextIOT | StreamWrapper = stream
# Ironically, Windows CI platforms such as GitHub Actions and AppVeyor don't support windows
# win32 system calls for colors, but it WILL interpret posix ansi escape codes! (The
# opposite of an actual windows command prompt)
Expand All @@ -135,7 +142,7 @@ def __init__(
if override_appveyor or (
(on_windows and not on_windows_ci) and not disable_windows
): # pragma: no cover
self.stream = wrap_stream(self.stream, None, None, None, True)
self.stream = wrap_stream(stream, None, None, False, True)
# set output is ascii-only
self._ascii_only_output = True
self.closed = False
Expand All @@ -147,23 +154,23 @@ def __init__(
# Susceptible to false-positives if other matching lines are output,
# so set this to None immediately before running a coverage report to
# guarantee accuracy.
self.coverage_percent = None
self.coverage_percent: int | None = None

def flush(self):
def flush(self) -> None:
self.stream.flush()

def writeln(self, text=""):
def writeln(self, text: str = "") -> None:
self.write(text + "\n")

def write(self, text):
if type(text) == bytes:
def write(self, text: str) -> None:
if isinstance(text, bytes):
text = text.decode("utf-8")
# Compensate for windows' anti-social unicode behavior
if self._ascii_only_output and not self.disable_unidecode:
# Windows doesn't actually want unicode, so we get
# the closest ASCII equivalent
text = text_type(unidecode(text))
# Since coverage doesn't like us switching out it's stream to run extra
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always thought the english language was really off base when it decided it's was an exception to the apostrophe rules.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seriously. English is just messed up. I know the rule, and still I mess it up when I'm not thinking about it.

# Since coverage doesn't like us switching out its stream to run extra
# reports to look for percent covered. We should replace this with
# grabbing the percentage directly from coverage if we can figure out
# how.
Expand All @@ -174,14 +181,14 @@ def write(self, text):
self.coverage_percent = int(percent_str)
self.stream.write(text)

def writelines(self, lines):
def writelines(self, lines: Iterable[str]) -> None:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI Iterable[str] is double edged as typing is concerned because strings are iterable lists of strings, so this typing will not catch if a caller is passing a plain string. This is an open 'bug' in mypy, but still better than having not typing at all.

"""
Just for better compatibility with real file objects
"""
for line in lines:
self.write(line)

def formatText(self, text, indent=0, outcome_char=""):
def formatText(self, text: str, indent: int = 0, outcome_char: str = "") -> str:
# We'll go through each line in the text, modify it, and store it in a
# new list
updated_lines = []
Expand All @@ -197,15 +204,15 @@ def formatText(self, text, indent=0, outcome_char=""):
output = "\n".join(updated_lines)
return output

def formatLine(self, line, indent=0, outcome_char=""):
def formatLine(self, line: str, indent: int = 0, outcome_char: str = "") -> str:
"""
Takes a single line, optionally adds an indent and/or outcome
character to the beginning of the line.
"""
actual_spaces = (indent * self.indent_spaces) - len(outcome_char)
return outcome_char + " " * actual_spaces + line

def isatty(self):
def isatty(self) -> bool:
"""
Wrap internal self.stream.isatty.
"""
Expand Down
Loading
Loading