mirror of
https://github.com/penpot/penpot.git
synced 2026-11-09 11:30:54 +00:00
:recyle: Consolidate error diagnostics
AI-assisted-by: gpt-6.1-sol, deepseek-flash
This commit is contained in:
parent
e24d4e8a98
commit
10ad433c78
@ -48,6 +48,40 @@
|
||||
|
||||
#?(:cljs
|
||||
(do
|
||||
(def ^:private schema:cause-value
|
||||
[:or nil? boolean? keyword? number? string?])
|
||||
|
||||
(def schema:cause-detail
|
||||
"One level of a portable exception description."
|
||||
[:map {:closed true}
|
||||
[:message string?]
|
||||
[:fn {:optional true} schema:cause-value]
|
||||
[:code {:optional true} schema:cause-value]
|
||||
[:type {:optional true} schema:cause-value]
|
||||
[:hint {:optional true} schema:cause-value]])
|
||||
|
||||
(def schema:diagnostic
|
||||
"Failure location and message with optional cause and partial evidence."
|
||||
[:map
|
||||
[:phase string?]
|
||||
[:message string?]
|
||||
[:status {:optional true} [:enum "failed" "stale"]]
|
||||
[:cause {:optional true} [:vector schema:cause-detail]]
|
||||
[:partial {:optional true} map?]
|
||||
[:reason {:optional true} :any]
|
||||
[:deadline-exceeded {:optional true} boolean?]])
|
||||
|
||||
(defn project-diagnostic
|
||||
"Returns the diagnostic fields of an execution envelope.
|
||||
|
||||
Args:
|
||||
evidence: Plain execution evidence.
|
||||
|
||||
Returns:
|
||||
Failure status, location, message and available diagnostic details."
|
||||
[evidence]
|
||||
(select-keys evidence [:status :phase :message :cause :partial :reason :deadline-exceeded]))
|
||||
|
||||
(def ^:private fatal-cause-depth 8)
|
||||
(def ^:private evidence-cause-depth 3)
|
||||
(def ^:private max-cause-chars 500)
|
||||
|
||||
@ -8,8 +8,15 @@
|
||||
"Projects browser phase evidence into saved attempts."
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[benches.render-wasm.errors :as errors]
|
||||
[benches.render-wasm.result :as result]))
|
||||
|
||||
(def ^:private schema:response
|
||||
[:multi {:dispatch :status}
|
||||
["ok" [:map [:status [:= "ok"]]]]
|
||||
["failed" (sm/merge errors/schema:diagnostic [:map [:status [:= "failed"]]])]
|
||||
["stale" [:map {:closed true} [:status [:= "stale"]]]]])
|
||||
|
||||
(defn partial-phases
|
||||
"Returns the valid prefix of delivered phase history.
|
||||
|
||||
@ -33,6 +40,31 @@
|
||||
out)
|
||||
out)))
|
||||
|
||||
(defn check-response
|
||||
"Returns checked bridge evidence or an invalid-output failure.
|
||||
|
||||
Stale responses retain their status and gain an ownership diagnostic.
|
||||
Malformed responses retain their valid phase prefix and raw response.
|
||||
|
||||
Args:
|
||||
phase: The preparation or attempt receiving the response.
|
||||
preparation-id: Expected owner of delivered phase blocks.
|
||||
response: Decoded bridge envelope.
|
||||
|
||||
Returns:
|
||||
Execution evidence with a known status and diagnostic fields on failure."
|
||||
[phase preparation-id response]
|
||||
(if (sm/validate schema:response response)
|
||||
(if (= "stale" (:status response))
|
||||
(assoc response :phase (name phase) :message "browser context was superseded")
|
||||
response)
|
||||
(assoc (errors/fail-data
|
||||
(errors/evidence :invalid-output
|
||||
(ex-info "invalid bridge response"
|
||||
{:type ::invalid-response :partial {:response response}})))
|
||||
:phases (partial-phases (:phases response) preparation-id
|
||||
(if (= :preparation phase) [:preparation] [:main :reset])))))
|
||||
|
||||
(defn attempt
|
||||
"Returns an attempt with raw phase histories and available failure evidence.
|
||||
|
||||
@ -49,5 +81,5 @@
|
||||
:outcome (if (= "ok" (:status evidence)) :completed :failed)
|
||||
:phases (or (:phases evidence) [])}
|
||||
(not= "ok" (:status evidence))
|
||||
(assoc :failure (select-keys evidence [:status :phase :message :cause]))
|
||||
(assoc :failure (dissoc (errors/project-diagnostic evidence) :partial))
|
||||
(:partial evidence) (assoc :partial (:partial evidence))))
|
||||
|
||||
@ -56,17 +56,6 @@
|
||||
(contains? (ex-data cause) ::browser/deadline-exceeded)
|
||||
(assoc :deadline-exceeded (::browser/deadline-exceeded (ex-data cause)))))
|
||||
|
||||
(defn- failure-evidence
|
||||
"Projects a bridge failure response into diagnostics evidence."
|
||||
[evidence fallback-phase]
|
||||
(if (map? evidence)
|
||||
(cond-> (select-keys evidence [:status :phase :message :cause :partial :phases :reason :deadline-exceeded])
|
||||
(nil? (:status evidence)) (assoc :status "failed")
|
||||
(nil? (:phase evidence)) (assoc :phase (name fallback-phase)))
|
||||
{:status "failed"
|
||||
:phase (name fallback-phase)
|
||||
:message (str evidence)}))
|
||||
|
||||
(defn- interruption
|
||||
[signal]
|
||||
(when-let [name (process/signal-name signal)]
|
||||
@ -142,7 +131,7 @@
|
||||
|
||||
(defn- record-failure
|
||||
[state case-id stage evidence]
|
||||
(let [failure (cond-> (assoc (select-keys evidence [:status :phase :message :cause :partial :reason])
|
||||
(let [failure (cond-> (assoc (errors/project-diagnostic evidence)
|
||||
:stage stage)
|
||||
(some? case-id) (assoc :case-id case-id))]
|
||||
(reset! (:harness-failed? state) true)
|
||||
@ -217,52 +206,56 @@
|
||||
(defn- ^:async prepare-case
|
||||
[state case page deadline warmup?]
|
||||
(check-deadline deadline)
|
||||
(let [build-id (:id (:selected-build state))
|
||||
(let [build-id (:id (:selected-build state))
|
||||
preparation-id (str (random-uuid))
|
||||
response (attempt
|
||||
(await ((get-in state [:services :prepare])
|
||||
page
|
||||
(transit/encode-str
|
||||
{:case case
|
||||
:preparation-id preparation-id
|
||||
:timeout-ms (max 1 (process/remaining deadline))
|
||||
:module-url (renderer-url build-id "render-wasm.js")
|
||||
:wasm-url (renderer-url build-id "render-wasm.wasm")})
|
||||
{:deadline deadline :signal (:signal state) :phase :preparation})))
|
||||
accepted (when (and (not (failure? response)) (= "ok" (:status response)))
|
||||
(attempt
|
||||
(let [preparation (assoc (:preparation response) :warmup? warmup?)]
|
||||
(when-not (and (= preparation-id (:preparation-id preparation))
|
||||
(= (:id case) (:case-id preparation))
|
||||
(not (:failure preparation)))
|
||||
(throw (ex-info "preparation success response is inconsistent" {})))
|
||||
(record-preparation state preparation)
|
||||
preparation)))]
|
||||
response (attempt
|
||||
(measurement/check-response
|
||||
:preparation preparation-id
|
||||
(await ((get-in state [:services :prepare])
|
||||
page
|
||||
(transit/encode-str
|
||||
{:case case
|
||||
:preparation-id preparation-id
|
||||
:timeout-ms (max 1 (process/remaining deadline))
|
||||
:module-url (renderer-url build-id "render-wasm.js")
|
||||
:wasm-url (renderer-url build-id "render-wasm.wasm")})
|
||||
{:deadline deadline :signal (:signal state) :phase :preparation}))))
|
||||
accepted (when (and (not (failure? response)) (= "ok" (:status response)))
|
||||
(attempt
|
||||
(let [preparation (assoc (:preparation response) :warmup? warmup?)]
|
||||
(when-not (and (= preparation-id (:preparation-id preparation))
|
||||
(= (:id case) (:case-id preparation))
|
||||
(not (:failure preparation)))
|
||||
(throw (ex-info "preparation success response is inconsistent" {})))
|
||||
(record-preparation state preparation)
|
||||
preparation)))]
|
||||
(if (and accepted (not (failure? accepted)))
|
||||
{:preparation accepted}
|
||||
(let [failure (cond
|
||||
(failure? accepted) (assoc (cause-evidence accepted :invalid-output)
|
||||
:partial {:response response})
|
||||
(failure? response) (cause-evidence response :preparation)
|
||||
:else (failure-evidence response :preparation))
|
||||
:else response)
|
||||
phases (if (failure? accepted) (get-in response [:preparation :phases]) (:phases failure))]
|
||||
(record-preparation state {:preparation-id preparation-id :case-id (:id case)
|
||||
:warmup? warmup?
|
||||
:phases (measurement/partial-phases phases preparation-id [:preparation])
|
||||
:diagnostics (or (get-in failure [:partial :setup]) {})
|
||||
:failure (dissoc failure :phases)})
|
||||
:failure (errors/project-diagnostic failure)})
|
||||
{:failure failure :preparation-id preparation-id}))))
|
||||
|
||||
(defn- ^:async run-attempt
|
||||
"Returns the bridge evidence of one attempt, or the failure."
|
||||
[state page preparation-id deadline]
|
||||
(attempt
|
||||
(await ((get-in state [:services :run])
|
||||
page
|
||||
(transit/encode-str
|
||||
{:preparation-id preparation-id
|
||||
:timeout-ms (max 1 (process/remaining deadline))})
|
||||
{:deadline deadline :signal (:signal state) :phase :attempt}))))
|
||||
(measurement/check-response
|
||||
:attempt preparation-id
|
||||
(await ((get-in state [:services :run])
|
||||
page
|
||||
(transit/encode-str
|
||||
{:preparation-id preparation-id
|
||||
:timeout-ms (max 1 (process/remaining deadline))})
|
||||
{:deadline deadline :signal (:signal state) :phase :attempt})))))
|
||||
|
||||
(defn- ^:async recycle-browser
|
||||
[state]
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as transit]
|
||||
[benches.render-wasm.codec :as codec]
|
||||
[benches.render-wasm.errors :as errors]
|
||||
[benches.render-wasm.features.plan :as feature-plan]
|
||||
[benches.render-wasm.quantities :as quantities]
|
||||
[benches.render-wasm.report.analysis :as analysis]))
|
||||
@ -46,7 +47,7 @@
|
||||
[:effective-graphics {:optional true} map?]
|
||||
[:diagnostics map?]
|
||||
[:phases [:vector schema:phase]]
|
||||
[:failure {:optional true} map?]])
|
||||
[:failure {:optional true} errors/schema:diagnostic]])
|
||||
|
||||
(def schema:attempt
|
||||
"One chronological execution; failure consumes a slot and may retain evidence."
|
||||
@ -57,7 +58,7 @@
|
||||
[:warmup? boolean?]
|
||||
[:outcome [:enum :completed :failed]]
|
||||
[:phases [:vector schema:phase]]
|
||||
[:failure {:optional true} map?]
|
||||
[:failure {:optional true} errors/schema:diagnostic]
|
||||
[:partial {:optional true} map?]])
|
||||
|
||||
(def schema:metadata
|
||||
@ -70,16 +71,19 @@
|
||||
[:environment {:optional true} [:map-of :string :string]]]]
|
||||
[:environment map?]
|
||||
[:scored? boolean?]
|
||||
[:diagnostics {:optional true} map?]])
|
||||
[:diagnostics {:optional true}
|
||||
[:map [:failures {:optional true} [:vector errors/schema:diagnostic]]]]])
|
||||
|
||||
(def schema:termination
|
||||
"A run's current state or final reason, with optional diagnostic details."
|
||||
[:map [:reason keyword?]])
|
||||
[:map
|
||||
[:reason keyword?]
|
||||
[:failure {:optional true} errors/schema:diagnostic]])
|
||||
|
||||
(def schema:case-outcome
|
||||
[:map {:closed true}
|
||||
[:outcome [:enum :completed :failed :timed-out :interrupted]]
|
||||
[:failure {:optional true} map?]])
|
||||
[:failure {:optional true} errors/schema:diagnostic]])
|
||||
|
||||
(def schema:run
|
||||
"schema:run stores saved case definitions, independent of any registry.
|
||||
@ -194,7 +198,7 @@
|
||||
[:run {:optional true} [:map [:run-id string?] [:record-path string?] [:log-path string?]
|
||||
[:exit-code {:optional true} :int]]]
|
||||
[:effective-features {:optional true} [:vector string?]]
|
||||
[:failure {:optional true} :map]
|
||||
[:failure {:optional true} errors/schema:diagnostic]
|
||||
[:reason {:optional true} string?]]]]
|
||||
[:edges
|
||||
[:vector
|
||||
@ -202,7 +206,9 @@
|
||||
[:from string?] [:to string?] [:feature string?]
|
||||
[:status [:enum :planned :completed :failed :unattempted]]
|
||||
[:path {:optional true} string?]
|
||||
[:failure {:optional true} :map]
|
||||
[:failure {:optional true}
|
||||
[:or errors/schema:diagnostic
|
||||
[:map {:closed true} [:unacknowledged-differences [:int {:min 1}]]]]]
|
||||
[:reason {:optional true} string?]]]]
|
||||
[:warnings [:vector string?]]])
|
||||
|
||||
|
||||
@ -279,7 +279,7 @@
|
||||
(mapv (fn [[index configuration]]
|
||||
(if (zero? index)
|
||||
(assoc (completed-configuration configuration)
|
||||
:status :failed :failure {:phase :run}
|
||||
:status :failed :failure {:phase "run" :message "run interrupted"}
|
||||
:reason "SIGINT")
|
||||
(assoc configuration :status :unattempted
|
||||
:reason "stopped after failure")))
|
||||
@ -288,7 +288,7 @@
|
||||
(mapv (fn [[index edge]]
|
||||
(if (zero? index)
|
||||
(assoc edge :status :failed :path "/comparison.transit"
|
||||
:failure {:message "partial"})
|
||||
:failure {:phase "comparison" :message "partial"})
|
||||
(assoc edge :status :unattempted
|
||||
:reason "stopped after failure")))
|
||||
(map-indexed vector (:edges record))))))
|
||||
@ -489,7 +489,7 @@
|
||||
:warmup? false
|
||||
:outcome :failed
|
||||
:phases []
|
||||
:failure {:message "fixture failure"}})
|
||||
:failure {:phase "main" :message "fixture failure"}})
|
||||
(let [prep (preparation case-id digest)]
|
||||
(-> run
|
||||
(result/record-preparation prep)
|
||||
@ -503,7 +503,7 @@
|
||||
run
|
||||
cases))
|
||||
termination (case kind
|
||||
:failed {:reason :failed :failure {:message "fixture failure"}}
|
||||
:failed {:reason :failed :failure {:phase "main" :message "fixture failure"}}
|
||||
:zero-attempts {:reason :incomplete}
|
||||
:interrupted {:reason :interrupted :signal "SIGINT"}
|
||||
{:reason :completed})
|
||||
|
||||
@ -17,8 +17,13 @@
|
||||
[benches.render-wasm.cases :as cases]
|
||||
[benches.render-wasm.declarations :as decl]
|
||||
[benches.render-wasm.errors :as errors]
|
||||
[benches.render-wasm.measurement :as measurement]
|
||||
[benches.render-wasm.report.format :as format]
|
||||
[benches.render-wasm.report.summarize :as summary]
|
||||
[benches.render-wasm.result :as result]
|
||||
[benches.render-wasm.runtime.camera :as camera]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
[cljs.test :as t :include-macros true]
|
||||
[clojure.string :as str]))
|
||||
|
||||
;; Forward declaration: entry-test helpers below build args against the
|
||||
;; fake Emscripten factory URL defined alongside the fake-DOM harness.
|
||||
@ -743,19 +748,49 @@
|
||||
:preparation {:callback #(work :preparation %)}
|
||||
:reset #(work :reset %)}
|
||||
#(work :main %))
|
||||
(let [request (-> (transit/decode-str (scene-args 7 {}))
|
||||
(assoc :preparation-id "phase-failure")
|
||||
(transit/encode-str))
|
||||
prepared (transit/decode-str (await (browser/prepare-scene-request request)))
|
||||
run (transit/encode-str {:preparation-id "phase-failure"})
|
||||
evidence (if (= failed-phase :preparation)
|
||||
prepared
|
||||
(transit/decode-str (await (browser/run-prepared run))))
|
||||
stopped (transit/decode-str (await (browser/run-prepared run)))
|
||||
blocks (:phases evidence)]
|
||||
(let [request (-> (transit/decode-str (scene-args 7 {}))
|
||||
(assoc :preparation-id "phase-failure")
|
||||
(transit/encode-str))
|
||||
prepared (transit/decode-str (await (browser/prepare-scene-request request)))
|
||||
run (transit/encode-str {:preparation-id "phase-failure"})
|
||||
evidence (if (= failed-phase :preparation)
|
||||
prepared
|
||||
(transit/decode-str (await (browser/run-prepared run))))
|
||||
stopped (transit/decode-str (await (browser/run-prepared run)))
|
||||
blocks (:phases evidence)
|
||||
diagnostic (errors/project-diagnostic evidence)
|
||||
preparation (if (= failed-phase :preparation)
|
||||
{:preparation-id "phase-failure" :case-id :rects/lifecycle
|
||||
:warmup? false :diagnostics (get-in evidence [:partial :setup])
|
||||
:phases blocks :failure diagnostic}
|
||||
(assoc (:preparation prepared) :warmup? false))
|
||||
saved (-> (cond->
|
||||
(-> (result/create-run
|
||||
{:run-id "phase-failure" :started-at "start"
|
||||
:plan {:cases [(load-case)] :warmups 0 :repetitions 1}
|
||||
:metadata {:git {:sha nil :dirty nil} :build {:functional {}}
|
||||
:environment {} :scored? true}})
|
||||
(result/record-preparation preparation))
|
||||
(or (= context :fresh) (not= failed-phase :preparation))
|
||||
(result/record-attempt
|
||||
(measurement/attempt :rects/lifecycle "phase-failure" false
|
||||
(if (= failed-phase :preparation)
|
||||
(dissoc evidence :phases)
|
||||
evidence))))
|
||||
(result/record-case-outcome :rects/lifecycle
|
||||
{:outcome :failed})
|
||||
(result/finish-run "end" {:reason :failed})
|
||||
result/encode result/decode)
|
||||
stored (if (= failed-phase :preparation)
|
||||
(first (:preparations saved))
|
||||
(first (:attempts saved)))]
|
||||
(t/is (= "failed" (:status evidence)))
|
||||
(t/is (= (name failed-phase) (:phase evidence)))
|
||||
(t/is (= "phase fixture failed" (:message evidence)))
|
||||
(t/is (= (:cause evidence) (get-in stored [:failure :cause])))
|
||||
(t/is (= blocks (:phases stored)))
|
||||
(t/is (= "phase-failure" (:preparation-id stored)))
|
||||
(t/is (str/includes? (format/format-run (summary/summarize saved)) "phase fixture failed"))
|
||||
(t/is (= (case failed-phase
|
||||
:preparation [:preparation]
|
||||
:main [:preparation :main]
|
||||
|
||||
@ -54,7 +54,8 @@
|
||||
run (measurement/attempt :saved/pan prep warmup?
|
||||
(cond-> {:status (if (= :completed outcome) "ok" "failed")
|
||||
:phases phases}
|
||||
(= :failed outcome) (assoc :message "renderer failed")))))
|
||||
(= :failed outcome) (assoc :phase (name (:phase (last phases)))
|
||||
:message "renderer failed")))))
|
||||
|
||||
(defn- sample-run
|
||||
[values]
|
||||
@ -223,7 +224,7 @@
|
||||
(let [run (-> (new-run 0 1 :reuse)
|
||||
(result/record-preparation
|
||||
(assoc (preparation :prep [(event :test/setup [9])])
|
||||
:failure {:message "initial Full failed"})))
|
||||
:failure {:phase "preparation" :message "initial Full failed"})))
|
||||
report (first (:cases (summary/summarize run)))
|
||||
metric (get-in report [:metrics [:preparation :test/setup :command/elapsed]])]
|
||||
(t/is (= {:executions 1 :completed 1 :failed 0 :warmup 0} (get-in report [:phases :preparation])))
|
||||
@ -255,7 +256,7 @@
|
||||
initial values)
|
||||
(result/record-attempt
|
||||
(measurement/attempt :saved/pan :prep false
|
||||
{:status "failed" :message "render failed"
|
||||
{:status "failed" :phase "main" :message "render failed"
|
||||
:cause [{:message "WASM trap"}]
|
||||
:partial {:clock js/NaN}
|
||||
:phases [(assoc (block :main :prep [(event :test/pan [99])])
|
||||
@ -294,7 +295,7 @@
|
||||
(let [failed (-> (new-run 0 1 :reuse)
|
||||
(result/record-preparation {:preparation-id :prep :case-id :saved/pan :warmup? false
|
||||
:diagnostics {} :phases []
|
||||
:failure {:message "setup failed"}})
|
||||
:failure {:phase "preparation" :message "setup failed"}})
|
||||
(result/finish-run "end" {:reason :preparation-error}))]
|
||||
(t/is (str/includes? (format/format-run (summary/summarize (result/decode (result/encode failed))))
|
||||
"setup failed"))))
|
||||
@ -409,7 +410,7 @@
|
||||
(result/record-preparation
|
||||
(-> (preparation :prep [(event :test/setup [9])])
|
||||
(dissoc :fingerprint :effective-graphics)
|
||||
(assoc :failure {:message "setup failed"}))))]
|
||||
(assoc :failure {:phase "preparation" :message "setup failed"}))))]
|
||||
(doseq [options [{} {:allow-scene-differences true}]]
|
||||
(t/is (= ::compare/incompatible (error-type #(compare/compare-runs run run options)))))
|
||||
(let [report (compare/compare-runs run run {:allow-all-differences true})]
|
||||
@ -422,7 +423,7 @@
|
||||
(doseq [bad [(assoc-in run [:attempts 0 :attempt-id] 3)
|
||||
(assoc-in run [:attempts 0 :phases] [])
|
||||
(assoc-in run [:preparations 0 :phases] [])
|
||||
(assoc-in run [:preparations 0 :failure] {:message "setup failed"})
|
||||
(assoc-in run [:preparations 0 :failure] {:phase "preparation" :message "setup failed"})
|
||||
(update-in run [:attempts 0] assoc :outcome :failed
|
||||
:phases [(assoc (block :main :prep []) :outcome :failed)
|
||||
(block :reset :prep [])])
|
||||
@ -434,6 +435,30 @@
|
||||
(t/is (= ::result/invalid-record
|
||||
(error-type #(result/decode (transit/encode-str bad))))))))
|
||||
|
||||
(t/deftest saved-failures-reject-malformed-diagnostics
|
||||
(let [diagnostic {:phase "main" :message "renderer failed"}
|
||||
failed-preparation (-> (new-run 0 1 :reuse)
|
||||
(result/record-preparation (assoc (preparation :prep []) :failure diagnostic))
|
||||
(result/finish-run "end" {:reason :failed})
|
||||
result/encode result/decode)
|
||||
failed-attempt (-> (new-run 0 1 :reuse)
|
||||
(result/record-preparation (preparation :prep []))
|
||||
(add-attempt :prep false :failed [(assoc (block :main :prep []) :outcome :failed)])
|
||||
(result/record-case-outcome :saved/pan {:outcome :failed :failure diagnostic})
|
||||
(result/finish-run "end" {:reason :failed :failure diagnostic})
|
||||
(assoc-in [:metadata :diagnostics :failures] [diagnostic])
|
||||
result/encode result/decode)]
|
||||
(doseq [[run path] [[failed-preparation [:preparations 0 :failure]]
|
||||
[failed-attempt [:attempts 0 :failure]]
|
||||
[failed-attempt [:case-outcomes :saved/pan :failure]]
|
||||
[failed-attempt [:termination :failure]]
|
||||
[failed-attempt [:metadata :diagnostics :failures 0]]]
|
||||
invalid [{:phase 42 :message "bad location"}
|
||||
{:phase "main" :message nil}
|
||||
{:phase "main" :message "bad cause" :cause [42]}]]
|
||||
(t/is (= ::result/invalid-record
|
||||
(error-type #(result/decode (transit/encode-str (assoc-in run path invalid)))))))))
|
||||
|
||||
(t/deftest case-outcomes-validate-completion-and-close-the-recording-boundary
|
||||
(let [run (sample-run [1])
|
||||
completed (result/record-case-outcome run :saved/pan {:outcome :completed})
|
||||
|
||||
@ -573,7 +573,8 @@
|
||||
:invalid-preparation (update response :preparation dissoc :fingerprint)
|
||||
:contradictory-preparation
|
||||
(-> response
|
||||
(assoc-in [:preparation :failure] {:message "callback failed"})
|
||||
(assoc-in [:preparation :failure]
|
||||
{:phase "preparation" :message "callback failed"})
|
||||
(assoc-in [:preparation :phases 0 :outcome] :failed))
|
||||
response)
|
||||
response)))
|
||||
@ -585,8 +586,14 @@
|
||||
(if (= :case/a case-id)
|
||||
(case phase
|
||||
:main {:status "failed" :phase "main" :message "main failed"
|
||||
:cause [{:message "WASM trap" :code "E_RENDER"}]
|
||||
:reason :renderer-rejected :deadline-exceeded false
|
||||
:partial {:completed-call 7}
|
||||
:phases [(assoc block :outcome :failed)]}
|
||||
:reset {:status "failed" :phase "reset" :message "reset failed"
|
||||
:cause [{:message "WASM trap" :code "E_RENDER"}]
|
||||
:reason :renderer-rejected :deadline-exceeded false
|
||||
:partial {:completed-call 7}
|
||||
:phases [block (assoc block :phase :reset :outcome :failed)]}
|
||||
:invalid-main {:status "ok" :phases []}
|
||||
:invalid-reset {:status "ok" :phases [block {:phase :reset}]})
|
||||
@ -601,6 +608,11 @@
|
||||
(t/is (= {:warmups 0 :repetitions (if (and prep-failed? (= :reuse context)) 2 1)}
|
||||
(get-in saved [:remaining :case/a])))
|
||||
(t/is (= [:completed :completed] (mapv :outcome (attempts-for saved :case/b))))
|
||||
(when (#{:main :reset} phase)
|
||||
(t/is (= {:cause [{:message "WASM trap" :code "E_RENDER"}]
|
||||
:reason :renderer-rejected :deadline-exceeded false}
|
||||
(select-keys (:failure (first attempts)) [:cause :reason :deadline-exceeded])))
|
||||
(t/is (= {:completed-call 7} (:partial (first attempts)))))
|
||||
(when (= :contradictory-preparation phase)
|
||||
(t/is (= "invalid-output" (get-in (first (preparations-for saved :case/a)) [:failure :phase]))))
|
||||
(when (= :invalid-reset phase)
|
||||
@ -609,6 +621,65 @@
|
||||
(when (#{:invalid-main :invalid-reset} phase)
|
||||
(t/is (= "invalid-output" (get-in (first attempts) [:failure :phase])))))))
|
||||
|
||||
(t/deftest ^:async malformed-bridge-diagnostics-stop-the-case-with-delivered-evidence
|
||||
(doseq [stage [:prepare :run]
|
||||
evidence [nil "unexpected response"
|
||||
{:status "unknown" :phase "main" :message "unexpected status"}
|
||||
{:status "failed" :phase "main"}
|
||||
{:status "failed" :phase "main" :message "bad cause" :cause [:invalid]}]]
|
||||
(let [base (fake-browser {})
|
||||
service (get-in base [:service stage])
|
||||
delivered (atom nil)
|
||||
fake (assoc-in base [:service stage]
|
||||
(fn [page request bounds]
|
||||
(let [response (service page request bounds)
|
||||
params (transit/decode-str request)
|
||||
case-id (if (= :prepare stage)
|
||||
(get-in params [:case :id])
|
||||
(get-in @(:state base) [:preparations (:preparation-id params)]))]
|
||||
(if (= :case/a case-id)
|
||||
(let [phases (if (= :prepare stage)
|
||||
(get-in response [:preparation :phases])
|
||||
(conj (:phases response) {:phase :reset}))
|
||||
rejected (cond-> evidence
|
||||
(map? evidence) (assoc :phases phases))]
|
||||
(reset! delivered rejected)
|
||||
(transit/decode-str (transit/encode-str rejected)))
|
||||
response))))
|
||||
response (await (execute fake [(collected :case/a :reuse) (collected :case/b :reuse)]
|
||||
(assoc (options fake (output-dir)) :repetitions 2)))
|
||||
saved (saved-record response)
|
||||
failed (if (= :prepare stage)
|
||||
(first (preparations-for saved :case/a))
|
||||
(first (attempts-for saved :case/a)))]
|
||||
(t/is (= 1 (:exit-code response)))
|
||||
(t/is (= "invalid-output" (get-in failed [:failure :phase])))
|
||||
(t/is (= "invalid bridge response" (get-in failed [:failure :message])))
|
||||
(t/is (= @delivered (if (= :prepare stage)
|
||||
(get-in failed [:failure :partial :response])
|
||||
(get-in failed [:partial :response]))))
|
||||
(t/is (= (if (map? evidence) [(first (:phases @delivered))] []) (:phases failed)))
|
||||
(t/is (= {:warmups 0 :repetitions (if (= :prepare stage) 2 1)}
|
||||
(get-in saved [:remaining :case/a])))
|
||||
(t/is (= [:completed :completed] (mapv :outcome (attempts-for saved :case/b))))
|
||||
(t/is (str/includes? (:report response) "invalid bridge response")))))
|
||||
|
||||
(t/deftest ^:async stale-bridge-results-stop-the-case-with-ownership-diagnostics
|
||||
(doseq [stage [:prepare :run]]
|
||||
(let [base (fake-browser {})
|
||||
fake (assoc-in base [:service stage] (constantly {:status "stale"}))
|
||||
response (await (execute fake [(collected :case/a :reuse)]
|
||||
(assoc (options fake (output-dir)) :repetitions 2)))
|
||||
saved (saved-record response)
|
||||
failure (get-in saved [:case-outcomes :case/a :failure])]
|
||||
(t/is (= 1 (:exit-code response)))
|
||||
(t/is (= :failed (get-in saved [:case-outcomes :case/a :outcome])))
|
||||
(t/is (= "stale" (:status failure)))
|
||||
(t/is (= (if (= :prepare stage) "preparation" "attempt") (:phase failure)))
|
||||
(t/is (= "browser context was superseded" (:message failure)))
|
||||
(t/is (= {:warmups 0 :repetitions (if (= :prepare stage) 2 1)}
|
||||
(get-in saved [:remaining :case/a]))))))
|
||||
|
||||
(t/deftest ^:async runner-terminates-a-blocked-page-and-completes-the-next-case
|
||||
(let [dir (build-dir)
|
||||
owners (atom [])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user