diff --git a/.gitea/workflows/example-cucumber-tests.yml b/.gitea/workflows/example-cucumber-tests.yml index 9f467ae..a9ab2d6 100644 --- a/.gitea/workflows/example-cucumber-tests.yml +++ b/.gitea/workflows/example-cucumber-tests.yml @@ -8,7 +8,7 @@ on: cucumber-node-image: required: false type: string - default: gitea.app.keskikuja.site/niko/ci-cucumber:latest + default: gitea.app.keskikuja.site/niko/ci-cucumber:with-python secrets: GITEA_TOKEN: required: true diff --git a/Dockerfile.ci-cucumber b/Dockerfile.ci-cucumber index 1e5f974..a3ef79e 100644 --- a/Dockerfile.ci-cucumber +++ b/Dockerfile.ci-cucumber @@ -1,6 +1,6 @@ FROM node:22 RUN apt-get update -qq && \ - apt-get install -y -qq --no-install-recommends lsof jq && \ + apt-get install -y -qq --no-install-recommends lsof jq python3 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ npm install -g @cucumber/cucumber diff --git a/scripts/ci-report.sh b/scripts/ci-report.sh index e509ba6..4299283 100644 --- a/scripts/ci-report.sh +++ b/scripts/ci-report.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu DESCRIPTION="${1:-}" CONTEXT="${2:-}" @@ -14,53 +14,71 @@ REPORT_DIR="reports/${SUITE}" if [ ! -d "$REPORT_DIR" ]; then echo "ERROR: $REPORT_DIR not found" >&2 - bash .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT" + sh .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT" exit 1 fi -FILES=() -while IFS= read -r -d '' f; do - FILES+=("$(basename "$f")") -done < <(find "$REPORT_DIR" -maxdepth 1 -type f ! -name index.html -print0 2>/dev/null || true) +FILE_COUNT=0 +SUBDIR_COUNT=0 +ENTRIES="" -SUBDIRS=() -while IFS= read -r -d '' d; do - name="${d#$REPORT_DIR/}" - [ -f "$d/index.html" ] && SUBDIRS+=("$name") -done < <(find "$REPORT_DIR" -maxdepth 1 -type d ! -name . -print0 2>/dev/null || true) +for f in "$REPORT_DIR"/*; do + [ -f "$f" ] || continue + base=$(basename "$f") + [ "$base" = "index.html" ] && continue + FILE_COUNT=$((FILE_COUNT + 1)) + ENTRIES="${ENTRIES}file:${base} +" +done -TOTAL=$(( ${#FILES[@]} + ${#SUBDIRS[@]} )) +for d in "$REPORT_DIR"/*/; do + [ -d "$d" ] || continue + base=$(basename "$d") + [ -f "$d/index.html" ] || continue + SUBDIR_COUNT=$((SUBDIR_COUNT + 1)) + ENTRIES="${ENTRIES}dir:${base} +" +done + +TOTAL=$((FILE_COUNT + SUBDIR_COUNT)) if [ "$TOTAL" -eq 0 ]; then echo "ERROR: no reportable items in $REPORT_DIR" >&2 - bash .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT" + sh .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT" exit 1 fi -SHA8="${GITHUB_SHA:0:8}" +SHA8=$(echo "${GITHUB_SHA:-xxxxxxxx}" | cut -c1-8) humanize() { - local name="$1" - name="${name%.*}" - name="${name//-/ }" - name="${name//_/ }" - echo "${name^}" + name="$1" + name=$(echo "$name" | sed -e 's/\.[^.]*$//' -e 's/[-_]/ /g') + first=$(echo "$name" | cut -c1 | tr '[:lower:]' '[:upper:]') + rest=$(echo "$name" | cut -c2-) + echo "${first}${rest}" } generate_index() { - local html - html='' - html+="$DESCRIPTION" - html+='' - html+="

$DESCRIPTION

' - printf '%s' "$html" > "$REPORT_DIR/index.html" + { + echo '' + echo "$DESCRIPTION" + echo '' + echo "

$DESCRIPTION

' + } > "$REPORT_DIR/index.html" } STAGED="reports/${SHA8}/${SUITE}" @@ -68,20 +86,25 @@ mkdir -p "$STAGED" if [ "$TOTAL" -eq 1 ]; then cp -a "$REPORT_DIR/." "$STAGED/" - bash .ci/scripts/publish-git-pages.sh "$SUITE" + sh .ci/scripts/publish-git-pages.sh "$SUITE" - if [ ${#FILES[@]} -eq 1 ]; then - ENTRY="${FILES[0]}" + first_entry=$(echo "$ENTRIES" | head -1) + first_type=$(echo "$first_entry" | cut -d: -f1) + first_name=$(echo "$first_entry" | cut -d: -f2-) + + if [ "$first_type" = "file" ]; then + SINGLE_ENTRY="$first_name" else - ENTRY="${SUBDIRS[0]}/index.html" + SINGLE_ENTRY="${first_name}/index.html" fi - URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${ENTRY}" - bash .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL" + + URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}" + sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL" else generate_index cp -a "$REPORT_DIR/." "$STAGED/" - bash .ci/scripts/publish-git-pages.sh "$SUITE" - bash .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "$SUITE" + sh .ci/scripts/publish-git-pages.sh "$SUITE" + sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "$SUITE" fi rm -rf "$STAGED" diff --git a/scripts/publish-git-pages.sh b/scripts/publish-git-pages.sh index acee12c..4575bc1 100755 --- a/scripts/publish-git-pages.sh +++ b/scripts/publish-git-pages.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env sh +set -eu SUITE_PATH="${1:-}" @@ -12,7 +12,7 @@ SUITE_PATH="${1:-}" OWNER="${GITHUB_REPOSITORY%%/*}" REPO="${GITHUB_REPOSITORY##*/}" -SHA8="${GITHUB_SHA:0:8}" +SHA8=$(echo "$GITHUB_SHA" | cut -c1-8) PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}" REPORT_DIR="reports/${SHA8}/${SUITE_PATH%/}" REPORT_BASE="${GIT_PAGES_URL}/${OWNER}/${REPO}/reports/${SHA8}" @@ -33,17 +33,30 @@ else fi mkdir -p "$TARGET" cp -a "$REPORT_DIR/." "$TARGET/" -if [ ! -f "$TARGET/index.html" ]; then - items=() - while IFS= read -r -d '' f; do - items+=("$(basename "$f")") - done < <(find "$TARGET" -maxdepth 1 -type f ! -name index.html -print0 2>/dev/null || true) - while IFS= read -r -d '' d; do - name=$(basename "$d") - [ -f "$d/index.html" ] && items+=("$name") - done < <(find "$TARGET" -maxdepth 1 -type d ! -name . -print0 2>/dev/null || true) - if [ ${#items[@]} -gt 1 ]; then +if [ ! -f "$TARGET/index.html" ]; then + ITEM_LIST="" + ITEM_COUNT=0 + + for f in "$TARGET"/*; do + [ -f "$f" ] || continue + base=$(basename "$f") + [ "$base" = "index.html" ] && continue + ITEM_LIST="${ITEM_LIST}file:${base} +" + ITEM_COUNT=$((ITEM_COUNT + 1)) + done + + for d in "$TARGET"/*/; do + [ -d "$d" ] || continue + base=$(basename "$d") + [ -f "$d/index.html" ] || continue + ITEM_LIST="${ITEM_LIST}dir:${base} +" + ITEM_COUNT=$((ITEM_COUNT + 1)) + done + + if [ "$ITEM_COUNT" -gt 1 ]; then { echo '' echo "Test report ${SHA8}" @@ -53,16 +66,21 @@ if [ ! -f "$TARGET/index.html" ]; then echo 'a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}' echo '' echo "

