Stephen Rees-Carter

March 12, 2021

The difference a single character makes...

I love debugging weird bugs. There is something fun about tracking down a weird bug, spending time replicating the circumstances, and eventually tracing the issue to the cause of the bug. Once you've found the cause, the fix is often incredibly trivial - something that was overlooked or not considered when the code was originally written. More often than not, you find a single character is either missing or present when it shouldn't be.

I just tracked down such a bug to discover exactly that. A single character was missing from my code, and it broke the tests in an incredibly entertaining way. Let's take a look!

While writing a unit test for code which I was confident would work, I encountered the following failure in PHPUnit:

Failed asserting that true is false.

Me: Sorry, wait, what?? My code returns false. I'm 100% sure it does!

The code looked good:

$converted = $unused->fresh();
$this->assertTrue($converted->isUnused());
$this->assertFalse($converted->legacy);

It's grabbing a fresh copy of the model from the database, first checking if the converted model is marked as Unused or not, and then checking the legacy flag specifically. It passed the first assertion, which meant the code that updates legacy was running. But why was I getting a false?

My next step was to dump some debugging output:

$converted = $unused->fresh();
dump($converted->legacy);
$this->assertTrue($converted->isUnused());
$this->assertFalse($converted->legacy);

(Yep, dump-powered debugging. 😎)

I dutifully ran the test, expecting to see true returned, so I could debug up the chain... and yet...

image.png


Me: WHAT??!! But it's true on the next line... right? What is going on??
 
I stopped and looked at my code and suddenly realised that there is some code in the middle I had completely overlooked! This bit...

$converted->isUnused()

So I jumped into the method and, well, see if you notice it...

image.png


It all made sense.

One missing character and weirdness reigned down.

The fix was trivial, and I went to my way, writing the next test.

If you're unfamiliar with PHP, a single equals sign is an assignment. This code should have used a double (==) or tripe (===) equals sign to provide a comparison of the value of "$this->legacy" to "true". It can also be shortened to simply "$this->legacy".

As strange as it may sound, finding this bug was what I'd call fun. The sense of victory at having found and fixed the bug was real, regardless of the trivial nature of the bug or the simplicity of the fix.

This is one of the joys of being a developer.