Procházet zdrojové kódy

Node test runner improvements

- Print stacktrace when exception occurs!
- No longer depend on old test runner
- AND multiple test selections
- Add -v for compatibility with old test runner
Gabriel Horner před 3 roky
rodič
revize
8d40c4667b
2 změnil soubory, kde provedl 103 přidání a 41 odebrání
  1. 2 0
      docs/dev-practices.md
  2. 101 41
      src/test/frontend/test/node_test_runner.cljs

+ 2 - 0
docs/dev-practices.md

@@ -89,6 +89,8 @@ For this workflow:
   tests. To run all tests except those tests run `node static/tests.js -e focus`.
 3. Or focus namespaces: Using the regex option `-r`, run tests for `frontend.text-test` with `node static/tests.js -r text`.
 
+Multiple options can be specified to AND selections. For example, to run all `frontend.text-test` tests except for the focused one: `node static/tests.js -r text -e focus`
+
 For help on more options, run `node static/tests.js -h`.
 
 #### Autorun Tests

+ 101 - 41
src/test/frontend/test/node_test_runner.cljs

@@ -8,13 +8,42 @@
   {:dev/always true} ;; necessary for test-data freshness
   (:require [shadow.test.env :as env]
             [clojure.tools.cli :as cli]
-            [shadow.test.node :as node]
             [clojure.string :as str]
             [clojure.set :as set]
+            [shadow.test :as st]
+            [cljs.test :as ct]
             ["util" :as util]
             ;; activate humane test output for all tests
             [pjstadig.humane-test-output]))
 
