~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to gentest.pl

initial import from internal tree

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
use lib 'lib';
 
3
use lib "$ENV{RQG_HOME}/lib";
 
4
use strict;
 
5
use GenTest;
 
6
 
 
7
$| = 1;
 
8
my $ctrl_c = 0;
 
9
 
 
10
$SIG{INT} = sub { $ctrl_c = 1 };
 
11
$SIG{TERM} = sub { exit(0) };
 
12
$SIG{CHLD} = "IGNORE" if windows();
 
13
 
 
14
if (defined $ENV{RQG_HOME}) {
 
15
        $ENV{RQG_HOME} = windows() ? $ENV{RQG_HOME}.'\\' : $ENV{RQG_HOME}.'/';
 
16
}
 
17
 
 
18
use constant PROCESS_TYPE_PARENT        => 0;
 
19
use constant PROCESS_TYPE_PERIODIC      => 1;
 
20
use constant PROCESS_TYPE_CHILD         => 2;
 
21
 
 
22
use POSIX;
 
23
use Getopt::Long;
 
24
use Time::HiRes;
 
25
 
 
26
use GenTest::XML::Report;
 
27
use GenTest::XML::Test;
 
28
use GenTest::XML::BuildInfo;
 
29
use GenTest::Constants;
 
30
use GenTest::Result;
 
31
use GenTest::Validator;
 
32
use GenTest::Generator::FromGrammar;
 
33
use GenTest::Executor::MySQL;
 
34
use GenTest::Mixer;
 
35
use GenTest::Reporter;
 
36
use GenTest::ReporterManager;
 
37
 
 
38
my @dsns;
 
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;
 
43
 
 
44
my ($gendata, $engine, $help, $debug, $rpl_mode, $grammar_file, $validators, $reporters, $mask, $rows, $varchar_len, $xml_output, $views, $start_dirty);
 
45
my $seed = 1;
 
46
 
 
47
my @ARGV_saved = @ARGV;
 
48
 
 
49
my $opt_result = GetOptions(
 
50
        'dsn=s' => \$dsns[0],
 
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,
 
59
        'help' => \$help,
 
60
        'debug' => \$debug,
 
61
        'rpl_mode=s' => \$rpl_mode,
 
62
        'validators:s' => \$validators,
 
63
        'reporters:s' => \$reporters,
 
64
        'seed=s' => \$seed,
 
65
        'mask=i' => \$mask,
 
66
        'rows=i' => \$rows,
 
67
        'varchar-length=i' => \$varchar_len,
 
68
        'xml-output=s' => \$xml_output,
 
69
        'views' => \$views,
 
70
        'start-dirty' => \$start_dirty
 
71
);
 
72
 
 
73
$seed = time() if $seed eq 'time';
 
74
 
 
75
help() if !$opt_result || $help || not defined $grammar_file;
 
76
 
 
77
say("Starting \n $0 \\ \n ".join(" \\ \n ", @ARGV_saved));
 
78
 
 
79
if ((defined $gendata) && (not defined $start_dirty)) {
 
80
        foreach my $dsn (@dsns) {
 
81
                next if $dsn eq '';
 
82
                my $gendata_result;
 
83
                if ($gendata eq '') {
 
84
                        $gendata_result = system("perl $ENV{RQG_HOME}gendata-old.pl --dsn=$dsn ".
 
85
                                (defined $engine ? "--engine=$engine" : "").
 
86
                                (defined $views ? "--views" : "")
 
87
                        );
 
88
                } else {
 
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" : "")." ");
 
95
                }
 
96
                safe_exit ($gendata_result >> 8) if $gendata_result > 0;
 
97
        }
 
98
}
 
99
 
 
100
my $test_start = time();
 
101
my $test_end = $test_start + $duration;
 
102
 
 
103
my (@executors, @reporters);
 
104
 
 
105
if (not defined $reporters) {
 
106
        @reporters = ('ErrorLog', 'Backtrace');
 
107
} else {
 
108
        @reporters = split(',', $reporters);
 
109
}
 
