This 30-day project explores the refactoring of a legacy system. The Everything Engine is an aging software project that powers Perl Monks, Everything 2, and a few other websites. It suffers from poor design and maintainiability. Learn what it’s like to look over the shoulder of an experienced developer as he refactors, redesigns, and updates the code.
Fixing Failing Tests
Today the goal is to fix the failing tests. Who knows what I’ll find? I’ve tried this a couple of times and usually ended up frustrated that the tests aren’t as nice as they would be if I’d written them today. (Of course, writing the tests as they were a few years ago taught me what I know today, so there’s a circular argument in there somewhere.)
What fails? I forget. Run the tests again.
$ ./Build test
Only:
t/ehfpermissionmenu.t 2 512 26 2 7.69% 22 24
t/ennode.t 255 65280 226 388 171.68% 33-226
First things first — the permission menu test. Oh — the test files have names corresponding to what they test. Someday I’d like to unflatten the hierarchy and store this under t/Everything/HTML/FormObject/permissionmenu.t or something. Maybe tomorrow, or even today if I fix the tests in time.
perl -Ilib t/ehfpermissionmenu.t
There are uninitialized value warnings probably getting in the way and two failed tests. Looking in the test file, I see a few bits I don’t like, so I might as well perltidy them. Why not just perltidy all of the tests? Makes sense to me.
for i in `find .`; do echo $i; perltidy -b $i; rm "$i.bak"; done
Yes, this won’t work on directories, giving an error. That’s fine. I’m
too lazy to look up the right invocation in the find manpage
before issuing the command (find . -type f, actually). This
takes longer than I anticipate, but that’s okay.
This huge commit is #826. Committing takes a while too.
Okay. The permission test still fails. I’m really not sure what’s going
on and FakeNode bothers me, so I’ll try porting a few of the
tests to use Test::MockObject instead. (Yes, I wrote the
latter after realizing how much nicer the interface to the former could
be.)
Oops. After five minutes of that, it’s really not working and I don’t
care to stumble through the tests to figure out why. I plan to port the
tests to Test::Class anyway someday (probably as or after
turning the node objects into proper, Perlish objects), so it’s a low
priority. Back to the failing tests.
Ahh, looking at the code in
Everything::HTML::FormObject::PermissionMenu, the problem is
that the expected field uses a double-colon separator. This was to fix an
old bug reported on SourceForge where fields with colons in them already
had big problems — so we moved to a double-colon separator instead of
URI-encoding the form data appropriately. No one updated the test to use
double colons in the mock data. Changing the one line (the
getBindField() sub definition for FakeNode) fixes
both failing tests. Nice. This is commit #827.
I also ran into something weird where the path appears wrong for the
code when running perl -Ilib t/test_file.t, but I fixed that
by using -Mblib instead. I’ll look into that later.
Thinking more about this bug, it would be nice to only care in one place about the encoding of multiple Everything values in a single form value. That’s definitely a code smell here. I’ll fix that later too.
The other failing test looked awful. Running it reveals only that it
already uses Test::MockObject and an unsuccessful mock (or
incomplete) is causing the error around test #33. That means most of the
194 failing tests will probably pass. (Everything::Node::node
is a big file that I would like to see shrink. It will.)
Telling the mock object to handle the canWorkspace() method
without logging it or even doing anything bypasses the error and the test
file completes, with only one failing test. Excellent! Now the real
debugging begins.
(It’s experience with Test::MockObject and making silly
mistakes that let me fix that so quickly.)
This test fails within nuke() (which removes a node from
the database) within the call to sqlSelectMany(). My initial
guess is because the API there changed somewhat. (You may be thinking right
now, “Why is the database code all mixed up in here?” and that would be
exactly my instinct too. Repeat after me. “Fix later.”)
What is the API to sqlSelectMany() and where is it? It’s
part of Everything::NodeBase. It takes:
- a string list of columns to return
- the name of the table on which to select
- a string containing the search criteria
- other SQL options to pass
- an array reference of bound values for placeholders
Looking at the test, it ignores the fourth argument and tries to find
the fifth in that slot. That won’t work. Changing 4 to
5 in the test makes it pass. That should fix the entire test
file. It does. Now for another checkin, number #828.
You might wonder why I didn’t say anything about that API or its use or even wonder why someone made this mistake. The answer to the first should be familiar. (It’s on my list of things to fix.) The answer to the second is more abstract. I could tear through the checkin logs or annotate the code to see what changed and why. Was it the test? The API? Should I worry about similar bugs in other tests or the code (does it have enough tests?)
The error occurred so long ago that there’s not much I can learn from it now. I just wanted to get the tests to work to have some baseline from which to work.
I have about 15 minutes left to work today, so maybe it’s a good place to move the test files around. I could edit them to fit more modern conventions, but that’s mostly just busy work that won’t make my life easier. Putting them in better directories allows people to map the test files to the modules they actually test more easily, though, so that’s a win.
The old t/base file is the base of all tests; I added it here before I developed a more general “start a new test” template-based Bash alias, so there’s little point in keeping it. Whack:
$ svk rm base
I assume that people know that everything under t/ refers to
Everything::*, so there’s no real need for an
Everything/ directory. Oh, that reminds me. I have to keep
MANIFEST up to date.
The easiest way to make these changes seems like editing a file, then running it through the shell.
$ chdir t
$ ls -1 > list>
$ vi list
This puts a list of all files in t/ into list. I edited
this file, prepending svk mv to the start and putting the
directory and new name at the end. I kept the MANIFEST open in
another terminal to help me get the spelling right. There are a handful of
untested modules here too. I think they’re in the SourceForge task list.
I’ll get to them.
I need to add a couple of directories.
$ svk mkdir HTML
$ svk mkdir HTML/FormObject
$ svk mkdir Node
$ svk mkdir NodeBase
$ sh list
That should be it, apart from editing the MANIFEST. Of course,
./Build manifest does that. (I missed a few files earlier,
strangely enough.)
Do all the tests still pass? I hope so… though I haven’t edited
Build.PL to find them all. Only the ones in t/*.t run. Oops.
File::Find
time! I figured out how to make this work with Module::Build in
Games::PMM, so
stealing from that:
use File::Find;
my @test_files;
find( sub { return unless /\.t\z/; push @test_files, $File::Find::name },
't/' );
# within the Module::Build constructor call ...
test_files => join( ' ', @test_files ),
# ...
With this in place, all of the tests run, but two throw warnings and one fails. Oops. More debugging.
Hmm… this one fails because Everything::Node::theme does not
inherit from Everything::Node::nodeball. That’s because the code
doesn’t do it either. Weird. I assume it should, so I have to fix the code. At
least, I would, if I thought that inheritance worked Perlishly here.
Unfortunately it doesn’t. Then again, there’s probably no problem fixing it to
do so. That means fixing the test a bit too. I hope this merges safely.
Now for the uninitialized value warnings. One’s in t/Node/node.t.
Actually, there are two here. They both relate to the use of
Everything::logErrors(). They test is mocking them inappropriatey
– how odd. Regardless, the tests pass, mostly because of bad regular
expressions. Ugh. Fixing the regular expressions (hey, don’t allow empty
strings!) makes the tests fail as they should, meaning that fixing the tests is
now obvious. That’s a fix.
I wonder if the other error (in t/HTML.t) is similar? Looking at
the code, it’s not. It comes somewhere within the test of
Everything::HTML::urlGen(). I have the sick feeling it’s in the
guts of CGI. After tracking it for a few minutes, it looks like
the attempt to set the script name within %ENV isn’t working –
because it’s a full URL in the test, not just the relative name on the server.
Changing the script name to /this.pl fixes the warnings, but
breaks the tests, which don’t expect the name of the script in the URL. That’s
fine. I know what to do now and can fix the tests.
Mocking things in tests makes life difficult sometimes — but until the architecture of Everything improves with regard to testability, this has to happen.
Everything should pass now without errors or warnings. It does. Here’s another big checkin, #829. Oops. The MIME type is wrong on all of the new test files. Unfun. That’s okay, that’s fixable with the script from day 1.
$ scripts/add_text_file.sh `find t/ -type f`
Of course, having added all of these files already, I’m only running this to set their properties. That’s okay. It just takes a few moments more and gives warnings that Svk has already marked these files as added. I suppose there could be a flag that just sets the properties, but that’s more work and argument handling in the shell and I started to do it before realizing how unpleasant shell argument handling is. That’s fine.
I bet most of the files in the repository have the wrong MIME types set,
so this’ll happen every time I move something. I could run this program
from the root of the repository, then svk revert the changes
in images/, which contains non-text files… seems like that could
save some time. (I could be extra sneaky and temporarily comment out the
svk add line… very clever. That’s especially handy because
there’s now a _build/ and blib/ directory.) This is checkin
#830.
That’s my hour (and a few minutes) for the day. See you next time.

