1-test_emacs_regexps.t

#!/usr/bin/perl
# 1-test_emacs_regexps.t                   doom@kzsu.stanford.edu
#                                          01 Oct 2007

use warnings;
use strict;
$|=1;
our $VERSION = 0.01;

use Data::Dumper;
use Test::More qw(no_plan);

use File::Path     qw(mkpath);
use File::Basename qw(fileparse basename dirname);
# use Fatal          qw( open close mkpath copy move );

use Env qw(HOME);
use FindBin qw( $Bin );
use lib "$Bin/../../lib/perl";

use Run::Emacs qw( emacs_version );

use Getopt::Long;
my %opt = ();
GetOptions( \%opt, "debug|d" );
my $DEBUG = $opt{debug} || 0;

my $prog = basename($0);

my $tempdir = "$Bin/tmp";
mkpath( $tempdir );

# elisp pattern (used by perlnow.el) to pick out the name of
# a perl module from the package line in a *.pm file
# goal: test that this pattern works on various cases
my $elisp_pattern = "^[ \t]*package[ \t\n]+\\(.*?\\)[ \t\n;]";

# test cases (strings we'll match against, paired with expected result);
my @cases = ( [] ); # initialize with a dummy to occupy position 0
@cases = (
          [  'package My::Perl::Module;',
             'My::Perl::Module',
             "Simple",
          ],
          [  'packageNot::That::I::Know::Of;',
             'nil',
             "Expected failure: no space after 'package'",
          ],
          [  'package My::Perl::Module; {',
             'My::Perl::Module',
             "Open bracket same line as semi-colon",
          ],
          [
            'package My::Perl::Module; blah',
            'My::Perl::Module',
             "a string after semi-colon",
          ],
          [
            'package My::Perl::Module::Again ;',
            'My::Perl::Module::Again',
             "Space before semi-colon",
          ],

          [
            "package\nNo::Such::Animal\n;",
            'No::Such::Animal',
             "linebreak seperators",
          ],
         );

# write these test cases to temp files we can try our regexp on
for my $i (1 .. $#cases) {
  my $case = $cases[ $i ];
  my $case_text = $case->[0];
  my $case_file = define_case_file( $i );
  open my $fh, ">", $case_file or die "$!";
  print {$fh} "$case_text\n\n";
  close $fh;
  $i++;
}

my $func_name = "doom-test-elisp-regexp";

$elisp_pattern =~ s{\\}{\\\\}xg;

for my $i (1 .. $#cases) {
  # log file for emacs errors
  my $log = "/home/doom/tmp/emacs_batch-$i.log";

  # Use this temp file to pass data from elisp to perl
  my $tempfile     = "$tempdir/result-$prog-$i.txt";
  if (-e $tempfile) {
    unlink( $tempfile ) or warn "could not delete $tempfile: $!";
  }

  my $case_file = define_case_file( $i );

  my $elisp = define_elisp( $case_file,
                            $tempfile,
                            $elisp_pattern,
                            $func_name );

  ($DEBUG) && print STDERR "Going to run elisp: \n", $elisp, "\n";

  my $emacs_cmd;
  if (emacs_version() < 22) {
    $emacs_cmd =
      "emacs -q --batch  -eval '$elisp' -f $func_name >& $log";
  } else {
    $emacs_cmd =
      "emacs -q --batch --no-splash -eval '$elisp' -f $func_name >& $log";
  }

  system($emacs_cmd);

  # Get the package name
  my $package_name = '';
  if ( open my $fh, "<", $tempfile ) {

    while (my $line = <$fh>) {
      if( $line =~ m{ (\d) : \s* (.*?) $ }msx ) {
        $package_name = $2;
      }
    }

  } else { # distinguish between a failed match and some other problem.
    $package_name = undef;

    open my $log_fh, "<", $log;
    LINE:
    while ( my $line = <$log_fh> ) {
      if( $line =~ m{ ^ \s* Search \s+ failed: \s+  }x ) {
        $package_name = 'nil';  # TODO would an empty string be better?
        last LINE;
      }
    }
  }

  # is $package_name expected value?
  my $expected = $cases[ $i ][ 1 ];
  my $label    = $cases[ $i ][ 2 ];
  is( $package_name, $expected, "Testing case number $i: $label");
}

### end main, into the subs

sub define_case_file {
  my $index = shift;
  my $case_file = "$tempdir/test-$index.pm";
  return $case_file;
}

### the heredoc ghetto

sub define_elisp {
  my $case_file     = shift;
  my $tempfile      = shift;
  my $elisp_pattern = shift;
  my $func_name     = shift;

my $elisp =<<"END_ELISP";
 (defun $func_name ()
  (find-file "$case_file")
  (goto-char (point-min))
  (re-search-forward "$elisp_pattern")
  (setq first_capture (match-string 1))
  (find-file "$tempfile")
  (insert "1: " first_capture)
  (save-buffer))
END_ELISP
  return $elisp
}

# ($func_name)

__END__

=head1 NAME

1-test_emacs_regexps.t - use perl to test emacs via batch mode

=head1 SYNOPSIS

  perl 1-test_emacs_regexps.t

=head1 DESCRIPTION

B<1-test_emacs_regexps.t> is a script which demonstrates the
technique of testing the behavior of elisp from perl, by shelling
out to an emacs run in batch mode.


=head2 running emacs non-interactively

This is conceptually simple, but not as straight-forward as you
might think.

The key feature is "--batch" which allows us to run emacs in
a non-interactive mode.

The two other important features we use to tell emacs
what code to run:

   `-f FUNCTION'
   `--funcall=FUNCTION'
        Call Lisp function FUNCTION.

   `--eval=EXPRESSION'
   `--execute=EXPRESSION'
      Evaluate Lisp expression EXPRESSION.


One minor annoyance is that emacs 22 has introduced a "splash"
screen unless you're told it not to:

  --no-splash

But using this is an error in earlier versions of emacs.
So we need to check what we have, using a seperate invocation
of emacs with the "--version" option (this code is sequestered
away in the emacs_version routine inside the Run::Emacs module).


Another detail: it's often useful to run emacs in it's default
configuration, supressing the usual init files (.emacs, etc):

   --no-init-file  aka -q
   --no-site-file


In non-interactive mode, emacs can, of course, modify files
which is the form of output I've used for this test script.
It can also send output to STDERR: the "message" command
is re-directed to there when running in --batch mode.


=head2 elisp

The generated elisp looks like this:

 (defun doom-test-elisp-regexp ()
  (find-file "/home/doom/End/Cave/GuiTest/Wall/code/tmp/test-0.pm")
  (goto-char (point-min))
  (re-search-forward "^[ 	]*package[
]+\\(.*?\\)[
;]")
  (setq first_capture (match-string 1))
  (find-file "/home/doom/End/Cave/GuiTest/Wall/code/tmp/result-1-test_emacs_regexps.t-0.txt")
  (insert "1: " first_capture)
  (save-buffer))

 (doom-test-elisp-regexp)

(There's probably other ways to do this... does the message function
write to STDOUT in non-interactive mode?  Try that... TODO)

( it's supposed to go to STDERR )

=head1 AUTHOR

Joseph Brenner, Edoom@kzsu.stanford.eduE

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2007 by Joseph Brenner

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.2 or,
at your option, any later version of Perl 5 you may have available.

=cut

Joseph Brenner, 27 Nov 2007