diff --git a/.gitignore b/.gitignore index 6f4c82ec..b0a9e7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Makefile.custom.config __pycache__ uploads .cache -.coverage +.coverage* coverage.xml .env .mypy_cache diff --git a/Makefile b/Makefile index abaf07e1..d0ebf521 100644 --- a/Makefile +++ b/Makefile @@ -232,6 +232,8 @@ test-e2e-client: E2E_ARGS=client $(PYTEST) e2e --driver firefox $(PYTEST_ARGS) test-python: + # for tests parallelization: 4 workers max. + # make test-python PYTEST_ARGS="-p no:warnings -n auto --maxprocesses=4" $(PYTEST) fittrackee --cov-config .coveragerc --cov=fittrackee --cov-report term-missing $(PYTEST_ARGS) test-client: diff --git a/db/create.sql b/db/create.sql index f393a9f7..05c59d7e 100644 --- a/db/create.sql +++ b/db/create.sql @@ -1,5 +1,9 @@ DROP DATABASE IF EXISTS fittrackee; DROP DATABASE IF EXISTS fittrackee_test; +DROP DATABASE IF EXISTS fittrackee_test_gw0; +DROP DATABASE IF EXISTS fittrackee_test_gw1; +DROP DATABASE IF EXISTS fittrackee_test_gw2; +DROP DATABASE IF EXISTS fittrackee_test_gw3; DROP SCHEMA IF EXISTS fittrackee; DROP USER IF EXISTS fittrackee; @@ -7,3 +11,7 @@ CREATE USER fittrackee WITH PASSWORD 'fittrackee'; CREATE SCHEMA fittrackee AUTHORIZATION fittrackee; CREATE DATABASE fittrackee OWNER fittrackee; CREATE DATABASE fittrackee_test OWNER fittrackee; +CREATE DATABASE fittrackee_test_gw0 OWNER fittrackee; +CREATE DATABASE fittrackee_test_gw1 OWNER fittrackee; +CREATE DATABASE fittrackee_test_gw2 OWNER fittrackee; +CREATE DATABASE fittrackee_test_gw3 OWNER fittrackee; diff --git a/fittrackee/config.py b/fittrackee/config.py index b398e32e..faac7e58 100644 --- a/fittrackee/config.py +++ b/fittrackee/config.py @@ -10,6 +10,12 @@ if os.getenv('APP_SETTINGS') == 'fittrackee.config.TestingConfig': else: broker = RedisBroker +XDIST_WORKER = ( + f"_{os.getenv('PYTEST_XDIST_WORKER')}" + if os.getenv('PYTEST_XDIST_WORKER') + else '' +) + class BaseConfig: @@ -74,7 +80,13 @@ class DevelopmentConfig(BaseConfig): class TestingConfig(BaseConfig): DEBUG = True TESTING = True - SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') + SQLALCHEMY_DATABASE_URI = ( + os.environ.get('DATABASE_TEST_URL', '') + XDIST_WORKER + ) + UPLOAD_FOLDER = os.path.join( + os.getenv('UPLOAD_FOLDER', current_app.root_path), + 'uploads' + XDIST_WORKER, + ) SECRET_KEY = 'test key' # nosec BCRYPT_LOG_ROUNDS = 4 TOKEN_EXPIRATION_DAYS = 0 diff --git a/fittrackee/tests/application/test_config.py b/fittrackee/tests/application/test_config.py index 7e43967d..375addac 100644 --- a/fittrackee/tests/application/test_config.py +++ b/fittrackee/tests/application/test_config.py @@ -41,7 +41,11 @@ class TestTestingConfig: app.config.from_object('fittrackee.config.TestingConfig') assert app.config['SQLALCHEMY_DATABASE_URI'] == os.environ.get( - 'DATABASE_TEST_URL' + 'DATABASE_TEST_URL', '' + ) + ( + f"_{os.getenv('PYTEST_XDIST_WORKER')}" + if os.getenv('PYTEST_XDIST_WORKER') + else '' ) def test_it_does_not_preserve_context_on_exception( diff --git a/fittrackee/tests/conftest.py b/fittrackee/tests/conftest.py index e8e959da..f0a40e34 100644 --- a/fittrackee/tests/conftest.py +++ b/fittrackee/tests/conftest.py @@ -8,6 +8,7 @@ os.environ['APP_SETTINGS'] = 'fittrackee.config.TestingConfig' # to avoid resetting dev database during tests os.environ['DATABASE_URL'] = os.environ['DATABASE_TEST_URL'] TEMP_FOLDER = '/tmp/FitTrackee' +os.makedirs(TEMP_FOLDER, exist_ok=True) os.environ['UPLOAD_FOLDER'] = TEMP_FOLDER os.environ['APP_LOG'] = TEMP_FOLDER + '/fittrackee.log' os.environ['AUTHLIB_INSECURE_TRANSPORT'] = '1' diff --git a/fittrackee/tests/fixtures/fixtures_app.py b/fittrackee/tests/fixtures/fixtures_app.py index af67a37e..ec093f8a 100644 --- a/fittrackee/tests/fixtures/fixtures_app.py +++ b/fittrackee/tests/fixtures/fixtures_app.py @@ -18,7 +18,11 @@ def get_app_config( max_users: Optional[int] = None, ) -> Optional[AppConfig]: if with_config: - config = AppConfig() + config = AppConfig.query.one_or_none() + if not config: + config = AppConfig() + db.session.add(config) + db.session.flush() config.gpx_limit_import = 10 if max_workouts is None else max_workouts config.max_single_file_size = ( (1 if max_single_file_size is None else max_single_file_size) @@ -31,7 +35,6 @@ def get_app_config( * 1024 ) config.max_users = 100 if max_users is None else max_users - db.session.add(config) db.session.commit() return config return None diff --git a/fittrackee/tests/oauth2/test_oauth2_client.py b/fittrackee/tests/oauth2/test_oauth2_client.py index 6b04b876..db86b46c 100644 --- a/fittrackee/tests/oauth2/test_oauth2_client.py +++ b/fittrackee/tests/oauth2/test_oauth2_client.py @@ -172,7 +172,7 @@ class TestCreateOAuth2Client: class TestOAuthCheckScopes: @pytest.mark.parametrize( - 'input_scope', ['', 1, random_string(), [random_string(), 'readwrite']] + 'input_scope', ['', 1, 'invalid_scope', ['invalid_scope', 'readwrite']] ) def test_it_raises_error_when_scope_is_invalid( self, input_scope: Any @@ -184,11 +184,11 @@ class TestOAuthCheckScopes: 'input_scope,expected_scope', [ ('profile:read', 'profile:read'), - ('profile:read ' + random_string(), 'profile:read'), + ('profile:read invalid_scope:read', 'profile:read'), ('profile:write', 'profile:write'), ('profile:read profile:write', 'profile:read profile:write'), ( - 'profile:write profile:read ' + random_string(), + 'profile:write invalid_scope:read profile:read', 'profile:write profile:read', ), ], diff --git a/fittrackee/tests/test_utils.py b/fittrackee/tests/test_utils.py index 0b6f59fd..124a74ea 100644 --- a/fittrackee/tests/test_utils.py +++ b/fittrackee/tests/test_utils.py @@ -1,5 +1,4 @@ from typing import Union -from uuid import uuid4 import pytest @@ -34,7 +33,7 @@ class TestReadableDuration: ('en', '30 seconds'), ('fr', '30 secondes'), (None, '30 seconds'), - (uuid4().hex, '30 seconds'), + ('invalid_locale', '30 seconds'), ], ) def test_it_returns_duration_in_locale( diff --git a/poetry.lock b/poetry.lock index f26d39f8..60425004 100644 --- a/poetry.lock +++ b/poetry.lock @@ -280,6 +280,17 @@ python-versions = ">=3.7" [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +testing = ["pre-commit"] + [[package]] name = "flake8" version = "3.9.2" @@ -745,6 +756,17 @@ python-versions = ">=3.6" [package.extras] twisted = ["twisted"] +[[package]] +name = "psutil" +version = "5.9.4" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + [[package]] name = "psycopg2-binary" version = "2.9.5" @@ -1002,6 +1024,24 @@ hjson = ["hjson"] toml = ["toml"] yaml = ["PyYAML"] +[[package]] +name = "pytest-xdist" +version = "3.0.2" +description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +execnet = ">=1.1" +psutil = {version = ">=3.0", optional = true, markers = "extra == \"psutil\""} +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1577,7 +1617,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "b8ef9801e4dcd04375d3a5e83303ddc8a1b65ad35a127e1e2fd115e85cbe095c" +content-hash = "ae84573fe28fd08fc1a7917d20ca97f73e070a668cf0baa0ac546d3a6f7e58ae" [metadata.files] alabaster = [ @@ -1840,6 +1880,10 @@ exceptiongroup = [ {file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"}, {file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"}, ] +execnet = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, @@ -2165,6 +2209,22 @@ prometheus-client = [ {file = "prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"}, {file = "prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"}, ] +psutil = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] psycopg2-binary = [ {file = "psycopg2-binary-2.9.5.tar.gz", hash = "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c"}, {file = "psycopg2_binary-2.9.5-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85"}, @@ -2315,6 +2375,10 @@ pytest-variables = [ {file = "pytest-variables-2.0.0.tar.gz", hash = "sha256:1c9e4fc321e33be7d1b352ac9cf20fdd2c39a8e4e6fa2dcd042aaf70ed516be7"}, {file = "pytest_variables-2.0.0-py3-none-any.whl", hash = "sha256:1a24a30b7acf9654d71bcdc8b10c1eb0d81b73b3eec72d810703c522475d643b"}, ] +pytest-xdist = [ + {file = "pytest-xdist-3.0.2.tar.gz", hash = "sha256:688da9b814370e891ba5de650c9327d1a9d861721a524eb917e620eec3e90291"}, + {file = "pytest_xdist-3.0.2-py3-none-any.whl", hash = "sha256:9feb9a18e1790696ea23e1434fa73b325ed4998b0e9fcb221f16fd1945e6df1b"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, diff --git a/pyproject.toml b/pyproject.toml index aaab3791..ead174ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ pytest-flake8 = "^1.1" pytest-isort = "^3.1" pytest-runner = "^6.0" pytest-selenium = "^2.0.1" +pytest-xdist = {extras = ["psutil"], version = "^3.0.2"} recommonmark = "^0.7" sphinx = "^5.1" sphinx-bootstrap-theme = "^0.8.1"