kaocha

Official support channel: https://clojureverse.org/c/projects/kaocha
reefersleep 2020-08-14T11:44:48.036Z

Hello!

reefersleep 2020-08-14T11:45:32.036800Z

If I were to write a reporter which would print some extra information when a test fails, but which otherwise works as normal, is there a guide I could follow?

plexus 2020-08-14T11:47:06.037900Z

that's pretty specific, there's no documented recipe for that, but it should be straightforward. A reporter is just a function. You would write one which deals with the cases you want to deal with, and otherwise delegates to an existing reporter.

reefersleep 2020-08-14T11:48:21.039200Z

This is what I thought I’d do. Maybe I’m not reading the docs closely enough, but I’m in doubt about how to delegate. (I was thinking that it was just a matter of calling (existing-reporter m), but I’m in doubt about which existing report to refer to, and whether that would fit into the flow)

plexus 2020-08-14T11:48:46.039400Z

(defn my-reporter [event]
  (if (kaocha.hierarchy/fail-type? event)
    (println "oh no that's not good")
    (kaocha.report/dots* event)))

plexus 2020-08-14T11:49:31.040200Z

something to be aware of here is that in kaocha you can specifiy multiple reporters, they will get merged into a single function, this is what happens in case of dots and documentation

plexus 2020-08-14T11:49:32.040400Z

(def dots
  "Reporter that prints progress as a sequence of dots and letters."
  [dots* result])

(def documentation
  "Reporter that prints an overview of all tests bein run using indentation."
  [doc result])

plexus 2020-08-14T11:50:34.041500Z

they share a result reporter which prints out the test summary in the end. So either you delegate to that too, or you add kaocha.report/result as an extra reporter in tests.edn

plexus 2020-08-14T11:51:04.042100Z

#kaocha/v1
{:reporter [my.project/my-reporter kaocha.report/result]}

plexus 2020-08-14T11:51:08.042300Z

does that make sense?

reefersleep 2020-08-14T12:00:37.043Z

That makes great sense, thanks @plexus!Now to iterate on it and see if I can do what I want 🙂

plexus 2020-08-14T12:01:12.043500Z

good look! the kaocha.report/debug reporter can be helpful to get a sense of what data you're getting

plexus 2020-08-14T12:01:55.044300Z

and as demonstrated in my example it's recommended to use kaocha.hierarchy instead of checking for concrete :type values, so as to be maximally compatible with different test types and assertions

plexus 2020-08-14T12:02:48.045500Z

A "writing reporters" section in the docs would certainly be welcome. If you have the appetite for it it would be great if you could gather up these tips and stick them in a markdown file 🙂

reefersleep 2020-08-14T12:03:00.045600Z

That’s good to know. I was expecting to match directly with =.

reefersleep 2020-08-14T12:03:16.046Z

If this goes well, I might do a writeup 🙂

plexus 2020-08-14T12:03:23.046300Z

awesome!

plexus 2020-08-14T12:05:58.048500Z

reporters are simple in theory but complex in practice, they're a concept we took directly from clojure.test, but then kind of ran with it. We stick a lot more information in the event map than clojure.test does, and well behaved kaocha reporters use that when available. This is also because we need to support many test types (cucumber, cljs), with sometimes quite specific requirements

plexus 2020-08-14T12:06:28.048800Z

some examples

(defmethod doc :kaocha/begin-test [m]
  (t/with-test-out
    (let [desc (or (some-> m :kaocha/testable :kaocha.testable/desc)
                   (some-> m :var meta :name))]
      (print (str "\n  " desc))
      (flush))))

plexus 2020-08-14T12:06:54.049400Z

:var is a clojure.test specific thing, the more general thing is to use a testable's description

plexus 2020-08-14T12:07:58.050400Z

(defmethod fail-summary :kaocha/fail-type [{:keys [testing-contexts testing-vars] :as m}]
  (println (str "\n" (output/colored :red "FAIL") " in") (testing-vars-str m))
  (when (seq testing-contexts)
    (println (str/join " " (reverse testing-contexts))))
  (when-let [message (:message m)]
    (println message))
  (if-let [expr (::printed-expression m)]
    (print expr)
    (print-expr m))
  (print-output m))

reefersleep 2020-08-14T12:08:32.051500Z

More info is great, and it’s probably what will enable me to do what I want. I just learned that I can’t go ahead and do (pprint/pprint m), that blows the heap space 😄 Too much info in one portion!

plexus 2020-08-14T12:09:23.052300Z

for clojurescript tests we do the formatting on the clojurescript side, because we can't always properly serialize/deserialize clojurescript assertions into clojurescript. So if :kaocha.report/printed-expression is present then the reporter simply prints that instead of trying to parse and format/print the assertion

plexus 2020-08-14T12:09:47.052900Z

