Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

schwern (1528)

schwern
  (email not shown publicly)
http://schwern.net/
AOL IM: MichaelSchwern (Add Buddy, Send Message)
Jabber: schwern@gmail.com

Schwern can destroy CPAN at his whim.

Journal of schwern (1528)

Saturday June 06, 2009
06:01 PM

Automated Javascript tests with Test.Simple + WWW::Selenium

I've been doing acceptance level QA at my $job lately which means a lot of clicking around in browsers and a lot of writing Selenium tests. Really my job is to reduce the amount of manual testing which needs to be done and automate as much as possible.

I was talking with Zack who said he hates Selenium. What he really meant was he hates testing at the browser level. Its so finicky to write Selenium regression tests that won't break later because the layout changed. He'd rather unit test his Javascript. I pointed out Test.Simple as a solution. Trouble is, that runs in a browser and someone still has to look at it. That's not very useful for automated tests.

So I began wondering... what would it take to pipe the Test.Simple TAP into TAP::Harness? Ideally I want to run the Javascript with a real DOM in a real browser, not some tinker toy simulation. Can I get Firefox to pipe its page rendering to STDOUT? I asked David Wheeler if he knew of anything and pointed me at some unfinished things like JSAN::Prove which requires a bunch of complicated setup and dependencies which I tl;dr'd.

Maybe there's some module on CPAN which can pipe from Firefox. I searched for "firefox" and what comes up but WWW::Selenium. OF COURSE! I can use WWW::Selenium to talk to Selenium Remote Control and get the output of a web page!

    require WWW::Selenium;
 
    # Assumes you have a selenium server running locally on 4444
    my $sel = WWW::Selenium->new(
        host => "localhost",
        post => 4444,
        browser => "*firefox",
        browser_url => "file://nothing"
    );
 
    $sel->start;
    $sel->open('http://isperldeadyet.com');
    print $sel->get_body_text();

Then it's a simple matter of hooking this into TAP::Harness.

#!/usr/bin/perl -w
 
use TAP::Harness;
 
