diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96d7af0..f596e17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,17 +6,19 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'temurin' java-version: '11' - - name: Setup Clojure CLI - uses: DeLaGuardo/setup-clojure@3.1 + - name: Setup Clojure CLI & Babashka + uses: DeLaGuardo/setup-clojure@9.5 with: - tools-deps: latest + cli: latest + bb: latest - name: Cache deps - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: | ~/.m2 @@ -30,15 +32,19 @@ jobs: clojure -A:check:test:cljs:coverage -P - name: Run check run: clojure -M:check:cljs - - name: Run tests for CLJ 1.10 + - name: Run tests for CLJ 1.11 run: clojure -M:test + - name: Run tests for CLJ 1.10 + run: clojure -M:1.10:test - name: Run tests for CLJ 1.9 run: clojure -M:1.9:test - name: Run tests for CLJ 1.8 run: clojure -M:1.8:test - name: Run CLJS tests on Node run: clojure -M:test:cljs:test-cljs + - name: Run tests on Babashka + run: bb test - name: Measure test coverage run: clojure -A:test:coverage - name: Upload coverage report to CodeCov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore index 96b2936..99a1bdd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ /classes /checkouts .cpcache/ -pom.xml -pom.xml.asc +/pom.xml +/pom.xml.asc *.jar *.class /.lein-* diff --git a/CHANGELOG.md b/CHANGELOG.md index 304ec7c..b0d5435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] +## [0.5.2] - 2022-11-05 +### Added +- Babashka support ([#17](https://github.com/athos/Postmortem/pull/17)) + +## [0.5.1] - 2022-06-21 +### Added +- Indexed session ([#12](https://github.com/athos/Postmortem/pull/12)) +- `merged-logs` ([#13](https://github.com/athos/Postmortem/pull/13)) + +### Fixed +- Fix override warning on Clojure 1.11 ([#16](https://github.com/athos/Postmortem/pull/16)) + ## [0.5.0] - 2021-03-31 ### Added - Simple logger implemented around one-shot session ([#11](https://github.com/athos/Postmortem/pull/11) @@ -27,7 +39,9 @@ All notable changes to this project will be documented in this file. This change ## [0.1.0] - 2019-12-09 - First release -[Unreleased]: https://github.com/athos/postmortem/compare/0.5.0...HEAD +[Unreleased]: https://github.com/athos/postmortem/compare/0.5.2...HEAD +[0.5.2]: https://github.com/athos/postmortem/compare/0.5.1...0.5.2 +[0.5.1]: https://github.com/athos/postmortem/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/athos/postmortem/compare/0.4.1...0.5.0 [0.4.1]: https://github.com/athos/postmortem/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/athos/postmortem/compare/0.3.0...0.4.0 diff --git a/README.md b/README.md index ce38ce0..e80350b 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ [![Clojars Project](https://img.shields.io/clojars/v/postmortem.svg)](https://clojars.org/postmortem) ![build](https://github.com/athos/Postmortem/workflows/build/badge.svg) [![codecov](https://codecov.io/gh/athos/postmortem/branch/master/graph/badge.svg)](https://codecov.io/gh/athos/postmortem) +[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://babashka.org) -A tiny data-oriented debugging tool for Clojure(Script), powered by transducers +A simple debug library for Clojure(Script) that features data-oriented logging and tracing ## Features -- Postmortem strongly encourages data-oriented debugging approaches +- Postmortem strongly encourages the data-oriented debugging approach - Logs are just Clojure data, so you can use DataScript, REBL or whatever tools for more sophisticated log analysis - [Integration with transducers](#integration-with-transducers) enables various flexible logging strategies - [Instrumentation](#instrumentation) on vars makes it easier to debug functions without touching their code -- Supports Clojure/ClojureScript/self-hosted ClojureScript +- Supports most of Clojure platforms (namely, Clojure, ClojureScript, self-hosted ClojureScript and Babashka) - Possible to use for debugging multi-threaded programs ## Synopsis @@ -75,6 +76,7 @@ A tiny data-oriented debugging tool for Clojure(Script), powered by transducers - [Handling sessions](#handling-sessions) - [Attaching a transducer](#attaching-a-transducer) - [`void-session`](#void-session) + - [Indexed sessions](#indexed-sessions) - [`make-unsafe-session`](#make-unsafe-session) - [Simple logger](#simple-logger) - [Instrumentation](#instrumentation) @@ -84,6 +86,7 @@ A tiny data-oriented debugging tool for Clojure(Script), powered by transducers - Clojure 1.8+, or - ClojureScript 1.10.238+, or +- Babashka v0.10.163+, or - Planck 2.24.0+, or - Lumo 1.10.1+ @@ -271,11 +274,11 @@ Note that once you call `stats`, all the log entries will be *completed*, as with the `logs` fn. For those who are using older versions (<= 0.4.0), `pm/stats` is the new name -for `pm/frequencies` added in 0.4.1. They can be used totally interchangeablly. +for `pm/frequencies` added in 0.4.1. They can be used totally interchangeably. Now `pm/stats` is recommended for primary use. Analogous to `logs`, `reset!` is useful to clear the whole log data at a time, -rathar than clearing each individual log entry one by one calling `reset-key!`: +rather than clearing each individual log entry one by one calling `reset-key!`: ```clojure (pm/logs) @@ -627,10 +630,10 @@ And for case 2, this (pm/dump :key (take 5)) ``` -is equavalent to: +is equivalent to: ```clojure -(pm/set-current-sesion! (pm/make-session)) +(pm/set-current-session! (pm/make-session)) (pm/dump :key (comp (drop 5) (take 5))) ``` @@ -662,7 +665,7 @@ Using it together with `with-session` disables logging temporarily: (pm/set-current-session! (pm/make-session)) (pm/spy>> :foo 1) -(pm/with-sesion (pm/void-session) +(pm/with-session (pm/void-session) (pm/spy>> :foo 2) (pm/spy>> :foo 3)) (pm/spy>> :foo 4) @@ -671,6 +674,91 @@ Using it together with `with-session` disables logging temporarily: ;=> [1 4] ``` +#### Indexed sessions + +When dealing with multiple log entries, it is sometimes useful to have a sequential +number (or index) for each log item throughout all the entries. + +An *indexed session* automatically adds an auto-increment index to each log item. +To create a new indexed session, use `make-indexed-session`: + +```clojure +(pm/set-current-session! (pm/make-indexed-session)) + +(pm/spy>> :foo 100) +(pm/spy>> :bar 101) +(pm/spy>> :foo 102) + +(pm/logs) +;=> {:foo [{:id 0 :val 100} +; {:id 2 :val 102}] +; :bar [{:id 1 :val 101}]} +``` + +Calling `reset!` on the indexed session resets the index: + +```clojure +(pm/spy>> :foo 100) +(pm/spy>> :foo 101) +(pm/log-for :foo) +;=> [{:id 0 :val 100} {:id 1 :val 101}] + +(pm/reset!) + +(pm/spy>> :foo 102) +(pm/spy>> :foo 103) +(pm/log-for :foo) +;=> [{:id 0 :val 102} {:id 1 :val 103}] +``` + +`make-indexed-session` takes an optional function to specify how the indexed +session will attach the index to each log item. + +The function must take two arguments, the index and the log item, and return +a new log item. The default function is `(fn [id item] {:id id :val item})`. + +The example below shows how it takes effect: + +```clojure +(pm/set-current-session! + (pm/make-indexed-session (fn [id item] [id item]))) + +(pm/spy>> :foo :a) +(pm/spy>> :foo :b) +(pm/spy>> :foo :c) +(pm/log-for :foo) +;=> [[0 :a] [1 :b] [2 :c]] + +(pm/set-current-session! + (pm/make-indexed-session #(assoc %2 :i %1))) + +(pm/spy>> :point {:x 100 :y 100}) +(pm/spy>> :point {:x 200 :y 200}) +(pm/spy>> :point {:x 300 :y 300}) +(pm/log-for :point) +;=> [{:i 0 :x 100 :y 100} +; {:i 1 :x 200 :y 200} +; {:i 2 :x 300 :y 300}] +``` + +The `indexed` function is another way to create an indexed session. +`(indexed )` creates a new indexed session based on another session. +In fact, `(make-indexed-session)` is equivalent to `(indexed (make-session))`. + +It's especially useful to make an session with base transducer into an indexed session: + +```clojure +(pm/set-current-session! + (pm/indexed (pm/make-session (take-while #(< (:id %) 3))))) + +(doseq [v [:a :b :c :d :e]] + (pm/spy>> :foo v)) +(pm/log-for :foo) +;=> [{:id 0 :val :a} +; {:id 1 :val :b} +; {:id 2 :val :c}] +``` + #### `make-unsafe-session` In Clojure, an ordinary session (created by `make-session`) is inherently diff --git a/bb.edn b/bb.edn new file mode 100644 index 0000000..4088e46 --- /dev/null +++ b/bb.edn @@ -0,0 +1,3 @@ +{:paths ["src" "test"] + :deps {local/deps {:local/root "."}} + :tasks {test postmortem.test-runner/-main}} diff --git a/build.clj b/build.clj new file mode 100644 index 0000000..bdbf474 --- /dev/null +++ b/build.clj @@ -0,0 +1,27 @@ +(ns build + (:require [clojure.tools.build.api :as b] + [org.corfield.build :as bb])) + +(def lib 'postmortem/postmortem) +(def version "0.5.2") +(def tag (b/git-process {:git-args "rev-parse HEAD"})) + +(defn clean [opts] + (bb/clean opts)) + +(defn jar [opts] + (-> opts + (assoc :src-pom "template/pom.xml" + :lib lib :version version :scm {:tag tag}) + (clean) + (bb/jar))) + +(defn install [opts] + (-> opts + (assoc :lib lib :version version) + (bb/install))) + +(defn deploy [opts] + (-> opts + (assoc :lib lib :version version) + (bb/deploy))) diff --git a/deps.edn b/deps.edn index 48fa2d1..7c9f6d8 100644 --- a/deps.edn +++ b/deps.edn @@ -1,5 +1,5 @@ {:paths ["src"] - :deps {org.clojure/clojure {:mvn/version "1.10.3"} + :deps {org.clojure/clojure {:mvn/version "1.11.1"} net.cgrand/macrovich {:mvn/version "0.2.1"}} :aliases {:check {:extra-deps {athos/clj-check @@ -7,14 +7,21 @@ :sha "0ca84df1357d71429243b99908303f45a934654c"}} :main-opts ["-m" "clj-check.check"]} :cljs {:extra-deps - {org.clojure/clojurescript {:mvn/version "1.10.773"}}} + {org.clojure/clojurescript {:mvn/version "1.11.57"}}} :test {:extra-paths ["test"] :main-opts ["-m" "postmortem.test-runner"]} - :coverage {:extra-deps {cloverage/cloverage {:mvn/version "1.2.2"}} + :coverage {:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}} :main-opts ["-m" "cloverage.coverage" "-p" "src" "-s" "test" "--codecov" "--ns-exclude-regex" "postmortem.instrument(.cljs)?"]} :test-cljs {:main-opts ["-m" "cljs.main" "-re" "node" "-m" "postmortem.test-runner"]} :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} - :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}}}} + :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} + :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} + :build {:deps + {io.github.clojure/tools.build + {:git/tag "v0.8.3" :git/sha "0d20256"} + io.github.seancorfield/build-clj + {:git/tag "v0.8.3" :git/sha "7ac1f8d"}} + :ns-default build}}} diff --git a/project.clj b/project.clj deleted file mode 100644 index 1f5a1cc..0000000 --- a/project.clj +++ /dev/null @@ -1,12 +0,0 @@ -(defproject postmortem "0.5.0" - :description "A tiny data-oriented debugging tool for Clojure(Script), powered by transducers" - :url "https://github.com/athos/Postmortem" - :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html"} - :dependencies [[org.clojure/clojure "1.10.3" :scope "provided"] - [org.clojure/clojurescript "1.10.773" :scope "provided"] - [net.cgrand/macrovich "0.2.1"]] - :plugins [[lein-eftest "0.5.9"]] - :eftest {:multithread? :vars} - :profiles {:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} - :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}}) diff --git a/src/postmortem/core.cljc b/src/postmortem/core.cljc index 37a8295..1194c3a 100644 --- a/src/postmortem/core.cljc +++ b/src/postmortem/core.cljc @@ -1,9 +1,9 @@ (ns postmortem.core (:refer-clojure :exclude [frequencies keys reset!]) (:require [clojure.core :as c] - #?(:clj [net.cgrand.macrovich :as macros]) [postmortem.protocols :as proto] - [postmortem.session :as session]) + [postmortem.session :as session] + #?(:clj [net.cgrand.macrovich :as macros])) #?(:cljs (:require-macros [net.cgrand.macrovich :as macros] [postmortem.core :refer [locals dump]]))) @@ -11,11 +11,11 @@ (defn session? "Returns true if x is a session." [x] - (satisfies? proto/ISession x)) + (satisfies? #?(:bb proto/ILogStorage :default proto/ISession) x)) (defn make-unsafe-session "Creates and returns a new thread-unsafe session. - Updates to a thread-unsafe session won't be synchronized among mulithreads. + Updates to a thread-unsafe session won't be synchronized among multithreads. If all updates to a session need to be synchronized, use make-session instead. In ClojureScript, make-unsafe-session is exactly the same as make-session." ([] (make-unsafe-session identity)) @@ -42,6 +42,26 @@ (let [session (session/void-session)] (fn [] session))) +(defn indexed + "Creates an indexed session based on the given session. + An indexed session manages an auto-incremental index and attaches it to each + log item. How the session attaches the index can be specified by a function f + passed as an optional argument. The function takes two arguments, the index + and the log item, and returns a new log item. The default function is + `(fn [id item] {:id id :val item})`." + {:added "0.5.1"} + ([session] (indexed session #(array-map :id %1 :val %2))) + ([session f] + (session/indexed session f))) + +(defn make-indexed-session + "Creates and returns a new indexed session. + Equivalent to `(indexed (make-session))` or `(indexed (make-session) f)`. + See the docstring for `indexed` for details." + {:added "0.5.1"} + ([] (indexed (make-session))) + ([f] (indexed (make-session) f))) + (def ^:dynamic *current-session* "Dynamic var bound to the current session. Don't use this directly, call (current-session) instead." @@ -122,9 +142,22 @@ (assert (session? session) "Invalid session specified") (logs* session))) +(defn merged-logs + "Merges all the log entries into a single vector. + This function optionally takes a function as an argument to specify how to + handle each log entry key. The function must take the log entry key and + the log item, and return a new log item. The default function is + `(fn [key item] item)`." + {:added "0.5.1"} + ([] (merged-logs (fn [_key val] val))) + ([f] (merged-logs (current-session) f)) + ([session f] + (into [] (mapcat (fn [[k v]] (map (partial f k) v))) + (logs* session)))) + (defn keys "Returns all the log entry keys that the session contains. - If session is ommited, the keys will be pulled from the current session." + If session is omitted, the keys will be pulled from the current session." ([] (keys (current-session))) ([session] (assert (session? session) "Invalid session specified") diff --git a/src/postmortem/instrument.cljc b/src/postmortem/instrument.cljc index 6c00ba5..dd672e3 100644 --- a/src/postmortem/instrument.cljc +++ b/src/postmortem/instrument.cljc @@ -2,7 +2,7 @@ (:require #?@(:clj [[net.cgrand.macrovich :as macros] [postmortem.instrument.clj :as clj]]) ;; necessary to ensure for CLJS that ns is loaded at runtime - [postmortem.instrument.core :as instr]) + postmortem.instrument.core) #?(:cljs (:require-macros [net.cgrand.macrovich :as macros] postmortem.instrument.cljs [postmortem.instrument :refer [instrument unstrument]]))) diff --git a/src/postmortem/instrument/clj.clj b/src/postmortem/instrument/clj.cljc similarity index 89% rename from src/postmortem/instrument/clj.clj rename to src/postmortem/instrument/clj.cljc index 3940e4d..fd57ca5 100644 --- a/src/postmortem/instrument/clj.clj +++ b/src/postmortem/instrument/clj.cljc @@ -2,10 +2,14 @@ (:require [clojure.string :as str] [postmortem.instrument.core :as instr] [postmortem.utils :refer [with-lock]]) - (:import [java.util.concurrent.locks ReentrantLock])) + #?@(:bb [] + :clj ((:import [java.util.concurrent.locks ReentrantLock])))) -(def ^:private ^ReentrantLock instrument-lock - (ReentrantLock.)) +#?(:bb + (def instrument-lock (Object.)) + :clj + (def ^:private ^ReentrantLock instrument-lock + (ReentrantLock.))) (defn- collectionize [x] (if (symbol? x) diff --git a/src/postmortem/instrument/cljs.cljc b/src/postmortem/instrument/cljs.cljc index 9d4af82..1cb85aa 100644 --- a/src/postmortem/instrument/cljs.cljc +++ b/src/postmortem/instrument/cljs.cljc @@ -1,8 +1,8 @@ (ns postmortem.instrument.cljs (:require [cljs.analyzer.api :as ana] [clojure.string :as str] - #?(:clj [net.cgrand.macrovich :as macros]) - [postmortem.instrument.core :as instr]) + [postmortem.instrument.core :as instr] + #?(:clj [net.cgrand.macrovich :as macros])) #?(:cljs (:require-macros [net.cgrand.macrovich :as macros] [postmortem.instrument.cljs :refer [instrument unstrument]]))) diff --git a/src/postmortem/instrument/core.cljc b/src/postmortem/instrument/core.cljc index 1b49701..7b0d0a2 100644 --- a/src/postmortem/instrument/core.cljc +++ b/src/postmortem/instrument/core.cljc @@ -44,7 +44,7 @@ {:raw to-wrapped :wrapped instrumented}) instrumented)))) -(defn unstrument-1* [sym v] +(defn unstrument-1* [_sym v] (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] (swap! instrumented-vars dissoc v) (let [current @v] diff --git a/src/postmortem/session.cljc b/src/postmortem/session.cljc index cf4dd37..bc446a4 100644 --- a/src/postmortem/session.cljc +++ b/src/postmortem/session.cljc @@ -1,7 +1,8 @@ (ns postmortem.session (:require [postmortem.protocols :as proto] - [postmortem.utils :refer [with-lock]]) - #?(:clj (:import [java.util.concurrent.locks ReentrantLock]))) + #?(:clj [postmortem.utils :refer [with-lock]])) + #?@(:bb [] + :clj ((:import [java.util.concurrent.locks ReentrantLock])))) (defn- xf->rf ([xform] (xf->rf xform conj)) @@ -61,72 +62,105 @@ (deftype ThreadUnsafeSession [xform ^:volatile-mutable logs] proto/ISession proto/ILogStorage - (-add-item! [this key xform' item] + (-add-item! [_ key xform' item] (set! logs (enqueue! logs key xform xform' item))) - (-keys [this] + (-keys [_] (keys logs)) - (-logs [this] + (-logs [_] (collect-logs logs (keys logs))) - (-logs [this keys] + (-logs [_ keys] (collect-logs logs keys)) - (-reset! [this] + (-reset! [_] (set! logs {})) - (-reset! [this keys] + (-reset! [_ keys] (set! logs (apply dissoc logs keys))) proto/ICompletable - (-completed? [this key] + (-completed? [_ key] (-> logs (get key) (get :completed?))) - (-complete! [this] + (-complete! [_] (set! logs (complete-logs! logs (set (keys logs))))) - (-complete! [this keys] + (-complete! [_ keys] (set! logs (complete-logs! logs keys)))) (defn void-session [] (reify proto/ISession proto/ILogStorage - (-add-item! [this key xform item]) - (-keys [this]) - (-logs [this] {}) - (-logs [this keys] {}) - (-reset! [this]) - (-reset! [this keys]) + (-add-item! [_ _key _xform _item]) + (-keys [_]) + (-logs [_] {}) + (-logs [_ _keys] {}) + (-reset! [_]) + (-reset! [_ _keys]) proto/ICompletable - (-completed? [this key] true) - (-complete! [this]) - (-complete! [this keys]))) + (-completed? [_ _key] true) + (-complete! [_]) + (-complete! [_ _keys]))) -#?(:clj +(defn indexed [session f] + (let [id (atom -1)] + (reify + proto/ISession + proto/ILogStorage + (-add-item! [_ key xform item] + (proto/-add-item! session key xform (f (swap! id inc) item))) + (-keys [_] + (proto/-keys session)) + (-logs [_] + (proto/-logs session)) + (-logs [_ keys] + (proto/-logs session keys)) + (-reset! [_] + (proto/-reset! session) + (reset! id -1) + nil) + (-reset! [_ keys] + (proto/-reset! session keys)) + proto/ICompletable + (-completed? [_ key] + (proto/-completed? session key)) + (-complete! [_] + (proto/-complete! session)) + (-complete! [_ keys] + (proto/-complete! session keys))))) + +#?(:cljs (do) + :default + (deftype SynchronizedSession [session lock] + proto/ISession + proto/ILogStorage + (-add-item! [_ key xform' item] + (with-lock lock + (proto/-add-item! session key xform' item))) + (-keys [_] + (with-lock lock + (proto/-keys session))) + (-logs [_] + (with-lock lock + (proto/-logs session))) + (-logs [_ keys] + (with-lock lock + (proto/-logs session keys))) + (-reset! [_] + (with-lock lock + (proto/-reset! session))) + (-reset! [_ keys] + (with-lock lock + (proto/-reset! session keys))) + proto/ICompletable + (-completed? [_ key] + (with-lock lock + (proto/-completed? session key))) + (-complete! [_] + (with-lock lock + (proto/-complete! session))) + (-complete! [_ keys] + (with-lock lock + (proto/-complete! session keys))))) + +#?(:bb + (defn synchronized [session] + (->SynchronizedSession session (Object.))) + :clj (defn synchronized [session] - (let [^ReentrantLock lock (ReentrantLock.)] - (reify - proto/ISession - proto/ILogStorage - (-add-item! [this key xform' item] - (with-lock lock - (proto/-add-item! session key xform' item))) - (-keys [this] - (with-lock lock - (proto/-keys session))) - (-logs [this] - (with-lock lock - (proto/-logs session))) - (-logs [this keys] - (with-lock lock - (proto/-logs session keys))) - (-reset! [this] - (with-lock lock - (proto/-reset! session))) - (-reset! [this keys] - (with-lock lock - (proto/-reset! session keys))) - proto/ICompletable - (-completed? [this key] - (with-lock lock - (proto/-completed? session key))) - (-complete! [this] - (with-lock lock - (proto/-complete! session))) - (-complete! [this keys] - (with-lock lock - (proto/-complete! session keys))))))) + (->SynchronizedSession session (ReentrantLock.)))) diff --git a/src/postmortem/utils.cljc b/src/postmortem/utils.cljc index 7018616..f8357d4 100644 --- a/src/postmortem/utils.cljc +++ b/src/postmortem/utils.cljc @@ -8,14 +8,15 @@ ;; to avoid using the locking macro, which is problematic in some environments (see CLJ-1472) (defmacro with-lock [lock & body] (macros/case - :clj - `(let [lock# ~lock] - (.lock lock#) - (try - ~@body - (finally - (.unlock lock#)))) + :clj + #?(:bb + `(locking ~lock ~@body) + :clj + `(let [^java.util.concurrent.locks.ReentrantLock lock# ~lock] + (.lock lock#) + (try + ~@body + (finally + (.unlock lock#))))) :cljs - `(do ~@body))) - - ) + `(do ~@body)))) diff --git a/src/postmortem/xforms.cljc b/src/postmortem/xforms.cljc index 8878b6b..4361244 100644 --- a/src/postmortem/xforms.cljc +++ b/src/postmortem/xforms.cljc @@ -71,7 +71,7 @@ (do (vreset! prev v) (rf acc input))))))))) -(defn- abs ^double [^double x] +(defn- abs* ^double [^double x] #?(:clj (Math/abs x) :cljs (js/Math.abs x))) @@ -87,7 +87,7 @@ (let [p @prev v (f input)] (if (or (= p ::none) - (>= (abs (- v p)) interval)) + (>= (abs* (- v p)) interval)) (do (vreset! prev v) (rf acc input)) acc)))))))) diff --git a/template/pom.xml b/template/pom.xml new file mode 100644 index 0000000..75dd430 --- /dev/null +++ b/template/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + postmortem + A simple debug library for Clojure(Script) that features data-oriented logging and tracing + https://github.com/athos/Postmortem + + + Eclipse Public License + http://www.eclipse.org/legal/epl-v10.html + + + + + athos + Shogo Ohta + + + + https://github.com/athos/Postmortem + scm:git:git://github.com/athos/Postmortem.git + scm:git:ssh://git@github.com/athos/Postmortem.git + + diff --git a/test/postmortem/core_test.cljc b/test/postmortem/core_test.cljc index 01c3254..e827ea9 100644 --- a/test/postmortem/core_test.cljc +++ b/test/postmortem/core_test.cljc @@ -1,6 +1,6 @@ (ns postmortem.core-test - (:require [clojure.test :refer [deftest is are testing]] - [postmortem.core :as pm :refer [locals dump spy> spy>>]] + (:require [clojure.test :refer [are deftest is testing]] + [postmortem.core :as pm :refer [dump locals spy>>]] [postmortem.xforms :as xf])) (deftest locals-test @@ -190,6 +190,41 @@ (is (= "" (with-out-str (f 1) (f 2) (f 3)))) (is (= "" (with-out-str (pm/logs sess))))))) +(deftest indexed-session-test + (testing "indexed session logs items with auto-incremental index" + (let [sess (pm/make-indexed-session)] + (pm/spy>> sess :foo identity 100) + (pm/spy>> sess :bar identity 101) + (pm/spy>> sess :foo identity 102) + (is (= {:foo [{:id 0 :val 100} + {:id 2 :val 102}] + :bar [{:id 1 :val 101}]} + (pm/logs sess))))) + (testing "indexed session accepts an optional fn that specifies how to attach the given index to the item" + (let [sess (pm/make-indexed-session vector)] + (pm/spy>> sess :foo identity 100) + (pm/spy>> sess :bar identity 101) + (pm/spy>> sess :foo identity 102) + (is (= {:foo [[0 100] [2 102]] :bar [[1 101]]} + (pm/logs sess))))) + (testing "calling reset! on an indexed session resets the index" + (let [sess (pm/make-indexed-session)] + (pm/spy>> sess :foo identity 100) + (pm/spy>> sess :foo identity 101) + (is (= [{:id 0 :val 100} {:id 1 :val 101}] + (pm/log-for sess :foo))) + (pm/reset-key! sess :foo) + (pm/spy>> sess :foo identity 102) + (pm/spy>> sess :foo identity 103) + (is (= [{:id 2 :val 102} {:id 3 :val 103}] + (pm/log-for sess :foo))) + (pm/reset! sess) + (is (= {} (pm/logs sess))) + (pm/spy>> sess :foo identity 104) + (pm/spy>> sess :foo identity 105) + (is (= [{:id 0 :val 104} {:id 1 :val 105}] + (pm/log-for sess :foo)))))) + #?(:clj (deftest ^:eftest/synchronized synchronization-test @@ -203,6 +238,24 @@ ) +(deftest merged-logs-test + (pm/with-session (pm/make-indexed-session) + (pm/spy>> :foo :a) + (pm/spy>> :bar :b) + (pm/spy>> :foo :c) + (is (= [{:id 0 :val :a} + {:id 1 :val :b} + {:id 2 :val :c}] + (sort-by :id (pm/merged-logs))))) + (pm/with-session (pm/make-indexed-session) + (pm/spy>> :foo :a) + (pm/spy>> :bar :b) + (pm/spy>> :foo :c) + (is (= [{:id 0 :key :foo :val :a} + {:id 1 :key :bar :val :b} + {:id 2 :key :foo :val :c}] + (sort-by :id (pm/merged-logs #(assoc %2 :key %1))))))) + (deftest logger-test (let [f (pm/make-logger)] (is (= [0 1 2 3 4] (map f (range 5)))) diff --git a/test/postmortem/xforms_test.cljc b/test/postmortem/xforms_test.cljc index c3d0a90..80d0f6d 100644 --- a/test/postmortem/xforms_test.cljc +++ b/test/postmortem/xforms_test.cljc @@ -1,6 +1,6 @@ (ns postmortem.xforms-test - (:require [postmortem.xforms :as xf] - [clojure.test :refer [deftest are]])) + (:require [clojure.test :refer [are deftest]] + [postmortem.xforms :as xf])) (deftest take-until-test (are [xform coll expected]