Hello Jucers,
I would like to run what is strictly need to receive OSC messages outside a JUCE app or plugin. The goal is to run behaviour-driven unit tests of classes with the Catch2 framework.
I understand that to get OSC message to my class, I will need the Message system of JUCE. So I wanted to run that “by hand” with a call to juce::MessageManager::getInstance() and juce::MessageManager::getInstance()->runDispatchLoop() in its own thead. But this does not seem to work… The thread seems to be dying, I don’t know exactly why yet.
Using the START_JUCE_APPLICATION macro is not an option either because it uses an main function and Catch2 frameworks needs its own main also…
So here is my question: what is strictly needed around (instances, classes, threads) to receive OSC messages with the OSCReceiver class and how to start this “by hand” outside a JUCE app or plugin?
Thank’s for any pointers!
Best,
Benjamin
PS: I have already found this thread: Using Catch2 but need a MessageLoop
And put the juce::ScopedJuceInitialiser_GUI in the Catch2 init and/or in my TEST_CASE but this does not seem to make any difference…
PPS: testing the JUCE_ASSERT_MESSAGE_MANAGER_EXISTS before my test does not stop/fails (so MessageManager should exist… ?), so what am I missing??
I’ve done this in the past, at the time the best method I found was to create an Application. Importantly it had to be an application not just an executable. On macOS you can then call the binary of that application just like any executable, and for Windows it shouldn’t really make a difference. Then I ran catch2 on its own thread rather than trying to manually run the JUCE message thread. I think if you inherit from JuceApplication you get the initialise() virtual function which gives the command line args that you can pass to catch 2 and start it up in it’s own thread (I recall I may have been very sneaky and passed the arguments in a different way as I really wanted access to argc and argv which JUCE didn’t give direct access to). That way the message thread and the catch2 thread can run side by side.
The problem I found was that I could never get [NSApp run] inside runDispatchLoop() to continuously run. Maybe you’ll have better luck though, it was a few years ago (before I joined the JUCE team) that I done it.
I do have some initial ideas on some internal branches for mocking the message manager to handle this kind of thing in tests but there is a lot to consider and I’m not likely to get back to that any time soon.
Hi @anthony-nicholls
Thank’s for your answer!
Instead of running runDispatchLoop, I can run runDispatchLoopUntil as described in this post: Suggested improvements in runDispathLoopUntil() on OSX
But even without a 0 argument, I still don’t get messages… And I don’t know why.
Debugging with XCode I think I get some events but I don’t know what happens next…
OOOoooh!
I got it working, provided that I call juce::MessageManager::getInstance()->runDispatchLoopUntil(TIMEOUT); in the same thread as the test.
I have difficulties understanding why but that should actually be usable and pretty handy for tests as I can also test if the classes are reacting to an OSC message in the correct timing (there are some Timer involved)!
For some others, maybe, sometime, here is my code:
SECTION ("network loopback")
{
TestOscReceiver::MessageBreak connect ("/plugin/connect");
auto oscListener = async(TestOscReceiver::runBreak, ref(connect));
testConnectionManager.discover(); // should send an `/plugin/connect xxxxx` message with xxxxx begin a socket port number
int pluginPort = oscListener.get();
auto fullmessage = oscPrefix+"/targetList";
TestOscReceiver::MessageCount targetList (fullmessage.c_str());
oscListener = async(launch::async, TestOscReceiver::runTimeout, ref(targetList), TIMEOUT);
TestOscSender::LocalMessageSender sender(pluginPort, "/plugin/connected", uuid);
juce::MessageManager::getInstance()->runDispatchLoopUntil(TIMEOUT);
// testConnectionManager should react to the "/plugin/connnected uuid" message by sending a "/.../targetList" message
int rcount = oscListener.get();
CHECK(rcount > 0);
}
testConnectionManager is an instance of the class I am testing.
TestOscReceiver::MessageBreak is an asynchronous OSC receiver made with OSCPack (GitHub - RossBencina/oscpack: Automatically exported from code.google.com/p/oscpack) and TestOscSender::LocalMessageSender is an OSC message sender to 127.0.0.1 also using OSCPack.
I would discourage using runDispatchLoopUntil as it means enabling modal loops.
Is there a reason you can’t run Catch2 in another thread like I suggested? I’ve done this to run tests exactly like you describe and I found it to work very well. If you need anything in the Catch2 thread to run on the message thread you can use messageManager::callSync() but I suggest only using it when you have to.
Just seen that you have it working now, that’s great although I would probably still discourage runDispatchLoopUntil().
Thank’s again!
Yes, I’ve seen that runDispatchLoopUntil() is forcing to use modal loops. For now, I have no idea what that means…
Running Catch2 in another thread is not a problem. But what I would like to avoid is having a JUCE Application to test classes that may be used/extracted at some point from the JUCE environment.
But reading again what you wrote, I am not sure if you suggested to have some “dummy app” just to have JUCE running and then having Catch2 somehow running “separated” from that… Is that what you are saying?
Or are you suggesting to build a JUCE App which would itself call the Catch::Session().run ?
Anyway, I will continue digging and refactoring if needed along the way.
What are the drawbacks of enabling modal loops? (What are those… ?)
For the context of unit tests with Catch2, I did the following:
-
The testing executable is a JUCE executable (loaded with CMake with juce_add_console_app)
-
I have my own main (not the Catch2-supplied main) that initialized the message thread with juce::ScopedJuceInitialiser_GUI before calling Catch::Session().run(argc, argv)
(You can also place that in individual tests if you want to)
-
I enable modal loops (only for the testing app!) and use runDispatchLoopUntil(1) to wait for message thread events during the tests.
It would be a JUCE App that calls Catch::Session().run() on a separate thread, once the tests have finished the thread would call JUCEApplicationBase::quit() to notify the app to stop the message loop. But to be clear you could still invoke your app from the command line, you wouldn’t need to implement a UI.
I’ve probably overlooked something and the below code is untested but I think it would be roughly as simple as this (but get JUCE to add an app target from CMake or the Projucer)…
class MyTestApp : public juce::JUCEApplication
{
public:
void initialise (const juce::String& commandLine) override
{
juce::Thread::launch ([&]
{
Catch::Session().run (/* pass the args from commandLine here */);
quit();
});
}
void shutdown() override {}
const juce::String getApplicationName() override { return "MyTestApp"; }
const juce::String getApplicationVersion() override { return "1.0"; }
};
START_JUCE_APPLICATION (MyTestApp)
In your specific use case it may actually work very well for you. They are generally used to implement some blocking dialog that still allows the message loop to run, here is a waring in the JUCE docs about modal loops
Modal loops are a dangerous construct because things that happen during the events that they dispatch could affect the state of objects which are currently in use somewhere on the stack, so when the loop finishes and the stack unwinds, horrible problems can occur. This is especially bad in plugins, where the host may choose to delete the plugin during runModalLoop(), so that when it returns, the entire DLL could have been unloaded from memory! Also, some OSes deliberately make it impossible to run modal loops (e.g. Android)
In your case you’re in full control, you’re unlikely to hit these kinds of issues.
One other thing to note is that any testing with runDispatchLoopUntil() is minimal from our end as it’s generally discouraged.
Other issues I see
- You’ll always have to wait at least 1ms even if the code executes in much less time
- You might have to estimate how long something will take, for example if there are multiple messages that need to be processed on the message thread and one of them takes a long time. If you over estimate, as far as I can tell you’ll always be running the loop for that length of to time even if you’ve actually completed the task in a much shorter space of time (maybe you could call
runDispatchLoopUntil (1) in a loop with some check in-between to see if the condition has already been met).
None of the above seems like a deal breaker though.
It would be awesome if the JUCE team can offer a solution for that without modal loops, it’s really the only solution for testing.
Calling tests from another thread is generally not possible in most JUCE code, especially UI related code - there are asserts all over the place if you do that.
I have plans for a better solution, but to address the assertions when you’re running on another thread run anything that needs to run on the message thread inside a call to MessageManager::callSync(). That’s basically what we were doing in my previous job (although callSync didn’t exist so we rolled our own).
1 Like
Hi there,
@eyalamir : that’s almost what I do, except for the juce_add_console_app that I do not use. I’m doing it with the CMake standard add_executable. It works smoothly because overall, I only need 2 JUCE modules to run the tests. I’m guessing that if you have a lot of them and/or need JUCE UI, then juce_add_console_app is a much simpler option.
Reading along the forum post, I believe there are more project using this methodology for testing with Catch2!
@anthony-nicholls: thank’s for the kickstart of JUCE app running Catch2. I will give it a go and see how that pan out for me.
One point that is important for this project is that the OSC interoperability has to be rock-solid. That’s also why I am formatting OSC tests message with another library than JUCE’s. Even if it shouldn’t, there are different ways to format and encapsulate OSC messages with/out sender’s IP, with/out bundles, with/out timetags, with/out padding aso and I need to be sure that any message from any sender is correctly received and interpreted by our code!
Hence usage of Catch2/OSCPack/Netif… for the tests even though they are dependencies that are not used for the final production.
I don’t see any of that causing any issues for the technique I’m suggesting above. If you already have those libraries working happily alongside JUCE then I wouldn’t expect a different thread to make a difference.
Yes, no, I agree! I wasn’t mentioning this with regards to the App solution that you recommend… I am confident that it should not make any difference indeed.
Thanks again @anthony-nicholls
1 Like
Yep! That works 
Just missing the constructor & destructor in your class definition…
Then, my test code runs unchanged except for the call to runDispatchLoopUntil() that I don’t need anymore. And so I don’t need the modal loops to be enable.
That’s a clean enough solution for me! Thanks
1 Like
Glad it’s worked for you. I’ve just noticed looking back at my example that it doesn’t capture the return value which will be very important when running your tests! I think that can easily be achieved be passing the result of Catch::Session().run() to setApplicationReturnValue() before calling quit().
Yes, indeed, I was about to post that at the moment for future readers…
I had to make it so:
const int result = Catch::Session().run(/* pass the args from commandLine here */);
setApplicationReturnValue (result);
quit();
In particular, to use it in a CI context.
Thx
1 Like