Skip to content

Commit 735cafd

Browse files
authored
chore: update CI conf to run Docker tests in parallel (#547)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent bd40806 commit 735cafd

15 files changed

Lines changed: 90 additions & 95 deletions

File tree

.github/workflows/check-code-and-docs-validation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Run unit tests
1+
name: Check code and docs validation
22

33
on:
44
workflow_dispatch:

.github/workflows/run-unit-tests-docker.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@ jobs:
2222
- name: Generate the .env file and the SECRET_KEY
2323
run: make envfile
2424

25+
- name: Build Docker image
26+
run: docker compose -f compose.yml -f compose.build.yml build
27+
2528
- name: Run tests
26-
run: docker compose run web python ./manage.py test --verbosity=2 --noinput
29+
run: |
30+
docker compose -f compose.yml -f compose.build.yml run web \
31+
python ./manage.py test --verbosity=2 --noinput --parallel auto

Makefile

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ superuser:
4747
${MANAGE} createsuperuser
4848

4949
########################################################################################
50-
# Utilities
50+
# Linters / docs
5151
########################################################################################
5252

5353
DOCS_LOCATION=./docs
@@ -76,36 +76,12 @@ docs:
7676
uvx --from sphinx==9.1.0 --with furo==2025.12.19 sphinx-build -b html ${DOCS_LOCATION} ${DOCS_LOCATION}/_build/html/
7777

7878
########################################################################################
79-
80-
VENV_LOCATION=.venv
81-
ACTIVATE?=. ${VENV_LOCATION}/bin/activate;
82-
#MANAGE=${VENV_LOCATION}/bin/python manage.py
83-
# Do not depend on Python to generate the SECRET_KEY
84-
GET_SECRET_KEY=`head -c50 /dev/urandom | base64 | head -c50`
85-
# Customize with `$ make envfile ENV_FILE=/etc/dejacode/.env`
86-
ENV_FILE=.env
87-
DOCKER_COMPOSE=docker compose -f docker-compose.yml
88-
DOCKER_EXEC=${DOCKER_COMPOSE} exec
89-
DB_NAME=dejacode_db
90-
DB_USERNAME=dejacode
91-
DB_PASSWORD=dejacode
92-
DB_CONTAINER_NAME=db
93-
DB_INIT_FILE=./data/postgresql/initdb.sql.gz
94-
POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8
95-
TIMESTAMP=$(shell date +"%Y-%m-%d_%H%M")
96-
97-
conf: virtualenv
98-
@echo "-> Install dependencies"
99-
uv sync --frozen
100-
@echo "-> Create the var/ directory"
101-
@mkdir -p var
102-
103-
dev: virtualenv
104-
@echo "-> Configure and install development dependencies"
105-
uv sync --frozen --extra dev
79+
# Utilities
80+
########################################################################################
10681

10782
outdated:
10883
@echo "-> Check for outdated packages (with 7 days cooldown)"
84+
uv sync --frozen --quiet
10985
uv pip list --outdated \
11086
--no-config \
11187
--index-url https://pypi.org/simple \
@@ -119,25 +95,61 @@ upgrade:
11995
exit 1; \
12096
fi
12197
@echo "-> Download $(PACKAGE) wheels for Linux x86_64"
122-
pip download $(PACKAGE) \
98+
uvx pip download $(PACKAGE) \
12399
--only-binary=:all: \
124100
--platform manylinux_2_28_x86_64 \
125101
--platform manylinux_2_17_x86_64 \
126102
--python-version 3.14 \
127103
--dest ./thirdparty/dist/
128104
@echo "-> Download $(PACKAGE) wheels for macOS ARM64"
129-
pip download $(PACKAGE) \
105+
uvx pip download $(PACKAGE) \
130106
--only-binary=:all: \
131107
--platform macosx_11_0_arm64 \
132108
--python-version 3.14 \
133109
--dest ./thirdparty/dist/
134110
@echo "-> Update pyproject.toml and uv.lock"
135-
uv add $(PACKAGE)
111+
uvx uv add $(PACKAGE)
136112

137113
lock:
138114
@echo "-> Regenerate uv.lock from local wheels"
139115
uv lock
140116

117+
clean:
118+
@echo "-> Clean the Python env"
119+
rm -rf .venv/ .*_cache/ *.egg-info/ build/ dist/
120+
find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete
121+
122+
########################################################################################
123+
# Local venv commands (legacy)
124+
########################################################################################
125+
126+
VENV_LOCATION=.venv
127+
ACTIVATE?=. ${VENV_LOCATION}/bin/activate;
128+
#MANAGE=${VENV_LOCATION}/bin/python manage.py
129+
# Do not depend on Python to generate the SECRET_KEY
130+
GET_SECRET_KEY=`head -c50 /dev/urandom | base64 | head -c50`
131+
# Customize with `$ make envfile ENV_FILE=/etc/dejacode/.env`
132+
ENV_FILE=.env
133+
DOCKER_COMPOSE=docker compose -f docker-compose.yml
134+
DOCKER_EXEC=${DOCKER_COMPOSE} exec
135+
DB_NAME=dejacode_db
136+
DB_USERNAME=dejacode
137+
DB_PASSWORD=dejacode
138+
DB_CONTAINER_NAME=db
139+
DB_INIT_FILE=./data/postgresql/initdb.sql.gz
140+
POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8
141+
TIMESTAMP=$(shell date +"%Y-%m-%d_%H%M")
142+
143+
conf: virtualenv
144+
@echo "-> Install dependencies"
145+
uv sync --frozen
146+
@echo "-> Create the var/ directory"
147+
@mkdir -p var
148+
149+
dev: virtualenv
150+
@echo "-> Configure and install development dependencies"
151+
uv sync --frozen --extra dev
152+
141153
envfile:
142154
@echo "-> Create the .env file and generate a secret key"
143155
@if test -f ${ENV_FILE}; then echo "${ENV_FILE} file exists already"; exit 1; fi
@@ -148,15 +160,6 @@ envfile_dev: envfile
148160
@echo "-> Update the .env file for development"
149161
@echo DATABASE_PASSWORD=\"dejacode\" >> ${ENV_FILE}
150162

151-
check-deploy:
152-
@echo "-> Check Django deployment settings"
153-
${MANAGE} check --deploy
154-
155-
clean:
156-
@echo "-> Clean the Python env"
157-
rm -rf .venv/ .*_cache/ *.egg-info/ build/ dist/
158-
find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete
159-
160163
initdb:
161164
@echo "-> Stop Docker services that access the database"
162165
${DOCKER_COMPOSE} stop web worker
@@ -181,4 +184,4 @@ psql:
181184
log:
182185
${DOCKER_COMPOSE} logs --tail="100" ${SERVICE}
183186

184-
.PHONY: virtualenv conf dev lock upgrade envfile envfile_dev check outdated doc8 valid check-deploy clean initdb postgresdb postgresdb_clean migrate run test docs build psql bash shell log superuser
187+
.PHONY: virtualenv conf dev lock upgrade envfile envfile_dev check outdated doc8 valid clean initdb postgresdb postgresdb_clean migrate run test docs build psql bash shell log superuser

component_catalog/tests/test_importers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,10 +601,10 @@ def test_importers_view_num_queries_view(self):
601601
with self.assertMaxQueries(9):
602602
self.client.get(reverse("admin:component_catalog_package_import"))
603603

604-
with self.assertNumQueries(4):
604+
with self.assertMaxQueries(5):
605605
self.client.get(reverse("admin:organization_owner_import"))
606606

607-
with self.assertMaxQueries(10):
607+
with self.assertMaxQueries(11):
608608
self.client.get(reverse("admin:component_catalog_component_import"))
609609

610610
def test_component_import_keywords(self):

component_catalog/tests/test_views.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from dje.models import ExternalReference
5353
from dje.models import ExternalSource
5454
from dje.models import History
55+
from dje.tests import MaxQueryMixin
5556
from dje.tests import add_perm
5657
from dje.tests import add_perms
5758
from dje.tests import create_superuser
@@ -77,7 +78,7 @@
7778
User = get_user_model()
7879

7980

80-
class ComponentUserViewsTestCase(TestCase):
81+
class ComponentUserViewsTestCase(MaxQueryMixin, TestCase):
8182
def setUp(self):
8283
self.nexb_dataspace = Dataspace.objects.create(name="nexB")
8384
self.nexb_user = User.objects.create_superuser(
@@ -980,7 +981,7 @@ def test_component_catalog_details_view_num_queries(self):
980981
History.log_change(self.basic_user, self.component1, "Changed version.")
981982
History.log_change(self.nexb_user, self.component1, "Changed notes.")
982983

983-
with self.assertNumQueries(32):
984+
with self.assertMaxQueries(33):
984985
self.client.get(url)
985986

986987
def test_component_catalog_details_view_package_tab_fields_visibility(self):
@@ -1095,7 +1096,7 @@ def test_component_catalog_component_create_ajax_view(self):
10951096
self.assertContains(response, expected, html=True)
10961097

10971098

1098-
class PackageUserViewsTestCase(TestCase):
1099+
class PackageUserViewsTestCase(MaxQueryMixin, TestCase):
10991100
testfiles_location = join(dirname(__file__), "testfiles")
11001101

11011102
def setUp(self):
@@ -1133,7 +1134,7 @@ def setUp(self):
11331134

11341135
def test_package_list_view_num_queries(self):
11351136
self.client.login(username=self.super_user.username, password="secret")
1136-
with self.assertNumQueries(16):
1137+
with self.assertMaxQueries(17):
11371138
self.client.get(reverse("component_catalog:package_list"))
11381139

11391140
def test_package_list_view_pagination(self):
@@ -1271,7 +1272,7 @@ def test_package_details_view_num_queries(self):
12711272
)
12721273

12731274
self.client.login(username=self.super_user.username, password="secret")
1274-
with self.assertNumQueries(30):
1275+
with self.assertMaxQueries(31):
12751276
self.client.get(self.package1.get_absolute_url())
12761277

12771278
def test_package_details_view_content(self):
@@ -3797,7 +3798,7 @@ def test_component_catalog_package_update_view_save_as_with_collect_data(
37973798
self.assertEqual(1, len(mock_collect_data.mock_calls))
37983799

37993800

3800-
class ComponentListViewTestCase(TestCase):
3801+
class ComponentListViewTestCase(MaxQueryMixin, TestCase):
38013802
def setUp(self):
38023803
self.dataspace = Dataspace.objects.create(
38033804
name="nexB",
@@ -3887,7 +3888,7 @@ def setUp(self):
38873888

38883889
def test_component_catalog_list_view_num_queries(self):
38893890
self.client.login(username="nexb_user", password="t3st")
3890-
with self.assertNumQueries(17):
3891+
with self.assertMaxQueries(18):
38913892
self.client.get(reverse("component_catalog:component_list"))
38923893

38933894
def test_component_catalog_list_view_default(self):

license_library/tests/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from organization.models import Subowner
3232

3333

34-
class LicenseListViewTestCase(TestCase):
34+
class LicenseListViewTestCase(MaxQueryMixin, TestCase):
3535
def setUp(self):
3636
self.nexb_dataspace = Dataspace.objects.create(
3737
name="nexB",
@@ -286,7 +286,7 @@ def test_license_library_list_previous_next_license_link(self):
286286
def test_license_library_list_view_num_queries(self):
287287
self.client.login(username="nexb_user", password="t3st")
288288

289-
with self.assertNumQueries(16):
289+
with self.assertMaxQueries(17):
290290
self.client.get(reverse("license_library:license_list"))
291291

292292
def test_license_profile_column_availability_in_license_list_view(self):

organization/tests/test_views.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django.urls import reverse
1414

1515
from dje.models import Dataspace
16+
from dje.tests import MaxQueryMixin
1617
from dje.tests import add_perm
1718
from dje.tests import create_superuser
1819
from dje.tests import create_user
@@ -24,7 +25,7 @@
2425
Component = apps.get_model("component_catalog", "Component")
2526

2627

27-
class OwnerUserViewsTestCase(TestCase):
28+
class OwnerUserViewsTestCase(MaxQueryMixin, TestCase):
2829
def setUp(self):
2930
self.dataspace = Dataspace.objects.create(name="Dataspace")
3031
self.super_user = create_superuser("super_user", self.dataspace)
@@ -94,12 +95,12 @@ def test_object_details_view_tab_owner(self):
9495

9596
def test_owner_list_view_num_queries(self):
9697
self.client.login(username=self.super_user.username, password="secret")
97-
with self.assertNumQueries(13):
98+
with self.assertMaxQueries(14):
9899
self.client.get(reverse("organization:owner_list"))
99100

100101
def test_owner_details_view_num_queries(self):
101102
self.client.login(username=self.super_user.username, password="secret")
102-
with self.assertNumQueries(18):
103+
with self.assertMaxQueries(19):
103104
self.client.get(self.owner1.get_absolute_url())
104105

105106
def test_owner_list_view_search_unicode_utf8_name_support(self):

product_portfolio/tests/test_views.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def test_product_portfolio_detail_view_tab_inventory_and_hierarchy_availability(
136136
ProductComponent.objects.create(
137137
product=self.product1, component=self.component1, dataspace=self.dataspace
138138
)
139-
with self.assertNumQueries(27):
139+
with self.assertMaxQueries(28):
140140
response = self.client.get(url)
141141
self.assertContains(response, expected1)
142142
self.assertContains(response, expected2)
@@ -162,7 +162,7 @@ def test_product_portfolio_detail_view_tab_inventory_availability(self):
162162
ProductPackage.objects.create(
163163
product=self.product1, package=self.package1, dataspace=self.dataspace
164164
)
165-
with self.assertNumQueries(25):
165+
with self.assertMaxQueries(26):
166166
response = self.client.get(url)
167167
self.assertContains(response, expected)
168168

@@ -267,7 +267,7 @@ def test_product_portfolio_detail_view_tab_dependency_view(self):
267267
resolved_to_package=package2,
268268
)
269269

270-
with self.assertMaxQueries(9):
270+
with self.assertMaxQueries(10):
271271
response = self.client.get(url)
272272
self.assertContains(response, "4 results")
273273

@@ -289,7 +289,7 @@ def test_product_portfolio_detail_view_tab_vulnerability_queryset(self):
289289
self.assertEqual(4, product1.packages.vulnerable().count())
290290

291291
url = product1.get_url("tab_vulnerabilities")
292-
with self.assertMaxQueries(11):
292+
with self.assertMaxQueries(12):
293293
response = self.client.get(url)
294294
self.assertContains(response, "4 results")
295295

@@ -357,7 +357,7 @@ def test_product_portfolio_tab_vulnerability_view_queries(self):
357357
make_vulnerability_analysis(product_package2, vulnerability2)
358358

359359
url = product1.get_url("tab_vulnerabilities")
360-
with self.assertNumQueries(11):
360+
with self.assertMaxQueries(12):
361361
self.client.get(url)
362362

363363
def test_product_portfolio_tab_vulnerability_risk_threshold(self):

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ dependencies = [
5050
"django-guardian==3.3.1",
5151
"django-environ==0.13.0",
5252
"django-debug-toolbar==6.3.0",
53+
# Parallel testing
54+
"tblib==3.2.2",
5355
# CAPTCHA
5456
"altcha==1.0.0",
5557
"django_altcha==0.10.0",
@@ -96,7 +98,7 @@ dependencies = [
9698
"XlsxWriter==3.2.9",
9799
# Markdown
98100
"markdown==3.10.2",
99-
"bleach==6.3.0",
101+
"bleach==6.4.0",
100102
"bleach_allowlist==1.0.3",
101103
"webencodings==0.5.1",
102104
# Authentication
@@ -160,8 +162,6 @@ dependencies = [
160162
dev = [
161163
# Linter and Validation
162164
"ruff==0.15.14",
163-
# Parallel testing
164-
"tblib==3.2.2"
165165
]
166166

167167
[project.urls]

reporting/tests/test_views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from component_catalog.models import Component
2020
from dje.copier import copy_object
2121
from dje.models import Dataspace
22+
from dje.tests import MaxQueryMixin
2223
from license_library.models import License
2324
from license_library.models import LicenseCategory
2425
from organization.models import Owner
@@ -32,7 +33,7 @@
3233
from reporting.models import Report
3334

3435

35-
class ReportDetailsViewTestCase(TestCase):
36+
class ReportDetailsViewTestCase(MaxQueryMixin, TestCase):
3637
def setUp(self):
3738
self.dataspace = Dataspace.objects.create(name="nexB")
3839
self.owner = Owner.objects.create(dataspace=self.dataspace, name="My Fancy Owner Name")
@@ -1166,7 +1167,7 @@ def test_report_list_view_num_queries(self):
11661167
# Needed to clear the queries from the License batch creation in setUp
11671168
self.client.get(url)
11681169

1169-
with self.assertNumQueries(9):
1170+
with self.assertMaxQueries(10):
11701171
self.client.get(url)
11711172

11721173
def test_run_report_view_query_using_related_fields(self):

0 commit comments

Comments
 (0)