Skip to content
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
9 changes: 8 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.5, 3.6, 3.7, 3.8, 3.9, "3.10", pypy3]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3]

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -45,3 +45,10 @@ jobs:
run: |
make clean-sphinx docs

success:
needs: build
runs-on: ubuntu-latest
name: test successful
steps:
- name: Success
run: echo Test successful
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Improvements
* Add support for Python 3.10.
(Jürgen Gmach)

* Drop support for Python 3.5 (EOL).
(Hugo van Kemenade)

* Distutils integration is deprecated and will be removed in the next major
version.

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ under the same license as Python, see LICENSE for details.
Supported platforms
-------------------

* Python 3.5+ or PyPy3
* Python 3.6+ or PyPy3

If you would like to use testtools for earlier Pythons, consult the compatibility docs:

Expand Down
2 changes: 1 addition & 1 deletion doc/hacking.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Coding style
In general, follow `PEP 8`_ except where consistency with the standard
library's unittest_ module would suggest otherwise.

testtools supports Python 3.5 and later.
testtools supports Python 3.6 and later.

Copyright assignment
--------------------
Expand Down
4 changes: 2 additions & 2 deletions doc/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Cross-Python compatibility
--------------------------

testtools gives you the very latest in unit testing technology in a way that
will work with Python 3.5+ and PyPy3.
will work with Python 3.6+ and PyPy3.

If you wish to use testtools with Python 2.4 or 2.5, then please use testtools
0.9.15.
Expand All @@ -94,4 +94,4 @@ If you wish to use testtools with Python 2.6 or 3.2, then please use testtools

If you wish to use testtools with Python 3.3 or 3.4, then please use testtools 2.3.0.

If you wish to use testtools with Python 2.7, then please use testtools 2.4.0.
If you wish to use testtools with Python 2.7 or 3.5, then please use testtools 2.4.0.
30 changes: 15 additions & 15 deletions scripts/_lp_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,16 @@ def assign_fix_committed_to_next(testtools, next_milestone):
"""Find all 'Fix Committed' and make sure they are in 'next'."""
fixed_bugs = list(testtools.searchTasks(status=FIX_COMMITTED))
for task in fixed_bugs:
LOG.debug("{}".format(task.title))
LOG.debug(f"{task.title}")
if task.milestone != next_milestone:
task.milestone = next_milestone
LOG.info("Re-assigning {}".format(task.title))
LOG.info(f"Re-assigning {task.title}")
task.lp_save()


def rename_milestone(next_milestone, new_name):
"""Rename 'next_milestone' to 'new_name'."""
LOG.info("Renaming {} to {}".format(next_milestone.name, new_name))
LOG.info(f"Renaming {next_milestone.name} to {new_name}")
next_milestone.name = new_name
next_milestone.lp_save()

Expand All @@ -103,8 +103,8 @@ def get_release_notes_and_changelog(news_path):
def is_heading_marker(line, marker_char):
return line and line == marker_char * len(line)

LOG.debug("Loading NEWS from {}".format(news_path))
with open(news_path, 'r') as news:
LOG.debug(f"Loading NEWS from {news_path}")
with open(news_path) as news:
for line in news:
line = line.strip()
if state is None:
Expand Down Expand Up @@ -146,7 +146,7 @@ def is_heading_marker(line, marker_char):
def release_milestone(milestone, release_notes, changelog):
date_released = datetime.now(tz=UTC)
LOG.info(
"Releasing milestone: {}, date {}".format(milestone.name, date_released))
f"Releasing milestone: {milestone.name}, date {date_released}")
release = milestone.createProductRelease(
date_released=date_released,
changelog=changelog,
Expand All @@ -159,20 +159,20 @@ def release_milestone(milestone, release_notes, changelog):

def create_milestone(series, name):
"""Create a new milestone in the same series as 'release_milestone'."""
LOG.info("Creating milestone {} in series {}".format(name, series.name))
LOG.info(f"Creating milestone {name} in series {series.name}")
return series.newMilestone(name=name)


def close_fixed_bugs(milestone):
tasks = list(milestone.searchTasks())
for task in tasks:
LOG.debug("Found {}".format(task.title))
LOG.debug(f"Found {task.title}")
if task.status == FIX_COMMITTED:
LOG.info("Closing {}".format(task.title))
LOG.info(f"Closing {task.title}")
task.status = FIX_RELEASED
else:
LOG.warning(
"Bug not fixed, removing from milestone: {}".format(task.title))
f"Bug not fixed, removing from milestone: {task.title}")
task.milestone = None
task.lp_save()

