3
use lib "$ENV{RQG_HOME}/lib";
10
$SIG{INT} = sub { $ctrl_c = 1 };
11
$SIG{TERM} = sub { exit(0) };
12
$SIG{CHLD} = "IGNORE" if windows();
14
if (defined $ENV{RQG_HOME}) {
15
$ENV{RQG_HOME} = windows() ? $ENV{RQG_HOME}.'\\' : $ENV{RQG_HOME}.'/';
18
use constant PROCESS_TYPE_PARENT => 0;
19
use constant PROCESS_TYPE_PERIODIC => 1;
20
use constant PROCESS_TYPE_CHILD => 2;
26
use GenTest::XML::Report;
27
use GenTest::XML::Test;
28
use GenTest::XML::BuildInfo;
29
use GenTest::Constants;
31
use GenTest::Validator;
32
use GenTest::Generator::FromGrammar;
33
use GenTest::Executor::MySQL;
35
use GenTest::Reporter;
36
use GenTest::ReporterManager;
39
$dsns[0] = my $default_dsn = 'dbi:mysql:host=127.0.0.1:port=9306:user=root:database=test';
40
my $threads = my $default_threads = 10;
41
my $queries = my $default_queries = 1000;
42
my $duration = my $default_duration = 3600;
44
my ($gendata, $engine, $help, $debug, $rpl_mode, $grammar_file, $validators, $reporters, $mask, $rows, $varchar_len, $xml_output, $views, $start_dirty);
47
my @ARGV_saved = @ARGV;
49
my $opt_result = GetOptions(
51
'dsn1=s' => \$dsns[0],
52
'dsn2=s' => \$dsns[1],
53
'engine=s' => \$engine,
54
'gendata:s' => \$gendata,
55
'grammar=s' => \$grammar_file,
56
'threads=i' => \$threads,
57
'queries=i' => \$queries,
58
'duration=i' => \$duration,
61
'rpl_mode=s' => \$rpl_mode,
62
'validators:s' => \$validators,
63
'reporters:s' => \$reporters,
67
'varchar-length=i' => \$varchar_len,
68
'xml-output=s' => \$xml_output,
70
'start-dirty' => \$start_dirty
73
$seed = time() if $seed eq 'time';
75
help() if !$opt_result || $help || not defined $grammar_file;
77
say("Starting \n $0 \\ \n ".join(" \\ \n ", @ARGV_saved));
79
if ((defined $gendata) && (not defined $start_dirty)) {
80
foreach my $dsn (@dsns) {
84
$gendata_result = system("perl $ENV{RQG_HOME}gendata-old.pl --dsn=$dsn ".
85
(defined $engine ? "--engine=$engine" : "").
86
(defined $views ? "--views" : "")
89
$gendata_result = system("perl $ENV{RQG_HOME}gendata.pl --config=$gendata --dsn=$dsn ".
90
(defined $engine ? "--engine=$engine" : "")." ".
91
(defined $seed ? "--seed=$seed" : "")." ".
92
(defined $rows ? "--rows=$rows" : "")." ".
93
(defined $views ? "--views" : "")." ".
94
(defined $varchar_len ? "--varchar-length=$varchar_len" : "")." ");
96
safe_exit ($gendata_result >> 8) if $gendata_result > 0;
100
my $test_start = time();
101
my $test_end = $test_start + $duration;
103
my (@executors, @reporters);
105
if (not defined $reporters) {
106
@reporters = ('ErrorLog', 'Backtrace');
108
@reporters = split(',', $reporters);
111
say("Reporters: ".($#reporters > -1 ? join(', ', @reporters) : "(none)"));
113
my $reporter_manager = GenTest::ReporterManager->new();
115
foreach my $i (0,1) {
116
next if $dsns[$i] eq '';
118
push @executors, GenTest::Executor::MySQL->new(
123
foreach my $reporter (@reporters) {
124
my $add_result = $reporter_manager->addReporter($reporter, {
126
test_start => $test_start,
127
test_end => $test_end,
128
test_duration => $duration
130
exit($add_result) if $add_result > STATUS_OK;
136
if (not defined $validators) {
137
@validators = ('ErrorMessageCorruption');
138
push @validators, 'ResultsetComparator' if $dsns[1] ne '';
139
push @validators, 'ReplicationSlaveStatus' if $rpl_mode ne '';
141
@validators = split(',', $validators);
144
say("Validators: ".($#validators > -1 ? join(', ', @validators) : "(none)"));
146
say("Starting $threads processes, $queries queries each, duration $duration seconds.");
148
my $buildinfo = GenTest::XML::BuildInfo->new(
152
my $test = GenTest::XML::Test->new(
153
id => Time::HiRes::time(),
157
grammar => $grammar_file,
160
validators => join (',', @validators),
161
reporters => join (',', @reporters),
165
'varchar-length' => $varchar_len
169
my $report = GenTest::XML::Report->new(
170
buildinfo => $buildinfo,
178
my $periodic_pid = fork();
179
if ($periodic_pid == 0) {
180
Time::HiRes::sleep(($threads + 1) / 10);
181
say("Started periodic reporting process...");
182
$process_type = PROCESS_TYPE_PERIODIC;
185
foreach my $i (1..$threads) {
186
my $child_pid = fork();
187
if ($child_pid == 0) { # This is a child
188
$process_type = PROCESS_TYPE_CHILD;
191
$child_pids{$child_pid} = 1;
192
$process_type = PROCESS_TYPE_PARENT;
195
Time::HiRes::sleep(0.1); # fork slowly for more predictability
201
if ($process_type == PROCESS_TYPE_PARENT) {
202
# We are the parent process, wait for for all spawned processes to terminate
203
my $children_died = 0;
204
my $total_status = STATUS_OK;
205
my $periodic_died = 0;
207
my $child_pid = wait();
208
my $exit_status = $? > 0 ? ($? >> 8) : 0;
210
$total_status = $exit_status if $exit_status > $total_status;
212
if ($child_pid == $periodic_pid) {
217
delete $child_pids{$child_pid};
220
last if $exit_status >= STATUS_CRITICAL_FAILURE;
221
last if $children_died == $threads;
222
last if $child_pid == -1;
225
foreach my $child_pid (keys %child_pids) {
226
say("Killing child process with pid $child_pid...");
227
kill(15, $child_pid);
230
if ($periodic_died == 0) {
231
# Wait for periodic process to return the status of its last execution
232
Time::HiRes::sleep(1);
233
say("Killing periodic reporting process with pid $periodic_pid...");
234
kill(15, $periodic_pid);
237
# We use sleep() + non-blocking waitpid() due to a bug in ActiveState Perl
238
Time::HiRes::sleep(1);
239
waitpid($periodic_pid, &POSIX::WNOHANG() );
241
waitpid($periodic_pid, 0);
245
my $periodic_status = $? > 0 ? $? >> 8 : 0;
246
$total_status = $periodic_status if $periodic_status > $total_status;
252
if ($total_status == STATUS_OK) {
253
@report_results = $reporter_manager->report(REPORTER_TYPE_SUCCESS | REPORTER_TYPE_ALWAYS);
255
($total_status == STATUS_LENGTH_MISMATCH) ||
256
($total_status == STATUS_CONTENT_MISMATCH)
258
@report_results = $reporter_manager->report(REPORTER_TYPE_DATA);
259
} elsif ($total_status == STATUS_SERVER_CRASHED) {
260
say("Server crash reported, initiating post-crash analysis...");
261
@report_results = $reporter_manager->report(REPORTER_TYPE_CRASH | REPORTER_TYPE_ALWAYS);
262
} elsif ($total_status == STATUS_SERVER_DEADLOCKED) {
263
say("Server deadlock reported, initiating analysis...");
264
@report_results = $reporter_manager->report(REPORTER_TYPE_DEADLOCK | REPORTER_TYPE_ALWAYS);
265
} elsif ($total_status == STATUS_SERVER_KILLED) {
266
@report_results = $reporter_manager->report(REPORTER_TYPE_SERVER_KILLED | REPORTER_TYPE_ALWAYS);
268
@report_results = $reporter_manager->report(REPORTER_TYPE_ALWAYS);
271
my $report_status = shift @report_results;
272
$total_status = $report_status if $report_status > $total_status;
273
$total_status = STATUS_OK if $total_status == STATUS_SERVER_KILLED;
275
foreach my $incident (@report_results) {
276
$test->addIncident($incident);
279
$test->end($total_status == STATUS_OK ? "pass" : "fail");
281
if (defined $xml_output) {
282
open (XML , ">$xml_output") or say("Unable to open $xml_output: $!");
283
print XML $report->xml();
287
if ($total_status == STATUS_OK) {
288
say("Test completed successfully.");
291
say("Test completed with failure status $total_status.");
292
safe_exit($total_status);
294
} elsif ($process_type == PROCESS_TYPE_PERIODIC) {
296
my $reporter_status = $reporter_manager->monitor(REPORTER_TYPE_PERIODIC);
297
exit($reporter_status) if $reporter_status > STATUS_CRITICAL_FAILURE;
300
} elsif ($process_type == PROCESS_TYPE_CHILD) {
301
# We are a child process, execute the desired queries and terminate
303
my $generator = GenTest::Generator::FromGrammar->new(
304
grammar_file => $grammar_file,
305
varchar_length => $varchar_len,
306
seed => $seed eq 'time' ? time() : $seed,
311
exit (STATUS_ENVIRONMENT_FAILURE) if not defined $generator;
313
my $mixer = GenTest::Mixer->new(
314
generator => $generator,
315
executors => \@executors,
316
validators => \@validators
319
exit (STATUS_ENVIRONMENT_FAILURE) if not defined $mixer;
323
foreach my $i (1..$queries) {
324
my $result = $mixer->next();
325
exit($result) if $result > STATUS_CRITICAL_FAILURE;
326
$max_result = $result if $result > $max_result && $result > STATUS_TEST_FAILURE;
327
last if $result == STATUS_EOF;
328
last if $ctrl_c == 1;
329
last if time() > $test_end;
332
if ($max_result > 0) {
333
say("Child process completed with error code $max_result.");
336
say("Child process completed successfully.");
341
die ("Unknown process type $process_type");
348
$0 - Testing via random query generation. Options:
350
--dsn : MySQL DBI resource to connect to (default $default_dsn)
351
--gendata : Execute gendata.pl in order to populate tables with sample data (default NO)
352
--engine : Table engine to use when creating tables with gendata (default: no ENGINE for CREATE TABLE)
353
--threads : Number of threads to spawn (default $default_threads)
354
--queries : Numer of queries to execute per thread (default $default_queries);
355
--duration : Duration of the test in seconds (default $default_duration seconds);
356
--grammar : Grammar file to use for generating the queries (REQUIRED);
357
--help : This help message
358
--debug : Provide debug output