Skip to content

Commit 99a3046

Browse files
authored
ci: gate staging on extension tests (+ build-time caching) (#205)
* ci: gate staging build/deploy on extension test suite Re-adds the CKAN extension tests that died with the Jenkins pipeline, now as a 'test' job in build-deploy.yml that build/deploy depend on. Brings up the docker-compose dev stack (the same flow the old ci_setup.sh/ci_test.sh drove via adx) on pinned submodule commits and runs the 5 Fjelltopp suites: unaids, validation, scheming, dhis2harvester, emailasusername Gate semantics: - push: tests must pass before build and deploy run - redeploy (workflow_dispatch + image_tag): tests skipped, image already tested when built - test failure blocks both build and deploy Tests run against the dev image + bind-mounted submodules, matching local 'adx test'. Not yet verified green under CKAN 2.11/Py3.10. * ci: run tests on PRs + cache pipenv venv and React node_modules - Add pull_request trigger so the extension-test job runs as a visible status check; build/deploy stay gated off pull_request events. - Cache .adxvenv on Pipfile.lock — the dominant cost is bootstrap's 'pipenv sync --dev' (CKAN + ~19 extensions), not the image build. - Cache the unaids React node_modules on its yarn.lock. Both caches are safe on miss, so the first run is a clean cold signal. * ci: run all suites (no fail-fast) and save caches on failure - Run all 5 extension suites and aggregate, so one run reports the full picture instead of stopping at the first failing suite. - Split cache restore/save so .adxvenv and node_modules are saved even when tests fail, making triage runs warm instead of cold. * test: fix extension test suite under CKAN 2.11/Py3.10 - Add 'mock' and 'pyfakefs' to dev-packages: ckanext-validation, -scheming and -emailasusername import the standalone 'mock' package (and pyfakefs in validation), which weren't installed, causing pytest collection errors. Dev-only — prod uses 'pipenv sync' without --dev, so these don't ship. - run_tests: blank CKAN_SMTP_SERVER for test runs so suites never reach the dev stack's smtp4dev. Fixes ckanext-unaids' test_send_dataset_transfer_emails_errors, which asserts mail sending fails when no server is configured. * deps: pin frictionless==5.13.1 and pyfakefs==4.6.* to match extensions The validation/unaids extensions pin frictionless[ckan]==5.13.1 and pyfakefs==4.6.* in their own requirements and pass their own CI against those. Our merged Pipfile had frictionless>=5.0.0,<6.0.0 (drifted to a newer 5.x that dropped Resource.__create__) and pyfakefs=* (6.x dropped the CreateFile API), so ckanext-validation's test suite failed in our stack only. Aligning the pins fixes it without touching the submodule; frictionless is prod-facing but this matches the version the extensions are built and tested against.
1 parent 62a3ae8 commit 99a3046

4 files changed

Lines changed: 188 additions & 33 deletions

File tree

.github/workflows/build-deploy.yml

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: Build and Deploy CKAN (staging)
33
on:
44
push:
55
branches: [master, ckan211-prod-deploy-pr]
6+
pull_request:
7+
# Runs the test job only (build/deploy are gated off pull_request below),
8+
# so PRs get a visible test status check without deploying.
9+
branches: [master, ckan211-prod-deploy-pr]
610
workflow_dispatch:
711
inputs:
812
image_tag:
@@ -21,9 +25,124 @@ env:
2125
URL: https://dev.adr.fjelltopp.org
2226

2327
jobs:
24-
build:
28+
test:
29+
name: Extension tests
30+
# Skip on a pure redeploy (workflow_dispatch with an existing image_tag);
31+
# that image was already tested when it was built.
2532
if: github.event_name != 'workflow_dispatch' || inputs.image_tag == ''
2633
runs-on: ubuntu-latest
34+
timeout-minutes: 45
35+
env:
36+
# Fresh runner DB — no need to restart the db container during testsetup.
37+
SKIP_DB_RESTART: "True"
38+
steps:
39+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
40+
with:
41+
submodules: recursive
42+
43+
# The dominant cost is `pipenv sync --dev` at bootstrap (CKAN + ~19
44+
# extensions), which lands in the bind-mounted .adxvenv. Caching it on
45+
# Pipfile.lock makes warm runs a near no-op. .adxvenv is gitignored and
46+
# written by root in-container, so there are no ownership issues here.
47+
# Restore + save are split (rather than the combined cache action) so the
48+
# venv is saved even when the test step fails — otherwise the expensive
49+
# `pipenv sync` is repeated cold on every triage run. Saved at job end.
50+
- name: Restore Python venv
51+
id: venv-cache
52+
uses: actions/cache/restore@v4
53+
with:
54+
path: .adxvenv
55+
key: adxvenv-${{ runner.os }}-${{ hashFiles('Pipfile.lock') }}
56+
restore-keys: |
57+
adxvenv-${{ runner.os }}-
58+
59+
# Second cost: the entrypoint runs `yarn install` + build for the unaids
60+
# React app on every boot. Cache its node_modules on the React yarn.lock.
61+
- name: Restore unaids React node_modules
62+
id: react-cache
63+
uses: actions/cache/restore@v4
64+
with:
65+
path: submodules/ckanext-unaids/ckanext/unaids/react/node_modules
66+
key: react-nm-${{ runner.os }}-${{ hashFiles('submodules/ckanext-unaids/ckanext/unaids/react/yarn.lock') }}
67+
restore-keys: |
68+
react-nm-${{ runner.os }}-
69+
70+
- name: Prepare .env
71+
run: cp dev.env .env
72+
73+
- name: Build images and start the dev stack
74+
run: |
75+
./adx build
76+
./adx up
77+
78+
- name: Wait for CKAN bootstrap
79+
run: |
80+
for i in $(seq 1 90); do
81+
if docker logs ckan 2>&1 | grep -q 'CKAN bootstrapping finished, environment ready'; then
82+
echo "CKAN ready after ${i} checks"
83+
exit 0
84+
fi
85+
echo "Waiting for CKAN bootstrap (${i}/90)…"
86+
sleep 10
87+
done
88+
echo "::error::CKAN did not finish bootstrapping in time"
89+
docker logs ckan
90+
exit 1
91+
92+
- name: Create test databases
93+
run: ./adx testsetup
94+
95+
- name: Run extension tests
96+
run: |
97+
# Run every suite (don't fail-fast) so one run shows the full picture,
98+
# then fail at the end if any suite failed.
99+
failed=()
100+
for ext in unaids validation scheming dhis2harvester emailasusername; do
101+
echo "::group::ckanext-${ext}"
102+
if ./adx test "${ext}" --no-interaction; then
103+
echo "ckanext-${ext}: PASS"
104+
else
105+
echo "ckanext-${ext}: FAIL"
106+
failed+=("${ext}")
107+
fi
108+
echo "::endgroup::"
109+
done
110+
echo "--- summary ---"
111+
if [ ${#failed[@]} -ne 0 ]; then
112+
echo "::error::Failing suites: ${failed[*]}"
113+
exit 1
114+
fi
115+
echo "All extension suites passed"
116+
117+
- name: Dump CKAN logs on failure
118+
if: failure()
119+
run: docker logs ckan || true
120+
121+
- name: Save Python venv
122+
if: always() && steps.venv-cache.outputs.cache-hit != 'true'
123+
uses: actions/cache/save@v4
124+
with:
125+
path: .adxvenv
126+
key: ${{ steps.venv-cache.outputs.cache-primary-key }}
127+
128+
- name: Save unaids React node_modules
129+
if: always() && steps.react-cache.outputs.cache-hit != 'true'
130+
uses: actions/cache/save@v4
131+
with:
132+
path: submodules/ckanext-unaids/ckanext/unaids/react/node_modules
133+
key: ${{ steps.react-cache.outputs.cache-primary-key }}
134+
135+
build:
136+
needs: test
137+
# Run when tests pass (success) or were skipped (redeploy), and we're
138+
# actually building (not a redeploy of an existing tag).
139+
if: >-
140+
always()
141+
&& github.event_name != 'pull_request'
142+
&& needs.test.result != 'failure'
143+
&& needs.test.result != 'cancelled'
144+
&& (github.event_name != 'workflow_dispatch' || inputs.image_tag == '')
145+
runs-on: ubuntu-latest
27146
outputs:
28147
image_tag: ${{ steps.meta.outputs.version }}
29148
steps:
@@ -61,8 +180,15 @@ jobs:
61180
labels: ${{ steps.meta.outputs.labels }}
62181

63182
deploy:
64-
needs: build
65-
if: always() && (needs.build.result == 'success' || needs.build.result == 'skipped')
183+
needs: [test, build]
184+
# Never deploy if tests failed/cancelled. Otherwise deploy when the image
185+
# was built (success) or already exists (build skipped on redeploy).
186+
if: >-
187+
always()
188+
&& github.event_name != 'pull_request'
189+
&& needs.test.result != 'failure'
190+
&& needs.test.result != 'cancelled'
191+
&& (needs.build.result == 'success' || needs.build.result == 'skipped')
66192
runs-on: ubuntu-latest
67193
environment:
68194
name: staging

Pipfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ python-jose = "==3.3.0"
9292
setuptools = ">=65.0.0,<80.0.0"
9393
raven = "==6.10.0"
9494
tableschema = "==1.20.2"
95-
frictionless = ">=5.0.0,<6.0.0"
95+
# Pinned to match ckanext-validation/-unaids requirements.txt (the version
96+
# those extensions are built and tested against). Newer 5.x dropped
97+
# frictionless.Resource.__create__, which their test suites still patch.
98+
frictionless = "==5.13.1"
9699
markupsafe = "==2.1.5"
97100

98101
[dev-packages]
@@ -120,6 +123,10 @@ pytest-rerunfailures = "==15.0"
120123
pytest-split = "==0.10.0"
121124
pytest-retry = "==1.7.0"
122125
pytest-mock = "*"
126+
mock = "*"
127+
# Pinned to match ckanext-validation dev-requirements.txt; pyfakefs 5.0+
128+
# removed the CamelCase API (CreateFile) its TestFiles suite uses.
129+
pyfakefs = "==4.6.*"
123130
coverage = "==7.7.1"
124131
junitparser = "==3.2.0"
125132
junit2html = "==31.0.2"

Pipfile.lock

Lines changed: 46 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

util/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,12 @@ def run_tests(args, extra):
162162
# for cases like ckanext-ytp-requests repository which uses ytp_requests test directory internally
163163
extension_sub_path = extension_sub_path.replace("-", "_")
164164
retcode = call_command([
165+
# Blank out the SMTP server for test runs so tests never reach the
166+
# dev stack's smtp4dev and send real mail. Some suites (e.g.
167+
# ckanext-unaids' send_dataset_transfer_emails error path) assert that
168+
# sending fails when no server is configured.
165169
f'docker exec {args.interaction} -e CKAN_SQLALCHEMY_URL={CKAN_TEST_SQLALCHEMY_URL} '
170+
f'-e CKAN_SMTP_SERVER= '
166171
f'ckan /usr/local/bin/ckan-pytest --capture=no --disable-warnings '
167172
f'--ckan-ini={extension_path}/test.ini '
168173
f'{extension_path}/{extension_sub_path}/tests '

0 commit comments

Comments
 (0)