Skip to content

Instantly share code, notes, and snippets.

@ianmuge
Last active November 30, 2022 11:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianmuge/bfc1e036de7294ad7cd55329268b4ce8 to your computer and use it in GitHub Desktop.
Save ianmuge/bfc1e036de7294ad7cd55329268b4ce8 to your computer and use it in GitHub Desktop.
Messing around with Babashka support Code
name: Build and Test
on:
push:
paths-ignore:
- '*.md'
jobs:
test:
runs-on: ubuntu-22.04
name: "Runner #${{ matrix.ci_node_index }}: Run test suite in parallel"
strategy:
fail-fast: false
matrix:
ci_node_total: [5]
ci_node_index: [0,1,2,3,4]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup java
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt'
- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@9.5
with:
lein: 2.9.8 # Leiningen
clj-kondo: 2021.04.23 # Clj-kondo
cli: 1.11.1.1105
bb: 1.0.165
- name: Cached Dependencies
uses: actions/cache@v3
with:
path: |
~/.m2/repository
key: ${{ runner.os }}-project-${{ hashFiles('**/project.clj') }}
restore-keys: ${{ runner.os }}-project
- name: Install dependencies
run: lein deps
- name: Download artifact
id: download-artifact
uses: dawidd6/action-download-artifact@v2
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
workflow: build-and-test.yml
workflow_conclusion: "completed"
name: test-artifacts
path: ./target/test2junit
check_artifacts: true
search_artifacts: true
if_no_artifact_found: warn
- name: Split timing
id: split-tests
run: |
bb split --workers ${{ matrix.ci_node_total }} --index ${{ matrix.ci_node_index }} --plan
echo ::set-output name=test-suite::$(bb split --workers ${{ matrix.ci_node_total }} --index ${{ matrix.ci_node_index }})
- name: Cleanup previous tests
run: |
lein clean
- name: Run tests
env:
_JAVA_OPTIONS: '-XX:MaxRAMPercentage=80'
run: |
echo "${{ steps.split-tests.outputs.test-suite }}"
echo "${{ steps.split-tests.outputs.test-suite }}" | xargs lein test2junit
- name: Test report
uses: dorny/test-reporter@v1
if: always() # run this step even if previous step failed
with:
name: "Runner #${{ matrix.ci_node_index }}: Clojure tests results"
path: target/test2junit/xml/*.xml
reporter: java-junit
fail-on-error: false
- name: Upload test artifact
uses: actions/upload-artifact@v3
if: always()
with:
name: test-artifacts
path: |
./target/test2junit
retention-days: 7
{:paths ["."]
:min-bb-version "1.0.165"
:tasks
{:requires ([ci.test-helper :as cth])
split {:docs "Split tests based on timings"
:task (cth/run-split *command-line-args*)}
}}
#!/usr/bin/env bb
(ns ci.test-helper
(:require
[babashka.fs :as fs]
[clojure.data.xml :refer :all]
[clojure.java.shell :refer :all]
[clojure.string :as str]
[clojure.tools.cli :as cli]
[clojure.repl :as repl]))
(def cli-options
;; Options with a required argument
[["-w" "--workers WORKERS" "Number of Workers/Runners"
:default 30
:parse-fn parse-long]
["-i" "--index INDEX" "Runner Index"
:parse-fn parse-long]
["-b" "--test-results-base-path TEST_RESULTS_BASE_PATH" "Test results base path"
:default "./target/test2junit/"
:parse-fn str]
["-p" "--test-results-pattern TEST_RESULTS_PATTERN" "Test results pattern"
:default "**/*.xml"
:parse-fn str]
["-t" "--tests-path TESTS_PATH" "Tests path"
:default "test/"
:parse-fn str]
["-h" "--help"]
["-p" "--plan" "Execution plan"
:default false]])
(defn filename-to-classname [filename]
(-> filename
(str/replace #"^target/test2junit/xml/(.+?)\.xml$" "$1")
(str/replace #"^test/(.+?)\.clj$" "$1")
(str/replace #"/" ".")
repl/demunge))
(defn add-to-bin [bin [_ size :as item]]
{:size (+ (:size bin) size)
:items (conj (:items bin) item)})
(defn select-smallest-bin [bins]
(first (apply min-key
(fn [[_ bin]] (:size bin))
(map-indexed vector bins))))
(defn pack
"Simple first fit packing algorithm."
[items no-fit-fn]
(let [init-bins [{:size 0 :items []}]]
(reduce no-fit-fn init-bins items)))
(defn grow-bins [bins item]
(conj bins (add-to-bin {:size 0 :items []} item)))
(defn add-to-smallest-bin [n bins item]
(if (< (count bins) n)
(grow-bins bins item)
(update-in bins [(select-smallest-bin bins)]
#(add-to-bin % item))))
(defn pack-n-bins
[items n]
(pack items (partial add-to-smallest-bin n)))
(defn parse-to-double [string]
"Decimal seperator varies depending on system locale, string replacement standardizes this"
(-> string
(str/replace #"," ".")
parse-double))
(defn try-parse-xml [xml-content filename]
(try
(parse-str xml-content)
(catch Exception _
{:tag :testsuite
:attrs {:name (filename-to-classname(str filename))
:errors 1
:failures 1
:tests 0
:time 1
:timestamp 0}
:content []}
)))
(defn read-file [file]
(-> file
str
slurp))
(defn parse-test-results [file]
(-> (read-file file)
(try-parse-xml file)
:attrs
((juxt :name :time))))
(defn parse-test-files [file test-results]
(let [file-content (read-file file)]
(when (str/includes? file-content "deftest")
{:filename (str file)
:line-count (count (str/split-lines file-content))
:test-time ((keyword (filename-to-classname file)) test-results 1)
})))
(defn run-split [arglist]
(if-let [errors (:errors (cli/parse-opts arglist cli-options))]
(print errors)
(let [options (:options (cli/parse-opts arglist cli-options))
{:keys [workers index tests-path test-results-base-path test-results-pattern]} options
test-results (when (fs/exists? test-results-base-path)
(->> (fs/glob test-results-base-path test-results-pattern {:recursive true})
(map parse-test-results)
(into {})
(#(update-vals % parse-to-double))
(#(update-keys % keyword))))
aggregated-results (->> (fs/glob tests-path "**/*.clj" {:recursive true})
(keep #(parse-test-files % test-results))
(sort-by :test-time #(compare %2 %1)))
items (->> (pack-n-bins
(map vector
(map (comp filename-to-classname :filename) aggregated-results)
(map :test-time aggregated-results))
workers))
runner-spec (-> items
(get index))
runner-items (->> runner-spec
(:items)
(into {})
keys)]
(if (:plan options)
(do
(println (str "Number of workers: " workers))
(println (str "Runner Index: " index))
(println (str "Estimated runtime: " (:size runner-spec)))
(println (str "Planned classes: " runner-items)))
(doseq [item runner-items]
(print (str item " ")))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment