Add automated test of basic functions
- Added test helpers in t/TestHelpers.pm
- Added basic test of update.py and query.py in t/100-basic.t
- Updated README:
- Added automated-testing section
- Formatted identifiers (i.e., added backticks)
This commit is contained in:
parent
b22a562b5b
commit
fa7243638d
4 changed files with 413 additions and 18 deletions
60
README.md
60
README.md
|
|
@ -1,6 +1,5 @@
|
|||
# The Elixir Cross Referencer
|
||||
|
||||
|
||||
Elixir is a source code cross-referencer inspired by
|
||||
[LXR](https://en.wikipedia.org/wiki/LXR_Cross_Referencer). It's written
|
||||
in Python and its main purpose is to index every release of a C or C++
|
||||
|
|
@ -13,14 +12,16 @@ duplicating work and data. It has a straightforward data structure
|
|||
|
||||
You can see it in action on https://elixir.bootlin.com/
|
||||
|
||||
Note: this documentation applies to version 1.0 of Elixir.
|
||||
|
||||
# Requirements
|
||||
|
||||
* Python >= 3.5
|
||||
* The Jinja2 and Pygments (>= 2.2) Python libraries
|
||||
* Berkeley DB (and its Python binding)
|
||||
* Exuberant Ctags
|
||||
* Perl (for non-greedy regexes)
|
||||
* Falcon and mod_wsgi (for the REST api)
|
||||
* Perl (for non-greedy regexes and automated testing)
|
||||
* Falcon and `mod_wsgi` (for the REST api)
|
||||
|
||||
# Installation
|
||||
|
||||
|
|
@ -63,8 +64,8 @@ yum install python36-jinja2 python36-pygments python36-bsddb3 python3-falcon glo
|
|||
sudo apt install python3 python3-jinja2 python3-pygments python3-bsddb3 python3-falcon exuberant-ctags perl git apache2 libapache2-mod-wsgi-py3
|
||||
```
|
||||
|
||||
To enable the REST api, follow the installation instructions on [mod_wsgi](https://github.com/GrahamDumpleton/mod_wsgi)
|
||||
and connect it to the apache installation as detailed in https://github.com/GrahamDumpleton/mod_wsgi#connecting-into-apache-installation
|
||||
To enable the REST api, follow the installation instructions on [`mod_wsgi`](https://github.com/GrahamDumpleton/mod_wsgi)
|
||||
and connect it to the apache installation as detailed in <https://github.com/GrahamDumpleton/mod_wsgi#connecting-into-apache-installation>.
|
||||
|
||||
To know which packages to install, you can also read the Docker files in the `docker/` directory
|
||||
to know what packages Elixir needs in your favorite distribution.
|
||||
|
|
@ -87,8 +88,8 @@ mkdir -p /path/elixir-data/linux/data
|
|||
Two environment variables are used to tell Elixir where to find the project's
|
||||
local git repository and its databases:
|
||||
|
||||
* LXR_REPO_DIR (the git repository directory for your project)
|
||||
* LXR_DATA_DIR (the database directory for your project)
|
||||
* `LXR_REPO_DIR` (the git repository directory for your project)
|
||||
* `LXR_DATA_DIR` (the database directory for your project)
|
||||
|
||||
Now open `/etc/profile` and append the following content.
|
||||
|
||||
|
|
@ -150,16 +151,16 @@ server. Since it includes support for indexing multiple projects,
|
|||
it expects a different variable (`LXR_PROJ_DIR`) which points to a
|
||||
directory with a specific structure:
|
||||
|
||||
* <LXR_PROJ_DIR>
|
||||
* <project 1>
|
||||
* data
|
||||
* repo
|
||||
* <project 2>
|
||||
* data
|
||||
* repo
|
||||
* <project 3>
|
||||
* data
|
||||
* repo
|
||||
* `<LXR_PROJ_DIR>`
|
||||
* `<project 1>`
|
||||
* `data`
|
||||
* `repo`
|
||||
* `<project 2>`
|
||||
* `data`
|
||||
* `repo`
|
||||
* `<project 3>`
|
||||
* `data`
|
||||
* `repo`
|
||||
|
||||
It will then generate the other two variables upon calling the query
|
||||
command.
|
||||
|
|
@ -415,4 +416,27 @@ The response body is of the following structure:
|
|||
}
|
||||
```
|
||||
|
||||
Note: this documentation applies to version 1.0 of Elixir.
|
||||
# Automated testing
|
||||
|
||||
Elixir includes a simple test suite in `t/`. To run it,
|
||||
from the top-level Elixir directory, run:
|
||||
|
||||
prove
|
||||
|
||||
The test suite uses code extracted from Linux v5.4 in `t/tree`.
|
||||
|
||||
## Licensing of code in `t/tree`
|
||||
|
||||
The copied code is licensed as described in the [COPYING] file included with
|
||||
Linux. All the files copied carry SPDX license identifiers of `GPL-2.0+` or
|
||||
`GPL-2.0-or-later`. Per [GNU's compatibility table], GPL 2.0+ code can be used
|
||||
under GPLv3 provided the combination is under GPLv3. Moreover, [GNU's overview
|
||||
of AGPLv3] indicates that its terms "effectively consist of the terms of GPLv3"
|
||||
plus the network-use paragraph. Therefore, the developers have a good-faith
|
||||
belief that licensing these files under AGPLv3 is authorized. (See also [this
|
||||
issue comment] for another example of a similar situation.)
|
||||
|
||||
[COPYING]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/COPYING
|
||||
[GNU's compatibility table]: https://www.gnu.org/licenses/gpl-faq.en.html#AllCompatibility
|
||||
[GNU's overview of AGPLv3]: https://www.gnu.org/licenses/license-list.en.html#AGPLv3.0
|
||||
[this issue comment]: https://github.com/Freemius/wordpress-sdk/issues/166#issuecomment-310561976
|
||||
|
|
|
|||
2
t/.gitignore
vendored
Normal file
2
t/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Where we put the test database used by 100-basic.t
|
||||
/db/
|
||||
145
t/100-basic.t
Normal file
145
t/100-basic.t
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env perl
|
||||
# 100-basic.t: Test basic elixir functions against the files in tree/ .
|
||||
#
|
||||
# Copyright (c) 2020 D3 Engineering, LLC.
|
||||
# By Christopher White, <cwhite@d3engineering.com>.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# This file uses core Perl modules only.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
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 TestHelpers;
|
||||
|
||||
# ===========================================================================
|
||||
# Main
|
||||
|
||||
# Set up
|
||||
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});
|
||||
}
|
||||
|
||||
# Check programs
|
||||
my $script_sh = find_program('script.sh');
|
||||
my $update_py = find_program('update.py');
|
||||
my $query_py = find_program('query.py');
|
||||
|
||||
ok_or_die( (-f $script_sh && -r _ && -x _), 'script.sh executable',
|
||||
"Could not find executable script.sh at $script_sh");
|
||||
ok_or_die( (-f $update_py && -r _ && -x _), 'update.py executable',
|
||||
"Could not find executable update.py at $update_py");
|
||||
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;
|
||||
|
||||
# Check for tags in `script.sh list-tags`, as a sanity check before
|
||||
# building the test DB
|
||||
my @tags = `$script_sh list-tags`;
|
||||
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');
|
||||
|
||||
ok_or_die( -d $db_dir, 'database dir exists',
|
||||
"Database dir $db_dir not present");
|
||||
|
||||
# Make sure the database has the files we expect
|
||||
ok( (-r File::Spec->catfile($db_dir, $_)), "$_ exists" )
|
||||
foreach qw(blobs.db definitions.db filenames.db hashes.db references.db
|
||||
variables.db versions.db);
|
||||
|
||||
# Spot-check some identifiers
|
||||
|
||||
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);
|
||||
|
||||
run_produces_ok('ident query (existent)',
|
||||
[$query_py, qw(v5.4 ident i2c_acpi_notify)],
|
||||
[qr{^Symbol Definitions:}, qr{^Symbol References:},
|
||||
qr{drivers/i2c/i2c-core-acpi\.c.+\b402\b.+\bfunction\b}, # def
|
||||
qr{drivers/i2c/i2c-core-acpi\.c.+\b402,439} # refs
|
||||
],
|
||||
1);
|
||||
|
||||
# Spot-check some files
|
||||
|
||||
run_produces_ok('file query (nonexistent)',
|
||||
[$query_py, qw(v5.4 file /SOME_NONEXISTENT_FILENAME_XYZZY_PLUGH)],
|
||||
[{not => qr{\S}}]);
|
||||
|
||||
run_produces_ok('file query (existent), .h',
|
||||
[$query_py, qw(v5.4 file /drivers/i2c/i2c-dev.c)],
|
||||
[qr{\S}],
|
||||
1);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
#system('bash'); # Uncomment this if you want to interact with the test repo
|
||||
|
||||
done_testing;
|
||||
224
t/TestHelpers.pm
Normal file
224
t/TestHelpers.pm
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
#!/usr/bin/env perl
|
||||
# TestHelpers.pm: Common routines for use in tests.
|
||||
# See license information at end of file.
|
||||
#
|
||||
# For a cleaner view of the documentation, run
|
||||
# perldoc TestHelpers.pm
|
||||
# (on Ubuntu, you may need to install the perl-doc package first.)
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# This file uses core Perl modules only.
|
||||
|
||||
=head1 NAME
|
||||
|
||||
TestHelpers - Common routines for use in tests
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
C<use TestHelpers;>, and all the functions below will be exported.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=cut
|
||||
|
||||
package TestHelpers;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use autodie; # note: still need to check system() calls manually
|
||||
|
||||
use File::Spec;
|
||||
use FindBin;
|
||||
use IO::Select;
|
||||
use IPC::Open3;
|
||||
use Symbol;
|
||||
|
||||
use Test::More;
|
||||
|
||||
# Automatically export all the functions below
|
||||
use parent 'Exporter';
|
||||
our @EXPORT;
|
||||
BEGIN { @EXPORT = qw(sibling_abs_path find_program run_program ok_or_die
|
||||
run_produces_ok); }
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
=head2 sibling_abs_path
|
||||
|
||||
Return the absolute path of a file or directory in the same directory as
|
||||
this file. Usage:
|
||||
|
||||
$path = sibling_abs_path('name');
|
||||
|
||||
=cut
|
||||
|
||||
sub sibling_abs_path {
|
||||
return File::Spec->rel2abs(File::Spec->catfile($FindBin::Bin, @_));
|
||||
}
|
||||
|
||||
=head2 find_program
|
||||
|
||||
Looks for a program in the parent directory of this script.
|
||||
Usage:
|
||||
|
||||
$path = find_program('program name')
|
||||
|
||||
=cut
|
||||
|
||||
sub find_program {
|
||||
my $program = shift;
|
||||
|
||||
my ($vol, $directories, $file) = 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);
|
||||
|
||||
return File::Spec->catpath($vol, $directories, $program);
|
||||
} #find_program()
|
||||
|
||||
=head2 run_program
|
||||
|
||||
Print a command, then run it. Returns true if system() and the command
|
||||
succeed, false otherwise. Usage:
|
||||
|
||||
$ok = run_program('program', 'arg1', ...)
|
||||
|
||||
=cut
|
||||
|
||||
sub run_program {
|
||||
diag "Running @_";
|
||||
my $status = system(@_);
|
||||
if ($status == -1) {
|
||||
diag "failed to execute $_[0]: $!";
|
||||
}
|
||||
elsif ($status & 127) {
|
||||
diag sprintf "$_[0] died with signal %d, %s coredump\n",
|
||||
($status & 127), ($status & 128) ? 'with' : 'without';
|
||||
}
|
||||
else {
|
||||
diag sprintf "$_[0] exited with value %d\n", $status >> 8;
|
||||
}
|
||||
|
||||
return($status == 0);
|
||||
} #run_program()
|
||||
|
||||
=head2 ok_or_die
|
||||
|
||||
Run a test, but die if it fails. Usage:
|
||||
|
||||
ok_or_die( <some condition>, 'description', 'what to print if it dies' )
|
||||
|
||||
=cut
|
||||
|
||||
sub ok_or_die {
|
||||
my ($cond, $msg, $err_msg) = @_;
|
||||
my $line = (caller)[2];
|
||||
my $retval = eval <<EOT; # Make the error message report the caller's line number
|
||||
#line $line
|
||||
ok(\$cond, \$msg);
|
||||
EOT
|
||||
die($err_msg) unless $retval;
|
||||
return $retval;
|
||||
} #ok_or_die()
|
||||
|
||||
=head2 run_produces_ok
|
||||
|
||||
Run a program and check whether it produces expected output.
|
||||
Usage:
|
||||
|
||||
run_produces_ok($desc, \@program_and_args, \@expected_regexes,
|
||||
<optional> $mustSucceed)
|
||||
|
||||
The test passes if each regex in C<@expected_regexes> matches at least one
|
||||
line in the output of C<@program_and_args>, and if each C<< { not => regex } >>
|
||||
in C<@expected_regexes> is NOT found in that output.
|
||||
If C<$mustSucceed> is true, also tests for exit status 0 and empty stderr.
|
||||
|
||||
=cut
|
||||
|
||||
sub run_produces_ok {
|
||||
my ($desc, $lrProgram, $lrRegexes, $mustSucceed) = @_;
|
||||
|
||||
# Run program and capture stdout and stderr
|
||||
my ($in , $out, $err); # Filehandles
|
||||
$err = Symbol::gensym;
|
||||
diag "Running @$lrProgram";
|
||||
my $pid = open3($in, $out, $err, @$lrProgram);
|
||||
|
||||
my (@outlines, @errlines); # Captured output
|
||||
my $s = IO::Select->new;
|
||||
$s->add($out);
|
||||
$s->add($err);
|
||||
|
||||
while(my @ready = $s->can_read) {
|
||||
for my $fh (@ready) {
|
||||
|
||||
if(eof($fh)) {
|
||||
$s->remove($fh);
|
||||
next;
|
||||
}
|
||||
|
||||
if($fh == $out) {
|
||||
push @outlines, scalar readline $fh;
|
||||
} else {
|
||||
push @errlines, scalar readline $fh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitpid $pid, 0;
|
||||
my $exit_status = $? >> 8;
|
||||
|
||||
# Basic checks
|
||||
if($mustSucceed) {
|
||||
cmp_ok($exit_status, '==', 0, "$desc: exit status 0");
|
||||
cmp_ok(@errlines, '==', 0, "$desc: stderr empty");
|
||||
}
|
||||
|
||||
# Check regexes
|
||||
for my $entry (@$lrRegexes) {
|
||||
|
||||
if(ref $entry eq 'Regexp') {
|
||||
ok( (grep { m{$entry} } @outlines), "$desc: output includes $entry" );
|
||||
|
||||
} elsif(ref $entry eq 'HASH' && ref $entry->{not} eq 'Regexp') {
|
||||
my $re = $entry->{not};
|
||||
ok( !(grep { m{$re} } @outlines), "$desc: output excludes $re" );
|
||||
|
||||
} else {
|
||||
die "Invalid entry $entry";
|
||||
}
|
||||
} #foreach $entry
|
||||
|
||||
} #run_produces_ok()
|
||||
|
||||
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
|
||||
Loading…
Reference in a new issue