my $harness = TAP::Harness->new({
    exec => sub {
        my( $harness, $file ) = @_;
 
        # Let Perl programs run normally
        return undef unless $file =~ m{\.(js|html)$};
 
        require WWW::Selenium;
        my $sel = WWW::Selenium->new(
            host => "localhost",
            post => 4444,
            browser => "*firefox",
            browser_url => "file://nothing"
        );
 
        require File::Spec;
        my $url = "file://" . File::Spec->rel2abs($file);
        $sel->start;
        $sel->open($url);
 
        # Get whatever's inside <pre id="TAP">
        return $sel->get_text(q{//pre[@id="TAP"]});
    },
    verbosity => 1
});

Then write up a little HTML wrapper to run a basic Test.Simple test.
(Note that t/lib contains the Test.Simple libraries)

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
  <head>
    <script type="text/javascript" src="t/lib/Test/Builder.js"></script>
    <script type="text/javascript" src="t/lib/Test/Simple.js"></script>
    <title>TAP test</title>
</head>
 
<body>
<pre id="TAP">
  <script type="text/javascript">
    plan({ tests: 1 });
    ok( 1, "this is a test from Javascript" );
  </script>
</pre>
</body>
</html>

And run it.

$ perl -w javascript_harness.plx tap.html
tap.html .. ok
All tests successful.
Files=1, Tests=1,  8 wallclock secs ( 0.19 usr +  0.03 sys =  0.22 CPU)
Result: PASS

Its slow, but it's awesome! It gives me something to stick into an
automated smoke server. Developers need a faster turn around time, so
they can quickly and individually unit test it directly in their browser using
Test.Harness.Browser... but that's another show.

But wait, there's more!

That HTML wrapper is icky. Wouldn't it be better if one could just
test Javascript directly? Why yes! How about we generate the wrapper
for .js files?

#!/usr/bin/perl -w
 
use autodie;
use TAP::Harness;
 
my $harness = TAP::Harness->new({
    exec => sub {
        my( $harness, $file ) = @_;
 
        my($type) = $file =~ m{\.(js|html)$};
        return unless $type;  # run Perl normally
 
        require File::Spec;
        require WWW::Selenium;
        my $sel = WWW::Selenium->new(
            host => "localhost",
            post => 4444,
            browser => "*firefox",
            browser_url => "file://nothing"
        );
 
        my $url = $type eq 'js'   ? _testify_javascript($file) :
                  $type eq 'html' ? File::Spec->rel2abs($file) :
                                    die "Unknown type $type";
 
        $sel->start;
        $sel->open($url);
        return $sel->get_text(q{//pre[@id="TAP"]});
    },
});
 
$harness->runtests(@ARGV);
 
# Turn .js files into .html with the Test.Simple libraries loaded.
sub _testify_javascript {
    my $file = shift;
 
    open my $fh, "<", $file;
    my $javascript = join "", <$fh>;
 
    use Cwd;
    use File::Temp 'tempfile';
 
    my $cwd = cwd;
 
    my($tmpfh, $tmpfile) = tempfile( CLEANUP => 1 );
    print $tmpfh <<"END_OF_HTML";
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
  <head>
    <script type="text/javascript" src="$cwd/t/lib/Test/Builder.js"></script>
    <script type="text/javascript" src="$cwd/t/lib/Test/Simple.js"></script>
    <title>TAP test</title>
</head>
 
<body>
<pre id="TAP">
<script type="text/javascript">
$javascript
</script>
</pre>
</body>
</html>
END_O F_HTML
 
    return "file://$tmpfile";
}

Which eliminates all the scaffolding from the Javascript test file.

plan({ tests: 1 });
ok( 1, "this is a test from Javascript" );

And the end result is I can run Javascript and Perl tests together from the command line.

$ perl javascript_harness.plx t/*.js t/*.t
t/tap.js .. ok
t/perl.t .. ok
All tests successful.
Files=2, Tests=2,  9 wallclock secs ( 0.19 usr  0.04 sys +  0.03 cusr  0.01 csys =  0.27 CPU)
Result: PASS

UPDATE: As threatened, its on github.

Monday May 25, 2009
04:01 AM

It is an oldie / But it is still a goodie / Coy is on github

Some of you young whipper snappers might not even know about Coy. This is the module that introduced Damian Conway to the Perl community as the super genius he his. It's a module that generates haiku based on error messages.


#!/usr/bin/perl -w

use Coy;

open my $fh, "<", "doesnotexist" or die "Can't open file: $!";


        -----
        Eshun departs near
        the village. A pair of woodpeckers
        nesting. Bankei.
        -----

                Eshun's commentary...

                Can't open file: No such file or directory

                        ("/Users/schwern/tmp/test.plx Speaks": line 5.)

The American idea of a haiku is that its a 5/7/5 syllable arrangement. So it must have a big dictionary of words and how many syllables they contain, right? Wrong! It has code to figure out how many syllables a word contains. It can also hyphenate them, pluralize them and has a basic understanding of what words and concepts go together sensibly. That's what makes it a Damian module. You should read his original presentation on it if nothing else than to see presentation grand master Damian using Comic Sans! But really because the whole thing is in haiku.

What also makes it a Damian module is it hasn't been touched since 1999. It contains a broken version of Lingua::EN::Inflect which overlays the separated CPAN version. All that clever hyphenating code has never been documented or released or tested. It needs love.

I asked Damian about it. He offered it to me. I can't even keep up with my own stuff so I declined... then I thought better and took it. Its on github now. I've removed the busted Lingua::EN::Inflect, write some basic tests and will re-release once I get PAUSE perms fro Damian. Lingua::EN::Hyphenate should be split out into its own release, if anyone is feeling their oats please take it.

This module is way too awesome to let die.

Sunday May 03, 2009
07:17 PM

Informal PPW 2007 Demographic Survey

At PPW 2007 I have a keynote about Skud's demographic Perl Survey (the domain has sadly lapsed). To illustrate the findings, I did an informal survey of the audience. Fortunately JCap caught it all on film^H^H^H^Helectrons.

First, as a control, I had everyone in the room stand up. Its well known that unless you're Jason Webley you'll never get 100% participation from the audience. So this provides a baseline to compare later measurements against.

As another baseline, we measured those who took the Perl Survey to have some way to compare this set with the set of people who took the survey.

Then I had just the white males stand up. If you visually swap back and forth between that picture and the one of everyone you see there's not a whole lot of difference.

Here we have everyone who is not white and male. Big difference.

And finally a surprising result, those who started Perl during the .com boom. I don't recall exactly what we specified that range as, but it contradicted the data in the survey which shows a big hump during the .com boom.

So there it is, for posterity.

Monday March 30, 2009
05:37 AM

Ability to destroy CPAN #982: UNIVERSAL::require

Adam Kennedy just informed me that UNIVERSAL::require is in the top 100 most depended on modules on CPAN. Roughly #50 with 243 direct dependents and about 900 total dependents. It's an order of magnitude less important than MakeMaker (with about 9000 total dependents) but that's really surprising. And another fun way to destroy CPAN.

Sunday February 22, 2009
07:27 PM

The Secret Life Of Whitespace REVEALED!

When I see code like this:

        # Locale-ize the parser
    my $ampm_list = join('|', @{$self->{_locale}->am_pms});
        $ampm_list .= '|' . lc $ampm_list;

its a dead give-away that the author is using 4-character tabs. Particularly hateful. Not only do I have to sleuth that tabs are being used, but I also have to change my tab stops. (Delicious irony: use.perl translated the tabs to spaces... four spaces... so I had to detabify the example for it to show up correctly).

I count about 20 tabs vs space indention mistakes in this module which just illustrates why tabs are so hateful... THEY'RE INVISIBLE! Little hidden surprises sprinkled all over the code. Even the author can't see the mistake. Tab users like to tout that it allows you to use whatever indentation level you like when in reality you just wind up with mixed up tabs and spaces.

Since I can't KILL ALL TAB USERS I guess I'll just have to get better tools. Fortunately there's already a large discussion of how to show whitespace on the EmacsWiki. I went with show-wspace.el. Now if only emacs could guess at the author's indentation style and adjust itself to match.

Comment from vi user in 3..2..1..

Saturday February 14, 2009
06:05 PM

I <3 Github's fork queue

Since it's Valentines Day, I'll squee about something I love. Github. Oh I loves it so hard. Hopefully this love will stand the test of time.

For years now I've wanted to replace mailing patches around with a version control centric system. I drop patches on the floor all the time, they get lost in my inbox or in the ticket queue. And that sucks, because people who actually write code are really valuable.

To fix this, I want a system that let anyone make their own branches, which they own, for each task they want to do. They could do easy updates from trunk, and when they're ready, they'd ping the project integrator to evaluate the change and pull it in.

SVK came close to this potential, with it's easy pushing and pulling and SVN-like syntax, but I never came around to making the associated tools and web site. Now, github has implemented this. This is why I've moved Test::More to github and why more will be following.

Instead of branching, you fork the whole project. Git makes this cheap. You have total control over that fork, which is good because I'm a terrible bottleneck. When your ready, you issue a "pull request" which pokes me to look at your changes and integrate them. Even better, there is a fork queue where you can see what changes are pending. Then a few clickies on web forms and the patches are integrated. It's a whole lot easier than the normal git pull process (see step 5). Github's integration process could provide a little more information about the merge, but I'm sure that will come.

Today I integrated a patch before it was even submitted!

Heart.

Tuesday December 30, 2008
07:41 PM

alias rm "rm -i" (slips vs mistakes)

This came up on hates-software recently. I have a special hate in my heart for this one. It's one of those special "helpful enhancements" which is both inconvenient and fails to do its job.

$ rm *
rm: remove regular file `foo.txt'? y
rm: remove regular file `bar.txt'? y
rm: remove regular file `this.html'? YES
rm: remove regular file `junk.html'? YES!
rm: remove regular file `temporary.tmp'? YES GOD DAMNIT
rm: remove regular file `important.txt'? YES
...
Wait, NOOOOOOOO!!!!!!!

This is the "are you sure?" anti-pattern, where the computer second guesses every potentially irreversible command issued by the user. The Microsoft approach. It results in slow interactions and a frustrated user trained to reflexively hit "yes" before comprehending the warning. By the time they do, it's too late.

This design ignores that there's a differences between a mistake and a slip. A mistake is when the user really doesn't know what they're doing. A slip is when they do know what they're doing, but have a temporary lapse. Many programmers assume users are idiots, that everything is a mistake, and don't account for slips.

Here's what the dialog would look like with the buttons taking slips into account. (I can't take credit for this one, I saw it at YAPC St. Louis)

  ------------------------------------------------------
               Remove file "foo.txt"?
 
  [Yes] [No] [No, but I meant Yes] [Yes, but I meant No]
  ------------------------------------------------------

You can't really make slips go away, everyone slips up. All you can do is reduce their chance of occurring (which is another show) and most importantly, lessen their impact. One simple way to do that is by turning an irreversible action into a reversible one. That is, provide an undo button. Or, in terms of deleting files, a trash.

$ cat ~/bin/trash
#!/bin/sh
 
mv --backup=numbered "$@" ~/.Trash/

IF you're going to monkey with rm to try and protect the user, make it move files to the trash. It doesn't break the outward interface (making it honor rm's flags is left as an exercise for the reader), and it actually does its intended job instead of just being broken, useless and annoying. Coupled with an automatic trash reaper (a cron job to delete the oldest files when the trash hits a certain size), and with hard drives sizes being what they are, most desktop users will never notice.

Slips happen. Cushion the blow.

Monday November 10, 2008
01:23 AM

I hate C

I'm in C portability hell with Time::y2038. The code to test the usable limits of the system time functions is touching off all sorts of fascinating bugs in various operating systems, many of which cause the test program to hang.

Oh, and then there's reports that "old" (read 2002) versions of Visual C++ don't speak C99 but some Microsoft extension gibberish. And, of course, Vista problems.

C: Fast, elegant, portable. Pick any one.

Also I need to add in code to test the limits of the system's mktime() and I haven't devised a sensible way to do that. I suppose I have to do a binary search over the year, then month, day, hour, minute and second. Oi, not fun in C. Maybe I'll just do it by year, that's really all the granularity I need.

Tuesday October 28, 2008
03:33 AM

Method::Signatures, now with 581% more debugger!

I'm happy to announce that with the latest release of Method::Signatures the last major bugs have been fixed. This is through no fault of my own, but Florian and Rhesa's latest work on Devel::Declare.

Method::Signatures now works with the debugger. This was the big one keeping it from any hope of being taken seriously for production use. Apparently Florian found some "this should never have worked" code in Devel::Declare that was causing the problem.

The debugger sees what's in the source file, not the generated code, which is good. But it has to step through each of the hidden statements, which is not so good. Suggestions on what to do about that welcome.

The other issue is now method declarations happen at compile-time, just like regular subroutines. This is thanks to a fresh Devel::BeginLift patch. While not a big deal for production, it was one of those "I have no idea how to fix this" problems.

All the really terrifying guts which were formally cut & pasted & hacked from a Devel::Declare test file have now been packed back into Devel::Declare and maintained by Rhesa. This lets all the fledgling signature modules share their fixes and lets others experiment.

[If it hasn't hit CPAN yet, get it from github]

There's one major technical issue left. Data::Alias, which provides the fast guts for "is alias" and "\@foo", doesn't work on Windows before 5.10. Unless that's fixed Method::Signatures is going to be a troublesome dependency for Windows users. One option is if Data::Alias doesn't work to fall back to Data::Bind, which does work but its a lot slower. I already do that with Readonly::XS, its an optional dependency because it has a silly test bug in 5.10.

So, let me know if anyone is crazy enough to be using this in production. And please keep sending test patches (or just fork it on github and send me a pull request), especially for signatures that shouldn't work but don't throw a sensible error.

PS If Method::Signatures interests you, but you want something a little simpler and with less dependencies, try Method::Signatures::Simple. Same backend as Method::Signatures, but only Devel::Declare as a dependency.

Tuesday October 21, 2008
04:03 AM

New Method::Signatures, now with crazy defaults

The latest release of Method::Signatures contains a big pile of bug fixes and a break through.

The bug fixes mostly relate to the handling of finding the end of the signature. Various edge cases previously broke things like...

# Closing paren on its own line
method foo(
    $arg
)
{
    ...
}
 
# Closing paren and opening block on the same line
method foo(
    $arg
) {
    ...
}

That's all fixed.

The other awesome part is a break through in how the signature parser works. Previously it was just a pile of regexes doing and simple code like split /\s*,\s*/, $signature which broke on simple defaults like $msg = "Hello, world!". First I figured I could handle it with something like Text::Balanced, but that lead to writing a rudimentary Perl parser (and a pile of Text::Balanced bugs). This violates the "no source filters" principle (and didn't work) so I scrapped that.

Then hdp suggested using PPI. My first reaction was it's not applicable, after all the signature isn't Perl. But PPI is just a tokenizer, and that's what I needed. Something to tokenize my very Perl-like signature syntax. Works like a charm. A very small parser using PPI splits up the signature by walking through the tokens looking for commas while avoiding any child structures. This is very robust and lets you pass in almost anything as a default.

method silly(
    $num    = 42,
    $string = q[Hello, world!],
    $hash   = { this => 42, that => 23 },
    $code   = sub { $num + 4 },
    @nums   = (1,2,3)
)
{
    ...
}

I'm just using PPI for splitting the list of parameters, but it works so well I may replace all the signature parser regexes with PPI.

This clears up the last of the major bugs under my control. The debugger continues to be a show stopper, but I have to wait on Devel::Declare for that.