File remove-all-dependencies-on-nose-in-the-code.patch of Package python-djvulibre

From 732f72871f38640e3e9931b4d1e1250b47072b48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= <mcepl@cepl.eu>
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, '<djvu.const.TextZoneType: page>')
-        assert_repr(TEXT_ZONE_COLUMN, '<djvu.const.TextZoneType: column>')
-        assert_repr(TEXT_ZONE_REGION, '<djvu.const.TextZoneType: region>')
-        assert_repr(TEXT_ZONE_PARAGRAPH, '<djvu.const.TextZoneType: para>')
-        assert_repr(TEXT_ZONE_LINE, '<djvu.const.TextZoneType: line>')
-        assert_repr(TEXT_ZONE_WORD, '<djvu.const.TextZoneType: word>')
-        assert_repr(TEXT_ZONE_CHARACTER, '<djvu.const.TextZoneType: char>')
+        self.assertEqual(repr(TEXT_ZONE_PAGE), '<djvu.const.TextZoneType: page>')
+        self.assertEqual(repr(TEXT_ZONE_COLUMN), '<djvu.const.TextZoneType: column>')
+        self.assertEqual(repr(TEXT_ZONE_REGION), '<djvu.const.TextZoneType: region>')
+        self.assertEqual(repr(TEXT_ZONE_PARAGRAPH), '<djvu.const.TextZoneType: para>')
+        self.assertEqual(repr(TEXT_ZONE_LINE), '<djvu.const.TextZoneType: line>')
+        self.assertEqual(repr(TEXT_ZONE_WORD), '<djvu.const.TextZoneType: word>')
+        self.assertEqual(repr(TEXT_ZONE_CHARACTER), '<djvu.const.TextZoneType: char>')
 
     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=<lang>.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=<lang>.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:
             '</rdf:Description>'
             '</rdf:RDF>\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


openSUSE Build Service is sponsored by