Refactor common test code into class
- Create new class t/TestEnvironment.pm. A TestEnvironment instance owns a temporary Git repo and a temporary Elixir database directory. - Update t/100-basic.t to use TestEnvironment. - Add constant MUST_SUCCEED so `run_produces_ok()` calls are more self-documenting
This commit is contained in:
parent
5f063ebdf9
commit
1db4fb8199
4 changed files with 277 additions and 44 deletions
|
|
@ -28,13 +28,11 @@ use autodie; # note: still need to check system() calls manually
|
|||
use FindBin '$Bin';
|
||||
use lib $Bin;
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use File::Path qw(remove_tree);
|
||||
use File::Spec;
|
||||
use File::Temp 0.14 qw(tempdir);
|
||||
|
||||
use Test::More;
|
||||
|
||||
use TestEnvironment;
|
||||
use TestHelpers;
|
||||
|
||||
# ===========================================================================
|
||||
|
|
@ -44,15 +42,12 @@ use TestHelpers;
|
|||
my $tree_src_dir = sibling_abs_path('tree');
|
||||
my $db_dir = sibling_abs_path('db'); # the db dir is .gitignored
|
||||
|
||||
{ # Remove any existing DB dir
|
||||
my $ignore;
|
||||
remove_tree($db_dir, {error => \$ignore});
|
||||
}
|
||||
my $tenv = TestEnvironment->new;
|
||||
|
||||
# Check programs
|
||||
my $script_sh = find_program('script.sh');
|
||||
my $update_py = find_program('update.py');
|
||||
my $query_py = find_program('query.py');
|
||||
my $script_sh = $tenv->script_sh;
|
||||
my $update_py = $tenv->update_py;
|
||||
my $query_py = $tenv->query_py;
|
||||
|
||||
ok_or_die( (-f $script_sh && -r _ && -x _), 'script.sh executable',
|
||||
"Could not find executable script.sh at $script_sh");
|
||||
|
|
@ -61,26 +56,8 @@ ok_or_die( (-f $update_py && -r _ && -x _), 'update.py executable',
|
|||
ok_or_die( (-f $query_py && -r _ && -x _), 'query.py executable',
|
||||
"Could not find executable query.py at $query_py");
|
||||
|
||||
# Copy tree/ into a temporary Git repository, since script.sh requires
|
||||
# it be run in a Git repo.
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
my $tempdir_path = abs_path($tempdir);
|
||||
diag "Using temporary directory $tempdir_path";
|
||||
run_program('bash', '-c', "cd \"$tempdir\" && git init") or die("git init failed");
|
||||
|
||||
run_program('bash', '-c', "tar cf - -C \"$tree_src_dir\" . | tar xf - -C \"$tempdir\"")
|
||||
or die("Could not copy files into $tempdir");
|
||||
|
||||
my @gitdir = ('-C', $tempdir_path);
|
||||
|
||||
run_program('git', @gitdir, 'add', '.') or die("git add failed");
|
||||
run_program('git', @gitdir, 'commit', '-am', 'Initial commit')
|
||||
or die("git commit failed");
|
||||
run_program('git', @gitdir, 'tag', 'v5.4') or die("git tag failed");
|
||||
|
||||
$ENV{LXR_REPO_DIR} = $tempdir;
|
||||
$ENV{LXR_DATA_DIR} = $db_dir;
|
||||
$tenv->build_repo($tree_src_dir);
|
||||
$tenv->update_env; # Set LXR_REPO_DIR
|
||||
|
||||
# Check for tags in `script.sh list-tags`, as a sanity check before
|
||||
# building the test DB
|
||||
|
|
@ -89,12 +66,8 @@ die("Could not list tags: $! ($?)") if $?;
|
|||
ok_or_die( @tags == 1, 'One tag present', "Not one tag (@{[scalar @tags]})");
|
||||
ok_or_die( $tags[0] =~ /^v5.4$/, 'Found the correct tag', 'Not the tag we expected');
|
||||
|
||||
# Make the new database
|
||||
ok_or_die( mkdir($db_dir), "Created $db_dir",
|
||||
"Could not create $db_dir");
|
||||
|
||||
ok_or_die( run_program($update_py), 'update.py succeeded',
|
||||
'Could not create database');
|
||||
$tenv->build_db($db_dir);
|
||||
$tenv->update_env; # Set LXR_DATA_DIR
|
||||
|
||||
ok_or_die( -d $db_dir, 'database dir exists',
|
||||
"Database dir $db_dir not present");
|
||||
|
|
@ -109,7 +82,7 @@ ok( (-r File::Spec->catfile($db_dir, $_)), "$_ exists" )
|
|||
run_produces_ok('ident query (nonexistent)',
|
||||
[$query_py, qw(v5.4 ident SOME_NONEXISTENT_IDENTIFIER_XYZZY_PLUGH)],
|
||||
[qr{^Symbol Definitions:}, qr{^Symbol References:}, qr{^\s*$}],
|
||||
1);
|
||||
MUST_SUCCEED);
|
||||
|
||||
run_produces_ok('ident query (existent)',
|
||||
[$query_py, qw(v5.4 ident i2c_acpi_notify)],
|
||||
|
|
@ -117,7 +90,7 @@ run_produces_ok('ident query (existent)',
|
|||
qr{drivers/i2c/i2c-core-acpi\.c.+\b402\b.+\bfunction\b}, # def
|
||||
qr{drivers/i2c/i2c-core-acpi\.c.+\b402,439} # refs
|
||||
],
|
||||
1);
|
||||
MUST_SUCCEED);
|
||||
|
||||
# Spot-check some files
|
||||
|
||||
|
|
@ -128,17 +101,17 @@ run_produces_ok('file query (nonexistent)',
|
|||
run_produces_ok('file query (existent), .h',
|
||||
[$query_py, qw(v5.4 file /drivers/i2c/i2c-dev.c)],
|
||||
[qr{\S}],
|
||||
1);
|
||||
MUST_SUCCEED);
|
||||
|
||||
run_produces_ok('file query (existent), .c',
|
||||
[$query_py, qw(v5.4 file /drivers/i2c/i2c-dev.c)],
|
||||
[qr{i2c-dev\.c}, qr{\bVogl\b}],
|
||||
1);
|
||||
MUST_SUCCEED);
|
||||
|
||||
run_produces_ok('file query (existent), .h',
|
||||
[$query_py, qw(v5.4 file /drivers/i2c/i2c-core.h)],
|
||||
[qr{i2c-core\.h}, qr{\bWe\b}],
|
||||
1);
|
||||
MUST_SUCCEED);
|
||||
|
||||
#system('bash'); # Uncomment this if you want to interact with the test repo
|
||||
|
||||
|
|
|
|||
8
t/TestClass.pm
Normal file
8
t/TestClass.pm
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# A minified OOP library - https://metacpan.org/pod/distribution/Mo/ReadMe.pod
|
||||
|
||||
package TestClass;
|
||||
# use Mo qw(build default is required import);
|
||||
# The following line of code was produced from the previous line by
|
||||
# Mo::Inline version 0.40
|
||||
no warnings;my$M=__PACKAGE__.'::';*{$M.Object::new}=sub{my$c=shift;my$s=bless{@_},$c;my%n=%{$c.'::'.':E'};map{$s->{$_}=$n{$_}->()if!exists$s->{$_}}keys%n;$s};*{$M.import}=sub{import warnings;$^H|=1538;my($P,%e,%o)=caller.'::';shift;eval"no Mo::$_",&{$M.$_.::e}($P,\%e,\%o,\@_)for@_;return if$e{M};%e=(extends,sub{eval"no $_[0]()";@{$P.ISA}=$_[0]},has,sub{my$n=shift;my$m=sub{$#_?$_[0]{$n}=$_[1]:$_[0]{$n}};@_=(default,@_)if!($#_%2);$m=$o{$_}->($m,$n,@_)for sort keys%o;*{$P.$n}=$m},%e,);*{$P.$_}=$e{$_}for keys%e;@{$P.ISA}=$M.Object};*{$M.'build::e'}=sub{my($P,$e)=@_;$e->{new}=sub{$c=shift;my$s=&{$M.Object::new}($c,@_);my@B;do{@B=($c.::BUILD,@B)}while($c)=@{$c.::ISA};exists&$_&&&$_($s)for@B;$s}};*{$M.'default::e'}=sub{my($P,$e,$o)=@_;$o->{default}=sub{my($m,$n,%a)=@_;exists$a{default}or return$m;my($d,$r)=$a{default};my$g='HASH'eq($r=ref$d)?sub{+{%$d}}:'ARRAY'eq$r?sub{[@$d]}:'CODE'eq$r?$d:sub{$d};my$i=exists$a{lazy}?$a{lazy}:!${$P.':N'};$i or ${$P.':E'}{$n}=$g and return$m;sub{$#_?$m->(@_):!exists$_[0]{$n}?$_[0]{$n}=$g->(@_):$m->(@_)}}};*{$M.'is::e'}=sub{my($P,$e,$o)=@_;$o->{is}=sub{my($m,$n,%a)=@_;$a{is}or return$m;sub{$#_&&$a{is}eq'ro'&&caller ne'Mo::coerce'?die$n.' is ro':$m->(@_)}}};*{$M.'required::e'}=sub{my($P,$e,$o)=@_;$o->{required}=sub{my($m,$n,%a)=@_;if($a{required}){my$C=*{$P."new"}{CODE}||*{$M.Object::new}{CODE};no warnings 'redefine';*{$P."new"}=sub{my$s=$C->(@_);my%a=@_[1..$#_];die$n." required"if!exists$a{$n};$s}}$m}};my$i=\&import;*{$M.import}=sub{(@_==2 and not$_[1])?pop@_:@_==1?push@_,grep!/import/,@f:();goto&$i};@f=qw[build default is required import];use strict;use warnings;
|
||||
1;
|
||||
238
t/TestEnvironment.pm
Normal file
238
t/TestEnvironment.pm
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
#!/usr/bin/env perl
|
||||
# TestEnvironment.pm: A class representing an Elixir test environment.
|
||||
# See license information at end of file.
|
||||
#
|
||||
# For a cleaner view of the documentation, run
|
||||
# perldoc TestEnvironment.pm
|
||||
# (on Ubuntu, you may need to install the perl-doc package first.)
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# This file uses only core Perl modules, and modules bundled with
|
||||
# the Elixir distribution.
|
||||
|
||||
=head1 NAME
|
||||
|
||||
TestEnvironment - Class representing an Elixir test environment
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use TestEnvironment;
|
||||
my $tenv = TestEnvironment->new;
|
||||
$tenv->build_repo($source_path); # Make a git repo
|
||||
$tenv->build_db($db_path); # Run update.py
|
||||
$tenv->export_env; # Set $LXR_* environment vars
|
||||
# Now run tests against the database in $db_path
|
||||
|
||||
=cut
|
||||
|
||||
package TestEnvironment;
|
||||
|
||||
use TestClass; # Now we are a class
|
||||
use autodie; # note: still need to check system() calls manually
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use File::Path qw(remove_tree);
|
||||
use File::Spec;
|
||||
use File::Temp 0.14 qw(tempdir);
|
||||
use FindBin;
|
||||
use IO::Select;
|
||||
use IPC::Open3;
|
||||
use Symbol;
|
||||
|
||||
use Test::More;
|
||||
|
||||
use TestHelpers;
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=head2 lxr_data_dir
|
||||
|
||||
C<$lxr_data_dir> is the value to use in the C<LXR_DATA_DIR> environment
|
||||
variable.
|
||||
|
||||
=head2 lxr_repo_dir
|
||||
|
||||
C<$lxr_repo_dir> is the value to use in the C<LXR_REPO_DIR> environment
|
||||
variable.
|
||||
|
||||
=head2 script_sh
|
||||
|
||||
The path to C<script.sh>. Assigned by default using
|
||||
C<TestHelpers/find_program> if not specified.
|
||||
|
||||
=head2 query_py
|
||||
|
||||
As L</script_sh>, but for C<query.py>.
|
||||
|
||||
=head2 update_py
|
||||
|
||||
As L</script_sh>, but for C<update.py>.
|
||||
|
||||
=cut
|
||||
|
||||
has lxr_data_dir => ();
|
||||
has lxr_repo_dir => ();
|
||||
has script_sh => (
|
||||
default => sub { find_program('script.sh') }
|
||||
);
|
||||
has query_py => (
|
||||
default => sub { find_program('query.py') }
|
||||
);
|
||||
has update_py => (
|
||||
default => sub { find_program('update.py') }
|
||||
);
|
||||
|
||||
# Internal attributes
|
||||
|
||||
# a variable representing the temporary repository directory.
|
||||
# When this goes out of scope, the directory will be removed.
|
||||
has _repo_dir_token => ();
|
||||
|
||||
# a variable representing the temporary DB directory, if any.
|
||||
# When this goes out of scope, the directory will be removed.
|
||||
has _data_dir_token => ();
|
||||
|
||||
=head1 MEMBER FUNCTIONS
|
||||
|
||||
=head2 build_repo
|
||||
|
||||
Create a Git repo and tag it. Usage:
|
||||
|
||||
$tenv->build_repo($source_tree_dir);
|
||||
|
||||
C<$source_tree_dir> is the directory holding the tree of source files
|
||||
you want to index.
|
||||
|
||||
Dies on error. On success, returns the instance, for chaining.
|
||||
|
||||
=cut
|
||||
|
||||
sub build_repo {
|
||||
my ($self, $tree_src_dir) = @_;
|
||||
die "Need a source dir" unless $tree_src_dir;
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
my $tempdir_path = abs_path($tempdir);
|
||||
my @gitdir = ('-C', $tempdir_path);
|
||||
|
||||
diag "Using temporary directory $tempdir_path";
|
||||
run_program('git', 'init', $tempdir_path) or die("git init failed");
|
||||
|
||||
run_program('bash', '-c', "tar cf - -C \"$tree_src_dir\" . | tar xf - -C \"$tempdir_path\"")
|
||||
or die("Could not copy files into $tempdir_path");
|
||||
|
||||
run_program('git', @gitdir, 'add', '.') or die("git add failed");
|
||||
run_program('git', @gitdir, 'commit', '-am', 'Initial commit')
|
||||
or die("git commit failed");
|
||||
run_program('git', @gitdir, 'tag', 'v5.4') or die("git tag failed");
|
||||
|
||||
# Save the results in the instance
|
||||
$self->_repo_dir_token($tempdir);
|
||||
$self->lxr_repo_dir($tempdir_path);
|
||||
|
||||
return $self;
|
||||
} #build_repo()
|
||||
|
||||
=head2 build_db
|
||||
|
||||
Build a test database for the repository. L</lxr_repo_dir> must be set
|
||||
before calling this. Usage:
|
||||
|
||||
$tenv->build_db([$db_dir])
|
||||
|
||||
C<$db_dir> is the directory where you want to put the database. If you do
|
||||
not provide one, a temporary directory will be created.
|
||||
|
||||
Dies on error. On success, returns the instance, for chaining.
|
||||
|
||||
B<CAUTION>: This function will remove the contents of C<$db_dir>
|
||||
unconditionally.
|
||||
|
||||
=cut
|
||||
|
||||
sub build_db {
|
||||
my ($self, $db_dir) = @_;
|
||||
die "No repo dir" unless $self->lxr_repo_dir;
|
||||
|
||||
if($db_dir) { # Remove any existing DB dir
|
||||
remove_tree($db_dir);
|
||||
mkdir($db_dir) or die "Could not create fresh $db_dir";
|
||||
}
|
||||
|
||||
# Create a temp DB dir if necessary
|
||||
my $temp_db_dir;
|
||||
unless($db_dir) {
|
||||
$temp_db_dir = tempdir(CLEANUP => 1);
|
||||
$db_dir = abs_path($temp_db_dir);
|
||||
}
|
||||
|
||||
local $ENV{LXR_REPO_DIR} = $self->lxr_repo_dir;
|
||||
local $ENV{LXR_DATA_DIR} = $db_dir;
|
||||
|
||||
run_program($self->update_py)
|
||||
or die "Could not create database from $ENV{LXR_REPO_DIR} in $ENV{LXR_DATA_DIR}";
|
||||
|
||||
$self->_data_dir_token($temp_db_dir);
|
||||
$self->lxr_data_dir($db_dir);
|
||||
|
||||
return $self;
|
||||
} #build_db()
|
||||
|
||||
=head2 update_env
|
||||
|
||||
Set the C<LXR_REPO_DIR> and C<LXR_DATA_DIR> environment variables.
|
||||
Will not set a variable if the corresponding member does not have a value.
|
||||
|
||||
Returns the instance, for chaining.
|
||||
|
||||
=cut
|
||||
|
||||
sub update_env {
|
||||
my $self = shift;
|
||||
$ENV{LXR_REPO_DIR} = $self->lxr_repo_dir if $self->lxr_repo_dir;
|
||||
$ENV{LXR_DATA_DIR} = $self->lxr_data_dir if $self->lxr_data_dir;
|
||||
return $self;
|
||||
} #update_env()
|
||||
|
||||
=head2 DESTROY
|
||||
|
||||
Destructor. Called automatically.
|
||||
|
||||
=cut
|
||||
|
||||
sub DESTROY {
|
||||
local($., $@, $!, $^E, $?);
|
||||
my $self = shift;
|
||||
|
||||
# Release the temporary directories
|
||||
$self->_data_dir_token(undef);
|
||||
$self->_repo_dir_token(undef);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Christopher White, C<< <cwhite@d3engineering.com> >>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2020 D3 Engineering, LLC.
|
||||
|
||||
Elixir is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Elixir is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Elixir. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
=cut
|
||||
|
|
@ -18,8 +18,6 @@ TestHelpers - Common routines for use in tests
|
|||
|
||||
C<use TestHelpers;>, and all the functions below will be exported.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=cut
|
||||
|
||||
package TestHelpers;
|
||||
|
|
@ -28,7 +26,9 @@ use strict;
|
|||
use warnings;
|
||||
use autodie; # note: still need to check system() calls manually
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use File::Spec;
|
||||
use File::Temp 0.14 qw(tempdir);
|
||||
use FindBin;
|
||||
use IO::Select;
|
||||
use IPC::Open3;
|
||||
|
|
@ -40,10 +40,24 @@ use Test::More;
|
|||
use parent 'Exporter';
|
||||
our @EXPORT;
|
||||
BEGIN { @EXPORT = qw(sibling_abs_path find_program run_program ok_or_die
|
||||
run_produces_ok); }
|
||||
run_produces_ok MUST_SUCCEED); }
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=head2 MUST_SUCCEED
|
||||
|
||||
True. So that calls to L</run_produces_ok> will be more self-explanatory.
|
||||
|
||||
=cut
|
||||
|
||||
use constant MUST_SUCCEED => !!1;
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
These are helper routines that generally perform specific tasks.
|
||||
|
||||
=head2 sibling_abs_path
|
||||
|
||||
Return the absolute path of a file or directory in the same directory as
|
||||
|
|
|
|||
Loading…
Reference in a new issue