Miguel de Benito Delgado 13af3fb323 🚧 WIP 27, improve logging and console reporting.
AI-assisted-by: deepseek-flash, gpt-6.1-sol
2026-10-29 13:58:32 +01:00

143 lines
6.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 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))))