yeah you have the full testable in there which can be huge. That's why the debug reporter limits the keys that it prints

plexus 2020-08-14T12:10:02.053100Z

(defn debug [m]
  (t/with-test-out
    (prn (cond-> (select-keys m [:type
                                 :file
                                 :line
                                 :var
                                 :ns
                                 :expected
                                 :actual
                                 :message
                                 :kaocha/testable
                                 :debug
                                 ::printed-expression])
           (:kaocha/testable m)
           (update :kaocha/testable select-keys [:kaocha.testable/id :kaocha.testable/type])))))

plexus 2020-08-14T12:11:26.054100Z

also notice the with-test-out, that's another clojure.test thing that allows you to rebind where test output is printed. It defaults to just *out*. We don't do much with it but users might. We really only use it to capture output in kaocha's own tests.

👍 1
reefersleep 2020-08-14T12:13:55.055200Z

I was just messing around with printing parts of m in a custom reporter, but I’ll try the debug reporter now, as iterating on it is a bit slow. (I wonder how I could get lein test to start up faster…)

plexus 2020-08-14T12:14:34.055500Z

use kaocha.repl! no need to start a new process every time

reefersleep 2020-08-14T12:14:51.055700Z

Even better 😄

reefersleep 2020-08-14T12:15:04.055900Z

Seems like you’ve thought of everything 🙂

reefersleep 2020-08-14T12:18:48.056800Z

Do you happen to know how to get the path for a source file based on the ns? I was hoping that the path was available in the Kaocha map, but :file only seems to refer to the filename itself.

reefersleep 2020-08-14T12:18:58.057100Z

Maybe I can also get it via the filename.

plexus 2020-08-14T12:23:41.057400Z