Expand All @@ -184,7 +184,7 @@ def upload_tarball(release, tarball_path):
with open(sig_path) as sig:
sig_content = sig.read()
tarball_name = os.path.basename(tarball_path)
LOG.info("Uploading tarball: {}".format(tarball_path))
LOG.info(f"Uploading tarball: {tarball_path}")
release.add_file(
file_type=CODE_RELEASE_TARBALL,
file_content=tarball_content, filename=tarball_name,
Expand All @@ -198,17 +198,17 @@ def release_project(launchpad, project_name, next_milestone_name):
next_milestone = testtools.getMilestone(name=next_milestone_name)
release_name, release_notes, changelog = get_release_notes_and_changelog(
get_path('NEWS'))
LOG.info("Releasing {} {}".format(project_name, release_name))
LOG.info(f"Releasing {project_name} {release_name}")
# Since reversing these operations is hard, and inspecting errors from
# Launchpad is also difficult, do some looking before leaping.
errors = []
tarball_path = get_path('dist/{}-{}.tar.gz'.format(project_name, release_name))
tarball_path = get_path(f'dist/{project_name}-{release_name}.tar.gz')
if not os.path.isfile(tarball_path):
errors.append("{} does not exist".format(tarball_path))
errors.append(f"{tarball_path} does not exist")
if not os.path.isfile(tarball_path + '.asc'):
errors.append("{} does not exist".format(tarball_path + '.asc'))
if testtools.getMilestone(name=release_name):
errors.append("Milestone {} exists on {}".format(release_name, project_name))
errors.append(f"Milestone {release_name} exists on {project_name}")
if errors:
for error in errors:
LOG.error(error)
Expand Down
2 changes: 1 addition & 1 deletion scripts/all-pythons
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,5 @@ def now():
if __name__ == '__main__':
sys.path.append(ROOT)
result = TestProtocolClient(sys.stdout)
for version in '3.5 3.6 3.7 3.8 3.9 3.10'.split():
for version in '3.6 3.7 3.8 3.9 3.10'.split():
run_for_python(version, result, sys.argv[1:])
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ classifier =
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


setuptools.setup(
python_requires='>=3.5',
python_requires='>=3.6',
cmdclass=cmd_class,
setup_requires=['pbr'],
pbr=True)
1 change: 0 additions & 1 deletion testtools/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

"""Compatibility support for python 2 and 3."""

__metaclass__ = type
__all__ = [
'_b',
'advance_iterator',
Expand Down
4 changes: 2 additions & 2 deletions testtools/content_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ def __repr__(self):
if self.parameters:
params = '; '
params += '; '.join(
sorted('{}="{}"'.format(k, v) for k, v in self.parameters.items()))
sorted(f'{k}="{v}"' for k, v in self.parameters.items()))
else:
params = ''
return "{}/{}{}".format(self.type, self.subtype, params)
return f"{self.type}/{self.subtype}{params}"


JSON = ContentType('application', 'json')
Expand Down
20 changes: 10 additions & 10 deletions testtools/matchers/_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(self, expected):
self.expected = expected

def __str__(self):
return "{}({!r})".format(self.__class__.__name__, self.expected)
return f"{self.__class__.__name__}({self.expected!r})"

def match(self, other):
if self.comparator(other, self.expected):
Expand All @@ -75,7 +75,7 @@ def __init__(self, actual, mismatch_string, reference,
@property
def expected(self):
warnings.warn(
'{}.expected deprecated after 1.8.1'.format(self.__class__.__name__),
f'{self.__class__.__name__}.expected deprecated after 1.8.1',
DeprecationWarning,
stacklevel=2,
)
Expand All @@ -84,7 +84,7 @@ def expected(self):
@property
def other(self):
warnings.warn(
'{}.other deprecated after 1.8.1'.format(self.__class__.__name__),
f'{self.__class__.__name__}.other deprecated after 1.8.1',
DeprecationWarning,
stacklevel=2,
)
Expand All @@ -102,7 +102,7 @@ def describe(self):
left, right = actual, reference
else:
left, right = reference, actual
return "{} {} {}".format(left, self._mismatch_string, right)
return f"{left} {self._mismatch_string} {right}"


class Equals(_BinaryComparison):
Expand Down Expand Up @@ -176,7 +176,7 @@ def __init__(self, expected):
self.expected = expected

def __str__(self):
return '{}({!r})'.format(self.__class__.__name__, self.expected)
return f'{self.__class__.__name__}({self.expected!r})'

def match(self, observed):
expected_only = list_subtract(self.expected, observed)
Expand Down Expand Up @@ -216,7 +216,7 @@ def __init__(self, expected):
self.expected = expected

def __str__(self):
return "StartsWith({!r})".format(self.expected)
return f"StartsWith({self.expected!r})"

def match(self, matchee):
if not matchee.startswith(self.expected):
Expand Down Expand Up @@ -251,7 +251,7 @@ def __init__(self, expected):
self.expected = expected

def __str__(self):
return "EndsWith({!r})".format(self.expected)
return f"EndsWith({self.expected!r})"

def match(self, matchee):
if not matchee.endswith(self.expected):
Expand Down Expand Up @@ -292,7 +292,7 @@ def describe(self):
else:
typestr = 'any of (%s)' % ', '.join(type.__name__ for type in
self.types)
return "'{}' is not an instance of {}".format(self.matchee, typestr)
return f"'{self.matchee}' is not an instance of {typestr}"


class DoesNotContain(Mismatch):
Expand All @@ -307,7 +307,7 @@ def __init__(self, matchee, needle):
self.needle = needle

def describe(self):
return "{!r} not in {!r}".format(self.needle, self.matchee)
return f"{self.needle!r} not in {self.matchee!r}"


class Contains(Matcher):
Expand All @@ -321,7 +321,7 @@ def __init__(self, needle):
self.needle = needle

def __str__(self):
return "Contains({!r})".format(self.needle)
return f"Contains({self.needle!r})"

def match(self, matchee):
try:
Expand Down
2 changes: 1 addition & 1 deletion testtools/matchers/_const.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __str__(self):

def match(self, value):
return Mismatch(
'Inevitable mismatch on {!r}'.format(value))
f'Inevitable mismatch on {value!r}')


def Never():
Expand Down
8 changes: 4 additions & 4 deletions testtools/matchers/_datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def update(self, **kws):
def __str__(self):
kws = []
for attr, matcher in sorted(self.kws.items()):
kws.append("{}={}".format(attr, matcher))
kws.append(f"{attr}={matcher}")
return "{}({})".format(self.__class__.__name__, ', '.join(kws))

def match(self, value):
Expand Down Expand Up @@ -208,18 +208,18 @@ def match(self, observed):
if common_length == 0:
raise AssertionError("common_length can't be 0 here")
if common_length > 1:
msg = "There were {} mismatches".format(common_length)
msg = f"There were {common_length} mismatches"
else:
msg = "There was 1 mismatch"
if len(remaining_matchers) > len(not_matched):
extra_matchers = remaining_matchers[common_length:]
msg += " and {} extra matcher".format(len(extra_matchers))
msg += f" and {len(extra_matchers)} extra matcher"
if len(extra_matchers) > 1:
msg += "s"
msg += ': ' + ', '.join(map(str, extra_matchers))
elif len(not_matched) > len(remaining_matchers):
extra_values = not_matched[common_length:]
msg += " and {} extra value".format(len(extra_values))
msg += f" and {len(extra_values)} extra value"
if len(extra_values) > 1:
msg += "s"
msg += ': ' + str(extra_values)
Expand Down
6 changes: 3 additions & 3 deletions testtools/matchers/_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, matchers):
self.matchers = matchers