Test report ${SHA8}

' } > "$TARGET/index.html" fi @@ -74,7 +92,7 @@ EOF find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T - publish() { - local method="$1" + method="$1" curl -sS -X "$method" "$PUBLISH_SITE_URL" \ -u "${PAGES_USER}:${GIT_PAGES_PUBLISH_TOKEN}" \ -H "Content-Type: application/x-tar" \ diff --git a/scripts/report-status.sh b/scripts/report-status.sh index 5d7f8c9..6c319ac 100755 --- a/scripts/report-status.sh +++ b/scripts/report-status.sh @@ -1,11 +1,10 @@ -#!/usr/bin/env bash -set -euo pipefail - -# https://docs.gitea.com/api/next/#tag/repository/operation/repoCreateStatus +#!/usr/bin/env sh +set -eu STATE="${1:-}" DESCRIPTION="${2:-}" -KEY="${3:-commit-${GITHUB_SHA:0:8}}" +SHA8=$(echo "${GITHUB_SHA:-}" | cut -c1-8) +KEY="${3:-commit-${SHA8}}" SUITE="${4:-}" CUSTOM_URL="${5:-}" @@ -18,7 +17,8 @@ if [ -n "$CUSTOM_URL" ]; then URL="$CUSTOM_URL" elif [ -n "$SUITE" ]; then SUITE="${SUITE%/}/" - URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/${SUITE}" + SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8) + URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8_CUT}/${SUITE}" else URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" fi diff --git a/skills/ci-container-build/SKILL.md b/skills/ci-container-build/SKILL.md index 4e9c659..04d5db8 100644 --- a/skills/ci-container-build/SKILL.md +++ b/skills/ci-container-build/SKILL.md @@ -32,6 +32,31 @@ Kun kontti on pushattu registryyn, se on muiden pipeline-jobien käytettävissä `latest`-tägillä — rebuild = käyttöönotto. Mitään versioviittauksia ei tarvitse päivittää. +## Offline-periaate (DoD) + +CI-kontin **Definition of Done**: + +> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä kontin +> runtime-prosessissa (`container:` / `docker run`). Kaikki riippuvuudet +> (kielikohtaiset paketit, työkalut, binäärit) on joko: +> - Pre-cachattu kontin **build-vaiheessa** Dockerfilessä, TAI +> - Kopioitu multi-stage buildilla toisesta imagesta (`COPY --from`) +> +> Ainoa sallittu lataushetki on `docker build`. Sen jälkeen kontti toimii +> ilman verkkoyhteyttä. + +**Miksi:** Toistettavuus, air gap -yhteensopivuus, nopeus. Pipeline ei saa +epäonnistua sen takia että ulkoinen registry on alhaalla tai että `go mod download` +joutuu latamaan 100 modulia jokaisella testiajolla. + +**Kielikohtaiset pre-cachet:** Jos kontissa ajetaan kielikohtaista testiä +(Go, Java, Node, Python, ...), kaikki kielikohtaiset riippuvuudet on +pre-cachattava Dockerfilessä build-vaiheessa: +- Go: `COPY go.mod go.sum ./` → `RUN go mod download` +- Java/Maven: `COPY pom.xml ./` → `RUN mvn dependency:go-offline` +- Node: `COPY package.json package-lock.json ./` → `RUN npm ci --omit=dev` +- Python: `COPY requirements.txt ./` → `RUN pip wheel --wheel-dir=/wheels -r requirements.txt` → `COPY --from` käyttöön + ## Nimeäminen CI-kontin build-workflow noudattaa samaa nimeämiskonventiota kuin muutkin @@ -123,27 +148,80 @@ tag: latest ### Dockerfile -Dockerfile yhdistää tarvitut työkalut yhteen konttiin. Molemmat tavat kelpaavat: +Dockerfile yhdistää tarvitut työkalut yhteen konttiin. +**Kaikki riippuvuudet ladataan build-vaiheessa — kontti on täysin itseriittoinen.** ```dockerfile # Tapa A: COPY --from toisesta imagesta FROM __BASE_IMAGE__:__VERSION__ COPY --from=__SOURCE_IMAGE__:__VERSION__ /path/to/binary /usr/local/bin/ -RUN apk add --no-cache __PAKETIT__ # Ei koskaan git:iä — kloonaus kuuluu pipelinelle +RUN apk add --no-cache __PAKETIT__ -# Tapa B: curl-lataus (normaali Dockerfilessa) +# Tapa B: Build-vaiheen curl-lataus FROM __BASE_IMAGE__:__VERSION__ RUN apk add --no-cache curl __PAKETIT__ && \ curl -fsSL __URL__/__BINARY__.tar.gz | tar xz -C /usr/local/bin && \ apk del curl + +# Tapa C: Multi-stage + kielikohtainen pre-cache +FROM __BASE_IMAGE__:__VERSION__ AS deps +COPY go.mod go.sum ./ +RUN go mod download + +FROM deps AS build +COPY . . +RUN go test -c -o /tmp/test.bin ./... + +FROM __BASE_IMAGE__:__VERSION__ +COPY --from=deps /go/pkg/mod /go/pkg/mod +COPY --from=build /tmp/test.bin /usr/local/bin/test ``` -`COPY --from` on kevyempi (ei curl-asennusta). `curl` on selkeämpi kun binääri -tulee suoraan GitHub Releasesista tai vastaavasta. +`COPY --from` on kevyempi (ei curl-asennusta). `curl` (Tapa B) on sallittu +vain build-vaiheessa — `apk del curl` poistaa työkalun ennen runtimea. +Tapa C pre-cacheaa kielikohtaiset riippuvuudet ja tuottaa täysin +offline-runtime-kontin. + +## Testaus ennen julkaisua + +Konttia ei saa pushata registryyn ennen kuin se on validoitu. + +### 1. Aja testit kontin sisällä + +Testit on ajettava **kontin sisällä**, ei suoraan lokaalilla koneella. + +```bash +# OIKEIN — kontin sisällä +docker build -t ci-tyokalu:test . +docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test bash -c 'bats tests/' + +# VÄÄRIN — lokaalit binäärit vs kontti +bats tests/ # eri bash/työkalut kuin kontissa +bashcov -- bats tests/ # eri ruby-versio kuin kontissa +``` + +Lokaali ympäristö (macOS, eri kirjastoversiot) poikkeaa aina kontista. +Testi voi mennä läpi lokaalissa mutta failata CI:ssä, tai päinvastoin. + +### 2. Fragile-testien seulonta (10x ajo) + +Aja koko testipaketti **10 kertaa peräkkäin** kontin sisällä ennen pushausta: + +```bash +for i in $(seq 1 10); do + echo "=== RUN $i ===" + docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test \ + bash -c 'bats tests/' || exit 1 +done +``` + +Jos yksikin ajo failaa, kontissa on fragile testi — korjaa ennen pushausta. +Fragile testit syövät devaukseen käytettyä aikaa turhilla uusinta-ajoilla. ## Mitä EI kannata tehdä - Älä lisää `workflow_call`-triggariä — CI-konttia ei koskaan buildata automaattisesti - Älä poista `.`-prefiksiä olemassaolevista tiedostoista — ne kuuluvat monorepo-nimeämiskonventioon - Älä sisällytä CI-konttiin mitään sovelluskoodia — vain työkalut -- Älä koskaan asenna `git`:iä CI-konttiin — repon kloonaus ja checkout ovat Gitea Actionsin natiiveja operaatioita, eivät kontin vastuulla. Git paisuttaa konttia turhaan ja luo harhan että kontti hallitsee repoa +- Älä koskaan lataa mitään pipeline- tai runtime-vaiheessa — kaikki lataukset kuuluvat `docker build` -vaiheeseen (Offline-periaate) +- Älä jätä kielikohtaisia riippuvuuksia pre-cachaamatta — `go mod download`, `npm install`, `mvn dependency:go-offline` jne. ajetaan Dockerfilessä, ei pipelinessä diff --git a/skills/consumer-pipelines/REFERENCE.md b/skills/consumer-pipelines/REFERENCE.md index 64d93c1..58ae7ce 100644 --- a/skills/consumer-pipelines/REFERENCE.md +++ b/skills/consumer-pipelines/REFERENCE.md @@ -2,6 +2,93 @@ Mallipohjat, esimerkit ja konfiguraatiot. Katso säännöt `SKILL.md`:stä. +## Pre-cache-esimerkit (Offline Container) + +Alla Dockerfile-esimerkit kielikohtaisista pre-cacheista. Kaikki ajetaan +build-vaiheessa — kontti on täysin itseriittoinen eikä lataa mitään +pipeline- tai runtime-vaiheessa. + +### Go + +```dockerfile +FROM golang:1.24-alpine AS deps +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download + +FROM deps AS test-build +COPY . . +RUN go test -c -o /tmp/test.bin ./... + +FROM alpine:3.21 +RUN apk add --no-cache git nodejs +COPY --from=deps /go/pkg/mod /go/pkg/mod +COPY --from=test-build /tmp/test.bin /usr/local/bin/test +``` + +### Node.js + +```dockerfile +FROM node:22-alpine AS deps +WORKDIR /build +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +FROM node:22-alpine +RUN apk add --no-cache git +COPY --from=deps /build/node_modules /app/node_modules +COPY . /app +WORKDIR /app +``` + +### Java / Maven + +```dockerfile +FROM maven:3.9-eclipse-temurin-21 AS deps +WORKDIR /build +COPY pom.xml ./ +RUN mvn dependency:go-offline -B + +FROM maven:3.9-eclipse-temurin-21 AS build +COPY --from=deps /root/.m2 /root/.m2 +COPY . . +RUN mvn package -B -DskipTests + +FROM eclipse-temurin:21-jre +RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* +COPY --from=build /build/target/*.jar /app/app.jar +WORKDIR /app +``` + +### Python + +```dockerfile +FROM python:3.12-alpine AS deps +WORKDIR /build +COPY requirements.txt ./ +RUN pip wheel --wheel-dir=/wheels -r requirements.txt + +FROM python:3.12-alpine +RUN apk add --no-cache git +COPY --from=deps /build/wheels /wheels +COPY --from=deps /build/requirements.txt / +RUN pip install --no-index --find-links=/wheels -r /requirements.txt && rm -rf /wheels +COPY . /app +WORKDIR /app +``` + +### Helm + Node.js (korvaa helm-build-push.yml:n runtime-apk) + +```dockerfile +FROM alpine/helm:3.16.0 AS helm-bin +FROM node:22-alpine +RUN apk add --no-cache git +COPY --from=helm-bin /usr/bin/helm /usr/local/bin/helm +``` + +Tämä kontti korvaa `helm-build-push.yml`:n `alpine/helm:3.19.0`-image-riippuvuuden +ja poistaa tarpeen asentaa node.js runtime-vaiheessa. + ## Reititin — täydellinen esimerkki ```yaml diff --git a/skills/consumer-pipelines/SKILL.md b/skills/consumer-pipelines/SKILL.md index 31a042a..542fc0c 100644 --- a/skills/consumer-pipelines/SKILL.md +++ b/skills/consumer-pipelines/SKILL.md @@ -82,7 +82,36 @@ koko stepin ensimmäisellä failaavalla komennolla, ja loput jäävät ajamatta. CI-kontin build-workflow'n template: `skills/ci-container-build/SKILL.md`. -### 4.1 CI-kontin ajaminen jobissa +### 4.1 Offline Container -vaatimus (DoD) + +CI-kontin (ja kaikkien pipeline-konttien) on oltava täysin itseriittoisia: + +> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä +> kontin runtime-prosessissa (`container:` / `docker run`). Kaikki +> riippuvuudet pre-cachataan `docker build` -vaiheessa. +> Ainoa sallittu lataushetki on `docker build`. + +**Esimerkkejä rikkomuksista:** +- `apk add`, `apt-get install`, `npm install`, `go mod download`, `pip install` + pipeline-stepissä +- `curl | tar xz` runtime-vaiheessa +- Node.js-konttikuva ilman nodea (joudutaan asentamaan lennossa) + +### 4.2 Kielikohtainen pre-cache + +Kun kontissa testataan kielikohtaista koodia, kaikki riippuvuudet on +pre-cachattava Dockerfilessä, ei pipeline-stepissä: + +| Kieli | Pre-cache Dockerfilessä | +|---|---| +| Go | `COPY go.mod go.sum ./` → `RUN go mod download` | +| Java/Maven | `COPY pom.xml ./` → `RUN mvn dependency:go-offline` | +| Node | `COPY package.json package-lock.json ./` → `RUN npm ci --omit=dev` | +| Python | `COPY requirements.txt ./` → `RUN pip install -r requirements.txt` | + +Katso tarkat Dockerfile-esimerkit `REFERENCE.md`:stä. + +### 4.3 CI-kontin ajaminen jobissa Ainoa sallittu tapa on `container:`-direktiivi. `docker run` komennolla kontin käynnistäminen stepin sisällä on anti-pattern. @@ -91,7 +120,8 @@ Katso CI-kontin template `REFERENCE.md`:stä. **Huomio `actions/checkout@v4`:stä:** `container:`-direktiivillä kaikki stepit ajetaan kontin *sisällä* — myös `actions/checkout@v4`. Se on JavaScript-action -joka vaatii sekä `nodejs` että `git`. Varmista että CI-kontin Dockerfilessä on molemmat. +joka vaatii sekä `nodejs` että `git`. Varmista että CI-kontin Dockerfilessä on +molemmat — muuten checkout ei toimi ja pipeline failaa. ## 5. Raporttitasot @@ -243,15 +273,14 @@ helm-build-push: # chart_path: '.' # oletus, vaihda jos Chart.yaml on alihakemistossa ``` -**Node.js-kompromissi:** `actions/checkout@v4` on JavaScript-action. -Kontissa `alpine/helm` ei ole node.js:ää, joten se asennetaan lennossa -`apk add --no-cache nodejs` ennen checkouttia. +**Vanhentunut käytäntö:** Nykyinen `helm-build-push.yml` asentaa node.js:n +lennossa `apk add --no-cache nodejs` ennen checkouttia — tämä rikkoo +Offline Container -vaatimusta (4.1). -- Vaatii internet-yhteyden -- Ei toimi air gap -ympäristössä -- Korvaa tarvittaessa custom-kontilla (helm + nodejs): - rakenna `ci-container-build`-skillillä ja päivitä workflow'n - `container: image:` osoittamaan omaan konttiin +**Korjaustoimenpide:** Rakenna custom CI-kontti `ci-container-build`-skillillä +jossa on helm + nodejs + git (katso pre-cache-esimerkit `REFERENCE.md`:stä), +päivitä workflow'n `container: image:` osoittamaan omaan konttiin, ja poista +runtime-apk. **Yksittäisten Helm-UI-linkkien raportointi:** `HELM_UI_URL` on tarkoitettu yleiselle registry UI:lle — provider muodostaa linkin diff --git a/tests/helpers/__pycache__/mock-server.cpython-314.pyc b/tests/helpers/__pycache__/mock-server.cpython-314.pyc new file mode 100644 index 0000000..2315b15 Binary files /dev/null and b/tests/helpers/__pycache__/mock-server.cpython-314.pyc differ diff --git a/tests/helpers/mock-api.sh b/tests/helpers/mock-api.sh index ae7a55f..e6ad4b5 100644 --- a/tests/helpers/mock-api.sh +++ b/tests/helpers/mock-api.sh @@ -12,8 +12,10 @@ MOCK_CONFIG_FILE="" _kill_port() { local pids pids=$(lsof -ti ":$MOCK_PORT" 2>/dev/null) || true - [ -n "$pids" ] && kill -9 $pids 2>/dev/null || true - sleep 0.5 + if [ -n "$pids" ]; then + kill -9 $pids 2>/dev/null || true + sleep 0.5 + fi } _wait_port_free() { @@ -26,7 +28,7 @@ _wait_port_free() { _wait_port_ready() { local i=0 - while ! lsof -ti ":$MOCK_PORT" >/dev/null 2>&1 && [ $i -lt 5 ]; do + while ! lsof -ti ":$MOCK_PORT" >/dev/null 2>&1 && [ $i -lt 30 ]; do sleep 0.2 i=$((i + 1)) done