TestEnvironment: create PROJ_DIR structure
- Instead of creating standalone temporary data and repo dirs, create a temporary LXR_PROJ_DIR and put the data and repo dirs under it. - Change API test project name to match that now used in TestEnvironment This is the beginning of the test infrastructure for web.py.
This commit is contained in:
parent
d3ba0cea3b
commit
52d52cb2d6
4 changed files with 192 additions and 66 deletions
|
|
@ -24,6 +24,9 @@ TestEnvironment - Class representing an Elixir test environment
|
|||
$tenv->export_env; # Set $LXR_* environment vars
|
||||
# Now run tests against the database in $db_path
|
||||
|
||||
This module creates a temporary project dir and populates it with repo and
|
||||
data subdirs in a single project, named "testproj".
|
||||
|
||||
=cut
|
||||
|
||||
package TestEnvironment;
|
||||
|
|
@ -44,8 +47,15 @@ use Test::More;
|
|||
|
||||
use TestHelpers;
|
||||
|
||||
use constant PROJECT => 'testproj';
|
||||
|
||||
=head1 ATTRIBUTES
|
||||
|
||||
=head2 lxr_proj_dir
|
||||
|
||||
C<$lxr_proj_dir> is the value to use in the C<LXR_DATA_DIR> environment
|
||||
variable.
|
||||
|
||||
=head2 lxr_data_dir
|
||||
|
||||
C<$lxr_data_dir> is the value to use in the C<LXR_DATA_DIR> environment
|
||||
|
|
@ -69,12 +79,17 @@ As L</script_sh>, but for C<query.py>.
|
|||
|
||||
As L</script_sh>, but for C<update.py>.
|
||||
|
||||
=head2 web_py
|
||||
|
||||
As L</script_sh>, but for C<web.py>.
|
||||
|
||||
=head2 find_doc
|
||||
|
||||
As L</script_sh>, but for C<find-file-doc-comments.pl>.
|
||||
|
||||
=cut
|
||||
|
||||
has lxr_proj_dir => ();
|
||||
has lxr_data_dir => ();
|
||||
has lxr_repo_dir => ();
|
||||
has script_sh => (
|
||||
|
|
@ -86,19 +101,18 @@ has query_py => (
|
|||
has update_py => (
|
||||
default => sub { find_program('update.py') }
|
||||
);
|
||||
has web_py => (
|
||||
default => sub { find_program(qw(http web.py)) }
|
||||
);
|
||||
has find_doc => (
|
||||
default => sub { find_program('find-file-doc-comments.pl') }
|
||||
);
|
||||
|
||||
# Internal attributes
|
||||
|
||||
# a variable representing the temporary repository directory.
|
||||
# a variable representing the temporary project 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 => ();
|
||||
has _proj_dir_token => ();
|
||||
|
||||
=head1 MEMBER FUNCTIONS
|
||||
|
||||
|
|
@ -118,15 +132,15 @@ Dies on error. On success, returns the instance, for chaining.
|
|||
sub build_repo {
|
||||
my ($self, $tree_src_dir) = @_;
|
||||
die "Need a source dir" unless $tree_src_dir;
|
||||
die "No repo dir" unless $self->lxr_repo_dir;
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
my $tempdir_path = abs_path($tempdir);
|
||||
my $tempdir_path = $self->lxr_repo_dir;
|
||||
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\"")
|
||||
run_program('sh', '-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");
|
||||
|
|
@ -134,10 +148,6 @@ sub build_repo {
|
|||
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()
|
||||
|
||||
|
|
@ -146,50 +156,40 @@ sub build_repo {
|
|||
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.
|
||||
$tenv->build_db()
|
||||
|
||||
Dies on error. On success, returns the instance, for chaining.
|
||||
|
||||
B<CAUTION>: This function will remove the contents of C<$db_dir>
|
||||
B<CAUTION>: This function will remove the contents of C<< $tenv->lxr_data_dir >>
|
||||
unconditionally.
|
||||
|
||||
=cut
|
||||
|
||||
sub build_db {
|
||||
my ($self, $db_dir) = @_;
|
||||
my $self = shift;
|
||||
die "No repo dir" unless $self->lxr_repo_dir;
|
||||
die "No data dir" unless $self->lxr_data_dir;
|
||||
my $db_dir = $self->lxr_data_dir;
|
||||
|
||||
if($db_dir) { # Remove any existing DB dir
|
||||
if(-e $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.
|
||||
Set the C<LXR_PROJ_DIR>, 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.
|
||||
|
||||
|
|
@ -197,6 +197,7 @@ Returns the instance, for chaining.
|
|||
|
||||
sub update_env {
|
||||
my $self = shift;
|
||||
$ENV{LXR_PROJ_DIR} = $self->lxr_proj_dir if $self->lxr_proj_dir;
|
||||
$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;
|
||||
|
|
@ -211,15 +212,73 @@ Returns a human-readable report of the current environment's state.
|
|||
sub report {
|
||||
my $self = shift;
|
||||
return <<EOT;
|
||||
Project: @{[$self->lxr_proj_dir || '<unknown>']}
|
||||
Repository: @{[$self->lxr_repo_dir || '<unknown>']}
|
||||
Database: @{[$self->lxr_data_dir || '<unknown>']}
|
||||
script.sh: @{[$self->script_sh || '<unknown>']}
|
||||
update.py: @{[$self->update_py || '<unknown>']}
|
||||
query.py: @{[$self->query_py || '<unknown>']}
|
||||
web.py: @{[$self->web_py || '<unknown>']}
|
||||
find-file-doc-comments.pl: @{[$self->find_doc || '<unknown>']}
|
||||
EOT
|
||||
} #report()
|
||||
|
||||
=head2 make_web_request
|
||||
|
||||
Request a URL from L</web_py>. Usage:
|
||||
|
||||
my $html = $tenv->make_web_request($url);
|
||||
# Returns the HTML from stdout, or dies
|
||||
|
||||
my ($exit_status, $lrStdout, $lrStderr) = $tenv->make_web_request($url);
|
||||
# Returns the shell exit status, stdout text, and stderr text.
|
||||
|
||||
See L<TestEnvironment/run_program> for the details of the return values
|
||||
in the second case.
|
||||
|
||||
=cut
|
||||
|
||||
sub make_web_request {
|
||||
my ($self, $url) = @_;
|
||||
|
||||
$self->update_env; # just in case
|
||||
|
||||
local $ENV{REQUEST_URI} = $url;
|
||||
my ($exit_status, $lrStdout, $lrStderr) = run_program($self->web_py);
|
||||
|
||||
if(!wantarray) {
|
||||
return $lrStdout;
|
||||
} else {
|
||||
return ($exit_status, $lrStdout, $lrStderr);
|
||||
}
|
||||
} #make_web_request()
|
||||
|
||||
=head2 BUILD
|
||||
|
||||
Constructor. Creates the temporary project dir.
|
||||
|
||||
=cut
|
||||
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
my $temp_proj_dir = tempdir(CLEANUP => 1);
|
||||
my $proj_dir = abs_path($temp_proj_dir);
|
||||
|
||||
# Make the directory structure
|
||||
mkdir File::Spec->catdir($proj_dir, PROJECT);
|
||||
my $data_dir = File::Spec->catdir($proj_dir, PROJECT, 'data');
|
||||
my $repo_dir = File::Spec->catdir($proj_dir, PROJECT, 'repo');
|
||||
mkdir $data_dir;
|
||||
mkdir $repo_dir;
|
||||
|
||||
# Save the paths
|
||||
$self->_proj_dir_token($temp_proj_dir);
|
||||
$self->lxr_proj_dir($proj_dir);
|
||||
$self->lxr_data_dir($data_dir);
|
||||
$self->lxr_repo_dir($repo_dir);
|
||||
} #BUILD()
|
||||
|
||||
=head2 DESTROY
|
||||
|
||||
Destructor. Called automatically.
|
||||
|
|
@ -231,9 +290,8 @@ sub DESTROY {
|
|||
my $self = shift;
|
||||
|
||||
# Release the temporary directories
|
||||
$self->_data_dir_token(undef);
|
||||
$self->_repo_dir_token(undef);
|
||||
}
|
||||
$self->_proj_dir_token(undef);
|
||||
} #DESTROY()
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
|
|
|||
|
|
@ -87,45 +87,90 @@ sub sibling_abs_path {
|
|||
Looks for a program in the parent directory of this script.
|
||||
Usage:
|
||||
|
||||
$path = find_program('program name')
|
||||
$path = find_program(['subdir', ]'program name')
|
||||
|
||||
=cut
|
||||
|
||||
sub find_program {
|
||||
my $program = shift;
|
||||
my $pgm_file = pop; # Last arg
|
||||
my @pgm_dirs = @_; # Any args before the last are additional dirs.
|
||||
|
||||
my ($vol, $directories, $file) = File::Spec->splitpath($FindBin::Bin, 1); # 1 => is a dir
|
||||
my ($my_vol, $my_dirs, undef) = File::Spec->splitpath($FindBin::Bin, 1); # 1 => is a dir
|
||||
|
||||
# Go up to the parent of the directory holding this file
|
||||
my @dirs = File::Spec->splitdir($directories);
|
||||
die "Cannot run from the root directory" unless @dirs >= 2;
|
||||
pop @dirs;
|
||||
$directories = File::Spec->catdir(@dirs);
|
||||
my @my_dirs = File::Spec->splitdir($my_dirs);
|
||||
die "Cannot run from the root directory" unless @my_dirs >= 2;
|
||||
pop @my_dirs;
|
||||
my $dest_dirs = File::Spec->catdir(@my_dirs, @pgm_dirs);
|
||||
|
||||
return File::Spec->catpath($vol, $directories, $program);
|
||||
return File::Spec->catpath($my_vol, $dest_dirs, $pgm_file);
|
||||
} #find_program()
|
||||
|
||||
=head2 run_program
|
||||
|
||||
Print a command, then run it. Returns true if system() and the command
|
||||
succeed, false otherwise. Usage:
|
||||
Print a command, then run it. Can be used three ways:
|
||||
|
||||
$ok = run_program('program', 'arg1', ...)
|
||||
=over
|
||||
|
||||
=item In void context
|
||||
|
||||
Returns if system() and the command succeed, dies otherwise. Usage:
|
||||
|
||||
run_program('program', 'arg1', ...);
|
||||
|
||||
=item In scalar context
|
||||
|
||||
Returns true if system() and the command succeed, false otherwise. Usage:
|
||||
|
||||
my $ok = run_program('program', 'arg1', ...);
|
||||
|
||||
=item In list context
|
||||
|
||||
Returns the exit status, stdout, and stderr. Usage:
|
||||
|
||||
my ($exit_status, $lrStdout, $lrStderr) = run_program('program', 'arg1', ...);
|
||||
# Returns the shell exit status, stdout text, and stderr text.
|
||||
|
||||
C<$lrStdout> and C<$lrStderr> are references to the lists of output lines
|
||||
on the respective handles.
|
||||
|
||||
C<$exit_status> is C<128+signal> if the process was killed by C<signal>,
|
||||
for consistency with bash (L<https://tldp.org/LDP/abs/html/exitcodes.html>).
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub _run_and_capture; # forward
|
||||
|
||||
sub run_program {
|
||||
diag "Running @_";
|
||||
|
||||
if(wantarray) {
|
||||
goto &_run_and_capture;
|
||||
}
|
||||
|
||||
my $errmsg;
|
||||
|
||||
my $status = system(@_);
|
||||
|
||||
if ($status == -1) {
|
||||
diag "failed to execute $_[0]: $!";
|
||||
$errmsg = "failed to execute $_[0]: $!";
|
||||
}
|
||||
elsif ($status & 127) {
|
||||
diag sprintf "$_[0] died with signal %d, %s coredump\n",
|
||||
$errmsg = sprintf "$_[0] died with signal %d, %s coredump\n",
|
||||
($status & 127), ($status & 128) ? 'with' : 'without';
|
||||
}
|
||||
elsif($status != 0) {
|
||||
$errmsg = sprintf "$_[0] exited with value %d\n", $status >> 8;
|
||||
}
|
||||
else {
|
||||
diag sprintf "$_[0] exited with value %d\n", $status >> 8;
|
||||
diag "$_[0] reported success";
|
||||
}
|
||||
|
||||
if($errmsg) {
|
||||
die $errmsg unless defined wantarray;
|
||||
diag $errmsg;
|
||||
}
|
||||
|
||||
return($status == 0);
|
||||
|
|
@ -200,14 +245,16 @@ the "Documented in" section of the output of C<@program_and_args>.
|
|||
|
||||
=cut
|
||||
|
||||
sub run_produces_ok {
|
||||
my ($desc, $lrProgram, $lrRegexes, $mustSucceed, $printOutput) = @_;
|
||||
# _run_and_capture: run a program and return its exit status and output.
|
||||
# Usage:
|
||||
# my ($exit_status, \@stdout, \@stderr) = run_program('program', 'arg1', ...);
|
||||
|
||||
# Run program and capture stdout and stderr
|
||||
sub _run_and_capture {
|
||||
my ($in , $out, $err); # Filehandles
|
||||
$err = Symbol::gensym;
|
||||
diag "Running @$lrProgram";
|
||||
my $pid = open3($in, $out, $err, @$lrProgram);
|
||||
|
||||
diag "Running @_";
|
||||
my $pid = open3($in, $out, $err, @_);
|
||||
|
||||
my (@outlines, @errlines); # Captured output
|
||||
my $s = IO::Select->new;
|
||||
|
|
@ -231,7 +278,19 @@ sub run_produces_ok {
|
|||
}
|
||||
|
||||
waitpid $pid, 0;
|
||||
my $exit_status = $? >> 8;
|
||||
my $exit_status = $?;
|
||||
|
||||
$exit_status = ($exit_status & 127) + 128 if $exit_status & 127; # Killed by signal
|
||||
|
||||
return ($exit_status, \@outlines, \@errlines);
|
||||
} #_run_and_capture()
|
||||
|
||||
sub run_produces_ok {
|
||||
my ($desc, $lrProgram, $lrRegexes, $mustSucceed, $printOutput) = @_;
|
||||
|
||||
my ($exit_status, $outlines, $errlines) = _run_and_capture(@$lrProgram);
|
||||
my @outlines = @$outlines;
|
||||
my @errlines = @$errlines;
|
||||
|
||||
if ($printOutput) {
|
||||
diag "@outlines";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import sys
|
|||
import falcon
|
||||
from falcon import testing
|
||||
|
||||
api_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..','api'))
|
||||
api_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..','api'))
|
||||
sys.path.insert(0, api_dir)
|
||||
|
||||
from api import create_ident_getter
|
||||
|
|
@ -17,14 +17,14 @@ class APITest(testing.TestCase):
|
|||
self.app = create_ident_getter()
|
||||
|
||||
def test_identifier_not_found(self):
|
||||
result = self.simulate_get('/ident/tree/SOME_NONEXISTENT_IDENTIFIER', query_string="version=latest&family=C")
|
||||
result = self.simulate_get('/ident/testproj/SOME_NONEXISTENT_IDENTIFIER', query_string="version=latest&family=C")
|
||||
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(result.json, {'definitions': [], 'references':[]})
|
||||
|
||||
def test_missing_version(self):
|
||||
# A get request without a version query string
|
||||
result = self.simulate_get('/ident/tree/of_i2c_get_board_info')
|
||||
result = self.simulate_get('/ident/testproj/of_i2c_get_board_info', query_string="")
|
||||
|
||||
self.assertEqual(result.status_code, 400)
|
||||
|
||||
|
|
@ -33,25 +33,25 @@ class APITest(testing.TestCase):
|
|||
self.assertEqual(result.json["description"], required_response.description)
|
||||
|
||||
def test_existing_identifier(self):
|
||||
result_for_specific_version = self.simulate_get('/ident/tree/of_i2c_get_board_info', query_string="version=v5.4&family=C")
|
||||
result_for_latest_version = self.simulate_get('/ident/tree/of_i2c_get_board_info', query_string="version=latest&family=C")
|
||||
result_for_specific_version = self.simulate_get('/ident/testproj/of_i2c_get_board_info', query_string="version=v5.4&family=C")
|
||||
result_for_latest_version = self.simulate_get('/ident/testproj/of_i2c_get_board_info', query_string="version=latest&family=C")
|
||||
|
||||
expected_json = {
|
||||
'definitions':
|
||||
'definitions':
|
||||
[
|
||||
{'path': 'drivers/i2c/i2c-core-of.c', 'line': 22, 'type': 'function'},
|
||||
{'path': 'include/linux/i2c.h', 'line': 968, 'type': 'function'},
|
||||
{'path': 'include/linux/i2c.h', 'line': 941, 'type': 'prototype'}
|
||||
],
|
||||
'references':
|
||||
],
|
||||
'references':
|
||||
[
|
||||
{'path': 'drivers/i2c/i2c-core-of.c', 'line': '22,62,73', 'type': None},
|
||||
{'path': 'include/linux/i2c.h', 'line': '941,968', 'type': None}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
self.assertEqual(result_for_specific_version.status_code, 200)
|
||||
self.assertEqual(result_for_latest_version.status_code, 200)
|
||||
|
||||
|
||||
self.assertEqual(result_for_specific_version.json, expected_json)
|
||||
self.assertEqual(result_for_latest_version.json, expected_json)
|
||||
|
|
|
|||
|
|
@ -22,13 +22,16 @@
|
|||
use FindBin '$Bin';
|
||||
use lib $Bin;
|
||||
|
||||
use Cwd;
|
||||
use TestEnvironment;
|
||||
use TestHelpers;
|
||||
|
||||
# ===========================================================================
|
||||
# Main
|
||||
|
||||
# These two lines are all that's required to set up for a test.
|
||||
my $pwd = getcwd;
|
||||
|
||||
# This block is all that's required to set up for a test.
|
||||
my $tenv = TestEnvironment->new;
|
||||
$tenv->build_repo(sibling_abs_path('tree')); # dies on error
|
||||
eval { $tenv->build_db; };
|
||||
|
|
@ -45,9 +48,15 @@ system(qw(ln -s), $tenv->update_py, 'update.py') == 0
|
|||
or warn "error creating ./update.py: $? $!";
|
||||
system(qw(ln -s), $tenv->query_py, 'query.py') == 0
|
||||
or warn "error creating ./query.py: $? $!";
|
||||
system(qw(ln -s), $tenv->web_py, 'web.py') == 0
|
||||
or warn "error creating ./web.py: $? $!";
|
||||
system(qw(ln -s), $tenv->find_doc, 'find-file-doc-comments.pl') == 0
|
||||
or warn "error creating ./find-file-doc-comments.pl: $? $!";
|
||||
|
||||
print("Exit when done, and the repository and database will be removed.\n");
|
||||
my $retval = system($ENV{SHELL} || 'sh');
|
||||
|
||||
# Don't stay in the temp dir --- the dir can't be removed if we are there.
|
||||
chdir $pwd;
|
||||
|
||||
exit $retval>>8;
|
||||
|
|
|
|||
Loading…
Reference in a new issue