3
# Copyright (c) 2008,2011 Oracle and/or its affiliates. All rights reserved.
4
# Use is subject to license terms.
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; version 2 of the License.
10
# This program is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
# General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20
package GenTest::App::GenTest;
30
use GenTest::Properties;
31
use GenTest::Constants;
32
use GenTest::App::Gendata;
33
use GenTest::App::GendataSimple;
34
use GenTest::IPC::Channel;
35
use GenTest::IPC::Process;
36
use GenTest::ErrorFilter;
41
use GenTest::XML::Report;
42
use GenTest::XML::Test;
43
use GenTest::XML::BuildInfo;
44
use GenTest::XML::Transporter;
45
use GenTest::Constants;
47
use GenTest::Validator;
48
use GenTest::Executor;
50
use GenTest::Reporter;
51
use GenTest::ReporterManager;
52
use GenTest::Filter::Regexp;
53
use GenTest::Incident;
55
use constant PROCESS_TYPE_PARENT => 0;
56
use constant PROCESS_TYPE_PERIODIC => 1;
57
use constant PROCESS_TYPE_CHILD => 2;
59
use constant GT_CONFIG => 0;
64
my $self = $class->SUPER::new({
65
'config' => GT_CONFIG},@_);
67
croak ("Need config") if not defined $self->config;
73
return $_[0]->[GT_CONFIG];
82
$SIG{INT} = sub { $ctrl_c = 1 };
83
$SIG{TERM} = sub { exit(0) };
84
$SIG{CHLD} = "IGNORE" if osWindows();
86
if (defined $ENV{RQG_HOME}) {
87
$ENV{RQG_HOME} = osWindows() ? $ENV{RQG_HOME}.'\\' : $ENV{RQG_HOME}.'/';
90
my $seed = $self->config->seed;
91
if ($seed eq 'time') {
93
say("Converting --seed=time to --seed=$seed");
96
$ENV{RQG_DEBUG} = 1 if $self->config->debug;
98
my $queries = $self->config->queries;
99
$queries =~ s{K}{000}so;
100
$queries =~ s{M}{000000}so;
102
say("-------------------------------\nConfiguration");
103
$self->config->printProps;
105
if ((defined $self->config->gendata) &&
106
(not defined $self->config->property('start-dirty'))) {
107
foreach my $dsn (@{$self->config->dsn}) {
111
if ($self->config->gendata eq '') {
112
$datagen = GenTest::App::GendataSimple->new(dsn => $dsn,
113
views => $self->config->views,
114
engine => $self->config->engine,
115
sqltrace=> $self->config->sqltrace,
116
notnull => $self->config->notnull);
118
$datagen = GenTest::App::Gendata->new(spec_file => $self->config->gendata,
120
engine => $self->config->engine,
122
debug => $self->config->debug,
123
rows => $self->config->rows,
124
views => $self->config->views,
125
varchar_length => $self->config->property('varchar-length'),
126
sqltrace => $self->config->sqltrace,
127
notnull => $self->config->notnull);
129
$gendata_result = $datagen->run();
131
return $gendata_result if $gendata_result > STATUS_OK;
135
my $test_start = time();
136
my $test_end = $test_start + $self->config->duration;
138
my $generator_name = "GenTest::Generator::".$self->config->generator;
139
say("Loading Generator $generator_name.");
140
eval("use $generator_name");
145
if ($generator_name eq 'GenTest::Generator::FromGrammar') {
146
$grammar = GenTest::Grammar->new(
147
grammar_file => $self->config->grammar
148
) if defined $self->config->grammar;
150
return STATUS_ENVIRONMENT_FAILURE if not defined $grammar;
152
if (defined $self->config->redefine) {
153
my $patch_grammar = GenTest::Grammar->new(
154
grammar_file => $self->config->redefine);
155
$grammar = $grammar->patch($patch_grammar);
158
return STATUS_ENVIRONMENT_FAILURE if not defined $grammar;
161
my $channel = GenTest::IPC::Channel->new();
164
foreach my $i (0..2) {
165
next if $self->config->dsn->[$i] eq '';
166
my $executor = GenTest::Executor->newFromDSN($self->config->dsn->[$i], osWindows() ? undef : $channel);
167
$executor->sqltrace($self->config->sqltrace);
168
$executor->setId($i+1);
169
push @executors, $executor;
170
if ($executor->type() == DB_MYSQL) {
171
my $metadata_executor = GenTest::Executor->newFromDSN($self->config->dsn->[$i], osWindows() ? undef : $channel);
172
$metadata_executor->init();
173
$metadata_executor->cacheMetaData() if defined $metadata_executor->dbh();
177
my $drizzle_only = $executors[0]->type == DB_DRIZZLE;
178
$drizzle_only = $drizzle_only && $executors[1]->type == DB_DRIZZLE if $#executors > 0;
180
my $mysql_only = $executors[0]->type == DB_MYSQL;
181
$mysql_only = $mysql_only && $executors[1]->type == DB_MYSQL if $#executors > 0;
183
if (not defined $self->config->reporters or $#{$self->config->reporters} < 0) {
184
$self->config->reporters([]);
185
if ($mysql_only || $drizzle_only) {
186
$self->config->reporters(['ErrorLog', 'Backtrace']);
187
push @{$self->config->reporters}, 'ValgrindXMLErrors' if (defined $self->config->property('valgrind-xml'));
188
push @{$self->config->reporters}, 'ReplicationConsistency' if $self->config->rpl_mode ne '';
191
## Remove the "None" reporter
192
foreach my $i (0..$#{$self->config->reporters}) {
193
delete $self->config->reporters->[$i]
194
if $self->config->reporters->[$i] eq "None"
195
or $self->config->reporters->[$i] eq '';
199
say("Reporters: ".($#{$self->config->reporters} > -1 ? join(', ', @{$self->config->reporters}) : "(none)"));
201
my $reporter_manager = GenTest::ReporterManager->new();
203
if ($mysql_only || $drizzle_only ) {
204
foreach my $i (0..2) {
205
next if $self->config->dsn->[$i] eq '';
206
foreach my $reporter (@{$self->config->reporters}) {
207
my $add_result = $reporter_manager->addReporter($reporter, {
208
dsn => $self->config->dsn->[$i],
209
test_start => $test_start,
210
test_end => $test_end,
211
test_duration => $self->config->duration,
212
properties => $self->config
214
return $add_result if $add_result > STATUS_OK;
219
if (not defined $self->config->validators or $#{$self->config->validators} < 0) {
220
$self->config->validators([]);
221
push(@{$self->config->validators}, 'ErrorMessageCorruption')
222
if ($mysql_only || $drizzle_only);
223
if ($self->config->dsn->[2] ne '') {
224
push @{$self->config->validators}, 'ResultsetComparator3';
225
} elsif ($self->config->dsn->[1] ne '') {
226
push @{$self->config->validators}, 'ResultsetComparator';
229
push @{$self->config->validators}, 'ReplicationSlaveStatus'
230
if $self->config->rpl_mode ne '' && ($mysql_only || $drizzle_only);
231
push @{$self->config->validators}, 'MarkErrorLog'
232
if (defined $self->config->valgrind) && ($mysql_only || $drizzle_only);
234
push @{$self->config->validators}, 'QueryProperties'
235
if defined $grammar && $grammar->hasProperties() && ($mysql_only || $drizzle_only);
237
## Remove the "None" validator
238
foreach my $i (0..$#{$self->config->validators}) {
239
delete $self->config->validators->[$i]
240
if $self->config->validators->[$i] eq "None"
241
or $self->config->validators->[$i] eq '';
244
## Add the transformer validator if --transformers is
246
if (defined $self->config->transformers and
247
$#{$self->config->transformers} >= 0) {
249
push @{$self->config->validators}, 'Transformer';
252
say("Validators: ".($self->config->validators and $#{$self->config->validators} > -1 ? join(', ', @{$self->config->validators}) : "(none)"));
254
say("Transformers: ".join(', ', @{$self->config->transformers}))
255
if $self->config->transformers and $#{$self->config->transformers} > -1;
259
$filter_obj = GenTest::Filter::Regexp->new( file => $self->config->filter )
260
if defined $self->config->filter;
262
say("Starting ".$self->config->threads." processes, ".
263
$self->config->queries." queries each, duration ".
264
$self->config->duration." seconds.");
267
if (defined $self->config->property('xml-output')) {
268
$buildinfo = GenTest::XML::BuildInfo->new(
269
dsns => $self->config->dsn
274
# Define test suite name for reporting purposes.
275
# Until we support test suites and/or reports with multiple suites/tests,
276
# we use the test name as test suite name, from config option "testname".
277
# Default test name is the basename portion of the grammar file name.
278
# If a grammar file is not given, the default is "rqg_no_name".
279
my $test_suite_name = $self->config->testname;
280
if (not defined $test_suite_name) {
281
if (defined $self->config->grammar) {
282
$test_suite_name = basename($self->config->grammar, '.yy');
284
$test_suite_name = "rqg_no_name";
288
my $test = GenTest::XML::Test->new(
290
name => $test_suite_name, # NOTE: Consider changing to test (or test case) name when suites are supported.
292
engine => $self->config->engine,
293
gendata => $self->config->gendata,
294
grammar => $self->config->grammar,
295
threads => $self->config->threads,
296
queries => $self->config->queries,
297
validators => join (',', @{$self->config->validators}),
298
reporters => join (',', @{$self->config->reporters}),
300
mask => $self->config->mask,
301
mask_level => $self->config->property('mask-level'),
302
rows => $self->config->rows,
303
'varchar-length' => $self->config->property('varchar-length')
307
my $report = GenTest::XML::Report->new(
308
buildinfo => $buildinfo,
309
name => $test_suite_name, # NOTE: name here refers to the name of the test suite or "test".
313
### Start central reporting thread ####
315
my $errorfilter = GenTest::ErrorFilter->new(channel=>$channel);
316
my $errorfilter_p = GenTest::IPC::Process->new(object=>$errorfilter);
318
$errorfilter_p->start();
321
### Start children ###
327
my $periodic_pid = fork();
328
if ($periodic_pid == 0) {
329
Time::HiRes::sleep(($self->config->threads + 1) / 10);
330
say("Started periodic reporting process...");
331
$process_type = PROCESS_TYPE_PERIODIC;
334
foreach my $i (1..$self->config->threads) {
335
my $child_pid = fork();
337
if ($child_pid == 0) { # This is a child
338
$process_type = PROCESS_TYPE_CHILD;
341
$child_pids{$child_pid} = 1;
342
$process_type = PROCESS_TYPE_PARENT;
345
Time::HiRes::sleep(0.1); # fork slowly for more predictability
354
if ($process_type == PROCESS_TYPE_PARENT) {
359
## Important that this is done here in the parent after the last
360
## fork since on windows Process.pm uses threads
361
$errorfilter_p->start();
363
# We are the parent process, wait for for all spawned processes to terminate
364
my $children_died = 0;
365
my $total_status = STATUS_OK;
366
my $periodic_died = 0;
368
## Parent thread does not use channel
372
my $child_pid = wait();
373
my $exit_status = $? > 0 ? ($? >> 8) : 0;
375
$total_status = $exit_status if $exit_status > $total_status;
377
if ($child_pid == $periodic_pid) {
382
delete $child_pids{$child_pid};
385
last if $exit_status >= STATUS_CRITICAL_FAILURE;
386
last if $children_died == $self->config->threads;
387
last if $child_pid == -1;
390
foreach my $child_pid (keys %child_pids) {
391
say("Killing child process with pid $child_pid...");
392
kill(15, $child_pid);
395
if ($periodic_died == 0) {
396
# Wait for periodic process to return the status of its last execution
397
Time::HiRes::sleep(1);
398
say("Killing periodic reporting process with pid $periodic_pid...");
399
kill(15, $periodic_pid);
402
# We use sleep() + non-blocking waitpid() due to a bug in ActiveState Perl
403
Time::HiRes::sleep(1);
404
waitpid($periodic_pid, &POSIX::WNOHANG() );
406
waitpid($periodic_pid, 0);
410
my $periodic_status = $? > 0 ? $? >> 8 : 0;
411
$total_status = $periodic_status if $periodic_status > $total_status;
415
$errorfilter_p->kill();
419
if ($total_status == STATUS_OK) {
420
@report_results = $reporter_manager->report(REPORTER_TYPE_SUCCESS | REPORTER_TYPE_ALWAYS);
422
($total_status == STATUS_LENGTH_MISMATCH) ||
423
($total_status == STATUS_CONTENT_MISMATCH)
425
@report_results = $reporter_manager->report(REPORTER_TYPE_DATA | REPORTER_TYPE_ALWAYS);
426
} elsif ($total_status == STATUS_SERVER_CRASHED) {
427
say("Server crash reported, initiating post-crash analysis...");
428
@report_results = $reporter_manager->report(REPORTER_TYPE_CRASH | REPORTER_TYPE_ALWAYS);
429
} elsif ($total_status == STATUS_SERVER_DEADLOCKED) {
430
say("Server deadlock reported, initiating analysis...");
431
@report_results = $reporter_manager->report(REPORTER_TYPE_DEADLOCK | REPORTER_TYPE_ALWAYS);
432
} elsif ($total_status == STATUS_SERVER_KILLED) {
433
@report_results = $reporter_manager->report(REPORTER_TYPE_SERVER_KILLED | REPORTER_TYPE_ALWAYS);
435
@report_results = $reporter_manager->report(REPORTER_TYPE_ALWAYS);
438
my $report_status = shift @report_results;
439
$total_status = $report_status if $report_status > $total_status;
440
$total_status = STATUS_OK if $total_status == STATUS_SERVER_KILLED;
442
foreach my $incident (@report_results) {
443
$test->addIncident($incident);
446
# If no Reporters reported an incident, and we have a test failure,
447
# create an incident report and add it to the test report.
448
if ((scalar(@report_results) < 1) && ($total_status != STATUS_OK)) {
449
my $unreported_incident = GenTest::Incident->new(
450
result => 'fail', # can we have other results as incidents?
451
description => 'Non-zero status code from RQG test run',
452
signature => 'Exit status '.$total_status # better than nothing?
454
# Add the incident to the test report
455
$test->addIncident($unreported_incident);
458
$test->end($total_status == STATUS_OK ? "pass" : "fail");
460
if (defined $self->config->property('xml-output')) {
461
open (XML , '>'.$self->config->property('xml-output')) or carp("Unable to open ".$self->config->property('xml-output').": $!");
462
print XML $report->xml();
464
say("XML report written to ". $self->config->property('xml-output'));
467
# XML Result reporting to Test Tool (TT).
468
# Currently both --xml-output=<filename> and --report-xml-tt must be
469
# set to trigger this.
470
if (defined $self->config->property('report-xml-tt')) {
471
my $xml_transporter = GenTest::XML::Transporter->new(
472
type => $self->config->property('report-xml-tt-type')
474
# If xml-output option is not set, bail out. TODO: Make xml-output optional.
475
if (not defined $self->config->property('xml-output')) {
476
carp("ERROR: --xml-output=<filename> must be set when using --report-xml-tt");
478
my $result = $xml_transporter->sendXML(
479
$self->config->property('xml-output'),
480
$self->config->property('report-xml-tt-dest')
482
if ($result != STATUS_OK) {
483
croak("Error from XML Transporter: $result");
487
if ($total_status == STATUS_OK) {
488
say("Test completed successfully.");
491
say("Test completed with failure status ".status2text($total_status)." ($total_status)");
492
return $total_status;
494
} elsif ($process_type == PROCESS_TYPE_PERIODIC) {
495
## Periodic does not use channel
498
local $SIG{TERM} = sub { $killed = 1 };
501
my $reporter_status = $reporter_manager->monitor(REPORTER_TYPE_PERIODIC);
502
$self->stop_child($reporter_status) if $reporter_status > STATUS_CRITICAL_FAILURE;
503
last if $killed == 1;
506
$self->stop_child(STATUS_OK);
507
} elsif ($process_type == PROCESS_TYPE_CHILD) {
509
# We are a child process, execute the desired queries and terminate
511
my $generator_obj = $generator_name->new(
513
varchar_length => $self->config->property('varchar-length'),
516
mask => $self->config->mask,
517
mask_level => $self->config->property('mask-level')
520
$self->stop_child(STATUS_ENVIRONMENT_FAILURE) if not defined $generator_obj;
522
my $mixer = GenTest::Mixer->new(
523
generator => $generator_obj,
524
executors => \@executors,
525
validators => $self->config->validators,
526
properties => $self->config,
527
filters => defined $filter_obj ? [ $filter_obj ] : undef
530
$self->stop_child(STATUS_ENVIRONMENT_FAILURE) if not defined $mixer;
534
foreach my $i (1..$queries) {
535
my $result = $mixer->next();
536
if ($result > STATUS_CRITICAL_FAILURE) {
537
undef $mixer; # so that destructors are called
538
$self->stop_child($result);
541
$max_result = $result if $result > $max_result && $result > STATUS_TEST_FAILURE;
542
last if $result == STATUS_EOF;
543
last if $ctrl_c == 1;
544
last if time() > $test_end;
547
for my $ex (@executors) {
552
# Forcefully deallocate the Mixer so that Validator destructors are called
556
if ($max_result > 0) {
557
say("Child process completed with error code $max_result.");
558
$self->stop_child($max_result);
560
say("Child process completed successfully.");
561
$self->stop_child(STATUS_OK);
564
croak ("Unknown process type $process_type");
570
my ($self, $status) = @_;
572
die "calling stop_child() without a \$status" if not defined $status;