mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 09:30:38 +00:00
197 lines
7.9 KiB
Clojure
197 lines
7.9 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.commands-test
|
|
(:require
|
|
[benches.render-wasm.builder :as builder]
|
|
[benches.render-wasm.errors :as errors]
|
|
[benches.render-wasm.runtime.camera :as camera]
|
|
[benches.render-wasm.runtime.commands :as cmd :refer [defcommand]]
|
|
[cljs.test :as t]))
|
|
|
|
(defcommand move
|
|
"Moves the camera by the supplied offset."
|
|
[rtx {:keys [dx dy]}]
|
|
(-> rtx
|
|
(update-in [:view :x] + dx)
|
|
(update-in [:view :y] + dy)))
|
|
|
|
(defcommand selected-renders
|
|
{:measured-variant false}
|
|
[rtx]
|
|
(-> rtx
|
|
(camera/render-sync*)
|
|
(move {:dx 10 :dy 5})
|
|
(camera/render-sync)
|
|
(camera/render-sync*)))
|
|
|
|
(defcommand unexpected-promise
|
|
[rtx cause]
|
|
(if cause
|
|
(js/Promise.reject cause)
|
|
(js/Promise.resolve rtx)))
|
|
|
|
(defcommand reject-offset
|
|
[rtx offset]
|
|
{:pre [(pos? offset)]}
|
|
((get-in rtx [:hooks :render-sync]))
|
|
(update-in rtx [:view :x] + offset))
|
|
|
|
(defcommand replace-owner
|
|
[rtx]
|
|
(assoc-in rtx [:hooks :check] (fn [])))
|
|
|
|
(defn- context
|
|
[clock]
|
|
{:case {:id :commands/example}
|
|
:scene (builder/scene {:seed 1})
|
|
:view {:scale 1 :x 0 :y 0}
|
|
:hooks {:now #(deref clock)
|
|
:check (fn [])
|
|
:render-sync #(vswap! clock + 7)}})
|
|
|
|
(t/deftest ^:async measurement-uses-core-time
|
|
(let [clock (volatile! 0)
|
|
rtx (assoc-in (context clock) [:hooks :render-sync]
|
|
#(do
|
|
(vswap! clock + 7)
|
|
(js/queueMicrotask (fn [] (vswap! clock + 500)))))
|
|
{:keys [outcome events context]}
|
|
(await
|
|
(cmd/execute
|
|
rtx
|
|
(fn [rtx]
|
|
(let [input (-> (js/Promise.resolve rtx)
|
|
(.then (fn [rtx]
|
|
(vswap! clock + 1000)
|
|
rtx)))]
|
|
(-> (camera/render-sync* input)
|
|
(.then (fn [rtx]
|
|
(vswap! clock + 2000)
|
|
rtx)))))))]
|
|
(t/is (= :completed outcome))
|
|
(t/is (= (:view rtx) (:view context)))
|
|
(t/is (= [{:command ::camera/render-sync
|
|
:metrics {:command/elapsed [7]}}]
|
|
events))
|
|
(t/is (= 3507 @clock))))
|
|
|
|
(t/deftest ^:async composite-preserves-view-and-selected-observations
|
|
(doseq [promised [false true]]
|
|
(let [clock (volatile! 0)
|
|
cost (volatile! 0)
|
|
rtx (assoc-in (context clock) [:hooks :render-sync]
|
|
#(vswap! clock + (vswap! cost + 7)))
|
|
{:keys [outcome context events]}
|
|
(await (cmd/execute rtx #(selected-renders
|
|
(if promised (js/Promise.resolve %) %))))]
|
|
(t/is (= :completed outcome))
|
|
(t/is (= {:scale 1 :x 10 :y 5} (:view context)))
|
|
(t/is (= [{:command ::camera/render-sync
|
|
:metrics {:command/elapsed [7]}}
|
|
{:command ::camera/render-sync
|
|
:metrics {:command/elapsed [21]}}]
|
|
events))
|
|
(t/is (= 42 @clock)))))
|
|
|
|
(t/deftest measured-command-documentation
|
|
(t/is (.startsWith (:doc (meta #'camera/render-sync*))
|
|
"Measured variant of `render-sync`."))
|
|
(t/is (= "Measured variant of `unexpected-promise`."
|
|
(:doc (meta #'unexpected-promise*)))))
|
|
|
|
(t/deftest ^:async zero-duration-is-a-raw-observation
|
|
(let [rtx (context (volatile! 0))
|
|
result (await (cmd/execute rtx #(move* % {:dx 2 :dy 3})))]
|
|
(t/is (= :completed (:outcome result)))
|
|
(t/is (= {:scale 1 :x 2 :y 3} (get-in result [:context :view])))
|
|
(t/is (= [{:command ::move :metrics {:command/elapsed [0]}}]
|
|
(:events result)))))
|
|
|
|
(t/deftest ^:async failed-work-retains-earlier-observations
|
|
(let [unhandled (atom [])
|
|
observe #(swap! unhandled conj %)]
|
|
(.on js/process "unhandledRejection" observe)
|
|
(try
|
|
(doseq [cause [nil (js/Error. "rejected core")]]
|
|
(let [rtx (context (volatile! 0))
|
|
result (await (cmd/execute rtx #(-> %
|
|
(camera/render-sync*)
|
|
(unexpected-promise* cause))))]
|
|
(t/is (= :failed (:outcome result)))
|
|
(t/is (= ::cmd/async-result (:type (ex-data (:error result)))))
|
|
(t/is (= [{:command ::camera/render-sync
|
|
:metrics {:command/elapsed [7]}}]
|
|
(:events result)))))
|
|
(await (js/Promise. #(js/setImmediate %)))
|
|
(t/is (= [] @unhandled))
|
|
(finally
|
|
(.removeListener js/process "unhandledRejection" observe)))))
|
|
|
|
(t/deftest ^:async execution-failures-preserve-causes-and-earlier-observations
|
|
(doseq [cause [(js/Error. "renderer failed")
|
|
"renderer disconnected"
|
|
{:reason :renderer-disconnected}
|
|
(errors/fatal-error "recording failed" {} (js/Error. "storage"))]]
|
|
(let [clock (volatile! 0)
|
|
rtx (assoc-in (context clock) [:hooks :render-sync]
|
|
#(if (zero? @clock)
|
|
(vswap! clock + 7)
|
|
(throw cause)))
|
|
result (await (cmd/execute rtx #(-> %
|
|
(camera/render-sync*)
|
|
(camera/render-sync*))))
|
|
error (:error result)]
|
|
(t/is (= :failed (:outcome result)))
|
|
(t/is (errors/failure? error))
|
|
(if (errors/failure? cause)
|
|
(t/is (identical? cause error))
|
|
(t/is (= cause (:thrown (ex-data error)))))
|
|
(t/is (= [{:command ::camera/render-sync
|
|
:metrics {:command/elapsed [7]}}]
|
|
(:events result))))))
|
|
|
|
(t/deftest ^:async invalid-boundaries-fail-the-execution
|
|
(doseq [[input command expected]
|
|
[[#(assoc-in % [:view :scale] 0) camera/render-sync* ::cmd/invalid-context]
|
|
[#(assoc % :module #js {}) camera/render-sync* ::cmd/invalid-context]
|
|
[#(update % :hooks dissoc :render-sync) camera/render-sync* ::cmd/invalid-capabilities]
|
|
[identity #(reject-offset* % -1) ::cmd/invalid-arguments]]]
|
|
(let [clock (volatile! 0)
|
|
now-reads (volatile! 0)
|
|
rtx (assoc-in (context clock) [:hooks :now]
|
|
#(do (vswap! now-reads inc) @clock))
|
|
result (await (cmd/execute rtx #(command (input %))))]
|
|
(t/is (= :failed (:outcome result)))
|
|
(t/is (= expected (:type (ex-data (:error result)))))
|
|
(t/is (= [] (:events result)))
|
|
(t/is (zero? @now-reads))
|
|
(t/is (zero? @clock)))))
|
|
|
|
(t/deftest ^:async commands-enforce-execution-ownership
|
|
(let [rtx (context (volatile! 0))
|
|
result (await (cmd/execute rtx replace-owner*))
|
|
cause (await (.then (camera/render-sync* rtx) (fn [_] nil) identity))]
|
|
(t/is (= :failed (:outcome result)))
|
|
(t/is (= ::cmd/ownership-lost (:type (ex-data (:error result)))))
|
|
(t/is (= [] (:events result)))
|
|
(t/is (= ::cmd/missing-execution (:type (ex-data cause))))))
|
|
|
|
(t/deftest ^:async completed-context-starts-a-new-execution
|
|
(let [first-result (await (cmd/execute (context (volatile! 0))
|
|
#(move* % {:dx 10 :dy 5})))
|
|
rtx (:context first-result)
|
|
late-error (await (.then (camera/render-sync* rtx)
|
|
(fn [_] nil)
|
|
identity))
|
|
next-result (await (cmd/execute rtx camera/render-sync*))]
|
|
(t/is (= ::cmd/inactive-execution (:type (ex-data late-error))))
|
|
(t/is (= :completed (:outcome next-result)))
|
|
(t/is (= {:scale 1 :x 10 :y 5} (get-in next-result [:context :view])))
|
|
(t/is (= [{:command ::camera/render-sync
|
|
:metrics {:command/elapsed [7]}}]
|
|
(:events next-result)))))
|