Merge ~cjwatson/lazr.delegates:no-py2 into lazr.delegates:main

Proposed by Colin Watson
Status: Merged
Merged at revision: edccc841995e77e9dbd61464b30b74ae63cb8dcd
Proposed branch: ~cjwatson/lazr.delegates:no-py2
Merge into: lazr.delegates:main
Diff against target: 696 lines (+78/-270)
11 files modified
.pre-commit-config.yaml (+5/-0)
NEWS.rst (+3/-3)
dev/null (+0/-159)
setup.py (+4/-5)
src/lazr/delegates/__init__.py (+5/-11)
src/lazr/delegates/_delegates.py (+51/-58)
src/lazr/delegates/docs/conf.py (+0/-2)
src/lazr/delegates/tests/test_api.py (+1/-8)
src/lazr/delegates/tests/test_docs.py (+0/-9)
src/lazr/delegates/tests/test_passthrough.py (+3/-3)
tox.ini (+6/-12)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+413588@code.launchpad.net

Commit message

Drop Python 2 support

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) wrote (last edit ):

LGTM with two minor nitpicks

review: Approve
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

Looks good! Thank you for the update.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
2index 8691251..5d1fb12 100644
3--- a/.pre-commit-config.yaml
4+++ b/.pre-commit-config.yaml
5@@ -16,3 +16,8 @@ repos:
6 rev: ee781d3ce0ddf835267764f27f4ffdd2dd21fa27
7 hooks:
8 - id: woke-from-source
9+- repo: https://github.com/asottile/pyupgrade
10+ rev: v2.31.0
11+ hooks:
12+ - id: pyupgrade
13+ args: [--keep-percent-format, --py3-plus]
14diff --git a/NEWS.rst b/NEWS.rst
15index af21504..80e1684 100644
16--- a/NEWS.rst
17+++ b/NEWS.rst
18@@ -2,12 +2,12 @@
19 NEWS for lazr.delegates
20 =======================
21
22-2.0.5
23+2.1.0
24 =====
25 - Officially add support for Python 3.6, 3.7, 3.8, 3.9, and 3.10.
26-- Drop support for Python 3.2, 3.3, and 3.4.
27+- Drop support for Python 2, 3.2, 3.3, and 3.4.
28 - Test using ``zope.testrunner`` rather than ``nose``.
29-- Combine coverage for Python 2 and 3.
30+- Bring coverage to 100%.
31
32
33 2.0.4 (2017-10-20)
34diff --git a/setup.py b/setup.py
35index abe2368..9474d1d 100755
36--- a/setup.py
37+++ b/setup.py
38@@ -49,8 +49,6 @@ delegating behavior.
39 "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
40 "Operating System :: OS Independent",
41 'Programming Language :: Python',
42- 'Programming Language :: Python :: 2',
43- 'Programming Language :: Python :: 2.7',
44 'Programming Language :: Python :: 3',
45 'Programming Language :: Python :: 3.5',
46 'Programming Language :: Python :: 3.6',
47@@ -59,7 +57,8 @@ delegating behavior.
48 'Programming Language :: Python :: 3.9',
49 'Programming Language :: Python :: 3.10',
50 ],
51- extras_require={
52- "docs": ["Sphinx"],
53- },
54+ extras_require={
55+ "docs": ["Sphinx"],
56+ },
57+ python_requires=">=3.5",
58 )
59diff --git a/src/lazr/delegates/__init__.py b/src/lazr/delegates/__init__.py
60index b0b15de..eb56e29 100644
61--- a/src/lazr/delegates/__init__.py
62+++ b/src/lazr/delegates/__init__.py
63@@ -17,6 +17,7 @@
64 """Decorator helpers that simplify class composition."""
65
66 __all__ = [
67+ 'Passthrough',
68 'delegate_to',
69 ]
70
71@@ -24,14 +25,7 @@ __all__ = [
72 from lazr.delegates._version import __version__
73 __version__
74
75-from lazr.delegates._passthrough import Passthrough
76-
77-# The class decorator syntax is different in Python 2 vs. Python 3.
78-import sys
79-if sys.version_info[0] == 2:
80- from lazr.delegates._python2 import delegate_to
81- # The legacy API is only compatible with Python 2.
82- from lazr.delegates._delegates import delegates
83- __all__.append('delegates')
84-else:
85- from lazr.delegates._python3 import delegate_to
86+from lazr.delegates._delegates import (
87+ Passthrough,
88+ delegate_to,
89+ )
90diff --git a/src/lazr/delegates/_delegates.py b/src/lazr/delegates/_delegates.py
91index ae09920..eb7c1b3 100644
92--- a/src/lazr/delegates/_delegates.py
93+++ b/src/lazr/delegates/_delegates.py
94@@ -1,4 +1,4 @@
95-# Copyright 2008-2015 Canonical Ltd. All rights reserved.
96+# Copyright 2008-2022 Canonical Ltd. All rights reserved.
97 #
98 # This file is part of lazr.delegates.
99 #
100@@ -16,34 +16,22 @@
101
102 """Decorator helpers that simplify class composition."""
103
104-from __future__ import absolute_import, print_function, unicode_literals
105-
106-
107-__metaclass__ = type
108 __all__ = [
109- 'delegates',
110+ 'Passthrough',
111+ 'delegate_to',
112 ]
113
114-
115-import sys
116-from types import ClassType
117-
118-from zope.interface.advice import addClassAdvisor
119 from zope.interface import classImplements
120-from zope.interface.interfaces import IInterface
121
122-from lazr.delegates._passthrough import Passthrough
123
124-
125-def delegates(interface_spec, context='context'):
126+def delegate_to(*interfaces, context='context'):
127 """Make an adapter into a decorator.
128
129 Use like:
130
131+ @implementer(IRosettaProject)
132+ @delegate_to(IProject)
133 class RosettaProject:
134- implements(IRosettaProject)
135- delegates(IProject)
136-
137 def __init__(self, context):
138 self.context = context
139
140@@ -53,10 +41,9 @@ def delegates(interface_spec, context='context'):
141 If you want to use a different name than "context" then you can explicitly
142 say so:
143
144+ @implementer(IRosettaProject)
145+ @delegate_to(IProject, context='project')
146 class RosettaProject:
147- implements(IRosettaProject)
148- delegates(IProject, context='project')
149-
150 def __init__(self, project):
151 self.project = project
152
153@@ -67,45 +54,51 @@ def delegates(interface_spec, context='context'):
154
155 The minimal decorator looks like this:
156
157+ @delegate_to(IProject)
158 class RosettaProject:
159- delegates(IProject)
160-
161 def __init__(self, context):
162 self.context = context
163-
164 """
165- # pylint: disable-msg=W0212
166- frame = sys._getframe(1)
167- locals = frame.f_locals
168-
169- # Try to make sure we were called from a class def
170- if (locals is frame.f_globals) or ('__module__' not in locals):
171- raise TypeError(
172- "delegates() can be used only from a class definition.")
173-
174- locals['__delegates_advice_data__'] = interface_spec, context
175- addClassAdvisor(_delegates_advice, depth=2)
176-
177-
178-def _delegates_advice(cls):
179- """Add a Passthrough class for each missing interface attribute.
180-
181- This function connects the decorator class to the delegate class.
182- Only new-style classes are supported.
183+ if len(interfaces) == 0:
184+ raise TypeError('At least one interface is required')
185+ def _decorator(cls):
186+ missing = object()
187+ for interface in interfaces:
188+ classImplements(cls, interface)
189+ for name in interface:
190+ if getattr(cls, name, missing) is missing:
191+ setattr(cls, name, Passthrough(name, context))
192+ return cls
193+ return _decorator
194+
195+
196+class Passthrough:
197+ """Call the delegated class for the decorator class.
198+
199+ If the ``adaptation`` argument is not None, it should be a callable. It
200+ will be called with the context, and should return an object that will
201+ have the delegated attribute. The ``adaptation`` argument is expected to
202+ be used with an interface, to adapt the context.
203 """
204- interface_spec, contextvar = cls.__dict__['__delegates_advice_data__']
205- del cls.__delegates_advice_data__
206- if isinstance(cls, ClassType):
207- raise TypeError(
208- 'Cannot use delegates() on a classic class: %s.' % cls)
209- if IInterface.providedBy(interface_spec):
210- interfaces = [interface_spec]
211- else:
212- interfaces = interface_spec
213- not_found = object()
214- for interface in interfaces:
215- classImplements(cls, interface)
216- for name in interface:
217- if getattr(cls, name, not_found) is not_found:
218- setattr(cls, name, Passthrough(name, contextvar))
219- return cls
220+ def __init__(self, name, contextvar, adaptation=None):
221+ self.name = name
222+ self.contextvar = contextvar
223+ self.adaptation = adaptation
224+
225+ def __get__(self, inst, cls=None):
226+ if inst is None:
227+ return self
228+ else:
229+ context = getattr(inst, self.contextvar)
230+ if self.adaptation is not None:
231+ context = self.adaptation(context)
232+ return getattr(context, self.name)
233+
234+ def __set__(self, inst, value):
235+ context = getattr(inst, self.contextvar)
236+ if self.adaptation is not None:
237+ context = self.adaptation(context)
238+ setattr(context, self.name, value)
239+
240+ def __delete__(self, inst):
241+ raise NotImplementedError
242diff --git a/src/lazr/delegates/_passthrough.py b/src/lazr/delegates/_passthrough.py
243deleted file mode 100644
244index 144f689..0000000
245--- a/src/lazr/delegates/_passthrough.py
246+++ /dev/null
247@@ -1,55 +0,0 @@
248-# Copyright 2008-2015 Canonical Ltd. All rights reserved.
249-#
250-# This file is part of lazr.delegates.
251-#
252-# lazr.delegates is free software: you can redistribute it and/or modify it
253-# under the terms of the GNU Lesser General Public License as published by
254-# the Free Software Foundation, version 3 of the License.
255-#
256-# lazr.delegates is distributed in the hope that it will be useful, but
257-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
258-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
259-# License for more details.
260-#
261-# You should have received a copy of the GNU Lesser General Public License
262-# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
263-
264-from __future__ import absolute_import, print_function, unicode_literals
265-
266-
267-__metaclass__ = type
268-__all__ = [
269- 'Passthrough',
270- ]
271-
272-
273-class Passthrough:
274- """Call the delegated class for the decorator class.
275-
276- If the ``adaptation`` argument is not None, it should be a callable. It
277- will be called with the context, and should return an object that will
278- have the delegated attribute. The ``adaptation`` argument is expected to
279- be used with an interface, to adapt the context.
280- """
281- def __init__(self, name, contextvar, adaptation=None):
282- self.name = name
283- self.contextvar = contextvar
284- self.adaptation = adaptation
285-
286- def __get__(self, inst, cls=None):
287- if inst is None:
288- return self
289- else:
290- context = getattr(inst, self.contextvar)
291- if self.adaptation is not None:
292- context = self.adaptation(context)
293- return getattr(context, self.name)
294-
295- def __set__(self, inst, value):
296- context = getattr(inst, self.contextvar)
297- if self.adaptation is not None:
298- context = self.adaptation(context)
299- setattr(context, self.name, value)
300-
301- def __delete__(self, inst):
302- raise NotImplementedError
303diff --git a/src/lazr/delegates/_python2.py b/src/lazr/delegates/_python2.py
304deleted file mode 100644
305index 2ce472a..0000000
306--- a/src/lazr/delegates/_python2.py
307+++ /dev/null
308@@ -1,38 +0,0 @@
309-# Copyright 2014-2015 Canonical Ltd. All rights reserved.
310-#
311-# This file is part of lazr.delegates.
312-#
313-# lazr.delegates is free software: you can redistribute it and/or modify it
314-# under the terms of the GNU Lesser General Public License as published by
315-# the Free Software Foundation, version 3 of the License.
316-#
317-# lazr.delegates is distributed in the hope that it will be useful, but
318-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
319-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
320-# License for more details.
321-#
322-# You should have received a copy of the GNU Lesser General Public License
323-# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
324-
325-"""Class decorator definition for Python 2."""
326-
327-from zope.interface import classImplements
328-
329-from lazr.delegates._passthrough import Passthrough
330-
331-
332-def delegate_to(*interfaces, **kws):
333- context = kws.pop('context', 'context')
334- if len(kws) > 0:
335- raise TypeError('Too many arguments')
336- if len(interfaces) == 0:
337- raise TypeError('At least one interface is required')
338- def _decorator(cls):
339- missing = object()
340- for interface in interfaces:
341- classImplements(cls, interface)
342- for name in interface:
343- if getattr(cls, name, missing) is missing:
344- setattr(cls, name, Passthrough(name, context))
345- return cls
346- return _decorator
347diff --git a/src/lazr/delegates/_python3.py b/src/lazr/delegates/_python3.py
348deleted file mode 100644
349index f9430f5..0000000
350--- a/src/lazr/delegates/_python3.py
351+++ /dev/null
352@@ -1,38 +0,0 @@
353-# Copyright 2014-2015 Canonical Ltd. All rights reserved.
354-#
355-# This file is part of lazr.delegates.
356-#
357-# lazr.delegates is free software: you can redistribute it and/or modify it
358-# under the terms of the GNU Lesser General Public License as published by
359-# the Free Software Foundation, version 3 of the License.
360-#
361-# lazr.delegates is distributed in the hope that it will be useful, but
362-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
363-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
364-# License for more details.
365-#
366-# You should have received a copy of the GNU Lesser General Public License
367-# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
368-
369-"""Class decorator definition for Python 3.
370-
371-This syntax is not legal in Python 2.
372-"""
373-
374-from zope.interface import classImplements
375-
376-from lazr.delegates._passthrough import Passthrough
377-
378-
379-def delegate_to(*interfaces, context='context'):
380- if len(interfaces) == 0:
381- raise TypeError('At least one interface is required')
382- def _decorator(cls):
383- missing = object()
384- for interface in interfaces:
385- classImplements(cls, interface)
386- for name in interface:
387- if getattr(cls, name, missing) is missing:
388- setattr(cls, name, Passthrough(name, context))
389- return cls
390- return _decorator
391diff --git a/src/lazr/delegates/docs/conf.py b/src/lazr/delegates/docs/conf.py
392index fed69d1..adc0c50 100644
393--- a/src/lazr/delegates/docs/conf.py
394+++ b/src/lazr/delegates/docs/conf.py
395@@ -1,5 +1,3 @@
396-# -*- coding: utf-8 -*-
397-#
398 # lazr.delegates documentation build configuration file, created by
399 # sphinx-quickstart on Mon Jan 7 10:37:37 2013.
400 #
401diff --git a/src/lazr/delegates/tests/test_api.py b/src/lazr/delegates/tests/test_api.py
402index 5ecea47..4f6f200 100644
403--- a/src/lazr/delegates/tests/test_api.py
404+++ b/src/lazr/delegates/tests/test_api.py
405@@ -25,11 +25,4 @@ class TestAPI(unittest.TestCase):
406 """Test various corner cases in the API."""
407
408 def test_no_interfaces(self):
409- try:
410- @delegate_to()
411- class SomeClass(object):
412- pass
413- except TypeError:
414- pass
415- else:
416- self.fail('TypeError expected')
417+ self.assertRaises(TypeError, delegate_to)
418diff --git a/src/lazr/delegates/tests/test_docs.py b/src/lazr/delegates/tests/test_docs.py
419index 7ca6971..9e19ce4 100644
420--- a/src/lazr/delegates/tests/test_docs.py
421+++ b/src/lazr/delegates/tests/test_docs.py
422@@ -16,9 +16,6 @@
423
424 """Test harness for doctests."""
425
426-from __future__ import absolute_import, print_function, unicode_literals
427-
428-__metaclass__ = type
429 __all__ = []
430
431 import atexit
432@@ -50,17 +47,11 @@ def load_tests(loader, tests, pattern):
433 )
434 )
435 atexit.register(cleanup_resources)
436- globs = {
437- "absolute_import": absolute_import,
438- "print_function": print_function,
439- "unicode_literals": unicode_literals,
440- }
441 tests.addTest(
442 doctest.DocFileSuite(
443 *doctest_files,
444 module_relative=False,
445 optionflags=DOCTEST_FLAGS,
446- globs=globs,
447 encoding="UTF-8"
448 )
449 )
450diff --git a/src/lazr/delegates/tests/test_passthrough.py b/src/lazr/delegates/tests/test_passthrough.py
451index 009ed73..0905e3b 100644
452--- a/src/lazr/delegates/tests/test_passthrough.py
453+++ b/src/lazr/delegates/tests/test_passthrough.py
454@@ -58,13 +58,13 @@ class TestPassthrough(unittest.TestCase):
455 # provided, should be a zope.interface.Interface subclass (although in
456 # practice any callable will do) to which the instance is adapted
457 # before getting/setting the delegated attribute.
458- class HasNoFoo(object):
459+ class HasNoFoo:
460 _foo = 1
461 no_foo = HasNoFoo()
462 # ... but IHasFooAdapter uses HasNoFoo._foo to provide its own .foo,
463 # so it works like an adapter for HasNoFoo into some interface that
464 # provides a 'foo' attribute.
465- class IHasFooAdapter(object):
466+ class IHasFooAdapter:
467 def __init__(self, inst):
468 self.inst = inst
469 @property
470@@ -74,7 +74,7 @@ class TestPassthrough(unittest.TestCase):
471 def foo(self, value):
472 self.inst._foo = value
473
474- class Example(object):
475+ class Example:
476 context = no_foo
477
478 p = Passthrough('foo', 'context', adaptation=IHasFooAdapter)
479diff --git a/src/lazr/delegates/tests/test_python2.py b/src/lazr/delegates/tests/test_python2.py
480deleted file mode 100644
481index ad3de7e..0000000
482--- a/src/lazr/delegates/tests/test_python2.py
483+++ /dev/null
484@@ -1,159 +0,0 @@
485-# Copyright 2013-2015 Canonical Ltd. All rights reserved.
486-#
487-# This file is part of lazr.delegates.
488-#
489-# lazr.delegates is free software: you can redistribute it and/or modify it
490-# under the terms of the GNU Lesser General Public License as published by
491-# the Free Software Foundation, version 3 of the License.
492-#
493-# lazr.delegates is distributed in the hope that it will be useful, but
494-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
495-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
496-# License for more details.
497-#
498-# You should have received a copy of the GNU Lesser General Public License
499-# along with lazr.delegates. If not, see <http://www.gnu.org/licenses/>.
500-
501-"""Test the legacy API, which only works in Python 2.
502-
503-All of these tests are copied almost verbatim from the old README.rst.
504-"""
505-
506-
507-from __future__ import absolute_import, print_function, unicode_literals
508-
509-
510-# Don't enable the following or we can't test classic class failures.
511-#__metaclass__ = type
512-__all__ = [
513- 'TestLegacyAPI',
514- ]
515-
516-
517-import sys
518-import unittest
519-
520-from zope.interface import Attribute, Interface, implements, providedBy
521-
522-from lazr.delegates import delegate_to
523-if sys.version_info[0] == 2:
524- from lazr.delegates import delegates
525-
526-
527-class IFoo0(Interface):
528- spoo = Attribute('attribute in IFoo0')
529-
530-class IFoo(IFoo0):
531- def bar():
532- 'some method'
533- baz = Attribute('some attribute')
534-
535-class BaseFoo0:
536- spoo = 'some spoo'
537-
538-class BaseFoo(BaseFoo0):
539- def bar(self):
540- return 'bar'
541- baz = 'hi baz!'
542-
543-class IOther(Interface):
544- another = Attribute('another attribute')
545-
546-class BaseOtherFoo(BaseFoo):
547- another = 'yes, another'
548-
549-
550-@unittest.skipIf(sys.version_info[0] > 2, "only relevant in Python 2")
551-class TestLegacyAPI(unittest.TestCase):
552- def test_basic_usage(self):
553- class SomeClass(object):
554- delegates(IFoo)
555- def __init__(self, context):
556- self.context = context
557-
558- f = BaseFoo()
559- s = SomeClass(f)
560- self.assertEqual(s.bar(), 'bar')
561- self.assertEqual(s.baz, 'hi baz!')
562- self.assertEqual(s.spoo, 'some spoo')
563- self.assertTrue(IFoo.providedBy(s))
564-
565- def test_keyword_context(self):
566- class SomeOtherClass(object):
567- delegates(IFoo, context='myfoo')
568- def __init__(self, foo):
569- self.myfoo = foo
570- spoo = 'spoo from SomeOtherClass'
571-
572- f = BaseFoo()
573- s = SomeOtherClass(f)
574- self.assertEqual(s.bar(), 'bar')
575- self.assertEqual(s.baz, 'hi baz!')
576- self.assertEqual(s.spoo, 'spoo from SomeOtherClass')
577-
578- s.baz = 'fish'
579- self.assertEqual(s.baz, 'fish')
580- self.assertEqual(f.baz, 'fish')
581-
582- def test_classic_is_error(self):
583- try:
584- class SomeClassicClass:
585- delegates(IFoo)
586- except TypeError:
587- pass
588- else:
589- self.fail('TypeError expected')
590-
591- def test_use_outside_class_is_error(self):
592- self.assertRaises(TypeError, delegates, IFoo)
593-
594- def test_multiple_interfaces(self):
595- class SomeOtherClass(object):
596- delegates([IFoo, IOther])
597-
598- s = SomeOtherClass()
599- s.context = BaseOtherFoo()
600- self.assertEqual(s.another, 'yes, another')
601- self.assertEqual(s.baz, 'hi baz!')
602- self.assertEqual(s.spoo, 'some spoo')
603- self.assertTrue(IFoo.providedBy(s))
604- self.assertTrue(IOther.providedBy(s))
605-
606- def test_decorate_existing_object(self):
607- class MoreFoo(BaseFoo, BaseOtherFoo):
608- implements([IFoo, IOther])
609-
610- foo = MoreFoo()
611-
612- class WithExtraTeapot(object):
613- delegates(providedBy(foo))
614- teapot = 'i am a teapot'
615-
616- foo_with_teapot = WithExtraTeapot()
617- foo_with_teapot.context = foo
618-
619- self.assertEqual(foo_with_teapot.baz, 'hi baz!')
620- self.assertEqual(foo_with_teapot.another, 'yes, another')
621- self.assertEqual(foo_with_teapot.teapot, 'i am a teapot')
622- self.assertTrue(IFoo.providedBy(foo_with_teapot))
623- self.assertTrue(IOther.providedBy(foo_with_teapot))
624-
625-
626-@unittest.skipIf(sys.version_info[0] > 2, "only relevant in Python 2")
627-class TestNewAPI(unittest.TestCase):
628- """Test corner cases in Python 2.
629-
630- Most of the new API is tested in the doctest. The implementation of the
631- new API is different between Python 2 and Python 3, so test these corner
632- cases.
633- """
634- def test_type_error(self):
635- # Too many arguments to @delegate_to() raises a TypeError.
636- try:
637- @delegate_to(IFoo0, context='myfoo', other='bogus')
638- class SomeClass(object):
639- pass
640- except TypeError:
641- pass
642- else:
643- self.fail('TypeError expected')
644diff --git a/tox.ini b/tox.ini
645index 7bdfc27..451caed 100644
646--- a/tox.ini
647+++ b/tox.ini
648@@ -1,7 +1,6 @@
649 [tox]
650 envlist =
651 lint
652- py27
653 py35
654 py36
655 py37
656@@ -13,11 +12,9 @@ envlist =
657
658 [testenv]
659 deps =
660- .
661 zope.testrunner
662- coverage
663 commands =
664- coverage run -m zope.testrunner --test-path src --tests-pattern ^tests {posargs}
665+ zope-testrunner --test-path src --tests-pattern ^tests {posargs}
666
667 [testenv:lint]
668 basepython = python3.8
669@@ -36,22 +33,19 @@ deps =
670 .[docs]
671
672 [testenv:coverage]
673-description = Run coverage either via `tox` or `tox -e py27,py38,coverage`
674 basepython = python3
675-skip_install = true
676-depends =
677- py27
678- py38
679 deps =
680 coverage
681+ zope.testrunner
682 commands =
683- coverage combine
684- coverage report -m --fail-under=97
685+ coverage erase
686+ coverage run -m zope.testrunner --test-path src --tests-pattern ^tests {posargs}
687+ coverage html
688+ coverage report -m --fail-under=100
689
690 [coverage:run]
691 source = lazr.delegates
692 omit = */docs/conf.py
693-parallel = true
694
695 [coverage:paths]
696 source =

Subscribers

People subscribed via source and target branches

to all changes: