rollbear / trompeloeil

Header only C++14 mocking framework
Boost Software License 1.0
811 stars 86 forks source link

Exception thrown exit #54

Closed tomvierjahn closed 7 years ago

tomvierjahn commented 7 years ago

Using

Tests crash with:

libc++abi.dylib: terminating with uncaught exception of type std::__1::system_error: recursive_mutex lock failed: Invalid argument

in trompeloeil.hpp line 3224:

template <typename Sig>
  struct expectations
  {
    ~expectations() {
      active.decommission();
      saturated.decommission();
    }                                    # line 3224   
    call_matcher_list<Sig> active;
    call_matcher_list<Sig> saturated;
  };

Have you seen this before?

rollbear commented 7 years ago

I'm afraid this is news to me.

Unfortunately I do not have a MacOS dev environment, so I'm a bit in the blind here.

Does this always happen, or is it intermittent? Do you have a back trace? Do you have an expectation in a global/static variable?

tomvierjahn commented 7 years ago

Let me try and summarise what we are doing:

open_gl_mock.hpp

#ifndef TESTS_SRC_OPEN_GL_MOCK_HPP_
#define TESTS_SRC_OPEN_GL_MOCK_HPP_

#include "trompeloeil.hpp"

#include "GL/glew.h"

class OpenGLMock {
 public:
  MAKE_MOCK1(glClear, void(GLbitfield));
};

extern OpenGLMock open_gl_mock;

#endif  // TESTS_SRC_OPEN_GL_MOCK_HPP_

open_gl_mock.cpp

#include "open_gl_mock.hpp"

OpenGLMock open_gl_mock;

extern template struct trompeloeil::reporter<trompeloeil::specialized>;

extern "C" {
void glClear(GLbitfield mask) {
  open_gl_mock.glClear(mask);
}
}

test_clear_pass.cpp

#include "GL/glew.h"

#include "phx/clear_pass.hpp"

#include "trompeloeil.hpp"

#include "catch/catch.hpp"

#include "open_gl_mock.hpp"

extern template struct trompeloeil::reporter<trompeloeil::specialized>;

