From b84035ee74cc646ddbabbb99d565e50973fe7b1d Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Thu, 28 Oct 2021 16:39:36 +1100 Subject: [PATCH 01/10] Change tests from nose to pytest Some of the 'yield' tests are easy to refactor into parameterised tests while others would take a fair amount of rethinking and/or additional helper functions. At this stage, the relevant functions are simply decorated with a 'collect_yielded' decorator that collects all the yield tests at collection time and sets them up as a parameterised test to be run later. --- pytest.ini | 4 + tests/conftest.py | 21 ++ tests/run-tests | 21 +- tests/test_changelog.py | 33 +- tests/test_domains.py | 23 +- tests/test_encodings.py | 117 +++--- tests/test_gettext.py | 134 ++++--- tests/test_iconv.py | 11 +- tests/test_ling.py | 566 +++++++++++++++--------------- tests/test_misc.py | 49 ++- tests/test_moparser.py | 21 +- tests/test_polib4us.py | 14 +- tests/test_strformat_c.py | 404 +++++++++++---------- tests/test_strformat_perlbrace.py | 19 +- tests/test_strformat_pybrace.py | 99 +++--- tests/test_strformat_python.py | 198 ++++++----- tests/test_tags.py | 27 +- tests/test_terminal.py | 8 +- tests/test_version.py | 8 +- tests/test_xml.py | 12 +- tests/tools.py | 41 ++- 21 files changed, 935 insertions(+), 895 deletions(-) create mode 100644 pytest.ini create mode 100644 tests/conftest.py Index: i18nspector-0.26/pytest.ini =================================================================== --- /dev/null +++ i18nspector-0.26/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +testpaths = tests +python_classes = test_* +python_functions = test *_test test_* Index: i18nspector-0.26/tests/conftest.py =================================================================== --- /dev/null +++ i18nspector-0.26/tests/conftest.py @@ -0,0 +1,19 @@ + +import os +import tempfile + + +def pytest_sessionstart(session): + envvar = 'XDG_CACHE_HOME' + old_xdg_cache_home = os.environ.get(envvar, None) + xdg_temp_dir = tempfile.TemporaryDirectory(prefix='i18nspector.tests.') # pylint: disable=consider-using-with + os.environ[envvar] = xdg_temp_dir.name + + def cleanup(): + xdg_temp_dir.cleanup() + if old_xdg_cache_home is None: + del os.environ[envvar] + else: + os.environ[envvar] = old_xdg_cache_home + + session.config.add_cleanup(cleanup) Index: i18nspector-0.26/tests/run-tests =================================================================== --- i18nspector-0.26.orig/tests/run-tests +++ i18nspector-0.26/tests/run-tests @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/bin/bash -# Copyright © 2013-2016 Jakub Wilk +# Copyright © 2021 Stuart Prescott # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal @@ -20,19 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os -import sys -import tempfile +set -e -import nose - -sys.path[0] += '/..' - -from tests import blackbox_tests - -if __name__ == '__main__': - with tempfile.TemporaryDirectory(prefix='i18nspector.tests.') as tmpdir: - os.environ['XDG_CACHE_HOME'] = tmpdir - nose.main(addplugins=[blackbox_tests.Plugin()]) - -# vim:ts=4 sts=4 sw=4 et +pytest -rsx "$@" Index: i18nspector-0.26/tests/test_changelog.py =================================================================== --- i18nspector-0.26.orig/tests/test_changelog.py +++ i18nspector-0.26/tests/test_changelog.py @@ -21,11 +21,9 @@ import os import re -from nose.tools import ( - assert_not_equal, -) - import lib.tags +from . import tools + here = os.path.dirname(__file__) docdir = os.path.join(here, os.pardir, 'doc') @@ -52,32 +50,39 @@ rename_re = re.compile( r'([\w-]+) [(]from ([\w-]+)[)]' ) -def test_tags(): - path = os.path.join(docdir, 'changelog') - with open(path, 'rt', encoding='UTF-8') as file: - changelog = file.read() - summaries = summary_re.findall(changelog) - changelog_tags = set() + +@tools.collect_yielded +def test_tag_paramerisation(): def add(info, tag): del info if tag in changelog_tags: raise AssertionError('changelog adds tag twice: ' + tag) changelog_tags.add(tag) + def remove(info, tag): del info if tag not in changelog_tags: raise AssertionError('changelog removes non-existent tag: ' + tag) changelog_tags.remove(tag) + def rename(info, removed_tag, added_tag): - assert_not_equal(removed_tag, added_tag) + assert removed_tag != added_tag remove(info, removed_tag) add(info, added_tag) + def check(info, tag): del info if tag not in changelog_tags: raise AssertionError('tag not in changelog: ' + tag) if tag not in data_tags: raise AssertionError('changelog adds unknown tag: ' + tag) + + path = os.path.join(docdir, 'changelog') + with open(path, 'rt', encoding='UTF-8') as file: + changelog = file.read() + summaries = summary_re.findall(changelog) + changelog_tags = set() + for summary in reversed(summaries): match = summary_details_re.match(summary) for key, lines in match.groupdict().items(): @@ -86,16 +91,17 @@ def test_tags(): lines = [l[8:] for l in lines.splitlines()] if key == 'added': for tag in lines: - yield add, 'add', tag + yield add, ('add', tag) elif key == 'renamed': for line in lines: added_tag, removed_tag = rename_re.match(line).groups() - yield rename, 'rename', removed_tag, added_tag + yield rename, ('rename', removed_tag, added_tag) else: assert False data_tags = frozenset(tag.name for tag in lib.tags.iter_tags()) for tag in sorted(changelog_tags | data_tags): - yield check, 'check', tag + yield check, ('check', tag) + def test_trailing_whitespace(): path = os.path.join(docdir, 'changelog') Index: i18nspector-0.26/tests/test_domains.py =================================================================== --- i18nspector-0.26.orig/tests/test_domains.py +++ i18nspector-0.26/tests/test_domains.py @@ -18,12 +18,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from nose.tools import ( - assert_false, - assert_is, - assert_raises, - assert_true, -) +import pytest import lib.domains as M @@ -32,9 +27,9 @@ class test_special_domains: def t(self, domain, special=True): result = M.is_special_domain(domain) if special: - assert_true(result) + assert result else: - assert_false(result) + assert not result def test_ok(self): self.t('test.jwilk.net', False) @@ -68,9 +63,9 @@ class test_special_domain_emails: def t(self, email, special=True): result = M.is_email_in_special_domain(email) if special: - assert_true(result) + assert result else: - assert_false(result) + assert not result def test_valid(self): self.t('jwilk@test.jwilk.net', False) @@ -79,14 +74,14 @@ class test_special_domain_emails: self.t('jwilk@example.net') def test_no_at(self): - with assert_raises(ValueError): + with pytest.raises(ValueError): self.t('jwilk%jwilk.net') class test_dotless_domains: def t(self, domain, dotless=True): result = M.is_dotless_domain(domain) - assert_is(result, dotless) + assert result is dotless def test_dotless(self): self.t('net') @@ -99,7 +94,7 @@ class test_dotless_emails: def t(self, email, dotless=True): result = M.is_email_in_dotless_domain(email) - assert_is(result, dotless) + assert result is dotless def test_dotless(self): self.t('jwilk@net') @@ -108,7 +103,7 @@ class test_dotless_emails: self.t('jwilk@example.net', False) def test_no_at(self): - with assert_raises(ValueError): + with pytest.raises(ValueError): self.t('jwilk%jwilk.net') # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_encodings.py =================================================================== --- i18nspector-0.26.orig/tests/test_encodings.py +++ i18nspector-0.26/tests/test_encodings.py @@ -20,79 +20,85 @@ import curses.ascii import sys +import unittest -import nose -from nose.tools import ( - assert_equal, - assert_false, - assert_is_none, - assert_not_in, - assert_raises, - assert_true, -) +import pytest import lib.encodings as E from . import tools + +# methods using the tools.collect_yielded decorator don't have a 'self' +# since they end up being run before 'self' exists. pylint doesn't +# understand this unusual situation +# pylint: disable=no-method-argument + + class test_is_portable_encoding: def test_found(self): - assert_true(E.is_portable_encoding('ISO-8859-2')) + assert E.is_portable_encoding('ISO-8859-2') def test_found_(self): - assert_true(E.is_portable_encoding('ISO_8859-2')) + assert E.is_portable_encoding('ISO_8859-2') def test_found_nonpython(self): - assert_false(E.is_portable_encoding('KOI8-T')) - assert_true(E.is_portable_encoding('KOI8-T', python=False)) + assert not E.is_portable_encoding('KOI8-T') + assert E.is_portable_encoding('KOI8-T', python=False) def test_notfound(self): - assert_false(E.is_portable_encoding('ISO-8859-16')) - assert_false(E.is_portable_encoding('ISO-8859-16', python=False)) + assert not E.is_portable_encoding('ISO-8859-16') + assert not E.is_portable_encoding('ISO-8859-16', python=False) class test_propose_portable_encoding: def test_identity(self): encoding = 'ISO-8859-2' portable_encoding = E.propose_portable_encoding(encoding) - assert_equal(portable_encoding, encoding) + assert portable_encoding == encoding - def test_found(self): + @tools.collect_yielded + def test_found(): def t(encoding, expected_portable_encoding): portable_encoding = E.propose_portable_encoding(encoding) - assert_equal(portable_encoding, expected_portable_encoding) - yield t, 'ISO8859-2', 'ISO-8859-2' - yield t, 'ISO_8859-2', 'ISO-8859-2' - yield t, 'Windows-1250', 'CP1250' + assert portable_encoding == expected_portable_encoding + yield t, ('ISO8859-2', 'ISO-8859-2') + yield t, ('ISO_8859-2', 'ISO-8859-2') + yield t, ('Windows-1250', 'CP1250') def test_notfound(self): portable_encoding = E.propose_portable_encoding('ISO-8859-16') - assert_is_none(portable_encoding) + assert portable_encoding is None + + +def _test_missing(encoding): + assert not E.is_ascii_compatible_encoding(encoding) + with pytest.raises(E.EncodingLookupError): + E.is_ascii_compatible_encoding(encoding, missing_ok=False) + class test_ascii_compatibility: - def test_portable(self): + @tools.collect_yielded + def test_portable(): def t(encoding): - assert_true(E.is_ascii_compatible_encoding(encoding)) - assert_true(E.is_ascii_compatible_encoding(encoding, missing_ok=False)) + assert E.is_ascii_compatible_encoding(encoding) + assert E.is_ascii_compatible_encoding(encoding, missing_ok=False) for encoding in E.get_portable_encodings(): yield t, encoding - def test_incompatible(self): + @tools.collect_yielded + def test_incompatible(): def t(encoding): - assert_false(E.is_ascii_compatible_encoding(encoding)) - assert_false(E.is_ascii_compatible_encoding(encoding, missing_ok=False)) + assert not E.is_ascii_compatible_encoding(encoding) + assert not E.is_ascii_compatible_encoding(encoding, missing_ok=False) yield t, 'UTF-7' yield t, 'UTF-16' - def _test_missing(self, encoding): - assert_false(E.is_ascii_compatible_encoding(encoding)) - with assert_raises(E.EncodingLookupError): - E.is_ascii_compatible_encoding(encoding, missing_ok=False) - - def test_non_text(self): - t = self._test_missing + @tools.collect_yielded + def test_non_text(): + t = _test_missing yield t, 'base64_codec' yield t, 'bz2_codec' yield t, 'hex_codec' @@ -102,7 +108,8 @@ class test_ascii_compatibility: yield t, 'zlib_codec' def test_missing(self): - self._test_missing('eggs') + _test_missing('eggs') + class test_get_character_name: @@ -110,44 +117,44 @@ class test_get_character_name: for i in range(ord('a'), ord('z')): u = chr(i) name = E.get_character_name(u) - assert_equal(name, 'LATIN SMALL LETTER ' + u.upper()) + assert name == 'LATIN SMALL LETTER ' + u.upper() u = chr(i).upper() name = E.get_character_name(u) - assert_equal(name, 'LATIN CAPITAL LETTER ' + u) + assert name == 'LATIN CAPITAL LETTER ' + u def test_c0(self): for i, curses_name in zip(range(0, 0x20), curses.ascii.controlnames): u = chr(i) name = E.get_character_name(u) expected_name = 'control character ' + curses_name - assert_equal(name, expected_name) + assert name == expected_name def test_del(self): name = E.get_character_name('\x7F') - assert_equal(name, 'control character DEL') + assert name == 'control character DEL' def test_c1(self): for i in range(0x80, 0xA0): u = chr(i) name = E.get_character_name(u) - assert_true(name.startswith('control character ')) + assert name.startswith('control character ') def test_uniqueness(self): names = set() for i in range(0, 0x100): u = chr(i) name = E.get_character_name(u) - assert_not_in(name, names) + assert name not in names names.add(name) def test_non_character(self): name = E.get_character_name('\uFFFE') - assert_equal(name, 'non-character') + assert name == 'non-character' name = E.get_character_name('\uFFFF') - assert_equal(name, 'non-character') + assert name == 'non-character' def test_lookup_error(self): - with assert_raises(ValueError): + with pytest.raises(ValueError): E.get_character_name('\uE000') class test_extra_encoding: @@ -164,13 +171,13 @@ class test_extra_encoding: except LookupError: pass else: - raise nose.SkipTest( + raise unittest.SkipTest( 'python{ver[0]}.{ver[1]} supports the {enc} encoding'.format( ver=sys.version_info, enc=encoding ) ) - with assert_raises(LookupError): + with pytest.raises(LookupError): dec() E.install_extra_encodings() enc() @@ -181,7 +188,7 @@ class test_extra_encoding: E.install_extra_encodings() encoding = '8859-2' portable_encoding = E.propose_portable_encoding(encoding) - assert_equal('ISO-' + encoding, portable_encoding) + assert 'ISO-' + encoding == portable_encoding @tools.fork_isolation def test_not_allowed(self): @@ -193,14 +200,14 @@ class test_extra_encoding: except LookupError: pass else: - raise nose.SkipTest( + raise unittest.SkipTest( 'python{ver[0]}.{ver[1]} supports the {enc} encoding'.format( ver=sys.version_info, enc=encoding ) ) E.install_extra_encodings() - with assert_raises(LookupError): + with pytest.raises(LookupError): enc() _viscii_unicode = 'Ti\u1EBFng Vi\u1EC7t' @@ -211,13 +218,13 @@ class test_extra_encoding: E.install_extra_encodings() u = self._viscii_unicode b = u.encode('VISCII') - assert_equal(b, self._viscii_bytes) + assert b == self._viscii_bytes @tools.fork_isolation def test_8b_encode_error(self): E.install_extra_encodings() u = self._viscii_unicode - with assert_raises(UnicodeEncodeError): + with pytest.raises(UnicodeEncodeError): u.encode('KOI8-RU') @tools.fork_isolation @@ -225,13 +232,13 @@ class test_extra_encoding: E.install_extra_encodings() b = self._viscii_bytes u = b.decode('VISCII') - assert_equal(u, self._viscii_unicode) + assert u == self._viscii_unicode @tools.fork_isolation def test_8b_decode_error(self): E.install_extra_encodings() b = self._viscii_bytes - with assert_raises(UnicodeDecodeError): + with pytest.raises(UnicodeDecodeError): b.decode('KOI8-T') _euc_tw_unicode = '\u4E2D\u6587' @@ -242,13 +249,13 @@ class test_extra_encoding: E.install_extra_encodings() u = self._euc_tw_unicode b = u.encode('EUC-TW') - assert_equal(b, self._euc_tw_bytes) + assert b == self._euc_tw_bytes @tools.fork_isolation def test_mb_encode_error(self): E.install_extra_encodings() u = self._viscii_unicode - with assert_raises(UnicodeEncodeError): + with pytest.raises(UnicodeEncodeError): u.encode('EUC-TW') @tools.fork_isolation @@ -256,13 +263,13 @@ class test_extra_encoding: E.install_extra_encodings() b = self._euc_tw_bytes u = b.decode('EUC-TW') - assert_equal(u, self._euc_tw_unicode) + assert u == self._euc_tw_unicode @tools.fork_isolation def test_mb_decode_error(self): E.install_extra_encodings() b = self._viscii_bytes - with assert_raises(UnicodeDecodeError): + with pytest.raises(UnicodeDecodeError): b.decode('EUC-TW') # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_gettext.py =================================================================== --- i18nspector-0.26.orig/tests/test_gettext.py +++ i18nspector-0.26/tests/test_gettext.py @@ -20,16 +20,7 @@ import datetime -from nose.tools import ( - assert_equal, - assert_false, - assert_is_instance, - assert_is_none, - assert_is_not_none, - assert_less, - assert_raises, - assert_true, -) +import pytest import lib.gettext as M @@ -38,21 +29,21 @@ class test_header_fields: def test_nonempty(self): # XXX Update this number after editing data/header-fields: expected = 12 - assert_equal(len(M.header_fields), expected) + assert len(M.header_fields) == expected def test_no_x(self): for field in M.header_fields: - assert_false(field.startswith('X-')) + assert not field.startswith('X-') def test_valid(self): for field in M.header_fields: - assert_true(M.is_valid_field_name(field)) + assert M.is_valid_field_name(field) class test_header_parser: def t(self, message, expected): parsed = list(M.parse_header(message)) - assert_equal(parsed, expected) + assert parsed == expected def test_ok(self): self.t( @@ -91,8 +82,8 @@ class test_plural_exp: def t(self, s, n=None, fn=None): f = M.parse_plural_expression(s) if n is not None: - assert_is_not_none(fn) - assert_equal(f(n), fn) + assert fn is not None + assert f(n) == fn def test_const(self): n = 42 @@ -101,7 +92,7 @@ class test_plural_exp: def test_const_overflow(self): m = (1 << 32) - 1 self.t(str(m), m, m) - with assert_raises(OverflowError): + with pytest.raises(OverflowError): self.t(str(m + 1), m + 1, False) self.t(str(m + 1), m + 42, False) @@ -112,7 +103,7 @@ class test_plural_exp: def test_var_overflow(self): m = (1 << 32) - 1 self.t('n', m, m) - with assert_raises(OverflowError): + with pytest.raises(OverflowError): self.t('n', m + 1, False) self.t('42', m + 1, 42) @@ -122,7 +113,7 @@ class test_plural_exp: def test_add_overflow(self): m = (1 << 32) - 1 self.t('n + 42', m - 42, m) - with assert_raises(OverflowError): + with pytest.raises(OverflowError): self.t('n + 42', m - 41, False) self.t('n + 42', m - 23, False) @@ -130,7 +121,7 @@ class test_plural_exp: self.t('n - 23', 37, 14) def test_sub_overflow(self): - with assert_raises(OverflowError): + with pytest.raises(OverflowError): self.t('n - 23', 6, False) def test_mul(self): @@ -140,7 +131,7 @@ class test_plural_exp: m = (1 << 32) - 1 assert m % 17 == 0 self.t('n * 17', m / 17, m) - with assert_raises(OverflowError): + with pytest.raises(OverflowError): self.t('n * 17', (m / 17) + 1, False) self.t('n * 2', (m + 1) / 2, False) @@ -148,14 +139,14 @@ class test_plural_exp: self.t('105 / n', 17, 6) def test_div_by_0(self): - with assert_raises(ZeroDivisionError): + with pytest.raises(ZeroDivisionError): self.t('105 / n', 0, False) def test_mod(self): self.t('105 % n', 17, 3) def test_mod_by_0(self): - with assert_raises(ZeroDivisionError): + with pytest.raises(ZeroDivisionError): self.t('105 % n', 0, False) def test_and(self): @@ -228,75 +219,75 @@ class test_plural_exp: self.t('(2 ? 3 : 7) ? 23 : 37') def test_badly_nested_conditional(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('2 ? (3 : 7 ? ) : 23') def test_unary_minus(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('-37') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('23 + (-37)') def test_unary_plus(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('+42') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('23 + (+37)') def test_func_call(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('n(42)') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('42(n)') def test_unbalanced_parentheses(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('(6 * 7') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 * 7)') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6) * (7') def test_dangling_binop(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 +') def test_junk_token(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 # 7') def test_shift(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 << 7') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 >> 7') def test_pow(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 ** 7') def test_floor_div(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 // 7') def test_tuple(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('()') - with assert_raises(self.error): + with pytest.raises(self.error): self.t('(6, 7)') def test_starred(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('*42') def test_exotic_whitespace(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('6 *\xA07') def test_empty(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('') - with assert_raises(self.error): + with pytest.raises(self.error): self.t(' ') class test_codomain: @@ -308,9 +299,9 @@ class test_codomain: cd = f.codomain() if min_ is None: assert max_ is None - assert_is_none(cd) + assert cd is None else: - assert_equal(cd, (min_, max_)) + assert cd == (min_, max_) def test_num(self): self.t('0', 0, 0) @@ -530,9 +521,9 @@ class test_period: op = f.period() if offset is None: assert period is None - assert_is_none(op) + assert op is None else: - assert_equal(op, (offset, period)) + assert op == (offset, period) def test_num(self): self.t('42', 0, 1) @@ -619,20 +610,20 @@ class test_plural_forms: def t(self, s, *, n, ljunk='', rjunk=''): if ljunk or rjunk: - with assert_raises(self.error): + with pytest.raises(self.error): M.parse_plural_forms(s) else: (n0, expr0) = M.parse_plural_forms(s) del expr0 - assert_equal(n0, n) + assert n0 == n (n1, expr1, ljunk1, rjunk1) = M.parse_plural_forms(s, strict=False) # pylint: disable=unbalanced-tuple-unpacking del expr1 - assert_equal(n1, n) - assert_equal(ljunk1, ljunk) - assert_equal(rjunk1, rjunk) + assert n1 == n + assert ljunk1 == ljunk + assert rjunk1 == rjunk def test_nplurals_0(self): - with assert_raises(self.error): + with pytest.raises(self.error): self.t('nplurals=0; plural=0;', n=0) def test_nplurals_positive(self): @@ -651,15 +642,15 @@ class test_fix_date_format: def t(self, old, expected): if expected is None: - with assert_raises(M.DateSyntaxError): + with pytest.raises(M.DateSyntaxError): M.fix_date_format(old) else: new = M.fix_date_format(old) - assert_is_not_none(new) - assert_equal(new, expected) + assert new is not None + assert new == expected def tbp(self, old): - with assert_raises(M.BoilerplateDate): + with pytest.raises(M.BoilerplateDate): M.fix_date_format(old) def test_boilerplate(self): @@ -710,10 +701,9 @@ class test_fix_date_format: self.t('2002-01-01 03:05', None) def test_tz_hint(self): - assert_equal( - M.fix_date_format('2002-01-01 03:05', tz_hint='+0900'), - '2002-01-01 03:05+0900', - ) + assert ( + M.fix_date_format('2002-01-01 03:05', tz_hint='+0900') == + '2002-01-01 03:05+0900') def test_gmt_before_tz(self): self.t( @@ -762,30 +752,30 @@ class test_parse_date: t = staticmethod(M.parse_date) def test_nonexistent(self): - with assert_raises(M.DateSyntaxError): + with pytest.raises(M.DateSyntaxError): self.t('2010-02-29 19:49+0200') def test_existent(self): d = self.t('2003-09-08 21:26+0200') - assert_equal(d.second, 0) - assert_is_instance(d, datetime.datetime) - assert_equal(str(d), '2003-09-08 21:26:00+02:00') + assert d.second == 0 + assert isinstance(d, datetime.datetime) + assert str(d) == '2003-09-08 21:26:00+02:00' def test_epoch(self): d = self.t('2008-04-03 16:06+0300') - assert_less(M.epoch, d) + assert M.epoch < d class test_string_formats: def test_nonempty(self): # XXX Update this number after editing data/string-formats: expected = 28 - assert_equal(len(M.string_formats), expected) + assert len(M.string_formats) == expected def test_lowercase(self): for s in M.string_formats: - assert_is_instance(s, str) - assert_true(s) - assert_equal(s, s.lower()) + assert isinstance(s, str) + assert s + assert s == s.lower() # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_iconv.py =================================================================== --- i18nspector-0.26.orig/tests/test_iconv.py +++ i18nspector-0.26/tests/test_iconv.py @@ -18,10 +18,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from nose.tools import ( - assert_equal, - assert_raises, -) +import pytest import lib.iconv as M @@ -32,11 +29,11 @@ class _test: def test_encode(self): b = M.encode(self.u, self.e) - assert_equal(b, self.b) + assert b == self.b def test_decode(self): u = M.decode(self.b, self.e) - assert_equal(u, self.u) + assert u == self.u class test_iso2(_test): u = 'Żrą łódź? Część miń!' @@ -50,7 +47,7 @@ class test_tcvn(_test): def test_incomplete_char(): b = 'Ę'.encode('UTF-8')[:1] - with assert_raises(UnicodeDecodeError): + with pytest.raises(UnicodeDecodeError): M.decode(b, 'UTF-8') # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_ling.py =================================================================== --- i18nspector-0.26.orig/tests/test_ling.py +++ i18nspector-0.26/tests/test_ling.py @@ -18,19 +18,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import nose -from nose.tools import ( - assert_equal, - assert_false, - assert_in, - assert_is, - assert_is_instance, - assert_is_none, - assert_not_equal, - assert_not_in, - assert_raises, - assert_true, -) +import unittest + +import pytest import lib.encodings import lib.ling @@ -45,12 +35,12 @@ class test_fix_codes: def t(self, l1, l2): lang = L.parse_language(l1) - assert_equal(str(lang), l1) + assert str(lang) == l1 if l1 == l2: - assert_is_none(lang.fix_codes()) + assert lang.fix_codes() is None else: - assert_is(lang.fix_codes(), True) - assert_equal(str(lang), l2) + assert lang.fix_codes() is True + assert str(lang) == l2 def test_2_to_2(self): self.t('grc', 'grc') @@ -69,18 +59,18 @@ class test_fix_codes: self.t('gre_GR', 'el_GR') def test_ll_not_found(self): - with assert_raises(L.FixingLanguageCodesFailed): + with pytest.raises(L.FixingLanguageCodesFailed): self.t('ry', '') def test_cc_not_found(self): - with assert_raises(L.FixingLanguageCodesFailed): + with pytest.raises(L.FixingLanguageCodesFailed): self.t('el_RY', '') def test_language_repr(): # Language.__repr__() is never used by i18nspector itself, # but it's useful for debugging test failures. lng = T('el') - assert_equal(repr(lng), '') + assert repr(lng) == '' class test_language_equality: @@ -89,47 +79,47 @@ class test_language_equality: def test_eq(self): l1 = T('el', 'GR') l2 = T('el', 'GR') - assert_equal(l1, l2) - assert_equal(l2, l1) + assert l1 == l2 + assert l2 == l1 def test_ne(self): l1 = T('el') l2 = T('el', 'GR') - assert_not_equal(l1, l2) - assert_not_equal(l2, l1) + assert l1 != l2 + assert l2 != l1 def test_ne_other_type(self): l1 = T('el') - assert_not_equal(l1, 42) - assert_not_equal(42, l1) + assert l1 != 42 + assert 42 != l1 # pylint: disable=misplaced-comparison-constant def test_almost_equal(self): l1 = T('el') l2 = T('el', 'GR') - assert_true(l1.is_almost_equal(l2)) - assert_true(l2.is_almost_equal(l1)) + assert l1.is_almost_equal(l2) + assert l2.is_almost_equal(l1) def test_not_almost_equal(self): l1 = T('el', 'GR') l2 = T('grc', 'GR') - assert_false(l1.is_almost_equal(l2)) - assert_false(l2.is_almost_equal(l1)) + assert not l1.is_almost_equal(l2) + assert not l2.is_almost_equal(l1) def test_not_almost_equal_other_type(self): l1 = T('el') - with assert_raises(TypeError): + with pytest.raises(TypeError): l1.is_almost_equal(42) class test_remove_encoding: def t(self, l1, l2): lang = L.parse_language(l1) - assert_equal(str(lang), l1) + assert str(lang) == l1 if l1 == l2: - assert_is_none(lang.remove_encoding()) + assert lang.remove_encoding() is None else: - assert_is(lang.remove_encoding(), True) - assert_equal(str(lang), l2) + assert lang.remove_encoding() is True + assert str(lang) == l2 def test_without_encoding(self): self.t('el', 'el') @@ -141,12 +131,12 @@ class test_remove_nonlinguistic_modifier def t(self, l1, l2): lang = L.parse_language(l1) - assert_equal(str(lang), l1) + assert str(lang) == l1 if l1 == l2: - assert_is_none(lang.remove_nonlinguistic_modifier()) + assert lang.remove_nonlinguistic_modifier() is None else: - assert_is(lang.remove_nonlinguistic_modifier(), True) - assert_equal(str(lang), l2) + assert lang.remove_nonlinguistic_modifier() is True + assert str(lang) == l2 def test_quot(self): self.t('en@quot', 'en@quot') @@ -162,18 +152,18 @@ class test_lookup_territory_code: def test_found(self): cc = L.lookup_territory_code('GR') - assert_equal(cc, 'GR') + assert cc == 'GR' def test_not_found(self): cc = L.lookup_territory_code('RG') - assert_is_none(cc) + assert cc is None class test_get_language_for_name: def t(self, name, expected): lang = L.get_language_for_name(name) - assert_is_instance(lang, T) - assert_equal(str(lang), expected) + assert isinstance(lang, T) + assert str(lang) == expected def test_found(self): self.t('Greek', 'el') @@ -194,69 +184,75 @@ class test_get_language_for_name: self.t('Pashto, Pushto', 'ps') def test_lone_comma(self): - with assert_raises(LookupError): + with pytest.raises(LookupError): self.t(',', None) def test_not_found(self): - with assert_raises(LookupError): + with pytest.raises(LookupError): self.t('Nadsat', None) class test_parse_language: def test_ll(self): lang = L.parse_language('el') - assert_equal(lang.language_code, 'el') - assert_is_none(lang.territory_code) - assert_is_none(lang.encoding) - assert_is_none(lang.modifier) + assert lang.language_code == 'el' + assert lang.territory_code is None + assert lang.encoding is None + assert lang.modifier is None def test_lll(self): lang = L.parse_language('ell') - assert_equal(lang.language_code, 'ell') - assert_is_none(lang.territory_code) - assert_is_none(lang.encoding) - assert_is_none(lang.modifier) + assert lang.language_code == 'ell' + assert lang.territory_code is None + assert lang.encoding is None + assert lang.modifier is None def test_ll_cc(self): lang = L.parse_language('el_GR') - assert_equal(lang.language_code, 'el') - assert_equal(lang.territory_code, 'GR') - assert_is_none(lang.encoding) - assert_is_none(lang.modifier) + assert lang.language_code == 'el' + assert lang.territory_code == 'GR' + assert lang.encoding is None + assert lang.modifier is None def test_ll_cc_enc(self): lang = L.parse_language('el_GR.UTF-8') - assert_equal(lang.language_code, 'el') - assert_equal(lang.territory_code, 'GR') - assert_equal(lang.encoding, 'UTF-8') - assert_is_none(lang.modifier) + assert lang.language_code == 'el' + assert lang.territory_code == 'GR' + assert lang.encoding == 'UTF-8' + assert lang.modifier is None def test_ll_cc_modifier(self): lang = L.parse_language('en_US@quot') - assert_equal(lang.language_code, 'en') - assert_equal(lang.territory_code, 'US') - assert_is_none(lang.encoding) - assert_equal(lang.modifier, 'quot') + assert lang.language_code == 'en' + assert lang.territory_code == 'US' + assert lang.encoding is None + assert lang.modifier == 'quot' def test_syntax_error(self): - with assert_raises(L.LanguageSyntaxError): + with pytest.raises(L.LanguageSyntaxError): L.parse_language('GR') class test_get_primary_languages: def test_found(self): langs = L.get_primary_languages() - assert_in('el', langs) + assert 'el' in langs def test_not_found(self): langs = L.get_primary_languages() - assert_not_in('ry', langs) + assert 'ry' not in langs - def test_iso_639(self): + # methods using the tools.collect_yielded decorator don't have a 'self' + # since they end up being run before 'self' exists. pylint doesn't + # understand this unusual situation + + @tools.collect_yielded + def test_iso_639(): + # pylint: disable=no-method-argument def t(lang_str): lang = L.parse_language(lang_str) - assert_is_none(lang.fix_codes()) - assert_equal(str(lang), lang_str) + assert lang.fix_codes() is None + assert str(lang) == lang_str for lang_str in L.get_primary_languages(): yield t, lang_str @@ -267,34 +263,30 @@ class test_get_plural_forms: return lang.get_plural_forms() def test_found_ll(self): - assert_equal( - self.t('el'), - ['nplurals=2; plural=n != 1;'] - ) + assert ( + self.t('el') == + ['nplurals=2; plural=n != 1;']) def test_found_ll_cc(self): - assert_equal( - self.t('el_GR'), - ['nplurals=2; plural=n != 1;'] - ) + assert ( + self.t('el_GR') == + ['nplurals=2; plural=n != 1;']) def test_en_ca(self): - assert_equal( - self.t('en'), - self.t('en_CA'), - ) + assert ( + self.t('en') == + self.t('en_CA')) def test_pt_br(self): - assert_not_equal( - self.t('pt'), - self.t('pt_BR'), - ) + assert ( + self.t('pt') != + self.t('pt_BR')) def test_not_known(self): - assert_is_none(self.t('la')) + assert self.t('la') is None def test_not_found(self): - assert_is_none(self.t('ry')) + assert self.t('ry') is None class test_principal_territory: @@ -302,115 +294,116 @@ class test_principal_territory: # el -> el_GR lang = L.parse_language('el') cc = lang.get_principal_territory_code() - assert_equal(cc, 'GR') + assert cc == 'GR' def test_remove_2(self): # el_GR -> el lang = L.parse_language('el_GR') - assert_equal(str(lang), 'el_GR') + assert str(lang) == 'el_GR' rc = lang.remove_principal_territory_code() - assert_is(rc, True) - assert_equal(str(lang), 'el') + assert rc is True + assert str(lang) == 'el' def test_found_3(self): # ang -> ang_GB lang = L.parse_language('ang') cc = lang.get_principal_territory_code() - assert_equal(cc, 'GB') + assert cc == 'GB' def test_remove_3(self): # ang_GB -> ang lang = L.parse_language('ang_GB') - assert_equal(str(lang), 'ang_GB') + assert str(lang) == 'ang_GB' rc = lang.remove_principal_territory_code() - assert_is(rc, True) - assert_equal(str(lang), 'ang') + assert rc is True + assert str(lang) == 'ang' def test_no_principal_territory_code(self): # en -/-> en_US lang = L.parse_language('en') cc = lang.get_principal_territory_code() - assert_is_none(cc) + assert cc is None def test_no_remove_principal_territory_code(self): # en_US -/-> en lang = L.parse_language('en_US') - assert_equal(str(lang), 'en_US') + assert str(lang) == 'en_US' rc = lang.remove_principal_territory_code() - assert_is_none(rc) - assert_equal(str(lang), 'en_US') + assert rc is None + assert str(lang) == 'en_US' def test_not_found(self): lang = L.parse_language('ry') cc = lang.get_principal_territory_code() - assert_equal(cc, None) + assert cc is None class test_unrepresentable_characters: def test_ll_bad(self): lang = L.parse_language('pl') result = lang.get_unrepresentable_characters('ISO-8859-1') - assert_not_equal(result, []) + assert result != [] def test_ll_ok(self): lang = L.parse_language('pl') result = lang.get_unrepresentable_characters('ISO-8859-2') - assert_equal(result, []) + assert result == [] def test_ll_cc_bad(self): lang = L.parse_language('pl_PL') result = lang.get_unrepresentable_characters('ISO-8859-1') - assert_not_equal(result, []) + assert result != [] def test_ll_cc_ok(self): lang = L.parse_language('pl_PL') result = lang.get_unrepresentable_characters('ISO-8859-2') - assert_equal(result, []) + assert result == [] def test_ll_mod_bad(self): lang = L.parse_language('en@quot') result = lang.get_unrepresentable_characters('ISO-8859-1') - assert_not_equal(result, []) + assert result != [] def test_ll_mod_ok(self): lang = L.parse_language('en@quot') result = lang.get_unrepresentable_characters('UTF-8') - assert_equal(result, []) + assert result == [] def test_ll_cc_mod_bad(self): lang = L.parse_language('en_US@quot') result = lang.get_unrepresentable_characters('ISO-8859-1') - assert_not_equal(result, []) + assert result != [] def test_ll_cc_mod_ok(self): lang = L.parse_language('en_US@quot') result = lang.get_unrepresentable_characters('UTF-8') - assert_equal(result, []) + assert result == [] def test_ll_optional(self): # U+0178 (LATIN CAPITAL LETTER Y WITH DIAERESIS) is not representable # in ISO-8859-1, but we normally turn a blind eye to this. lang = L.parse_language('fr') result = lang.get_unrepresentable_characters('ISO-8859-1') - assert_equal(result, []) + assert result == [] result = lang.get_unrepresentable_characters('ISO-8859-1', strict=True) - assert_not_equal(result, []) + assert result != [] def test_ll_not_found(self): lang = L.parse_language('ry') result = lang.get_unrepresentable_characters('ISO-8859-1') - assert_is_none(result) + assert result is None @tools.fork_isolation def test_extra_encoding(self): encoding = 'GEORGIAN-PS' lang = L.parse_language('pl') - with assert_raises(LookupError): + with pytest.raises(LookupError): ''.encode(encoding) E.install_extra_encodings() result = lang.get_unrepresentable_characters(encoding) - assert_not_equal(result, []) + assert result != [] +@tools.collect_yielded def test_glibc_supported(): def t(l): lang = L.parse_language(l) @@ -419,16 +412,16 @@ def test_glibc_supported(): except L.FixingLanguageCodesFailed: # FIXME: some ISO-639-3 codes are not recognized yet if len(l.split('_')[0]) == 3: - raise nose.SkipTest('expected failure') + raise unittest.SkipTest('expected failure') reason = locales_to_skip.get(l) if reason is not None: - raise nose.SkipTest(reason) + raise unittest.SkipTest(reason) raise assert_equal(str(lang), l) try: file = open('/usr/share/i18n/SUPPORTED', encoding='ASCII') except OSError as exc: - raise nose.SkipTest(exc) + raise unittest.SkipTest(exc) locales = set() with file: for line in file: @@ -452,6 +445,7 @@ def test_glibc_supported(): for l in sorted(locales): yield t, l +@tools.collect_yielded def test_poedit(): # https://github.com/vslavik/poedit/blob/v1.8.1-oss/src/language_impl_legacy.h # There won't be any new names in this table, @@ -459,178 +453,178 @@ def test_poedit(): def t(name, poedit_ll): poedit_ll = L.parse_language(poedit_ll) ll = L.get_language_for_name(name) - assert_equal(ll, poedit_ll) + assert ll == poedit_ll def x(name, poedit_ll): poedit_ll = L.parse_language(poedit_ll) - with assert_raises(LookupError): + with pytest.raises(LookupError): L.get_language_for_name(name) - raise nose.SkipTest('expected failure') - yield t, 'Abkhazian', 'ab' - yield t, 'Afar', 'aa' - yield t, 'Afrikaans', 'af' - yield t, 'Albanian', 'sq' - yield t, 'Amharic', 'am' - yield t, 'Arabic', 'ar' - yield t, 'Armenian', 'hy' - yield t, 'Assamese', 'as' - yield t, 'Avestan', 'ae' - yield t, 'Aymara', 'ay' - yield t, 'Azerbaijani', 'az' - yield t, 'Bashkir', 'ba' - yield t, 'Basque', 'eu' - yield t, 'Belarusian', 'be' - yield t, 'Bengali', 'bn' - yield t, 'Bislama', 'bi' - yield t, 'Bosnian', 'bs' - yield t, 'Breton', 'br' - yield t, 'Bulgarian', 'bg' - yield t, 'Burmese', 'my' - yield t, 'Catalan', 'ca' - yield t, 'Chamorro', 'ch' - yield t, 'Chechen', 'ce' - yield t, 'Chichewa; Nyanja', 'ny' - yield t, 'Chinese', 'zh' - yield t, 'Church Slavic', 'cu' - yield t, 'Chuvash', 'cv' - yield t, 'Cornish', 'kw' - yield t, 'Corsican', 'co' - yield t, 'Croatian', 'hr' - yield t, 'Czech', 'cs' - yield t, 'Danish', 'da' - yield t, 'Dutch', 'nl' - yield t, 'Dzongkha', 'dz' - yield t, 'English', 'en' - yield t, 'Esperanto', 'eo' - yield t, 'Estonian', 'et' - yield t, 'Faroese', 'fo' - yield t, 'Fijian', 'fj' - yield t, 'Finnish', 'fi' - yield t, 'French', 'fr' - yield t, 'Frisian', 'fy' - yield t, 'Friulian', 'fur' - yield t, 'Gaelic', 'gd' - yield t, 'Galician', 'gl' - yield t, 'Georgian', 'ka' - yield t, 'German', 'de' - yield t, 'Greek', 'el' - yield t, 'Guarani', 'gn' - yield t, 'Gujarati', 'gu' - yield t, 'Hausa', 'ha' - yield t, 'Hebrew', 'he' - yield t, 'Herero', 'hz' - yield t, 'Hindi', 'hi' - yield t, 'Hiri Motu', 'ho' - yield t, 'Hungarian', 'hu' - yield t, 'Icelandic', 'is' - yield t, 'Indonesian', 'id' - yield t, 'Interlingua', 'ia' - yield t, 'Interlingue', 'ie' - yield t, 'Inuktitut', 'iu' - yield t, 'Inupiaq', 'ik' - yield t, 'Irish', 'ga' - yield t, 'Italian', 'it' - yield t, 'Japanese', 'ja' - yield t, 'Javanese', 'jv' # https://github.com/vslavik/poedit/pull/193 - yield t, 'Kalaallisut', 'kl' - yield t, 'Kannada', 'kn' - yield t, 'Kashmiri', 'ks' - yield t, 'Kazakh', 'kk' - yield t, 'Khmer', 'km' - yield t, 'Kikuyu', 'ki' - yield t, 'Kinyarwanda', 'rw' - yield t, 'Komi', 'kv' - yield t, 'Korean', 'ko' - yield t, 'Kuanyama', 'kj' - yield t, 'Kurdish', 'ku' - yield t, 'Kyrgyz', 'ky' - yield t, 'Lao', 'lo' - yield t, 'Latin', 'la' - yield t, 'Latvian', 'lv' - yield t, 'Letzeburgesch', 'lb' - yield t, 'Lingala', 'ln' - yield t, 'Lithuanian', 'lt' - yield t, 'Macedonian', 'mk' - yield t, 'Malagasy', 'mg' - yield t, 'Malay', 'ms' - yield t, 'Malayalam', 'ml' - yield t, 'Maltese', 'mt' - yield t, 'Maori', 'mi' - yield t, 'Marathi', 'mr' - yield t, 'Marshall', 'mh' - yield t, 'Moldavian', 'ro_MD' # XXX poedit uses deprecated "mo" - yield t, 'Mongolian', 'mn' - yield t, 'Nauru', 'na' - yield t, 'Navajo', 'nv' - yield t, 'Ndebele, South', 'nr' - yield t, 'Ndonga', 'ng' - yield t, 'Nepali', 'ne' - yield t, 'Northern Sami', 'se' - yield t, 'Norwegian Bokmal', 'nb' - yield t, 'Norwegian Nynorsk', 'nn' - yield t, 'Occitan', 'oc' - yield t, 'Oriya', 'or' - yield t, 'Ossetian; Ossetic', 'os' - yield t, 'Pali', 'pi' - yield t, 'Panjabi', 'pa' - yield t, 'Pashto, Pushto', 'ps' - yield t, 'Persian', 'fa' - yield t, 'Polish', 'pl' - yield t, 'Portuguese', 'pt' - yield t, 'Quechua', 'qu' - yield t, 'Rhaeto-Romance', 'rm' - yield t, 'Romanian', 'ro' - yield t, 'Rundi', 'rn' - yield t, 'Russian', 'ru' - yield t, 'Samoan', 'sm' - yield t, 'Sangro', 'sg' - yield t, 'Sanskrit', 'sa' - yield t, 'Sardinian', 'sc' - yield t, 'Serbian', 'sr' - yield t, 'Sesotho', 'st' - yield t, 'Setswana', 'tn' - yield t, 'Shona', 'sn' - yield t, 'Sindhi', 'sd' - yield t, 'Sinhalese', 'si' - yield t, 'Siswati', 'ss' - yield t, 'Slovak', 'sk' - yield t, 'Slovenian', 'sl' - yield t, 'Somali', 'so' - yield t, 'Spanish', 'es' - yield t, 'Sundanese', 'su' - yield t, 'Swahili', 'sw' - yield t, 'Swedish', 'sv' - yield t, 'Tagalog', 'tl' - yield t, 'Tahitian', 'ty' - yield t, 'Tajik', 'tg' - yield t, 'Tamil', 'ta' - yield t, 'Tatar', 'tt' - yield t, 'Telugu', 'te' - yield t, 'Thai', 'th' - yield t, 'Tibetan', 'bo' - yield t, 'Tigrinya', 'ti' - yield t, 'Tonga', 'to' - yield t, 'Tsonga', 'ts' - yield t, 'Turkish', 'tr' - yield t, 'Turkmen', 'tk' - yield t, 'Twi', 'tw' - yield t, 'Ukrainian', 'uk' - yield t, 'Urdu', 'ur' - yield t, 'Uyghur', 'ug' - yield t, 'Uzbek', 'uz' - yield t, 'Vietnamese', 'vi' - yield t, 'Volapuk', 'vo' - yield t, 'Walloon', 'wa' - yield t, 'Welsh', 'cy' - yield t, 'Wolof', 'wo' - yield t, 'Xhosa', 'xh' - yield t, 'Yiddish', 'yi' - yield t, 'Yoruba', 'yo' - yield t, 'Zhuang', 'za' - yield t, 'Zulu', 'zu' + raise unittest.SkipTest('expected failure') + yield t, ('Abkhazian', 'ab') + yield t, ('Afar', 'aa') + yield t, ('Afrikaans', 'af') + yield t, ('Albanian', 'sq') + yield t, ('Amharic', 'am') + yield t, ('Arabic', 'ar') + yield t, ('Armenian', 'hy') + yield t, ('Assamese', 'as') + yield t, ('Avestan', 'ae') + yield t, ('Aymara', 'ay') + yield t, ('Azerbaijani', 'az') + yield t, ('Bashkir', 'ba') + yield t, ('Basque', 'eu') + yield t, ('Belarusian', 'be') + yield t, ('Bengali', 'bn') + yield t, ('Bislama', 'bi') + yield t, ('Bosnian', 'bs') + yield t, ('Breton', 'br') + yield t, ('Bulgarian', 'bg') + yield t, ('Burmese', 'my') + yield t, ('Catalan', 'ca') + yield t, ('Chamorro', 'ch') + yield t, ('Chechen', 'ce') + yield t, ('Chichewa; Nyanja', 'ny') + yield t, ('Chinese', 'zh') + yield t, ('Church Slavic', 'cu') + yield t, ('Chuvash', 'cv') + yield t, ('Cornish', 'kw') + yield t, ('Corsican', 'co') + yield t, ('Croatian', 'hr') + yield t, ('Czech', 'cs') + yield t, ('Danish', 'da') + yield t, ('Dutch', 'nl') + yield t, ('Dzongkha', 'dz') + yield t, ('English', 'en') + yield t, ('Esperanto', 'eo') + yield t, ('Estonian', 'et') + yield t, ('Faroese', 'fo') + yield t, ('Fijian', 'fj') + yield t, ('Finnish', 'fi') + yield t, ('French', 'fr') + yield t, ('Frisian', 'fy') + yield t, ('Friulian', 'fur') + yield t, ('Gaelic', 'gd') + yield t, ('Galician', 'gl') + yield t, ('Georgian', 'ka') + yield t, ('German', 'de') + yield t, ('Greek', 'el') + yield t, ('Guarani', 'gn') + yield t, ('Gujarati', 'gu') + yield t, ('Hausa', 'ha') + yield t, ('Hebrew', 'he') + yield t, ('Herero', 'hz') + yield t, ('Hindi', 'hi') + yield t, ('Hiri Motu', 'ho') + yield t, ('Hungarian', 'hu') + yield t, ('Icelandic', 'is') + yield t, ('Indonesian', 'id') + yield t, ('Interlingua', 'ia') + yield t, ('Interlingue', 'ie') + yield t, ('Inuktitut', 'iu') + yield t, ('Inupiaq', 'ik') + yield t, ('Irish', 'ga') + yield t, ('Italian', 'it') + yield t, ('Japanese', 'ja') + yield t, ('Javanese', 'jv') # https://github.com/vslavik/poedit/pull/193 + yield t, ('Kalaallisut', 'kl') + yield t, ('Kannada', 'kn') + yield t, ('Kashmiri', 'ks') + yield t, ('Kazakh', 'kk') + yield t, ('Khmer', 'km') + yield t, ('Kikuyu', 'ki') + yield t, ('Kinyarwanda', 'rw') + yield t, ('Komi', 'kv') + yield t, ('Korean', 'ko') + yield t, ('Kuanyama', 'kj') + yield t, ('Kurdish', 'ku') + yield t, ('Kyrgyz', 'ky') + yield t, ('Lao', 'lo') + yield t, ('Latin', 'la') + yield t, ('Latvian', 'lv') + yield t, ('Letzeburgesch', 'lb') + yield t, ('Lingala', 'ln') + yield t, ('Lithuanian', 'lt') + yield t, ('Macedonian', 'mk') + yield t, ('Malagasy', 'mg') + yield t, ('Malay', 'ms') + yield t, ('Malayalam', 'ml') + yield t, ('Maltese', 'mt') + yield t, ('Maori', 'mi') + yield t, ('Marathi', 'mr') + yield t, ('Marshall', 'mh') + yield t, ('Moldavian', 'ro_MD') # XXX poedit uses deprecated "mo" + yield t, ('Mongolian', 'mn') + yield t, ('Nauru', 'na') + yield t, ('Navajo', 'nv') + yield t, ('Ndebele, South', 'nr') + yield t, ('Ndonga', 'ng') + yield t, ('Nepali', 'ne') + yield t, ('Northern Sami', 'se') + yield t, ('Norwegian Bokmal', 'nb') + yield t, ('Norwegian Nynorsk', 'nn') + yield t, ('Occitan', 'oc') + yield t, ('Oriya', 'or') + yield t, ('Ossetian; Ossetic', 'os') + yield t, ('Pali', 'pi') + yield t, ('Panjabi', 'pa') + yield t, ('Pashto, Pushto', 'ps') + yield t, ('Persian', 'fa') + yield t, ('Polish', 'pl') + yield t, ('Portuguese', 'pt') + yield t, ('Quechua', 'qu') + yield t, ('Rhaeto-Romance', 'rm') + yield t, ('Romanian', 'ro') + yield t, ('Rundi', 'rn') + yield t, ('Russian', 'ru') + yield t, ('Samoan', 'sm') + yield t, ('Sangro', 'sg') + yield t, ('Sanskrit', 'sa') + yield t, ('Sardinian', 'sc') + yield t, ('Serbian', 'sr') + yield t, ('Sesotho', 'st') + yield t, ('Setswana', 'tn') + yield t, ('Shona', 'sn') + yield t, ('Sindhi', 'sd') + yield t, ('Sinhalese', 'si') + yield t, ('Siswati', 'ss') + yield t, ('Slovak', 'sk') + yield t, ('Slovenian', 'sl') + yield t, ('Somali', 'so') + yield t, ('Spanish', 'es') + yield t, ('Sundanese', 'su') + yield t, ('Swahili', 'sw') + yield t, ('Swedish', 'sv') + yield t, ('Tagalog', 'tl') + yield t, ('Tahitian', 'ty') + yield t, ('Tajik', 'tg') + yield t, ('Tamil', 'ta') + yield t, ('Tatar', 'tt') + yield t, ('Telugu', 'te') + yield t, ('Thai', 'th') + yield t, ('Tibetan', 'bo') + yield t, ('Tigrinya', 'ti') + yield t, ('Tonga', 'to') + yield t, ('Tsonga', 'ts') + yield t, ('Turkish', 'tr') + yield t, ('Turkmen', 'tk') + yield t, ('Twi', 'tw') + yield t, ('Ukrainian', 'uk') + yield t, ('Urdu', 'ur') + yield t, ('Uyghur', 'ug') + yield t, ('Uzbek', 'uz') + yield t, ('Vietnamese', 'vi') + yield t, ('Volapuk', 'vo') + yield t, ('Walloon', 'wa') + yield t, ('Welsh', 'cy') + yield t, ('Wolof', 'wo') + yield t, ('Xhosa', 'xh') + yield t, ('Yiddish', 'yi') + yield t, ('Yoruba', 'yo') + yield t, ('Zhuang', 'za') + yield t, ('Zulu', 'zu') # TODO: - yield x, '(Afan) Oromo', 'om' - yield x, 'Bihari', 'bh' # "bh" is marked as collective in ISO-639-2 - yield x, 'Serbian (Latin)', 'sr_RS@latin' - yield x, 'Serbo-Croatian', 'sh' # "sh" is deprecated in favor of "sr", "hr", "bs" + yield x, ('(Afan) Oromo', 'om') + yield x, ('Bihari', 'bh') # "bh" is marked as collective in ISO-639-2 + yield x, ('Serbian (Latin)', 'sr_RS@latin') + yield x, ('Serbo-Croatian', 'sh') # "sh" is deprecated in favor of "sr", "hr", "bs" # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_misc.py =================================================================== --- i18nspector-0.26.orig/tests/test_misc.py +++ i18nspector-0.26/tests/test_misc.py @@ -24,14 +24,7 @@ import stat import tempfile import time -from nose.tools import ( - assert_almost_equal, - assert_equal, - assert_is_instance, - assert_is_not_none, - assert_raises, - assert_true, -) +import pytest import lib.misc as M @@ -40,11 +33,11 @@ from . import tools class test_unsorted: def t(self, lst, expected): - assert_is_instance(lst, list) + assert isinstance(lst, list) r = M.unsorted(lst) - assert_equal(r, expected) + assert r == expected r = M.unsorted(x for x in lst) - assert_equal(r, expected) + assert r == expected def test_0(self): self.t([], None) @@ -71,7 +64,7 @@ class test_unsorted: while True: yield 23 r = M.unsorted(iterable()) - assert_equal(r, (37, 23)) + assert r == (37, 23) class test_check_sorted: @@ -79,25 +72,24 @@ class test_check_sorted: M.check_sorted([17, 23, 37]) def test_unsorted(self): - with assert_raises(M.DataIntegrityError) as cm: + with pytest.raises(M.DataIntegrityError) as cm: M.check_sorted([23, 37, 17]) - assert_equal(str(cm.exception), '37 > 17') + assert str(cm.value) == '37 > 17' def test_sorted_vk(): lst = ['eggs', 'spam', 'ham'] d = dict(enumerate(lst)) - assert_equal( - lst, - list(M.sorted_vk(d)) - ) + assert ( + lst == + list(M.sorted_vk(d))) class test_utc_now: def test_types(self): now = M.utc_now() - assert_is_instance(now, datetime.datetime) - assert_is_not_none(now.tzinfo) - assert_equal(now.tzinfo.utcoffset(now), datetime.timedelta(0)) + assert isinstance(now, datetime.datetime) + assert now.tzinfo is not None + assert now.tzinfo.utcoffset(now) == datetime.timedelta(0) @tools.fork_isolation def test_tz_resistance(self): @@ -108,18 +100,17 @@ class test_utc_now: now1 = t('Etc/GMT-4') now2 = t('Etc/GMT+2') tdelta = (now1 - now2).total_seconds() - assert_almost_equal(tdelta, 0, delta=0.75) + assert abs(tdelta - 0) <= 0.75 class test_format_range: def t(self, x, y, max, expected): # pylint: disable=redefined-builtin - assert_equal( - M.format_range(range(x, y), max=max), - expected - ) + assert ( + M.format_range(range(x, y), max=max) == + expected) def test_max_is_lt_4(self): - with assert_raises(ValueError): + with pytest.raises(ValueError): self.t(5, 10, 3, None) def test_len_lt_max(self): @@ -139,7 +130,7 @@ def test_throwaway_tempdir(): with M.throwaway_tempdir('test'): d = tempfile.gettempdir() st = os.stat(d) - assert_equal(stat.S_IMODE(st.st_mode), 0o700) - assert_true(stat.S_ISDIR(st.st_mode)) + assert stat.S_IMODE(st.st_mode) == 0o700 + assert stat.S_ISDIR(st.st_mode) # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_moparser.py =================================================================== --- i18nspector-0.26.orig/tests/test_moparser.py +++ i18nspector-0.26/tests/test_moparser.py @@ -20,10 +20,7 @@ import random -from nose.tools import ( - assert_equal, - assert_raises, -) +import pytest import lib.moparser as M @@ -38,21 +35,21 @@ def parser_for_bytes(data): class test_magic: def test_value(self): - assert_equal(M.little_endian_magic, b'\xDE\x12\x04\x95') - assert_equal(M.big_endian_magic, b'\x95\x04\x12\xDE') + assert M.little_endian_magic == b'\xDE\x12\x04\x95' + assert M.big_endian_magic == b'\x95\x04\x12\xDE' def test_short(self): for j in range(0, 3): data = M.little_endian_magic[:j] - with assert_raises(M.SyntaxError) as cm: + with pytest.raises(M.SyntaxError) as cm: parser_for_bytes(data) - assert_equal(str(cm.exception), 'unexpected magic') + assert str(cm.value) == 'unexpected magic' def test_full(self): for magic in {M.little_endian_magic, M.big_endian_magic}: - with assert_raises(M.SyntaxError) as cm: + with pytest.raises(M.SyntaxError) as cm: parser_for_bytes(magic) - assert_equal(str(cm.exception), 'truncated file') + assert str(cm.value) == 'truncated file' def test_random(self): while True: @@ -62,8 +59,8 @@ class test_magic: if random_magic in {M.little_endian_magic, M.big_endian_magic}: continue break - with assert_raises(M.SyntaxError) as cm: + with pytest.raises(M.SyntaxError) as cm: parser_for_bytes(random_magic) - assert_equal(str(cm.exception), 'unexpected magic') + assert str(cm.value) == 'unexpected magic' # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_polib4us.py =================================================================== --- i18nspector-0.26.orig/tests/test_polib4us.py +++ i18nspector-0.26/tests/test_polib4us.py @@ -20,11 +20,6 @@ import polib -from nose.tools import ( - assert_list_equal, - assert_true, -) - import lib.polib4us as M from . import tools @@ -50,7 +45,7 @@ msgstr "b" file.write(s) file.flush() po = polib.pofile(file.name) - assert_true(po[-1].obsolete) + assert po[-1].obsolete t() M.install_patches() t() @@ -60,9 +55,8 @@ def test_flag_splitting(): M.install_patches() e = polib.POEntry() e.flags = ['fuzzy,c-format'] - assert_list_equal( - e.flags, - ['fuzzy', 'c-format'] - ) + assert ( + e.flags == + ['fuzzy', 'c-format']) # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_strformat_c.py =================================================================== --- i18nspector-0.26.orig/tests/test_strformat_c.py +++ i18nspector-0.26/tests/test_strformat_c.py @@ -21,22 +21,25 @@ import os import struct import sys +import unittest import unittest.mock -import nose -from nose.tools import ( - assert_equal, - assert_greater, - assert_is_instance, - assert_raises, - assert_sequence_equal, -) +import pytest import lib.strformat.c as M +from . import tools + + +# methods using the tools.collect_yielded decorator don't have a 'self' +# since they end up being run before 'self' exists. pylint doesn't +# understand this unusual situation +# pylint: disable=no-method-argument + + def test_INT_MAX(): struct.pack('=i', M.INT_MAX) - with assert_raises(struct.error): + with pytest.raises(struct.error): struct.pack('=i', M.INT_MAX + 1) def is_glibc(): @@ -49,121 +52,134 @@ def is_glibc(): def test_NL_ARGMAX(): plat = sys.platform if plat.startswith('linux') and is_glibc(): - assert_equal( - M.NL_ARGMAX, - os.sysconf('SC_NL_ARGMAX') - ) + assert ( + M.NL_ARGMAX == + os.sysconf('SC_NL_ARGMAX')) else: - raise nose.SkipTest('Test specific to Linux with glibc') + raise unittest.SkipTest('Test specific to Linux with glibc') small_NL_ARGMAX = unittest.mock.patch('lib.strformat.c.NL_ARGMAX', 42) # Setting NL_ARGMAX to a small number makes the *_index_out_of_range() tests # much faster. def test_lone_percent(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('%') def test_invalid_conversion_spec(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('%!') def test_add_argument(): fmt = M.FormatString('%s') - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): fmt.add_argument(2, None) def test_text(): fmt = M.FormatString('eggs%dbacon%dspam') - assert_equal(len(fmt), 5) + assert len(fmt) == 5 fmt = list(fmt) - assert_equal(fmt[0], 'eggs') - assert_equal(fmt[2], 'bacon') - assert_equal(fmt[4], 'spam') + assert fmt[0] == 'eggs' + assert fmt[2] == 'bacon' + assert fmt[4] == 'spam' class test_types: - def t(self, s, tp, warn_type=None, integer=False): + @staticmethod + def t(s, tp, warn_type=None, integer=False): fmt = M.FormatString(s) [conv] = fmt - assert_is_instance(conv, M.Conversion) - assert_equal(conv.type, tp) + assert isinstance(conv, M.Conversion) + assert conv.type == tp if tp == 'void': - assert_sequence_equal(fmt.arguments, []) + assert fmt.arguments == [] else: [[arg]] = fmt.arguments - assert_equal(arg.type, tp) + assert arg.type == tp if warn_type is None: - assert_sequence_equal(fmt.warnings, []) + assert fmt.warnings == [] else: [warning] = fmt.warnings - assert_is_instance(warning, warn_type) - assert_equal(conv.integer, integer) + assert isinstance(warning, warn_type) + assert conv.integer == integer - def test_integer(self): - def t(s, tp, warn_type=None): + @tools.collect_yielded + def test_integer(): + def t(s, tp, suffix, warn_type=None): integer = not suffix - self.t(s, tp + suffix, warn_type, integer) + test_types.t(s, tp + suffix, warn_type, integer) for c in 'din': suffix = '' if c == 'n': suffix = ' *' - yield t, ('%hh' + c), 'signed char' - yield t, ('%h' + c), 'short int' - yield t, ('%' + c), 'int' - yield t, ('%l' + c), 'long int' - yield t, ('%ll' + c), 'long long int' - yield t, ('%L' + c), 'long long int', M.NonPortableConversion - yield t, ('%q' + c), 'long long int', M.NonPortableConversion - yield t, ('%j' + c), 'intmax_t' - yield t, ('%z' + c), 'ssize_t' - yield t, ('%Z' + c), 'ssize_t', M.NonPortableConversion - yield t, ('%t' + c), 'ptrdiff_t' + yield t, (('%hh' + c), 'signed char', suffix) + yield t, (('%h' + c), 'short int', suffix) + yield t, (('%' + c), 'int', suffix) + yield t, (('%l' + c), 'long int', suffix) + yield t, (('%ll' + c), 'long long int', suffix) + yield t, (('%L' + c), 'long long int', suffix, M.NonPortableConversion) + yield t, (('%q' + c), 'long long int', suffix, M.NonPortableConversion) + yield t, (('%j' + c), 'intmax_t', suffix) + yield t, (('%z' + c), 'ssize_t', suffix) + yield t, (('%Z' + c), 'ssize_t', suffix, M.NonPortableConversion) + yield t, (('%t' + c), 'ptrdiff_t', suffix) for c in 'ouxX': suffix = '' - yield t, ('%hh' + c), 'unsigned char' - yield t, ('%h' + c), 'unsigned short int' - yield t, ('%' + c), 'unsigned int' - yield t, ('%l' + c), 'unsigned long int' - yield t, ('%ll' + c), 'unsigned long long int' - yield t, ('%L' + c), 'unsigned long long int', M.NonPortableConversion - yield t, ('%q' + c), 'unsigned long long int', M.NonPortableConversion - yield t, ('%j' + c), 'uintmax_t' - yield t, ('%z' + c), 'size_t' - yield t, ('%Z' + c), 'size_t', M.NonPortableConversion - yield t, ('%t' + c), '[unsigned ptrdiff_t]' + yield t, (('%hh' + c), 'unsigned char', suffix) + yield t, (('%h' + c), 'unsigned short int', suffix) + yield t, (('%' + c), 'unsigned int', suffix) + yield t, (('%l' + c), 'unsigned long int', suffix) + yield t, (('%ll' + c), 'unsigned long long int', suffix) + yield t, (('%L' + c), 'unsigned long long int', suffix, M.NonPortableConversion) + yield t, (('%q' + c), 'unsigned long long int', suffix, M.NonPortableConversion) + yield t, (('%j' + c), 'uintmax_t', suffix) + yield t, (('%z' + c), 'size_t', suffix) + yield t, (('%Z' + c), 'size_t', suffix, M.NonPortableConversion) + yield t, (('%t' + c), '[unsigned ptrdiff_t]', suffix) + + @tools.collect_yielded + def test_double(): + def t(*args): + test_types.t(*args) - def test_double(self): - t = self.t for c in 'aefgAEFG': - yield t, ('%' + c), 'double' - yield t, ('%l' + c), 'double', M.NonPortableConversion - yield t, ('%L' + c), 'long double' - - def test_char(self): - t = self.t - yield t, '%c', 'char' - yield t, '%lc', 'wint_t' - yield t, '%C', 'wint_t', M.NonPortableConversion - yield t, '%s', 'const char *' - yield t, '%ls', 'const wchar_t *' - yield t, '%S', 'const wchar_t *', M.NonPortableConversion - - def test_void(self): - t = self.t - yield t, '%p', 'void *' - yield t, '%m', 'void' - yield t, '%%', 'void' + yield t, (('%' + c), 'double') + yield t, (('%l' + c), 'double', M.NonPortableConversion) + yield t, (('%L' + c), 'long double') + + @tools.collect_yielded + def test_char(): + def t(*args): + test_types.t(*args) + + yield t, ('%c', 'char') + yield t, ('%lc', 'wint_t') + yield t, ('%C', 'wint_t', M.NonPortableConversion) + yield t, ('%s', 'const char *') + yield t, ('%ls', 'const wchar_t *') + yield t, ('%S', 'const wchar_t *', M.NonPortableConversion) + + @tools.collect_yielded + def test_void(): + def t(*args): + test_types.t(*args) + + yield t, ('%p', 'void *') + yield t, ('%m', 'void') + yield t, ('%%', 'void') - def test_c99_macros(self): + @tools.collect_yielded + def test_c99_macros(): # pylint: disable=undefined-loop-variable def _t(s, tp): - return self.t(s, tp, integer=True) + return test_types.t(s, tp, integer=True) def t(s, tp): return ( _t, - '%<{macro}>'.format(macro=s.format(c=c, n=n)), - ('u' if unsigned else '') + tp.format(n=n) + ( + '%<{macro}>'.format(macro=s.format(c=c, n=n)), + ('u' if unsigned else '') + tp.format(n=n) + ) ) # pylint: enable=undefined-loop-variable for c in 'diouxX': @@ -175,64 +191,71 @@ class test_types: yield t('PRI{c}MAX', 'intmax_t') yield t('PRI{c}PTR', 'intptr_t') +_lengths = ['hh', 'h', 'l', 'll', 'q', 'j', 'z', 't', 'L'] + class test_invalid_length: - def t(self, s): - with assert_raises(M.LengthError): + @staticmethod + def t(s): + with pytest.raises(M.LengthError): M.FormatString(s) - _lengths = ['hh', 'h', 'l', 'll', 'q', 'j', 'z', 't', 'L'] - - def test_double(self): - t = self.t + @tools.collect_yielded + def test_double(): + def t(*args): + test_invalid_length.t(*args) for c in 'aefgAEFG': - for l in self._lengths: + for l in _lengths: if l in 'lL': continue yield t, ('%' + l + c) - def test_char(self): - t = self.t + @tools.collect_yielded + def test_char(): + def t(*args): + test_invalid_length.t(*args) for c in 'cs': - for l in self._lengths: + for l in _lengths: if l != 'l': yield t, '%' + l + c yield t, ('%' + l + c.upper()) - def test_void(self): - t = self.t + @tools.collect_yielded + def test_void(): + def t(*args): + test_invalid_length.t(*args) for c in 'pm%': - for l in self._lengths: + for l in _lengths: yield t, ('%' + l + c) class test_numeration: def test_percent(self): - with assert_raises(M.ForbiddenArgumentIndex): + with pytest.raises(M.ForbiddenArgumentIndex): M.FormatString('%1$%') def test_errno(self): # FIXME? fmt = M.FormatString('%1$m') - assert_equal(len(fmt), 1) - assert_equal(len(fmt.arguments), 0) + assert len(fmt) == 1 + assert len(fmt.arguments) == 0 def test_swapped(self): fmt = M.FormatString('%2$s%1$d') - assert_equal(len(fmt), 2) + assert len(fmt) == 2 [a1], [a2] = fmt.arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'const char *') + assert a1.type == 'int' + assert a2.type == 'const char *' def test_numbering_mixture(self): def t(s): - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): M.FormatString(s) t('%s%2$s') t('%2$s%s') @small_NL_ARGMAX def test_index_out_of_range(self): - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): M.FormatString('%0$d') def fs(n): s = ''.join( @@ -241,17 +264,17 @@ class test_numeration: ) return M.FormatString(s) fmt = fs(M.NL_ARGMAX) - assert_equal(len(fmt), M.NL_ARGMAX) - assert_equal(len(fmt.arguments), M.NL_ARGMAX) - with assert_raises(M.ArgumentRangeError): + assert len(fmt) == M.NL_ARGMAX + assert len(fmt.arguments) == M.NL_ARGMAX + with pytest.raises(M.ArgumentRangeError): fs(M.NL_ARGMAX + 1) def test_initial_gap(self): - with assert_raises(M.MissingArgument): + with pytest.raises(M.MissingArgument): M.FormatString('%2$d') def test_gap(self): - with assert_raises(M.MissingArgument): + with pytest.raises(M.MissingArgument): M.FormatString('%3$d%1$d') class test_redundant_flag: @@ -259,7 +282,7 @@ class test_redundant_flag: def t(self, s): fmt = M.FormatString(s) [exc] = fmt.warnings - assert_is_instance(exc, M.RedundantFlag) + assert isinstance(exc, M.RedundantFlag) def test_duplicate(self): self.t('%--17d') @@ -274,87 +297,114 @@ class test_redundant_flag: class test_expected_flag: - def t(self, s): + @staticmethod + def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 - def test_hash(self): + @tools.collect_yielded + def test_hash(): + def t(*args): + test_expected_flag.t(*args) for c in 'oxXaAeEfFgG': - yield self.t, ('%#' + c) + yield t, ('%#' + c) - def test_zero(self): + @tools.collect_yielded + def test_zero(): + def t(*args): + test_expected_flag.t(*args) for c in 'diouxXaAeEfFgG': - yield self.t, ('%0' + c) + yield t, ('%0' + c) - def test_apos(self): + @tools.collect_yielded + def test_apos(): + def t(*args): + test_expected_flag.t(*args) for c in 'diufFgG': - yield self.t, ("%'" + c) + yield t, ("%'" + c) - def test_other(self): + @tools.collect_yielded + def test_other(): + def t(*args): + test_expected_flag.t(*args) for flag in '- +I': for c in 'diouxXaAeEfFgGcCsSpm': - yield self.t, ('%' + flag + c) + yield t, ('%' + flag + c) class test_unexpected_flag: - def t(self, s): - with assert_raises(M.FlagError): + @staticmethod + def t(s): + with pytest.raises(M.FlagError): M.FormatString(s) - def test_hash(self): + @tools.collect_yielded + def test_hash(): + def t(*args): + test_unexpected_flag.t(*args) for c in 'dicCsSnpm%': - yield self.t, ('%#' + c) + yield t, ('%#' + c) - def test_zero(self): + @tools.collect_yielded + def test_zero(): + def t(*args): + test_unexpected_flag.t(*args) for c in 'cCsSnpm%': - yield self.t, ('%0' + c) + yield t, ('%0' + c) - def test_apos(self): + @tools.collect_yielded + def test_apos(): + def t(*args): + test_unexpected_flag.t(*args) for c in 'oxXaAeEcCsSnpm%': - yield self.t, ("%'" + c) + yield t, ("%'" + c) - def test_other(self): + @tools.collect_yielded + def test_other(): + def t(*args): + test_unexpected_flag.t(*args) for c in '%n': for flag in '- +I': - yield self.t, ('%' + flag + c) + yield t, ('%' + flag + c) class test_width: - def test_ok(self): + @tools.collect_yielded + def test_ok(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 for c in 'diouxXaAeEfFgGcCsSp': yield t, ('%1' + c) yield t, '%1m' # FIXME? def test_invalid(self): for c in '%n': - with assert_raises(M.WidthError): + with pytest.raises(M.WidthError): M.FormatString('%1' + c) def test_too_large(self): fmt = M.FormatString('%{0}d'.format(M.INT_MAX)) - assert_equal(len(fmt), 1) - assert_equal(len(fmt.arguments), 1) - with assert_raises(M.WidthRangeError): + assert len(fmt) == 1 + assert len(fmt.arguments) == 1 + with pytest.raises(M.WidthRangeError): M.FormatString('%{0}d'.format(M.INT_MAX + 1)) def test_variable(self): fmt = M.FormatString('%*s') - assert_equal(len(fmt), 1) - assert_equal(len(fmt.arguments), 2) + assert len(fmt) == 1 + assert len(fmt.arguments) == 2 [a1], [a2] = fmt.arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'const char *') + assert a1.type == 'int' + assert a2.type == 'const char *' def _test_index(self, i): fmt = M.FormatString('%2$*{0}$s'.format(i)) - assert_equal(len(fmt), 1) - assert_equal(len(fmt.arguments), 2) + assert len(fmt) == 1 + assert len(fmt.arguments) == 2 [a1], [a2] = fmt.arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'const char *') + assert a1.type == 'int' + assert a2.type == 'const char *' def test_index(self): self._test_index(1) @@ -365,7 +415,7 @@ class test_width: @small_NL_ARGMAX def test_index_out_of_range(self): - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): M.FormatString('%1$*0$s') def fs(n): s = ''.join( @@ -374,14 +424,14 @@ class test_width: ) + '%1$*{0}$s'.format(n) return M.FormatString(s) fmt = fs(M.NL_ARGMAX) - assert_equal(len(fmt), M.NL_ARGMAX - 1) - assert_equal(len(fmt.arguments), M.NL_ARGMAX) - with assert_raises(M.ArgumentRangeError): + assert len(fmt) == M.NL_ARGMAX - 1 + assert len(fmt.arguments) == M.NL_ARGMAX + with pytest.raises(M.ArgumentRangeError): fs(M.NL_ARGMAX + 1) def test_numbering_mixture(self): def t(s): - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): M.FormatString(s) t('%1$*s') t('%*1$s') @@ -390,58 +440,62 @@ class test_width: class test_precision: - def test_ok(self): + @tools.collect_yielded + def test_ok(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 for c in 'diouxXaAeEfFgGsS': yield t, ('%.1' + c) - def test_redundant_0(self): + @tools.collect_yielded + def test_redundant_0(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 [warning] = fmt.warnings - assert_is_instance(warning, M.RedundantFlag) + assert isinstance(warning, M.RedundantFlag) for c in 'diouxX': yield t, ('%0.1' + c) - def test_non_redundant_0(self): + @tools.collect_yielded + def test_non_redundant_0(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) - assert_sequence_equal(fmt.warnings, []) + assert len(fmt) == 1 + assert fmt.warnings == [] for c in 'aAeEfFgG': yield t, ('%0.1' + c) - def test_unexpected(self): + @tools.collect_yielded + def test_unexpected(): def t(s): - with assert_raises(M.PrecisionError): + with pytest.raises(M.PrecisionError): M.FormatString(s) for c in 'cCpnm%': yield t, ('%.1' + c) def test_too_large(self): fmt = M.FormatString('%.{0}f'.format(M.INT_MAX)) - assert_equal(len(fmt), 1) - with assert_raises(M.PrecisionRangeError): + assert len(fmt) == 1 + with pytest.raises(M.PrecisionRangeError): M.FormatString('%.{0}f'.format(M.INT_MAX + 1)) def test_variable(self): fmt = M.FormatString('%.*f') - assert_equal(len(fmt), 1) - assert_equal(len(fmt.arguments), 2) + assert len(fmt) == 1 + assert len(fmt.arguments) == 2 [a1], [a2] = fmt.arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'double') + assert a1.type == 'int' + assert a2.type == 'double' def _test_index(self, i): fmt = M.FormatString('%2$.*{0}$f'.format(i)) - assert_equal(len(fmt), 1) - assert_equal(len(fmt.arguments), 2) + assert len(fmt) == 1 + assert len(fmt.arguments) == 2 [a1], [a2] = fmt.arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'double') + assert a1.type == 'int' + assert a2.type == 'double' def test_index(self): self._test_index(1) @@ -452,7 +506,7 @@ class test_precision: @small_NL_ARGMAX def test_index_out_of_range(self): - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): M.FormatString('%1$.*0$f') def fs(n): s = ''.join( @@ -461,14 +515,14 @@ class test_precision: ) + '%1$.*{0}$f'.format(n) return M.FormatString(s) fmt = fs(M.NL_ARGMAX) - assert_equal(len(fmt), M.NL_ARGMAX - 1) - assert_equal(len(fmt.arguments), M.NL_ARGMAX) - with assert_raises(M.ArgumentRangeError): + assert len(fmt) == M.NL_ARGMAX - 1 + assert len(fmt.arguments) == M.NL_ARGMAX + with pytest.raises(M.ArgumentRangeError): fs(M.NL_ARGMAX + 1) def test_numbering_mixture(self): def t(s): - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): M.FormatString(s) t('%1$.*f') t('%.*1$f') @@ -481,15 +535,15 @@ class test_type_compatibility: def t(s, tp): fmt = M.FormatString(s) [args] = fmt.arguments - assert_greater(len(args), 1) + assert len(args) > 1 for arg in args: - assert_equal(arg.type, tp) + assert arg.type == tp t('%1$d%1$d', 'int') t('%1$d%1$i', 'int') def test_mismatch(self): def t(s): - with assert_raises(M.ArgumentTypeMismatch): + with pytest.raises(M.ArgumentTypeMismatch): M.FormatString(s) t('%1$d%1$hd') t('%1$d%1$u') @@ -498,11 +552,11 @@ class test_type_compatibility: @small_NL_ARGMAX def test_too_many_conversions(): def t(s): - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): M.FormatString(s) s = M.NL_ARGMAX * '%d' fmt = M.FormatString(s) - assert_equal(len(fmt), M.NL_ARGMAX) + assert len(fmt) == M.NL_ARGMAX t(s + '%f') t(s + '%*f') t(s + '%.*f') @@ -512,7 +566,7 @@ class test_get_last_integer_conversion: def test_overflow(self): fmt = M.FormatString('%s%d') for n in [-1, 0, 3]: - with assert_raises(IndexError): + with pytest.raises(IndexError): fmt.get_last_integer_conversion(n=n) def t(self, s, n, tp=M.Conversion): @@ -520,7 +574,7 @@ class test_get_last_integer_conversion: conv = fmt.get_last_integer_conversion(n=n) if tp is None: tp = type(tp) - assert_is_instance(conv, tp) + assert isinstance(conv, tp) return conv def test_okay(self): Index: i18nspector-0.26/tests/test_strformat_perlbrace.py =================================================================== --- i18nspector-0.26.orig/tests/test_strformat_perlbrace.py +++ i18nspector-0.26/tests/test_strformat_perlbrace.py @@ -18,40 +18,37 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from nose.tools import ( - assert_equal, - assert_raises, -) +import pytest import lib.strformat.perlbrace as M def test_lone_lcb(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{') def test_lone_rcb(): M.FormatString('}') def test_invalid_field(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{@}') def test_text(): fmt = M.FormatString('bacon') - assert_equal(len(fmt), 1) + assert len(fmt) == 1 [fld] = fmt - assert_equal(fld, 'bacon') + assert fld == 'bacon' class test_named_arguments: def test_good(self): fmt = M.FormatString('{spam}') - assert_equal(len(fmt), 1) + assert len(fmt) == 1 [fld] = fmt - assert_equal(fld, '{spam}') + assert fld == '{spam}' def test_bad(self): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{3ggs}') # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_strformat_pybrace.py =================================================================== --- i18nspector-0.26.orig/tests/test_strformat_pybrace.py +++ i18nspector-0.26/tests/test_strformat_pybrace.py @@ -21,18 +21,13 @@ import struct import unittest.mock -from nose.tools import ( - assert_equal, - assert_is, - assert_is_instance, - assert_raises, -) +import pytest import lib.strformat.pybrace as M def test_SSIZE_MAX(): struct.pack('=i', M.SSIZE_MAX) - with assert_raises(struct.error): + with pytest.raises(struct.error): struct.pack('=i', M.SSIZE_MAX + 1) small_SSIZE_MAX = unittest.mock.patch('lib.strformat.pybrace.SSIZE_MAX', 42) @@ -40,31 +35,31 @@ small_SSIZE_MAX = unittest.mock.patch('l # a very large number of arguments without running out of memory. def test_lone_lcb(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{') def test_lone_rcb(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('}') def test_invalid_field(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{@}') def test_add_argument(): fmt = M.FormatString('{}') - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): fmt.add_argument(None, None) - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): fmt.add_argument('eggs', None) def test_text(): fmt = M.FormatString('eggs{}bacon{}spam') - assert_equal(len(fmt), 5) + assert len(fmt) == 5 fmt = list(fmt) - assert_equal(fmt[0], 'eggs') - assert_equal(fmt[2], 'bacon') - assert_equal(fmt[4], 'spam') + assert fmt[0] == 'eggs' + assert fmt[2] == 'bacon' + assert fmt[4] == 'spam' class test_types: @@ -72,12 +67,12 @@ class test_types: types = frozenset(tp.__name__ for tp in types) fmt = M.FormatString('{:' + k + '}') [fld] = fmt - assert_is_instance(fld, M.Field) - assert_equal(fld.types, types) - assert_equal(len(fmt.argument_map), 1) + assert isinstance(fld, M.Field) + assert fld.types == types + assert len(fmt.argument_map) == 1 [(key, [afld])] = fmt.argument_map.items() - assert_equal(key, 0) - assert_is(fld, afld) + assert key == 0 + assert fld is afld def test_default(self): self.t('', int, float, str) @@ -102,12 +97,12 @@ class test_conversion: types = frozenset(tp.__name__ for tp in types) fmt = M.FormatString('{!' + c + ':' + k + '}') [fld] = fmt - assert_is_instance(fld, M.Field) - assert_equal(fld.types, types) - assert_equal(len(fmt.argument_map), 1) + assert isinstance(fld, M.Field) + assert fld.types == types + assert len(fmt.argument_map) == 1 [(key, [afld])] = fmt.argument_map.items() - assert_equal(key, 0) - assert_is(fld, afld) + assert key == 0 + assert fld is afld def test_default(self): for c in 'sra': @@ -120,11 +115,11 @@ class test_conversion: def test_numeric(self): for c in 'sra': for k in 'bcdoxXneEfFgG': - with assert_raises(M.FormatTypeMismatch): + with pytest.raises(M.FormatTypeMismatch): self.t(c, k, int) def test_bad(self): - with assert_raises(M.ConversionError): + with pytest.raises(M.ConversionError): self.t('z', '') class test_numbered_arguments: @@ -134,12 +129,12 @@ class test_numbered_arguments: def t(self, s, *types): fmt = M.FormatString(s) - assert_equal(len(fmt), len(types)) - assert_equal(len(fmt.argument_map), len(types)) + assert len(fmt) == len(types) + assert len(fmt.argument_map) == len(types) for (key, args), (xkey, xtype) in zip(sorted(fmt.argument_map.items()), enumerate(types)): [arg] = args - assert_equal(key, xkey) - assert_equal(arg.types, frozenset({xtype.__name__})) + assert key == xkey + assert arg.types == frozenset({xtype.__name__}) def test_unnumbered(self): self.t('{:d}{:f}', int, float) @@ -151,9 +146,9 @@ class test_numbered_arguments: self.t('{1:d}{0:f}', float, int) def test_mixed(self): - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): self.t('{0:d}{:f}') - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): self.t('{:d}{0:f}') def test_numbered_out_of_range(self): @@ -161,7 +156,7 @@ class test_numbered_arguments: s = ('{' + str(i) + '}') M.FormatString(s) t(M.SSIZE_MAX) - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): t(M.SSIZE_MAX + 1) @small_SSIZE_MAX @@ -170,7 +165,7 @@ class test_numbered_arguments: s = '{}' * i M.FormatString(s) t(M.SSIZE_MAX + 1) - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): t(M.SSIZE_MAX + 2) class test_named_arguments: @@ -179,21 +174,21 @@ class test_named_arguments: fmt = M.FormatString('{spam}') [fld] = fmt [(aname, [afld])] = fmt.argument_map.items() - assert_equal(aname, 'spam') - assert_is(fld, afld) + assert aname == 'spam' + assert fld is afld def test_bad(self): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{3ggs}') class test_format_spec: def test_bad_char(self): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{:@}') def test_bad_letter(self): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('{:Z}') def test_comma(self): @@ -203,7 +198,7 @@ class test_format_spec: for k in 'bcdoxXeEfFgG': t(k) for k in 'ns': - with assert_raises(M.Error): + with pytest.raises(M.Error): t(k) def test_alt_sign(self): @@ -213,7 +208,7 @@ class test_format_spec: t(c, '') for k in 'bcdoxXneEfFgG': t(c, k) - with assert_raises(M.Error): + with pytest.raises(M.Error): t(c, 's') def test_align(self): @@ -228,7 +223,7 @@ class test_format_spec: t(c, '') for k in 'bcdoxXneEfFgG': t(c, k) - with assert_raises(M.Error): + with pytest.raises(M.Error): t(c, 's') def test_width(self): @@ -239,7 +234,7 @@ class test_format_spec: for k in 'bcdoxXneEfFgGs\0': for i in 4, 37, M.SSIZE_MAX: t(i, k) - with assert_raises(M.Error): + with pytest.raises(M.Error): t(M.SSIZE_MAX + 1, k) def test_precision(self): @@ -250,11 +245,11 @@ class test_format_spec: for k in 'neEfFgGs\0': for i in {4, 37, M.SSIZE_MAX}: t(i, k) - with assert_raises(M.Error): + with pytest.raises(M.Error): t(M.SSIZE_MAX + 1, k) for k in 'bcdoxX': for i in {4, 37, M.SSIZE_MAX, M.SSIZE_MAX + 1}: - with assert_raises(M.Error): + with pytest.raises(M.Error): t(i, k) def test_type_compat(self): @@ -262,7 +257,7 @@ class test_format_spec: s = '{0:' + k1 + '}{0:' + k2 + '}' M.FormatString(s) def e(k1, k2): - with assert_raises(M.ArgumentTypeMismatch): + with pytest.raises(M.ArgumentTypeMismatch): t(k1, k2) ks = 'bcdoxXneEfFgGs' compat = [ @@ -291,13 +286,13 @@ class test_format_spec: s = '{' + str(v) + ':{' + str(f) + '}}' return M.FormatString(s) fmt = t() - assert_equal(len(fmt.argument_map), 2) + assert len(fmt.argument_map) == 2 t(v=0, f=M.SSIZE_MAX) - with assert_raises(M.ArgumentRangeError): + with pytest.raises(M.ArgumentRangeError): t(v=0, f=(M.SSIZE_MAX + 1)) - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): t(v=0) - with assert_raises(M.ArgumentNumberingMixture): + with pytest.raises(M.ArgumentNumberingMixture): t(f=0) # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_strformat_python.py =================================================================== --- i18nspector-0.26.orig/tests/test_strformat_python.py +++ i18nspector-0.26/tests/test_strformat_python.py @@ -20,53 +20,55 @@ import struct -from nose.tools import ( - assert_equal, - assert_greater, - assert_is_instance, - assert_raises, - assert_sequence_equal, -) +import pytest import lib.strformat.python as M +from . import tools + + +# methods using the tools.collect_yielded decorator don't have a 'self' +# since they end up being run before 'self' exists. pylint doesn't +# understand this unusual situation +# pylint: disable=no-method-argument + def test_SSIZE_MAX(): struct.pack('=i', M.SSIZE_MAX) - with assert_raises(struct.error): + with pytest.raises(struct.error): struct.pack('=i', M.SSIZE_MAX + 1) def test_lone_percent(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('%') def test_invalid_conversion_spec(): - with assert_raises(M.Error): + with pytest.raises(M.Error): M.FormatString('%!') def test_add_argument(): fmt = M.FormatString('%s') - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): fmt.add_argument(None, None) - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): fmt.add_argument('eggs', None) def test_text(): fmt = M.FormatString('eggs%dbacon%dspam') - assert_equal(len(fmt), 5) + assert len(fmt) == 5 fmt = list(fmt) - assert_equal(fmt[0], 'eggs') - assert_equal(fmt[2], 'bacon') - assert_equal(fmt[4], 'spam') + assert fmt[0] == 'eggs' + assert fmt[2] == 'bacon' + assert fmt[4] == 'spam' class test_map: def t(self, key): s = '%(' + key + ')s' fmt = M.FormatString(s) - assert_equal(len(fmt), 1) - assert_sequence_equal(fmt.seq_arguments, []) + assert len(fmt) == 1 + assert fmt.seq_arguments == [] [pkey] = fmt.map_arguments.keys() - assert_equal(key, pkey) + assert key == pkey def test_simple(self): self.t('eggs') @@ -75,69 +77,82 @@ class test_map: self.t('eggs(ham)spam') def test_unbalanced_parens(self): - with assert_raises(M.Error): + with pytest.raises(M.Error): self.t('eggs(ham') class test_types: - def t(self, s, tp, warn_type=None): + @staticmethod + def t( s, tp, warn_type=None): fmt = M.FormatString(s) [conv] = fmt - assert_is_instance(conv, M.Conversion) - assert_equal(conv.type, tp) - assert_equal(len(fmt.map_arguments), 0) + assert isinstance(conv, M.Conversion) + assert conv.type == tp + assert len(fmt.map_arguments) == 0 if tp == 'None': - assert_sequence_equal(fmt.seq_arguments, []) + assert fmt.seq_arguments == [] else: [arg] = fmt.seq_arguments - assert_equal(arg.type, tp) + assert arg.type == tp if warn_type is None: - assert_equal(len(fmt.warnings), 0) + assert len(fmt.warnings) == 0 else: [warning] = fmt.warnings - assert_is_instance(warning, warn_type) + assert isinstance(warning, warn_type) - def test_integer(self): - t = self.t + @tools.collect_yielded + def test_integer(): + def t(*args): + test_types.t(*args) for c in 'oxXdi': - yield t, '%' + c, 'int' - yield t, '%u', 'int', M.ObsoleteConversion + yield t, ('%' + c, 'int') + yield t, ('%u', 'int', M.ObsoleteConversion) - def test_float(self): - t = self.t + @tools.collect_yielded + def test_float(): + def t(*args): + test_types.t(*args) for c in 'eEfFgG': - yield t, '%' + c, 'float' - - def test_str(self): - t = self.t - yield t, '%c', 'chr' - yield t, '%s', 'str' + yield t, ('%' + c, 'float') - def test_repr(self): - t = self.t + @tools.collect_yielded + def test_str(): + def t(*args): + test_types.t(*args) + yield t, ('%c', 'chr') + yield t, ('%s', 'str') + + @tools.collect_yielded + def test_repr(): + def t(*args): + test_types.t(*args) for c in 'ra': - yield t, '%' + c, 'object' + yield t, ('%' + c, 'object') - def test_void(self): - yield self.t, '%%', 'None' + @tools.collect_yielded + def test_void(): + def t(*args): + test_types.t(*args) + yield t, ('%%', 'None') +@tools.collect_yielded def test_length(): def t(l): fmt = M.FormatString('%' + l + 'd') [warning] = fmt.warnings - assert_is_instance(warning, M.RedundantLength) + assert isinstance(warning, M.RedundantLength) for l in 'hlL': yield t, l class test_indexing: def test_percent(self): - with assert_raises(M.ForbiddenArgumentKey): + with pytest.raises(M.ForbiddenArgumentKey): M.FormatString('%(eggs)%') def test_indexing_mixture(self): def t(s): - with assert_raises(M.ArgumentIndexingMixture): + with pytest.raises(M.ArgumentIndexingMixture): M.FormatString(s) t('%s%(eggs)s') t('%(eggs)s%s') @@ -149,7 +164,7 @@ class test_multiple_flags: def t(self, s): fmt = M.FormatString(s) [exc] = fmt.warnings - assert_is_instance(exc, M.RedundantFlag) + assert isinstance(exc, M.RedundantFlag) def test_duplicate(self): self.t('%--17d') @@ -160,108 +175,114 @@ class test_multiple_flags: def test_plus_space(self): self.t('%+ d') +@tools.collect_yielded def test_single_flag(): def t(s, expected): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 if expected: - assert_sequence_equal(fmt.warnings, []) + assert fmt.warnings == [] else: [exc] = fmt.warnings - assert_is_instance(exc, M.RedundantFlag) + assert isinstance(exc, M.RedundantFlag) for c in 'dioxXeEfFgGcrsa%': - yield t, ('%#' + c), (c in 'oxXeEfFgG') + yield t, (('%#' + c), (c in 'oxXeEfFgG')) for flag in '0 +': - yield t, ('%' + flag + c), (c in 'dioxXeEfFgG') - yield t, ('%-' + c), True + yield t, (('%' + flag + c), (c in 'dioxXeEfFgG')) + yield t, (('%-' + c), True) class test_width: - def test_ok(self): + @tools.collect_yielded + def test_ok(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) - assert_sequence_equal(fmt.warnings, []) + assert len(fmt) == 1 + assert fmt.warnings == [] for c in 'dioxXeEfFgGcrsa%': yield t, ('%1' + c) def test_too_large(self): fmt = M.FormatString('%{0}d'.format(M.SSIZE_MAX)) - assert_equal(len(fmt), 1) - assert_equal(len(fmt.seq_arguments), 1) - assert_equal(len(fmt.map_arguments), 0) - with assert_raises(M.WidthRangeError): + assert len(fmt) == 1 + assert len(fmt.seq_arguments) == 1 + assert len(fmt.map_arguments) == 0 + with pytest.raises(M.WidthRangeError): M.FormatString('%{0}d'.format(M.SSIZE_MAX + 1)) def test_variable(self): fmt = M.FormatString('%*s') - assert_equal(len(fmt), 1) - assert_equal(len(fmt.map_arguments), 0) + assert len(fmt) == 1 + assert len(fmt.map_arguments) == 0 [a1, a2] = fmt.seq_arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'str') + assert a1.type == 'int' + assert a2.type == 'str' def test_indexing_mixture(self): def t(s): - with assert_raises(M.ArgumentIndexingMixture): + with pytest.raises(M.ArgumentIndexingMixture): M.FormatString(s) t('%*s%(eggs)s') t('%(eggs)s%*s') class test_precision: - def test_ok(self): + @tools.collect_yielded + def test_ok(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 for c in 'dioxXeEfFgGrsa': yield t, ('%.1' + c) - def test_redundant_0(self): + @tools.collect_yielded + def test_redundant_0(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 [warning] = fmt.warnings - assert_is_instance(warning, M.RedundantFlag) + assert isinstance(warning, M.RedundantFlag) for c in 'dioxX': yield t, ('%0.1' + c) - def test_non_redundant_0(self): + @tools.collect_yielded + def test_non_redundant_0(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) - assert_sequence_equal(fmt.warnings, []) + assert len(fmt) == 1 + assert fmt.warnings == [] for c in 'eEfFgG': yield t, ('%0.1' + c) - def test_unexpected(self): + @tools.collect_yielded + def test_unexpected(): def t(s): fmt = M.FormatString(s) - assert_equal(len(fmt), 1) + assert len(fmt) == 1 [warning] = fmt.warnings - assert_is_instance(warning, M.RedundantPrecision) + assert isinstance(warning, M.RedundantPrecision) for c in 'c%': yield t, ('%.1' + c) def test_too_large(self): fmt = M.FormatString('%.{0}f'.format(M.SSIZE_MAX)) - assert_equal(len(fmt), 1) - with assert_raises(M.PrecisionRangeError): + assert len(fmt) == 1 + with pytest.raises(M.PrecisionRangeError): M.FormatString('%.{0}f'.format(M.SSIZE_MAX + 1)) def test_variable(self): fmt = M.FormatString('%.*f') - assert_equal(len(fmt), 1) - assert_equal(len(fmt.map_arguments), 0) + assert len(fmt) == 1 + assert len(fmt.map_arguments) == 0 [a1, a2] = fmt.seq_arguments - assert_equal(a1.type, 'int') - assert_equal(a2.type, 'float') + assert a1.type == 'int' + assert a2.type == 'float' def test_indexing_mixture(self): def t(s): - with assert_raises(M.ArgumentIndexingMixture): + with pytest.raises(M.ArgumentIndexingMixture): M.FormatString(s) t('%.*f%(eggs)f') t('%(eggs)f%.*f') @@ -271,26 +292,26 @@ class test_type_compatibility: def test_okay(self): def t(s, tp): fmt = M.FormatString(s) - assert_equal(len(fmt.seq_arguments), 0) + assert len(fmt.seq_arguments) == 0 [args] = fmt.map_arguments.values() - assert_greater(len(args), 1) + assert len(args) > 1 for arg in args: - assert_equal(arg.type, tp) + assert arg.type == tp t('%(eggs)d%(eggs)d', 'int') t('%(eggs)d%(eggs)i', 'int') def test_mismatch(self): def t(s): - with assert_raises(M.ArgumentTypeMismatch): + with pytest.raises(M.ArgumentTypeMismatch): M.FormatString(s) t('%(eggs)d%(eggs)s') def test_seq_conversions(): def t(s, n): fmt = M.FormatString(s) - assert_equal(len(fmt.seq_conversions), n) + assert len(fmt.seq_conversions) == n for arg in fmt.seq_conversions: - assert_is_instance(arg, M.Conversion) + assert isinstance(arg, M.Conversion) t('%d', 1) t('%d%d', 2) t('eggs%dham', 1) Index: i18nspector-0.26/tests/test_tags.py =================================================================== --- i18nspector-0.26.orig/tests/test_tags.py +++ i18nspector-0.26/tests/test_tags.py @@ -24,26 +24,20 @@ import inspect import operator import pkgutil -from nose.tools import ( - assert_equal, - assert_false, - assert_is_instance, - assert_raises, - assert_true, -) +import pytest import lib.check import lib.tags as M +from . import tools class test_escape: def t(self, s, expected): result = M.safe_format('{}', s) - assert_is_instance(result, M.safestr) - assert_equal( - result, - expected - ) + assert isinstance(result, M.safestr) + assert ( + result == + expected) def test_safe(self): s = 'fox' @@ -75,6 +69,7 @@ def ast_to_tagnames(node): if ok: yield node.args[0].s +@tools.collect_yielded def test_consistency(): source_tagnames = set() def visit_mod(modname): @@ -117,13 +112,13 @@ class test_enums: for op in operators: for i, x in enumerate(keys): for j, y in enumerate(keys): - assert_equal(op(x, y), op(i, j)) + assert op(x, y) == op(i, j) if op is operator.eq: - assert_false(op(x, j)) + assert not op(x, j) elif op is operator.ne: - assert_true(op(x, j)) + assert op(x, j) else: - with assert_raises(TypeError): + with pytest.raises(TypeError): op(x, j) def test_severities(self): Index: i18nspector-0.26/tests/test_terminal.py =================================================================== --- i18nspector-0.26.orig/tests/test_terminal.py +++ i18nspector-0.26/tests/test_terminal.py @@ -23,17 +23,13 @@ import os import pty import sys -from nose.tools import ( - assert_equal, -) - import lib.terminal as T from . import tools def test_strip_delay(): def t(s, r=b''): - assert_equal(T._strip_delay(s), r) # pylint: disable=protected-access + assert T._strip_delay(s) == r # pylint: disable=protected-access t(b'$<1>') t(b'$<2/>') t(b'$<3*>') @@ -56,7 +52,7 @@ def assert_tseq_equal(s, expected): # but not their subclasses. We don't want detailed comparisons, # because diff could contain control characters. pass - assert_equal(S(expected), S(s)) + assert S(expected) == S(s) def test_dummy(): t = assert_tseq_equal Index: i18nspector-0.26/tests/test_version.py =================================================================== --- i18nspector-0.26.orig/tests/test_version.py +++ i18nspector-0.26/tests/test_version.py @@ -20,10 +20,6 @@ import os -from nose.tools import ( - assert_equal, -) - from lib.cli import __version__ here = os.path.dirname(__file__) @@ -34,7 +30,7 @@ def test_changelog(): with open(path, 'rt', encoding='UTF-8') as file: line = file.readline() changelog_version = line.split()[1].strip('()') - assert_equal(changelog_version, __version__) + assert changelog_version == __version__ def test_manpage(): path = os.path.join(docdir, 'manpage.rst') @@ -44,6 +40,6 @@ def test_manpage(): if line.startswith(':version:'): manpage_version = line.split()[-1] break - assert_equal(manpage_version, __version__) + assert manpage_version == __version__ # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/test_xml.py =================================================================== --- i18nspector-0.26.orig/tests/test_xml.py +++ i18nspector-0.26/tests/test_xml.py @@ -21,11 +21,7 @@ import re import xml.etree.ElementTree as etree -from nose.tools import ( - assert_is_none, - assert_is_not_none, - assert_raises, -) +import pytest import lib.xml as M @@ -44,7 +40,7 @@ class test_well_formed: class test_malformed: def t(self, s): - with assert_raises(M.SyntaxError): + with pytest.raises(M.SyntaxError): M.check_fragment(s) def test_non_xml_character(self): @@ -73,7 +69,7 @@ class test_name_re(): def test_good(self): def t(s): match = self.regexp.match(s) - assert_is_not_none(match) + assert match is not None t(':') t('_') t('e') @@ -85,7 +81,7 @@ class test_name_re(): def test_bad(self): def t(s): match = self.regexp.match(s) - assert_is_none(match) + assert match is None t('') t('0') t('-') Index: i18nspector-0.26/tests/tools.py =================================================================== --- i18nspector-0.26.orig/tests/tools.py +++ i18nspector-0.26/tests/tools.py @@ -1,4 +1,5 @@ # Copyright © 2012-2019 Jakub Wilk +# Copyright © 2021 Stuart Prescott # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal @@ -23,8 +24,9 @@ import os import sys import tempfile import traceback +import unittest -import nose +import pytest temporary_file = functools.partial( tempfile.NamedTemporaryFile, @@ -52,8 +54,8 @@ def fork_isolation(f): EXIT_SKIP_TEST = 102 exit = os._exit # pylint: disable=redefined-builtin,protected-access - # sys.exit() can't be used here, because nose catches all exceptions, - # including SystemExit + # sys.exit() can't be used here, because the test harness catches all + # exceptions, including SystemExit # pylint:disable=consider-using-sys-exit @@ -66,7 +68,7 @@ def fork_isolation(f): os.close(readfd) try: f(*args, **kwargs) - except nose.SkipTest as exc: + except unittest.SkipTest as exc: s = str(exc).encode('UTF-8') with os.fdopen(writefd, 'wb') as fp: fp.write(s) @@ -90,7 +92,7 @@ def fork_isolation(f): if status == (EXIT_EXCEPTION << 8): raise IsolatedException() from Exception('\n\n' + msg) elif status == (EXIT_SKIP_TEST << 8): - raise nose.SkipTest(msg) + raise unittest.SkipTest(msg) elif status == 0 and msg == '': pass else: @@ -100,6 +102,37 @@ def fork_isolation(f): return wrapper + +def collect_yielded(collect_func): + # figure out if this is a function or method being wrapped + # as the wrapper needs a slightly different signature + if collect_func.__name__ != collect_func.__qualname__: + # method + @pytest.mark.parametrize("func, test_args", + list(collect_func())) + def yield_tester(self, func, test_args): + # pylint: disable=unused-argument + # self is unused here + if isinstance(test_args, (tuple, list)): + func(*test_args) + elif isinstance(test_args, (str, int, float)): + func(test_args) + else: + raise ValueError("args must be either a tuple or a str") + else: + # function + @pytest.mark.parametrize("func, test_args", + list(collect_func())) + def yield_tester(func, test_args): + if isinstance(test_args, (tuple, list)): + func(*test_args) + elif isinstance(test_args, (str, int, float)): + func(test_args) + else: + raise ValueError("args must be either a tuple or a str") + return yield_tester + + basedir = os.path.join( os.path.dirname(__file__), os.pardir, Index: i18nspector-0.26/.coveragerc =================================================================== --- i18nspector-0.26.orig/.coveragerc +++ i18nspector-0.26/.coveragerc @@ -1,5 +1,11 @@ [run] branch = true +omit = + *_boot*.py + */lib/python*/* + */dist-packages/* + */tests/* +source = lib [report] show_missing = true Index: i18nspector-0.26/private/update-branch-coverage =================================================================== --- i18nspector-0.26.orig/private/update-branch-coverage +++ i18nspector-0.26/private/update-branch-coverage @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/bin/bash -# Copyright © 2014-2018 Jakub Wilk +# Copyright © 2021 Stuart Prescott # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal @@ -20,55 +20,22 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import glob -import io -import os -import sys - -import nose -import nose.plugins.cover - -class Coverage(nose.plugins.cover.Coverage): - - stream = None - - def report(self, stream): - return super().report(self.stream) - -basedir = os.path.join( - os.path.dirname(__file__), - os.pardir, -) - -def main(): - os.chdir(basedir) - module_glob = os.path.join('tests', 'test_*.py') - modules = glob.glob(module_glob) - argv = [ - sys.argv[0], - '--with-coverage', - '--cover-package=lib', - '--cover-erase', - ] + modules - path = os.path.join( - 'tests', - 'coverage' - ) - plugin = Coverage() - report_stream = plugin.stream = io.StringIO() - print('Generated automatically by private/update-branch-coverage. ' - 'Do not edit.\n', file=report_stream) - ok = nose.run(argv=argv, plugins=[plugin]) - if not ok: - sys.exit(1) - report_stream.seek(0) - with open(path + '.tmp', 'wt', encoding='ASCII') as file: - for line in report_stream: - line = line.rstrip() - print(line, file=file) - os.rename(path + '.tmp', path) -if __name__ == '__main__': - main() +set -e + +basedir=$(dirname "$0") + +cd "$basedir/.." + +pytest --cov lib/ --cov-branch -rsx -v tests --ignore tests/blackbox_tests + +( + set -e + echo 'Generated automatically by private/update-branch-coverage. Do not edit.' + echo + python3-coverage report +) > tests/coverage.tmp + +mv tests/coverage.tmp tests/coverage # vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/blackbox_tests/__init__.py =================================================================== --- i18nspector-0.26.orig/tests/blackbox_tests/__init__.py +++ i18nspector-0.26/tests/blackbox_tests/__init__.py @@ -1,415 +0,0 @@ -# Copyright © 2012-2017 Jakub Wilk -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the “Software”), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import difflib -import inspect -import io -import multiprocessing as mp -import os -import re -import shlex -import signal -import subprocess as ipc -import sys -import traceback -import unittest - -import nose -import nose.plugins - -from .. import tools - -here = os.path.dirname(__file__) - -# ---------------------------------------- - -def this(): - ''' - Return function that called this function. (Hopefully!) - ''' - return globals()[inspect.stack()[1][0].f_code.co_name] - -# ---------------------------------------- - -_parse_etag = re.compile(r'([A-Z]): (([\w-]+).*)').match - -def parse_etag(contents, path): - match = _parse_etag(contents) - if match is None: - return - t = ETag(match.group(1), path, match.group(2)) - return t - -def etags_from_tagstring(obj, path): - try: - docstring = obj.tagstring - except AttributeError: - return - for line in docstring.splitlines(): - line = line.lstrip() - t = parse_etag(line, path) - if t is not None: - yield t - -def tagstring(s): - def update(x): - x.tagstring = s - return x - return update - -# ---------------------------------------- - -class ETag(): - - _ellipsis = '<...>' - _split = re.compile('({})'.format(re.escape(_ellipsis))).split - - def __init__(self, code, path, rest): - self._s = s = '{code}: {path}: {rest}'.format( - code=code, - path=path, - rest=rest, - ) - self.tag = rest.split(None, 1)[0] - regexp = ''.join( - '.*' if chunk == self._ellipsis else re.escape(chunk) - for chunk in self._split(s) - ) - self._regexp = re.compile('^{}$'.format(regexp)) - - def __eq__(self, other): - if isinstance(other, str): - return self._regexp.match(other) - else: - return NotImplemented - - def __str__(self): - return self._s - - def __repr__(self): - return repr(self._s) - -# ---------------------------------------- - -def _get_signal_names(): - data = dict( - (name, getattr(signal, name)) - for name in dir(signal) - if re.compile('^SIG[A-Z0-9]*$').match(name) - ) - try: - if data['SIGABRT'] == data['SIGIOT']: - del data['SIGIOT'] - except KeyError: - pass - try: - if data['SIGCHLD'] == data['SIGCLD']: - del data['SIGCLD'] - except KeyError: - pass - for name, n in data.items(): - yield n, name - -_signal_names = dict(_get_signal_names()) - -def get_signal_name(n): - try: - return _signal_names[n] - except KeyError: - return str(n) - -# ---------------------------------------- - -test_file_extensions = ('.mo', '.po', '.pot', '.pop') -# .pop is a special extension to trigger unknown-file-type - -class Plugin(nose.plugins.Plugin): - - name = 'po-plugin' - enabled = True - - def options(self, parser, env): - pass - - def wantFile(self, path): - if path.endswith(test_file_extensions): - if path.startswith(os.path.join(os.path.abspath(here), '')): - return True - - def loadTestsFromFile(self, path): - if self.wantFile(path): - yield TestCase(path) - - def wantFunction(self, func): - if getattr(func, 'redundant', False): - return False - -class TestCase(unittest.TestCase): - - def __init__(self, path): - super().__init__('_test') - self.path = os.path.relpath(path) - - def _test(self): - _test_file(self.path, basedir=None) - - def __str__(self): - return self.path - -class SubprocessError(Exception): - pass - -def queue_get(queue, process): - ''' - Remove and return an item from the queue. - Block until the process terminates. - ''' - while True: - try: - return queue.get(timeout=1) - # This semi-active waiting is ugly, but there doesn't seem be any - # obvious better way. - except mp.queues.Empty: - if process.exitcode is None: - continue - else: - raise - -def run_i18nspector(options, path): - commandline = os.environ.get('I18NSPECTOR_COMMANDLINE') - if commandline is None: - # We cheat here a bit, because exec(3)ing is very expensive. - # Let's load the needed Python modules, and use multiprocessing to - # “emulate” the command execution. - import lib.cli # pylint: disable=import-outside-toplevel - assert lib.cli # make pyflakes happy - prog = os.path.join(here, os.pardir, os.pardir, 'i18nspector') - commandline = [sys.executable, prog] - queue = mp.Queue() - child = mp.Process( - target=_mp_run_i18nspector, - args=(prog, options, path, queue) - ) - child.start() - [stdout, stderr] = ( - s.splitlines() - for s in queue_get(queue, child) - ) - child.join() - rc = child.exitcode - else: - commandline = shlex.split(commandline) - commandline += options - commandline += [path] - fixed_env = dict(os.environ, PYTHONIOENCODING='UTF-8') - child = ipc.Popen(commandline, stdout=ipc.PIPE, stderr=ipc.PIPE, env=fixed_env) - stdout, stderr = ( - s.decode('UTF-8').splitlines() - for s in child.communicate() - ) - rc = child.poll() - assert isinstance(rc, int) - if rc == 0: - return stdout - if rc < 0: - message = ['command was interrupted by signal {sig}'.format(sig=get_signal_name(-rc))] # pylint: disable=invalid-unary-operand-type - else: - message = ['command exited with status {rc}'.format(rc=rc)] - message += [''] - if stdout: - message += ['stdout:'] - message += ['| ' + s for s in stdout] + [''] - else: - message += ['stdout: (empty)'] - if stderr: - message += ['stderr:'] - message += ['| ' + s for s in stderr] - else: - message += ['stderr: (empty)'] - raise SubprocessError('\n'.join(message)) - -def _mp_run_i18nspector(prog, options, path, queue): - with open(prog, 'rt', encoding='UTF-8') as file: - code = file.read() - sys.argv = [prog] + list(options) + [path] - orig_stdout = sys.stdout - orig_stderr = sys.stderr - code = compile(code, prog, 'exec') - io_stdout = io.StringIO() - io_stderr = io.StringIO() - gvars = dict( - __file__=prog, - ) - (sys.stdout, sys.stderr) = (io_stdout, io_stderr) - try: - try: - exec(code, gvars) # pylint: disable=exec-used - finally: - (sys.stdout, sys.stderr) = (orig_stdout, orig_stderr) - stdout = io_stdout.getvalue() - stderr = io_stderr.getvalue() - except SystemExit: - queue.put([stdout, stderr]) - raise - except: # pylint: disable=bare-except - exctp, exc, tb = sys.exc_info() - stderr += ''.join( - traceback.format_exception(exctp, exc, tb) - ) - queue.put([stdout, stderr]) - sys.exit(1) - raise # hi, pydiatra! - else: - queue.put([stdout, stderr]) - sys.exit(0) - -def assert_emit_tags(path, etags, *, options=()): - etags = list(etags) - stdout = run_i18nspector(options, path) - expected_failure = os.path.basename(path).startswith('xfail-') - if stdout != etags: - if expected_failure: - raise nose.SkipTest('expected failure') - str_etags = [str(x) for x in etags] - message = ['Tags differ:', ''] - diff = list( - difflib.unified_diff(str_etags, stdout, n=9999) - ) - message += diff[3:] - raise AssertionError('\n'.join(message)) - elif expected_failure: - raise AssertionError('unexpected success') - -class TestFileSyntaxError(Exception): - pass - -def _parse_test_header_file(file, path, *, comments_only): - etags = [] - options = [] - for n, line in enumerate(file): - orig_line = line - if comments_only: - if n == 0 and line.startswith('#!'): - continue - if line.startswith('# '): - line = line[2:] - else: - break - if line.startswith('--'): - options += shlex.split(line) - else: - etag = parse_etag(line, path) - if etag is None: - if comments_only: - break - else: - raise TestFileSyntaxError(orig_line) - etags += [etag] - return etags, options - -def _parse_test_headers(path): - # .tags: - try: - file = open(path + '.tags', encoding='UTF-8') - except FileNotFoundError: - pass - else: - with file: - return _parse_test_header_file(file, path, comments_only=False) - # .gen: - try: - file = open(path + '.gen', encoding='UTF-8', errors='ignore') - except FileNotFoundError: - pass - else: - with file: - return _parse_test_header_file(file, path, comments_only=True) - # : - with open(path, 'rt', encoding='UTF-8', errors='ignore') as file: - return _parse_test_header_file(file, path, comments_only=True) - -def _test_file(path, basedir=here): - if basedir is not None: - path = os.path.relpath(os.path.join(basedir, path), start=os.getcwd()) - options = [] - etags, options = _parse_test_headers(path) - assert_emit_tags(path, etags, options=options) - -def get_coverage_for_file(path): - etags, options = _parse_test_headers(path) - del options - return (t.tag for t in etags) - -def get_coverage_for_function(fn): - for etag in etags_from_tagstring(fn, ''): - yield etag.tag - -def _get_test_filenames(): - for root, dirnames, filenames in os.walk(here): - del dirnames - for filename in filenames: - if not filename.endswith(test_file_extensions): - continue - yield os.path.join(root, filename) - -def test_file(): - for filename in _get_test_filenames(): - path = os.path.relpath(filename, start=here) - yield _test_file, path -test_file.redundant = True # not needed if the plugin is enabled - -@tagstring(''' -E: os-error No such file or directory -''') -def test_os_error_no_such_file(): - with tools.temporary_directory() as tmpdir: - path = os.path.join(tmpdir, 'nonexistent.po') - expected = etags_from_tagstring(this(), path) - assert_emit_tags(path, expected) - -@tagstring(''' -E: os-error Permission denied -''') -def test_os_error_permission_denied(): - if os.getuid() == 0: - raise nose.SkipTest('this test must not be run as root') - with tools.temporary_directory() as tmpdir: - path = os.path.join(tmpdir, 'denied.po') - with open(path, 'wb'): - pass - os.chmod(path, 0) - expected = etags_from_tagstring(this(), path) - assert_emit_tags(path, expected) - -# ---------------------------------------- - -def get_coverage(): - coverage = set() - for filename in _get_test_filenames(): - for tag in get_coverage_for_file(filename): - coverage.add(tag) - for objname, obj in globals().items(): - if not objname.startswith('test_'): - continue - for tag in get_coverage_for_function(obj): - coverage.add(tag) - return coverage - -# vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/blackbox_tests/conftest.py =================================================================== --- /dev/null +++ i18nspector-0.26/tests/blackbox_tests/conftest.py @@ -0,0 +1,424 @@ +# Copyright © 2012-2021 Jakub Wilk +# Copyright © 2021 Stuart Prescott +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the “Software”), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import difflib +import inspect +import io +import multiprocessing as mp +import os +import re +import shlex +import signal +import subprocess as ipc +import sys +import traceback +import unittest + +import pytest + + +here = os.path.dirname(__file__) + + +# ---------------------------------------- + +def this(): + ''' + Return function that called this function. (Hopefully!) + ''' + return globals()[inspect.stack()[1][0].f_code.co_name] + +# ---------------------------------------- + +_parse_etag = re.compile(r'([A-Z]): (([\w-]+).*)').match + +def parse_etag(contents, path): + match = _parse_etag(contents) + if match is None: + return + t = ETag(match.group(1), path, match.group(2)) + return t + +def etags_from_tagstring(obj, path): + try: + docstring = obj.tagstring + except AttributeError: + return + for line in docstring.splitlines(): + line = line.lstrip() + t = parse_etag(line, path) + if t is not None: + yield t + +def tagstring(s): + def update(x): + x.tagstring = s + return x + return update + +# ---------------------------------------- + +class ETag(): + + _ellipsis = '<...>' + _split = re.compile('({})'.format(re.escape(_ellipsis))).split + + def __init__(self, code, path, rest): + self._s = s = '{code}: {path}: {rest}'.format( + code=code, + path=path, + rest=rest, + ) + self.tag = rest.split(None, 1)[0] + regexp = ''.join( + '.*' if chunk == self._ellipsis else re.escape(chunk) + for chunk in self._split(s) + ) + self._regexp = re.compile('^{}$'.format(regexp)) + + def __eq__(self, other): + if isinstance(other, str): + return self._regexp.match(other) + else: + return NotImplemented + + def __str__(self): + return self._s + + def __repr__(self): + return repr(self._s) + +# ---------------------------------------- + +def _get_signal_names(): + data = dict( + (name, getattr(signal, name)) + for name in dir(signal) + if re.compile('^SIG[A-Z0-9]*$').match(name) + ) + try: + if data['SIGABRT'] == data['SIGIOT']: + del data['SIGIOT'] + except KeyError: + pass + try: + if data['SIGCHLD'] == data['SIGCLD']: + del data['SIGCLD'] + except KeyError: + pass + for name, n in data.items(): + yield n, name + +_signal_names = dict(_get_signal_names()) + +def get_signal_name(n): + try: + return _signal_names[n] + except KeyError: + return str(n) + +# ---------------------------------------- + +# Here, a pytest hook function (pytest_collect_file) is used to decide that +# the .mo/.po/.pot files in the directory are actually test items. +# For each file that is found, a PoTestFile is created. For each PoTestFile, +# a testable item (PoTestItem) is created; if needed, there could be be +# more than one PoTestItem per PoTestFile, but the code currently only +# creates a single test. + +test_file_extensions = ('.mo', '.po', '.pot', '.pop') +# .pop is a special extension to trigger unknown-file-type + + +def pytest_collect_file(parent, path): + """hook function that decides that po files are actually tests""" + if path.ext in test_file_extensions: + return PoTestFile.from_parent(parent, fspath=path) + + +class PoTestFile(pytest.File): + """Class to yield one or more test items that are contained in the file + + In this implementation, only one test is yielded. + """ + # pylint: disable=abstract-method + # pylint gets confused about this subclassing and emits warnings about + # (unneeded) abstract methods not being defined. + # https://github.com/pytest-dev/pytest/issues/7591 + + def collect(self): + # record the po file's basename and its full filesystem path as + # strings; pytest will happily use Path objects but the rest of + # stdlib on older Python versions only wants str not Path. + fname = str(self.fspath) + name = os.path.basename(fname) + yield PoTestItem.from_parent(self, name=name, filename=fname) + + +class PoTestItem(pytest.Item): + """Class that represents a single test.""" + # pylint: disable=abstract-method + + def __init__(self, name, parent, filename): + super().__init__(name, parent) + self.filename = filename + + def runtest(self): + """run the test + + The intended i18nspector error tags are extracted from the file, + i18nspector is run on the file and the sets of tags are asserted + to be identical. Exceptions may also be raised for various failure + modes of i18nspector or other test harness failures. + """ + etags, options = self.parse_test_headers() + assert_emit_tags(self.filename, etags, options=options) + + def repr_failure(self, excinfo, style=None): + """Presentation of test failures for when self.runtest() raises an exception.""" + if isinstance(excinfo.value, PoTestFileException): + return "\n".join( + [ + "blackbox test failed for " + self.name, + str(excinfo.value), + ] + ) + else: + return "\n".join( + [ + "blackbox test error for " + self.name, + str(excinfo.value), + ] + ) + + def reportinfo(self): + """ header for test failure/error/stdout reporting """ + return self.fspath, 0, "blackbox test: " + self.name + + def _parse_test_header_file(self, fh, path, *, comments_only): + etags = [] + options = [] + for n, line in enumerate(fh): + orig_line = line + if comments_only: + if n == 0 and line.startswith('#!'): + continue + if line.startswith('# '): + line = line[2:] + else: + break + if line.startswith('--'): + options += shlex.split(line) + else: + etag = parse_etag(line, path) + if etag is None: + if comments_only: + break + else: + raise TestFileSyntaxError(orig_line) + etags += [etag] + return etags, options + + def parse_test_headers(self): + path = self.filename + # .tags: + try: + fh = open(path + '.tags', encoding='UTF-8') # pylint: disable=consider-using-with + except FileNotFoundError: + pass + else: + with fh: + return self._parse_test_header_file(fh, path, comments_only=False) + # .gen: + try: + fh = open(path + '.gen', encoding='UTF-8', errors='ignore') # pylint: disable=consider-using-with + except FileNotFoundError: + pass + else: + with fh: + return self._parse_test_header_file(fh, path, comments_only=True) + # : + with open(path, 'rt', encoding='UTF-8', errors='ignore') as fh: + return self._parse_test_header_file(fh, path, comments_only=True) + + +class PoTestFileException(AssertionError): + """Custom exception for error reporting.""" + + +class TestFileSyntaxError(Exception): + pass + + +def queue_get(queue, process): + ''' + Remove and return an item from the queue. + Block until the process terminates. + ''' + while True: + try: + return queue.get(timeout=1) + # This semi-active waiting is ugly, but there doesn't seem be any + # obvious better way. + except mp.queues.Empty: + if process.exitcode is None: + continue + else: + raise + +def run_i18nspector(options, path): + commandline = os.environ.get('I18NSPECTOR_COMMANDLINE') + if commandline is None: + # We cheat here a bit, because exec(3)ing is very expensive. + # Let's load the needed Python modules, and use multiprocessing to + # “emulate” the command execution. + import lib.cli # pylint: disable=import-outside-toplevel + assert lib.cli # make pyflakes happy + prog = os.path.join(here, os.pardir, os.pardir, 'i18nspector') + commandline = [sys.executable, prog] + queue = mp.Queue() + child = mp.Process( + target=_mp_run_i18nspector, + args=(prog, options, path, queue) + ) + child.start() + [stdout, stderr] = ( + s.splitlines() + for s in queue_get(queue, child) + ) + child.join() + rc = child.exitcode + else: + commandline = shlex.split(commandline) + commandline += options + commandline += [path] + fixed_env = dict(os.environ, PYTHONIOENCODING='UTF-8') + with ipc.Popen(commandline, stdout=ipc.PIPE, stderr=ipc.PIPE, env=fixed_env) as child: + stdout, stderr = ( + s.decode('UTF-8').splitlines() + for s in child.communicate() + ) + rc = child.poll() + assert isinstance(rc, int) + if rc == 0: + return stdout + if rc < 0: + message = ['command was interrupted by signal {sig}'.format(sig=get_signal_name(-rc))] # pylint: disable=invalid-unary-operand-type + else: + message = ['command exited with status {rc}'.format(rc=rc)] + message += [''] + if stdout: + message += ['stdout:'] + message += ['| ' + s for s in stdout] + [''] + else: + message += ['stdout: (empty)'] + if stderr: + message += ['stderr:'] + message += ['| ' + s for s in stderr] + else: + message += ['stderr: (empty)'] + raise PoTestFileException('\n'.join(message)) + +def _mp_run_i18nspector(prog, options, path, queue): + with open(prog, 'rt', encoding='UTF-8') as file: + code = file.read() + sys.argv = [prog] + list(options) + [path] + orig_stdout = sys.stdout + orig_stderr = sys.stderr + code = compile(code, prog, 'exec') + io_stdout = io.StringIO() + io_stderr = io.StringIO() + gvars = dict( + __file__=prog, + ) + (sys.stdout, sys.stderr) = (io_stdout, io_stderr) + try: + try: + exec(code, gvars) # pylint: disable=exec-used + finally: + (sys.stdout, sys.stderr) = (orig_stdout, orig_stderr) + stdout = io_stdout.getvalue() + stderr = io_stderr.getvalue() + except SystemExit: + queue.put([stdout, stderr]) + raise + except: # pylint: disable=bare-except + exctp, exc, tb = sys.exc_info() + stderr += ''.join( + traceback.format_exception(exctp, exc, tb) + ) + queue.put([stdout, stderr]) + sys.exit(1) + raise # hi, pydiatra! + else: + queue.put([stdout, stderr]) + sys.exit(0) + +def assert_emit_tags(path, etags, *, options=()): + etags = list(etags) + stdout = run_i18nspector(options, path) + expected_failure = os.path.basename(path).startswith('xfail-') + if stdout != etags: + if expected_failure: + raise unittest.SkipTest('expected failure') + str_etags = [str(x) for x in etags] + message = ['Tags differ:', ''] + diff = list( + difflib.unified_diff(str_etags, stdout, n=9999) + ) + message += diff[3:] + raise PoTestFileException('\n'.join(message)) + elif expected_failure: + raise PoTestFileException('unexpected success') + + +# ---------------------------------------- +# Tag coverage recording code + +class PofileRecorderPlugin: + """pytest plugin to just record the test files that are collected""" + def __init__(self): + self.collected = [] + + def pytest_collection_modifyitems(self, items): + for item in items: + self.collected.append((item.nodeid, item)) + + +def get_coverage(): + # ask pytest to enumerate the tests it knows of in this directory + directory = os.path.dirname(__file__) + pofile_recorder = PofileRecorderPlugin() + pytest.main(['--collect-only', '-p', 'no:terminal', directory], plugins=[pofile_recorder]) + + coverage = set() + for _, item in pofile_recorder.collected: + if isinstance(item, PoTestItem): + etags, _ = item.parse_test_headers() + for t in etags: + coverage.add(t.tag) + else: + for tag in etags_from_tagstring(item.function, ''): + coverage.add(tag.tag) + + return coverage + +# vim:ts=4 sts=4 sw=4 et Index: i18nspector-0.26/tests/blackbox_tests/test_errors.py =================================================================== --- /dev/null +++ i18nspector-0.26/tests/blackbox_tests/test_errors.py @@ -0,0 +1,36 @@ +import inspect +import os.path +import unittest + +from .conftest import tagstring, etags_from_tagstring, assert_emit_tags +from .. import tools + + +def this(): + ''' + Return function that called this function. (Hopefully!) + ''' + return globals()[inspect.stack()[1][0].f_code.co_name] + +@tagstring(''' +E: os-error No such file or directory +''') +def test_os_error_no_such_file(): + with tools.temporary_directory() as tmpdir: + path = os.path.join(tmpdir, 'nonexistent.po') + expected = etags_from_tagstring(this(), path) + assert_emit_tags(path, expected) + +@tagstring(''' +E: os-error Permission denied +''') +def test_os_error_permission_denied(): + if os.getuid() == 0: + raise unittest.SkipTest('this test must not be run as root') + with tools.temporary_directory() as tmpdir: + path = os.path.join(tmpdir, 'denied.po') + with open(path, 'wb'): + pass + os.chmod(path, 0) + expected = etags_from_tagstring(this(), path) + assert_emit_tags(path, expected) Index: i18nspector-0.26/private/update-tag-coverage =================================================================== --- i18nspector-0.26.orig/private/update-tag-coverage +++ i18nspector-0.26/private/update-tag-coverage @@ -21,12 +21,14 @@ # SOFTWARE. import os +import os.path import sys sys.path[0] += '/..' from lib import tags -from tests import blackbox_tests +from tests.blackbox_tests import conftest as blackbox_tests + def main(): coverage = blackbox_tests.get_coverage()