I have heard programming compared to brain surgery. If that's true, maintenance programmers do brain surgery on American football quarterbacks during the Super Bowl, and it's the programmer's fault if the team loses.
]]>$hashref1->{$item} = \@values;
), that line in ChatGPT's code is buggy, and goes out of its way to be so.
But given the notorious difficulty of transcoding English, not a bad effort.
]]>HARNESS_OPTIONS
thing should work on whatever tool chain you use, because it acts directly on TAP::Harness.]]>
The thing is, the purpose of testing is two-fold: to demonstrate success and to find failures. But in my experience failures are not often solitary -- more commonly they occur in cascade. When a cascade occurs, it is very handy to the maintainer to have the problem that initiated the cascade come out first, rather than buried in a welter of subsidiary failures. Unfortunately I see no way to make this happen without testing the more fundamental code first, and I see no way to test the more fundamental code first without specifying the test order.
The activity that triggered this post was my attempt to understand Chinese New Year by re-implementing the algorithms from Calendrical Calculations in Perl. It seemed logical to have a module per chapter. If something from the Gregorian chapter/module fails, not much else has a chance to succeed. Diagnosing a cascade involves running down (or up) the internal river. Why not test in river order, at least while under active development?
]]>test
actions of both ExtUtils::MakeMaker
and Module::Build
test t/*.t
in lexicographic order (a.k.a. ASCIIbetical order). Under this default, some Perl module authors who want tests performed in a given order have resorted to numbering tests: t/01_basic.t
, t/10_functional.t
, and so on.
My personal preference is to take the lexicographic ordering into consideration when naming test files: t/basic.t
through t/whole_thing.t
. But the price of this choice is a certain number of contrived test names, and even the occasional thesaurus lookup.
But there is a better way. Both ExtUtils::MakeMaker
and Module::Build
allow you to specify tests explicitly.
Under ExtUtils::MakeMaker
version 6.76 or above, you call WriteMakeFile()
thus:
WriteMakeFile( ... test => { TESTS => 't/one.t t/two.t t/three.t t/four.t', }, ... );
If you do this, the tests specified (and only the tests specified) are performed in the order specified.
ExtUtils::MakeMaker
version 6.76 was released September 5 2013 and shipped with Perl 5.19.4, so any reasonably modern Perl should support this.
The equivalent incantation under Module::Build
version 0.23 or above is:
Module::Build->new( ... test_files => [ qw{ t/one.t t/two.t t/three.t t/four.t } ], ... )->create_build_script();
Module::Build
version 0.23 was released February 9 2004.
I understand yield
to call for something like an iterator. You can do this in Perl now, but I would be curious to know details.
The most thought-provoking (to me at least) was dataclass
, which I understand to be a restricted hash on steroids.
I have no idea why I was oblivious to these, but it made me want to audit myself to see if any other distributions had the same problem. GitHub has these nice links at the top of the page, Pull requests and Issues, but these show pull requests and issues that I initiated. I found no obvious way to display pull requests or issues filed against my repositories.
Now, maybe it is just me, but I find GitHub's documentation moderately opaque. But with considerable help from Duck Duck Go, I discovered the answer: you type into the search box is:open user:<your GitHub user name>
. This gets you both open issues and open pull requests. If you want, you can restrict this further with is:issue
(for issues) or is:pr
(for pull requests). Do not leave off the user name, even if you are logged in. If you forget this you will get every open item on GitHub -- all 102 million of them as of this writing.
Now I am lazy, so I made a browser shortcut to do this for me. I don't think you get private repositories this way, but I was not worried about that. The string will have to be URI-escaped. So now if I want to audit myself I just click on https://github.com/search?q=user%3Atrwyant+is%3Aopen and see what I get.
]]>for
loop, and discovered there were circumstances where I wanted to skip an iteration. Well, the skip()
provided by Test2::Tools::Basic operates by executing last SKIP;
. In the case of a labeled for
this skips not only the current iteration but all subsequent iterations.
I wondered if there was a Test2::Tools
plugin that did a next SKIP;
, so I generated an annotated index of Test2 tools. This index reports all of them in ASCIIbetical order, with the distribution they are found in and the abstract from the =head1 NAME
section of the POD.
I found 44 tools after eliminating a few helper classes that lived in the same name space. None of the 44 appears to do what I want. It would be easy enough to create such a tool, but I doubted that anyone would use it but me. So I indented another level and stuck a SKIP:
block inside the for
loop.
Like the previous Annotated Perl::Critic Policy Index this will be updated approximately weekly. That is, a cron job runs Friday morning, and I push the repository when I get around to it, after reviewing the change and coming up with (I hope) a descriptive commit message.
]]>My::Module
conforming to the parts of the PerlIO::via
interface you need, and provide it to the second argument of open()
or binmode()
as ':via(My::Module)'
. How cool is that? And how cool is a language that lets you do that with a minimum of fuss, bother, and code?
I encountered this when trying to modify (OK, hack) the behavior of a large and complex hunk of Perl not under my control. Rummaging around in this turned up the fact that all file input went through a single module/object, which had an open()
method. I realized if I could insert my own PerlIO layer into the input stream, I would have control over what the victim host code saw.
In the true spirit of the Conan the Barbarian school of programming ("Bash it until it submits!") I wrote a PerlIO::via
module whose import()
method monkey-patched the open()
to insert my layer into the stack. All I had to do was launch the host code with -MMy::Module
and the dirty deed was done.
If you read the PerlIO::via documentation you see a whole host of methods you can provide. All I wanted to do was modify the input stream, and that can be done by implementing just two or three:
You will have to provide PUSHED()
, which is called when your layer is pushed onto the I/O stack. That is, when someone specifies it in the second argument of open()
or binmode()
. This is called as a static method, and given a fopen()
-style mode string (i.e. 'r'
, 'w'
, or what have you) and the already-opened handle, which represents the layer below. This method needs to instantiate and return an object of the given class. Depending on your needs, this can be as simple as
sub PUSHED { my ( $class ) = @_; return bless {}, $class; }
You have a couple options for how to get the input, but I opted for FILL()
. This is called as a method, and passed a file handle which is open to the next layer down in the PerlIO stack. This would look something like:
sub FILL { my ( $self, $fh ) = @_; defined( my $data = <$fh> ) or return;# Do your worst to the $data
return $data;
}
A few paragraphs back I said "two or three" methods. For a while I was content with the above two. But then I realized that the caller was getting back bytes even if the file was opened with :encoding(...)
specified in a lower layer, and the FILL()
method preserved the character-nature of the data. Wrestling with this finally drove me back to the documentation, where I found the UTF8()
method.
The UTF8()
method is optional, and is called (if it exists) right after PUSHED()
. It receives one argument, which is interpreted as a Boolean, and is true if the next-lower layer provides characters rather than bytes. The returned value tells PerlIO
whether your layer provides characters (if true) or bytes (if false). A minimal-but-sufficient implementation is
sub UTF8 { my ( undef, $below_flag ) = @_; return $below_flag; }
Caveat: If you apply the encoding and your layer in the same operation (e.g. binmode $fh, ':encoding(utf-8):via(My::Module)';
, the UTF8()
method will not see a true value of $below_flag
. There are two ways of dealing with this:
PerlIO::via
layer in a separate call to binmode()
, or:utf8
after your layer (that is, binmode $fh, ':encoding(utf-8):via(My::Module):utf8';
).This is already a longer note than I like, but I have to say something about :utf8
. The current documentation calls it a pseudo-layer. What it really is is a bit on the layer below, telling PerlIO
that the layer it applies to provides characters rather than bytes on input, or accepts characters on output. Around Perl 5.8 or 5.10 there was a fair amount of misunderstanding about what :utf8
did, and there was actually core Perl documentation that said (or seemed to say) that you did UTF-8 I/O by specifying this layer. Most such instances of :utf8
in the core documentation have been replaced by :encoding(utf-8)
but there may still be some :utf8
in outlying regions of the documentation.
By using :utf8
in the second example above, what I am telling Perl is that :via(My::Module)
produces decoded output. It does, because the layer below it (:encode(utf-8)
) does, and :via(My::Module)
preserves this property. Without the :encode(utf-8)
below it it would be an error to tell PerlIO that :via(My::Module)
produced characters unless My::Module
did the decoding itself.
If you want to see what layers are in effect on file handle $fh
, you can call PerlIO::get_layers( $fh )
. This returns a list, which will include :utf8
as a separate entry, maybe more than once if more than one layer has that bit set.
Previous entries in this series:
]]>/\w/
can be used provided there is white space between the operator and the delimiter: m X foo Xsmx
compiles and matches 'foobar'
. In the presence of use utf8;
you can go wild.
A query on the Perl 5 Porters Mailing List (a.k.a. 'p5p') a few days ago asked for opinions about appropriating the colon (':'
) as a delimiter for modifiers to the regular expression operators. This got me wondering about what regular expression delimiters were actually in use.
I scratched that itch by plowing through my local Mini CPAN, running everything that looked like Perl through PPI, and checking anything that parsed to an object of one of the relevant classes. A summary of the results is appended.
It was no surprise that "/"
was the overwhelming favorite. The colon (":"
) came in 13th. I was a little surprised (after I thought about it) not to see "'"
(7th) more popular, since it does not interpolate. After all, why write m/[\@\$]/
when you can write m'[@$]'
?
You made it to the end of this post. Your prize (if you want to call it that) is the threatened list of regular expression delimiters, in decreasing order of frequency. The delimiters themselves were formatted by running them through B::perlstring()
. I suspect most of the single-digit ones are the result of mis-parses, but believe it or not, some of the instances of "\\"
are real regular expression delimiters.
"/" 1420735 "{" 128788 "!" 36081 "|" 23932 "#" 14893 "(" 7369 "'" 5180 "[" 4220 "," 3376 "<" 2926 "%" 2308 "\@" 1302 ":" 1232 "\"" 828 "." 349 "~" 313 "-" 249 ";" 194 "?" 182 "=" 109 "^" 59 "0" 43 "`" 35 "+" 29 ")" 18 "&" 17 "o" 15 "n" 14 "]" 14 "r" 13 "*" 11 "\\" 11 "\036" 8 "i" 6 "\$" 6 "\a" 6 "" 5 "e" 4 ">" 4 "1" 4 "8" 3 "S" 3 "6" 3 "9" 3 "_" 2 "f" 2 "a" 2 "}" 2 "g" 2 "m" 2 "5" 2 "v" 1 "q" 1 "l" 1 "I" 1 "d" 1 "M" 1 "c" 1 "s" 1 "t" 1 "H" 1 "\247" 1 "u" 1 "x" 1]]>
Note that I had no luck using the "Edit Profile" page accessible from the initial log-in. I hit "Post", (which required a second login with the same password), and then had no trouble.
]]>One of my personal desires was to test my distributions on the oldest practicable Perl for each available architecture. For Unix (i.e. Linux and macOS) this is 5.8.8, provided the distribution itself supports that. A couple days ago, though, I pushed a modification to one of my distributions and had the 5.8.8 tests blow up.
The problem turned out to be that Module::Build, for reasons I have not investigated, has Pod::Man as a dependency. The current version of Module::Build
requires Pod::Man
version 2.17, but according to corelist Perl 5.8.8 comes with Pod::Man
version 1.37, so cpanm
wants to upgrade it.
The problem with this is that as of version 5.0 released November 25 2022, the podlators distribution, which supplies Pod::Man
, requires Perl 5.10. So under 5.8.8, cpanm --with-configure --notest --installdeps .
dies trying to install podlators
.
The solution I came up with was to pre-emptively install RRA/podlators-4.14.tar.gz
under Perl 5.8.8. The implementation was in two parts: define an environment variable that recorded whether we were running under Perl 5.10, and define a job step conditioned on that variable to install podlators 4.14
if we were using an earlier Perl.
Under GitHub Actions you can define environment variables by appending their definitions to the file whose path is in environment variable GITHUB_ENV
. After struggling with PowerShell for the Windows runners, I decided to do that step in Perl. The core of the Perl script is:
defined $ENV{GITHUB_ENV} and $ENV{GITHUB_ENV} ne '' or die "Environment variable GITHUB_ENV undefined or empty\n"; open my $fh, '>>:encoding(utf-8)', $ENV{GITHUB_ENV} or die "Can not open $ENV{GITHUB_ENV}: $!\n";my $home = File::HomeDir->my_home();
my $is_5_10 = "$]" >= 5.010 ? 1 : '';
my $is_windows = {
MSWin32 => 1,
dos => 1,
}->{$^O} || '';
my $is_unix = $is_windows ? '' : 1;print $fh <<"EOD";
MY_HOME=$home
MY_IS_UNIX=$is_unix
MY_IS_WINDOWS=$is_windows
MY_PERL_IS_5_10=$is_5_10
EOD
Next I had to run this from the YAML file that defined the workflow, and act on the created value. This was done using two steps:
- name: Customize environment run: | cpanm -v cpanm File::HomeDir perl .github/workflows/environment.PL
and
- name: Install old podlators distro if on old Perl if: "! env.MY_PERL_IS_5_10" run: cpanm RRA/podlators-4.14.tar.gz
The entirety of both the GitHub Actions file ci.yml
and the Perl script environment.PL
can be found in the GitHub repository for Astro::Coord::ECI. Other, and probably better, implementations can be imagined.