summaryrefslogtreecommitdiffstats
path: root/scripts/lib/perl5/QtQA/TestScript.pm
diff options
context:
space:
mode:
authorRohan McGovern <rohan.mcgovern@nokia.com>2011-06-03 08:33:04 +1000
committerRohan McGovern <rohan.mcgovern@nokia.com>2011-06-03 13:17:27 +1000
commitd8f33c082625333ee2d4a11148db409bb762dae8 (patch)
tree36621491ae3d1838b2a53ac6a2848ffac8f93ea0 /scripts/lib/perl5/QtQA/TestScript.pm
parent584fa7649168c6d5966d2fc148619f2e321749c0 (diff)
Rename `Qt' namespace used in qtqa perl scripts to `QtQA'
The `Qt' perl namespace should be reserved for code which conceptually forms a part of the Qt API, e.g. the Qt perl bindings. Reviewed-by: Keith Isdale Change-Id: I0f2a69156e03abebd177bc7f5231e5d4a84499a2
Diffstat (limited to 'scripts/lib/perl5/QtQA/TestScript.pm')
-rw-r--r--scripts/lib/perl5/QtQA/TestScript.pm620
1 files changed, 620 insertions, 0 deletions
diff --git a/scripts/lib/perl5/QtQA/TestScript.pm b/scripts/lib/perl5/QtQA/TestScript.pm
new file mode 100644
index 00000000..0f04b338
--- /dev/null
+++ b/scripts/lib/perl5/QtQA/TestScript.pm
@@ -0,0 +1,620 @@
+package QtQA::TestScript;
+use strict;
+use warnings;
+
+use Carp qw(confess croak);
+use Cwd qw();
+use Getopt::Long qw(GetOptionsFromArray);
+use IO::Socket::INET qw();
+use Pod::Simple::Text qw();
+use Pod::Usage qw(pod2usage);
+
+#======================== private variables ===================================
+
+# some common properties with subs or scalars to determine their default values
+my %DEFAULT_COMMON_PROPERTIES = (
+ 'base.dir' => \&Cwd::getcwd,
+ 'location' => \&_default_location,
+ 'make.bin' => 'make',
+ 'make.args' => '-j5',
+);
+
+#======================== public methods ======================================
+# These must all be documented at the end of the file
+
+
+sub new
+{
+ my $class = shift;
+
+ my %self = (
+ resolved_property => {}, # resolved property cache starts empty
+ verbose => 0,
+ );
+
+ bless \%self, $class;
+ return \%self;
+}
+
+
+sub set_permitted_properties
+{
+ my ($self, %permitted_properties) = @_;
+
+ $self->{permitted_properties} = \%permitted_properties;
+ return;
+}
+
+
+sub property
+{
+ my $self = shift;
+ my $property_name = shift;
+ my $default_value = shift;
+
+ unless ($self->{permitted_properties}) {
+ croak q{test script error: `property' was called before `set_permitted_properties'};
+ }
+
+ unless (exists($self->{permitted_properties}->{$property_name})) {
+ croak "test script error: test script attempted to read property `$property_name', "
+ . "but did not declare it as a permitted property";
+ }
+
+ my $value = $self->_resolved_property($property_name);
+ if (defined($value)) {
+ return $value;
+ }
+
+ # This will die if the value is not set in a `PULSE_...' environment variable.
+ $value = $self->_resolve_property_from_env($property_name, $default_value);
+
+ $self->_set_resolved_property($property_name, $value);
+
+ return $value;
+}
+
+
+sub get_options_from_array
+{
+ my ($self, $arg_values_ref, @arg_specifiers) = @_;
+
+ # Simple args understood by all test scripts ...
+ my @standard_arg_specifiers = (
+ 'help' => sub { $self->print_usage(1) },
+ 'verbose+' => \$self->{verbose} ,
+ );
+
+ # Args which may be used to set properties...
+ my @permitted_property_arg_specifiers;
+ if (exists $self->{permitted_properties}) {
+ foreach my $property_name (keys %{$self->{permitted_properties}}) {
+ my $option_name = $self->_property_name_to_option_name( $property_name );
+ push( @permitted_property_arg_specifiers,
+ "$option_name=s" => sub {
+ $self->_set_resolved_property($property_name, $_[1]);
+ }
+ );
+ }
+ }
+
+ GetOptionsFromArray( $arg_values_ref,
+ @arg_specifiers,
+ @standard_arg_specifiers,
+ @permitted_property_arg_specifiers,
+ ) || $self->print_usage(2);
+
+ # Flag that get_options_from_array has been called; we can use this later
+ # for improved warning or debugging messages.
+ $self->{called_get_options_from_array} = 1;
+
+ return;
+}
+
+
+sub read_and_store_properties
+{
+ my ($self, @properties) = @_;
+
+ while (@properties) {
+ my $property_name = shift @properties;
+ my $property_default_value = shift @properties;
+
+ # default value may be a sub which we must execute ...
+ if (ref($property_default_value) eq 'CODE') {
+ $property_default_value = $property_default_value->($self, $property_name);
+ }
+
+ $self->{$property_name} = $self->property( $property_name, $property_default_value );
+ }
+
+ return;
+}
+
+
+sub default_common_property
+{
+ my ($self, $property_name) = @_;
+
+ my $property_value = $DEFAULT_COMMON_PROPERTIES{$property_name};
+ if (ref($property_value) eq 'CODE') {
+ $property_value = $property_value->($self, $property_name);
+ }
+
+ return $property_value;
+}
+
+
+sub exe
+{
+ my ($self, @command) = @_;
+
+ # We are going to add values to env for all properties which are defined.
+ # This ensures that the parent script always has full control over default
+ # values of properties. Otherwise, parent and child scripts could have
+ # different defaults and therefore give unexpected behavior.
+ my @property_env_keys
+ = map { $self->_property_name_to_env_name($_) } keys %{$self->{resolved_property}};
+
+ local @ENV{@property_env_keys} = values %{$self->{resolved_property}};
+
+ # XXX important missing feature compared to Pulse::x - automatic retry.
+ #
+ # Pulse::x supported parsing of command output and automatically retrying
+ # on certain types of errors. Unfortunately the code for that was very fragile,
+ # especially on Windows where quoting issues were common. So it has not been
+ # ported for now.
+
+ $self->print_when_verbose(1, '+ ', join(' ', @command), "\n");
+ my $status = system( @command );
+ if ($status != 0) {
+ croak "@command exited with status $status";
+ }
+
+ return;
+}
+
+
+sub print_when_verbose
+{
+ my ($self, $verbosity, @print_list) = @_;
+
+ my $out = 0;
+
+ if ($self->{verbose} >= $verbosity) {
+ print @print_list;
+ $out = 1;
+ }
+
+ return $out;
+}
+
+
+sub print_usage
+{
+ my ($self, $exitcode) = @_;
+
+ pod2usage({
+ -exitval => 'NOEXIT',
+ });
+
+ my $properties_pod = join "\n", (
+ '=head2 Standard options:',
+ '',
+ '=over',
+ '',
+ '=item --help',
+ '',
+ 'Print this help.',
+ '',
+ '=item --verbose',
+ '',
+ 'Be more verbose. Specify multiple times for more verbosity.',
+ '',
+ '=back',
+ );
+
+ if ($self->{permitted_properties}) {
+ $properties_pod .= "\n\n=head2 Options specific to this script:\n\n=over\n\n";
+
+ foreach my $property_name (sort keys %{$self->{permitted_properties}}) {
+ my $property_doc = $self->{permitted_properties}->{$property_name};
+
+ my $option_name = $self->_property_name_to_option_name( $property_name );
+
+ $properties_pod .= "=item [$property_name] --$option_name <value>\n\n";
+ $properties_pod .= "$property_doc\n\n";
+ }
+
+ $properties_pod .= "=back\n\n=cut\n";
+
+ Pod::Simple::Text->filter( \$properties_pod );
+ }
+
+ exit $exitcode;
+}
+
+#====================== internals =============================================
+
+# get the value of a property which has been resolved already.
+# `resolved' means it has been read from command-line arguments or from environment.
+#
+# Parameters:
+# $name - name of the property to get
+#
+# Returns the value, or undef if the property has not yet been resolved.
+#
+sub _resolved_property
+{
+ my ($self, $name) = @_;
+
+ if (exists($self->{resolved_property}->{$name})) {
+ return $self->{resolved_property}->{$name};
+ }
+
+ return;
+}
+
+# set the resolved value of a property.
+#
+# Parameters:
+# $name - name of the property, e.g. 'base.dir'
+# $value - value of the property, e.g. '/tmp/foo/bar'
+#
+sub _set_resolved_property
+{
+ my ($self, $name, $value) = @_;
+
+ $self->{resolved_property}->{$name} = $value;
+
+ return;
+}
+
+# Converts a property name (e.g. qt.configure.args) to an option
+# name suitable for getopt (e.g. qt-configure-args)
+sub _property_name_to_option_name
+{
+ my ($self, $name) = @_;
+
+ $name = lc $name;
+ $name =~ s/[^a-z0-9\-]/-/g;
+
+ return $name;
+}
+
+# Converts a property name (e.g. qt.configure.args) to an option
+# name suitable for an environment variable (e.g. PULSE_QT_CONFIGURE_ARGS)
+# The `PULSE_...' style of naming is used for convenient integration
+# with the Pulse CI tool.
+sub _property_name_to_env_name
+{
+ my ($self, $name) = @_;
+
+ $name = uc $name;
+ $name =~ s/[^A-Z0-9]/_/g;
+ $name = "PULSE_$name";
+
+ return $name;
+
+}
+
+# Get the value of a property from an environment variable
+sub _resolve_property_from_env
+{
+ my ($self, $property_name, $property_default_value) = @_;
+
+ my $value;
+ my $env_name = $self->_property_name_to_env_name( $property_name );
+ if (exists $ENV{$env_name}) {
+ $value = $ENV{$env_name};
+ }
+ elsif (defined $property_default_value) {
+ $value = $property_default_value;
+ }
+ else {
+ $self->_croak_from_missing_property( $property_name, {
+ tried_env => 1,
+ tried_argv => $self->{called_get_options_from_array}
+ });
+ }
+
+ return $value;
+}
+
+# Croak with a sensible error message about an undefined property
+sub _croak_from_missing_property
+{
+ my ($self, $property_name, $arg_ref) = @_;
+
+ my $message = "The required property `$property_name' was not defined and there is no "
+ ."default value.\n";
+
+ my @set_methods;
+
+ if ($arg_ref->{tried_env}) {
+ my $env_name = $self->_property_name_to_env_name( $property_name );
+ push @set_methods, "via environment variable $env_name";
+ }
+ if ($arg_ref->{tried_argv}) {
+ my $option_name = $self->_property_name_to_option_name( $property_name );
+ push @set_methods, "via --$option_name command-line option";
+ }
+
+ if (@set_methods) {
+ $message .= "It may be defined by one of the following:\n";
+ $message .= join(q{}, map { " $_\n" } @set_methods);
+ }
+
+ croak $message;
+}
+
+# Attempt to return this host's most significant IP address
+sub _get_primary_ip
+{
+ my $sock = IO::Socket::INET->new(
+ PeerAddr=> "example.com",
+ PeerPort=> 80,
+ Proto => "tcp");
+ return $sock->sockhost;
+}
+
+# Returns default location (e.g. `brisbane', `oslo')
+sub _default_location
+{
+ my ($self) = shift;
+
+ my $ip;
+ eval {
+ $ip = $self->_get_primary_ip; # may fail if lacking Internet connectivity
+ };
+
+ return '' if (!$ip);
+
+ # Brisbane subnets:
+ # 172.30.116.0/24
+ # 172.30.136.0/24
+ # 172.30.138.0/24
+ # 172.30.139.0/24
+ if ($ip =~ /^172\.30\.(116|136|138|139)\./) {
+ return 'brisbane';
+ }
+
+ # Oslo subnets:
+ # 172.30.105.0/24
+ # 172.24.105.0/24
+ # 172.24.90.0/24 europe.nokia.com, consider as oslo
+ if ($ip =~ /^172\.30\.105\./ ||
+ $ip =~ /^172\.24\.(90|105)\./) {
+ return 'oslo';
+ }
+
+ return '';
+}
+
+
+1;
+
+__END__
+
+
+=head1 NAME
+
+QtQA::TestScript - base class for Qt test scripts
+
+=head1 SYNOPSIS
+
+ use base qw(QtQA::TestScript);
+ ...
+
+This is the recommended base class for all new test scripts for Qt test
+infrastructure. It encapsulates some functionality which all test scripts
+are likely to benefit from, and encourages some uniform coding and
+documentation conventions between test scripts.
+
+
+=head1 METHODS
+
+=over
+
+
+
+=item B<new>
+
+Create a new TestScript object, with empty state.
+
+
+
+=item B<property>( NAME )
+
+=item B<property>( NAME, DEFAULT )
+
+Returns the value of the specified property.
+
+In the first form, where DEFAULT is not specified, the test script will die
+if the property has not been set.
+
+In the second form, DEFAULT will be returned if the property has not been set.
+
+A property is a string value which affects the behavior of the current test
+script. Properties may be sourced from:
+
+=over
+
+=item environment
+
+Environment variables prefixed with C<PULSE_> may be used to set properties.
+This facilitates integration with the Pulse CI tool by Zutubi.
+Read the Pulse documentation for more information about the concepts of Pulse
+properties.
+
+=item command line arguments
+
+Arguments passed to the test script may be used to set properties.
+For example:
+
+ $ ./testscript.pl --qt-configure-args '-nomake demos -nomake examples'
+
+... will set the C<qt.configure.args> property to `-nomake demos -nomake examples'.
+
+=back
+
+Example:
+
+ my $base_dir = $self->property('base.dir', getcwd());
+ my $qt_configure_args = $self->property('qt.configure.args', '-nokia-developer');
+
+ chdir($base_dir);
+ system('./configure', split(/ /, $qt_configure_args));
+
+=cut
+
+
+
+=item B<default_common_property>( PROPERTYNAME )
+
+Get the default value for the property with the given PROPERTYNAME, if any
+is available. Returns undef if no default is available.
+
+There are some properties which are used from many test scripts but are
+rarely set explicitly. This method may be used to ensure that all test
+scripts using these properties will use the same default values.
+
+Some examples of common properties with default values:
+
+=over
+
+=item base.dir
+
+The top-level directory of the source under test; defaults to the current
+working directory.
+
+=item location
+
+Location hint for determining (among other things) which git mirror may be
+used, if any. Default is calculated based on IP address of the current host.
+
+=back
+
+
+
+=item B<set_permitted_properties>( NAME1 => DOC1 [, NAME2 => DOC2, ... ] )
+
+Set the properties which this script is permitted to use, along with their
+documentation.
+
+This method enforces that all properties used by this script are declared
+and documented. The method must be called prior to any call to L<property>.
+
+After the permitted properties have been set, any call to L<property> which
+refers to a property not in this list will cause a fatal error.
+
+The documentation of properties may be used to automatically generate some
+documentation or help messages for test scripts.
+
+Example:
+
+ $self->set_permitted_properties(
+ q{base.dir} => q{top-level source directory},
+ q{configure.args} => q{space-separated arguments to be passed to `configure'},
+ );
+
+ # later ...
+ my @configure_args = split(/ /, $self->property('configure.args'));
+
+
+
+=item B<get_options_from_array>( ARRAYREF [, LIST ] )
+
+Read command-line options from the given ARRAYREF (which would typically be \@ARGV ).
+Most test scripts should call this function as one of the first steps.
+
+The following options are processed:
+
+=over
+
+=item --help
+
+Prints a suitable --help message for the current script, by using
+pod2usage.
+
+=item --verbose
+
+Increments the verbosity setting of the script.
+May be specified more than once.
+
+=item any options passed in LIST
+
+The optional LIST contains Getopt-compatible option specifiers.
+See L<Getopt::Long> for details on the format of LIST.
+
+=item options for any properties set via L<set_permitted_properties>
+
+Every permitted property may be set via the command-line.
+
+The option name is equal to the property name with all . replaced with -.
+For example, if 'base.dir' is a permitted property, it may be set by
+invoking the script with:
+
+ --base-dir /tmp/foo/baz
+
+=back
+
+
+
+=item B<exe>( LIST )
+
+Run an external program, and die if it fails. LIST is interpreted the same way as
+in the builtin L<system> function.
+
+This method is similar to the builtin L<system> function, with the following
+additional features:
+
+=over
+
+=item verbosity
+
+The command is printed before it is run, if the verbosity setting is >= 1.
+
+=item automatic death
+
+If the program does not exit with a 0 exit code, the script will die.
+Similar to the L<autodie> module.
+
+=item properties passed to child script
+
+If the script being called is also a QtQA::TestScript, it will automatically
+get the same values for all properties which are set in the currently
+running script.
+
+=back
+
+
+
+=item B<print_usage>( EXITCODE )
+
+Display a usage message for the current script, then exit with the specified
+exit code.
+
+This function uses pod2usage to print a usage message.
+
+If L<set_permitted_properties> has been called, each property will also be
+printed, along with its documentation.
+
+If L<get_options_from_array> is used, this method will be called when the
+C<--help> option is passed. Therefore there is often no need to call this
+method directly.
+
+
+
+=item B<print_when_verbose>( VERBOSITY, LIST )
+
+Print LIST if and only if the current verbosity is greater than or equal
+to VERBOSITY.
+
+LIST is interpreted the same way as for the L<print> builtin.
+
+Returns a true value if anything was printed, false otherwise.
+
+=back
+
+=cut