emacs-buttercup icon indicating copy to clipboard operation
emacs-buttercup copied to clipboard

`:var` does not seem to work with dynamic vars

Open Fuco1 opened this issue 7 years ago • 3 comments

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.

Fuco1 avatar Jul 23 '18 11:07 Fuco1

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.

Fuco1 avatar Jul 23 '18 11:07 Fuco1

In the meantime, it might be prudent to have :var throw an error if lexical binding is not enabled.

DarwinAwardWinner avatar Nov 05 '19 19:11 DarwinAwardWinner

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.

snogge avatar Nov 05 '19 23:11 snogge