110
 
 
111
say("Reporters: ".($#reporters > -1 ? join(', ', @reporters) : "(none)"));
 
112
 
 
113
my $reporter_manager = GenTest::ReporterManager->new();
 
114
 
 
115
foreach my $i (0,1) {
 
116
        next if $dsns[$i] eq '';
 
117
 
 
118
        push @executors, GenTest::Executor::MySQL->new(
 
119
                dsn => $dsns[$i],
 
120
                debug => $debug
 
121
        );
 
122
 
 
123
        foreach my $reporter (@reporters) {
 
124
                my $add_result = $reporter_manager->addReporter($reporter, {
 
125
                        dsn             => $dsns[$i],
 
126
                        test_start      => $test_start,
 
127
                        test_end        => $test_end,
 
128
                        test_duration   => $duration
 
129
                } );
 
130
                exit($add_result) if $add_result > STATUS_OK;
 
131
        }
 
132
}
 
133
 
 
134
my @validators;
 
135
 
 
136
if (not defined $validators) {
 
137
        @validators = ('ErrorMessageCorruption');
 
138
        push @validators, 'ResultsetComparator' if $dsns[1] ne '';
 
139
        push @validators, 'ReplicationSlaveStatus' if $rpl_mode ne '';
 
140
} else {
 
141
        @validators = split(',', $validators);
 
142
}
 
143
 
 
144
say("Validators: ".($#validators > -1 ? join(', ', @validators) : "(none)"));
 
145
 
 
146
say("Starting $threads processes, $queries queries each, duration $duration seconds.");
 
147
 
 
148
my $buildinfo = GenTest::XML::BuildInfo->new(
 
149
        dsns => \@dsns
 
150
);
 
151
 
 
152
my $test = GenTest::XML::Test->new(
 
153
        id => Time::HiRes::time(),
 
154
        attributes => {
 
155
                engine => $engine,
 
156
                gendata => $gendata,
 
157
                grammar => $grammar_file,
 
158
                threads => $threads,
 
159
                queries => $queries,
 
160
                validators => join (',', @validators),
 
161
                reporters => join (',', @reporters),
 
162
                seed => $seed,
 
163
                mask => $mask,
 
164
                rows => $rows,
 
165
                'varchar-length' => $varchar_len
 
166
        }
 
167
);
 
168
 
 
169
my $report = GenTest::XML::Report->new(
 
170
        buildinfo => $buildinfo,
 
171
        tests => [ $test ]
 
172
);
 
173
 
 
174
my $process_type;
 
175
my %child_pids;
 
176
my $id = 1;
 
177
 
 
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;
 
183
        $id = 0;
 
184
} else {
 
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;
 
189
                        last;
 
190
                } else {
 
191
                        $child_pids{$child_pid} = 1;
 
192
                        $process_type = PROCESS_TYPE_PARENT;
 
193
                        $seed++;
 
194
                        $id++;
 
195
                        Time::HiRes::sleep(0.1);        # fork slowly for more predictability
 
196
                        next;
 
197
                }
 
198
        }
 
199
}
 
200
 
 
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;
 
206
        while (1) {
 
207
                my $child_pid = wait();
 
208
                my $exit_status = $? > 0 ? ($? >> 8) : 0;
 
209
 
 
210
                $total_status = $exit_status if $exit_status > $total_status;
 
211
 
 
212
                if ($child_pid == $periodic_pid) {
 
213
                        $periodic_died = 1;
 
214
                        last;
 
215
                } else {
 
216
                        $children_died++;
 
217
                        delete $child_pids{$child_pid};
 
218
                }
 
219
 
 
220
                last if $exit_status >= STATUS_CRITICAL_FAILURE;
 
221
                last if $children_died == $threads;
 
222
                last if $child_pid == -1;
 
223
        }
 
224
 
 
225
        foreach my $child_pid (keys %child_pids) {
 
226
                say("Killing child process with pid $child_pid...");
 
227
                kill(15, $child_pid);
 
228
        }
 
229
 
 
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);
 
235
 
 
236
                if (windows()) {
 
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() );
 
240
                } else {
 
241
                        waitpid($periodic_pid, 0);
 
242
                }
 
243
 
 
244
                if ($? > -1 ) {
 
245
                        my $periodic_status = $? > 0 ? $? >> 8 : 0;
 
246
                        $total_status = $periodic_status if $periodic_status > $total_status;
 
247
                }
 
248
        }
 
249
 
 
250
        my @report_results;
 
251
 
 
252
        if ($total_status == STATUS_OK) {
 
253
                @report_results = $reporter_manager->report(REPORTER_TYPE_SUCCESS | REPORTER_TYPE_ALWAYS);
 
254
        } elsif (
 
255
                ($total_status == STATUS_LENGTH_MISMATCH) ||
 
256
                ($total_status == STATUS_CONTENT_MISMATCH)
 
257
        ) {
 
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);
 
267
        } else {
 
268
                @report_results = $reporter_manager->report(REPORTER_TYPE_ALWAYS);
 
269
        }
 
270
 
 
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;
 
274
 
 
275
        foreach my $incident (@report_results) {
 
276
                $test->addIncident($incident);
 
277
        }
 
278
 
 
279
        $test->end($total_status == STATUS_OK ? "pass" : "fail");
 
280
 
 
281
        if (defined $xml_output) {
 
282
                open (XML , ">$xml_output") or say("Unable to open $xml_output: $!");
 
283
                print XML $report->xml();
 
284
                close XML;
 
285
        }
 
286
 
 
287
        if ($total_status == STATUS_OK) {
 
288
                say("Test completed successfully.");
 
289
                safe_exit(0);
 
290
        } else {
 
291
                say("Test completed with failure status $total_status.");
 
292
                safe_exit($total_status);
 
293
        }
 
294
} elsif ($process_type == PROCESS_TYPE_PERIODIC) {
 
295
        while (1) {
 
296
                my $reporter_status = $reporter_manager->monitor(REPORTER_TYPE_PERIODIC);
 
297
                exit($reporter_status) if $reporter_status > STATUS_CRITICAL_FAILURE;
 
298
                sleep(10);
 
299
        }
 
300
} elsif ($process_type == PROCESS_TYPE_CHILD) {
 
301
        # We are a child process, execute the desired queries and terminate
 
302
 
 
303
        my $generator = GenTest::Generator::FromGrammar->new(
 
304
                grammar_file => $grammar_file,
 
305
                varchar_length => $varchar_len,
 
306
                seed => $seed eq 'time' ? time() : $seed, 
 
307
                thread_id => $id,
 
308
                mask => $mask
 
309
        );
 
310
 
 
311
        exit (STATUS_ENVIRONMENT_FAILURE) if not defined $generator;
 
312
 
 
313
        my $mixer = GenTest::Mixer->new(
 
314
                generator => $generator,
 
315
                executors => \@executors,
 
316
                validators => \@validators
 
317
        );
 
318
 
 
319
        exit (STATUS_ENVIRONMENT_FAILURE) if not defined $mixer;
 
320
 
 
321
        my $max_result;
 
322
 
 
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;
 
330
        }
 
331
 
 
332
        if ($max_result > 0) {
 
333
                say("Child process completed with error code $max_result.");
 
334
                exit($max_result);
 
335
        } else {
 
336
                say("Child process completed successfully.");
 
337
                exit(0);
 
338
        }
 
339
 
 
340
} else {
 
341
        die ("Unknown process type $process_type");
 
342
}
 
343
 
 
344
sub help {
 
345
 
 
346
        print <<EOF
 
347
 
 
348
        $0 - Testing via random query generation. Options:
 
349
 
 
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
 
359
EOF
 
360
        ;
 
361
        safe_exit(1);
 
362
}