diff --git a/python-djvulibre.changes b/python-djvulibre.changes index f33e2c1..4ddae3a 100644 --- a/python-djvulibre.changes +++ b/python-djvulibre.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Mon Jul 26 09:25:12 UTC 2021 - Matej Cepl + +- Port testing of the package to unittest, adding these patches: + * remove-all-dependencies-on-nose-in-the-code.patch + * remove-nose-in-documentation.patch + * sphinx_4_compatibility.patch + ------------------------------------------------------------------- Mon Mar 8 13:48:17 UTC 2021 - Kyrill Detinov diff --git a/python-djvulibre.spec b/python-djvulibre.spec index 6d0f776..9396c6d 100644 --- a/python-djvulibre.spec +++ b/python-djvulibre.spec @@ -27,10 +27,14 @@ URL: http://jwilk.net/software/python-djvulibre Source0: https://files.pythonhosted.org/packages/source/p/python-djvulibre/%{name}-%{version}.tar.gz Source1: https://files.pythonhosted.org/packages/source/p/python-djvulibre/%{name}-%{version}.tar.gz.asc Source2: %{name}.keyring +Patch1: remove-all-dependencies-on-nose-in-the-code.patch +Patch2: remove-nose-in-documentation.patch +# PATCH-FIX-UPSTREAM sphinx_4_compatibility.patch gh#sphinx-doc/sphinx#7747 mcepl@suse.com +# Sphinx doesn't stable API +Patch3: sphinx_4_compatibility.patch BuildRequires: %{python_module Cython >= 0.19.1} BuildRequires: %{python_module Sphinx} BuildRequires: %{python_module devel} -BuildRequires: %{python_module nose} BuildRequires: %{python_module setuptools} BuildRequires: djvulibre BuildRequires: fdupes @@ -59,7 +63,8 @@ an open source implementation of DjVu. This package contains technical documentation. %prep -%setup -q +%autosetup -p1 + chmod -x examples/* %build @@ -74,8 +79,7 @@ rm build/sphinx/html/.buildinfo build/sphinx/html/objects.inv %check cd tests/ -PYTHONPATH=%{buildroot}%{$python_sitearch} -%python_expand PYTHONPATH=%{buildroot}%{$python_sitearch} $python -m nose --exclude='^test_export_ps$' --verbose +%pyunittest_arch -v %files %{python_files} %license doc/COPYING diff --git a/remove-all-dependencies-on-nose-in-the-code.patch b/remove-all-dependencies-on-nose-in-the-code.patch new file mode 100644 index 0000000..0fc17e7 --- /dev/null +++ b/remove-all-dependencies-on-nose-in-the-code.patch @@ -0,0 +1,2547 @@ +From 732f72871f38640e3e9931b4d1e1250b47072b48 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= +Date: Mon, 26 Jul 2021 09:24:45 +0200 +Subject: [PATCH] Remove all dependencies on nose in the code. + +--- + tests/test_const.py | 72 ++-- + tests/test_decode.py | 955 +++++++++++++++++++++---------------------- + tests/test_sexpr.py | 520 ++++++++++++----------- + tests/tools.py | 133 +----- + 4 files changed, 763 insertions(+), 917 deletions(-) + +diff --git a/tests/test_const.py b/tests/test_const.py +index 6679bcc..4c40b27 100644 +--- a/tests/test_const.py ++++ b/tests/test_const.py +@@ -13,6 +13,8 @@ + # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + # more details. + ++import unittest ++ + from djvu.const import ( + TEXT_ZONE_CHARACTER, + TEXT_ZONE_COLUMN, +@@ -29,21 +31,10 @@ from djvu.sexpr import ( + Symbol, + ) + +-from tools import ( +- assert_equal, +- assert_is, +- assert_is_instance, +- assert_list_equal, +- assert_not_equal, +- assert_raises_str, +- assert_repr, +- wildcard_import, +- # Python 2/3 compat: +- cmp, +-) +- +-class test_text_zones(): ++# Py2, Py3 compatibility shim ++from tools import cmp + ++class test_text_zones(unittest.TestCase): + zones = [ + TEXT_ZONE_PAGE, + TEXT_ZONE_COLUMN, +@@ -56,42 +47,42 @@ class test_text_zones(): + + def test_type(self): + for zone in self.zones: +- assert_equal(type(zone), TextZoneType) +- assert_is_instance(zone, Symbol) ++ self.assertEqual(type(zone), TextZoneType) ++ self.assertIsInstance(zone, Symbol) + + def test_repr(self): +- assert_repr(TEXT_ZONE_PAGE, '') +- assert_repr(TEXT_ZONE_COLUMN, '') +- assert_repr(TEXT_ZONE_REGION, '') +- assert_repr(TEXT_ZONE_PARAGRAPH, '') +- assert_repr(TEXT_ZONE_LINE, '') +- assert_repr(TEXT_ZONE_WORD, '') +- assert_repr(TEXT_ZONE_CHARACTER, '') ++ self.assertEqual(repr(TEXT_ZONE_PAGE), '') ++ self.assertEqual(repr(TEXT_ZONE_COLUMN), '') ++ self.assertEqual(repr(TEXT_ZONE_REGION), '') ++ self.assertEqual(repr(TEXT_ZONE_PARAGRAPH), '') ++ self.assertEqual(repr(TEXT_ZONE_LINE), '') ++ self.assertEqual(repr(TEXT_ZONE_WORD), '') ++ self.assertEqual(repr(TEXT_ZONE_CHARACTER), '') + + def test_identity(self): +- assert_is(TEXT_ZONE_PAGE, get_text_zone_type(Symbol('page'))) +- assert_is(TEXT_ZONE_COLUMN, get_text_zone_type(Symbol('column'))) +- assert_is(TEXT_ZONE_REGION, get_text_zone_type(Symbol('region'))) +- assert_is(TEXT_ZONE_PARAGRAPH, get_text_zone_type(Symbol('para'))) +- assert_is(TEXT_ZONE_LINE, get_text_zone_type(Symbol('line'))) +- assert_is(TEXT_ZONE_WORD, get_text_zone_type(Symbol('word'))) +- assert_is(TEXT_ZONE_CHARACTER, get_text_zone_type(Symbol('char'))) ++ self.assertIs(TEXT_ZONE_PAGE, get_text_zone_type(Symbol('page'))) ++ self.assertIs(TEXT_ZONE_COLUMN, get_text_zone_type(Symbol('column'))) ++ self.assertIs(TEXT_ZONE_REGION, get_text_zone_type(Symbol('region'))) ++ self.assertIs(TEXT_ZONE_PARAGRAPH, get_text_zone_type(Symbol('para'))) ++ self.assertIs(TEXT_ZONE_LINE, get_text_zone_type(Symbol('line'))) ++ self.assertIs(TEXT_ZONE_WORD, get_text_zone_type(Symbol('word'))) ++ self.assertIs(TEXT_ZONE_CHARACTER, get_text_zone_type(Symbol('char'))) + + def test_comparison1(self): +- assert_not_equal(TEXT_ZONE_PAGE, '') +- assert_not_equal(TEXT_ZONE_PAGE, 42) +- with assert_raises_str(TypeError, 'cannot compare text zone type with other object'): ++ self.assertNotEqual(TEXT_ZONE_PAGE, '') ++ self.assertNotEqual(TEXT_ZONE_PAGE, 42) ++ with self.assertRaisesRegex(TypeError, 'cannot compare text zone type with other object'): + TEXT_ZONE_PAGE < 42 +- with assert_raises_str(TypeError, 'cannot compare text zone type with other object'): ++ with self.assertRaisesRegex(TypeError, 'cannot compare text zone type with other object'): + TEXT_ZONE_PAGE <= 42 +- with assert_raises_str(TypeError, 'cannot compare text zone type with other object'): ++ with self.assertRaisesRegex(TypeError, 'cannot compare text zone type with other object'): + TEXT_ZONE_PAGE > 42 +- with assert_raises_str(TypeError, 'cannot compare text zone type with other object'): ++ with self.assertRaisesRegex(TypeError, 'cannot compare text zone type with other object'): + TEXT_ZONE_PAGE >= 42 + + def test_comparison2(self): +- assert_equal(self.zones, sorted(self.zones, reverse=True)) +- assert_equal( ++ self.assertEqual(self.zones, sorted(self.zones, reverse=True)) ++ self.assertEqual( + [[cmp(z1, z2) for z1 in self.zones] for z2 in self.zones], [ + [0, -1, -1, -1, -1, -1, -1], + [+1, 0, -1, -1, -1, -1, -1], +@@ -104,8 +95,9 @@ class test_text_zones(): + ) + + def test_wildcard_import(): +- ns = wildcard_import('djvu.const') +- assert_list_equal( ++ ns = {} ++ exec("from djvu.const import *", {}, ns) ++ self.assertEqual( + sorted(ns.keys()), [ + 'ANNOTATION_ALIGN', + 'ANNOTATION_BACKGROUND', +diff --git a/tests/test_decode.py b/tests/test_decode.py +index 44a4418..a90f4e1 100644 +--- a/tests/test_decode.py ++++ b/tests/test_decode.py +@@ -21,6 +21,7 @@ import shutil + import sys + import tempfile + import warnings ++import unittest + + if sys.version_info >= (3, 2): + import subprocess +@@ -86,18 +87,6 @@ from djvu.sexpr import ( + ) + + from tools import ( +- assert_equal, +- assert_false, +- assert_is, +- assert_is_instance, +- assert_list_equal, +- assert_multi_line_equal, +- assert_raises, +- assert_raises_regex, +- assert_raises_str, +- assert_repr, +- assert_true, +- SkipTest, + skip_unless_c_messages, + skip_unless_command_exists, + skip_unless_translation_exists, +@@ -114,71 +103,70 @@ from tools import ( + + images = os.path.join(os.path.dirname(__file__), 'images', '') + +-if sys.version_info >= (3, 2): +- array_tobytes = array.array.tobytes +-else: +- array_tobytes = array.array.tostring ++class TestBase(unittest.TestCase): ++ @staticmethod ++ def run_cmd(*cmd, **kwargs): ++ stdin = kwargs.pop('stdin', None) ++ env = dict(os.environ) ++ for key, value in kwargs.items(): ++ if key.isupper(): ++ env[key] = value ++ continue ++ raise TypeError('{key!r} is an invalid keyword argument for this function'.format(key=key)) ++ kwargs = dict( ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, ++ env=env, ++ ) ++ if stdin is not None: ++ kwargs.update(stdin=subprocess.PIPE) ++ child = subprocess.Popen(list(cmd), **kwargs) ++ (stdout, stderr) = child.communicate(stdin) ++ if child.returncode != 0: ++ raise subprocess.CalledProcessError(child.returncode, cmd[0]) ++ return (stdout, stderr) + +-if sys.version_info < (2, 7): +- memoryview = None # make pyflakes happy ++ def create_djvu(self, commands='', sexpr=''): ++ skip_unless_command_exists('djvused') ++ if sexpr: ++ commands += '\nset-ant\n{sexpr}\n.\n'.format(sexpr=sexpr) ++ file = tempfile.NamedTemporaryFile(prefix='test', suffix='djvu') ++ file.seek(0) ++ file.write( ++ b'\x41\x54\x26\x54\x46\x4F\x52\x4D\x00\x00\x00\x22\x44\x4A\x56\x55' ++ b'\x49\x4E\x46\x4F\x00\x00\x00\x0A\x00\x01\x00\x01\x18\x00\x2C\x01' ++ b'\x16\x01\x53\x6A\x62\x7A\x00\x00\x00\x04\xBC\x73\x1B\xD7' ++ ) ++ file.flush() ++ (stdout, stderr) = self.run_cmd('djvused', '-s', file.name, stdin=commands.encode(locale_encoding)) ++ self.assertEqual(stdout, ''.encode(locale_encoding)) ++ self.assertEqual(stderr, ''.encode(locale_encoding)) ++ return file + +-def run(*cmd, **kwargs): +- stdin = kwargs.pop('stdin', None) +- env = dict(os.environ) +- for key, value in kwargs.items(): +- if key.isupper(): +- env[key] = value +- continue +- raise TypeError('{key!r} is an invalid keyword argument for this function'.format(key=key)) +- kwargs = dict( +- stdout=subprocess.PIPE, +- stderr=subprocess.PIPE, +- env=env, +- ) +- if stdin is not None: +- kwargs.update(stdin=subprocess.PIPE) +- child = subprocess.Popen(list(cmd), **kwargs) +- (stdout, stderr) = child.communicate(stdin) +- if child.returncode != 0: +- raise subprocess.CalledProcessError(child.returncode, cmd[0]) +- return (stdout, stderr) +- +-def create_djvu(commands='', sexpr=''): +- skip_unless_command_exists('djvused') +- if sexpr: +- commands += '\nset-ant\n{sexpr}\n.\n'.format(sexpr=sexpr) +- file = tempfile.NamedTemporaryFile(prefix='test', suffix='djvu') +- file.seek(0) +- file.write( +- b'\x41\x54\x26\x54\x46\x4F\x52\x4D\x00\x00\x00\x22\x44\x4A\x56\x55' +- b'\x49\x4E\x46\x4F\x00\x00\x00\x0A\x00\x01\x00\x01\x18\x00\x2C\x01' +- b'\x16\x01\x53\x6A\x62\x7A\x00\x00\x00\x04\xBC\x73\x1B\xD7' +- ) +- file.flush() +- (stdout, stderr) = run('djvused', '-s', file.name, stdin=commands.encode(locale_encoding)) +- assert_equal(stdout, ''.encode(locale_encoding)) +- assert_equal(stderr, ''.encode(locale_encoding)) +- return file +- +-def test_context_cache(): +- context = Context() +- assert_equal(context.cache_size, 10 << 20) +- for n in -100, 0, 1 << 31: +- with assert_raises_str(ValueError, '0 < cache_size < (2 ** 31) must be satisfied'): ++ # Not used by anything ++ def test_context_cache(self): ++ context = Context() ++ self.assertEqual(context.cache_size, 10 << 20) ++ for n in -100, 0, 1 << 31: ++ with self.assertRaisesRegex( ++ ValueError, ++ r'0 < cache_size < \(2 \*\* 31\) must be satisfied'): ++ context.cache_size = n ++ with self.assertRaisesRegex( ++ ValueError, ++ r'0 < cache_size < \(2 \*\* 31\) must be satisfied'): ++ context.cache_size = 0 ++ n = 1 ++ while n < (1 << 31): + context.cache_size = n +- with assert_raises_str(ValueError, '0 < cache_size < (2 ** 31) must be satisfied'): +- context.cache_size = 0 +- n = 1 +- while n < (1 << 31): +- context.cache_size = n +- assert_equal(context.cache_size, n) +- n = (n + 1) * 2 - 1 +- context.clear_cache() ++ self.assertEqual(context.cache_size, n) ++ n = (n + 1) * 2 - 1 ++ context.clear_cache() + +-class test_documents: ++class test_documents(TestBase): + + def test_bad_new(self): +- with assert_raises_str(TypeError, "cannot create 'djvu.decode.Document' instances"): ++ with self.assertRaisesRegex(TypeError, r"cannot create 'djvu.decode.Document' instances"): + Document() + + def test_nonexistent(self): +@@ -189,20 +177,20 @@ class test_documents: + c_message = ex.args[1] + else: + raise OSError(errno.EEXIST, os.strerror(errno.EEXIST), path) +- c_message.encode('ASCII') ++ c_message.encode('utf-8') + skip_unless_c_messages() + context = Context() +- with assert_raises(JobFailed): ++ with self.assertRaises(JobFailed): + context.new_document(FileUri(path)) + message = context.get_message() +- assert_equal(type(message), ErrorMessage) +- assert_equal(type(message.message), unicode) +- assert_equal( ++ self.assertEqual(type(message), ErrorMessage) ++ self.assertEqual(type(message.message), unicode) ++ self.assertEqual( + message.message, + "[1-11711] Failed to open '{path}': {msg}.".format(path=path, msg=c_message) + ) +- assert_equal(str(message), message.message) +- assert_equal(unicode(message), message.message) ++ self.assertEqual(str(message), message.message) ++ self.assertEqual(unicode(message), message.message) + + def test_nonexistent_ja(self): + skip_unless_c_messages() +@@ -225,57 +213,57 @@ class test_documents: + 'ja_JP error message is ASCII-only: {msg!r}'.format(msg=c_message) + ) + with interim_locale(LC_ALL='ja_JP.UTF-8'): +- with assert_raises(JobFailed): ++ with self.assertRaises(JobFailed): + context.new_document(FileUri(path)) + message = context.get_message() +- assert_equal(type(message), ErrorMessage) +- assert_equal(type(message.message), unicode) +- assert_equal( ++ self.assertEqual(type(message), ErrorMessage) ++ self.assertEqual(type(message.message), unicode) ++ self.assertEqual( + message.message, + u("[1-11711] Failed to open '{path}': {msg}.".format(path=path, msg=c_message)) + ) +- assert_equal( ++ self.assertEqual( + str(message), + "[1-11711] Failed to open '{path}': {msg}.".format(path=path, msg=c_message) + ) +- assert_equal(unicode(message), message.message) ++ self.assertEqual(unicode(message), message.message) + + def test_new_document(self): + context = Context() + document = context.new_document(FileUri(images + 'test1.djvu')) +- assert_equal(type(document), Document) ++ self.assertEqual(type(document), Document) + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) +- assert_true(document.decoding_done) +- assert_false(document.decoding_error) +- assert_equal(document.decoding_status, JobOK) +- assert_equal(document.type, DOCUMENT_TYPE_SINGLE_PAGE) +- assert_equal(len(document.pages), 1) +- assert_equal(len(document.files), 1) ++ self.assertEqual(type(message), DocInfoMessage) ++ self.assertTrue(document.decoding_done) ++ self.assertFalse(document.decoding_error) ++ self.assertEqual(document.decoding_status, JobOK) ++ self.assertEqual(document.type, DOCUMENT_TYPE_SINGLE_PAGE) ++ self.assertEqual(len(document.pages), 1) ++ self.assertEqual(len(document.files), 1) + decoding_job = document.decoding_job +- assert_true(decoding_job.is_done) +- assert_false(decoding_job.is_error) +- assert_equal(decoding_job.status, JobOK) ++ self.assertTrue(decoding_job.is_done) ++ self.assertFalse(decoding_job.is_error) ++ self.assertEqual(decoding_job.status, JobOK) + file = document.files[0] +- assert_is(type(file), File) +- assert_is(file.document, document) +- assert_is(file.get_info(), None) +- assert_equal(file.type, 'P') +- assert_equal(file.n_page, 0) ++ self.assertIs(type(file), File) ++ self.assertIs(file.document, document) ++ self.assertIs(file.get_info(), None) ++ self.assertEqual(file.type, 'P') ++ self.assertEqual(file.n_page, 0) + page = file.page +- assert_equal(type(page), Page) +- assert_is(page.document, document) +- assert_equal(page.n, 0) +- assert_is(file.size, None) +- assert_equal(file.id, u('test1.djvu')) +- assert_equal(type(file.id), unicode) +- assert_equal(file.name, u('test1.djvu')) +- assert_equal(type(file.name), unicode) +- assert_equal(file.title, u('test1.djvu')) +- assert_equal(type(file.title), unicode) ++ self.assertEqual(type(page), Page) ++ self.assertIs(page.document, document) ++ self.assertEqual(page.n, 0) ++ self.assertIs(file.size, None) ++ self.assertEqual(file.id, u('test1.djvu')) ++ self.assertEqual(type(file.id), unicode) ++ self.assertEqual(file.name, u('test1.djvu')) ++ self.assertEqual(type(file.name), unicode) ++ self.assertEqual(file.title, u('test1.djvu')) ++ self.assertEqual(type(file.title), unicode) + dump = document.files[0].dump +- assert_equal(type(dump), unicode) +- assert_equal( ++ self.assertEqual(type(dump), unicode) ++ self.assertEqual( + [line for line in dump.splitlines()], [ + u(' FORM:DJVU [83] '), + u(' INFO [10] DjVu 64x48, v24, 300 dpi, gamma=2.2'), +@@ -283,40 +271,40 @@ class test_documents: + ] + ) + page = document.pages[0] +- assert_equal(type(page), Page) +- assert_is(page.document, document) +- assert_is(page.get_info(), None) +- assert_equal(page.width, 64) +- assert_equal(page.height, 48) +- assert_equal(page.size, (64, 48)) +- assert_equal(page.dpi, 300) +- assert_equal(page.rotation, 0) +- assert_equal(page.version, 24) ++ self.assertEqual(type(page), Page) ++ self.assertIs(page.document, document) ++ self.assertIs(page.get_info(), None) ++ self.assertEqual(page.width, 64) ++ self.assertEqual(page.height, 48) ++ self.assertEqual(page.size, (64, 48)) ++ self.assertEqual(page.dpi, 300) ++ self.assertEqual(page.rotation, 0) ++ self.assertEqual(page.version, 24) + file = page.file +- assert_equal(type(file), File) +- assert_equal(file.id, u('test1.djvu')) +- assert_equal(type(file.id), unicode) ++ self.assertEqual(type(file), File) ++ self.assertEqual(file.id, u('test1.djvu')) ++ self.assertEqual(type(file.id), unicode) + dump = document.files[0].dump +- assert_equal(type(dump), unicode) +- assert_equal( ++ self.assertEqual(type(dump), unicode) ++ self.assertEqual( + [line for line in dump.splitlines()], [ + u(' FORM:DJVU [83] '), + u(' INFO [10] DjVu 64x48, v24, 300 dpi, gamma=2.2'), + u(' Sjbz [53] JB2 bilevel data'), + ] + ) +- assert_is(document.get_message(wait=False), None) +- assert_is(context.get_message(wait=False), None) +- with assert_raises_str(IndexError, 'file number out of range'): ++ self.assertIs(document.get_message(wait=False), None) ++ self.assertIs(context.get_message(wait=False), None) ++ with self.assertRaisesRegex(IndexError, 'file number out of range'): + document.files[-1].get_info() +- assert_is(document.get_message(wait=False), None) +- assert_is(context.get_message(wait=False), None) +- with assert_raises_str(IndexError, 'page number out of range'): ++ self.assertIs(document.get_message(wait=False), None) ++ self.assertIs(context.get_message(wait=False), None) ++ with self.assertRaisesRegex(IndexError, 'page number out of range'): + document.pages[-1] +- with assert_raises_str(IndexError, 'page number out of range'): ++ with self.assertRaisesRegex(IndexError, 'page number out of range'): + document.pages[1] +- assert_is(document.get_message(wait=False), None) +- assert_is(context.get_message(wait=False), None) ++ self.assertIs(document.get_message(wait=False), None) ++ self.assertIs(context.get_message(wait=False), None) + + def test_save(self): + skip_unless_command_exists('djvudump') +@@ -324,28 +312,28 @@ class test_documents: + original_filename = images + 'test0.djvu' + document = context.new_document(FileUri(original_filename)) + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) +- assert_true(document.decoding_done) +- assert_false(document.decoding_error) +- assert_equal(document.decoding_status, JobOK) +- assert_equal(document.type, DOCUMENT_TYPE_BUNDLED) +- assert_equal(len(document.pages), 2) +- assert_equal(len(document.files), 3) +- (stdout0, stderr0) = run('djvudump', original_filename, LC_ALL='C') +- assert_equal(stderr0, b'') ++ self.assertEqual(type(message), DocInfoMessage) ++ self.assertTrue(document.decoding_done) ++ self.assertFalse(document.decoding_error) ++ self.assertEqual(document.decoding_status, JobOK) ++ self.assertEqual(document.type, DOCUMENT_TYPE_BUNDLED) ++ self.assertEqual(len(document.pages), 2) ++ self.assertEqual(len(document.files), 3) ++ (stdout0, stderr0) = self.run_cmd('djvudump', original_filename, LC_ALL='C') ++ self.assertEqual(stderr0, b'') + stdout0 = stdout0.replace(b'\r\n', b'\n') + tmpdir = tempfile.mkdtemp() + try: + tmp = open(os.path.join(tmpdir, 'tmp.djvu'), 'wb') + job = document.save(tmp) +- assert_equal(type(job), SaveJob) +- assert_true(job.is_done) +- assert_false(job.is_error) ++ self.assertEqual(type(job), SaveJob) ++ self.assertTrue(job.is_done) ++ self.assertFalse(job.is_error) + tmp.close() +- (stdout, stderr) = run('djvudump', tmp.name, LC_ALL='C') +- assert_equal(stderr, b'') ++ (stdout, stderr) = self.run_cmd('djvudump', tmp.name, LC_ALL='C') ++ self.assertEqual(stderr, b'') + stdout = stdout.replace(b'\r\n', b'\n') +- assert_equal(stdout, stdout0) ++ self.assertEqual(stdout, stdout0) + finally: + shutil.rmtree(tmpdir) + tmp = None +@@ -353,19 +341,19 @@ class test_documents: + try: + tmp = open(os.path.join(tmpdir, 'tmp.djvu'), 'wb') + job = document.save(tmp, pages=(0,)) +- assert_equal(type(job), SaveJob) +- assert_true(job.is_done) +- assert_false(job.is_error) ++ self.assertEqual(type(job), SaveJob) ++ self.assertTrue(job.is_done) ++ self.assertFalse(job.is_error) + tmp.close() +- stdout, stderr = run('djvudump', tmp.name, LC_ALL='C') +- assert_equal(stderr, b'') ++ stdout, stderr = self.run_cmd('djvudump', tmp.name, LC_ALL='C') ++ self.assertEqual(stderr, b'') + stdout = stdout.replace(b'\r\n', b'\n') + stdout0 = stdout0.split(b'\n') + stdout = stdout.split(b'\n') + stdout[4] = stdout[4].replace(b' (1)', b'') +- assert_equal(len(stdout), 10) +- assert_equal(stdout[3:-1], stdout0[4:10]) +- assert_equal(stdout[-1], b'') ++ self.assertEqual(len(stdout), 10) ++ self.assertEqual(stdout[3:-1], stdout0[4:10]) ++ self.assertEqual(stdout[-1], b'') + finally: + shutil.rmtree(tmpdir) + tmp = None +@@ -373,37 +361,37 @@ class test_documents: + try: + tmpfname = os.path.join(tmpdir, 'index.djvu') + job = document.save(indirect=tmpfname) +- assert_equal(type(job), SaveJob) +- assert_true(job.is_done) +- assert_false(job.is_error) +- (stdout, stderr) = run('djvudump', tmpfname, LC_ALL='C') +- assert_equal(stderr, b'') ++ self.assertEqual(type(job), SaveJob) ++ self.assertTrue(job.is_done) ++ self.assertFalse(job.is_error) ++ (stdout, stderr) = self.run_cmd('djvudump', tmpfname, LC_ALL='C') ++ self.assertEqual(stderr, b'') + stdout = stdout.replace(b'\r\n', b'\n') + stdout = stdout.split(b'\n') + stdout0 = ( + [b' shared_anno.iff -> shared_anno.iff'] + + [b(' p{n:04}.djvu -> p{n:04}.djvu'.format(n=n)) for n in range(1, 3)] + ) +- assert_equal(len(stdout), 7) +- assert_equal(stdout[2:-2], stdout0) +- assert_equal(stdout[-1], b'') ++ self.assertEqual(len(stdout), 7) ++ self.assertEqual(stdout[2:-2], stdout0) ++ self.assertEqual(stdout[-1], b'') + finally: + shutil.rmtree(tmpdir) + tmpdir = tempfile.mkdtemp() + try: + tmpfname = os.path.join(tmpdir, 'index.djvu') + job = document.save(indirect=tmpfname, pages=(0,)) +- assert_equal(type(job), SaveJob) +- assert_true(job.is_done) +- assert_false(job.is_error) +- (stdout, stderr) = run('djvudump', tmpfname, LC_ALL='C') ++ self.assertEqual(type(job), SaveJob) ++ self.assertTrue(job.is_done) ++ self.assertFalse(job.is_error) ++ (stdout, stderr) = self.run_cmd('djvudump', tmpfname, LC_ALL='C') + stdout = stdout.replace(b'\r\n', b'\n') +- assert_equal(stderr, b'') ++ self.assertEqual(stderr, b'') + stdout = stdout.split(b'\n') +- assert_equal(len(stdout), 5) +- assert_equal(stdout[2], b' shared_anno.iff -> shared_anno.iff') +- assert_equal(stdout[3], b' p0001.djvu -> p0001.djvu') +- assert_equal(stdout[-1], b'') ++ self.assertEqual(len(stdout), 5) ++ self.assertEqual(stdout[2], b' shared_anno.iff -> shared_anno.iff') ++ self.assertEqual(stdout[3], b' p0001.djvu -> p0001.djvu') ++ self.assertEqual(stdout[-1], b'') + finally: + shutil.rmtree(tmpdir) + +@@ -412,63 +400,63 @@ class test_documents: + context = Context() + document = context.new_document(FileUri(images + 'test0.djvu')) + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) +- assert_true(document.decoding_done) +- assert_false(document.decoding_error) +- assert_equal(document.decoding_status, JobOK) +- assert_equal(document.type, DOCUMENT_TYPE_BUNDLED) +- assert_equal(len(document.pages), 2) +- assert_equal(len(document.files), 3) ++ self.assertEqual(type(message), DocInfoMessage) ++ self.assertTrue(document.decoding_done) ++ self.assertFalse(document.decoding_error) ++ self.assertEqual(document.decoding_status, JobOK) ++ self.assertEqual(document.type, DOCUMENT_TYPE_BUNDLED) ++ self.assertEqual(len(document.pages), 2) ++ self.assertEqual(len(document.files), 3) + with tempfile.NamedTemporaryFile() as tmp: + job = document.export_ps(tmp.file) +- assert_equal(type(job), SaveJob) +- assert_true(job.is_done) +- assert_false(job.is_error) +- stdout, stderr = run('ps2ascii', tmp.name, LC_ALL='C') +- assert_equal(stderr, b'') ++ self.assertEqual(type(job), SaveJob) ++ self.assertTrue(job.is_done) ++ self.assertFalse(job.is_error) ++ stdout, stderr = self.run_cmd('ps2ascii', tmp.name, LC_ALL='C') ++ self.assertEqual(stderr, b'') + stdout = re.sub(br'[\x00\s]+', b' ', stdout) +- assert_equal(stdout, b' ') ++ self.assertEqual(stdout, b' ') + with tempfile.NamedTemporaryFile() as tmp: + job = document.export_ps(tmp.file, pages=(0,), text=True) +- assert_equal(type(job), SaveJob) +- assert_true(job.is_done) +- assert_false(job.is_error) +- stdout, stderr = run('ps2ascii', tmp.name, LC_ALL='C') +- assert_equal(stderr, b'') ++ self.assertEqual(type(job), SaveJob) ++ self.assertTrue(job.is_done) ++ self.assertFalse(job.is_error) ++ stdout, stderr = self.run_cmd('ps2ascii', tmp.name, LC_ALL='C') ++ self.assertEqual(stderr, b'') + stdout = stdout.decode('ASCII') + stdout = re.sub(r'[\x00\s]+', ' ', stdout) + stdout = ' '.join(stdout.split()[:3]) + expected = '1 Lorem ipsum' +- assert_multi_line_equal(stdout, expected) ++ self.assertEqual(stdout, expected) + +-class test_pixel_formats(): ++class test_pixel_formats(TestBase): + + def test_bad_new(self): +- with assert_raises_str(TypeError, "cannot create 'djvu.decode.PixelFormat' instances"): ++ with self.assertRaisesRegex(TypeError, r"cannot create 'djvu.decode.PixelFormat' instances"): + PixelFormat() + + def test_rgb(self): + pf = PixelFormatRgb() +- assert_repr(pf, "djvu.decode.PixelFormatRgb(byte_order = 'RGB', bpp = 24)") ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatRgb(byte_order = 'RGB', bpp = 24)") + pf = PixelFormatRgb('RGB') +- assert_repr(pf, "djvu.decode.PixelFormatRgb(byte_order = 'RGB', bpp = 24)") ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatRgb(byte_order = 'RGB', bpp = 24)") + pf = PixelFormatRgb('BGR') +- assert_repr(pf, "djvu.decode.PixelFormatRgb(byte_order = 'BGR', bpp = 24)") ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatRgb(byte_order = 'BGR', bpp = 24)") + + def test_rgb_mask(self): + pf = PixelFormatRgbMask(0xFF, 0xF00, 0x1F000, 0, 16) +- assert_repr(pf, "djvu.decode.PixelFormatRgbMask(red_mask = 0x00ff, green_mask = 0x0f00, blue_mask = 0xf000, xor_value = 0x0000, bpp = 16)") ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatRgbMask(red_mask = 0x00ff, green_mask = 0x0f00, blue_mask = 0xf000, xor_value = 0x0000, bpp = 16)") + pf = PixelFormatRgbMask(0xFF000000, 0xFF0000, 0xFF00, 0xFF, 32) +- assert_repr(pf, "djvu.decode.PixelFormatRgbMask(red_mask = 0xff000000, green_mask = 0x00ff0000, blue_mask = 0x0000ff00, xor_value = 0x000000ff, bpp = 32)") ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatRgbMask(red_mask = 0xff000000, green_mask = 0x00ff0000, blue_mask = 0x0000ff00, xor_value = 0x000000ff, bpp = 32)") + + def test_grey(self): + pf = PixelFormatGrey() +- assert_repr(pf, "djvu.decode.PixelFormatGrey(bpp = 8)") ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatGrey(bpp = 8)") + + def test_palette(self): +- with assert_raises(KeyError) as ecm: ++ with self.assertRaises(KeyError) as ecm: + pf = PixelFormatPalette({}) +- assert_equal( ++ self.assertEqual( + ecm.exception.args, + ((0, 0, 0),) + ) +@@ -477,272 +465,271 @@ class test_pixel_formats(): + data_repr = ', '.join( + '{k!r}: 0x{v:02x}'.format(k=k, v=v) for k, v in sorted(data.items()) + ) +- assert_equal( ++ self.assertEqual( + repr(pf), + 'djvu.decode.PixelFormatPalette({{{data}}}, bpp = 8)'.format(data=data_repr) + ) + + def test_packed_bits(self): + pf = PixelFormatPackedBits('<') +- assert_repr(pf, "djvu.decode.PixelFormatPackedBits('<')") +- assert_equal(pf.bpp, 1) ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatPackedBits('<')") ++ self.assertEqual(pf.bpp, 1) + pf = PixelFormatPackedBits('>') +- assert_repr(pf, "djvu.decode.PixelFormatPackedBits('>')") +- assert_equal(pf.bpp, 1) ++ self.assertEqual(repr(pf), "djvu.decode.PixelFormatPackedBits('>')") ++ self.assertEqual(pf.bpp, 1) + +-class test_page_jobs(): ++class test_page_jobs(TestBase): + + def test_bad_new(self): +- with assert_raises_str(TypeError, "cannot create 'djvu.decode.PageJob' instances"): ++ with self.assertRaisesRegex(TypeError, r"cannot create 'djvu.decode.PageJob' instances"): + PageJob() + + def test_decode(self): + context = Context() + document = context.new_document(FileUri(images + 'test1.djvu')) + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) ++ self.assertEqual(type(message), DocInfoMessage) + page_job = document.pages[0].decode() +- assert_true(page_job.is_done) +- assert_equal(type(page_job), PageJob) +- assert_true(page_job.is_done) +- assert_false(page_job.is_error) +- assert_equal(page_job.status, JobOK) +- assert_equal(page_job.width, 64) +- assert_equal(page_job.height, 48) +- assert_equal(page_job.size, (64, 48)) +- assert_equal(page_job.dpi, 300) +- assert_equal(page_job.gamma, 2.2) +- assert_equal(page_job.version, 24) +- assert_equal(page_job.type, PAGE_TYPE_BITONAL) +- assert_equal((page_job.rotation, page_job.initial_rotation), (0, 0)) +- with assert_raises_str(ValueError, 'rotation must be equal to 0, 90, 180, or 270'): ++ self.assertTrue(page_job.is_done) ++ self.assertEqual(type(page_job), PageJob) ++ self.assertTrue(page_job.is_done) ++ self.assertFalse(page_job.is_error) ++ self.assertEqual(page_job.status, JobOK) ++ self.assertEqual(page_job.width, 64) ++ self.assertEqual(page_job.height, 48) ++ self.assertEqual(page_job.size, (64, 48)) ++ self.assertEqual(page_job.dpi, 300) ++ self.assertEqual(page_job.gamma, 2.2) ++ self.assertEqual(page_job.version, 24) ++ self.assertEqual(page_job.type, PAGE_TYPE_BITONAL) ++ self.assertEqual((page_job.rotation, page_job.initial_rotation), (0, 0)) ++ with self.assertRaisesRegex(ValueError, 'rotation must be equal to 0, 90, 180, or 270'): + page_job.rotation = 100 + page_job.rotation = 180 +- assert_equal((page_job.rotation, page_job.initial_rotation), (180, 0)) ++ self.assertEqual((page_job.rotation, page_job.initial_rotation), (180, 0)) + del page_job.rotation +- assert_equal((page_job.rotation, page_job.initial_rotation), (0, 0)) ++ self.assertEqual((page_job.rotation, page_job.initial_rotation), (0, 0)) + +- with assert_raises_str(ValueError, 'page_rect width/height must be a positive integer'): ++ with self.assertRaisesRegex(ValueError, 'page_rect width/height must be a positive integer'): + page_job.render(RENDER_COLOR, (0, 0, -1, -1), (0, 0, 10, 10), PixelFormatRgb()) + +- with assert_raises_str(ValueError, 'render_rect width/height must be a positive integer'): ++ with self.assertRaisesRegex(ValueError, 'render_rect width/height must be a positive integer'): + page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, -1, -1), PixelFormatRgb()) + +- with assert_raises_str(ValueError, 'render_rect must be inside page_rect'): ++ with self.assertRaisesRegex(ValueError, 'render_rect must be inside page_rect'): + page_job.render(RENDER_COLOR, (0, 0, 10, 10), (2, 2, 10, 10), PixelFormatRgb()) + +- with assert_raises_str(ValueError, 'row_alignment must be a positive integer'): ++ with self.assertRaisesRegex(ValueError, 'row_alignment must be a positive integer'): + page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 10, 10), PixelFormatRgb(), -1) + +- with assert_raises_regex(MemoryError, r'\AUnable to allocate [0-9]+ bytes for an image memory\Z'): ++ with self.assertRaisesRegex(MemoryError, r'\AUnable to allocate [0-9]+ bytes for an image memory\Z'): + x = int((sys.maxsize // 2) ** 0.5) + page_job.render(RENDER_COLOR, (0, 0, x, x), (0, 0, x, x), PixelFormatRgb(), 8) + + s = page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1) +- assert_equal(s, b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEF\xFF\xFF\xFF\xA4\xFF\xFF\xFF\xB8') ++ self.assertEqual(s, b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEF\xFF\xFF\xFF\xA4\xFF\xFF\xFF\xB8') + + buffer = array.array('B', b'\0') +- with assert_raises_str(ValueError, 'Image buffer is too small (16 > 1)'): ++ with self.assertRaisesRegex(ValueError, r'Image buffer is too small \(16 > 1\)'): + page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1, buffer) + + buffer = array.array('B', b'\0' * 16) +- assert_is(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1, buffer), buffer) +- s = array_tobytes(buffer) +- assert_equal(s, b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEF\xFF\xFF\xFF\xA4\xFF\xFF\xFF\xB8') ++ self.assertIs(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 4, 4), PixelFormatGrey(), 1, buffer), buffer) ++ s = buffer.tobytes() ++ self.assertEqual(s, b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xEF\xFF\xFF\xFF\xA4\xFF\xFF\xFF\xB8') + + buffer = array.array('I', [0] * 4) + pixel_format = PixelFormatRgbMask(0xFF0000, 0xFF00, 0xFF, bpp=32) +- assert_is(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 2, 2), pixel_format, 1, buffer), buffer) +- s = array_tobytes(buffer) +- assert_equal(s, b'\xFF\xFF\xFF\x00' * 4) ++ self.assertIs(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 2, 2), pixel_format, 1, buffer), buffer) ++ s = buffer.tobytes() ++ self.assertEqual(s, b'\xFF\xFF\xFF\x00' * 4) + + if sys.version_info >= (3, 3): + buffer = bytearray(16) + memview = memoryview(buffer).cast('I', shape=(2, 2)) +- assert_is(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 2, 2), pixel_format, 1, memview), memview) ++ self.assertIs(page_job.render(RENDER_COLOR, (0, 0, 10, 10), (0, 0, 2, 2), pixel_format, 1, memview), memview) + s = bytes(buffer) +- assert_equal(s, b'\xFF\xFF\xFF\x00' * 4) ++ self.assertEqual(s, b'\xFF\xFF\xFF\x00' * 4) + +-class test_thumbnails: ++class test_thumbnails(TestBase): + + def test(self): + context = Context() + document = context.new_document(FileUri(images + 'test1.djvu')) + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) ++ self.assertEqual(type(message), DocInfoMessage) + thumbnail = document.pages[0].thumbnail +- assert_equal(thumbnail.status, JobOK) +- assert_equal(thumbnail.calculate(), JobOK) ++ self.assertEqual(thumbnail.status, JobOK) ++ self.assertEqual(thumbnail.calculate(), JobOK) + message = document.get_message() +- assert_equal(type(message), ThumbnailMessage) +- assert_equal(message.thumbnail.page.n, 0) ++ self.assertEqual(type(message), ThumbnailMessage) ++ self.assertEqual(message.thumbnail.page.n, 0) + (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey(), dry_run=True) +- assert_equal((w, h, r), (5, 3, 5)) +- assert_is(pixels, None) ++ self.assertEqual((w, h, r), (5, 3, 5)) ++ self.assertIs(pixels, None) + (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey()) +- assert_equal((w, h, r), (5, 3, 5)) +- assert_equal(pixels[:15], b'\xFF\xEB\xA7\xF2\xFF\xFF\xBF\x86\xBE\xFF\xFF\xE7\xD6\xE7\xFF') ++ self.assertEqual((w, h, r), (5, 3, 5)) ++ self.assertEqual(pixels[:15], b'\xFF\xEB\xA7\xF2\xFF\xFF\xBF\x86\xBE\xFF\xFF\xE7\xD6\xE7\xFF') + buffer = array.array('B', b'\0') +- with assert_raises_str(ValueError, 'Image buffer is too small (25 > 1)'): ++ with self.assertRaisesRegex(ValueError, r'Image buffer is too small \(25 > 1\)'): + (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey(), buffer=buffer) + buffer = array.array('B', b'\0' * 25) + (w, h, r), pixels = thumbnail.render((5, 5), PixelFormatGrey(), buffer=buffer) +- assert_is(pixels, buffer) +- s = array_tobytes(buffer[:15]) +- assert_equal(s, b'\xFF\xEB\xA7\xF2\xFF\xFF\xBF\x86\xBE\xFF\xFF\xE7\xD6\xE7\xFF') ++ self.assertIs(pixels, buffer) ++ s = buffer[:15].tobytes() ++ self.assertEqual(s, b'\xFF\xEB\xA7\xF2\xFF\xFF\xBF\x86\xBE\xFF\xFF\xE7\xD6\xE7\xFF') + +-def test_jobs(): ++ def test_jobs(self): ++ with self.assertRaisesRegex(TypeError, "cannot create 'djvu.decode.Job' instances"): ++ Job() ++ with self.assertRaisesRegex(TypeError, "cannot create 'djvu.decode.DocumentDecodingJob' instances"): ++ DocumentDecodingJob() + +- with assert_raises_str(TypeError, "cannot create 'djvu.decode.Job' instances"): +- Job() +- +- with assert_raises_str(TypeError, "cannot create 'djvu.decode.DocumentDecodingJob' instances"): +- DocumentDecodingJob() +- +-class test_affine_transforms(): ++class test_affine_transforms(TestBase): + + def test_bad_args(self): +- with assert_raises_str(ValueError, 'need more than 2 values to unpack'): ++ with self.assertRaisesRegex(ValueError, 'need more than 2 values to unpack'): + AffineTransform((1, 2), (3, 4, 5)) + + def test1(self): + af = AffineTransform((0, 0, 10, 10), (17, 42, 42, 100)) +- assert_equal(type(af), AffineTransform) +- assert_equal(af((0, 0)), (17, 42)) +- assert_equal(af((0, 10)), (17, 142)) +- assert_equal(af((10, 0)), (59, 42)) +- assert_equal(af((10, 10)), (59, 142)) +- assert_equal(af((0, 0, 10, 10)), (17, 42, 42, 100)) +- assert_equal(af(x for x in (0, 0, 10, 10)), (17, 42, 42, 100)) +- assert_equal(af.apply((123, 456)), af((123, 456))) +- assert_equal(af.apply((12, 34, 56, 78)), af((12, 34, 56, 78))) +- assert_equal(af.inverse((17, 42)), (0, 0)) +- assert_equal(af.inverse((17, 142)), (0, 10)) +- assert_equal(af.inverse((59, 42)), (10, 0)) +- assert_equal(af.inverse((59, 142)), (10, 10)) +- assert_equal(af.inverse((17, 42, 42, 100)), (0, 0, 10, 10)) +- assert_equal(af.inverse(x for x in (17, 42, 42, 100)), (0, 0, 10, 10)) +- assert_equal(af.inverse(af((234, 567))), (234, 567)) +- assert_equal(af.inverse(af((23, 45, 67, 78))), (23, 45, 67, 78)) ++ self.assertEqual(type(af), AffineTransform) ++ self.assertEqual(af((0, 0)), (17, 42)) ++ self.assertEqual(af((0, 10)), (17, 142)) ++ self.assertEqual(af((10, 0)), (59, 42)) ++ self.assertEqual(af((10, 10)), (59, 142)) ++ self.assertEqual(af((0, 0, 10, 10)), (17, 42, 42, 100)) ++ self.assertEqual(af(x for x in (0, 0, 10, 10)), (17, 42, 42, 100)) ++ self.assertEqual(af.apply((123, 456)), af((123, 456))) ++ self.assertEqual(af.apply((12, 34, 56, 78)), af((12, 34, 56, 78))) ++ self.assertEqual(af.inverse((17, 42)), (0, 0)) ++ self.assertEqual(af.inverse((17, 142)), (0, 10)) ++ self.assertEqual(af.inverse((59, 42)), (10, 0)) ++ self.assertEqual(af.inverse((59, 142)), (10, 10)) ++ self.assertEqual(af.inverse((17, 42, 42, 100)), (0, 0, 10, 10)) ++ self.assertEqual(af.inverse(x for x in (17, 42, 42, 100)), (0, 0, 10, 10)) ++ self.assertEqual(af.inverse(af((234, 567))), (234, 567)) ++ self.assertEqual(af.inverse(af((23, 45, 67, 78))), (23, 45, 67, 78)) + +-class test_messages(): ++class test_messages(TestBase): + + def test_bad_new(self): +- with assert_raises_str(TypeError, "cannot create 'djvu.decode.Message' instances"): ++ with self.assertRaisesRegex(TypeError, "cannot create 'djvu.decode.Message' instances"): + Message() + +-class test_streams: ++class test_streams(TestBase): + + def test_bad_new(self): +- with assert_raises_str(TypeError, "Argument 'document' has incorrect type (expected djvu.decode.Document, got NoneType)"): +- Stream(None, 42) ++ with self.assertRaisesRegex( ++ TypeError, ++ r"Argument 'document' has incorrect type \(expected djvu.decode.Document, got NoneType\)"): ++ Stream(None, 42) + + def test(self): + context = Context() + document = context.new_document('dummy://dummy.djvu') + message = document.get_message() +- assert_equal(type(message), NewStreamMessage) +- assert_equal(message.name, 'dummy.djvu') +- assert_equal(message.uri, 'dummy://dummy.djvu') +- assert_equal(type(message.stream), Stream) +- with assert_raises(NotAvailable): ++ self.assertEqual(type(message), NewStreamMessage) ++ self.assertEqual(message.name, 'dummy.djvu') ++ self.assertEqual(message.uri, 'dummy://dummy.djvu') ++ self.assertEqual(type(message.stream), Stream) ++ with self.assertRaises(NotAvailable): + document.outline.sexpr +- with assert_raises(NotAvailable): ++ with self.assertRaises(NotAvailable): + document.annotations.sexpr +- with assert_raises(NotAvailable): ++ with self.assertRaises(NotAvailable): + document.pages[0].text.sexpr +- with assert_raises(NotAvailable): ++ with self.assertRaises(NotAvailable): + document.pages[0].annotations.sexpr + try: + with open(images + 'test1.djvu', 'rb') as fp: + message.stream.write(fp.read()) + finally: + message.stream.close() +- with assert_raises_str(IOError, 'I/O operation on closed file'): ++ with self.assertRaisesRegex(IOError, 'I/O operation on closed file'): + message.stream.write(b'eggs') + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) ++ self.assertEqual(type(message), DocInfoMessage) + outline = document.outline + outline.wait() + x = outline.sexpr +- assert_equal(x, Expression([])) ++ self.assertEqual(x, Expression([])) + anno = document.annotations + anno.wait() + x = anno.sexpr +- assert_equal(x, Expression([])) ++ self.assertEqual(x, Expression([])) + text = document.pages[0].text + text.wait() + x = text.sexpr +- assert_equal(x, Expression([])) ++ self.assertEqual(x, Expression([])) + anno = document.pages[0].annotations + anno.wait() + x = anno.sexpr +- assert_equal(x, Expression([])) ++ self.assertEqual(x, Expression([])) + +-def test_metadata(): +- +- model_metadata = { +- 'English': 'eggs', +- u('Русский'): u('яйца'), +- } +- meta = '\n'.join(u('|{k}| {v}').format(k=k, v=v) for k, v in model_metadata.items()) +- test_script = u('set-meta\n{meta}\n.\n').format(meta=meta) +- try: +- test_file = create_djvu(test_script) +- except UnicodeEncodeError: +- raise SkipTest('you need to run this test with LC_CTYPE=C or LC_CTYPE=.UTF-8') +- try: +- context = Context() +- document = context.new_document(FileUri(test_file.name)) +- message = document.get_message() +- assert_equal(type(message), DocInfoMessage) +- annotations = document.annotations +- assert_equal(type(annotations), DocumentAnnotations) +- annotations.wait() +- metadata = annotations.metadata +- assert_equal(type(metadata), Metadata) +- assert_equal(len(metadata), len(model_metadata)) +- assert_equal(sorted(metadata), sorted(model_metadata)) +- if not py3k: +- assert_equal(sorted(metadata.iterkeys()), sorted(model_metadata.iterkeys())) +- assert_equal(sorted(metadata.keys()), sorted(model_metadata.keys())) +- if not py3k: +- assert_equal(sorted(metadata.itervalues()), sorted(model_metadata.itervalues())) +- assert_equal(sorted(metadata.values()), sorted(model_metadata.values())) +- if not py3k: +- assert_equal(sorted(metadata.iteritems()), sorted(model_metadata.iteritems())) +- assert_equal(sorted(metadata.items()), sorted(model_metadata.items())) +- for k in metadata: +- assert_equal(type(k), unicode) +- assert_equal(type(metadata[k]), unicode) +- for k in None, 42, '+'.join(model_metadata): +- with assert_raises(KeyError) as ecm: +- metadata[k] +- assert_equal(ecm.exception.args, (k,)) +- finally: +- test_file.close() +- +-class test_sexpr: ++class TestMetadata(TestBase): ++ def test_metadata(self): ++ model_metadata = { ++ 'English': 'eggs', ++ u('Русский'): u('яйца'), ++ } ++ meta = '\n'.join(u('|{k}| {v}').format(k=k, v=v) for k, v in model_metadata.items()) ++ test_script = u('set-meta\n{meta}\n.\n').format(meta=meta) ++ try: ++ test_file = self.create_djvu(test_script) ++ except UnicodeEncodeError: ++ raise unittest.SkipTest('you need to run this test with LC_CTYPE=C or LC_CTYPE=.UTF-8') ++ try: ++ context = Context() ++ document = context.new_document(FileUri(test_file.name)) ++ message = document.get_message() ++ self.assertEqual(type(message), DocInfoMessage) ++ annotations = document.annotations ++ self.assertEqual(type(annotations), DocumentAnnotations) ++ annotations.wait() ++ metadata = annotations.metadata ++ self.assertEqual(type(metadata), Metadata) ++ self.assertEqual(len(metadata), len(model_metadata)) ++ self.assertEqual(sorted(metadata), sorted(model_metadata)) ++ if not py3k: ++ self.assertEqual(sorted(metadata.iterkeys()), sorted(model_metadata.iterkeys())) ++ self.assertEqual(sorted(metadata.keys()), sorted(model_metadata.keys())) ++ if not py3k: ++ self.assertEqual(sorted(metadata.itervalues()), sorted(model_metadata.itervalues())) ++ self.assertEqual(sorted(metadata.values()), sorted(model_metadata.values())) ++ if not py3k: ++ self.assertEqual(sorted(metadata.iteritems()), sorted(model_metadata.iteritems())) ++ self.assertEqual(sorted(metadata.items()), sorted(model_metadata.items())) ++ for k in metadata: ++ self.assertEqual(type(k), unicode) ++ self.assertEqual(type(metadata[k]), unicode) ++ for k in None, 42, '+'.join(model_metadata): ++ with self.assertRaises(KeyError) as ecm: ++ metadata[k] ++ self.assertEqual(ecm.exception.args, (k,)) ++ finally: ++ test_file.close() + ++class test_sexpr(TestBase): + def test(self): + context = Context() + document = context.new_document(FileUri(images + 'test0.djvu')) +- assert_equal(type(document), Document) ++ self.assertEqual(type(document), Document) + message = document.get_message() +- assert_equal(type(message), DocInfoMessage) ++ self.assertEqual(type(message), DocInfoMessage) + anno = DocumentAnnotations(document, shared=False) +- assert_equal(type(anno), DocumentAnnotations) ++ self.assertEqual(type(anno), DocumentAnnotations) + anno.wait() + x = anno.sexpr +- assert_equal(x, Expression([])) ++ self.assertEqual(x, Expression([])) + anno = document.annotations +- assert_equal(type(anno), DocumentAnnotations) ++ self.assertEqual(type(anno), DocumentAnnotations) + anno.wait() +- assert_is(anno.background_color, None) +- assert_is(anno.horizontal_align, None) +- assert_is(anno.vertical_align, None) +- assert_is(anno.mode, None) +- assert_is(anno.zoom, None) ++ self.assertIs(anno.background_color, None) ++ self.assertIs(anno.horizontal_align, None) ++ self.assertIs(anno.vertical_align, None) ++ self.assertIs(anno.mode, None) ++ self.assertIs(anno.zoom, None) + expected_metadata = [ + Symbol('metadata'), + [Symbol('ModDate'), '2015-08-17 19:54:57+02:00'], +@@ -766,20 +753,20 @@ class test_sexpr: + '' + '\n' + ] +- assert_equal( ++ self.assertEqual( + anno.sexpr, + Expression([expected_metadata, expected_xmp]) + ) + metadata = anno.metadata +- assert_equal(type(metadata), Metadata) ++ self.assertEqual(type(metadata), Metadata) + hyperlinks = anno.hyperlinks +- assert_equal(type(hyperlinks), Hyperlinks) +- assert_equal(len(hyperlinks), 0) +- assert_equal(list(hyperlinks), []) ++ self.assertEqual(type(hyperlinks), Hyperlinks) ++ self.assertEqual(len(hyperlinks), 0) ++ self.assertEqual(list(hyperlinks), []) + outline = document.outline +- assert_equal(type(outline), DocumentOutline) ++ self.assertEqual(type(outline), DocumentOutline) + outline.wait() +- assert_equal(outline.sexpr, Expression( ++ self.assertEqual(outline.sexpr, Expression( + [Symbol('bookmarks'), + ['Lorem ipsum', '#p0001.djvu'], + ['Hyperlinks', '#p0002.djvu', +@@ -790,41 +777,41 @@ class test_sexpr: + )) + page = document.pages[1] + anno = page.annotations +- assert_equal(type(anno), PageAnnotations) ++ self.assertEqual(type(anno), PageAnnotations) + anno.wait() +- assert_is(anno.background_color, None) +- assert_is(anno.horizontal_align, None) +- assert_is(anno.vertical_align, None) +- assert_is(anno.mode, None) +- assert_is(anno.zoom, None) ++ self.assertIs(anno.background_color, None) ++ self.assertIs(anno.horizontal_align, None) ++ self.assertIs(anno.vertical_align, None) ++ self.assertIs(anno.mode, None) ++ self.assertIs(anno.zoom, None) + expected_hyperlinks = [ + [Symbol('maparea'), '#p0001.djvu', '', [Symbol('rect'), 520, 2502, 33, 42], [Symbol('border'), Symbol('#ff0000')]], + [Symbol('maparea'), 'http://jwilk.net/', '', [Symbol('rect'), 458, 2253, 516, 49], [Symbol('border'), Symbol('#00ffff')]] + ] +- assert_equal( ++ self.assertEqual( + anno.sexpr, + Expression([expected_metadata, expected_xmp] + expected_hyperlinks) + ) + page_metadata = anno.metadata +- assert_equal(type(page_metadata), Metadata) +- assert_equal(page_metadata.keys(), metadata.keys()) +- assert_equal([page_metadata[k] == metadata[k] for k in metadata], [True, True, True, True, True]) ++ self.assertEqual(type(page_metadata), Metadata) ++ self.assertEqual(page_metadata.keys(), metadata.keys()) ++ self.assertEqual([page_metadata[k] == metadata[k] for k in metadata], [True, True, True, True, True]) + hyperlinks = anno.hyperlinks +- assert_equal(type(hyperlinks), Hyperlinks) +- assert_equal(len(hyperlinks), 2) +- assert_equal( ++ self.assertEqual(type(hyperlinks), Hyperlinks) ++ self.assertEqual(len(hyperlinks), 2) ++ self.assertEqual( + list(hyperlinks), + [Expression(h) for h in expected_hyperlinks] + ) + text = page.text +- assert_equal(type(text), PageText) ++ self.assertEqual(type(text), PageText) + text.wait() + text_s = text.sexpr + text_s_detail = [PageText(page, details).sexpr for details in (TEXT_DETAILS_PAGE, TEXT_DETAILS_COLUMN, TEXT_DETAILS_REGION, TEXT_DETAILS_PARAGRAPH, TEXT_DETAILS_LINE, TEXT_DETAILS_WORD, TEXT_DETAILS_CHARACTER, TEXT_DETAILS_ALL)] +- assert_equal(text_s_detail[0], text_s_detail[1]) +- assert_equal(text_s_detail[1], text_s_detail[2]) +- assert_equal(text_s_detail[2], text_s_detail[3]) +- assert_equal( ++ self.assertEqual(text_s_detail[0], text_s_detail[1]) ++ self.assertEqual(text_s_detail[1], text_s_detail[2]) ++ self.assertEqual(text_s_detail[2], text_s_detail[3]) ++ self.assertEqual( + text_s_detail[0], + Expression( + [Symbol('page'), 0, 0, 2550, 3300, +@@ -836,7 +823,7 @@ class test_sexpr: + ] + ) + ) +- assert_equal( ++ self.assertEqual( + text_s_detail[4], + Expression( + [Symbol('page'), 0, 0, 2550, 3300, +@@ -849,10 +836,10 @@ class test_sexpr: + ] + ) + ) +- assert_equal(text_s_detail[5], text_s) +- assert_equal(text_s_detail[6], text_s) +- assert_equal(text_s_detail[7], text_s) +- assert_equal( ++ self.assertEqual(text_s_detail[5], text_s) ++ self.assertEqual(text_s_detail[6], text_s) ++ self.assertEqual(text_s_detail[7], text_s) ++ self.assertEqual( + text_s, + Expression( + [Symbol('page'), 0, 0, 2550, 3300, +@@ -865,106 +852,108 @@ class test_sexpr: + ] + ) + ) +- with assert_raises_str(TypeError, 'details must be a symbol or none'): ++ with self.assertRaisesRegex(TypeError, 'details must be a symbol or none'): + PageText(page, 'eggs') +- with assert_raises_str(ValueError, 'details must be equal to TEXT_DETAILS_PAGE, or TEXT_DETAILS_COLUMN, or TEXT_DETAILS_REGION, or TEXT_DETAILS_PARAGRAPH, or TEXT_DETAILS_LINE, or TEXT_DETAILS_WORD, or TEXT_DETAILS_CHARACTER or TEXT_DETAILS_ALL'): ++ with self.assertRaisesRegex(ValueError, 'details must be equal to TEXT_DETAILS_PAGE, or TEXT_DETAILS_COLUMN, or TEXT_DETAILS_REGION, or TEXT_DETAILS_PARAGRAPH, or TEXT_DETAILS_LINE, or TEXT_DETAILS_WORD, or TEXT_DETAILS_CHARACTER or TEXT_DETAILS_ALL'): + PageText(page, Symbol('eggs')) + +-def test_version(): +- assert_is_instance(__version__, str) +- assert_equal(__version__, get_changelog_version()) +- assert_is_instance(DDJVU_VERSION, int) ++class TestGeneralSettings(TestBase): ++ def test_version(self): ++ self.assertIsInstance(__version__, str) ++ self.assertEqual(__version__, get_changelog_version()) ++ self.assertIsInstance(DDJVU_VERSION, int) + +-def test_wildcard_import(): +- ns = wildcard_import('djvu.decode') +- assert_list_equal( +- sorted(ns.keys()), [ +- 'AffineTransform', +- 'Annotations', +- 'ChunkMessage', +- 'Context', +- 'DDJVU_VERSION', +- 'DOCUMENT_TYPE_BUNDLED', +- 'DOCUMENT_TYPE_INDIRECT', +- 'DOCUMENT_TYPE_OLD_BUNDLED', +- 'DOCUMENT_TYPE_OLD_INDEXED', +- 'DOCUMENT_TYPE_SINGLE_PAGE', +- 'DOCUMENT_TYPE_UNKNOWN', +- 'DocInfoMessage', +- 'Document', +- 'DocumentAnnotations', +- 'DocumentDecodingJob', +- 'DocumentExtension', +- 'DocumentFiles', +- 'DocumentOutline', +- 'DocumentPages', +- 'ErrorMessage', +- 'FILE_TYPE_INCLUDE', +- 'FILE_TYPE_PAGE', +- 'FILE_TYPE_THUMBNAILS', +- 'File', +- 'FileURI', +- 'FileUri', +- 'Hyperlinks', +- 'InfoMessage', +- 'Job', +- 'JobDone', +- 'JobException', +- 'JobFailed', +- 'JobNotDone', +- 'JobNotStarted', +- 'JobOK', +- 'JobStarted', +- 'JobStopped', +- 'Message', +- 'Metadata', +- 'NewStreamMessage', +- 'NotAvailable', +- 'PAGE_TYPE_BITONAL', +- 'PAGE_TYPE_COMPOUND', +- 'PAGE_TYPE_PHOTO', +- 'PAGE_TYPE_UNKNOWN', +- 'PRINT_BOOKLET_NO', +- 'PRINT_BOOKLET_RECTO', +- 'PRINT_BOOKLET_VERSO', +- 'PRINT_BOOKLET_YES', +- 'PRINT_ORIENTATION_AUTO', +- 'PRINT_ORIENTATION_LANDSCAPE', +- 'PRINT_ORIENTATION_PORTRAIT', +- 'Page', +- 'PageAnnotations', +- 'PageInfoMessage', +- 'PageJob', +- 'PageText', +- 'PixelFormat', +- 'PixelFormatGrey', +- 'PixelFormatPackedBits', +- 'PixelFormatPalette', +- 'PixelFormatRgb', +- 'PixelFormatRgbMask', +- 'ProgressMessage', +- 'RENDER_BACKGROUND', +- 'RENDER_BLACK', +- 'RENDER_COLOR', +- 'RENDER_COLOR_ONLY', +- 'RENDER_FOREGROUND', +- 'RENDER_MASK_ONLY', +- 'RedisplayMessage', +- 'RelayoutMessage', +- 'SaveJob', +- 'Stream', +- 'TEXT_DETAILS_ALL', +- 'TEXT_DETAILS_CHARACTER', +- 'TEXT_DETAILS_COLUMN', +- 'TEXT_DETAILS_LINE', +- 'TEXT_DETAILS_PAGE', +- 'TEXT_DETAILS_PARAGRAPH', +- 'TEXT_DETAILS_REGION', +- 'TEXT_DETAILS_WORD', +- 'Thumbnail', +- 'ThumbnailMessage', +- 'cmp_text_zone' +- ] +- ) ++ def test_wildcard_import(self): ++ ns = {} ++ exec('from djvu.decode import *', {}, ns) ++ self.assertEqual( ++ sorted(ns.keys()), [ ++ 'AffineTransform', ++ 'Annotations', ++ 'ChunkMessage', ++ 'Context', ++ 'DDJVU_VERSION', ++ 'DOCUMENT_TYPE_BUNDLED', ++ 'DOCUMENT_TYPE_INDIRECT', ++ 'DOCUMENT_TYPE_OLD_BUNDLED', ++ 'DOCUMENT_TYPE_OLD_INDEXED', ++ 'DOCUMENT_TYPE_SINGLE_PAGE', ++ 'DOCUMENT_TYPE_UNKNOWN', ++ 'DocInfoMessage', ++ 'Document', ++ 'DocumentAnnotations', ++ 'DocumentDecodingJob', ++ 'DocumentExtension', ++ 'DocumentFiles', ++ 'DocumentOutline', ++ 'DocumentPages', ++ 'ErrorMessage', ++ 'FILE_TYPE_INCLUDE', ++ 'FILE_TYPE_PAGE', ++ 'FILE_TYPE_THUMBNAILS', ++ 'File', ++ 'FileURI', ++ 'FileUri', ++ 'Hyperlinks', ++ 'InfoMessage', ++ 'Job', ++ 'JobDone', ++ 'JobException', ++ 'JobFailed', ++ 'JobNotDone', ++ 'JobNotStarted', ++ 'JobOK', ++ 'JobStarted', ++ 'JobStopped', ++ 'Message', ++ 'Metadata', ++ 'NewStreamMessage', ++ 'NotAvailable', ++ 'PAGE_TYPE_BITONAL', ++ 'PAGE_TYPE_COMPOUND', ++ 'PAGE_TYPE_PHOTO', ++ 'PAGE_TYPE_UNKNOWN', ++ 'PRINT_BOOKLET_NO', ++ 'PRINT_BOOKLET_RECTO', ++ 'PRINT_BOOKLET_VERSO', ++ 'PRINT_BOOKLET_YES', ++ 'PRINT_ORIENTATION_AUTO', ++ 'PRINT_ORIENTATION_LANDSCAPE', ++ 'PRINT_ORIENTATION_PORTRAIT', ++ 'Page', ++ 'PageAnnotations', ++ 'PageInfoMessage', ++ 'PageJob', ++ 'PageText', ++ 'PixelFormat', ++ 'PixelFormatGrey', ++ 'PixelFormatPackedBits', ++ 'PixelFormatPalette', ++ 'PixelFormatRgb', ++ 'PixelFormatRgbMask', ++ 'ProgressMessage', ++ 'RENDER_BACKGROUND', ++ 'RENDER_BLACK', ++ 'RENDER_COLOR', ++ 'RENDER_COLOR_ONLY', ++ 'RENDER_FOREGROUND', ++ 'RENDER_MASK_ONLY', ++ 'RedisplayMessage', ++ 'RelayoutMessage', ++ 'SaveJob', ++ 'Stream', ++ 'TEXT_DETAILS_ALL', ++ 'TEXT_DETAILS_CHARACTER', ++ 'TEXT_DETAILS_COLUMN', ++ 'TEXT_DETAILS_LINE', ++ 'TEXT_DETAILS_PAGE', ++ 'TEXT_DETAILS_PARAGRAPH', ++ 'TEXT_DETAILS_REGION', ++ 'TEXT_DETAILS_WORD', ++ 'Thumbnail', ++ 'ThumbnailMessage', ++ 'cmp_text_zone' ++ ] ++ ) + + # vim:ts=4 sts=4 sw=4 et +diff --git a/tests/test_sexpr.py b/tests/test_sexpr.py +index 3e9631d..37725dc 100644 +--- a/tests/test_sexpr.py ++++ b/tests/test_sexpr.py +@@ -21,12 +21,15 @@ import os + import shutil + import sys + import tempfile ++import unittest + + if sys.version_info >= (3, 3): + import collections.abc as collections_abc + else: + import collections as collections_abc + ++from io import StringIO ++ + import pickle + try: + import cPickle as cpickle +@@ -42,23 +45,8 @@ from djvu.sexpr import ( + ) + + from tools import ( +- SkipTest, +- assert_equal, +- assert_false, +- assert_in, +- assert_is, +- assert_is_instance, +- assert_less, +- assert_list_equal, +- assert_not_equal, +- assert_not_in, +- assert_raises, +- assert_raises_str, +- assert_repr, + get_changelog_version, +- wildcard_import, + # Python 2/3 compat: +- StringIO, + b, + long, + py3k, +@@ -66,65 +54,66 @@ from tools import ( + unicode, + ) + +-def assert_pickle_equal(obj): +- for pickle_module in pickle, cpickle: +- if pickle_module is None: +- continue +- for protocol in range(pickle.HIGHEST_PROTOCOL + 1): +- pickled_obj = pickle_module.dumps(obj, protocol=protocol) +- repickled_obj = pickle_module.loads(pickled_obj) +- assert_equal(obj, repickled_obj) + +-class test_int_expressions(): ++class TestBase(unittest.TestCase): ++ def assert_pickle_equal(self, obj): ++ for pickle_module in pickle, cpickle: ++ if pickle_module is None: ++ continue ++ for protocol in range(pickle.HIGHEST_PROTOCOL + 1): ++ pickled_obj = pickle_module.dumps(obj, protocol=protocol) ++ repickled_obj = pickle_module.loads(pickled_obj) ++ self.assertEqual(obj, repickled_obj) + ++class test_int_expressions(TestBase): + def t(self, n, x=None): + if x is None: + x = Expression(n) +- assert_is(x, Expression(x)) ++ self.assertIs(x, Expression(x)) + # __repr__(): +- assert_repr(x, 'Expression({n})'.format(n=int(n))) ++ self.assertEqual(repr(x), 'Expression({n})'.format(n=int(n))) + # value: + v = x.value +- assert_equal(type(v), int) +- assert_equal(v, n) ++ self.assertEqual(type(v), int) ++ self.assertEqual(v, n) + # lvalue: + v = x.lvalue +- assert_equal(type(v), int) +- assert_equal(v, n) ++ self.assertEqual(type(v), int) ++ self.assertEqual(v, n) + # __int__(): + i = int(x) +- assert_equal(type(i), int) +- assert_equal(i, n) ++ self.assertEqual(type(i), int) ++ self.assertEqual(i, n) + # __long__(): + i = long(x) +- assert_equal(type(i), long) +- assert_equal(i, n) ++ self.assertEqual(type(i), long) ++ self.assertEqual(i, n) + # __float__(): + i = float(x) +- assert_equal(type(i), float) +- assert_equal(i, n) ++ self.assertEqual(type(i), float) ++ self.assertEqual(i, n) + # __str__(): + s = str(x) +- assert_equal(s, str(n)) ++ self.assertEqual(s, str(n)) + # __unicode__(): + s = unicode(x) +- assert_equal(s, str(n)) ++ self.assertEqual(s, str(n)) + # __eq__(), __ne__(): +- assert_equal(x, Expression(n)) +- assert_not_equal(x, n) +- assert_not_equal(x, Expression(n + 37)) ++ self.assertEqual(x, Expression(n)) ++ self.assertNotEqual(x, n) ++ self.assertNotEqual(x, Expression(n + 37)) + # __hash__(): +- assert_equal(hash(x), n) ++ self.assertEqual(hash(x), n) + # __bool__() / __nonzero__(): + obj = object() + if n: +- assert_is(x and obj, obj) +- assert_is(x or obj, x) ++ self.assertIs(x and obj, obj) ++ self.assertIs(x or obj, x) + else: +- assert_is(x and obj, x) +- assert_is(x or obj, obj) ++ self.assertIs(x and obj, x) ++ self.assertIs(x or obj, obj) + # pickle: +- assert_pickle_equal(x) ++ self.assert_pickle_equal(x) + + def test_int(self): + self.t(42) +@@ -145,24 +134,28 @@ class test_int_expressions(): + self.t(long(42)) + + def test_limits(self): +- assert_equal(Expression((1 << 29) - 1).value, (1 << 29) - 1) +- assert_equal(Expression(-1 << 29).value, -1 << 29) +- with assert_raises_str(ValueError, 'value not in range(-2 ** 29, 2 ** 29)'): +- Expression(1 << 29) +- with assert_raises_str(ValueError, 'value not in range(-2 ** 29, 2 ** 29)'): +- Expression((-1 << 29) - 1) ++ self.assertEqual(Expression((1 << 29) - 1).value, (1 << 29) - 1) ++ self.assertEqual(Expression(-1 << 29).value, -1 << 29) ++ with self.assertRaisesRegex( ++ ValueError, ++ r'value not in range\(-2 \*\* 29, 2 \*\* 29\)'): ++ Expression(1 << 29) ++ with self.assertRaisesRegex( ++ ValueError, ++ r'value not in range\(-2 \*\* 29, 2 \*\* 29\)'): ++ Expression((-1 << 29) - 1) + +-class test_float_expressions(): ++class test_float_expressions(TestBase): + + # TODO: float expressions are not implemented yet + + def test_parse(self): +- with assert_raises(ExpressionSyntaxError): ++ with self.assertRaises(ExpressionSyntaxError): + x = Expression.from_string('3.14') + if isinstance(x.value, Symbol): + raise ExpressionSyntaxError + +-class test_symbols(): ++class test_symbols(TestBase): + + def t(self, name, sname=None): + if sname is None: +@@ -172,15 +165,15 @@ class test_symbols(): + else: + [uname, bname] = [sname.decode('UTF-8'), sname] + symbol = Symbol(name) +- assert_equal(type(symbol), Symbol) +- assert_equal(symbol, Symbol(name)) +- assert_is(symbol, Symbol(name)) +- assert_equal(str(symbol), sname) +- assert_equal(unicode(symbol), uname) +- assert_not_equal(symbol, bname) +- assert_not_equal(symbol, uname) +- assert_equal(hash(symbol), hash(bname)) +- assert_pickle_equal(symbol) ++ self.assertEqual(type(symbol), Symbol) ++ self.assertEqual(symbol, Symbol(name)) ++ self.assertIs(symbol, Symbol(name)) ++ self.assertEqual(str(symbol), sname) ++ self.assertEqual(unicode(symbol), uname) ++ self.assertNotEqual(symbol, bname) ++ self.assertNotEqual(symbol, uname) ++ self.assertEqual(hash(symbol), hash(bname)) ++ self.assert_pickle_equal(symbol) + return symbol + + def test_ascii(self): +@@ -192,12 +185,12 @@ class test_symbols(): + assert x is y + + def test_inequality(self): +- assert_less( ++ self.assertLess( + Symbol('eggs'), + Symbol('ham'), + ) + +-class test_symbol_expressions(): ++class test_symbol_expressions(TestBase): + + def t(self, name, sname): + if sname is None: +@@ -208,34 +201,34 @@ class test_symbol_expressions(): + [uname, bname] = [sname.decode('UTF-8'), sname] + sym = Symbol(name) + x = Expression(sym) +- assert_is(x, Expression(x)) ++ self.assertIs(x, Expression(x)) + # __repr__(x) +- assert_repr(x, 'Expression({sym!r})'.format(sym=sym)) ++ self.assertEqual(repr(x), 'Expression({sym!r})'.format(sym=sym)) + # value: + v = x.value +- assert_equal(type(v), Symbol) +- assert_equal(v, sym) ++ self.assertEqual(type(v), Symbol) ++ self.assertEqual(v, sym) + # lvalue: + v = x.lvalue +- assert_equal(type(v), Symbol) +- assert_equal(v, sym) ++ self.assertEqual(type(v), Symbol) ++ self.assertEqual(v, sym) + # __str__(): +- assert_equal(str(x), sname) +- assert_repr(x, repr(Expression.from_string(sname))) ++ self.assertEqual(str(x), sname) ++ self.assertEqual(repr(x), repr(Expression.from_string(sname))) + # __unicode__(): +- assert_equal(unicode(x), uname) +- assert_repr(x, repr(Expression.from_string(uname))) ++ self.assertEqual(unicode(x), uname) ++ self.assertEqual(repr(x), repr(Expression.from_string(uname))) + # __eq__(), __ne__(): +- assert_equal(x, Expression(sym)) +- assert_not_equal(x, Expression(name)) +- assert_not_equal(x, sym) ++ self.assertEqual(x, Expression(sym)) ++ self.assertNotEqual(x, Expression(name)) ++ self.assertNotEqual(x, sym) + # __hash__(): +- assert_equal( ++ self.assertEqual( + hash(x), + hash(bname.strip(b'|')) + ) + # pickle: +- assert_pickle_equal(x) ++ self.assert_pickle_equal(x) + return x + + def test_ascii(self): +@@ -244,97 +237,97 @@ class test_symbol_expressions(): + def test_nonascii(self): + x = self.t(b('ветчина'), '|ветчина|') + y = self.t(u('ветчина'), '|ветчина|') +- assert_equal(x, y) +- assert_equal(hash(x), hash(y)) ++ self.assertEqual(x, y) ++ self.assertEqual(hash(x), hash(y)) + + def test_string_expressions(): + x = Expression('eggs') +- assert_repr(x, "Expression('eggs')") +- assert_is(x, Expression(x)) +- assert_equal(x.value, 'eggs') +- assert_equal(x.lvalue, 'eggs') +- assert_equal(str(x), '"eggs"') +- assert_repr(x, repr(Expression.from_string(str(x)))) +- assert_equal(x, Expression('eggs')) +- assert_not_equal(x, Expression(Symbol('eggs'))) +- assert_not_equal(x, 'eggs') +- assert_equal(hash(x), hash('eggs')) +- assert_pickle_equal(x) ++ self.assertEqual(repr(x), "Expression('eggs')") ++ self.assertIs(x, Expression(x)) ++ self.assertEqual(x.value, 'eggs') ++ self.assertEqual(x.lvalue, 'eggs') ++ self.assertEqual(str(x), '"eggs"') ++ self.assertEqual(repr(x), repr(Expression.from_string(str(x)))) ++ self.assertEqual(x, Expression('eggs')) ++ self.assertNotEqual(x, Expression(Symbol('eggs'))) ++ self.assertNotEqual(x, 'eggs') ++ self.assertEqual(hash(x), hash('eggs')) ++ self.assert_pickle_equal(x) + +-class test_unicode_expressions(): ++class test_unicode_expressions(TestBase): + + def test1(self): + x = Expression(u('eggs')) +- assert_repr(x, "Expression('eggs')") +- assert_is(x, Expression(x)) ++ self.assertEqual(repr(x), "Expression('eggs')") ++ self.assertIs(x, Expression(x)) + + def test2(self): + x = Expression(u('żółw')) + if py3k: +- assert_repr(x, "Expression('żółw')") ++ self.assertEqual(repr(x), "Expression('żółw')") + else: +- assert_repr(x, r"Expression('\xc5\xbc\xc3\xb3\xc5\x82w')") ++ self.assertEqual(repr(x), r"Expression('\xc5\xbc\xc3\xb3\xc5\x82w')") + +-class test_list_expressions(): ++class test_list_expressions(TestBase): + + def test1(self): + x = Expression(()) +- assert_repr(x, "Expression([])") ++ self.assertEqual(repr(x), "Expression([])") + y = Expression(x) +- assert_is(x, y) +- assert_equal(x.value, ()) +- assert_equal(x.lvalue, []) +- assert_equal(len(x), 0) +- assert_equal(bool(x), False) +- assert_equal(list(x), []) ++ self.assertIs(x, y) ++ self.assertEqual(x.value, ()) ++ self.assertEqual(x.lvalue, []) ++ self.assertEqual(len(x), 0) ++ self.assertEqual(bool(x), False) ++ self.assertEqual(list(x), []) + + def test2(self): + x = Expression([[1, 2], 3, [4, 5, Symbol('baz')], ['quux']]) +- assert_repr(x, "Expression([[1, 2], 3, [4, 5, Symbol('baz')], ['quux']])") ++ self.assertEqual(repr(x), "Expression([[1, 2], 3, [4, 5, Symbol('baz')], ['quux']])") + y = Expression(x) +- assert_repr(y, repr(x)) +- assert_false(x is y) +- assert_equal(x.value, ((1, 2), 3, (4, 5, Symbol('baz')), ('quux',))) +- assert_equal(x.lvalue, [[1, 2], 3, [4, 5, Symbol('baz')], ['quux']]) +- assert_equal(str(x), '((1 2) 3 (4 5 baz) ("quux"))') +- assert_repr(x, repr(Expression.from_string(str(x)))) +- assert_equal(len(x), 4) +- assert_equal(bool(x), True) +- assert_equal(tuple(x), (Expression((1, 2)), Expression(3), Expression((4, 5, Symbol('baz'))), Expression(('quux',)))) +- with assert_raises_str(TypeError, 'key must be an integer or a slice'): ++ self.assertEqual(repr(y), repr(x)) ++ self.assertIsNot(x, y) ++ self.assertEqual(x.value, ((1, 2), 3, (4, 5, Symbol('baz')), ('quux',))) ++ self.assertEqual(x.lvalue, [[1, 2], 3, [4, 5, Symbol('baz')], ['quux']]) ++ self.assertEqual(str(x), '((1 2) 3 (4 5 baz) ("quux"))') ++ self.assertEqual(repr(x), repr(Expression.from_string(str(x)))) ++ self.assertEqual(len(x), 4) ++ self.assertEqual(bool(x), True) ++ self.assertEqual(tuple(x), (Expression((1, 2)), Expression(3), Expression((4, 5, Symbol('baz'))), Expression(('quux',)))) ++ with self.assertRaisesRegex(TypeError, 'key must be an integer or a slice'): + x[object()] +- assert_equal(x[1], Expression(3)) +- assert_equal(x[-1][0], Expression('quux')) +- with assert_raises_str(IndexError, 'list index of out range'): ++ self.assertEqual(x[1], Expression(3)) ++ self.assertEqual(x[-1][0], Expression('quux')) ++ with self.assertRaisesRegex(IndexError, 'list index of out range'): + x[6] +- with assert_raises_str(IndexError, 'list index of out range'): ++ with self.assertRaisesRegex(IndexError, 'list index of out range'): + x[-6] +- assert_equal(x[:].value, x.value) +- assert_equal(x[:].lvalue, x.lvalue) +- assert_repr(x[1:], "Expression([3, [4, 5, Symbol('baz')], ['quux']])") +- assert_repr(x[-2:], "Expression([[4, 5, Symbol('baz')], ['quux']])") ++ self.assertEqual(x[:].value, x.value) ++ self.assertEqual(x[:].lvalue, x.lvalue) ++ self.assertEqual(repr(x[1:]), "Expression([3, [4, 5, Symbol('baz')], ['quux']])") ++ self.assertEqual(repr(x[-2:]), "Expression([[4, 5, Symbol('baz')], ['quux']])") + x[-2:] = 4, 5, 6 +- assert_repr(x, 'Expression([[1, 2], 3, 4, 5, 6])') ++ self.assertEqual(repr(x), 'Expression([[1, 2], 3, 4, 5, 6])') + x[0] = 2 +- assert_repr(x, 'Expression([2, 3, 4, 5, 6])') ++ self.assertEqual(repr(x), 'Expression([2, 3, 4, 5, 6])') + x[:] = (1, 3, 5) +- assert_repr(x, 'Expression([1, 3, 5])') ++ self.assertEqual(repr(x), 'Expression([1, 3, 5])') + x[3:] = 7, +- assert_repr(x, 'Expression([1, 3, 5, 7])') +- with assert_raises_str(NotImplementedError, 'only [n:] slices are supported'): ++ self.assertEqual(repr(x), 'Expression([1, 3, 5, 7])') ++ with self.assertRaisesRegex(NotImplementedError, r'only \[n:\] slices are supported'): + x[object():] +- with assert_raises_str(NotImplementedError, 'only [n:] slices are supported'): ++ with self.assertRaisesRegex(NotImplementedError, r'only \[n:\] slices are supported'): + x[:2] +- with assert_raises_str(NotImplementedError, 'only [n:] slices are supported'): ++ with self.assertRaisesRegex(NotImplementedError, r'only \[n:\] slices are supported'): + x[object():] = [] +- with assert_raises_str(NotImplementedError, 'only [n:] slices are supported'): ++ with self.assertRaisesRegex(NotImplementedError, r'only \[n:\] slices are supported'): + x[:2] = [] +- with assert_raises_str(TypeError, 'can only assign a list expression'): ++ with self.assertRaisesRegex(TypeError, 'can only assign a list expression'): + x[:] = 0 +- assert_equal(x, Expression((1, 3, 5, 7))) +- assert_not_equal(x, Expression((2, 4, 6))) +- assert_not_equal(x, (1, 3, 5, 7)) +- with assert_raises_str(TypeError, "unhashable type: 'ListExpression'"): ++ self.assertEqual(x, Expression((1, 3, 5, 7))) ++ self.assertNotEqual(x, Expression((2, 4, 6))) ++ self.assertNotEqual(x, (1, 3, 5, 7)) ++ with self.assertRaisesRegex(TypeError, "unhashable type: 'ListExpression'"): + hash(x) + + def test_insert(self): +@@ -342,16 +335,16 @@ class test_list_expressions(): + expr = Expression(()) + for pos in [-8, 4, 6, -5, -7, 5, 7, 2, -3, 8, 10, -2, 1, -9, -10, -4, -6, 0, 9, 3, -1]: + lst.insert(pos, pos) +- assert_is(expr.insert(pos, pos), None) +- assert_equal(expr, Expression(lst)) +- assert_equal(expr.lvalue, lst) ++ self.assertIs(expr.insert(pos, pos), None) ++ self.assertEqual(expr, Expression(lst)) ++ self.assertEqual(expr.lvalue, lst) + + def test_append(self): + expr = Expression(()) + for i in range(10): +- assert_is(expr.append(i), None) +- assert_equal(expr, Expression(range(i + 1))) +- assert_equal(expr.lvalue, list(range(i + 1))) ++ self.assertIs(expr.append(i), None) ++ self.assertEqual(expr, Expression(range(i + 1))) ++ self.assertEqual(expr.lvalue, list(range(i + 1))) + + def test_extend(self): + lst = [] +@@ -359,9 +352,9 @@ class test_list_expressions(): + for ext in [1], [], [2, 3]: + lst.extend(ext) + expr.extend(ext) +- assert_equal(expr, Expression(lst)) +- assert_equal(expr.lvalue, lst) +- with assert_raises_str(TypeError, "'int' object is not iterable"): ++ self.assertEqual(expr, Expression(lst)) ++ self.assertEqual(expr.lvalue, lst) ++ with self.assertRaisesRegex(TypeError, r"'int' object is not iterable"): + expr.extend(0) + + def test_inplace_add(self): +@@ -370,96 +363,96 @@ class test_list_expressions(): + for ext in [], [1], [], [2, 3]: + lst += ext + expr += ext +- assert_equal(expr, Expression(lst)) +- assert_equal(expr.lvalue, lst) +- assert_is(expr, expr0) +- with assert_raises_str(TypeError, "'int' object is not iterable"): ++ self.assertEqual(expr, Expression(lst)) ++ self.assertEqual(expr.lvalue, lst) ++ self.assertIs(expr, expr0) ++ with self.assertRaisesRegex(TypeError, r"'int' object is not iterable"): + expr += 0 + + def test_pop(self): + expr = Expression([0, 1, 2, 3, 4, 5, 6]) +- assert_equal(expr.pop(0), Expression(0)) +- assert_equal(expr, Expression([1, 2, 3, 4, 5, 6])) +- with assert_raises_str(IndexError, 'pop index of out range'): ++ self.assertEqual(expr.pop(0), Expression(0)) ++ self.assertEqual(expr, Expression([1, 2, 3, 4, 5, 6])) ++ with self.assertRaisesRegex(IndexError, 'pop index of out range'): + expr.pop(6) +- assert_equal(expr.pop(5), Expression(6)) +- assert_equal(expr, Expression([1, 2, 3, 4, 5])) +- assert_equal(expr.pop(-1), Expression(5)) +- assert_equal(expr, Expression([1, 2, 3, 4])) +- assert_equal(expr.pop(-2), Expression(3)) +- assert_equal(expr, Expression([1, 2, 4])) +- assert_equal(expr.pop(1), Expression(2)) +- assert_equal(expr, Expression([1, 4])) ++ self.assertEqual(expr.pop(5), Expression(6)) ++ self.assertEqual(expr, Expression([1, 2, 3, 4, 5])) ++ self.assertEqual(expr.pop(-1), Expression(5)) ++ self.assertEqual(expr, Expression([1, 2, 3, 4])) ++ self.assertEqual(expr.pop(-2), Expression(3)) ++ self.assertEqual(expr, Expression([1, 2, 4])) ++ self.assertEqual(expr.pop(1), Expression(2)) ++ self.assertEqual(expr, Expression([1, 4])) + expr.pop() + expr.pop() +- with assert_raises_str(IndexError, 'pop from empty list'): ++ with self.assertRaisesRegex(IndexError, 'pop from empty list'): + expr.pop() + for i in range(-2, 3): +- with assert_raises_str(IndexError, 'pop from empty list'): ++ with self.assertRaisesRegex(IndexError, 'pop from empty list'): + expr.pop(i) + + def test_delitem(self): + expr = Expression([0, 1, 2, 3, 4, 5, 6]) + del expr[0] +- assert_equal(expr, Expression([1, 2, 3, 4, 5, 6])) +- with assert_raises_str(IndexError, 'pop index of out range'): ++ self.assertEqual(expr, Expression([1, 2, 3, 4, 5, 6])) ++ with self.assertRaisesRegex(IndexError, 'pop index of out range'): + expr.pop(6) + del expr[5] +- assert_equal(expr, Expression([1, 2, 3, 4, 5])) ++ self.assertEqual(expr, Expression([1, 2, 3, 4, 5])) + del expr[-1] +- assert_equal(expr, Expression([1, 2, 3, 4])) ++ self.assertEqual(expr, Expression([1, 2, 3, 4])) + del expr[-2] +- assert_equal(expr, Expression([1, 2, 4])) ++ self.assertEqual(expr, Expression([1, 2, 4])) + del expr[1] +- assert_equal(expr, Expression([1, 4])) ++ self.assertEqual(expr, Expression([1, 4])) + del expr[1:] +- assert_equal(expr, Expression([1])) ++ self.assertEqual(expr, Expression([1])) + del expr[:] +- assert_equal(expr, Expression([])) ++ self.assertEqual(expr, Expression([])) + for i in range(-2, 3): +- with assert_raises_str(IndexError, 'pop from empty list'): ++ with self.assertRaisesRegex(IndexError, 'pop from empty list'): + del expr[i] + + def test_remove(self): + expr = Expression([0, 1, 2, 3, 4, 5, 6]) + expr.remove(Expression(0)) +- assert_equal(expr, Expression([1, 2, 3, 4, 5, 6])) +- with assert_raises_str(IndexError, 'item not in list'): ++ self.assertEqual(expr, Expression([1, 2, 3, 4, 5, 6])) ++ with self.assertRaisesRegex(IndexError, 'item not in list'): + expr.remove(Expression(0)) + expr.remove(Expression(6)) +- assert_equal(expr, Expression([1, 2, 3, 4, 5])) ++ self.assertEqual(expr, Expression([1, 2, 3, 4, 5])) + expr.remove(Expression(5)) +- assert_equal(expr, Expression([1, 2, 3, 4])) ++ self.assertEqual(expr, Expression([1, 2, 3, 4])) + expr.remove(Expression(3)) +- assert_equal(expr, Expression([1, 2, 4])) ++ self.assertEqual(expr, Expression([1, 2, 4])) + expr.remove(Expression(2)) +- assert_equal(expr, Expression([1, 4])) ++ self.assertEqual(expr, Expression([1, 4])) + expr.remove(Expression(4)) + expr.remove(Expression(1)) +- with assert_raises_str(IndexError, 'item not in list'): ++ with self.assertRaisesRegex(IndexError, 'item not in list'): + expr.remove(Expression(-1)) + + def test_contains(self): + expr = Expression(()) +- assert_not_in(Expression(42), expr) ++ self.assertNotIn(Expression(42), expr) + lst = (1, 2, 3) + expr = Expression(lst) + for x in lst: +- assert_not_in(x, expr) +- assert_in(Expression(x), expr) +- assert_not_in(Expression(max(lst) + 1), expr) ++ self.assertNotIn(x, expr) ++ self.assertIn(Expression(x), expr) ++ self.assertNotIn(Expression(max(lst) + 1), expr) + + def test_index(self): + expr = Expression(()) +- with assert_raises_str(ValueError, 'value not in list'): ++ with self.assertRaisesRegex(ValueError, 'value not in list'): + expr.index(Expression(42)) + lst = [1, 2, 3] + expr = Expression(lst) + for x in lst: + i = lst.index(x) + j = expr.index(Expression(x)) +- assert_equal(i, j) +- with assert_raises_str(ValueError, 'value not in list'): ++ self.assertEqual(i, j) ++ with self.assertRaisesRegex(ValueError, 'value not in list'): + expr.index(Expression(max(lst) + 1)) + + def test_count(self): +@@ -468,25 +461,25 @@ class test_list_expressions(): + for x in lst + [max(lst) + 1]: + i = lst.count(x) + j = expr.count(Expression(x)) +- assert_equal(i, j) ++ self.assertEqual(i, j) + + def test_reverse(self): + for lst in (), (1, 2, 3): + expr = Expression(lst) +- assert_equal( ++ self.assertEqual( + Expression(reversed(expr)), + Expression(reversed(lst)) + ) +- assert_equal( ++ self.assertEqual( + Expression(reversed(expr)).value, + tuple(reversed(lst)) + ) +- assert_is(expr.reverse(), None) +- assert_equal( ++ self.assertIs(expr.reverse(), None) ++ self.assertEqual( + expr, + Expression(reversed(lst)) + ) +- assert_equal( ++ self.assertEqual( + expr.value, + tuple(reversed(lst)) + ) +@@ -495,67 +488,67 @@ class test_list_expressions(): + x = Expression([1, [2], 3]) + y = Expression(x) + x[1][0] = 0 +- assert_repr(x, 'Expression([1, [0], 3])') +- assert_repr(y, 'Expression([1, [0], 3])') ++ self.assertEqual(repr(x), 'Expression([1, [0], 3])') ++ self.assertEqual(repr(y), 'Expression([1, [0], 3])') + x[1] = 0 +- assert_repr(x, 'Expression([1, 0, 3])') +- assert_repr(y, 'Expression([1, [0], 3])') ++ self.assertEqual(repr(x), 'Expression([1, 0, 3])') ++ self.assertEqual(repr(y), 'Expression([1, [0], 3])') + + def test_copy2(self): + x = Expression([1, [2], 3]) + y = copy.copy(x) + x[1][0] = 0 +- assert_repr(x, 'Expression([1, [0], 3])') +- assert_repr(y, 'Expression([1, [0], 3])') ++ self.assertEqual(repr(x), 'Expression([1, [0], 3])') ++ self.assertEqual(repr(y), 'Expression([1, [0], 3])') + x[1] = 0 +- assert_repr(x, 'Expression([1, 0, 3])') +- assert_repr(y, 'Expression([1, [0], 3])') ++ self.assertEqual(repr(x), 'Expression([1, 0, 3])') ++ self.assertEqual(repr(y), 'Expression([1, [0], 3])') + + def test_copy3(self): + x = Expression([1, [2], 3]) + y = copy.deepcopy(x) + x[1][0] = 0 +- assert_repr(x, 'Expression([1, [0], 3])') +- assert_repr(y, 'Expression([1, [2], 3])') ++ self.assertEqual(repr(x), 'Expression([1, [0], 3])') ++ self.assertEqual(repr(y), 'Expression([1, [2], 3])') + x[1] = 0 +- assert_repr(x, 'Expression([1, 0, 3])') +- assert_repr(y, 'Expression([1, [2], 3])') ++ self.assertEqual(repr(x), 'Expression([1, 0, 3])') ++ self.assertEqual(repr(y), 'Expression([1, [2], 3])') + + def test_abc(self): + x = Expression(()) +- assert_is_instance(x, collections_abc.MutableSequence) +- assert_is_instance(iter(x), collections_abc.Iterator) ++ self.assertIsInstance(x, collections_abc.MutableSequence) ++ self.assertIsInstance(iter(x), collections_abc.Iterator) + + def test_pickle(self): + for lst in (), (1, 2, 3), (1, (2, 3)): + x = Expression(lst) +- assert_pickle_equal(x) ++ self.assert_pickle_equal(x) + +-class test_expression_parser(): ++class test_expression_parser(TestBase): + + def test_badstring(self): +- with assert_raises(ExpressionSyntaxError): ++ with self.assertRaises(ExpressionSyntaxError): + Expression.from_string('(1') + + def test_attr_from_file(self): +- assert_is(getattr(Expression, 'from_file', None), None) ++ self.assertIs(getattr(Expression, 'from_file', None), None) + + def test_bad_io(self): +- with assert_raises_str(AttributeError, "'int' object has no attribute 'read'"): ++ with self.assertRaisesRegex(AttributeError, "'int' object has no attribute 'read'"): + Expression.from_stream(42) + + def test_bad_file_io(self): + if os.name == 'nt': +- raise SkipTest('not implemented on Windows') ++ raise unittest.SkipTest('not implemented on Windows') + path = '/proc/self/mem' + try: + os.stat(path) + except OSError as exc: +- raise SkipTest('{exc.filename}: {exc.strerror}'.format(exc=exc)) ++ raise unittest.SkipTest('{exc.filename}: {exc.strerror}'.format(exc=exc)) + with open('/proc/self/mem') as fp: +- with assert_raises(IOError) as ecm: ++ with self.assertRaises(IOError) as ecm: + Expression.from_stream(fp) +- assert_in( ++ self.assertIn( + ecm.exception.errno, + (errno.EIO, errno.EFAULT) + ) +@@ -563,10 +556,10 @@ class test_expression_parser(): + if py3k: + def test_bad_unicode_io(self): + fp = StringIO(chr(0xD800)) +- with assert_raises(UnicodeEncodeError): ++ with self.assertRaises(UnicodeEncodeError): + Expression.from_stream(fp) + +-class test_expression_parser_ascii(): ++class test_expression_parser_ascii(TestBase): + + expr = '(eggs) (ham)' + repr = ["Expression([Symbol('eggs')])", "Expression([Symbol('ham')])"] +@@ -575,10 +568,10 @@ class test_expression_parser_ascii(): + def read(): + return Expression.from_stream(fp) + x = read() +- assert_repr(x, self.repr[0]) ++ self.assertEqual(repr(x), self.repr[0]) + x = read() +- assert_repr(x, self.repr[1]) +- with assert_raises(ExpressionSyntaxError): ++ self.assertEqual(repr(x), self.repr[1]) ++ with self.assertRaises(ExpressionSyntaxError): + x = read() + + def test_stringio(self): +@@ -625,11 +618,11 @@ class test_expression_parser_nonascii(test_expression_parser_ascii): + if not py3k: + repr = [s.decode('ISO-8859-1').encode('ASCII', 'backslashreplace') for s in repr] + +-class test_expression_writer(): ++class test_expression_writer(TestBase): + + def test_bad_io(self): + expr = Expression(23) +- with assert_raises_str(AttributeError, "'int' object has no attribute 'write'"): ++ with self.assertRaisesRegex(AttributeError, "'int' object has no attribute 'write'"): + expr.print_into(42) + + def test_bad_file_io(self): +@@ -638,11 +631,11 @@ class test_expression_writer(): + try: + os.stat(path) + except OSError as exc: +- raise SkipTest('{exc.filename}: {exc.strerror}'.format(exc=exc)) ++ raise unittest.SkipTest('{exc.filename}: {exc.strerror}'.format(exc=exc)) + fp = open(path, 'w', buffering=2) + expr = Expression(23) + try: +- with assert_raises(IOError) as ecm: ++ with self.assertRaises(IOError) as ecm: + for i in range(10000): + expr.print_into(fp) + finally: +@@ -651,11 +644,11 @@ class test_expression_writer(): + except IOError: + if ecm is None: + raise +- assert_equal(ecm.exception.errno, errno.ENOSPC) ++ self.assertEqual(ecm.exception.errno, errno.ENOSPC) + + def test_reentrant(self): + if not _ExpressionIO._reentrant: +- raise SkipTest('this test requires DjVuLibre >= 3.5.26') ++ raise unittest.SkipTest('this test requires DjVuLibre >= 3.5.26') + class File(object): + def write(self, s): + expr.as_string() +@@ -670,7 +663,7 @@ class test_expression_writer(): + expr.print_into(fp, escape_unicode=v) + expr.as_string(escape_unicode=v) + +-class test_expression_writer_ascii(): ++class test_expression_writer_ascii(TestBase): + + expr = Expression([Symbol('eggs'), Symbol('ham')]) + repr = urepr = '(eggs ham)' +@@ -678,28 +671,28 @@ class test_expression_writer_ascii(): + def test_stringio_7(self): + fp = StringIO() + self.expr.print_into(fp) +- assert_equal(fp.getvalue(), self.repr) ++ self.assertEqual(fp.getvalue(), self.repr) + + def test_stringio_8(self): + fp = StringIO() + self.expr.print_into(fp, escape_unicode=False) +- assert_equal(fp.getvalue(), self.urepr) ++ self.assertEqual(fp.getvalue(), self.urepr) + + def test_bytesio_7(self): + fp = io.BytesIO() + self.expr.print_into(fp) +- assert_equal(fp.getvalue(), b(self.repr)) ++ self.assertEqual(fp.getvalue(), b(self.repr)) + + def test_bytesio_8(self): + fp = io.BytesIO() + self.expr.print_into(fp, escape_unicode=False) +- assert_equal(fp.getvalue(), b(self.urepr)) ++ self.assertEqual(fp.getvalue(), b(self.urepr)) + + def test_file_io_text_7(self): + with tempfile.TemporaryFile(mode='w+t') as fp: + self.expr.print_into(fp) + fp.seek(0) +- assert_equal(fp.read(), self.repr) ++ self.assertEqual(fp.read(), self.repr) + + def test_file_io_text_8(self): + if py3k: +@@ -709,7 +702,7 @@ class test_expression_writer_ascii(): + with fp: + self.expr.print_into(fp, escape_unicode=False) + fp.seek(0) +- assert_equal(fp.read(), self.urepr) ++ self.assertEqual(fp.read(), self.urepr) + + def test_codecs_io_text_7(self): + tmpdir = tempfile.mkdtemp() +@@ -718,7 +711,7 @@ class test_expression_writer_ascii(): + with codecs.open(path, mode='w+', encoding='UTF-16-LE') as fp: + self.expr.print_into(fp) + fp.seek(0) +- assert_equal(fp.read(), self.repr) ++ self.assertEqual(fp.read(), self.repr) + finally: + shutil.rmtree(tmpdir) + +@@ -729,7 +722,7 @@ class test_expression_writer_ascii(): + with codecs.open(path, mode='w+', encoding='UTF-16-LE') as fp: + self.expr.print_into(fp, escape_unicode=False) + fp.seek(0) +- assert_equal(fp.read(), u(self.urepr)) ++ self.assertEqual(fp.read(), u(self.urepr)) + finally: + shutil.rmtree(tmpdir) + +@@ -737,45 +730,48 @@ class test_expression_writer_ascii(): + with tempfile.TemporaryFile(mode='w+b') as fp: + self.expr.print_into(fp) + fp.seek(0) +- assert_equal(fp.read(), b(self.repr)) ++ self.assertEqual(fp.read(), b(self.repr)) + + def test_file_io_binary_8(self): + with tempfile.TemporaryFile(mode='w+b') as fp: + self.expr.print_into(fp, escape_unicode=False) + fp.seek(0) +- assert_equal(fp.read(), b(self.urepr)) ++ self.assertEqual(fp.read(), b(self.urepr)) + + def test_as_string_7(self): + s = self.expr.as_string() +- assert_equal(s, self.repr) ++ self.assertEqual(s, self.repr) + + def test_as_string_8(self): + s = self.expr.as_string(escape_unicode=False) +- assert_equal(s, self.urepr) ++ self.assertEqual(s, self.urepr) + + class test_expression_writer_nonascii(test_expression_writer_ascii): ++ def test_expression_nonascii(self): ++ expr = Expression(u('żółw')) ++ repr = r'"\305\274\303\263\305\202w"' ++ urepr = r'"żółw"' + +- expr = Expression(u('żółw')) +- repr = r'"\305\274\303\263\305\202w"' +- urepr = r'"żółw"' ++class TestGeneralSettings(TestBase): + +-def test_version(): +- assert_is_instance(__version__, str) +- assert_equal(__version__, get_changelog_version()) ++ def test_version(self): ++ self.assertIsInstance(__version__, str) ++ self.assertEqual(__version__, get_changelog_version()) + +-def test_wildcard_import(): +- ns = wildcard_import('djvu.sexpr') +- assert_list_equal( +- sorted(ns.keys()), [ +- 'Expression', +- 'ExpressionSyntaxError', +- 'IntExpression', +- 'InvalidExpression', +- 'ListExpression', +- 'StringExpression', +- 'Symbol', +- 'SymbolExpression' +- ] +- ) ++ def test_wildcard_import(self): ++ ns = {} ++ exec('from djvu.sexpr import *', {}, ns) ++ self.assertEqual( ++ sorted(ns.keys()), [ ++ 'Expression', ++ 'ExpressionSyntaxError', ++ 'IntExpression', ++ 'InvalidExpression', ++ 'ListExpression', ++ 'StringExpression', ++ 'Symbol', ++ 'SymbolExpression' ++ ] ++ ) + + # vim:ts=4 sts=4 sw=4 et +diff --git a/tests/tools.py b/tests/tools.py +index f7591ad..d71675b 100644 +--- a/tests/tools.py ++++ b/tests/tools.py +@@ -22,16 +22,7 @@ import os + import re + import sys + +-from nose import SkipTest +- +-import nose.tools +- +-from nose.tools import ( +- assert_true, +- assert_false, +- assert_equal, +- assert_not_equal, +-) ++from unittest import SkipTest + + def get_changelog_version(): + here = os.path.dirname(__file__) +@@ -40,108 +31,6 @@ def get_changelog_version(): + line = file.readline() + return line.split()[1].strip('()') + +-def noseimport(vmaj, vmin, name=None): +- def wrapper(f): +- if f.__module__ == 'unittest.case': +- return f +- if sys.version_info >= (vmaj, vmin): +- return getattr(nose.tools, name or f.__name__) +- return f +- return wrapper +- +-@noseimport(2, 7) +-def assert_in(x, y): +- assert_true( +- x in y, +- msg='{0!r} not found in {1!r}'.format(x, y) +- ) +- +-@noseimport(2, 7) +-def assert_is(x, y): +- assert_true( +- x is y, +- msg='{0!r} is not {1!r}'.format(x, y) +- ) +- +-@noseimport(2, 7) +-def assert_is_instance(obj, cls): +- assert_true( +- isinstance(obj, cls), +- msg='{0!r} is not an instance of {1!r}'.format(obj, cls) +- ) +- +-@noseimport(2, 7) +-def assert_less(x, y): +- assert_true( +- x < y, +- msg='{0!r} not less than {1!r}'.format(x, y) +- ) +- +-@noseimport(2, 7) +-def assert_list_equal(x, y): +- assert_is_instance(x, list) +- assert_is_instance(y, list) +- return assert_equal(x, y) +- +-@noseimport(2, 7) +-def assert_multi_line_equal(x, y): +- return assert_equal(x, y) +-if sys.version_info >= (2, 7): +- type(assert_multi_line_equal.__self__).maxDiff = None +- +-@noseimport(2, 7) +-def assert_not_in(x, y): +- assert_true( +- x not in y, +- msg='{0!r} unexpectedly found in {1!r}'.format(x, y) +- ) +- +-@noseimport(2, 7) +-class assert_raises(object): +- def __init__(self, exc_type): +- self._exc_type = exc_type +- self.exception = None +- def __enter__(self): +- return self +- def __exit__(self, exc_type, exc_value, tb): +- if exc_type is None: +- assert_true(False, '{0} not raised'.format(self._exc_type.__name__)) +- if not issubclass(exc_type, self._exc_type): +- return False +- if isinstance(exc_value, exc_type): +- pass +- # This branch is not always taken in Python 2.6: +- # https://bugs.python.org/issue7853 +- elif isinstance(exc_value, tuple): +- exc_value = exc_type(*exc_value) +- else: +- exc_value = exc_type(exc_value) +- self.exception = exc_value +- return True +- +-@noseimport(2, 7, 'assert_raises_regexp') +-@noseimport(3, 2) +-@contextlib.contextmanager +-def assert_raises_regex(exc_type, regex): +- with assert_raises(exc_type) as ecm: +- yield +- assert_regex(str(ecm.exception), regex) +- +-@noseimport(2, 7, 'assert_regexp_matches') +-@noseimport(3, 2) +-def assert_regex(text, regex): +- if isinstance(regex, (bytes, str, unicode)): +- regex = re.compile(regex) +- if not regex.search(text): +- message = "Regex didn't match: {0!r} not found in {1!r}".format(regex.pattern, text) +- assert_true(False, msg=message) +- +-@contextlib.contextmanager +-def assert_raises_str(exc_type, s): +- with assert_raises(exc_type) as ecm: +- yield +- assert_equal(str(ecm.exception), s) +- + try: + locale.LC_MESSAGES + except AttributeError: +@@ -247,32 +136,13 @@ def wildcard_import(mod): + + __all__ = [ + # Python 2/3 compat: +- 'StringIO', + 'b', + 'cmp', + 'long', + 'py3k', + 'u', + 'unicode', +- # nose +- 'SkipTest', +- 'assert_equal', +- 'assert_false', +- 'assert_in', +- 'assert_is', +- 'assert_is_instance', +- 'assert_less', +- 'assert_list_equal', +- 'assert_multi_line_equal', +- 'assert_not_equal', +- 'assert_not_in', +- 'assert_raises', +- 'assert_raises_regex', +- 'assert_regex', +- 'assert_true', + # misc +- 'assert_raises_str', +- 'assert_repr', + 'get_changelog_version', + 'interim', + 'interim_locale', +@@ -280,7 +150,6 @@ __all__ = [ + 'skip_unless_c_messages', + 'skip_unless_command_exists', + 'skip_unless_translation_exists', +- 'wildcard_import', + ] + + # vim:ts=4 sts=4 sw=4 et +-- +2.32.0 + + diff --git a/remove-nose-in-documentation.patch b/remove-nose-in-documentation.patch new file mode 100644 index 0000000..d489c90 --- /dev/null +++ b/remove-nose-in-documentation.patch @@ -0,0 +1,60 @@ +From e68b6780bc191645ac7bfd0085f8b68a58729e71 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= +Date: Mon, 26 Jul 2021 09:45:53 +0200 +Subject: [PATCH] Remove nose in documentation + +--- + doc/README | 3 --- + private/apt-install-build-reqs | 1 - + private/build-and-test | 2 +- + 3 files changed, 1 insertion(+), 5 deletions(-) + +diff --git a/doc/README b/doc/README +index 76f2b63..c808cb2 100644 +--- a/doc/README ++++ b/doc/README +@@ -21,7 +21,6 @@ The following software is required to build python-djvulibre: + + Additionally, the following software is needed to run the tests: + +-* nose_ + * subprocess32_ (only for Python 2.X) + * DjVuLibre_ command-line tools + * Ghostscript_ +@@ -32,8 +31,6 @@ Additionally, the following software is needed to run the tests: + https://cython.org/ + .. _pkg-config: + https://wiki.freedesktop.org/www/Software/pkg-config/ +-.. _nose: +- https://nose.readthedocs.io/ + .. _subprocess32: + https://pypi.org/project/subprocess32/ + .. _Ghostscript: +diff --git a/private/apt-install-build-reqs b/private/apt-install-build-reqs +index 69beade..744ba5f 100755 +--- a/private/apt-install-build-reqs ++++ b/private/apt-install-build-reqs +@@ -21,7 +21,6 @@ python-dev + cython + ' + pkgs_tests=' +-python-nose + python-subprocess32 + djvulibre-bin + ghostscript +diff --git a/private/build-and-test b/private/build-and-test +index 08aa4fb..c86665b 100755 +--- a/private/build-and-test ++++ b/private/build-and-test +@@ -45,6 +45,6 @@ printf '%s\n' "$@" \ + | xargs -P"$opt_jobs" -t -I'{python}' env '{python}' setup.py build --build-lib 'build/{python}' + cd tests + printf '%s\n' "$@" \ +-| xargs -t -I'{python}' env PYTHONPATH="$PWD/../build/{python}" '{python}' -c 'import nose; nose.main()' --verbose ++| xargs -t -I'{python}' env PYTHONPATH="$PWD/../build/{python}" '{python}' -c 'import unittest; unittest.main()' --verbose + + # vim:ts=4 sts=4 sw=4 et +-- +2.32.0 + + diff --git a/sphinx_4_compatibility.patch b/sphinx_4_compatibility.patch new file mode 100644 index 0000000..bdeba30 --- /dev/null +++ b/sphinx_4_compatibility.patch @@ -0,0 +1,14 @@ +--- + doc/api/conf.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/doc/api/conf.py ++++ b/doc/api/conf.py +@@ -55,6 +55,6 @@ rst_epilog = ''' + import sphinx.writers.html + del sphinx.writers.html.HTMLTranslator.visit_math + def setup(app): +- app.add_stylesheet('docutils-math.css') ++ app.add_css_file('docutils-math.css') + + # vim:ts=4 sts=4 sw=4 et