emacs-buttercup
emacs-buttercup copied to clipboard
`:var` does not seem to work with dynamic vars
I was trying to do something like this: for each spec set a dynamic variable to a test mock value. I can't use before-each and setq because that would modify the global state forever.
However, emacs allows let binding a dynamic variable and that should then work for all the code called in that scope (dynamic).
;; -*- lexical-binding: t -*-
(defvar my-dynamic-var 123)
(describe "Test dynamic vars by binding in describe-form"
:var ((my-dynamic-var nil))
(it "does rebind dynamic vars"
(expect my-dynamic-var :to-be nil)))
(describe "Test dynamic vars by binding in it-form"
(it "does rebind dynamic vars"
(let ((my-dynamic-var nil))
(expect my-dynamic-var :to-be nil))))
I would therefore expect this to work, but the test fails. I think the it form is called in a different "dynamic" environment than the describe. I'm not sure if this can be somehow reconsiled. A "work around" is to let bind the variable in the it forms but this leads to major duplication (I have 20-40 it forms for each describe)
Maybe there could be some other mechanism to do this, like an unwind-protect that would set and re-set the variables but this would need a different keyword or notation.
There's also the useful idiom to preserve a value of a special variable, for example kill-ring like so
(let ((kill-ring kill-ring)) ; bind var to itself
(whatever-here))
The whatever-here code can do whatever but the global state of the kill-ring will be preserved when it returns. This is quite useful in tests also. This would also be possible if :var worked like this, i.e. preserved the "call stack" and dynamic lookup.
I think the analysis is as follows: since buttercup relies on lexical scoping, the let form generated by :var is placed as parent to all the "gathering functions" (buttercup-before-each, buttercup-it etc) which generate lambdas which capture these variables in their context. Then this works fine because it's the same variable in all the closures and so before-each affects the it forms as expected.
But then the functions are simply run in a loop from somewhere else and the dynamic scope is no longer extended there. Yea, not quite sure what to do here, but a feature to modify dynamic scope would be very very rad.
In the meantime, it might be prudent to have :var throw an error if lexical binding is not enabled.
Buttercup doesn't work without lexical binding, this is documented in two places in doc/writing-tests.md. So a global error for disabled lexical binding seems more appropriate.
Warning for dynamic variables used in a :var could be useful though.