11# ADO pipeline for the msal Python test suite.
2- # Two stages → two GitHub checks (Unit tests, E2E tests). Each stage uses
3- # one job that loops the supported Python matrix internally so the GitHub
4- # check count stays small .
2+ # Two stages → two GitHub checks (Unit tests, E2E tests). Each stage is one
3+ # job that fans out the Python matrix as parallel bash background processes
4+ # so wall-clock time ≈ slowest single Python version, not the sum .
55
66trigger :
77 branches :
@@ -21,10 +21,10 @@ stages:
2121# Stage 1 · Unit tests — no Key Vault, no service connection.
2222# ─────────────────────────────────────────────────────────────────────────────
2323- stage : UnitTests
24- displayName : ' Unit tests '
24+ displayName : ' Unit test '
2525 jobs :
2626 - job : Pytest
27- displayName : ' pytest (unit, all Python versions) '
27+ displayName : ' Unit test '
2828 pool :
2929 vmImage : ubuntu-22.04
3030 timeoutInMinutes : 30
@@ -50,49 +50,71 @@ stages:
5050
5151 - bash : |
5252 set -uo pipefail
53- mkdir -p test-results
54- OVERALL_RC=0
53+ mkdir -p test-results logs
54+
55+ run_unit() {
56+ local V="$1"
57+ local LOG="logs/unit-${V}.log"
58+ {
59+ echo "=== Unit tests · Python ${V} (parallel) ==="
60+ local PY="python${V}"
61+ if ! command -v "$PY" >/dev/null; then
62+ echo "ERROR: $PY not on PATH"
63+ exit 1
64+ fi
65+ "$PY" -m venv ".venv-unit-${V}"
66+ # shellcheck disable=SC1090
67+ source ".venv-unit-${V}/bin/activate"
68+ python -m pip install --upgrade pip
69+ pip install -r requirements.txt
70+ pip install pytest pytest-azurepipelines
71+ pytest -vv \
72+ --junitxml="test-results/junit-unit-${V}.xml" \
73+ --ignore=tests/test_e2e.py \
74+ --ignore=tests/test_e2e_manual.py \
75+ --ignore=tests/test_fmi_e2e.py
76+ local RC=$?
77+ deactivate
78+ exit $RC
79+ } >"$LOG" 2>&1
80+ }
5581
56- for V in 3.9 3.10 3.11 3.12 3.13 3.14; do
57- echo "============================================================"
58- echo " Unit tests · Python ${V}"
59- echo "============================================================"
82+ VERSIONS=(3.9 3.10 3.11 3.12 3.13 3.14)
83+ PIDS=()
84+ for V in "${VERSIONS[@]}"; do
85+ run_unit "$V" &
86+ PIDS+=($!)
87+ done
6088
61- PY="python${V}"
62- if ! command -v "$PY" >/dev/null; then
63- echo "##vso[task.logissue type=error]${PY} not on PATH"
89+ OVERALL_RC=0
90+ declare -A RESULTS
91+ for i in "${!VERSIONS[@]}"; do
92+ V="${VERSIONS[$i]}"
93+ PID="${PIDS[$i]}"
94+ if wait "$PID"; then
95+ RESULTS[$V]="PASS"
96+ else
97+ RESULTS[$V]="FAIL (rc=$?)"
6498 OVERALL_RC=1
65- continue
6699 fi
100+ done
67101
68- "$PY" -m venv ".venv-${V}"
69- # shellcheck disable=SC1090
70- source ".venv-${V}/bin/activate"
71-
72- python -m pip install --upgrade pip
73- pip install -r requirements.txt
74- pip install pytest pytest-azurepipelines
75-
76- set -o pipefail
77- pytest -vv \
78- --junitxml="test-results/junit-unit-${V}.xml" \
79- --ignore=tests/test_e2e.py \
80- --ignore=tests/test_e2e_manual.py \
81- --ignore=tests/test_fmi_e2e.py \
82- 2>&1 | tee "test-results/pytest-unit-${V}.log"
83- RC=$?
84- set +o pipefail
85-
86- deactivate
102+ for V in "${VERSIONS[@]}"; do
103+ echo ""
104+ echo "############################################################"
105+ echo "# Unit tests · Python ${V} · ${RESULTS[$V]}"
106+ echo "############################################################"
107+ cat "logs/unit-${V}.log"
108+ done
87109
88- if [ $RC -ne 0 ]; then
89- echo "Python ${V}: unit pytest exited ${RC} "
90- OVERALL_RC=1
91- fi
110+ echo ""
111+ echo "=== Summary === "
112+ for V in "${VERSIONS[@]}"; do
113+ echo " Python ${V}: ${RESULTS[$V]}"
92114 done
93115
94116 exit $OVERALL_RC
95- displayName: 'Run pytest (unit, all Python versions)'
117+ displayName: 'Run pytest (unit, all Python versions in parallel )'
96118
97119 - task : PublishTestResults@2
98120 displayName : ' Publish JUnit test results'
@@ -115,7 +137,7 @@ stages:
115137 condition : succeeded()
116138 jobs :
117139 - job : Pytest
118- displayName : ' pytest ( E2E, all Python versions) '
140+ displayName : ' E2E tests '
119141 pool :
120142 vmImage : ubuntu-22.04
121143 timeoutInMinutes : 60
@@ -163,47 +185,69 @@ stages:
163185
164186 - bash : |
165187 set -uo pipefail
166- mkdir -p test-results
167- OVERALL_RC=0
188+ mkdir -p test-results logs
189+
190+ run_e2e() {
191+ local V="$1"
192+ local LOG="logs/e2e-${V}.log"
193+ {
194+ echo "=== E2E tests · Python ${V} (parallel) ==="
195+ local PY="python${V}"
196+ if ! command -v "$PY" >/dev/null; then
197+ echo "ERROR: $PY not on PATH"
198+ exit 1
199+ fi
200+ "$PY" -m venv ".venv-e2e-${V}"
201+ # shellcheck disable=SC1090
202+ source ".venv-e2e-${V}/bin/activate"
203+ python -m pip install --upgrade pip
204+ pip install -r requirements.txt
205+ pip install pytest pytest-azurepipelines
206+ pytest -vv \
207+ --junitxml="test-results/junit-e2e-${V}.xml" \
208+ tests/test_e2e.py tests/test_fmi_e2e.py
209+ local RC=$?
210+ deactivate
211+ exit $RC
212+ } >"$LOG" 2>&1
213+ }
168214
169- for V in 3.9 3.10 3.11 3.12 3.13 3.14; do
170- echo "============================================================"
171- echo " E2E tests · Python ${V}"
172- echo "============================================================"
215+ VERSIONS=(3.9 3.10 3.11 3.12 3.13 3.14)
216+ PIDS=()
217+ for V in "${VERSIONS[@]}"; do
218+ run_e2e "$V" &
219+ PIDS+=($!)
220+ done
173221
174- PY="python${V}"
175- if ! command -v "$PY" >/dev/null; then
176- echo "##vso[task.logissue type=error]${PY} not on PATH"
222+ OVERALL_RC=0
223+ declare -A RESULTS
224+ for i in "${!VERSIONS[@]}"; do
225+ V="${VERSIONS[$i]}"
226+ PID="${PIDS[$i]}"
227+ if wait "$PID"; then
228+ RESULTS[$V]="PASS"
229+ else
230+ RESULTS[$V]="FAIL (rc=$?)"
177231 OVERALL_RC=1
178- continue
179232 fi
233+ done
180234
181- "$PY" -m venv ".venv-${V}"
182- # shellcheck disable=SC1090
183- source ".venv-${V}/bin/activate"
184-
185- python -m pip install --upgrade pip
186- pip install -r requirements.txt
187- pip install pytest pytest-azurepipelines
188-
189- set -o pipefail
190- pytest -vv \
191- --junitxml="test-results/junit-e2e-${V}.xml" \
192- tests/test_e2e.py tests/test_fmi_e2e.py \
193- 2>&1 | tee "test-results/pytest-e2e-${V}.log"
194- RC=$?
195- set +o pipefail
196-
197- deactivate
235+ for V in "${VERSIONS[@]}"; do
236+ echo ""
237+ echo "############################################################"
238+ echo "# E2E tests · Python ${V} · ${RESULTS[$V]}"
239+ echo "############################################################"
240+ cat "logs/e2e-${V}.log"
241+ done
198242
199- if [ $RC -ne 0 ]; then
200- echo "Python ${V}: e2e pytest exited ${RC} "
201- OVERALL_RC=1
202- fi
243+ echo ""
244+ echo "=== Summary === "
245+ for V in "${VERSIONS[@]}"; do
246+ echo " Python ${V}: ${RESULTS[$V]}"
203247 done
204248
205249 exit $OVERALL_RC
206- displayName: 'Run pytest (E2E, all Python versions)'
250+ displayName: 'Run pytest (E2E, all Python versions in parallel )'
207251 env:
208252 LAB_APP_CLIENT_CERT_PFX_PATH: $(LAB_APP_CLIENT_CERT_PFX_PATH)
209253
0 commit comments