friday, 15 september 2006

posted at 07:23
tags:

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 open (via 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 -t otherwise.

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 -tty or -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 ARGV and - 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.