# # (c) Jan Gehring # # vim: set ts=2 sw=2 tw=0: # vim: set expandtab: =head1 NAME Rex::Test::Base - Basic Test Module =head1 DESCRIPTION This is a basic test module to test your code with the help of local VMs. You can place your tests in the "t" directory. =head1 EXAMPLE use Rex::Test::Base; use Data::Dumper; use Rex -base; test { my $t = shift; $t->name("ubuntu test"); $t->base_vm("http://box.rexify.org/box/ubuntu-server-12.10-amd64.ova"); $t->vm_auth(user => "root", password => "box"); $t->run_task("setup"); $t->has_package("vim"); $t->has_package("ntp"); $t->has_package("unzip"); $t->has_file("/etc/ntp.conf"); $t->has_service_running("ntp"); $t->has_content("/etc/passwd", qr{root:x:0:}ms); run "ls -l"; $t->ok($? == 0, "ls -l returns success."); $t->finish; }; 1; # last line =head1 METHODS =cut package Rex::Test::Base; use strict; use warnings; use base 'Test::Builder::Module'; our $VERSION = '1.7.0'; # VERSION require Rex::Commands; use Rex::Commands::Box; use Data::Dumper; use Carp; require Exporter; use base qw(Exporter); use vars qw(@EXPORT); @EXPORT = qw(test); =head2 new(name => $test_name) Constructor if used in OO mode. my $test = Rex::Test::Base->new(name => "test_name"); =cut sub new { my $that = shift; my $proto = ref($that) || $that; my $self = {@_}; bless( $self, $proto ); my ( $pkg, $file ) = caller(0); $self->{name} ||= $file; $self->{redirect_port} = 2222; $self->{memory} ||= 512; # default, in MB $self->{cpu} ||= 1; # default return $self; } =head2 name($name) The name of the test. A VM called $name will be created for each test. If the VM already exists, Rex will try to reuse it. =cut sub name { my ( $self, $name ) = @_; $self->{name} = $name; } =head2 memory($amount) The amount of memory the VM should use, in Megabytes. =cut sub memory { my ( $self, $memory ) = @_; $self->{memory} = $memory; } =head2 cpus($number) The number of CPUs the VM should use. =cut sub cpus { my ( $self, $cpus ) = @_; $self->{cpus} = $cpus; } =head2 vm_auth(%auth) Authentication options for the VM. It accepts the same parameters as Cauth()>. =cut sub vm_auth { my ( $self, %auth ) = @_; $self->{auth} = \%auth; } =head2 base_vm($vm) The URL to a base image to be used for the test VM. =cut sub base_vm { my ( $self, $vm ) = @_; $self->{vm} = $vm; } sub test(&) { ## no critic ProhibitSubroutinePrototypes my $code = shift; my $test = __PACKAGE__->new; $code->($test); } =head2 redirect_port($port) Redirect local $port to the VM's SSH port (default: 2222). =cut sub redirect_port { my ( $self, $port ) = @_; $self->{redirect_port} = $port; } =head2 run_task($task) The task to run on the test VM. You can run multiple tasks by passing an array reference. =cut sub run_task { my ( $self, $task ) = @_; # allow multiple calls to run_task() without setting up new box if ( $self->{box} ) { $self->{box}->provision_vm($task); return; } my $box; box { $box = shift; $box->name( $self->{name} ); $box->url( $self->{vm} ); $box->memory( $self->{memory} ); $box->cpus( $self->{cpus} ); $box->network( 1 => { type => "nat", } ); $box->forward_port( ssh => [ $self->{redirect_port}, 22 ] ); $box->auth( %{ $self->{auth} } ); if ( ref $task eq 'ARRAY' ) { $box->setup(@$task); } else { $box->setup($task); } }; $self->{box} = $box; # connect to the machine Rex::connect( server => $box->ip, %{ $self->{auth} }, ); } sub ok() { my ( $self, $test, $msg ) = @_; my $tb = Rex::Test::Base->builder; $tb->ok( $test, $msg ); } sub like { my ( $self, $thing, $want, $name ) = @_; my $tb = Rex::Test::Base->builder; $tb->like( $thing, $want, $name ); } sub diag { my ( $self, $msg ) = @_; my $tb = Rex::Test::Base->builder; $tb->diag($msg); } sub finish { my $tb = Rex::Test::Base->builder; $tb->done_testing(); $tb->is_passing() ? print "PASS\n" : print "FAIL\n"; if ( !$tb->is_passing() ) { Rex::Test::push_exit("FAIL"); } $tb->reset(); Rex::pop_connection(); } =head1 TEST METHODS =head2 has_content($file, $regexp) Test if the content of $file matches against $regexp. =head2 has_dir($path) Test if $path is present and is a directory. =head2 has_file($file) Test if $file is present. =head2 has_package($package, $version) Test if $package is installed, optionally at $version. =head2 has_service_running($service) Test if $service is running. =head2 has_service_stopped($service) Test if $service is stopped. =head2 has_stat($file, $stat) Test if $file has properties described in hash reference $stat. List of supported checks: =over 4 =item group =item owner =back =cut our $AUTOLOAD; sub AUTOLOAD { my $self = shift or return; ( my $method = $AUTOLOAD ) =~ s{.*::}{}; if ( $method eq "DESTROY" ) { return; } my $real_method = $method; my $is_not = 0; if ( $real_method =~ m/^has_not_/ ) { $real_method =~ s/^has_not_/has_/; $is_not = 1; } my $pkg = __PACKAGE__ . "::$real_method"; eval "use $pkg"; if ($@) { confess "Error loading $pkg. No such test method."; } my $p = $pkg->new; if ($is_not) { $p->run_not_test(@_); } else { $p->run_test(@_); } } 1;