1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
|
#!/usr/bin/env perl
use strict;
use warnings;
=head1 NAME
10-TestScript.t - test QtQA::TestScript module
=cut
use FindBin;
use lib "$FindBin::Bin/../..";
use English qw( -no_match_vars );
use Data::Dumper qw( Dumper );
use IO::CaptureOutput qw( capture );
use Test::Exception;
use Test::Exit;
use Test::More;
use Readonly;
Readonly my $WINDOWS => ($OSNAME =~ m{win32}i);
BEGIN { use_ok 'QtQA::TestScript'; }
#==============================================================================
my %TEST_PERMITTED_PROPERTIES = (
'dog.color' => 'The color of the dog.',
'cat.color' => 'The color of the cat.',
'fish.color'=> 'The color of the fish.',
);
my @TEST_EXE_ARGS1 = (
q{arg1},
q{arg two},
q{arg the third},
q{arg 'with quotes'},
q{arg "with dquotes"},
q{arg with $shell ^meta %characters%},
);
my $TEST_EXE_ARGS1_DUMP = Dumper(\@TEST_EXE_ARGS1);
# Test that `property' will die in various ways
sub test_property_death
{
my $script = QtQA::TestScript->new;
# Should die if called before set_permitted_properties
throws_ok { $script->property('foo.bar' ) } qr/set_permitted_properties/;
throws_ok { $script->property('foo.bar', 'def') } qr/set_permitted_properties/;
$script->set_permitted_properties(%TEST_PERMITTED_PROPERTIES);
# Should die if property is not permitted
throws_ok { $script->property('bird.color' ) } qr/did not declare it as a permitted property/;
throws_ok { $script->property('bird.color', 'def') } qr/did not declare it as a permitted property/;
# Should die if property is permitted, but is not defined and has no default
throws_ok { $script->property('dog.color' ) } qr/default/;
return;
}
# Test that `property' will use the `default' parameter appropriately
sub test_property_get_defaults
{
my $script = QtQA::TestScript->new;
$script->set_permitted_properties(%TEST_PERMITTED_PROPERTIES);
# Should be able to get defaults
my $value;
lives_ok { $value = $script->property('dog.color', 'black') };
is($value, 'black');
lives_ok { $value = $script->property('cat.color', 'white') };
is($value, 'white');
# A repeated call should return the same value (cached)
lives_ok { $value = $script->property('dog.color', 'red') };
is($value, 'black');
lives_ok { $value = $script->property('dog.color') };
is($value, 'black');
return;
}
# Test that `property' will get the property values from environment variables
sub test_property_get_from_env
{
my $script = QtQA::TestScript->new;
$script->set_permitted_properties(%TEST_PERMITTED_PROPERTIES);
my $value;
{
local $ENV{PULSE_DOG_COLOR} = 'grey'; # old style, PULSE
local $ENV{QTQA_CAT_COLOR} = 'light blue'; # new style, QTQA
lives_ok { $value = $script->property('dog.color', 'black') }; # default is ignored
is($value, 'grey');
lives_ok { $value = $script->property('cat.color') };
is($value, 'light blue');
# And verify that QTQA takes precedence over PULSE
local $ENV{QTQA_FISH_COLOR} = 'silver';
local $ENV{PULSE_FISH_COLOR} = 'gold';
lives_ok { $value = $script->property('fish.color') };
is($value, 'silver');
}
# A repeated call should return the same value (cached),
# even though the ENV vars are no longer set
lives_ok { $value = $script->property('dog.color', 'red') }; # default is ignored
is($value, 'grey');
lives_ok { $value = $script->property('cat.color') };
is($value, 'light blue');
lives_ok { $value = $script->property('fish.color') };
is($value, 'silver');
return;
}
# Test that `property' will get the properties from command-line arguments
sub test_property_get_from_args
{
my $script = QtQA::TestScript->new;
$script->set_permitted_properties(%TEST_PERMITTED_PROPERTIES);
my @args = ('--dog-color', 'green', '--cat-color', 'dark red');
$script->get_options_from_array(\@args);
my $value;
lives_ok { $value = $script->property('dog.color', 'black') }; # default is ignored
is($value, 'green');
lives_ok { $value = $script->property('cat.color') };
is($value, 'dark red');
return;
}
# Trivial test of default_common_property function
sub test_default_common_property
{
my $script = QtQA::TestScript->new;
# Here, we basically test that default common properties returns
# anything at all for a known property, and nothing for an unknown property.
# We don't attempt to test the actual value returned (since the whole point
# of this function is that the value may be unpredictable).
ok( $script->default_common_property('base.dir'), 'base.dir has a default' );
is( $script->default_common_property('fake.property'), undef, 'fake.property has no default' );
return;
}
# Test interaction between `print_verbose' and `--verbose' command-line option
sub test_verbosity
{
my $script = QtQA::TestScript->new;
my $stdout;
my $stderr;
capture { $script->print_when_verbose(0, "hello", "world") } \$stdout, \$stderr;
is( $stdout, "helloworld" );
is( $stderr, "" );
capture { $script->print_when_verbose(-10, "negative") } \$stdout, \$stderr;
is( $stdout, "negative" );
is( $stderr, "" );
capture { $script->print_when_verbose(1, "should not print") } \$stdout, \$stderr;
is( $stdout, "" );
is( $stderr, "" );
# Should set verbosity to 2
$script->get_options_from_array(['--verbose', '--verbose']);
capture { $script->print_when_verbose(1, "should print") } \$stdout, \$stderr;
is( $stdout, "should print" );
is( $stderr, "" );
capture { $script->print_when_verbose(2, "again print") } \$stdout, \$stderr;
is( $stdout, "again print" );
is( $stderr, "" );
capture { $script->print_when_verbose(3, "silent") } \$stdout, \$stderr;
is( $stdout, "" );
is( $stderr, "" );
return;
}
# Basic test of command-line parsing
sub test_get_options_from_array
{
my $script = QtQA::TestScript->new;
# `--help' is tested by 20-TestScript-autodocs.t
# `--verbose' is tested by test_verbosity
# passing arguments for properties is tested by test_property_get_from_args
# Here, we're just testing that our arguments are passed to getopt unmolested.
my $value1;
my $value2;
my @args = (
'--option-one', 'A string',
'--option-two',
'something else',
);
$script->get_options_from_array(\@args,
'option-one=s' => \$value1,
'option-two' => \$value2,
);
is( $value1, 'A string' );
is( $value2, 1 );
is( scalar(@args), 1 ); # non-parsed option is left behind
is( $args[0], 'something else' );
return;
}
# Test that exe logs, dies and passes arguments correctly
sub test_exe
{
my $script = QtQA::TestScript->new;
# Any non-zero exit code should make the script die
local $? = 0;
dies_ok( sub { $script->exe('/bin/false') }, 'non-zero exit code implies death' );
isnt( $?, 0, 'non-zero exit code is passed through $?' );
my $stdout;
my $stderr;
my @good_cmd = (
'perl',
'-e',
'use Data::Dumper; print Dumper(\@ARGV);',
@TEST_EXE_ARGS1,
);
my $expected_log = "+ @good_cmd\n$TEST_EXE_ARGS1_DUMP";
# We invoke a subprocess which uses Data::Dumper to print out all
# arguments. This is a simple way to check unambiguously what args
# were received.
local $? = 1;
lives_ok( sub { capture { $script->exe(@good_cmd) } \$stdout, \$stderr },
'successful command lives');
is( $?, 0, 'zero exit code is passed through $?' );
TODO: {
local $TODO = 'fix or document argument passing on Windows' if $WINDOWS;
is( $stdout, $expected_log, 'exe passes arguments correctly' );
}
is( $stderr, q{}, 'no unexpected warnings or stderr' );
# If verbose, should print out the command being run
$script->get_options_from_array(['--verbose']);
lives_ok( sub { capture { $script->exe(@good_cmd) } \$stdout, \$stderr },
'successful command lives (verbose)');
# exe should print out one line before running the command, like this:
# + cmd with each arg separated by space
# Note it currently does not attempt to print args with whitespace unambiguously
TODO: {
local $TODO = 'fix or document argument passing on Windows' if $WINDOWS;
is( $stdout, $expected_log, 'exe logs correctly' );
}
is( $stderr, q{}, 'no unexpected warnings or stderr' );
# verify that an exit status is passed into $? correctly
my @exit12_cmd = (
'perl',
'-e',
'exit(12)',
);
local $? = 0;
dies_ok( sub { $script->exe(@exit12_cmd) }, 'exit(12) implies death' );
is( $?, (12 << 8), 'correct exit code is passed through $?' );
return;
}
# Test that exe_qx works like exe and also captures output.
sub test_exe_qx
{
my $script = QtQA::TestScript->new;
# Any non-zero exit code should make the script die
local $? = 0;
dies_ok( sub { $script->exe_qx('/bin/false') }, 'non-zero exit code implies death' );
isnt( $?, 0, 'non-zero exit code is passed through $?' );
# Note that there are two sets of output: the output from the child process
# (which we expect to be returned), and the output from this process
# (which is empty in the non-verbose case, and should never be returned).
# From child process:
my $stdout;
my $stderr;
my $merged;
# From this process:
my $log_stdout;
my $log_stderr;
my $test_stdout = $TEST_EXE_ARGS1_DUMP;
my $test_stderr = "Some stderr\n";
my $test_merged = $test_stdout.$test_stderr;
my @good_cmd = (
'perl',
'-e',
'$|++; use Data::Dumper; print Dumper(\@ARGV); print STDERR qq{Some stderr\n};',
@TEST_EXE_ARGS1,
);
local $? = 1;
lives_ok(
sub {
capture { ($stdout, $stderr) = $script->exe_qx(@good_cmd) } \$log_stdout, \$log_stderr;
capture { $merged = $script->exe_qx(@good_cmd) }; # discard any log output
},
'successful command lives'
);
is( $?, 0, 'zero exit code is passed through $?' );
TODO: {
local $TODO = 'fix or document argument passing on Windows' if $WINDOWS;
is( $stdout, $test_stdout, 'exe_qx passes arguments correctly' );
is( $merged, $test_merged, 'merged output OK' );
}
is( $stderr, $test_stderr, 'stderr OK' );
ok( !$log_stdout, 'no log output (verbose1)' );
ok( !$log_stderr, 'no log error (verbose1)' );
# If verbose 1, command will be logged before it is run.
$script->get_options_from_array(['--verbose']);
lives_ok(
sub {
capture { ($stdout, $stderr) = $script->exe_qx(@good_cmd) } \$log_stdout, \$log_stderr;
capture { $merged = $script->exe_qx(@good_cmd) }; # discard any log output
},
'successful command lives (verbose1)'
);
is( $stderr, $test_stderr, 'stderr OK (verbose1)' );
TODO: {
local $TODO = 'fix or document argument passing on Windows' if $WINDOWS;
is( $stdout, $test_stdout, 'exe_qx passes arguments correctly (verbose1)' );
is( $merged, $test_merged, 'merged output OK (verbose1)' );
}
is( $log_stdout, "qx @good_cmd\n", 'log output OK (verbose1)' );
ok( !$log_stderr, 'no log error (verbose1)' );
# If verbose 2, command will be logged before it is run, and stdout/stderr is logged
# after it is run. We need to test the log for merged vs non-merged independently here.
$script->get_options_from_array(['--verbose']);
my $log_stdout_merged;
my $log_stderr_merged;
my $expected_log_stdout = "qx @good_cmd\n" . <<'EOF';
qx stdout:
$VAR1 = [
'arg1',
'arg two',
'arg the third',
'arg \'with quotes\'',
'arg "with dquotes"',
'arg with $shell ^meta %characters%'
];
qx stderr:
Some stderr
EOF
my $expected_log_stdout_merged = "qx @good_cmd\n" . <<'EOF';
qx stdout & stderr:
$VAR1 = [
'arg1',
'arg two',
'arg the third',
'arg \'with quotes\'',
'arg "with dquotes"',
'arg with $shell ^meta %characters%'
];
Some stderr
EOF
lives_ok(
sub {
capture { ($stdout, $stderr) = $script->exe_qx(@good_cmd) }
\$log_stdout, \$log_stderr;
capture { $merged = $script->exe_qx(@good_cmd) }
\$log_stdout_merged, \$log_stderr_merged;
},
'successful command lives (verbose2)'
);
is( $stderr, $test_stderr, 'stderr OK (verbose2)' );
ok( !$log_stderr, 'no log error (verbose2)' );
ok( !$log_stderr_merged, 'no log error (verbose2, merged)' );
TODO: {
local $TODO = 'fix or document argument passing on Windows' if $WINDOWS;
is( $stdout, $test_stdout, 'exe_qx passes arguments correctly (verbose2)' );
is( $merged, $test_merged, 'merged output OK (verbose2)' );
is( $log_stdout, $expected_log_stdout, 'log output OK (verbose2)' );
is( $log_stdout_merged, $expected_log_stdout_merged, 'log output OK (verbose2, merged)' );
}
# verify that an exit status is passed into $? correctly
my @exit12_cmd = (
'perl',
'-e',
'exit(12)',
);
local $? = 0;
dies_ok( sub { $script->exe_qx(@exit12_cmd) }, 'exit(12) implies death' );
is( $?, (12 << 8), 'correct exit code is passed through $?' );
return;
}
sub test_fatal_error
{
my $script = QtQA::TestScript->new;
throws_ok {
$script->fatal_error(
"Error occurred while making sandwich:\n"
."Somebody left the fridge door open all weekend\n"
);
} qr{
# Note it is OK to have some stuff before and after the YAML.
# However, the header must always be on its own line and the
# footer must always be followed by a newline.
(?:\n|\A)
\Q--- !qtqa.qt-project.org/error
message: |
Error occurred while making sandwich:
Somebody left the fridge door open all weekend
... \E\#\Q end qtqa.qt-project.org/error\E
(?:\n|\z)
}xms, 'fatal_error output looks OK';
return;
}
sub test_fail
{
my $script = QtQA::TestScript->new;
throws_ok {
$script->fail(
"Error occurred while making sandwich:\n"
."Somebody left the fridge door open all weekend\n"
);
} qr{
(?:\n|\A)
\Q--- !qtqa.qt-project.org/failure
message: |
Error occurred while making sandwich:
Somebody left the fridge door open all weekend
... \E\#\Q end qtqa.qt-project.org/failure\E
(?:\n|\z)
}xms, 'fail output looks OK';
return;
}
sub test_doing
{
my $script = QtQA::TestScript->new;
my $make_expected = sub {
my ($type, $message, @scopes) = @_;
@scopes = reverse @scopes;
return
"--- !qtqa.qt-project.org/$type\nmessage: $message\n"
.(@scopes
? "while:\n - "
.join("\n - ", @scopes)
."\n"
: q{}
)
."... # end qtqa.qt-project.org/$type\n";
};
my $expected_error_for_scopes = sub { return $make_expected->( 'error', @_ ) };
my $expected_failure_for_scopes = sub { return $make_expected->( 'failure', @_ ) };
{
my $outer = $script->doing( 'outer1' );
{
my $inner1 = $script->doing( 'inner1' );
my $expected = $expected_error_for_scopes->( 'quux', 'outer1', 'inner1' );
throws_ok { $script->fatal_error( 'quux' ) } qr{\A\Q$expected\E}, 'two scopes';
}
my $expected = $expected_failure_for_scopes->( 'bar', 'outer1' );
throws_ok { $script->fail( 'bar' ) } qr{\A\Q$expected\E}, 'one scope';
}
my $expected = $expected_error_for_scopes->( 'baz' );
throws_ok { $script->fatal_error( 'baz' ) } qr{\A\Q$expected\E}, 'no scope';
return;
}
# Run all the tests
sub run_test
{
test_property_death;
test_property_get_defaults;
test_property_get_from_env;
test_property_get_from_args;
test_default_common_property;
test_verbosity;
test_get_options_from_array;
test_exe;
test_exe_qx;
test_fatal_error;
test_fail;
test_doing;
return;
}
#==============================================================================
if (!caller) {
run_test;
done_testing;
}
1;
|