# MyTestHelpers.pm -- my shared test script helpers
# Copyright 2008, 2009, 2010, 2011, 2012, 2015, 2017, 2018 Kevin Ryde
# MyTestHelpers.pm is shared by several distributions.
#
# MyTestHelpers.pm is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# MyTestHelpers.pm 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 General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this file. If not, see <http://www.gnu.org/licenses/>.
BEGIN { require 5 }
package MyTestHelpers;
use strict;
# Don't want to load Exporter here since that could hide a problem of a
# module missing a "use Exporter". Though Test.pm and Test::More (via
# Test::Builder::Module) both use it anyway.
#
# use Exporter;
# use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
# @ISA = ('Exporter');
# @EXPORT_OK = qw(findrefs
# main_iterations
# warn_suppress_gtk_icon
# glib_gtk_versions
# any_signal_connections
# nowarnings);
# %EXPORT_TAGS = (all => \@EXPORT_OK);
sub DEBUG { 0 }
#-----------------------------------------------------------------------------
{
my $warning_count;
my $stacktraces;
my $stacktraces_count = 0;
sub nowarnings_handler {
my ($msg) = @_;
# don't error out for cpan alpha version number warnings
unless (defined $msg
&& $msg =~ /^Argument "[0-9._]+" isn't numeric in numeric gt/) {
$warning_count++;
if ($stacktraces_count < 3 && eval { require Devel::StackTrace }) {
$stacktraces_count++;
$stacktraces .= "\n" . Devel::StackTrace->new->as_string() . "\n";
}
}
warn @_;
}
sub nowarnings {
$SIG{'__WARN__'} = \&nowarnings_handler;
}
END {
if ($warning_count) {
MyTestHelpers::diag ("Saw $warning_count warning(s):");
if (defined $stacktraces) {
MyTestHelpers::diag ($stacktraces);
} else {
MyTestHelpers::diag('(Devel::StackTrace not available for backtrace)');
}
MyTestHelpers::diag ('Exit code 1 for warnings');
$? = 1;
}
}
}
sub diag {
if (do { local $@; eval { Test::More->can('diag') }}) {
Test::More::diag (@_);
} else {
my $msg = join('', map {defined($_)?$_:'[undef]'} @_)."\n";
$msg =~ s/^/# /mg;
print STDERR $msg;
}
}
sub dump {
my ($thing) = @_;
if (eval { require Data::Dumper; 1 }) {
MyTestHelpers::diag (Data::Dumper::Dumper ($thing));
} else {
MyTestHelpers::diag ("Data::Dumper not available");
}
}
#-----------------------------------------------------------------------------
# Test::Weaken and other weaking
sub findrefs {
my ($obj) = @_;
defined $obj or return;
require Scalar::Util;
if (ref $obj && Scalar::Util::reftype($obj) eq 'HASH') {
MyTestHelpers::diag ("Keys: ",
join(' ',
map {"$_=".(defined $obj->{$_}
? "$obj->{$_}" : '[undef]')}
keys %$obj));
}
if (eval { require Devel::FindRef }) {
MyTestHelpers::diag (Devel::FindRef::track($obj, 8));
} else {
MyTestHelpers::diag ("Devel::FindRef not available -- ", $@);
}
}
sub test_weaken_show_leaks {
my ($leaks) = @_;
$leaks || return;
my $unfreed = $leaks->unfreed_proberefs;
my $unfreed_count = scalar(@$unfreed);
MyTestHelpers::diag ("Test-Weaken leaks $unfreed_count objects");
MyTestHelpers::dump ($leaks);
my $proberef;
foreach $proberef (@$unfreed) {
MyTestHelpers::diag (" unfreed ", $proberef);
}
foreach $proberef (@$unfreed) {
MyTestHelpers::diag ("search ", $proberef);
MyTestHelpers::findrefs($proberef);
}
}
#-----------------------------------------------------------------------------
# Gtk/Glib helpers
# Gtk 2.16 can go into a hard loop on events_pending() / main_iteration_do()
# if dbus is not running, or something like that. In any case limiting the
# iterations is good for test safety.
#
sub main_iterations {
my $count = 0;
if (DEBUG) { MyTestHelpers::diag ("main_iterations() ..."); }
while (Gtk2->events_pending) {
$count++;
Gtk2->main_iteration_do (0);
if ($count >= 500) {
MyTestHelpers::diag ("main_iterations(): oops, bailed out after $count events/iterations");
return;
}
}
MyTestHelpers::diag ("main_iterations(): ran $count events/iterations");
}
# warn_suppress_gtk_icon() is a $SIG{__WARN__} handler which suppresses spam
# from Gtk trying to make you buy the hi-colour icon theme. Eg,
#
# {
# local $SIG{'__WARN__'} = \&MyTestHelpers::warn_suppress_gtk_icon;
# $something = SomeThing->new;
# }
#
sub warn_suppress_gtk_icon {
my ($message) = @_;
unless ($message =~ /Gtk-WARNING.*icon/
|| $message =~ /\Qrecently-used.xbel/
) {
warn @_;
}
}
sub glib_gtk_versions {
my $gtk1_loaded = Gtk->can('init');
my $gtk2_loaded = Gtk2->can('init');
my $glib_loaded = Glib->can('get_home_dir');
if ($gtk1_loaded) {
MyTestHelpers::diag ("Perl-Gtk1 version ",Gtk->VERSION);
}
if ($gtk2_loaded) {
MyTestHelpers::diag ("Perl-Gtk2 version ",Gtk2->VERSION);
}
if ($glib_loaded) { # when loaded
MyTestHelpers::diag ("Perl-Glib version ",Glib->VERSION);
MyTestHelpers::diag ("Compiled against Glib version ",
Glib::MAJOR_VERSION(), ".",
Glib::MINOR_VERSION(), ".",
Glib::MICRO_VERSION(), ".");
MyTestHelpers::diag ("Running on Glib version ",
Glib::major_version(), ".",
Glib::minor_version(), ".",
Glib::micro_version(), ".");
}
if ($gtk2_loaded) {
MyTestHelpers::diag ("Compiled against Gtk version ",
Gtk2::MAJOR_VERSION(), ".",
Gtk2::MINOR_VERSION(), ".",
Gtk2::MICRO_VERSION(), ".");
MyTestHelpers::diag ("Running on Gtk version ",
Gtk2::major_version(), ".",
Gtk2::minor_version(), ".",
Gtk2::micro_version(), ".");
}
if ($gtk1_loaded) {
MyTestHelpers::diag ("Running on Gtk version ",
Gtk->major_version(), ".",
Gtk->minor_version(), ".",
Gtk->micro_version(), ".");
}
}
# Return true if there's any signal handlers connected to $obj.
#
# Signal IDs are from 1 up, don't pass 0 to signal_handler_is_connected()
# since in Glib 2.4.1 it spits out a g_log() error.
#
sub any_signal_connections {
my ($obj) = @_;
my @connected = grep {$obj->signal_handler_is_connected ($_)} (1 .. 500);
if (@connected) {
my $connected = join(',',@connected);
MyTestHelpers::diag ("$obj signal handlers connected: $connected");
return $connected;
}
return undef;
}
# wait for $signame to be emitted on $widget, with a timeout
sub wait_for_event {
my ($widget, $signame) = @_;
if (DEBUG) { MyTestHelpers::diag ("wait_for_event() $signame on ",$widget); }
my $done = 0;
my $got_event = 0;
my $sig_id = $widget->signal_connect
($signame => sub {
if (DEBUG) { MyTestHelpers::diag ("wait_for_event() $signame received"); }
$done = 1;
return 0; # Gtk2::EVENT_PROPAGATE (new in Gtk2 1.220)
});
my $timer_id = Glib::Timeout->add
(30_000, # 30 seconds
sub {
$done = 1;
MyTestHelpers::diag ("wait_for_event() oops, timeout waiting for $signame on ",$widget);
return 1; # Glib::SOURCE_CONTINUE (new in Glib 1.220)
});
if ($widget->can('get_display')) {
# display new in Gtk 2.2
$widget->get_display->sync;
} else {
# in Gtk 2.0 gdk_flush() is a sync actually
Gtk2::Gdk->flush;
}
my $count = 0;
while (! $done) {
if (DEBUG >= 2) { MyTestHelpers::diag ("wait_for_event() iteration $count"); }
Gtk2->main_iteration;
$count++;
}
MyTestHelpers::diag ("wait_for_event(): '$signame' ran $count events/iterations\n");
$widget->signal_handler_disconnect ($sig_id);
Glib::Source->remove ($timer_id);
}
#-----------------------------------------------------------------------------
# X11::Protocol helpers
sub X11_chosen_screen_number {
my ($X) = @_;
my $i;
foreach $i (0 .. $#{$X->{'screens'}}) {
if ($X->{'screens'}->[$i]->{'root'} == $X->{'root'}) {
return $i;
}
}
die "Oops, current screen not found";
}
sub X11_server_info {
my ($X) = @_;
MyTestHelpers::diag("");
MyTestHelpers::diag("X server info");
MyTestHelpers::diag("vendor: ",$X->{'vendor'});
MyTestHelpers::diag("release_number: ",$X->{'release_number'});
MyTestHelpers::diag("protocol_major_version: ",$X->{'protocol_major_version'});
MyTestHelpers::diag("protocol_minor_version: ",$X->{'protocol_minor_version'});
MyTestHelpers::diag("byte_order: ",$X->{'byte_order'});
MyTestHelpers::diag("num screens: ",scalar(@{$X->{'screens'}}));
MyTestHelpers::diag("width_in_pixels: ",$X->{'width_in_pixels'});
MyTestHelpers::diag("height_in_pixels: ",$X->{'height_in_pixels'});
MyTestHelpers::diag("width_in_millimeters: ",$X->{'width_in_millimeters'});
MyTestHelpers::diag("height_in_millimeters: ",$X->{'height_in_millimeters'});
MyTestHelpers::diag("root_visual: ",$X->{'root_visual'});
my $visual_info = $X->{'visuals'}->{$X->{'root_visual'}};
MyTestHelpers::diag(" depth: ",$visual_info->{'depth'});
MyTestHelpers::diag(" class: ",$visual_info->{'class'},
' ', $X->interp('VisualClass', $visual_info->{'class'}));
MyTestHelpers::diag(" colormap_entries: ",$visual_info->{'colormap_entries'});
MyTestHelpers::diag(" bits_per_rgb_value: ",$visual_info->{'bits_per_rgb_value'});
MyTestHelpers::diag(" red_mask: ",sprintf('%#X',$visual_info->{'red_mask'}));
MyTestHelpers::diag(" green_mask: ",sprintf('%#X',$visual_info->{'green_mask'}));
MyTestHelpers::diag(" blue_mask: ",sprintf('%#X',$visual_info->{'blue_mask'}));
MyTestHelpers::diag("ima"."ge_byte_order: ",$X->{'ima'.'ge_byte_order'},
' ', $X->interp('Significance', $X->{'ima'.'ge_byte_order'}));
MyTestHelpers::diag("black_pixel: ",sprintf('%#X',$X->{'black_pixel'}));
MyTestHelpers::diag("white_pixel: ",sprintf('%#X',$X->{'white_pixel'}));
foreach (0 .. $#{$X->{'screens'}}) {
if ($X->{'screens'}->[$_]->{'root'} == $X->{'root'}) {
MyTestHelpers::diag("chosen screen: $_");
}
}
MyTestHelpers::diag("");
}
1;
__END__