Skip to content

Commit 8899b1b

Browse files
authored
ci: rework unit tests (#185)
Treat tests to run as data. Have GitHub Actions source its matrix from that data. This allows us more flexibility in adapting to changes (ex. shadow-cljs now requires >= jdk11) It greatly breaks down the granularity of tests. Although we might have gone a bit too far here? We'll see how elapsed ci times are affected and adapt as necessary. Although we are running many more GitHub Actions jobs, we are running fewer tests. For example, cljs tests are now only run under a single JDK instead of all JDKs. Linting tests are run under all oses (to make sure they will work for contributors, regardless of os) but under only a single JDK version and Clojure version. Also: I avoided the complexity of optimizing setup. Ex. we rarely need Planck but always install it. We only need npm setup for cljs but always set it up. Can add in that complexity at a later date if there is merit.
1 parent 4e73fbf commit 8899b1b

File tree

6 files changed

+191
-50
lines changed

6 files changed

+191
-50
lines changed

.github/workflows/unit-test.yml

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,56 @@
11
name: Unit Tests
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
branches: ['main']
6+
pull_request:
47

58
jobs:
9+
setup:
10+
runs-on: ubuntu-latest
11+
12+
outputs:
13+
tests: ${{ steps.set-tests.outputs.tests }}
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
18+
- name: Clojure deps cache
19+
uses: actions/cache@v3
20+
with:
21+
path: |
22+
~/.m2/repository
23+
~/.deps.clj
24+
~/.gitlibs
25+
key: cljdeps-${{ hashFiles('deps.edn', 'bb.edn') }}
26+
restore-keys: cljdeps-
27+
28+
- name: "Setup Java"
29+
uses: actions/setup-java@v3
30+
with:
31+
distribution: 'temurin'
32+
java-version: '11'
33+
34+
- name: Install Clojure Tools
35+
uses: DeLaGuardo/[email protected]
36+
with:
37+
cli: 'latest'
38+
bb: 'latest'
39+
40+
- id: set-tests
41+
name: Set test var for matrix
42+
# run test.clj directly instead of via bb task to avoid generic task output
43+
run: echo "::set-output name=tests::$(bb script/ci_unit_tests.clj matrix-for-ci --format=json)"
44+
645
build:
46+
needs: setup
747
runs-on: ${{ matrix.os }}-latest
848
strategy:
949
fail-fast: false
1050
matrix:
11-
os: [ windows, ubuntu, macos ]
12-
java: [ '8', '11', '17' ]
51+
include: ${{fromJSON(needs.setup.outputs.tests)}}
1352

14-
name: ${{ matrix.os }},jdk ${{ matrix.java }}
53+
name: ${{ matrix.desc }}
1554

1655
steps:
1756
#
@@ -49,7 +88,7 @@ jobs:
4988
uses: actions/setup-java@v3
5089
with:
5190
distribution: 'temurin'
52-
java-version: ${{ matrix.java }}
91+
java-version: ${{ matrix.jdk }}
5392

5493
#
5594
# Install Planck
@@ -63,9 +102,6 @@ jobs:
63102
sudo apt-get update
64103
sudo apt-get install -y planck
65104
if: matrix.os == 'ubuntu'
66-
- name: Install planck (macOS)
67-
run: brew install planck
68-
if: matrix.os == 'macos'
69105

70106
#
71107
# Install Babashka
@@ -133,5 +169,5 @@ jobs:
133169
#
134170
# Run tests
135171
#
136-
- name: Run CI tests
137-
run: bb ci-unit-tests
172+
- name: Run Tests
173+
run: ${{ matrix.cmd }}

bb.edn

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{:min-bb-version "0.3.7"
1+
{:min-bb-version "0.9.162"
22
:paths ["script" "build"]
33
:deps {org.clojure/data.zip {:mvn/version "1.0.0"}
44
io.aviso/pretty {:mvn/version "1.1.1"}
@@ -34,5 +34,5 @@
3434
doc-api-diffs {:task doc-api-diffs/-main :doc "generate diff docs for rewrite-clj* APIs"}
3535
doc-update-readme {:task doc-update-readme/-main :doc "honour our contributors in README"}
3636
cljdoc-preview {:task cljdoc-preview/-main :doc "preview what docs will look like on cljdoc, use --help for args"}
37-
ci-unit-tests {:task ci-unit-tests/-main}
37+
ci-unit-tests {:task ci-unit-tests/-main :doc "run/list continuous integration unit tests, use --help for args"}
3838
ci-release {:task ci-release/-main :doc "release tasks, use --help for args"}}}

doc/02-developer-guide.adoc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ We test that rewrite-clj operates as expected when natively compile via GraalVM.
1515
Automated testing is setup using GraalVM v22 JDK11.
1616

1717
== Prerequisites
18-
* Java JDK 1.8 or above
18+
* Java JDK 1.8 or above (shadow-cljs tests require min of JDK 11)
1919
* NodeJs v12 or above
2020
* Clojure v1.10.1.697 or above for `clojure` command
2121
** Note that rewrite-clj v1 itself supports Clojure v1.8 and above
@@ -210,11 +210,22 @@ This generates tests for doc code blocks and then runs them under Clojure and Cl
210210
Before pushing, you likely want to mimic what is run on each push via GitHub Actions.
211211

212212
=== Unit tests
213-
Unit tests are run via:
213+
Unit tests can be run locally on your dev box.
214+
Some tests require a specific os and jdk, you will see warnings if a test is skipped for your current environment.
214215
----
215216
bb ci-unit-tests
216217
----
217218

219+
Unit tests for our ci matrix are driven by:
220+
----
221+
bb script/ci_unit_tests.clj matrix-for-ci --format=json
222+
----
223+
224+
To get a human readable version of the ci matrix:
225+
----
226+
bb ci-unit-tests matrix-for-ci
227+
----
228+
218229
=== Native image tests
219230
We also verify that rewrite-clj functions as expected when compiled via Graal's `native-image`.
220231

script/ci_unit_tests.clj

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,125 @@
11
#!/usr/bin/env bb
22

33
(ns ci-unit-tests
4-
(:require [helper.fs :as fs]
4+
(:require [cheshire.core :as json]
5+
[clojure.string :as string]
6+
[doric.core :as doric]
7+
[helper.fs :as fs]
8+
[helper.jdk :as jdk]
59
[helper.main :as main]
610
[helper.os :as os]
711
[helper.shell :as shell]
812
[lread.status-line :as status]))
913

10-
(defn clean []
11-
(doseq [dir ["target" ".cpcache" ".shadow-cljs"]]
12-
(fs/delete-file-recursively dir true)))
14+
(defn- matrix-os []
15+
(case (os/get-os)
16+
:win "windows"
17+
:mac "macos"
18+
;; else assume ubuntu
19+
"ubuntu"))
1320

14-
(defn lint []
15-
(shell/command "bb lint"))
21+
;; matrix params to be used on ci
22+
(def ^:private all-oses ["ubuntu" "macos" "windows"])
23+
(def ^:private all-jdks ["8" "11" "17"])
1624

17-
(defn check-import-vars []
18-
(shell/command "bb apply-import-vars check"))
25+
(defn- test-tasks []
26+
(concat [;; run lintish tasks across all oses to verify that they will work for all devs regardless of their os choice
27+
{:desc "import-vars" :cmd "bb apply-import-vars check" :oses all-oses :jdks ["8"]}
28+
{:desc "lint" :cmd "bb lint" :oses all-oses :jdks ["8"]}
29+
;; test-docs on default clojure version across all oses and jdks
30+
{:desc "test-doc" :cmd "bb test-doc" :oses all-oses :jdks all-jdks}]
31+
(for [version ["1.8" "1.9" "1.10" "1.11"]]
32+
{:desc (str "clj-" version)
33+
:cmd (str "bb test-clj --clojure-version " version)
34+
:oses all-oses
35+
:jdks all-jdks})
36+
;; I'm not sure there's much value testing across jdks for ClojureScript tests, for now we'll stick with jdk 8 only
37+
(for [env [{:param "node" :desc "node"}
38+
{:param "chrome-headless" :desc "browser"}]
39+
opt [{:param "none"}
40+
{:param "advanced" :desc "adv"}]]
41+
{:desc (str "cljs-"
42+
(:desc env)
43+
(when (:desc opt) (str "-" (:desc opt))))
44+
:cmd (str "bb test-cljs --env " (:param env) " --optimizations " (:param opt))
45+
:oses all-oses
46+
:jdks ["8"]})
47+
;; shadow-cljs requires a min of jdk 11 so we'll test on that
48+
[{:desc "shadow-cljs" :cmd "bb test-shadow-cljs" :oses all-oses :jdks ["11"]
49+
:skip-reason-fn (fn [{:keys [jdk]}] (when (< (parse-long jdk) 11)
50+
"jdk must be >= 11"))}]
51+
;; planck does not run on windows, and I don't think it needs a jdk
52+
[{:desc "cljs-bootstrap" :cmd "bb test-cljs --env planck --optimizations none"
53+
:oses ["macos" "ubuntu"] :jdks ["8"]}]))
1954

20-
(defn doc-tests[]
21-
(shell/command "bb test-doc"))
55+
(defn- ci-test-matrix []
56+
(for [{:keys [desc cmd oses jdks]} (test-tasks)
57+
os oses
58+
jdk jdks]
59+
{:desc (str desc " " os " jdk" jdk)
60+
:cmd cmd
61+
:os os
62+
:jdk jdk}))
2263

23-
(defn clojure-tests []
24-
(doseq [version ["1.8" "1.9" "1.10" "1.11"]]
25-
(shell/command "bb test-clj --clojure-version" version)) )
64+
(defn- local-test-list [local-os local-jdk]
65+
(for [{:keys [desc cmd oses skip-reason-fn]} (test-tasks)]
66+
(let [skip-reasons (cond-> []
67+
(not (some #{local-os} oses))
68+
(conj (str "os must be among " oses))
69+
(and skip-reason-fn (skip-reason-fn {:jdk local-jdk}))
70+
(conj (skip-reason-fn {:jdk local-jdk})))]
71+
(cond-> {:desc desc
72+
:cmd cmd}
73+
(seq skip-reasons)
74+
(assoc :skip-reasons skip-reasons)))))
2675

27-
(defn cljs-tests []
28-
(doseq [env ["node" "chrome-headless"]
29-
opt ["none" "advanced"]]
30-
(shell/command "bb" "test-cljs" "--env" env "--optimizations" opt)))
76+
(defn- clean []
77+
(doseq [dir ["target" ".cpcache" ".shadow-cljs"]]
78+
(fs/delete-file-recursively dir true)))
3179

32-
(defn shadow-cljs-tests []
33-
(shell/command "bb test-shadow-cljs"))
80+
(def args-usage "Valid args:
81+
[matrix-for-ci [--format=json]]
82+
--help
3483
35-
(defn cljs-bootstrap-tests []
36-
(if (some #{(os/get-os)} '(:mac :unix))
37-
(shell/command "bb test-cljs --env planck --optimizations none")
38-
(status/line :warn "skipping planck tests, they can only be run on linux and macOS")) )
84+
Commands:
85+
matrix-for-ci Return a matrix for use within GitHub Actions workflow
86+
87+
Options:
88+
--help Show this help
89+
90+
By default, will run all tests applicable to your current jdk and os.")
3991

4092
(defn -main [& args]
41-
(when (main/doc-arg-opt args)
42-
(clean)
43-
(check-import-vars)
44-
(lint)
45-
(doc-tests)
46-
(clojure-tests)
47-
(cljs-tests)
48-
(shadow-cljs-tests)
49-
(cljs-bootstrap-tests))
93+
(when-let [opts (main/doc-arg-opt args-usage args)]
94+
(if (get opts "matrix-for-ci")
95+
(let [matrix (ci-test-matrix)]
96+
(if (= "json" (get opts "--format"))
97+
(status/line :detail (json/generate-string matrix))
98+
(do
99+
(status/line :detail (doric/table [:os :jdk :desc :cmd] matrix))
100+
(status/line :detail "Total jobs found: %d" (count matrix)))))
101+
(let [cur-os (matrix-os)
102+
cur-jdk (jdk/version)
103+
cur-major-jdk (str (:major cur-jdk))
104+
test-list (local-test-list cur-os cur-major-jdk)
105+
tests-skipped (filter :skip-reasons test-list)
106+
tests-to-run (remove :skip-reasons test-list)]
107+
(when (not (some #{cur-major-jdk} all-jdks))
108+
(status/line :warn "CI runs only on jdks %s but you are on jdk %s\nWe'll run tests anyway."
109+
all-jdks (:version cur-jdk)))
110+
111+
(status/line :head "Test plan")
112+
(status/line :detail "Found %d runnable tests for jdk %s on %s:"
113+
(count tests-to-run) cur-major-jdk cur-os)
114+
(doseq [{:keys [cmd]} tests-to-run]
115+
(status/line :detail (str " " cmd)))
116+
(doseq [{:keys [cmd skip-reasons]} tests-skipped]
117+
(status/line :warn (string/join "\n* "
118+
(concat [(str "Skipping: " cmd)]
119+
skip-reasons))))
120+
(clean)
121+
(doseq [{:keys [cmd]} tests-to-run]
122+
(shell/command cmd)))))
50123
nil)
51124

52125
(main/when-invoked-as-script

script/helper/jdk.clj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(ns helper.jdk
2+
(:require [helper.shell :as shell]))
3+
4+
(defn version
5+
"Returns jdk version and major version with appropriate conversion. (ex 1.8 returns major of 8)"
6+
[]
7+
(let [raw-version (->> (shell/command {:err :string}
8+
"java -version")
9+
:err
10+
(re-find #"version \"(.*)\"")
11+
last)
12+
major-minor (->> raw-version
13+
(re-find #"(\d+)(?:\.(\d+))?.*")
14+
rest
15+
(map #(when % (Integer/parseInt %))))]
16+
{:version raw-version
17+
:major (if (= (first major-minor) 1)
18+
(second major-minor)
19+
(first major-minor))}))

script/test_shadow_cljs.clj

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
#!/usr/bin/env bb
22

33
(ns test-shadow-cljs
4-
(:require [helper.main :as main]
5-
[helper.os :as os]
4+
(:require [helper.jdk :as jdk]
5+
[helper.main :as main]
66
[helper.shell :as shell]
77
[lread.status-line :as status]))
88

99
;; see also: shadow-cljs.edn
1010
(def compiled-tests "target/shadow-cljs/node-test.js")
1111

1212
(defn -main [& args]
13+
(let [{:keys [version major]} (jdk/version)]
14+
(when (<= major 8)
15+
(status/die 1 "shadow-cljs requires JDK 11 or above, found version %s" version)))
1316
(when (main/doc-arg-opt args)
1417
(status/line :head "testing ClojureScript source with Shadow CLJS, node, optimizations: none")
15-
(shell/command (if (= :win (os/get-os)) "npx.cmd" "npx")
16-
"shadow-cljs" "compile" "test")
18+
(shell/command "npx" "shadow-cljs" "compile" "test")
1719
(shell/command "node" compiled-tests))
1820
nil)
1921

0 commit comments

Comments
 (0)