Skip to content

Commit

Permalink
Support 'repeat' in steps
Browse files Browse the repository at this point in the history
Allows retrying exec/run commands until they succeed. API for 'repeat'
inspired by docker compose's 'healthcheck' options.

Supplying 'repeat' without setting 'retries' will retry indefinitely.
  • Loading branch information
jonsmock committed May 16, 2024
1 parent cbdbf9f commit ead8eee
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 16 deletions.
1 change: 1 addition & 0 deletions examples/00-intro.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tests:
FOO: bar
run: |
[ "${FOO}" == "bar" ]
repeat: { retries: 3, interval: '1s' }

test-2:
name: Failing test
Expand Down
147 changes: 147 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"license": "SEE LICENSE IN LICENSE",
"description": "docker-compose integration test runner",
"dependencies": {
"@lonocloud/cljs-utils": "0.1.3",
"ajv": "^8.12.0",
"dockerode": "^3.3.1",
"js-yaml": "^4.1.0",
Expand Down
9 changes: 9 additions & 0 deletions schema.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
$defs:
interval: { type: string, pattern: "^\\s*((\\d+)m)?((\\d+)s)?\\s*$" }
env:
type: object
propertyNames: { type: string }
Expand Down Expand Up @@ -35,3 +36,11 @@ properties:
exec: { type: string }
name: { type: string }
run: { type: string }
repeat:
type: object
additionalProperties: false
required: []
properties:
interval: { "$ref": "#/$defs/interval", default: "1s" }
retries: { type: integer } # no default (infinite retries)
default: { retries: 0 } # do not retry, if repeat is missing
4 changes: 3 additions & 1 deletion scripts/nbb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ die() { echo >&2 "${*}"; exit 1; }

[ -e "${NBB}" ] || die "Missing ${NBB}. Maybe run 'npm install' in ${ROOT_DIR}?"

NODE_PATH="${ROOT_DIR}/node_modules" exec ${NBB} -cp "${ROOT_DIR}/src:${ROOT_DIR}/test" "${TARGET_DIR}/${TARGET_NAME}.cljs" "${@}"
NODE_PATH="${ROOT_DIR}/node_modules" exec ${NBB} \
-cp "${ROOT_DIR}/src:${ROOT_DIR}/test:${ROOT_DIR}/node_modules/@lonocloud/cljs-utils/src" \
"${TARGET_DIR}/${TARGET_NAME}.cljs" "${@}"
2 changes: 1 addition & 1 deletion shadow-cljs.edn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{:source-paths ["src/" "test/"]
{:source-paths ["src/" "test/" "node_modules/@lonocloud/cljs-utils/src"]

:dependencies [[funcool/promesa "8.0.450"]
[cljs-bean "1.8.0"]]
Expand Down
55 changes: 41 additions & 14 deletions src/dctest/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

(ns dctest.core
(:require [cljs-bean.core :refer [->clj]]
[clojure.edn :as edn]
[clojure.string :as S]
[clojure.pprint :refer [pprint]]
[dctest.util :as util :refer [fatal obj->str log]]
[promesa.core :as P]
[viasat.retry :as retry]
["stream" :as stream]
#_["dockerode$default" :as Docker]
))
Expand Down Expand Up @@ -98,26 +100,38 @@ Options:
(defn short-outcome [{:keys [outcome]}]
(get {:pass "" :fail "F" :skip "S"} outcome "?"))

;; TODO: Support more than exec :-)
(defn execute-step* [context step]
(P/let [{:keys [docker opts]} context
{:keys [project]} opts
{service :exec index :index command :run} step
{:keys [interval retries]} (:repeat step)
env (merge (:env context) (:env step))
index (or index 1)

container (dc-service docker project service index)
_ (when-not container
(throw (ex-info (str "No container found for service '" service "' (index=" index ")")
{})))

env (mapv (fn [[k v]] (str k "=" v)) env)
exec (P/then (docker-exec container command {:Env env})
->clj)]

(when-not (zero? (:ExitCode exec))
(throw (ex-info (str "Non-zero exit code for command: " command)
{})))
run-expect! (fn [result]
(when-not (zero? (:ExitCode result))
(throw (ex-info (str "Non-zero exit code for command: " command)
{}))))
run-exec (fn []
(P/catch
(P/let [container (dc-service docker project service index)
_ (when-not container
(throw (ex-info (str "No container found for service '" service "' (index=" index ")")
{})))
env (mapv (fn [[k v]] (str k "=" v)) env)
result (P/then (docker-exec container command {:Env env})
->clj)]
(run-expect! result)
{:result result})
(fn [err]
{:error (.-message err)})))
exec (retry/retry-times run-exec
retries
{:delay-ms interval
:check-fn :result})]

(when-let [msg (:error exec)]
(throw (ex-info msg {})))

context))

Expand Down Expand Up @@ -223,9 +237,22 @@ Options:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Load Suite

;; see schema.yaml
(def INTERVAL-RE #"^\s*((\d+)m)?((\d+)s)?\s*$") ; 1m20s

(defn parse-interval [time-str]
(let [[_ _ minutes _ seconds] (re-matches INTERVAL-RE time-str)
minutes (edn/read-string (or minutes "0"))
seconds (edn/read-string (or seconds "0"))]
(-> (* minutes 60)
(+ seconds)
(* 1000))))

(defn normalize [suite]
(let [->step (fn [step]
(update step :env update-keys name))
(-> step
(update :env update-keys name)
(update-in [:repeat :interval] parse-interval)))
->test (fn [test]
(-> test
(update :env update-keys name)
Expand Down

0 comments on commit ead8eee

Please sign in to comment.