devlog > juce
Restructuring GUI unit tests
Captain's Log: Stardate 77916.5
Today I continued to slog through all the unit tests on MacOS to get more of them working. I got pretty much all of the tests that do not involve OpenCL working, which is very nice.
Most of the problems that gave me grief had to do with the fact that JUCE is really just not designed to make apps that are easily testable. I had found some hacks on Windows that worked well enough, but the same kinds of hacks on MacOS just lead me to a dead end -- OS-level GUI stuff that wasn't playing along.
So, I finally broke down and solved the issue in a more general way, which is to run all the unit tests in a JUCEApplication. This means that the tests are actually running in a full JUCE app, so they have access to all the environmental stuff that JUCE sets up behind the scenes via the app machinery.
This annoys me, because it removes a lot of isolation between the tests. Before, I had hacked a way to destroy and recreate the GUI state between tests, so they couldn't interfere with one another. But now they all use the same application state, etc.
Still, it's nice to have most of the tests passing. I think now I just need to clean up some of the ridiculous hacks I've done for the MacOS support and I can leave it alone for the moment.
Fixing a crash in JUCE
Captain's Log: Stardate 77708.1
It was one of THOSE days again, where you lose a bunch of time to a JUCE bug: https://github.com/juce-framework/JUCE/pull/1370
This morning I wasn't feeling all that energized, so I figured I'd just close out an easy TODO and play some Factorio. The easy task I picked was adding some unit tests for the new MIDI note picking GUI stuff. First I ran into some extremely weird and hard to debug crashes, which ultimately were the cause of the bug I linked (and fixed) above. I've been running into this kind of issue frequently because I appear to be the only one who is really unit testing my JUCE GUI in a sane way. The particular issue I ran into is that I tear down JUCE's GUI thread between tests to keep everything nice and hermetic. Apparently nobody else does that, or they'd likely have run into this bug.
But once I got that fixed, I discovered that my new tests were flaky, which is the worst thing! But every time I tried to reproduce the random failures, say, by running the tests 10 times in a row, they'd work perfectly. I was pulling my hair out until I finally noticed that the random failures only happened when I kicked off the tests and then alt+tabbed to another window. It turns out that some of JUCE's GUI code cares about whether the window is in the foreground. This is why I never could get the tests to fail when I was trying: I was focused on the tests and not alt+tabbing! So they'd always be in the foreground and work correctly.
My ugly solution to this problem was to just force the windows to the front. Fortunately most of the tests don't require real windows, but anything that creates a top-level dialog window does need this. So while the tests run, little windows pop up and close rapidly while parts of the GUI are being tested. Weird, but I'd rather have the code tested than not!
Fractal complexity in MIDI note picker
Captain's Log: Stardate 77699.9
Fractal complexity! The visual keyboard note picker should be so simple, but then you realize that it's a top-level window, and thus does not automatically forward keypresses along to the main top-level window that listens for hotkeys like undo/redo (ctrl+z, ctrl+r). Okay, that actually makes sense, because it would break things if the user could e.g. hit "delete" and kill the entities for which they're picking notes.
But you really do want ctrl+z/r to work while picking notes. So then you listen for those hotkeys in the keyboard window (now centralizing the hotkey so if you decide to change it, you won't end up with two different hotkeys for redo). But then you realize that it's really bad if the user can undo things that happened before the window opened! So you have to create some idea of an undo restriction to allow undo/redo to work as expected for note edits, but not for previous things.
Okay so that works... but does it? You write a simple unit test and it fails. It turns out that keeping a simple low-water mark for the redo stack doesn't work, because when the user makes an edit, the redo stack is invalidated (cleared). So now you have to account for that in the undo/redo restrictor thing.
Great, so now that all works, but: undo/redo causes the window to flicker. WTF? Well, it turns out a hack from a few months ago where you're manhandling the keyboard focus has now come back to bite you. Okay, good, there's a TODO from back then about how to actually solve that problem. Not so good is that it involves rewriting JUCE's concertina class from scratch. Which... you know you need to do, but you just don't want to. ;D The flickering fix can wait until later...