Repository navigation
254 lines (229 loc) · 11.1 KB
/
Copy pathpython-ci.yml
File metadata and controls
254 lines (229 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
name: Delphi Python Tests
concurrency:
group: python-ci-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [ edge, stable ]
paths: &paths
- 'delphi/**/*.py'
- 'delphi/requirements*.txt'
- 'delphi/Dockerfile'
- 'scripts/*install.sh'
- 'scripts/application_stop.sh'
- 'scripts/validate_service.sh'
- 'scripts/test-deploy-hooks.sh'
- 'docker-compose*.yml'
- '.github/workflows/python-ci.yml'
- 'ci/test-migrations.sh'
- 'docker-compose.test.yml'
- 'server/Dockerfile-db'
- 'server/postgres/**'
- 'queue-rs/polis-migrate/**'
# server/src so a new server-side wildcard runs the projection-gate sweep
- 'server/src/**'
# Representative payload tests execute the probe planner and independent gate.
- 'ci/private_cert/**'
- 'ci/probe_box/**'
pull_request:
branches:
- edge
- stable
- 'jc/**'
paths: *paths
# Manual trigger: lets repo write-access users run CI on any branch
# (including spr/edge/* stack branches, which `pull_request` no longer
# auto-triggers for). UI: Actions → Delphi Python Tests → "Run workflow".
# CLI: `gh workflow run "Delphi Python Tests" --ref <branch>`.
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: 1. Free up disk space on runner
run: |
echo "Starting disk cleanup..."
df -h
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell
echo "Disk space after cleanup:"
df -h
- name: 2. Create .env file for CI
run: |
if [ -f "test.env" ]; then
cp test.env .env
echo "Using test.env for CI."
else
echo "::error::test.env file not found!"
exit 1
fi
# Add hostnames for container-to-container networking
# The 'delphi' service in 'docker-compose.test.yml' reads these
echo "POSTGRES_HOST=postgres" >> .env
echo "DYNAMODB_ENDPOINT=http://dynamodb:8000" >> .env
echo "AWS_S3_ENDPOINT=http://minio:9000" >> .env
# BuildKit is required by `RUN --mount=type=cache` in the npm Dockerfiles.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 3. Build Docker images
run: |
# Build all services in the test file (including delphi)
docker compose -f docker-compose.test.yml --env-file .env build
# Apply/check before starting application services on this volume.
bash ci/test-migrations.sh
# The 'delphi' container will start and run 'tail -f /dev/null'
docker compose -f docker-compose.test.yml --env-file .env up -d
echo "Waiting for services to be healthy..."
sleep 20 # Wait for containers to start
docker compose -f docker-compose.test.yml ps
- name: 5. Check service health
run: |
# Wait for postgres to be ready
echo "Checking postgres..."
docker compose -f docker-compose.test.yml exec -T postgres \
bash -c 'until pg_isready -U $POSTGRES_USER; do sleep 5; done'
echo "Postgres is ready."
# The opt-in Postgres integration tests (require_polis_postgres) run here
# against the compose `postgres` service, whose image bakes the polis
# migrations through polis-migrate during initialization and the explicit CI step,
# rule are already applied. The pytest step exports POLIS_TEST_POSTGRES_URL;
# the cold-start generator under test is already baked into the delphi image
# (Dockerfile `COPY scripts/ ./scripts/`), built from this checkout.
- name: 6. Run Delphi Pytest
run: |
echo "Copying test files into container..."
docker compose -f docker-compose.test.yml cp delphi/tests delphi:/app/tests
docker compose -f docker-compose.test.yml cp delphi/real_data delphi:/app/real_data
echo "Copying coverage script into container..."
docker compose -f docker-compose.test.yml cp delphi/generate_coverage_md.py delphi:/app/generate_coverage_md.py
echo "Copying script to be tested into container..."
docker compose -f docker-compose.test.yml cp delphi/polismath/run_math_pipeline.py delphi:/app/run_math_pipeline.py
docker compose -f docker-compose.test.yml cp delphi/umap_narrative delphi:/app/umap_narrative
# The same container-layout preparation and hook tests run on mm5.
bash scripts/test-deploy-hooks.sh
# Wire a checkout-shaped root so delphi/tests/scripts collects AND RUNS the
# pure-Python inventory sweep + the DB-free gate unit tests (the conftest
# locator resolves POLIS_CHECKOUT_DIR). The scan inputs are copied in as REAL,
# traversable directories (NOT symlinks — os.walk would skip those). The set
# of inputs is DERIVED from the scanner's own declaration
# (`--print-scan-inputs`) so it can never diverge from what the sweep scans;
# a test asserts every declared input is present under /app/projgate.
echo "Wiring projection-gate scan inputs (derived from the scanner declaration)..."
docker compose -f docker-compose.test.yml exec -T delphi mkdir -p /app/projgate
# Iterate a CAPTURED list, not `... | while read`: a `docker compose exec`
# inside a while-read loop drains the piped stdin and truncates the list to
# its first item (observed: only server/src copied).
# Recordings tests also load the packer and its digest helper from ci/.
# battery_coverage.py is already included in the declared delphi/scripts.
# Representative payload tests import the box planner/gate and their
# helpers from this same checkout root; keep their relative paths intact.
# Light-shadow triage tests also import the compare/triage modules.
for rel in $(python3 delphi/scripts/projection_inventory.py --print-scan-inputs) \
ci/p022_recordings_manifest.py ci/p022_battery_digest.py \
ci/private_cert/images/gate.py ci/private_cert/images/probe.py \
ci/private_cert/images/attribution.py ci/private_cert/images/diagnostic_projection.py ci/private_cert/images/selection_context.py \
ci/private_cert/images/g12.py ci/private_cert/images/near_ties.py ci/private_cert/control.py \
ci/private_cert/image_admission.py ci/probe_box/receipt.py \
ci/probe_box/contracts.py ci/probe_box/light_shadow.py ci/probe_box/light_shadow_queries.py \
ci/private_cert/images/light_shadow_compare.py ci/private_cert/images/light_shadow_triage.py \
ci/private_cert/images/recipe.py; do
docker compose -f docker-compose.test.yml exec -T delphi mkdir -p "/app/projgate/$(dirname "$rel")"
docker compose -f docker-compose.test.yml cp "$rel" "delphi:/app/projgate/$rel" \
|| { echo "failed to copy scan input: $rel"; exit 1; }
done
echo "Running tests and generating coverage report..."
docker compose \
-f docker-compose.test.yml \
--env-file .env \
exec -T \
-e AWS_DEFAULT_REGION=us-east-1 \
-e AWS_REGION=us-east-1 \
-e AWS_ACCESS_KEY_ID=dummy \
-e AWS_SECRET_ACCESS_KEY=dummy \
-e SKIP_GOLDEN=1 \
-e POSTGRES_CONNECT_TIMEOUT=5 \
-e POLIS_CHECKOUT_DIR=/app/projgate \
delphi \
bash -c " \
set -e; \
echo '--- Setting up DynamoDB Tables ---'; \
python create_dynamodb_tables.py --region us-east-1; \
echo '--- Running Pytest ---'; \
export PYTHONPATH=\$PYTHONPATH:/app; \
export POLIS_TEST_POSTGRES_URL=\"postgresql://\$DATABASE_USER:\$DATABASE_PASSWORD@\$DATABASE_HOST/\$DATABASE_NAME\"; \
pytest --cov=polismath --cov=run_math_pipeline --cov=./umap_narrative --cov-report=xml:/app/coverage.xml /app/tests --ignore=/app/tests/test_pakistan_conversation.py
echo '--- Generating Coverage Comment Text ---'; \
python /app/generate_coverage_md.py > /app/coverage-comment.md \
"
- name: 7. Copy coverage report from container
if: success()
run: |
echo "Copying coverage-comment.md from delphi container..."
docker compose -f docker-compose.test.yml cp delphi:/app/coverage-comment.md .
echo "=== Coverage Report ==="
cat coverage-comment.md
- name: 8. Upload Coverage Report
if: success()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage-comment.md
- name: 9. Post Coverage Comment
if: success() && github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const commentBody = fs.readFileSync('coverage-comment.md', 'utf8');
const marker = '<!-- delphi-coverage-report -->';
const body = `${marker}\n## Delphi Coverage Report\n\n${commentBody}`;
try {
// Find existing coverage comment
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const existingComment = comments.find(c => c.body.includes(marker));
if (existingComment) {
// Delete existing comment so new one appears after latest commit
await github.rest.issues.deleteComment({
comment_id: existingComment.id,
owner: context.repo.owner,
repo: context.repo.repo,
});
console.log('Existing coverage comment deleted.');
}
// Always create a new comment
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
console.log('Coverage comment posted successfully.');
} catch (error) {
if (error.status === 403) {
console.log('Note: Could not post coverage comment to PR.');
console.log('This is expected for PRs from forks due to GitHub token permissions.');
console.log('Coverage report is available in the workflow logs (step 7) and as a downloadable artifact (step 8).');
} else {
console.log(`Unexpected error posting comment: ${error.message}`);
}
}
- name: 10. Show service logs on failure
if: failure()
run: |
echo "=== Delphi service logs ==="
docker compose -f docker-compose.test.yml logs delphi || true
echo "=== Postgres service logs ==="
docker compose -f docker-compose.test.yml logs postgres || true
echo "=== DynamoDB service logs ==="
docker compose -f docker-compose.test.yml logs dynamodb || true
- name: 11. Clean up services
if: always()
run: |
echo "Cleaning up services..."
docker compose -f docker-compose.test.yml down -v