(let [ns 'foo.bar-test]
  (io/resource (str (.. (name ns)
                        (replace \- \_)
                        (replace \. \/))
                    ".clj")))

plexus 2020-08-14T12:24:26.058Z

not sure if that's really the best way but that's roughly the idea. Might be some other option through tools.namespace

reefersleep 2020-08-14T12:24:39.058300Z

🙏 thank you very much!

reefersleep 2020-08-14T12:48:25.059100Z

@plexus I don’t understand this example. I tried it verbatim, but it seems like even though I have a test failure (as reported by the summary: #:kaocha.result{:count 1, :pass 0, :error 0, :fail 1, :pending 0}), (kaocha.hierarchy/fail-type? event)` does not return true during my run.

reefersleep 2020-08-14T12:49:03.059300Z

Ah wait, I am mistaken.

reefersleep 2020-08-14T12:49:31.059500Z

The printing confused me.

(run 'query.opgavefabrikken-test.config-test
     {:reporter 'util.reporters/show-owner})
[()]
Randomized with --seed 1576729021(:pending :file :type :fail :line :error :kaocha/testable :pass :kaocha/test-plan :test)

=> #:kaocha.result{:count 1, :pass 0, :error 0, :fail 1, :pending 0}

reefersleep 2020-08-14T12:49:56.059700Z

No wait, I confused myself! The code should be like this:

reefersleep 2020-08-14T12:50:02.059900Z

(ns util.reporters
  (:require [kaocha.hierarchy :as hierarchy]
            [kaocha.report :as report]))

(defn show-owner [{:keys [] :as m}]
  (if (hierarchy/fail-type? m)
    (println "oh oh!")
    (report/dots* m)))

reefersleep 2020-08-14T12:50:13.060100Z

And the output is then:

(run 'query.opgavefabrikken-test.config-test
     {:reporter 'util.reporters/show-owner})
[()]
Randomized with --seed 1706915743
=> #:kaocha.result{:count 1, :pass 0, :error 0, :fail 1, :pending 0}

plexus 2020-08-14T12:52:01.060500Z

might be output capturing? try adding the t/with-test-out

plexus 2020-08-14T12:53:07.060700Z

also add :kaocha.plugin.capture-output/capture-output? false to your config when calling run, that's generally a good idea when working on stuff like this to make sure things don't get hidden that you really would want to see

reefersleep 2020-08-14T12:54:09.060900Z

Both things work separately. I mean, for some definition of “work” 😄 The output is a bit funny looking, but at least it includes my prn now.

reefersleep 2020-08-14T12:54:17.061100Z

(run 'query.opgavefabrikken-test.config-test
     {:reporter 'util.reporters/show-owner
      :kaocha.plugin.capture-output/capture-output? false})
[(oh oh!
)]
Randomized with --seed 278474731
=> #:kaocha.result{:count 1, :pass 0, :error 0, :fail 1, :pending 0}

reefersleep 2020-08-14T12:55:16.061300Z

Will it always output whatever I prn as a part of that dots-like vector?

plexus 2020-08-14T12:56:36.061500Z

if you are using the dots reporter, yes. keep in mind that these reporters aren't very smart, they just get handed event after event and write some output to stdout as a result. The dot reporter prints "[" when a suite starts, "(" when a group starts, etc.

plexus 2020-08-14T12:57:12.061700Z

what are you trying to achieve?

reefersleep 2020-08-14T12:58:21.061900Z

Specifically, I think it’d be neat to use something (perhaps a reporter) to react to failing tests by printing the names of the people who last touched the file that the ns belongs to, possibly via some git command that uses the file name.

reefersleep 2020-08-14T12:58:46.062100Z

It seems like all the pieces of the puzzle are available, provided that git is available in the environment (and it can be)

plexus 2020-08-14T12:59:45.062300Z

I think that will be easier with a plugin/hook

plexus 2020-08-14T13:00:08.062500Z

since that way you can just print a "report" at the end of the test run

reefersleep 2020-08-14T13:00:59.062700Z

I’ll give that a go 🙂

reefersleep 2020-08-14T13:01:08.062900Z

Iterating with the repl is, as always, great

plexus 2020-08-14T13:01:52.063100Z

I'll write up something to get you started

reefersleep 2020-08-14T13:10:19.063300Z

I’ve hooked into the :kaocha.hooks/post-run, but the m doesnt’ seem to have the info I want

reefersleep 2020-08-14T13:12:35.063500Z

Nor for :kaocha.hooks/pre-report

plexus 2020-08-14T13:12:48.063700Z

it's all there, just give me a moment

reefersleep 2020-08-14T13:12:57.063900Z

🙂

plexus 2020-08-14T13:15:11.064100Z

managed to freeze up my emacs by printing a too big a datastructure 🙂

reefersleep 2020-08-14T13:15:20.064300Z

😉

plexus 2020-08-14T13:18:48.064700Z

(ns my.project.kaocha-hooks
  (:require [kaocha.testable :as testable]
            [kaocha.hierarchy :as hierarchy]
            [kaocha.result :as result]))

;; use as a post-summary hook
(defn blame-report [result]
  (let [clojure-test-suites (filter (comp #{:kaocha.type/clojure.test} :kaocha.testable/type)
                                    (:kaocha.result/tests result))]
    (doseq [suite clojure-test-suites
            ns-testable (:kaocha.result/tests suite)
            :when (result/failed? ns-testable)]
      (prn (:kaocha.ns/name ns-testable)))
    )
  )


(comment
  (require '[kaocha.repl :as repl]
           '[kaocha.api :as api])

  (def test-result (api/run (repl/config {})))

  (blame-report test-result)
  )
@reefersleep

plexus 2020-08-14T13:19:52.065500Z

this specifically assumes clojure.test, since you can't really generalize this, unless we make test types add a :kaocha.testable/file to their testables, which might be a good idea

reefersleep 2020-08-14T13:21:21.066100Z

I’ll let you be the judge of that. You’re deep into datastructures that I only have surface knowledge of 🙂

reefersleep 2020-08-14T13:21:25.066300Z

Just trying this now

plexus 2020-08-14T13:24:35.066500Z

(defn blame-report [result]
  (let [clojure-test-suites (filter (comp #{:kaocha.type/clojure.test} :kaocha.testable/type)
                                    (:kaocha.result/tests result))]
    (doseq [suite clojure-test-suites
            ns-testable (:kaocha.result/tests suite)
            :when (result/failed? ns-testable)
            :let [ns-name (:kaocha.ns/name ns-testable)]]
      (println
       ns-name "last touched by"
       (re-find #"Author:.*"
                (:out
                 (sh/sh "git" "log" "-1" (str/replace (str (or (io/resource (str (.. (name ns-name)
                                                                                     (replace \- \_)
                                                                                     (replace \. \/))
                                                                                 ".clj"))
                                                               (io/resource (str (.. (name ns-name)
                                                                                     (replace \- \_)
                                                                                     (replace \. \/))
                                                                                 ".cljc"))))
                                                      "file:" ""))))))))

reefersleep 2020-08-14T13:25:42.067200Z

I’m trying to hook it in as a post-summary hook. I think I’m either doing it wrong, or on the wrong version of kaocha.

reefersleep 2020-08-14T13:25:52.067400Z

(run 'query.opgavefabrikken-test.config-test
     {:plugins [:kaocha.plugin/hooks]
      :kaocha.hooks/post-summary [util.hooks/blame-report]
      :kaocha.plugin.capture-output/capture-output? false})

reefersleep 2020-08-14T13:26:00.067700Z

no sign of the output of blame-report in the output

plexus 2020-08-14T13:26:40.068300Z

ah right, post-summary doesn't get run when running via kaocha.repl, a post-run should also work

reefersleep 2020-08-14T13:27:25.068700Z

I’m on "0.0-541" 😮

plexus 2020-08-14T13:27:39.069Z

owwww better do something about that 🙂

plexus 2020-08-14T13:27:58.069200Z

yeah that's almost a year old

plexus 2020-08-14T13:28:33.069600Z

and exactly 100 commits behind 1.0.641 🙂

reefersleep 2020-08-14T13:29:34.070100Z

😄

reefersleep 2020-08-14T13:30:04.070500Z

Time flies in this project. I feel like Kaocha was introduced just yesterday.

reefersleep 2020-08-14T13:31:10.070700Z

Brilliant! Now I get proper output

plexus 2020-08-14T13:33:07.070900Z

commit 723ff0e2ed7c63d8daf36ee39b6da57186d8686a
Author: Arne Brasseur <arne@arnebrasseur.net>
Date:   Thu Apr 12 09:16:35 2018 +0200

    Initial skeleton

plexus 2020-08-14T13:33:37.071200Z

2.3333333 years old 🙂

reefersleep 2020-08-14T13:35:31.071500Z

that is darn cool

reefersleep 2020-08-14T13:35:58.071900Z

I’m iterating towards your code, seeing that it works along the way

reefersleep 2020-08-14T13:39:39.072300Z

Yes! This is fantastic. Thanks Arne!

reefersleep 2020-08-14T13:40:23.072800Z

I’ll refine this and add it to our repo, it should help out whenever our CICD fails.

reefersleep 2020-08-14T13:42:14.073700Z

Is the missing run from post-summary when running via kaocha.repl intentional, or just something that hasn’t been fixed yet?

reefersleep 2020-08-14T13:43:08.074700Z

I’d like to write in our docs about how to test this stuff, and if post-summary is more appropriate, it’d be cool to be able to test that in the REPL as well.

plexus 2020-08-14T13:50:03.076900Z

it's kind of intentional, post-summary is handled in kaocha.runner, not in kaocha.api, to handle cases like --print-test-result which does a test run via kaocha.api but then does completely different output handling, and we don't want plugins that print stuff at the end of the test run to clobber that

plexus 2020-08-14T13:50:24.077400Z

now we could call it from kaocha.repl as well, which I think would be appropriate

plexus 2020-08-14T13:51:05.078300Z

so yes, post-summary is the right place to print this kind of extra info at the end of a test run

plexus 2020-08-14T13:51:42.078600Z

that's also for instance what the profiling plugin uses

reefersleep 2020-08-14T14:01:13.078700Z

Call what and when? kaocha.runner when testing in the REPL, or?

plexus 2020-08-14T14:48:49.079Z

no, call the post-summary hook from kaocha.repl

plexus 2020-08-14T14:49:51.079200Z

basically kaocha.repl and kaocha.runner both are ways to invoke kaocha.api. We don't want post-summary to be called from api, because there are use cases where api is used directly where it's not appropriate to call post-summary (this is the main difference between post-summary and post-run, the former is called outside kaocha.api, the latter inside)

reefersleep 2020-08-14T21:26:04.080600Z

@plexus how do you specify the equivalent of

{:plugins [:kaocha.plugin/hooks]
        :kaocha.hooks/post-run [util.hooks/blame-report]}
When running in the terminal? I see that I can specify --plugin kaocha.plugin/hooks, but I don’t see how to specify options for that plugin.

reefersleep 2020-08-14T21:27:58.080700Z

Alrighty 🙂 So that’s a feature request for kaocha, I guess.

reefersleep 2020-08-14T21:41:33.081100Z

I guess by using a tests.edn. Trying that now.

reefersleep 2020-08-14T21:52:43.083400Z

Seems to work great! Though kaocha.hooks/post-summary does not seem to work, only kaocha.hooks/post-run, like we tried earlier. Kaocha is not documented to have post-summary either. Are you ahead of the published code when talking about post-summary? 😅

plexus 2020-08-15T12:18:03.084200Z

Oh you're actually right, post-summary has been around for a while for use in plugins, but it wasn't supported by the hooks plugin as a standalone hook. That's fixed in master. I'll push a release. Are you using deps.edn? In that case you can just use the version off github

plexus 2020-08-15T12:19:18.084400Z

Ah I guess you're using leiningen. I'll cut a release, might not make it today but should be out by tomorrow.

🍻 1
reefersleep 2020-08-14T22:01:16.083500Z

Hm, no, I see it in the source.

reefersleep 2020-08-14T22:06:21.083700Z

Expected:
  true
Actual:
  -true +false
171 tests, 675 assertions, 1 failures.
Error encountered performing task 'run' with profile(s): 'base,system,user,provided,dev,kaocha'
Suppressed exit
Am I maybe doing something wrong that causes the code to exit before the report is printed?