From 52d52cb2d6c46b65c95d5f8aca6300363708752e Mon Sep 17 00:00:00 2001 From: Christopher White Date: Wed, 13 May 2020 10:02:56 -0400 Subject: [PATCH] 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. --- t/TestEnvironment.pm | 128 +++++++++++++++++++++++++++++++------------ t/TestHelpers.pm | 99 ++++++++++++++++++++++++++------- t/api_test.py | 20 +++---- t/interact.pl | 11 +++- 4 files changed, 192 insertions(+), 66 deletions(-) diff --git a/t/TestEnvironment.pm b/t/TestEnvironment.pm index e1ed8b7..76a1ce5 100644 --- a/t/TestEnvironment.pm +++ b/t/TestEnvironment.pm @@ -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 environment +variable. + =head2 lxr_data_dir C<$lxr_data_dir> is the value to use in the C environment @@ -69,12 +79,17 @@ As L, but for C. As L, but for C. +=head2 web_py + +As L, but for C. + =head2 find_doc As L, but for C. =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 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: This function will remove the contents of C<$db_dir> +B: 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 and C environment variables. -Will not set a variable if the corresponding member does not have a value. +Set the C, C, and C 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 <lxr_proj_dir || '']} Repository: @{[$self->lxr_repo_dir || '']} Database: @{[$self->lxr_data_dir || '']} script.sh: @{[$self->script_sh || '']} update.py: @{[$self->update_py || '']} query.py: @{[$self->query_py || '']} +web.py: @{[$self->web_py || '']} find-file-doc-comments.pl: @{[$self->find_doc || '']} EOT } #report() +=head2 make_web_request + +Request a URL from L. 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 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__ diff --git a/t/TestHelpers.pm b/t/TestHelpers.pm index ef376e2..f9c04f8 100644 --- a/t/TestHelpers.pm +++ b/t/TestHelpers.pm @@ -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, +for consistency with bash (L). + +=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"; diff --git a/t/api_test.py b/t/api_test.py index 32c8821..daa5c87 100644 --- a/t/api_test.py +++ b/t/api_test.py @@ -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) diff --git a/t/interact.pl b/t/interact.pl index be86611..6734cb3 100755 --- a/t/interact.pl +++ b/t/interact.pl @@ -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;