diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..14ff2b6 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,9 @@ +[run] +branch = true +parallel = true + +[report] +show_missing = true +skip_empty = true +skip_covered = true +precision = 2 \ No newline at end of file diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 16a47d4..b9dc29b 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index 2ba568e..e0bdfcc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.pyc .DS_Store .tox +.coverage +.vscode MANIFEST build dist diff --git a/setup.py b/setup.py index f559da8..4a8294c 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,10 @@ import os from setuptools import setup, find_packages -# Workaround for multiprocessing/nose issue. See http://bugs.python.org/msg170215 -try: - import multiprocessing -except ImportError: - pass -read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read() +def read(filepath): + with codecs.open(filepath, 'r', 'utf-8') as f: + return f.read() # Load package meta from the pkgmeta module without loading the package. pkgmeta = {} @@ -31,13 +28,6 @@ packages=find_packages(exclude=['tests', 'tests.*']), zip_safe=False, include_package_data=True, - tests_require=[ - 'mock>=1.0.1', - 'nose>=1.3.6', - 'nose-progressive>=1.5.1', - 'Pillow', - ], - test_suite='nose.collector', install_requires=[], classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -47,12 +37,12 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Utilities' ], ) diff --git a/tests/test_processors.py b/tests/test_processors.py index 5fb1dd1..2782f1a 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1,22 +1,23 @@ +import os +import mock +import pytest + from pilkit.lib import Image, ImageDraw, ImageColor from pilkit.processors import (Resize, ResizeToFill, ResizeToFit, SmartCrop, SmartResize, MakeOpaque, ColorOverlay, Convert, GaussianBlur) -from nose.tools import eq_, assert_true -import os from pilkit.processors.resize import Thumbnail from .utils import create_image, compare_images, get_image_file -import mock def test_smartcrop(): img = SmartCrop(100, 100).process(create_image()) - eq_(img.size, (100, 100)) + assert img.size == (100, 100) def test_resizetofill(): img = ResizeToFill(100, 100).process(create_image()) - eq_(img.size, (100, 100)) + assert img.size == (100, 100) def test_resizetofit(): @@ -27,7 +28,7 @@ def test_resizetofit(): img = ResizeToFit(100, 100).process(img) # Assert that the image has maintained the aspect ratio. - eq_(img.size, (100, 50)) + assert img.size == (100, 50) def test_resize_rounding(): @@ -37,13 +38,13 @@ def test_resize_rounding(): img = Image.new('RGB', (95, 95)) img = ResizeToFill(28, 28).process(img) - eq_(img.size, (28, 28)) + assert img.size == (28, 28) def test_resizetofit_mat(): img = Image.new('RGB', (200, 100)) img = ResizeToFit(100, 100, mat_color=0x000000).process(img) - eq_(img.size, (100, 100)) + assert img.size == (100, 100) def test_coloroverlay(): @@ -53,16 +54,16 @@ def test_coloroverlay(): img = Image.new('RGB', (200, 100)) color = ImageColor.getrgb('#cc0000') img = ColorOverlay(color, overlay_opacity=1.0).process(img) - eq_(img.getpixel((0,0)), (204, 0, 0)) + assert img.getpixel((0,0)) == (204, 0, 0) def test_convert(): img = Image.new('RGBA', (200, 100)) img_RGBa = Convert("RGBa").process(img) - eq_(img_RGBa.mode, "RGBa") + assert img_RGBa.mode == "RGBa" img_RGBa_RGBA = Convert("RGBA").process(img) - eq_(img_RGBa_RGBA.mode, "RGBA") + assert img_RGBa_RGBA.mode == "RGBA" def test_resize_antialiasing(): @@ -92,7 +93,7 @@ def test_resize_antialiasing(): # Count the number of colors color_count = len(list(filter(None, img.histogram()))) - assert_true(color_count > 2) + assert color_count > 2 def test_upscale(): @@ -105,37 +106,33 @@ def test_upscale(): for P in [Resize, ResizeToFit, ResizeToFill, SmartResize]: img2 = P(500, 500, upscale=True).process(img) - eq_(img2.size, (500, 500)) + assert img2.size == (500, 500) img2 = P(500, 500, upscale=False).process(img) - eq_(img2.size, (100, 100)) + assert img2.size == (100, 100) def test_should_raise_exception_if_anchor_is_passed_and_crop_is_set_to_false(): - try: + with pytest.raises(Exception, match=r"You can't specify an anchor point if crop is False."): Thumbnail(height=200, width=200, upscale=False, crop=False, anchor='t') - except Exception as e: - eq_(str(e), "You can't specify an anchor point if crop is False.") def test_should_set_crop_to_true_if_anchor_is_passed_without_crop(): thumb = Thumbnail(height=200, width=200, upscale=False, anchor='t') - assert_true(thumb.crop) + assert thumb.crop def test_should_raise_exception_when_crop_is_passed_without_height_and_width(): img = Image.new('RGB', (100, 100)) - try: + with pytest.raises(Exception, match=r"You must provide both a width and height when cropping."): Thumbnail(crop=True).process(img) - except Exception as e: - eq_(str(e), 'You must provide both a width and height when cropping.') @mock.patch('pilkit.processors.resize.SmartResize') def test_should_call_smartresize_when_crop_not_passed(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, upscale=False).process(img) - assert_true(my_mock.called) + assert my_mock.called @mock.patch('pilkit.processors.resize.SmartResize') @@ -156,27 +153,27 @@ def test_should_repass_upscale_option_false(my_mock): def test_should_call_resizetofill_when_crop_and_ancho_is_passed(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, anchor='fake').process(img) - assert_true(my_mock.called) + assert my_mock.called @mock.patch('pilkit.processors.resize.ResizeToFit') def test_should_call_resizetofit_when_crop_is_not_passed(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, crop=False).process(img) - assert_true(my_mock.called) + assert my_mock.called def test_GaussianBlur_radius_3(): img = GaussianBlur(radius = 3).process(create_image()) img = img.crop((112,112,144,144)) expected_img = Image.open(get_image_file("GaussianBlur_radius_3.png")) - assert_true(compare_images(img, expected_img)) + assert compare_images(img, expected_img) def test_GaussianBlur_radius_7(): img = GaussianBlur(radius=7).process(create_image()) img = img.crop((112, 112, 144, 144)) expected_img = Image.open(get_image_file("GaussianBlur_radius_7.png")) - assert_true(compare_images(img, expected_img)) + assert compare_images(img, expected_img) def test_make_gifs_opaque(): dir = os.path.dirname(__file__) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7657efc..164dc2b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,33 +1,35 @@ import os -from io import UnsupportedOperation +import io +import pytest +from mock import Mock, patch +from tempfile import NamedTemporaryFile + from pilkit.exceptions import UnknownFormat, UnknownExtension from pilkit.lib import Image from pilkit.utils import (extension_to_format, format_to_extension, FileWrapper, save_image, prepare_image, quiet) -from mock import Mock, patch -from nose.tools import eq_, raises, ok_ -from tempfile import NamedTemporaryFile + from .utils import create_image def test_extension_to_format(): - eq_(extension_to_format('.jpeg'), 'JPEG') - eq_(extension_to_format('.rgba'), 'SGI') + assert extension_to_format('.jpeg') == 'JPEG' + assert extension_to_format('.rgba') == 'SGI' def test_format_to_extension_no_init(): - eq_(format_to_extension('PNG'), '.png') - eq_(format_to_extension('ICO'), '.ico') + assert format_to_extension('PNG') == '.png' + assert format_to_extension('ICO') == '.ico' -@raises(UnknownFormat) def test_unknown_format(): - format_to_extension('TXT') + with pytest.raises(UnknownFormat): + format_to_extension('TXT') -@raises(UnknownExtension) def test_unknown_extension(): - extension_to_format('.txt') + with pytest.raises(UnknownExtension): + extension_to_format('.txt') def test_default_extension(): @@ -40,17 +42,17 @@ def test_default_extension(): extensions we'd prefer, and this tests to make sure it's working. """ - eq_(format_to_extension('JPEG'), '.jpg') + assert format_to_extension('JPEG') == '.jpg' -@raises(AttributeError) def test_filewrapper(): class K(object): def fileno(self): - raise UnsupportedOperation + raise io.UnsupportedOperation - FileWrapper(K()).fileno() + with pytest.raises(AttributeError): + FileWrapper(K()).fileno() def test_save_with_filename(): @@ -60,9 +62,8 @@ def test_save_with_filename(): """ im = create_image() - outfile = NamedTemporaryFile() - save_image(im, outfile.name, 'JPEG') - outfile.close() + with NamedTemporaryFile() as outfile: + save_image(im, outfile.name, 'JPEG') def test_format_normalization(): @@ -71,7 +72,8 @@ def test_format_normalization(): See https://github.com/matthewwithanm/django-imagekit/issues/262 """ im = Image.new('RGBA', (100, 100)) - ok_('transparency' in prepare_image(im, 'gIF')[1]) + assert 'transparency' in prepare_image(im, 'gIF')[1] + def test_quiet(): """ diff --git a/tox.ini b/tox.ini index f10212a..122083c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,22 @@ [tox] envlist = - py{39,38,37,36,35,34,27} + py{310,39,38,37,36,35,27}, + coverage-report [testenv] -commands = python setup.py test +setenv = COVERAGE_FILE=.coverage.{envname} +commands = python -m pytest --cov --cov-report term-missing:skip-covered deps = - py34: Pillow<6 + pytest + pytest-cov + mock>=1.0.1 + Pillow + + +[testenv:coverage-report] +deps = coverage +skip_install = true +setenv = COVERAGE_FILE=.coverage +commands = + coverage combine + coverage report \ No newline at end of file