Skip to content

Commit ca7ac29

Browse files
committed
adding re-testing python script in case a test failed - renaming docker images
1 parent 26b5838 commit ca7ac29

4 files changed

Lines changed: 267 additions & 103 deletions

File tree

.github/workflows/docker.yml

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -70,67 +70,62 @@ jobs:
7070
with:
7171
images: ${{ needs.discover.outputs.image }}
7272
tags: |
73-
type=raw,value=gemc-${{ matrix.tag }}-${{ matrix.os }}
74-
type=raw,value=gemc-latest-${{ matrix.os }},enable=${{ matrix.latest }}
73+
type=raw,value=${{ matrix.tag }}-${{ matrix.os }}
74+
type=raw,value=latest-${{ matrix.os }},enable=${{ matrix.latest }}
7575
labels: |
7676
org.opencontainers.image.source=${{ github.repository }}
7777
org.opencontainers.image.description=GEMC image (${{ matrix.tag }} on ${{ matrix.os }})
7878
79-
# Build on PRs; Build+Push on main/tags; publish multi-arch manifest to GHCR
80-
- name: Build (PR) or Build+Push (main/tags)
81-
uses: docker/build-push-action@v6
82-
with:
83-
context: .
84-
file: ${{ matrix.file }}
85-
platforms: linux/amd64,linux/arm64 # ← multi-arch; change to linux/amd64 if you prefer single-arch
86-
push: ${{ github.event_name != 'pull_request' }}
87-
tags: ${{ steps.meta.outputs.tags }}
88-
labels: ${{ steps.meta.outputs.labels }}
89-
cache-from: type=gha
90-
cache-to: type=gha,mode=max
79+
# Decide platforms per Dockerfile by inspecting its base image
80+
- name: Decide platforms from Dockerfile base
81+
id: plats
82+
shell: bash
83+
run: |
84+
set -euo pipefail
85+
BASE="$(awk 'toupper($1)=="FROM"{print $2; exit}' "${{ matrix.file }}")"
86+
echo "Base image: $BASE"
87+
if docker buildx imagetools inspect "$BASE" | grep -qE 'manifest\.list|image\.index'; then
88+
PLATS="linux/amd64,linux/arm64"
89+
else
90+
PLATS="linux/amd64"
91+
fi
92+
echo "platforms=$PLATS" >> "$GITHUB_OUTPUT"
9193
92-
# Build for PRs: single-arch, load into local Docker so we can copy logs out
93-
- name: Build (PR) [load locally]
94-
if: ${{ github.event_name == 'pull_request' }}
94+
# Single build step for PRs AND main/tags
95+
- name: Build (PR loads; main/tags push)
9596
uses: docker/build-push-action@v6
9697
with:
9798
context: .
9899
file: ${{ matrix.file }}
99-
platforms: linux/amd64 # load:true only supports single arch
100-
load: true # <-- makes the image available locally
101-
push: false
102-
tags: ${{ steps.meta.outputs.tags }}
100+
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || steps.plats.outputs.platforms }}
101+
load: ${{ github.event_name == 'pull_request' }} # PR: load locally for log extraction
102+
push: ${{ github.event_name != 'pull_request' }} # main/tags: push to GHCR
103+
tags: ${{ steps.meta.outputs.tags }} # e.g., ghcr.io/gemc/gemc:dev3-ubuntu24
103104
labels: ${{ steps.meta.outputs.labels }}
104105
cache-from: type=gha
105106
cache-to: type=gha,mode=max
106107

107-
# Build & push for main/tags: multi-arch & publish to GHCR
108-
- name: Build & Push (main/tags)
109-
if: ${{ github.event_name != 'pull_request' }}
110-
uses: docker/build-push-action@v6
111-
with:
112-
context: .
113-
file: ${{ matrix.file }}
114-
platforms: linux/amd64,linux/arm64
115-
push: true
116-
tags: ${{ steps.meta.outputs.tags }}
117-
labels: ${{ steps.meta.outputs.labels }}
118-
cache-from: type=gha
119-
cache-to: type=gha,mode=max
120108

121-
# Extract logs from the built image (local on PRs, pull on main/tags)
109+
110+
# Extract logs from the built image (use new tag names)
122111
- name: Extract /root/src/logs from image
123112
if: ${{ always() }}
124113
env:
125-
IMG: ${{ needs.discover.outputs.image }}:gemc-${{ matrix.tag }}-${{ matrix.os }}
114+
IMG_PRIMARY: ${{ needs.discover.outputs.image }}:${{ matrix.tag }}-${{ matrix.os }}
115+
IMG_LATEST: ${{ needs.discover.outputs.image }}:latest-${{ matrix.os }}
126116
run: |
127117
set -euo pipefail
128118
mkdir -p ci-logs
129119
130-
# If image isn't local (e.g., main build), try pulling it
120+
# Prefer the primary tag; fall back to latest-<os> if needed
121+
IMG="$IMG_PRIMARY"
131122
if ! docker image inspect "$IMG" >/dev/null 2>&1; then
132-
echo "Image not local; attempting docker pull (PRs will skip)…"
133-
docker pull "$IMG" || true
123+
# Not local? try pull (main/tags). If still missing, try the latest alias.
124+
docker pull "$IMG" >/dev/null 2>&1 || true
125+
if ! docker image inspect "$IMG" >/dev/null 2>&1; then
126+
IMG="$IMG_LATEST"
127+
docker pull "$IMG" >/dev/null 2>&1 || true
128+
fi
134129
fi
135130
136131
if docker image inspect "$IMG" >/dev/null 2>&1; then
@@ -145,6 +140,7 @@ jobs:
145140
146141
ls -l ci-logs || true
147142
143+
148144
- name: Upload build logs artifact
149145
if: ${{ always() }}
150146
uses: actions/upload-artifact@v4

