Older blog entries for chromatic (starting at number 223)

F/OSS Testing:

zhaoway claims that the open source way to test is to make your users do it.

If I thought I could find users who ran tests completely, perfectly, and quickly, report errors accurately, never felt bored, never skipped steps, and who could perform tests in real time as I develop, I'd consider the idea.

In other words, it sounds like yet another excuse for software quality by wishful thinking. Sorry.

Dark, Dark Corners of Perl:

After tonight's PDX.pm meeting, in the bar, I bet Ovid that I could reimplement his Sub::Signatures by replacing the source filter with subroutine attributes. (Wisely, he believed me.)

A few minutes later, he asked idly if I knew of any reason why Perl couldn't call methods with spaces in their names. Offhand, I couldn't think of anything besides the tokenizer that prevents it, believing that gv.c cares more about null-byte termination than the <code>\w+</code>ness of identifiers. If you can bypass that for symbol installation and method invocation (and that's trivial), you've done it.

I hate to give away the punchline before both frightening people who didn't know such things were possible and stumping a few of the people who know it's possible but don't see the answer right away, so here are my tests. They all pass on my machine and I expect them to run just fine on at least Perl 5.8.0 and newer. (Perl 5.6 probably works, but I expect it to fail on more complicated things.) You don't need any non-core modules besides Attribute::Handlers, and I believe that's in the core in the 5.8.x family.

#!/usr/bin/perl -w

BEGIN { chdir 't' if -d 't'; use lib '../lib', '../blib/lib'; }

use strict; use Test::More tests => 4;

my $module = 'Attribute::Scary'; use_ok( $module ) or exit;

package Hello;

use strict; use warnings;

use Attribute::Scary;

sub new { my ($class, $name) = @_; bless \$name, $class; }

sub name :Method { return $$self; }

sub greet :Method { return sprintf( "Hello, %s!\n", $self->name() ); }

package main;

my $hi = Hello->new( 'Bob' ); is( $hi->greet(), "Hello, Bob!\n", ':Method attribute should autoadd invocant shift' );

my $spacey_name = 'spacey 0'; my $spacey_greet = 'spacey 1';

is( $hi->$spacey_name(), 'Bob', '... also installing first method as "spacey 0"' ); is( $hi->$spacey_greet(), "Hello, Bob!\n", '... and second as "spacey 1"' );

There are two tricks in the implementation, one of which is an "eww, evil--and CLEVER!" trick and the other is something either you know about or you don't.

Quieter Test Output:

I never really understood the point of <code>prove</code>; I write my tests as normal Perl programs, launchable from the command line. <code>perl t/testname.t</code> did most of what I wanted to do.

With more than 24 tests in a file, though, I often promised myself to write or find someday a variant test harness that ignored successes and reported only failures or an all clear message.

I looked at the code of <code>prove</code> today to find where to add it, but again, it doesn't seem to do anything I really need and there was no way to add it. I moved on to Test::Harness::Straps. I don't know if anyone's ever actually used this module (and it's admittedly a bit of a mess inside, though the long-promised Test::Builder refactoring may make it easier to write), but it took 67 lines of code to do what I needed (and a little more) in a well-factored way.

I call it qtest:

#!/usr/bin/perl

use strict; use warnings;

use Test::Harness::Straps;

my $strap = Test::Harness::Straps->new();

for my $file (@ARGV) { next unless -f $file;

my %results = $strap->analyze_file( $file );

if ($results{passing}) { report( sprintf('All (%d) tests passed in %s', $results{seen}, $file)); } elsif ($results{skip_all}) { report( sprintf('All (%d) tests skipped in %s', $results{seen}, $file)); } else { report( find_failures( $file, \%results ) ); } }

sub report { my $message = shift; print "$message\n"; }

sub find_failures { my ($file, $results) = @_; my $report = create_header($file, @{$results}{qw( max seen ok )}); my $count = 0;

for my $test ( @{ $results->{details} } ) { $count++; next if $test->{ok}; $report .= create_test_result( $count, @{ $test }{qw( name reason ) } ); }

return $report; }