SCENARIO("The clear pass clears the active color and depth buffers.",
         "[phx][phx::ClearPass]") {
  GIVEN("A clear pass") {
  phx::ClearPass clearPass;

    WHEN("We execute the clear pass.") {
      THEN("The glClear is called properly") {
        REQUIRE_CALL(open_gl_mock,
                    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
          .TIMES(1);
        clearPass.Execute();
      }
    }
}
tomvierjahn commented 7 years ago
#0  0x00007fffc50ccd42 in __pthread_kill ()
#1  0x00000001003007a0 in pthread_kill ()
#2  0x00007fffc5032420 in abort ()
#3  0x00007fffc3b8594a in abort_message ()
#4  0x00007fffc3baac17 in default_terminate_handler() ()
#5  0x00007fffc46ba713 in _objc_terminate() ()
#6  0x00007fffc3ba7d49 in std::__terminate(void (*)()) ()
#7  0x00007fffc3ba7dc3 in std::terminate() ()
#8  0x000000010000521f in __clang_call_terminate ()
#9  0x00000001000052fc in trompeloeil::expectations<void (unsigned int)>::~expectations() at /Users/tom/.conan/data/trompeloeil/v25/rollbear/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/trompeloeil.hpp:3224
#10 0x0000000100005275 in trompeloeil::expectations<void (unsigned int)>::~expectations() at /Users/tom/.conan/data/trompeloeil/v25/rollbear/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/trompeloeil.hpp:3221
#11 0x0000000100005255 in OpenGLMock::~OpenGLMock() at /Users/tom/rwth/code/project_phoenix/tests/src/open_gl_mock.hpp:31
#12 0x0000000100004c85 in OpenGLMock::~OpenGLMock() at /Users/tom/rwth/code/project_phoenix/tests/src/open_gl_mock.hpp:31
#13 0x00007fffc5033178 in __cxa_finalize_ranges ()
#14 0x00007fffc50334b2 in exit ()
#15 0x00007fffc4f9e23c in start ()
tomvierjahn commented 7 years ago

BTW how can I get v28 via conan?

rollbear commented 7 years ago

OK, cool. I'm pretty sure I understand what the problem is; C++ and its static initialization order fiasco, or in this case destruction.

Trompeloeil has a static recursive mutex, which is seen in the exception thrown. Your open_gl_mock is a global, which has the same problem. The init fiasco is avoided by constructing the mutex in a function call, but the order of destruction is still undefined between the mutex and the global mock object. In this case, the mutex was destroyed first. When the open_gl_mock is destroyed, it tries to acquire the mutex.

I'll see if I can make this work with the nifty counter idiom (from the same FAQ.) This is likely to take a few days, though.

Do you think you can work around it until then? One possible (but ugly, I admit) solution is to allocate your global mock on the heap, and leak it. Better, but probably more work, is to not have the mock as a global.

tomvierjahn commented 7 years ago

I'll try this.

rollbear commented 7 years ago

v28 on conan. Yes, probably.

https://bintray.com/trompeloeil/trompeloeil/trompeloeil%3Arollbear/v28%3Astable

I'm in their queue for inclusion in "conan-center," but they're pretty over worked, and I expect feedback with changes I need to make, so it's likely to take a while.

tomvierjahn commented 7 years ago

Leaking does the trick. Terrific … yet terrifically ugly :-)

tomvierjahn commented 7 years ago

RE v28: What a pity. I would like to avoid adding more remotes. Otherwise, we diminish the advantage of a package manager …

tomvierjahn commented 7 years ago

… would you mind if we provided it in our own third party channel? That remote has to be included anyway.

rollbear commented 7 years ago

If you can try the latest version on branch develop, I'd be happy to receive your feedback.

I was not able to reproduce your exact problem, but I wrote a small nonsense program that exposed the bug I'm convinced is behind this, and could solve it using the exact same implementation technique. Unless I've misunderstood the situation, this should solve your problem, but there is always the chance for a misunderstanding.

Regarding conan: Sure, go ahead and mirror in your own channel if you want to.

tomvierjahn commented 7 years ago

Does not work unfortunately.

Output:

libc++abi.dylib: terminating with uncaught exception of type std::__1::system_error: recursive_mutex lock failed: Invalid argument

Stack Trace:

#0  0x00007fff9ba69d42 in __pthread_kill ()
#1  0x00000001004dc7a0 in pthread_kill ()
#2  0x00007fff9b9cf420 in abort ()
#3  0x00007fff9a52294a in abort_message ()
#4  0x00007fff9a547c17 in default_terminate_handler() ()
#5  0x00007fff9b057713 in _objc_terminate() ()
#6  0x00007fff9a544d49 in std::__terminate(void (*)()) ()
#7  0x00007fff9a544dc3 in std::terminate() ()
#8  0x0000000100405cdf in __clang_call_terminate ()
#9  0x0000000100409b2c in trompeloeil::expectations<unsigned int ()>::~expectations() at /Users/tom/Desktop/trompeloeil.hpp:3242
#10 0x0000000100409965 in trompeloeil::expectations<unsigned int ()>::~expectations() at /Users/tom/Desktop/trompeloeil.hpp:3239
#11 0x00000001004097e5 in OpenGLMockInternal::~OpenGLMockInternal() at /Users/tom/rwth/code/project_phoenix/tests/src/mocks/open_gl_mock.hpp:54
#12 0x0000000100404ab5 in OpenGLMockInternal::~OpenGLMockInternal() at /Users/tom/rwth/code/project_phoenix/tests/src/mocks/open_gl_mock.hpp:54
#13 0x00007fff9b9d0178 in __cxa_finalize_ranges ()
#14 0x00007fff9b9d04b2 in exit ()
#15 0x00007fff9b93b23c in start ()
tomvierjahn commented 7 years ago

But thank you very much for trying to fix it

rollbear commented 7 years ago

Rats. I thought I had this one. Oh well, back to the drawing board. Thanks for the feedback.

tomvierjahn commented 7 years ago

Just gave it an extra try with an MWE:

test.cpp:

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

#include "trompeloeil.hpp"

namespace trompeloeil {
template <>
void reporter<specialized>::send(severity s, const char* file,
                                 unsigned long line, const char* msg) {
  std::ostringstream os;
  if (line) os << file << ':' << line << '\n';
  os << msg;
  auto failure = os.str();
  if (s == severity::fatal) {
    FAIL(failure);
  } else {
    CAPTURE(failure);
    CHECK(failure.empty());
  }
}

}  // namespace trompeloeil

mock.hpp:

#ifndef MOCK_HPP_
#define MOCK_HPP_

#include "trompeloeil.hpp"

class Mock {
 public:
  MAKE_MOCK0(foo, void());
};

extern Mock mock;

#endif  // MOCK_HPP_

mock.cpp:

#include "mock.hpp"

extern template struct trompeloeil::reporter<trompeloeil::specialized>;

Mock mock;

main.cpp:

#include <iostream>

#include "catch.hpp"
#include "mock.hpp"

extern template struct trompeloeil::reporter<trompeloeil::specialized>;

TEST_CASE("Try it", "[mwe]") {
  REQUIRE_CALL(mock, foo());
  mock.foo();
  CHECK(true == true);
}

Results

So I might have made a mistake when trying to include the latest develop version in our larger project. I'll come back once we have changed that …

offa commented 7 years ago

I can confirm this issue with latest master and V28 an Travis CI (Trusty) with all versions of clang (tested 3.8, 3.9, 4.0), while GCC (5, 6, 7) works. Compiling locally with GCC 7 and Clang 4.0 doesn't show the issue and works too.

Switching to latest develop fixed the issue for me.

rollbear commented 7 years ago

Sorry for my silence. I've been failing miserably in reproducing this problem. I'll have to take your word for it that my fix on develop is OK. Ill make a release shortly, unless you find a reason for me to work more on this.

tomvierjahn commented 7 years ago

No worries, Björn.

I cannot check it before Sep 25.

Am 16.09.2017 um 13:43 schrieb Björn Fahller notifications@github.com:

Sorry for my silence. I've been failing miserably in reproducing this problem. I'll have to take your word for it that my fix on develop is OK. Ill make a release shortly, unless you find a reason for me to work more on this.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

rollbear commented 7 years ago

Fix included in release v29. Reopen if there are still problems.