Skip to content

Commit

Permalink
improve test coverage (django-import-export#1372)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhegarty committed Dec 27, 2021
1 parent 33730d8 commit f971ae5
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 103 deletions.
121 changes: 40 additions & 81 deletions .github/workflows/django-import-export-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,122 +7,81 @@ on:
pull_request:
branches:
- main
# this is a temporary addition - can be removed after 3.0 release
- release-3-x

jobs:
test:
runs-on: ubuntu-latest
env:
USERNAME: testuser
PASSWD: ${{ secrets.TEST_PASSWD }}
DB_NAME: import_export
MYSQL_USER: root
MYSQL_PASS: root
IMPORT_EXPORT_POSTGRESQL_USER: postgres
IMPORT_EXPORT_POSTGRESQL_PASSWORD: somepass
IMPORT_EXPORT_MYSQL_USER: root
IMPORT_EXPORT_MYSQL_PASSWORD: root
strategy:
max-parallel: 4
matrix:
db: [ sqlite, postgres, mysql ]
python-version: [ 3.6, 3.7, 3.8, 3.9, "3.10" ]
django-version: [ 2.2, 3.0, 3.1, 3.2, 4.0, main ]
include:
- db: postgres
db_port: 5432
- db: mysql
db_port: 3306
exclude:
- django-version: main
python-version: 3.6
- django-version: main
python-version: 3.7
- django-version: 4.0
python-version: 3.6
- django-version: 4.0
python-version: 3.7
- django-version: 2.2
python-version: "3.10"
- django-version: 3.0
python-version: "3.10"
- django-version: 3.1
python-version: "3.10"
- django-version: 3.2
python-version: "3.10"
python-version:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
services:
mysql:
image: mysql:8.0
env:
IMPORT_EXPORT_TEST_TYPE: mysql-innodb
IMPORT_EXPORT_MYSQL_USER: ${{ env.TESTUSER }}
IMPORT_EXPORT_MYSQL_PASSWORD: ${{ env.PASSWD }}
MYSQL_USER: ${{ env.TESTUSER }}
MYSQL_PASSWORD: ${{ env.IMPORT_EXPORT_MYSQL_PASSWORD }}
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: import_export
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
postgres:
image: postgres
env:
IMPORT_EXPORT_TEST_TYPE: postgres
IMPORT_EXPORT_POSTGRESQL_USER: postgres
IMPORT_EXPORT_POSTGRESQL_PASSWORD: ${{ env.PASSWD }}
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: import_export
POSTGRES_USER: ${{ env.IMPORT_EXPORT_POSTGRESQL_USER }}
POSTGRES_PASSWORD: ${{ env.IMPORT_EXPORT_POSTGRESQL_PASSWORD }}
POSTGRES_DB: ${{ env.DB_NAME }}
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Set up MySQL
run: |
sudo /etc/init.d/mysql start
mysql -e 'CREATE DATABASE ${{ env.DB_NAME }};' -u${{ env.MYSQL_USER }} -p${{ env.MYSQL_PASS }}
- name: Check out repository code
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Run isort checks
uses: jamescurtin/isort-action@master
- uses: actions/cache@v2
with:
sortPaths: "import_export tests"
configuration: "--check-only"
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade pip
pip install -r requirements/base.txt
pip install -r requirements/test.txt
- if: matrix.django-version != 'main'
name: Upgrade Django version (release)
run: |
python -m pip install "Django~=${{ matrix.django-version }}.0"
- if: matrix.django-version == 'main'
name: Upgrade Django version (main)
run: |
python -m pip install "https://github.com/django/django/archive/main.tar.gz"
- name: List versions
run: |
echo "Python ${{ matrix.python-version }} -> Django ${{ matrix.django-version }}"
python --version
echo "Django `django-admin --version`"
- name: Run Tests
- name: Run tox targets for ${{ matrix.python-version }} (sqlite)
run: tox
- name: Run tox targets for ${{ matrix.python-version }} (postgres)
run: tox
env:
DB: ${{ matrix.db }}
DB_HOST: 127.0.0.1
DB_PORT: ${{ matrix.db_port }}
DB_PASSWORD: ${{ env.PASSWD }}
run: >-
PYTHONPATH=".:tests:$PYTHONPATH" python
-W error::DeprecationWarning -W error::PendingDeprecationWarning
-m coverage run --omit='setup.py,./tests/*,./import_export/locale/*'
--source=. tests/manage.py test core --settings=
IMPORT_EXPORT_TEST_TYPE: postgres
- name: Run tox targets for ${{ matrix.python-version }} (mysql)
run: tox
env:
IMPORT_EXPORT_TEST_TYPE: mysql-innodb
- name: Combine test coverage
run: |
coverage combine
- name: Upload coverage data to coveralls.io
run: coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: ${{ matrix.db }}-${{ matrix.django-version }}-${{ matrix.python-version }}
COVERALLS_FLAG_NAME: ${{ matrix.python-version }}
COVERALLS_PARALLEL: true

coveralls:
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ exporting data with included admin integration.
import_workflow
bulk_import
celery
testing
changelog

.. toctree::
Expand Down
5 changes: 5 additions & 0 deletions import_export/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import warnings

warnings.warn("the exceptions module is deprecated and will be removed in a future release", DeprecationWarning)


class ImportExportError(Exception):
"""A generic exception for all others to extend."""
pass
Expand Down
15 changes: 5 additions & 10 deletions import_export/formats/base_formats.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from importlib import import_module

import tablib
from tablib.formats import registry


class Format:
Expand Down Expand Up @@ -61,14 +60,10 @@ def get_format(self):
"""
Import and returns tablib module.
"""
try:
# Available since tablib 1.0
from tablib.formats import registry
except ImportError:
return import_module(self.TABLIB_MODULE)
else:
key = self.TABLIB_MODULE.split('.')[-1].replace('_', '')
return registry.get_format(key)
if not self.TABLIB_MODULE:
raise AttributeError("TABLIB_MODULE must be defined")
key = self.TABLIB_MODULE.split('.')[-1].replace('_', '')
return registry.get_format(key)

@classmethod
def is_available(cls):
Expand Down
4 changes: 3 additions & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ coveralls
chardet
pytz
memory-profiler
django-extensions
django-extensions
tox
tox-gh-actions
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ python-file-with-version = import_export/__init__.py

[isort]
profile = black

[coverage:run]
parallel = true
source = import_export
10 changes: 10 additions & 0 deletions tests/core/tests/test_base_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ def test_can_import_default(self):
def test_can_export_default(self):
self.assertFalse(self.format.can_export())


class TablibFormatTest(TestCase):
def setUp(self):
self.format = base_formats.TablibFormat()

def test_get_format_for_undefined_TABLIB_MODULE_raises_AttributeError(self):
with self.assertRaises(AttributeError):
self.format.get_format()


class XLSTest(TestCase):

def setUp(self):
Expand Down
10 changes: 10 additions & 0 deletions tests/core/tests/test_import_export_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from unittest import TestCase

from import_export.templatetags import import_export_tags


class TagsTest(TestCase):

def test_compare_values(self):
target = '<del style="background:#ffe6e6;">a</del><ins style="background:#e6ffe6;">b</ins>'
self.assertEqual(target, import_export_tags.compare_values("a", "b"))
13 changes: 13 additions & 0 deletions tests/core/tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import date
from decimal import Decimal, InvalidOperation
from unittest import mock, skip, skipIf, skipUnless
from unittest.mock import patch

import django
import tablib
Expand Down Expand Up @@ -160,6 +161,18 @@ def test_init_instance_raises_NotImplementedError(self):
with self.assertRaises(NotImplementedError):
self.my_resource.init_instance([])

@patch("core.models.Book.full_clean")
def test_validate_instance_called_with_import_validation_errors_as_None_creates_empty_dict(self, full_clean_mock):
# validate_instance() import_validation_errors is an optional kwarg
# If not provided, it defaults to an empty dict
# this tests that scenario by ensuring that an empty dict is passed
# to the model instance full_clean() method.
book = Book()
self.my_resource._meta.clean_model_instances = True
self.my_resource.validate_instance(book)
target = dict()
full_clean_mock.assert_called_once_with(exclude=target.keys(), validate_unique=True)


class AuthorResource(resources.ModelResource):

Expand Down
17 changes: 16 additions & 1 deletion tests/core/tests/test_widgets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from unittest import mock
from unittest import mock, skipUnless

import django
import pytz
from core.models import Author, Category
from django.test import TestCase
Expand Down Expand Up @@ -42,6 +43,20 @@ def test_render(self):
self.assertEqual(self.widget.render(None), "")


class FormatDatetimeTest(TestCase):
date = date(10, 8, 2)
target_dt = "02.08.0010"
format = "%d.%m.%Y"

@skipUnless(django.VERSION[0] < 4, f"skipping django {django.VERSION} version specific test")
def test_format_datetime_lt_django4(self):
self.assertEqual(self.target_dt, widgets.format_datetime(self.date, self.format))

@skipUnless(django.VERSION[0] >= 4, f"skipping django {django.VERSION} version specific test")
def test_format_datetime_gte_django4(self):
self.assertEqual(self.target_dt, widgets.format_datetime(self.date, self.format))


class DateWidgetTest(TestCase):

def setUp(self):
Expand Down
4 changes: 2 additions & 2 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'import_export',
'USER': os.environ.get('IMPORT_EXPORT_MYSQL_USER', 'root'),
'PASSWORD': os.environ.get('IMPORT_EXPORT_MYSQL_PASSWORD', 'password'),
'USER': os.environ.get('IMPORT_EXPORT_MYSQL_USER'),
'PASSWORD': os.environ.get('IMPORT_EXPORT_MYSQL_PASSWORD'),
'HOST': '127.0.0.1',
'PORT': 3306,
'TEST': {
Expand Down
28 changes: 20 additions & 8 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
[tox]
envlist =
isort
{py36,py37,py38,py39,py310}-django22-tablib{dev,stable}
{py36,py37,py38,py39,py310}-django31-tablib{dev,stable}
{py36,py37,py38,py39,py310}-django32-tablib{dev,stable}
{py38,py39,py310}-django40-tablib{dev,stable}
{py38,py39,py310}-djangomain-tablib{dev,stable}
{py37,py38,py39,py310}-{django32}
{py38,py39,py310}-{django40,djangomain}
py310-djangomain-tablibdev

[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310

[testenv]
commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning {toxinidir}/tests/manage.py test core
setenv = PYTHONPATH = {toxinidir}/tests
commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning -m coverage run {toxinidir}/tests/manage.py test core
deps =
tablibdev: -egit+https://github.com/jazzband/tablib.git#egg=tablib
tablibstable: tablib
django22: Django>=2.2,<3.0
django31: Django>=3.1,<3.2
django32: Django>=3.2,<4.0
django40: Django>=4.0,<4.1
djangomain: https://github.com/django/django/archive/main.tar.gz
-rrequirements/test.txt

# if postgres / mysql environment variables exist, we can go ahead and run db specific tests
passenv =
IMPORT_EXPORT_POSTGRESQL_USER
IMPORT_EXPORT_POSTGRESQL_PASSWORD
IMPORT_EXPORT_MYSQL_USER
IMPORT_EXPORT_MYSQL_PASSWORD
IMPORT_EXPORT_TEST_TYPE

[testenv:isort]
skip_install = True
deps = isort
Expand Down

0 comments on commit f971ae5

Please sign in to comment.