sub create_header { my ($file, $expected, $seen, $passed) = @_; my $failed = $seen - $passed; return sprintf "File '%s'\nExpected %d / Seen %d / Okay %d / Failed %d\n", @_, $failed; }

sub create_test_result { my ($number, $name, $reason) = @_; $name =~ s/^-\s*//; $reason ||= ''; $reason = " ($reason)" if $reason; return sprintf "\tTest #%d: %s%s\n", $number, $name, $reason; }

In practice, I may tweak the formatting somewhat. It might be nice to report skips and surprisingly-passing TODO tests, too. I also might want a flag to end after the first failure, to solve the problem of cascading failures rolling off of the screen. Still, this was a lot simpler than I thought and already makes my life easier.

Excuses Not To Hack:

Having started Write Your Life the first week of October, I spent a month writing my own versions of the assignments. (No, you probably won't ever see them.) That was enlightening and habit-forming.

Unfortunately, the weird not-on-a-month-boundary timing caused me to miss the start of National Novel Writing Month by at least a week. That's too bad; if I'd known and could have planned for it, I would have participated. 50,000 words is intimidating, but a thousand a day is doable. (It would have been wrong to set aside my first ever One Month Project because I know I'd probably never go back to it.)

The discipline of writing at a set time every day for a month was valuable though. Instead of writing about myself (it drove me crazy to see personal pronouns in so many sentences), I've written over 7000 words of what may just become a novel in the past week.

You might see that online in the near future.

In the meantime, feeling guilty about not hacking (though not guilty enough to figure out why the JIT fixes for Parrot on AIX have broken it on Linux PPC), I've released Test::MockObject 0.15. This version allows you to prevent logging certain method calls; that'll make many lives easier.

Too Many Choices:

If "too many choices" of applications in the typical Linux distribution is really keeping people on Windows, why aren't there more people starving to death in the cereal aisles of grocery stores?

A Strange Mix of Writer and Programmer:

Though most people know me for my programming, I also consider myself a writer. My day job combines both fields very nicely, but it doesn't leave a lot of room for non-technical writing.

The other night, I had a conversation spanning several subjects, including the deaths of my grandparents, the fear that I hadn't heard their full life stories, and my desire to make the world a better place, in part by helping non-programmers learn just enough programming to solve smaller problems. I felt guilty explaining the latter idea, as it seems self-indulgent in some ways and trivial in others.

As often happens, several divergent ideas ganged up on me and presented a different solution. I do want to make the world a better place, but maybe programming isn't the right place at the moment. Instead, I'm looking for a wider audience.

I've spent the past week putting the foundations of Write Your Life in place. The idea is to encourage people who want to improve their writing to practice writing every day for a month by providing one topic per day.

It uses an autobiographical theme for two reasons. First, it's important to write what you know. Though it's still difficult to start writing a thousand words about your home, once you cross that barrier, it can be more difficult to stop. (I blew past the word limit and didn't even start on the upper story of my house.)

Second, I believe everyone has a story to tell. I really regret not learning more from my grandparents while I had the chance, though my friend did point out that they may have told me as much as they felt comfortable sharing. There's a wealth of wisdom and experience in the previous generations that we may lose unless we encourage people to write and speak.

I hope my project will convince a few people to publish their own lives. Please feel free to pass the link to your friends.

(I'll try to add an RSS or Atom feed for the assignments today.)

On "Scripting" Languages:

The nice thing about saying "nobody has ever written a reliable, high-performance program" in a so-called scripting language is the person saying it controls the debate. If you point one out, he can claim that such and such a program isn't reliable enough or doesn't exhibit high enough performance.

Remember folks, it's not the experience, skill, dedication, talent, or luck of your developers, it's whether you choose a language that forces a dedicated compilation stage... at least if you're poisoning the well.

The Command-Line is My File Chooser:

Like brian d foy, I have a lot of little command-line tools to make my life easier. Today, I wrote two more.

One of the parts of Mac OS X I actually miss (and there are a few) is its open command that examines a named file and attempts to open it in an appropriate application. That's an easy program to write, if you aim for 80% effectiveness, but it's so convenient that when I found myself wishing for it today, I spent five minutes writing it.

#!/usr/bin/perl

use strict; use warnings;

use File::MMagic;

my $file = shift or die "Usage: $0 <filename>\n";

my %subtypes = map { $_ => 1 } 'application/x-zip', 'text/plain', 'text/html'; my %apps = ( 'application/star-office' => 'xooffice', 'application/msword' => 'abiword-2.0', 'application/mozilla' => 'moz_tab', 'image/jpeg' => 'eog', 'image/gif' => 'eog', 'text/html' => 'moz_tab', ); my %exts = ( 'application/star-office', => qr/\.sxw\Z/, 'application/mozilla', => qr/\.html?\Z/i, );

my $mm = File::MMagic->new();

while (my ($subtype, $regex) = each %exts) { $mm->addFileExts( $regex, $subtype ); }

my $type = $mm->checktype_filename( $file ); $type = $mm->checktype_byfilename( $file ) if exists $subtypes{ $type };

die "Unknown type '$type'\n" unless exists $apps{ $type };

fork and exit; exec $apps{$type}, $file, @ARGV;

It's worth factoring out the file types, applications, and subtypes into data somewhere, but when I find myself needing to maintain a bigger list, I'll do that.

You might notice a program called moz_tab. What does that do? I usually have Mozilla running with several tabs on a different virtual desktop. (Now you begin to see what I missed from a real window manager when I used Mac OS X!) I do want to open HTML files in Mozilla, but I don't want to open them in a new browser window or, worse, with a different profile. moz_tab checks to see if there's an existing window and opens a new tab or a window as appropriate.

#!/bin/sh

# create an absolute path DIR=`pwd` FILE="$DIR/$1"

# check if Mozilla is already running /usr/bin/mozilla -remote 'ping()' STATUS=$?

# launch a new tab, if so if [ "$STATUS" == 0 ]; then exec /usr/bin/mozilla -remote "openurl(file://$FILE,new-tab)" fi

# or launch a new window exec /usr/bin/mozilla "file://$FILE"

It doesn't check for an absolute path before absolutifying the path, but if I need that, I'll add another line.

Having both of these programs available has saved me almost a minute today. That doesn't seem like much, but keep in mind that I'd have spent that minute navigating developer-hostile file chooser windows. If I can avoid that by using the developer-friendly command line in a ubiquitous terminal window, my life is much more pleasant.

P5NCI:

The name isn't great, the available thunks are too few, the known types are too simple, and it has a tendency to rebuild the C files every time you call ./Build, but P5NCI version 0.01 works and you're more than welcome to try it and send me patches.

Alternately, tell me how cool P5NCI::Library actually is in practice.

Thanks to jplindstrom for pointing me at Win32::API which already handles structs; I think I can modify that code to work on other platforms. What's a little sizeof between friends?

Throw Away Your C Compiler (after building Perl):

#!/usr/bin/perl

# P5NCI.t

BEGIN { chdir 't' if -d 't'; use blib; }

use strict; use warnings;

use Test::More tests => 9;

use_ok('P5NCI') or exit;

my $double_lib = P5NCI::load_lib( '../nci_demo.so' ); my $double_double = P5NCI::load_nci_func( $double_lib, 'double_double', 'dd' ); is( $double_double->( 1.0 ), 2.0 ); is( $double_double->( 3.14 ), 6.28 );

my $double_int = P5NCI::load_nci_func( $double_lib, 'double_int', 'ii' ); is( $double_int->( 1 ), 2 ); is( $double_int->( 3 ), 6 );

my $double_float = P5NCI::load_nci_func( $double_lib, 'double_float', 'ff' ); is( $double_float->( 1.0 ), 2.0 ); ok( abs( $double_float->( 0.314 ) - 0.628) < 0.00001 );

my $multiply_ints = P5NCI::load_nci_func( $double_lib, 'multiply_ints', 'iii' ); is( $multiply_ints->( 10, 20 ), 200 ); is( $multiply_ints->( 5, 5 ), 25 );

The interface is a bit grotty, the documentation is spotty, the build system needs some help, and it only handles really simple functions right now, but it works.

214 older entries...

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!