ci/build.sh

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,22 @@ fi
7878

7979
echo " > $GEMC recursive content:" >> $compile_log
8080
ls -lR $GEMC >> $compile_log
81+
cd ..
8182

8283
# if $1 is NOT one of sanitize option, run meson test
8384
if [[ $1 != @(address|thread|undefined|memory|leak) ]]; then
84-
echo " > Running meson test" > $test_log
85-
meson test -j 1 --print-errorlogs --no-rebuild >> $test_log
86-
if [ $? -ne 0 ]; then
87-
echo "Test failed. Log: "
88-
cat $test_log
89-
exit 1
90-
else
91-
echo Install Successful
92-
echo ; echo
93-
fi
85+
echo " > Running meson test" > $test_log
86+
# meson test -j 1 --print-errorlogs --no-rebuild --num-processes 1 >> $test_log
87+
./ci/meson_rerun_failed.py build 2 1 first-pass
88+
if [ $? -ne 0 ]; then
89+
echo "Test failed. Log: "
90+
cat $test_log
91+
echo Test Failure
92+
exit 1
93+
else
94+
echo Install Successful
95+
echo ; echo
96+
fi
9497
fi
9598

9699
echo

ci/meson_rerun_failed.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python3
2+
import json, os, shutil, subprocess, sys, glob
3+
4+
build = sys.argv[1] if len(sys.argv) > 1 else 'build'
5+
retries = int(sys.argv[2]) if len(sys.argv) > 2 else 2
6+
np = sys.argv[3] if len(sys.argv) > 3 else '1'
7+
logbase = sys.argv[4] if len(sys.argv) > 4 else 'first-pass' # set to 'meson-logs' to match your current output
8+
9+
logdir = os.path.join(build, 'meson-logs')
10+
json_path = os.path.join(logdir, f'{logbase}.json')
11+
txt_path = os.path.join(logdir, f'{logbase}.txt')
12+
13+
def run_all():
14+
# clean old logs for this logbase to avoid concatenation issues
15+
for p in (json_path, txt_path):
16+
try: os.remove(p)
17+
except FileNotFoundError: pass
18+
subprocess.check_call([
19+
'meson','test','-C',build,'--no-rebuild','-v',
20+
'--num-processes', np, '--print-errorlogs',
21+
'--logbase', logbase
22+
])
23+
24+
def find_json():
25+
if os.path.exists(json_path):
26+
return json_path
27+
# Fallback: try to find any single *.json produced by this run
28+
candidates = sorted(glob.glob(os.path.join(logdir, '*.json')))
29+
if len(candidates) == 1:
30+
return candidates[0]
31+
raise FileNotFoundError(
32+
f"Could not find {json_path}. Available JSONs: {candidates}"
33+
)
34+
35+
def load_concat_json(path):
36+
# Handle cases where Meson (or plugins) produce concatenated JSON docs
37+
with open(path, 'r', encoding='utf-8') as f:
38+
s = f.read().strip()
39+
dec = json.JSONDecoder()
40+
objs = []
41+
while s:
42+
s = s.lstrip()
43+
if not s: break
44+
obj, n = dec.raw_decode(s)
45+
objs.append(obj)
46+
s = s[n:]
47+
return objs[-1] if objs else {}
48+
49+
def failed_names(report):
50+
return [t['name'] for t in report.get('tests', []) if t.get('result') != 'OK']
51+
52+
# 1) First pass
53+
run_all()
54+
55+
# 2) Read failures from the produced JSON
56+
json_found = find_json()
57+
backup = os.path.join(logdir, f'{logbase}.first.json')
58+
shutil.copy2(json_found, backup)
59+
report = load_concat_json(backup)
60+
fails = failed_names(report)
61+
62+
# 3) Rerun failures serially, up to N retries
63+
attempt = 0
64+
while fails and attempt < retries:
65+
print(f"Re-running failed tests (round {attempt+1}): {fails}")
66+
for t in fails:
67+
subprocess.call([
68+
'meson','test','-C',build,'--no-rebuild','-v',
69+
'--num-processes','1','--print-errorlogs', t
70+
])
71+
# Optionally, re-check current failures by re-reading the latest JSON
72+
try:
73+
report = load_concat_json(find_json())
74+
fails = failed_names(report)
75+
except Exception:
76+
# If no fresh JSON is present, leave 'fails' as-is for the next round
77+
pass
78+
attempt += 1
79+
80+
if fails:
81+
print("Still failing:", fails)
82+
sys.exit(1)

0 commit comments

Comments
 (0)