+;; Cljs.test customization
+;; Inherit behavior from default reporter
+(derive ::node ::ct/default)
+
+;; Needed for process to exit correctly
+(defmethod ct/report [::node :end-run-tests] [m]
+  (if (ct/successful? m)
+    (js/process.exit 0)
+    (js/process.exit 1)))
+
+;; Improves on default error behavior by printing full stacktrace.
+;; clojure.test runner does this but cljs.test does not - https://github.com/clojure/clojure/blob/9af0d1d9a0dc34c406c3588dfe9b60dbe4530981/src/clj/clojure/test.clj#L384-L395
+(defmethod ct/report [::node :error] [m]
+  ;; Add to counter for ::node
+  (ct/inc-report-counter! :error)
+
+  ;; Print standard error messages
+  (let [env (ct/get-current-env)]
+    (binding [ct/*current-env* (assoc env :reporter ::ct/default)]
+      (ct/report m)))
+
+  ;; Also print stacktrace
+  (when (.hasOwnProperty (:actual m) "stack")
+    (println (.-stack (:actual m)))))
+
+;; CLI utils
+;; =========
+
 (defn- print-summary
   "Print help summary given args and opts strings"
   [options-summary additional-msg]
@@ -35,30 +64,65 @@
         (js/process.exit 1))
       parsed-input)))
 
-(defn- run-test-options
-  "Given available tests namespaces as symbols, test vars and options,
-returns run options for selected tests to run"
-  [namespaces
-   vars
-   {:keys [include exclude namespace namespace-regex]}]
+(defn- get-selected-tests
+  "Given available tests namespaces as symbols, test vars and user options,
+returns selected tests and namespaces to run"
+  [test-namespaces
+   test-vars
+   {:keys [include exclude namespace namespace-regex vars]}]
   (let [focused-tests (cond
+                        (seq vars)
+                        vars
                         (seq include)
                         (map symbol (filter
                                      #(seq (set/intersection include (set (keys (meta %)))))
-                                     vars))
+                                     test-vars))
                         (seq exclude)
                         (map symbol (remove
                                      #(seq (set/intersection exclude (set (keys (meta %)))))
-                                     vars)))
+                                     test-vars)))
         test-syms (cond (some? focused-tests)
                     focused-tests
                     namespace
                     [namespace]
                     namespace-regex
-                    (filter #(re-find namespace-regex (str %)) namespaces))]
-    (cond-> {}
-            (some? test-syms)
-            (assoc :test-syms test-syms))))
+                    (filter #(re-find namespace-regex (str %)) test-namespaces))]
+    test-syms))
+
+;; This is a patched version of https://github.com/thheller/shadow-cljs/blob/f271b3c40d3ccd4e587b0ffeaa2713d2f642114a/src/main/shadow/test/node.cljs#L44-L56
+;; that consistently works for all symbols
+(defn find-matching-test-vars
+  "Converts symbols to vars"
+  [test-syms all-test-vars]
+  (let [test-namespaces
+        (->> test-syms (filter simple-symbol?) (set))
+        test-var-syms
+        (->> test-syms (filter qualified-symbol?) (set))]
+    (->> all-test-vars
+         (filter (fn [the-var]
+                   (let [{:keys [ns] :as m} (meta the-var)]
+                     (or (contains? test-namespaces ns)
+                         ;; PATCH: (symbol SYMBOL SYMBOL) leads to buggy equality behavior
+                         ;; in cljs. In clj, this throws an exception. Modified to
+                         ;; (symbol STRING STRING) to avoid bug
+                         (contains? test-var-syms
+                                    (symbol (name ns) (name (:name m)))))))))))
+
+(defn- get-selected-vars
+  "Given test selections from user options, returns the selected tests as
+  vars"
+  [test-namespaces test-vars options]
+  (->> [:include :exclude :namespace :namespace-regex :vars]
+       ;; Only AND options users have specified
+       (filter #(let [val (get options %)]
+                  ;; Some options default to empty so we have filter these out
+                  (if (coll? val) (seq val) (some? val))))
+       (map #(get-selected-tests test-namespaces test-vars (select-keys options [%])))
+       (map #(set (find-matching-test-vars % test-vars)))
+       (apply set/intersection)))
+
+;; Main test functionality
+;; =======================
 
 (def cli-options
   [["-h" "--help"]
@@ -76,49 +140,45 @@ returns run options for selected tests to run"
     :multi true
     :update-fn conj
     :desc "Exclude tests that have this keyword"]
+   ;; --test is long name for compatability with older runner
+   ["-v" "--test VAR" "Fully qualified var to test"
+    :id :vars
+    :default #{}
+    :default-desc ""
+    :parse-fn symbol
+    :multi true
+    :update-fn conj]
    ["-n" "--namespace NAMESPACE"
     :parse-fn symbol :desc "Specific namespace to test"]
    ["-r" "--namespace-regex REGEX"
     :parse-fn re-pattern :desc "Regex for namespaces to test"]])
 
-;; Necessary to have test-data in this ns for freshness. Relying on
-;; node/reset-test-data! was buggy
+;; get-test-data is a macro so this namespace REQUIRES :dev/always hint ns so
+;; that it is always recompiled
 (defn ^:dev/after-load reset-test-data! []
   (-> (env/get-test-data)
       (env/reset-test-data!)))
 
-;; This is a patched version of https://github.com/thheller/shadow-cljs/blob/f271b3c40d3ccd4e587b0ffeaa2713d2f642114a/src/main/shadow/test/node.cljs#L44-L56
-;; that consistently works for all symbols
-(defn find-matching-test-vars [test-syms]
-  (let [test-namespaces
-        (->> test-syms (filter simple-symbol?) (set))
-        test-var-syms
-        (->> test-syms (filter qualified-symbol?) (set))]
-    (->> (env/get-test-vars)
-         (filter (fn [the-var]
-                   (let [{:keys [ns] :as m} (meta the-var)]
-                     (or (contains? test-namespaces ns)
-                         ;; PATCH: (symbol SYMBOL SYMBOL) leads to buggy equality behavior
-                         ;; in cljs. In clj, this throws an exception. Modified to
-                         ;; (symbol STRING STRING) to avoid bug
-                         (contains? test-var-syms
-                                    (symbol (name ns) (name (:name m)))))))))))
+(defn- run-tests
+  [test-namespaces test-vars options]
+  ;; We define a custom runner so we can inherit behavior from the default
+  ;; runner and improve on it
+  (let [test-env (ct/empty-env ::node)
+        selected-vars (get-selected-vars test-namespaces test-vars options)]
+
+    (if (some? selected-vars)
+      ;; Don't run tests if none are selected
+      (let [test-vars (if (empty? selected-vars) [] selected-vars)]
+        (st/run-test-vars test-env test-vars))
+      (st/run-all-tests test-env nil))))
 
 (defn main [& args]
-  ;; Load test data as is done with shadow.test.node/main
   (reset-test-data!)
 
   (let [{:keys [options summary]} (parse-options args cli-options)]
     (if (:help options)
       (do
         (print-summary summary
-                       "\n\nNone of these options can be composed. Defaults to running all tests")
+                       "\n\nMultiple options are ANDed. Defaults to running all tests")
         (js/process.exit 0))
-      (with-redefs [node/find-matching-test-vars find-matching-test-vars]
-        (let [opts (run-test-options (keys (env/get-tests)) (env/get-test-vars) options)]
-          ;; If :test-syms is specified but empty, skip execute-cli because the
-          ;; user has specified an empty test selection
-          (if (and (seq opts) (empty? (:test-syms opts)))
-            (do (println "No tests found.")
-              (js/process.exit 0))
-            (node/execute-cli opts)))))))
+      (run-tests (keys (env/get-tests)) (env/get-test-vars) options))))