At work, we have this goofy Python that creates “magic” CLI args. Nowhere in
the codebase are the arguments defined. If you run
program.py --help you get
the empty argparse message.
The “magic” happens in a dependency program we pull in (also ours) that
interrogates one of the class methods
program runs and builds a bunch of args
Yes, this is bad, but that’s not why we’re here.
program codebase also comes with a bunch of config files in various
formats that contain code to execute
program. These are not actually part of
program, but the live in its repo.
If a developer changes the innocuous method mentioned above, the arguments
required to run
program will change at runtime, and all the config files will
Yes, I did that, but it didn’t work.
I also checked my years of first hand experience against the Python Universe to see if this was just “pythonic.”
In : import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.
I was not at liberty to change the imported program with the magic, so I whipped
up a bunch of tests in
program that check the mystery method above, use its
output and some hard-coded magic-knowledge to generate expectations on what is
required from the CLI args.
Then I greps through the codebase to figure out which configs pertain to this, imports and parses them (including some homebrewed jsonette hacky parsing), and figures out if each one has the correct magic args.
This sucked, but it sucked so much less than all the magic happening silently and breakages not discovered until runtime.
Plan C(lean up that hardcoded garbage)
If the imported program with the magic changed, we were screwed. We were
looking into the black box so hard. To fix that, another merge request (Yes,
Gitlab. You’ve finally broken me down.), I added a test that ignored any
knowledge obtained from inside the black box. All it did was try to run
program with the args pulled from each config. There was a big fat
try...except with a few targeted exception types around it.
This worked quite well… EXCEPT…
argparse didn’t raise its custom exception
all the way to userland. The magic program appeared to catch it and in turn
SystemExit exception. To show something useful to the user, that
meant we had to use
contextlib.redirect_stderr and then relay that to our
Anywho, it’s not perfect, but it’s several steps closer to it than when I first ran into this. The most notable thing, though, and why I wrote this, was a final curveball at the end.
I wanted to demonstrate how awesome this was with a little demo video for the team. Tests running in one terminal, Neovim in the other with those hard to read configs. I made a change and the tests were still green!
What the hot fuck?
I’m a TDD guy, but I spend so much time retrofitting tests to lazy bastards’ code that stuff like this happens. It sucks, but it didn’t make any sense!
Then I changed something else and the tests were red (as expected)!
I kept notes on what worked and what didn’t and eventually a pattern emerged. Then I felt like a real turd.
argparse does a matching to allow