So a couple of weeks ago I got the chance to chat to Damian about
IO::Prompt and my completion patch. While he rejected the patch because the interface was sucky (and I agree), he accepted my offer to take on maintenance duties for the module. Thats not it for completion though; we’re currently designing a much better completion and history interface, which I’ll write more about that some other time. My first trick will be to get a test suite up and running.
IO::Prompt doesn’t currently have a test suite, and I’m not confident that I’ll be able to make any significant changes without breaking whats there, so the current functionality has be recorded. The difficult thing about it is that we’re testing something terminal based, so we have to pretend to type something, and then watch not only what the API returns, but also watch what appears on the screen.
This turns out to be quite complicated. The module opens
/dev/tty directly, both for reading and writing, so we need to intercept the calls to
CORE::GLOBAL::open) and returns some filehandles we can manipulate directly. My first cut used basic scalar handles, but then I ran into further trouble when I found that the module uses
-t to see if its talking to a terminal. Obviously my scalar handles are not terminals, so I needed a way to convince
After a deep tour into the guts of Perl itself (a fascinating and scary place) I determined that there’s really no pleasant way of overriding
-t, though there is a patch under consideration for 5.10, and I did figure out a really evil way that might do it by twisting the optree in ways that I wouldn’t dare give to the world. So the only other option is to somehow produce filehandles that are in fact terminals.
IO::Pty provides the answer, by allowing me to get pseudo-terminals from the operating system. I kinda didn’t want to go there, because it ties the implementation to systems that have terminals, which doesn’t include Windows, but I’ve since decided that it’ll be fine for now since the current code hits
/dev/tty directly, and that doesn’t exist on Windows either.
Time passes. I play with this module, figure out the difference between master and slave and make a note of it because its stupid and I can never remember, and finally produce
Test::MockTerm. Its not CPAN-ready yet, its currently a build helper for
IO::Prompt, but I think it may have a life of its own someday. Using it, I write some basic tests for
IO::Prompt, and run it .. and it hangs, waiting to read from standard input.
After further perusal of the code, it seems that
IO::Prompt only reads directly from
/dev/tty when the
-argv options are specified. Otherwise, it arranges to read from standard input. However, it does this not be simply using the
STDIN handle, but by using the first file in
ARGV, and if that doesn’t work, trying to open
- (using the single-arg scalar-and-filehandle form of
open). I think (more testing required) Damian did it this way because
STDIN may have been redirected away from wherever
- pointed initially.
This presents an interesting problem. I now need to arrange for opening
- to actually cause my pseudo-terminal input handle to be used instead. But, I’ve already overridden
open, and you can’t have multple overrides, so I need some kind of multiplexing/dispatch thing to figure out which
open replacement to use.
Except I don’t. I’ve just now had a good idea. What if you specified
/dev/tty explicitly on the command line as the input source? Wouldn’t we want that intercepted also? And isn’t that in the scope of what
Test::MockTerm should do? The answer is yes. I’m going to modify my code to look for
/dev/tty in the one-arg form of
open, as well as to look for
- and use the same handles. That should take care of it. Epiphany!
So thats where I’m at for now. This has been an incredibly challenging project so far, and I haven’t actually written any real tests yet! I intend for this code to be released in
IO::Prompt 0.99.5 or 0.99.6, depending on how long it takes.