def __str__(self):
return 'MatchesAllDict({})'.format(_format_matcher_dict(self.matchers))
return f'MatchesAllDict({_format_matcher_dict(self.matchers)})'

def match(self, observed):
mismatches = {}
Expand All @@ -56,7 +56,7 @@ def __init__(self, mismatches, details=None):
def describe(self):
lines = ['{']
lines.extend(
[' {!r}: {},'.format(key, mismatch.describe())
[f' {key!r}: {mismatch.describe()},'
for (key, mismatch) in sorted(self.mismatches.items())])
lines.append('}')
return '\n'.join(lines)
Expand Down Expand Up @@ -133,7 +133,7 @@ def match(self, super_dict):

def _format_matcher_dict(matchers):
return '{%s}' % (
', '.join(sorted('{!r}: {}'.format(k, v) for k, v in matchers.items())))
', '.join(sorted(f'{k!r}: {v}' for k, v in matchers.items())))


class _CombinedMatcher(Matcher):
Expand Down
2 changes: 1 addition & 1 deletion testtools/matchers/_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __str__(self):
flagstr = ", flags=%d" % self.flags
else:
flagstr = ""
return 'DocTestMatches({!r}{})'.format(self.want, flagstr)
return f'DocTestMatches({self.want!r}{flagstr})'

def _with_nl(self, actual):
result = self.want.__class__(actual)
Expand Down
4 changes: 2 additions & 2 deletions testtools/matchers/_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def match(self, other):
if self._is_instance:
expected_class = expected_class.__class__
if not issubclass(other[0], expected_class):
return Mismatch('{!r} is not a {!r}'.format(other[0], expected_class))
return Mismatch(f'{other[0]!r} is not a {expected_class!r}')
if self._is_instance:
if other[1].args != self.expected.args:
return Mismatch('{} has different arguments to {}.'.format(
Expand Down Expand Up @@ -95,7 +95,7 @@ def __init__(self, exception_matcher=None):
def match(self, matchee):
try:
result = matchee()
return Mismatch('{!r} returned {!r}'.format(matchee, result))
return Mismatch(f'{matchee!r} returned {result!r}')
# Catch all exceptions: Raises() should be able to match a
# KeyboardInterrupt or SystemExit.
except:
Expand Down
Loading