Le dim. 24 nov. 2019 à 18:54, Ludovic Courtès <l...@gnu.org> a écrit : > > Hello! > > It seems that if you ‘set!’ a public variable of a declarative module, > the change is visible to all the module users, but it’s not necessarily > visible to procedures within that module, presumably because they use an > inlined or specialized variant of that thing. > > I would have imagined that public bindings are considered mutable and > thus not subject to inlining; OTOH, that would obviously be a loss, so > the current approach makes sense. > > Anyway, it complicates a use case for me. In Guix, we “mock” bindings > like so: > > (define-syntax-rule (mock (module proc replacement) body ...) > "Within BODY, replace the definition of PROC from MODULE with the > definition > given by REPLACEMENT." > (let* ((m (resolve-interface 'module)) > (original (module-ref m 'proc))) > (dynamic-wind > (lambda () (module-set! m 'proc replacement)) > (lambda () body ...) > (lambda () (module-set! m 'proc original))))) > > and that allows us to write tests that temporarily modify public (or > private!) bindings. > > It seems like this could be addressed by compiling selected modules with > ‘user-modules-declarative?’ set to #false, or by avoiding the above hack > altogether when possible, but I thought I’d share my impressions and > listen to what people think. :-) >
For what it is worth, in my project I take a different approach to mock. Some may call it inversion-of-control of something like that. Basically, everything that must be mocked is passed as a procedure. For instance, in babelia there is a pool of thread worker with a fibers mainthread. There is three primitives: initialize the thread pool, apply a thunk in a worker, and for-each-par-map. During the tests I can not run the pool of thread worker because of the #:drain behavior [0], I could workaround it some other way like explained in the ticket by Wingo. [0] https://github.com/wingo/fibers/issues/30 Anyway, the other advantage of making the thread pool configurable is that my OKVS abstraction is not tied to it. The full-text search abstraction dubbed fts is passed `apply` and `for-each-map` as last two arguments in the constructor: (define-record-type <fts> (make-fts engine ustore prefix limit apply for-each-map) fts? (engine fts-engine) (prefix fts-prefix) (ustore fts-ustore) (limit fts-limit) (apply %fts-apply) (for-each-map %fts-for-each-map)) Then during the tests or else, I can pass custom implementations: (define (for-each-map sproc pproc lst) (for-each sproc (map pproc lst))) (define fts (make-fts engine ustore '(test-fts-prefix) 1 (lambda (thunk) (apply thunk '())) ;; fts-apply for-each-map)) Similarly, in OKVS SRFI, see [1], to make database engine swappable, I rely on a similar pattern that was dubbed typeclass object by SRFI-128 (comparators). In those cases, the record instance contains only procedures. [1] https://github.com/scheme-requests-for-implementation/srfi-167/blob/master/srfi/engine.sld#L1 The approach I described, that boils down to passing a wanna be mocked procedure as argument, can work. > Thanks, > Ludo’. Hope this helps.