Miguel de Benito Delgado 756541939a
🔧 Add renderer benchmark runner (#12756)
Add a CLI under frontend/performance/benches/render_wasm that builds the
renderer, runs seeded scenes (rects, plus, paths, masks) in headless
Chromium and saves per-case timings.

- build, run: prepare or reuse builds; measure cases with warmups,
  repetitions, deadlines
- ab: build and compare Cargo feature configurations
- compare, merge: median changes with bootstrap CIs between saved runs,
  guarded by scene, build and settings checks
- purge: delete managed builds, runs or comparisons
- GPU runs through `run-devenv --gpu`

Lint, format and pnpm scripts now cover performance/ and its tests.

AI-assisted-by: gpt-6.1-sol, deepseek-flash, muse-spark, glm-5.3, gpt6-astra, claude-5.5-sonnet, claude-haiku-5.5
2026-11-12 11:02:34 +01:00

162 lines
7.6 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns performance-tests.render-wasm.log-test
(:require
["fs" :as fs]
["node:stream" :as stream]
["node:timers/promises" :as timers]
["os" :as os]
["path" :as path]
[benches.render-wasm.node.log :as log]
[cljs.test :as t :include-macros true]
[clojure.string :as str]))
(def temporary-root (atom nil))
(t/use-fixtures :each
{:before #(reset! temporary-root (fs/mkdtempSync (path/join (os/tmpdir) "bench-log-")))
:after (fn []
(log/configure {})
(fs/rmSync @temporary-root #js {:recursive true :force true}))})
(defn- capture
[]
(let [text (atom "")
out (stream/Writable.
#js {:write (fn [chunk _encoding done]
(swap! text str (.toString chunk))
(done))})]
{:stream out :text text}))
(t/deftest levels-filter-console-and-retain-every-line-in-the-file
(doseq [[level expected] [[:warn "! warning\n✖ failure\n"]
[:info "Phase\n attempt 1/1 completed\n! warning\n✖ failure\n"]
[:debug "Phase\n attempt 1/1 completed\n· raw child\n! warning\n✖ failure\n"]]]
(let [stdout (capture)
stderr (capture)
file (path/join @temporary-root (name level))]
(log/configure {:level level :color :never
:stdout (:stream stdout) :stderr (:stream stderr)})
(binding [log/*log-path* file]
(log/info "Phase")
(log/info " attempt 1/1 completed")
(log/debug "raw child")
(log/warn "warning")
(log/error {:message "failure" :tail? false})
(log/result "Run saved"))
(t/is (= "Run saved\n" @(:text stdout)))
(t/is (= (str/replace expected "✖ failure\n" (str "✖ failure\n log " file "\n"))
@(:text stderr)))
(t/is (= (str "Phase\n attempt 1/1 completed\nraw child\nwarning\n✖ failure\n log "
file "\nRun saved\n")
(fs/readFileSync file "utf8"))))))
(t/deftest colors-apply-to-console-headings-and-preserve-indented-progress
(doseq [[mode tty no-color term colored?]
[[:always false "1" "dumb" true] [:never true "" "xterm" false]
[:auto false "" "xterm" false] [:auto true "" "xterm" true]
[:auto true "1" "xterm" false] [:auto true "" "dumb" false]]]
(let [stdout (capture)
stderr (capture)
file (path/join @temporary-root (str (random-uuid)))
env (.-env js/process)
old-no-color (.-NO_COLOR env)
old-term (.-TERM env)]
(try
(set! (.-NO_COLOR env) no-color)
(set! (.-TERM env) term)
(set! (.-isTTY ^js (:stream stderr)) tty)
(log/configure {:color mode :stdout (:stream stdout) :stderr (:stream stderr)})
(binding [log/*log-path* file]
(log/info "Phase\n progress")
(log/result "Output /tmp/run"))
(t/is (= colored? (str/includes? @(:text stderr) "\u001b[")))
(t/is (= "Phase\n progress\n"
(str/replace @(:text stderr) #"\u001b\[[0-9;]*m" "")))
(t/is (= "Output /tmp/run\n" @(:text stdout)))
(t/is (= "Phase\n progress\nOutput /tmp/run\n" (fs/readFileSync file "utf8")))
(finally
(if (nil? old-no-color) (js-delete env "NO_COLOR") (set! (.-NO_COLOR env) old-no-color))
(if (nil? old-term) (js-delete env "TERM") (set! (.-TERM env) old-term)))))))
(t/deftest ^:async nested-tasks-route-lines-to-their-own-files-on-success-and-failure
(let [parent (path/join @temporary-root "comparison.log")
child (path/join @temporary-root "build.log")
output (capture)]
(log/configure {:level :warn :stdout (:stream output) :stderr (:stream output)})
(binding [log/*log-path* parent]
(log/debug "comparison before")
(await ((^:async fn []
(binding [log/*log-path* child]
(await (timers/setImmediate))
(log/debug "child success")))))
(log/debug "comparison between")
(try
(await ((^:async fn []
(binding [log/*log-path* child]
(await (timers/setImmediate))
(log/debug "child failure")
(throw (js/Error. "failed"))))))
(catch :default _ (log/debug "comparison after"))))
(t/is (= "comparison before\ncomparison between\ncomparison after\n"
(fs/readFileSync parent "utf8")))
(t/is (= "child success\nchild failure\n" (fs/readFileSync child "utf8")))))
(t/deftest console-diagnostics-escape-controls-and-retain-the-log-evidence
(let [file (path/join @temporary-root "controls.log")
output (capture)
message (str "work failed\u001b\u0007" (apply str (repeat 2000 "x")))]
(log/configure {:color :never :stderr (:stream output)})
(binding [log/*log-path* file]
(log/warn message))
(t/is (= (str message "\n") (fs/readFileSync file "utf8")))
(t/is (str/includes? @(:text output) "\\u001b\\u0007"))
(t/is (< (count @(:text output)) 1100))
(doseq [separator ["" "\n"]]
(reset! (:text output) "")
(fs/writeFileSync file (str (apply str (repeat 2000 "\u0007")) separator "FATAL: compiler failed\n") "utf8")
(log/error {:message "tail evidence" :tail-log file})
(t/is (str/includes? @(:text output) "FATAL: compiler failed"))
(t/is (every? #(let [code (.charCodeAt % 0)]
(or (= code 10) (and (>= code 32) (or (< code 127) (> code 159)))))
(seq @(:text output)))))))
(t/deftest error-names-tail-source-and-bounds-evidence-before-appending
(let [file (path/join @temporary-root "comparison.log")
child (path/join @temporary-root "build.log")
output (capture)]
(fs/writeFileSync child (str "excluded\n" (apply str (repeat 9000 "x")) "\n"
(str/join "\n" (map #(str "line-" %) (range 30)))) "utf8")
(log/configure {:color :never :stderr (:stream output)})
(binding [log/*log-path* file]
(log/error {:message "build failed" :fields [[:phase :renderer] [:build "id"]]
:tail-log child}))
(let [expected (str "✖ build failed\n phase :renderer\n build id\n"
" log " file "\n tail-log " child "\n tail\n"
(str/join "\n" (map #(str " line-" %) (range 10 30))) "\n")]
(t/is (= expected @(:text output)))
(t/is (= expected (fs/readFileSync file "utf8"))))
(fs/writeFileSync child (apply str (repeat 10000 "z")) "utf8")
(reset! (:text output) "")
(binding [log/*log-path* file]
(log/error {:message "bounded" :tail-log child}))
(t/is (= 8192 (count (re-find #"z+$" (str/trim @(:text output))))))
(fs/writeFileSync file "last line\n" "utf8")
(reset! (:text output) "")
(binding [log/*log-path* file]
(log/error {:message "same source"}))
(t/is (= (str "✖ same source\n log " file "\n tail\n last line\n")
@(:text output)))))
(t/deftest error-reporting-survives-unreadable-tail-and-file-append-failure
(let [output (capture)]
(log/configure {:color :never :stderr (:stream output)})
(binding [log/*log-path* @temporary-root]
(log/error {:message "original failure" :tail-log (path/join @temporary-root "missing")}))
(t/is (str/starts-with? @(:text output) "✖ original failure\n"))
(t/is (str/includes? @(:text output) @temporary-root))))