Skip to content

lein test loads namespaces out of order and multiple times #2715

Description

@frenchy64

Initial debugging steps
Before creating a report, especially around exceptions being thrown when running Leiningen, please check if the error still occurs after:

  • Updating to using the latest released version of Leiningen (lein upgrade).
  • Moving your ~/.lein/profiles.clj (if present) out of the way. This contains third-party dependencies and plugins that can cause problems inside Leiningen.
  • Updating any old versions of plugins in your project.clj, especially if the problem is with a plugin not working. Old versions of plugins like nREPL and CIDER (as well as others) can cause problems with newer versions of Leiningen.
  • (If you are using Java 9 or newer), updating your dependencies to their most recent versions. Recent JDK's have introduced changes which can break some Clojure libraries.

Describe the bug
lein test can reload namespaces out of order. This can leave deftypes implementing expired protocol interfaces, and thus an exception will be thrown when attempting to invoke a protocol method.

This is also a potential performance problem, since a namespace maybe reloaded twice.

To Reproduce
A possible cause of this bug is that lein test uses clojure.core/require's :reload flag.

Working backwards from there, a simple way to make require :reload reload things out of order is to call

(require :reload 'A 'B)

where A depends on B. B will be loaded twice, first as a dependency of A, then via the :reload logic.

If B contains a protocol and A contains a deftype, then the deftype will implement a now-stale interface.

This scenario is demonstrated here, and can be triggered with lein test.

The concrete scenario involves 2 namespaces:

(ns lein-test-reload-bug.a-deftype
  (:require [lein-test-reload-bug.b-protocol
             :refer [B]]))

(deftype A []
  B
  (b [this]))
(ns lein-test-reload-bug.b-protocol)

(defprotocol B
  (b [this]))

Notice how this matches the hypothetical scenario, except

  • A = lein-test-reload-bug.a-deftype
  • B = lein-test-reload-bug.b-protocol

The following require is called by lein test:

(require :reload 'lein-test-reload-bug.a-deftype
                 'lein-test-reload-bug.b-protocol)

Actual behavior
lein-test-reload-bug.b-protocol is loaded twice, the second time is after lein-test-reload-bug.a-deftype, thus leaving it in a bad state.

It is now impossible to make instances of A that can be called via the protocol, because it implements an old protocol.

Relevant output from sample project:

lein test lein-test-reload-bug.core-test
"The current hash of interface lein_test_reload_bug.b_protocol.B is" 1214133948
"The current instance of A implements lein_test_reload_bug.b_protocol.B with hash" -1634164376

ERROR in (a-test) (core_deftype.clj:583)
Uncaught exception, not in assertion.
expected: nil
  actual: java.lang.IllegalArgumentException: No implementation of method: :b of protocol: #'lein-test-reload-bug.b-protocol/B found for class: lein_test_reload
_bug.a_deftype.A
 at clojure.core$_cache_protocol_fn.invokeStatic (core_deftype.clj:583)
    clojure.core$_cache_protocol_fn.invoke (core_deftype.clj:575)
    lein_test_reload_bug.b_protocol$eval418$fn__419$G__409__424.invoke (b_protocol.clj:5)
    lein_test_reload_bug.core_test$fn__448.invokeStatic (core_test.clj:39)
<SNIP>

See lein-test-reload-bug.core-test/a-test for exact test setup, but it boils down to the familiar "Foo is not Foo" problem.

Expected behavior
lein-test-reload-bug.b-protocol is loaded exactly once.

Link to sample project
https://github.com/frenchy64/lein-test-reload-bug

Logs

Environment

  • Leiningen Version: Leiningen 2.9.5 on Java 1.8.0_275 OpenJDK 64-Bit Server VM
  • Leiningen installation method: manual
  • JDK Version:
openjdk version "1.8.0_275"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_275-b01)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.275-b01, mixed mode)
  • OS: macOS 10.15.7

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions