diff --git a/CHANGELOG.md b/CHANGELOG.md index ae0a12a..c03933f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## [5.14.5] +- switch the declare/import-var function to def in state.api [#171](https://github.com/nubank/state-flow/pull/171) +- ## [5.14.4] - Bump midje to 1.10.9 diff --git a/project.clj b/project.clj index e38f3a9..88d8ab8 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject nubank/state-flow "5.14.4" +(defproject nubank/state-flow "5.14.5" :description "Integration testing with composable flows" :url "https://github.com/nubank/state-flow" :license {:name "MIT"} diff --git a/src/state_flow/api.clj b/src/state_flow/api.clj index 5432fec..262ee28 100644 --- a/src/state_flow/api.clj +++ b/src/state_flow/api.clj @@ -5,44 +5,133 @@ [state-flow.assertions.matcher-combinators] [state-flow.cljtest] [state-flow.core] - [state-flow.state] - [state-flow.vendor.potemkin :refer [import-fn import-vars]])) - -;; TODO: (dchelimsky,2020-05-18) Intellij / Cursive doesn't recognize the -;; vars imported below unless we declare them. If that is ever fixed, we -;; should remove this. -(declare flow - run - run* - log-and-throw-error! - ignore-error - invoke - return - fmap - defflow - match? - get-state - swap-state - when) - -(import-vars - state-flow.core/flow - state-flow.core/run - state-flow.core/run* - state-flow.core/log-and-throw-error! - state-flow.core/ignore-error - - state-flow.state/invoke - state-flow.state/return - state-flow.state/fmap - state-flow.state/when - - state-flow.cljtest/defflow - - state-flow.assertions.matcher-combinators/match?) - -(import-fn state-flow.state/gets get-state) -(import-fn state-flow.state/modify swap-state) + [state-flow.state])) + +(def ^{:doc "Creates a flow which is a composite of flows." + :arglists '([description & flows]) + :macro true} + flow + #'state-flow.core/flow) + +(def ^{:arglists '([flow] [flow initial-state]), + :doc "Given an initial-state (default {}), runs a flow and returns a tuple of + the result of the last step in the flow and the end state."} + run + #'state-flow.core/run) + +(def ^{:arglists '([{:keys [init cleanup runner on-error fail-fast? before-flow-hook], + :or {init (constantly {}), + cleanup identity, + runner run, + fail-fast? false, + before-flow-hook identity, + on-error (comp throw-error! log-error (filter-stack-trace default-stack-trace-exclusions))}} + flow]), + :doc "Runs a flow with specified parameters. Use `run` unless you need + the customizations `run*` supports. + + Supported keys in the first argument are: + + `:fail-fast?` optional, default `false`, when set to `true`, the flow stops running after the first failing assertion + `:init` optional, default (constantly {}), function of no arguments that returns the initial state + `:cleanup` optional, default `identity`, function of the final state used to perform cleanup, if necessary + `:runner` optional, default `run`, function of a flow and an initial state which will execute the flow + `:before-flow-hook` optional, default `identity`, function from state to new-state that is applied before excuting a flow, after flow description is updated. + `:on-error` optional, function of the final result pair to be invoked when the first value in the pair represents an error, default: + `(comp throw-error! + log-error + (filter-stack-trace default-stack-trace-exclusions))`"} + run* + #'state-flow.core/run*) + +(def ^{:deprecated true, + :arglists '([pair]), + :doc "DEPRECATED: Use (comp throw-error! log-error) instead. "} + log-and-throw-error! + #'state-flow.core/log-and-throw-error!) + +(def ^{:arglists '([pair]), + :doc "No-op error handler that ignores the error."} + ignore-error + #'state-flow.core/ignore-error) + +(def ^{:arglists '([pair]), + :doc "No-op error handler that ignores the error."} + ignore-error + #'state-flow.core/ignore-error) + +(def ^{:arglists '([my-fn]), + :doc "Creates a flow that invokes a function of no arguments and returns the + result. Used to invoke side effects e.g. + + (state-flow.core/invoke #(Thread/sleep 1000))"} + invoke + #'state-flow.state/invoke) + +(def ^{:arglists '([v]), + :doc "Creates a flow that returns v. Use this as the last + step in a flow that you want to reuse in other flows, in + order to clarify the return value, e.g. + + (def increment-count + (flow \"increments :count and returns it\" + (state/modify update :count inc) + [new-count (state/gets :count)] + (state-flow/return new-count)))"} + return + #'state-flow.state/return) + +(def ^{:doc "Creates a flow that returns the application of f to the return of flow", + :arglists '([f flow])} + fmap + #'state-flow.state/fmap) + +(def ^{:arglists '([e flow]), + :doc "Given an expression `e` and a flow, if the expression is logical true, return the flow. Otherwise, return nil in a monadic context."} + when + #'state-flow.state/when) + +(def ^{:arglists '([name & flows] [name parameters & flows]), + :doc "Creates a flow and binds it a Var named by name", + :macro true} + defflow + #'state-flow.cljtest/defflow) + +(def ^{:arglists '([expected actual & [{:keys [times-to-try sleep-time], :as params}]]), + :doc "Builds a state-flow step which uses matcher-combinators to make an + assertion. + + `expected` can be a literal value or a matcher-combinators matcher + `actual` can be a literal value, a primitive step, or a flow + `params` are optional keyword-style args, supporting: + + :times-to-try optional, default 1 + :sleep-time optional, millis to wait between tries, default 200 + + Given (= times-to-try 1), match? will evaluate `actual` just once. + + Given (> times-to-try 1), match? will use `state-flow-probe/probe` to + retry up to :times-to-try times, waiting :sleep-time between each try, + and stopping when `actual` produces a value that matches `expected`. + + NOTE: when (> times-to-try 1), `actual` must be a step or a flow. + + Returns a map (in the left value) with information about the success + or failure of the match, the details of which are used internally by + state-flow and subject to change.", + :macro true} + match? + #'state-flow.assertions.matcher-combinators/match?) + +(def ^{:arglists '([] [f & args]), + :doc "Creates a flow that returns the result of applying f (default identity)\nto state with any additional args."} + get-state + #'state-flow.state/gets) + +(def ^{:arglists '([f & args]), + :doc "Creates a flow that replaces state with the result of applying f to\nstate with any additional args."} + swap-state + #'state-flow.state/modify) ;; NOTE: this could be imported directly from cats.core, but we're defining ;; it here to keep the documentation in terms of state-flow rather than cats. diff --git a/src/state_flow/vendor/potemkin.clj b/src/state_flow/vendor/potemkin.clj deleted file mode 100644 index 532c454..0000000 --- a/src/state_flow/vendor/potemkin.clj +++ /dev/null @@ -1,128 +0,0 @@ -(ns ^:no-doc state-flow.vendor.potemkin) - -;; --- copied from ztellman/potemkin -;; -;; Copyright (c) 2013 Zachary Tellman -;; -;; Permission is hereby granted, free of charge, to any person obtaining a copy -;; of this software and associated documentation files (the "Software"), to -;; deal in the Software without restriction, including without limitation the -;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -;; sell copies of the Software, and to permit persons to whom the Software is -;; furnished to do so, subject to the following conditions: -;; -;; The above copyright notice and this permission notice shall be included in -;; all copies or substantial portions of the Software. -;; -;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -;; IN THE SOFTWARE. -;; -;; --- - -;; --- potemkin.namespaces - -(defn link-vars - "Makes sure that all changes to `src` are reflected in `dst`." - [src dst] - (add-watch src dst - (fn [_ src old new] - (alter-var-root dst (constantly @src)) - (alter-meta! dst merge (dissoc (meta src) :name))))) - -(defmacro import-fn - "Given a function in another namespace, defines a function with the - same name in the current namespace. Argument lists, doc-strings, - and original line-numbers are preserved." - ([sym] - `(import-fn ~sym nil)) - ([sym name] - (let [vr (resolve sym) - m (meta vr) - n (or name (:name m)) - arglists (:arglists m) - protocol (:protocol m)] - (when-not vr - (throw (IllegalArgumentException. (str "Don't recognize " sym)))) - (when (:macro m) - (throw (IllegalArgumentException. - (str "Calling import-fn on a macro: " sym)))) - - `(do - (def ~(with-meta n {:protocol protocol}) (deref ~vr)) - (alter-meta! (var ~n) merge (dissoc (meta ~vr) :name)) - (link-vars ~vr (var ~n)) - ~vr)))) - -(defmacro import-macro - "Given a macro in another namespace, defines a macro with the same - name in the current namespace. Argument lists, doc-strings, and - original line-numbers are preserved." - ([sym] - `(import-macro ~sym nil)) - ([sym name] - (let [vr (resolve sym) - m (meta vr) - n (or name (with-meta (:name m) {})) - arglists (:arglists m)] - (when-not vr - (throw (IllegalArgumentException. (str "Don't recognize " sym)))) - (when-not (:macro m) - (throw (IllegalArgumentException. - (str "Calling import-macro on a non-macro: " sym)))) - `(do - (def ~n ~(resolve sym)) - (alter-meta! (var ~n) merge (dissoc (meta ~vr) :name)) - (.setMacro (var ~n)) - (link-vars ~vr (var ~n)) - ~vr)))) - -(defmacro import-def - "Given a regular def'd var from another namespace, defined a new var with the - same name in the current namespace." - ([sym] - `(import-def ~sym nil)) - ([sym name] - (let [vr (resolve sym) - m (meta vr) - n (or name (:name m)) - n (with-meta n (if (:dynamic m) {:dynamic true} {})) - nspace (:ns m)] - (when-not vr - (throw (IllegalArgumentException. (str "Don't recognize " sym)))) - `(do - (def ~n @~vr) - (alter-meta! (var ~n) merge (dissoc (meta ~vr) :name)) - (link-vars ~vr (var ~n)) - ~vr)))) - -(defmacro import-vars - "Imports a list of vars from other namespaces." - [& syms] - (let [unravel (fn unravel [x] - (if (sequential? x) - (->> x - rest - (mapcat unravel) - (map - #(symbol - (str (first x) - (when-let [n (namespace %)] - (str "." n))) - (name %)))) - [x])) - syms (mapcat unravel syms)] - `(do - ~@(map - (fn [sym] - (let [vr (resolve sym) - m (meta vr)] - (cond - (:macro m) `(import-macro ~sym) - (:arglists m) `(import-fn ~sym) - :else `(import-def ~sym)))) - syms))))