GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

BrowserBundleTests.pm 32.5 KB
Newer Older
1
package TBBTestSuite::TestSuite::BrowserBundleTests;
2 3 4

use warnings;
use strict;
boklm's avatar
boklm committed
5 6 7

use parent 'TBBTestSuite::TestSuite';

8 9 10 11 12 13 14
use English;
use FindBin;
use File::Slurp;
use File::Spec;
use File::Find;
use File::Type;
use File::Copy;
15
use File::Temp;
16 17 18
use JSON;
use Digest::SHA qw(sha256_hex);
use LWP::UserAgent;
boklm's avatar
boklm committed
19
use IO::CaptureOutput qw(capture_exec);
20
use TBBTestSuite::Common qw(exit_error winpath clone_strip_coderef screenshot_thumbnail);
21 22
use TBBTestSuite::Options qw($options);
use TBBTestSuite::Tests::VirusTotal qw(virustotal_run);
23
use TBBTestSuite::Tests::Command qw(command_run);
24
use TBBTestSuite::Tests::TorBootstrap;
boklm's avatar
boklm committed
25
use TBBTestSuite::XServer qw(start_X stop_X set_Xmode);
26

boklm's avatar
boklm committed
27 28 29
sub test_types {
    return {
        tor_bootstrap => \&TBBTestSuite::Tests::TorBootstrap::start_tor,
30
        marionette    => \&marionette_run,
boklm's avatar
boklm committed
31 32 33 34
        virustotal    => \&virustotal_run,
        command       => \&command_run,
    };
}
35

boklm's avatar
boklm committed
36 37 38
sub type {
    'browserbundle';
}
39

boklm's avatar
boklm committed
40 41 42
sub description {
    'Tor Browser Bundle integration tests';
}
43

44 45
our @tests = (
    {
boklm's avatar
boklm committed
46 47 48 49 50 51 52 53 54
        name            => 'readelf_RELRO',
        fail_type       => 'warning',
        type            => 'command',
        descr           => 'Check if binaries are RELocation Read-Only',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-ld' ],
        check_output    => sub { ( $_[0] =~ m/GNU_RELRO/ )
                                 && ( $_[0] =~ m/BIND_NOW/ ) },
        enable          => sub { $OSNAME eq 'linux' },
55 56
    },
    {
boklm's avatar
boklm committed
57 58 59 60 61 62 63 64
        name            => 'readelf_stack_canary',
        fail_type       => 'warning',
        type            => 'command',
        descr           => 'Check for stack canary support',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-s' ],
        check_output    => sub { $_[0] =~ m/__stack_chk_fail/ },
        enable          => sub { $OSNAME eq 'linux' },
65 66
    },
    {
boklm's avatar
boklm committed
67 68 69 70 71 72 73
        name            => 'readelf_NX',
        type            => 'command',
        descr           => 'Check for NX support',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-W', '-l' ],
        check_output    => sub { ! ($_[0] =~ m/GNU_STACK.+RWE/) },
        enable          => sub { $OSNAME eq 'linux' },
74 75
    },
    {
boklm's avatar
boklm committed
76 77 78 79 80 81 82
        name            => 'readelf_PIE',
        type            => 'command',
        descr           => 'Check for PIE support',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-h' ],
        check_output    => sub { $_[0] =~ m/Type:\s+DYN/ },
        enable          => sub { $OSNAME eq 'linux' },
83 84
    },
    {
boklm's avatar
boklm committed
85 86 87 88 89 90 91 92
        name            => 'readelf_no_rpath',
        fail_type       => 'warning',
        type            => 'command',
        descr           => 'Check for no rpath',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-d' ],
        check_output    => sub { ! ( $_[0] =~ m/RPATH/ ) },
        enable          => sub { $OSNAME eq 'linux' },
93 94
    },
    {
boklm's avatar
boklm committed
95 96 97 98 99 100 101
        name            => 'readelf_no_runpath',
        type            => 'command',
        descr           => 'Check for no runpath',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-d' ],
        check_output    => sub { ! ( $_[0] =~ m/runpath/ ) },
        enable          => sub { $OSNAME eq 'linux' },
102
    },
boklm's avatar
boklm committed
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
    {
        name            => 'otool_PIE',
        type            => 'command',
        descr           => 'Check for PIE support',
        files           => \&tbb_osx_executable_files,
        command         => [ 'otool', '-hv' ],
        check_output    => sub {
            my @lines = split("\n", $_[0]);
            my $last_line = pop @lines;
            my ($flags) = $last_line =~ m/^\s*[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+(.*)/;
            my %flags = map { $_ => 1 } split(/\s+/, $flags);
            return $flags{PIE};
        },
        enable          => sub { $OSNAME eq 'darwin' },
        retry           => 1,
    },
119
    {
boklm's avatar
boklm committed
120 121 122 123 124
        name            => 'tor_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
125
        run_once        => 1,
126 127
    },
    {
boklm's avatar
boklm committed
128 129 130
        name            => 'tor_bridge',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using a bridge',
131
        enable          => sub { $OSNAME eq 'linux' },
132
        run_once        => 1,
133 134
    },
    {
boklm's avatar
boklm committed
135 136 137 138
        name            => 'tor_bridge_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using a bridge and an http proxy',
        httpproxy       => 1,
139
        enable          => sub { $OSNAME eq 'linux' },
140
        run_once        => 1,
141 142
    },
    {
boklm's avatar
boklm committed
143 144 145 146
        name            => 'tor_obfs3',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs3',
        enable          => sub { $OSNAME eq 'linux' },
147
        run_once        => 1,
148 149
    },
    {
boklm's avatar
boklm committed
150 151 152 153 154
        name            => 'tor_obfs3_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs3 and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
155
        run_once        => 1,
156
    },
157
    {
boklm's avatar
boklm committed
158 159 160 161
        name            => 'tor_obfs4',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs4',
        enable          => sub { $OSNAME eq 'linux' && $_[0]->{version} !~ m/^4.0/ },
162
        run_once        => 1,
163 164
    },
    {
boklm's avatar
boklm committed
165 166 167 168 169
        name            => 'tor_obfs4_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs4 and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' && $_[0]->{version} !~ m/^4.0/ },
170
        run_once        => 1,
171
    },
172
    {
boklm's avatar
boklm committed
173 174 175 176
        name            => 'tor_fte',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using fteproxy',
        enable          => sub { $OSNAME eq 'linux' },
177
        run_once        => 1,
178 179
    },
    {
boklm's avatar
boklm committed
180 181 182 183 184
        name            => 'tor_fte_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using fteproxy and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
185
        run_once        => 1,
186
    },
187 188 189 190 191
    {
        name            => 'tor_scramblesuit',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using scramblesuit',
        enable          => sub { $OSNAME eq 'linux' },
192
        run_once        => 1,
193 194 195 196 197 198 199
    },
    {
        name            => 'tor_scramblesuit_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using scramblesuit and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
200
        run_once        => 1,
201
    },
202 203 204 205 206
    {
        name            => 'tor_meek-amazon',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using meek-amazon',
        enable          => sub { $OSNAME eq 'linux' },
207
        run_once        => 1,
208 209 210 211 212 213
    },
    {
        name            => 'tor_meek-azure',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using meek-azure',
        enable          => sub { $OSNAME eq 'linux' },
214
        run_once        => 1,
215
    },
216
    {
boklm's avatar
boklm committed
217 218 219 220 221
        name            => 'tor_bootstrap',
        type            => 'tor_bootstrap',
        descr           => 'Check that we can bootstrap tor',
        fail_type       => 'fatal',
        no_kill         => 1,
222 223 224
        use_default_config => 1,
    },
    {
boklm's avatar
boklm committed
225
        name            => 'screenshots',
226
        type            => 'marionette',
boklm's avatar
boklm committed
227
        descr           => 'Take some screenshots',
228 229
    },
    {
boklm's avatar
boklm committed
230
        name            => 'check',
231
        type            => 'marionette',
boklm's avatar
boklm committed
232 233
        use_net         => 1,
        descr           => 'Check that http://check.torproject.org/ think we are using tor',
234 235
    },
    {
boklm's avatar
boklm committed
236
        name            => 'https-everywhere',
237
        type            => 'marionette',
boklm's avatar
boklm committed
238 239
        use_net         => 1,
        descr           => 'Check that https everywhere is enabled and working',
240 241
    },
    {
boklm's avatar
boklm committed
242
        name            => 'https-everywhere-disabled',
243 244
        marionette_test => 'https-everywhere',
        type            => 'marionette',
boklm's avatar
boklm committed
245 246 247 248
        descr           => 'Check that https everywhere is not doing anything when disabled',
        use_net         => 1,
        pre             => sub { toggle_https_everywhere($_[0], 0) },
        post            => sub { toggle_https_everywhere($_[0], 1) },
249 250
    },
    {
boklm's avatar
boklm committed
251
        name            => 'settings',
252
        type            => 'marionette',
boklm's avatar
boklm committed
253
        descr           => 'Check that some important settings are correctly set',
254
    },
boklm's avatar
boklm committed
255
    {
boklm's avatar
boklm committed
256
        name            => 'acid3',
boklm's avatar
boklm committed
257
        type            => 'marionette',
boklm's avatar
boklm committed
258 259
        descr           => 'acid3 tests',
        use_net         => 1,
boklm's avatar
boklm committed
260
        retry           => 4,
boklm's avatar
boklm committed
261 262
        # the acid3 test is disabled for now
        enable          => sub { 0; },
boklm's avatar
boklm committed
263
    },
boklm's avatar
boklm committed
264
    {
boklm's avatar
boklm committed
265
        name            => 'slider_settings_1',
266 267
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
268 269 270 271 272
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 1,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
273 274
    },
    {
boklm's avatar
boklm committed
275
        name            => 'slider_settings_2',
276 277
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
278 279 280 281 282
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 2,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
283 284
    },
    {
boklm's avatar
boklm committed
285
        name            => 'slider_settings_3',
286 287
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
288 289 290 291 292
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 3,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
293 294
    },
    {
boklm's avatar
boklm committed
295
        name            => 'slider_settings_4',
296 297
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
298 299 300 301 302
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 4,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
303
    },
boklm's avatar
boklm committed
304
    {
boklm's avatar
boklm committed
305
        name            => 'dom-objects-enumeration',
306
        type            => 'marionette',
boklm's avatar
boklm committed
307
        descr           => 'Check the list of DOM Objects exposed in the global namespace',
boklm's avatar
boklm committed
308
    },
309 310 311 312 313
    {
        name            => 'dom-objects-enumeration-worker',
        type            => 'marionette',
        descr           => 'Check the list of DOM Objects exposed in a Worker context',
    },
314
    {
boklm's avatar
boklm committed
315
        name            => 'navigation-timing',
316
        type            => 'marionette',
boklm's avatar
boklm committed
317 318
        descr           => 'Check that the Navigation Timing API is really disabled',
        use_net         => 1,
319
    },
320
    {
boklm's avatar
boklm committed
321
        name            => 'resource-timing',
322
        type            => 'marionette',
323
        descr           => 'Check that the Resource Timing API is really disabled',
boklm's avatar
boklm committed
324
        use_net         => 1,
325 326
        # To check that the test fails when resource timing is enabled,
        # uncomment this:
327 328
        #prefs           => {
        #    'dom.enable_resource_timing' => 'true',
329 330 331 332 333 334 335 336 337 338
        #},
    },
    {
        name            => 'user-timing',
        type            => 'marionette',
        descr           => 'Check that the User Timing API is really disabled',
        use_net         => 1,
        # To check that the test fails when user timing is enabled,
        # uncomment this:
        #prefs           => {
339
        #    'dom.enable_user_timing' => 'true',
340
        #},
341
    },
342 343 344 345 346 347 348 349 350 351 352 353 354 355
    {
        name            => 'user-timing-worker',
        type            => 'marionette',
        marionette_test => 'page',
        remote          => 0,
        timeout         => 500,
        descr           => 'Check that the User Timing API in Worker context is really disabled',
        use_net         => 1,
        # To check that the test fails when user timing is enabled,
        # uncomment this:
        #prefs           => {
        #    'dom.enable_user_timing' => 'true',
        #},
    },
356 357 358 359 360 361 362 363 364 365 366
    {
        name            => 'performance-observer',
        type            => 'marionette',
        descr           => 'Check that the Performance Observer API is really disabled',
        use_net         => 1,
        # To check that the test fails when performance observer is enabled,
        # uncomment this:
        #prefs           => {
        #    'dom.enable_performance_observer' => 'true',
        #},
    },
367
    {
boklm's avatar
boklm committed
368
        name            => 'searchengines',
369
        type            => 'marionette',
boklm's avatar
boklm committed
370
        descr           => 'Check that we have the default search engines set',
371
    },
boklm's avatar
boklm committed
372
    {
boklm's avatar
boklm committed
373
        name            => 'noscript',
374
        type            => 'marionette',
boklm's avatar
boklm committed
375 376 377
        descr           => 'Check that noscript options are working',
        use_net         => 1,
        prefs           => {
378
            'extensions.torbutton.security_slider' => 2,
boklm's avatar
boklm committed
379
        },
boklm's avatar
boklm committed
380
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
381
    },
boklm's avatar
boklm committed
382
    {
boklm's avatar
boklm committed
383
        name            => 'fp_screen_dimensions',
384
        type            => 'marionette',
boklm's avatar
boklm committed
385
        descr           => 'Check that screen dimensions are spoofed correctly',
386 387
    },
    {
boklm's avatar
boklm committed
388
        name            => 'fp_screen_coords',
389
        type            => 'marionette',
boklm's avatar
boklm committed
390
        descr           => 'Check that screenX, screenY, screenLeft, screenTop, mozInnerScreenX, mozInnerScreenY are 0',
391 392
    },
    {
boklm's avatar
boklm committed
393
        name            => 'fp_plugins',
394
        type            => 'marionette',
boklm's avatar
boklm committed
395
        descr           => 'Check that plugins are disabled',
396 397
    },
    {
boklm's avatar
boklm committed
398
        name            => 'fp_useragent',
399
        type            => 'marionette',
boklm's avatar
boklm committed
400
        descr           => 'Check that userAgent is as expected',
401 402
    },
    {
boklm's avatar
boklm committed
403
        name            => 'fp_navigator',
404
        type            => 'marionette',
boklm's avatar
boklm committed
405 406 407 408
        descr           => 'Check that navigator properties are as expected',
    },
    {
        name            => 'play_videos',
409
        type            => 'marionette',
boklm's avatar
boklm committed
410 411
        descr           => 'Play some videos',
        use_net         => 1,
412
        marionette_test => 'page',
boklm's avatar
boklm committed
413 414 415 416 417
        remote          => 1,
        timeout         => 50000,
    },
    {
        name            => 'svg-disable',
boklm's avatar
boklm committed
418
        type            => 'marionette',
boklm's avatar
boklm committed
419
        descr           => 'Check if disabling svg is working',
boklm's avatar
boklm committed
420
        marionette_test => 'svg',
boklm's avatar
boklm committed
421 422
        use_net         => 1,
        prefs           => {
423 424 425
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'false',
        },
boklm's avatar
boklm committed
426
        enable          => sub { $OSNAME eq 'linux' },
427 428
    },
    {
boklm's avatar
boklm committed
429
        name            => 'svg-enable',
boklm's avatar
boklm committed
430
        type            => 'marionette',
boklm's avatar
boklm committed
431
        descr           => 'Check if enabling svg is working',
boklm's avatar
boklm committed
432
        marionette_test => 'svg',
boklm's avatar
boklm committed
433 434
        use_net         => 1,
        prefs           => {
435 436 437
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'true',
        },
boklm's avatar
boklm committed
438
        enable          => sub { $OSNAME eq 'linux' },
439
    },
440 441 442 443 444 445
    {
        name            => 'download_pdf',
        type            => 'marionette',
        descr           => 'Check if download of PDF is working (#19402)',
        use_net         => 1,
    },
boklm's avatar
boklm committed
446 447 448 449 450 451 452
    {
        name            => 'pinning_now',
        type            => 'marionette',
        descr           => 'Check if static public key pinning is working (#20149)',
        marionette_test => 'pinning',
        use_net         => 1,
    },
453 454 455 456 457 458
);

sub toggle_https_everywhere {
    my ($tbbinfos, $t) = @_;
    my $prefs = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere@eff.org/defaults/preferences/preferences.js';
459 460 461
    my $prefs_eff = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere-eff@eff.org/defaults/preferences/preferences.js';
    $prefs = $prefs_eff unless -f $prefs;
462 463 464 465 466 467 468 469 470 471 472 473 474
    my @f = read_file($prefs);
    foreach (@f) {
        if ($t) {
            s/pref\("extensions\.https_everywhere\.globalEnabled",false\);
             /pref("extensions.https_everywhere.globalEnabled",true);/x;
        } else {
            s/pref\("extensions\.https_everywhere\.globalEnabled",true\);
             /pref("extensions.https_everywhere.globalEnabled",false);/x;
        }
    }
    write_file($prefs, @f);
}

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
sub set_test_prefs {
    my ($tbbinfos, $t) = @_;
    return unless $t->{prefs};
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    copy $prefs, "$prefs.backup";
    my $new_prefs = '';
    foreach my $prefname (sort keys %{$t->{prefs}}) {
        $new_prefs .= "pref(\"$prefname\", $t->{prefs}{$prefname});\n";
    }
    write_file($prefs, {append => 1}, $new_prefs);
}

sub reset_test_prefs {
    my ($tbbinfos, $t) = @_;
    return unless $t->{prefs};
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    move "$prefs.backup", $prefs;
}

boklm's avatar
boklm committed
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
sub set_slider_mode {
    my ($tbbinfos, $t) = @_;
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    copy $prefs, "$prefs.slider_backup";
    write_file($prefs, {append => 1},
      'pref("extensions.torbutton.security_custom", false);' . "\n" .
      "pref(\"extensions.torbutton.security_slider\", $t->{slider_mode});\n");
}

sub reset_slider_mode {
    my ($tbbinfos, $t) = @_;
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    move "$prefs.slider_backup", $prefs;
}

509 510 511
sub tbb_binfiles {
    my ($tbbinfos, $test) = @_;
    return $tbbinfos->{binfiles} if $tbbinfos->{binfiles};
512
    my %binfiles;
513 514 515 516
    my %wanted_types = (
        'application/x-executable-file' => 1,
        'application/x-ms-dos-executable' => 1,
    );
517 518 519
    my $wanted = sub {
        return unless -f $File::Find::name;
        my $type = File::Type->new->checktype_filename($File::Find::name);
520
        return unless $wanted_types{$type};
boklm's avatar
boklm committed
521 522 523
        my $name = $File::Find::name;
        $name =~ s/^$tbbinfos->{tbbdir}\///;
        $binfiles{$name} = 1;
524 525 526 527 528
    };
    find($wanted, $tbbinfos->{tbbdir});
    return $tbbinfos->{binfiles} = [ keys %binfiles ];
}

boklm's avatar
boklm committed
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
sub tbb_osx_executable_files {
    my ($tbbinfos, $test) = @_;
    return $tbbinfos->{osx_executable_files} if $tbbinfos->{osx_executable_files};
    my %exec_files;
    my $wanted = sub {
        return unless -f $File::Find::name;
        $ENV{LC_ALL}= 'C';
        my ($out, $err, $success) = capture_exec('otool', '-hv', $File::Find::name);
        return unless $success;
        my @out_lines = split("\n", $out);
        return if $out_lines[0] =~ m/is not an object file/;
        my $last_line = pop @out_lines;
        my ($type) = $last_line =~ m/^\s*[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+([^\s]+)\s+[^\s]+\s+[^\s]+\s+/;
        my $name = $File::Find::name;
        $name =~ s/^$tbbinfos->{tbbdir}\///;
        $exec_files{$name} = 1 if $type eq 'EXECUTE';
    };
    find($wanted, $tbbinfos->{tbbdir});
    return $tbbinfos->{osx_executable_files} = [ keys %exec_files ];
}

550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
sub list_tests {
    foreach my $test (@tests) {
        print "$test->{name} ($test->{type})\n   $test->{descr}\n\n";
    }
}

sub get_tbbfile {
    my ($tbbinfos) = @_;
    $tbbinfos->{tbbfile_orig} = $tbbinfos->{tbbfile};
    if ($tbbinfos->{tbbfile} =~ m/^https?:\/\//) {
        my (undef, undef, $file) = File::Spec->splitpath($tbbinfos->{tbbfile});
        my $output = $options->{'download-dir'} ?
                "$options->{'download-dir'}/$file" : "$tbbinfos->{tmpdir}/$file";
        return $output if -f $output;
        print "Downloading $tbbinfos->{tbbfile}\n";
        my $ua = LWP::UserAgent->new;
        my $resp = $ua->get($tbbinfos->{tbbfile}, ':content_file' => $output);
        exit_error "Error downloading $tbbinfos->{tbbfile}:\n" . $resp->status_line
                unless $resp->is_success;
        $tbbinfos->{tbbfile} = $output;
    }
    exit_error "File $tbbinfos->{tbbfile} does not exist"
                unless -f $tbbinfos->{tbbfile};
}

sub extract_tbb {
    my ($tbbinfos) = @_;
    exit_error "Can't open file $tbbinfos->{tbbfile}" unless -f $tbbinfos->{tbbfile};
    my $tbbfile = File::Spec->rel2abs($tbbinfos->{tbbfile});
    my $tmpdir = $tbbinfos->{tmpdir};
    chdir $tmpdir;
    if ($tbbinfos->{os} eq 'Linux') {
        system('tar', 'xf', $tbbfile);
583 584 585 586 587
        if ($tbbinfos->{language} eq 'ALL') {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser";
        } else {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser_$tbbinfos->{language}";
        }
588
        $tbbinfos->{tbbdir} .= '/Browser';
589 590 591 592
    } elsif ($tbbinfos->{os} eq 'Windows') {
        my (undef, undef, $f) = File::Spec->splitpath($tbbfile);
        copy($tbbfile, "$tmpdir/$f");
        system('7z', 'x', $f);
593
        $tbbinfos->{tbbdir} = "$tmpdir/torbrowser/Browser";
594 595 596 597 598
        move("$tmpdir/\$_OUTDIR", "$tmpdir/torbrowser") if -d "$tmpdir/\$_OUTDIR";
        if (-d "$tmpdir/Browser") {
            mkdir "$tmpdir/torbrowser";
            move("$tmpdir/Browser", "$tmpdir/torbrowser/Browser");
        }
599
        move ("$tmpdir/Start Tor Browser.exe", "$tmpdir/torbrowser/");
600
        system('chmod', '-R', '+rx', $tmpdir) if $OSNAME eq 'cygwin';
boklm's avatar
boklm committed
601 602 603 604 605 606
    } elsif ($tbbinfos->{os} eq 'MacOSX') {
        my $mountpoint = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
        system('hdiutil', 'mount', '-mountpoint', $mountpoint, $tbbfile);
        system('cp', '-a', "$mountpoint/TorBrowser.app", "$tmpdir/TorBrowser.app");
        system('hdiutil', 'unmount', $mountpoint);
        $tbbinfos->{tbbdir} = "$tmpdir/TorBrowser.app";
607 608 609 610 611 612 613 614 615 616 617 618 619
    }
}

sub xvfb_run {
    my ($test) = @_;
    return () unless $options->{xvfb};
    my $resolution = $test->{resolution} ? $test->{resolution}
                                         : $options->{resolution};
    return ('xvfb-run', '--auto-servernum', '-s', "-screen 0 ${resolution}x24");
}

sub check_opened_connections {
    my ($tbbinfos, $test) = @_;
620 621 622 623 624 625
    my %bad_connections =  %{$test->{results}{connections}};
    delete $bad_connections{"127.0.0.1:$options->{'tor-control-port'}"};
    delete $bad_connections{"127.0.0.1:$options->{'tor-socks-port'}"};
    # For some reasons, tor-browser creates two connections to the default
    # socks port even when when TOR_SOCKS_PORT is set
    # https://lists.torproject.org/pipermail/tbb-dev/2014-May/000050.html
boklm's avatar
boklm committed
626 627 628 629
    if (defined $bad_connections{'127.0.0.1:9150'}
        && $bad_connections{'127.0.0.1:9150'} <= 2) {
        delete $bad_connections{'127.0.0.1:9150'}
    }
630 631 632 633
    if (%bad_connections) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
634
    $test->{clean_strace} //= !%bad_connections;
635
    $test->{results}{bad_connections} = \%bad_connections;
636 637 638 639
}

sub check_modified_files {
    my ($tbbinfos, $test) = @_;
640
    my @bad_modified_files = @{$test->{results}{modified_files}};
641 642 643 644
    if (@bad_modified_files) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
645
    $test->{clean_strace} //= !@bad_modified_files;
646 647 648
    $test->{results}{bad_modified_files} = \@bad_modified_files;
}

649 650
sub clean_strace {
    my ($tbbinfos, $test) = @_;
651
    return unless $test->{clean_strace};
652 653
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    unlink $logfile;
boklm's avatar
boklm committed
654
    unlink "$logfile.tmp";
655 656
}

657 658 659
sub parse_strace {
    my ($tbbinfos, $test) = @_;
    my %ignore_files = map { $_ => 1 } qw(/dev/null /dev/tty);
660 661
    my @ignore_re = ( qr/^\/dev\/dri/ );
    push @ignore_re, qr/^$test->{workspace}/ if $test->{workspace};
662 663 664 665 666
    my %files;
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    $test->{results}{connections} = {};
    my %modified_files;
    my %removed_files;
667 668 669 670 671
    if (-f "$logfile.tmp") {
        my $txt = read_file("$logfile.tmp");
        write_file($logfile, { append => 1 }, $txt);
        unlink "$logfile.tmp";
    }
672
    my @lines = read_file($logfile) if -f $logfile;
673
    LINE: foreach my $line (@lines) {
674 675 676 677 678
        if ($line =~ m/^\d+ open\("((?:[^"\\]++|\\.)*+)", ([^\)]+)/ ||
            $line =~ m/^\d+ openat\([^,]+, "((?:[^"\\]++|\\.)*+)", ([^\)]+)/) {
            next if $2 =~ m/O_RDONLY/;
            next if $1 =~ m/^$tbbinfos->{tbbdir}/;
            next if $ignore_files{$1};
679 680 681
            if ($ENV{'MOZMILL_SCREENSHOTS'}) {
                next if $1 =~ m/^$ENV{'MOZMILL_SCREENSHOTS'}/;
            }
682 683 684
            foreach my $re (@ignore_re) {
                next LINE if $1 =~ m/$re/;
            }
685 686 687 688
            $modified_files{$1}++;
        }
        if ($line =~ m/^\d+ unlink\("((?:[^"\\]++|\\.)*+)"/) {
            next if $1 =~ m/^$tbbinfos->{tbbdir}/;
689 690 691 692
            next if $ignore_files{$1};
            foreach my $re (@ignore_re) {
                next LINE if $1 =~ m/$re/;
            }
693 694 695
            $removed_files{$1}++;
            delete $modified_files{$1} unless -f $1;
        }
boklm's avatar
boklm committed
696
        if ($line =~ m/^\d+ connect\(\d+, \{sa_family=AF_INET, sin_port=htons\((\d+)\), sin_addr=inet_addr\("((?:[^"\\]++|\\.)*+)"\)/) {
697
            $test->{results}{connections}{"$2:$1"}++;
698 699
        }
    }
700 701
    $test->{results}{modified_files} = [ keys %modified_files ];
    $test->{results}{removed_files} = [ keys %removed_files ];
702 703 704 705 706 707 708 709 710
}

sub ff_wrapper {
    my ($tbbinfos, $test) = @_;
    my $wrapper_file = "$tbbinfos->{tbbdir}/ff_wrapper";
    return $wrapper_file if -f $wrapper_file;
    my $wrapper = <<EOF;
#!/bin/sh
set -e
711
export HOME="$tbbinfos->{tbbdir}"
712
export LD_LIBRARY_PATH="$tbbinfos->{tbbdir}:$tbbinfos->{tordir}"
713 714
export FONTCONFIG_PATH="\${HOME}/TorBrowser/Data/fontconfig"
export FONTCONFIG_FILE="fonts.conf"
715 716 717 718 719 720 721
exec \'$tbbinfos->{ffbin}\' "\$@"
EOF
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

722
sub ff_strace_wrapper {
723 724
    my ($tbbinfos, $test) = @_;
    my $ff_wrapper = ff_wrapper($tbbinfos, $test);
725
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
726 727
    my $wrapper = <<EOF;
#!/bin/sh
728 729 730 731 732 733
if [ -f $logfile.tmp ]
then
   cat $logfile.tmp >> $logfile
   rm $logfile.tmp
fi
echo \$@ >> /tmp/ff_run.log
734 735 736 737 738
strace -f -o $logfile.tmp -- \'$ff_wrapper\' "\$@"
exit_code=\$?
cat $logfile.tmp >> $logfile
rm $logfile.tmp
exit \$?
739 740 741 742 743 744 745 746 747 748 749 750
EOF
    my $wrapper_file = "$tbbinfos->{tbbdir}/ff_$test->{name}";
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

sub ffbin_path {
    my ($tbbinfos, $test) = @_;
    if ($OSNAME eq 'cygwin') {
        return winpath("$tbbinfos->{ffbin}.exe");
    }
751
    my %t = map { $_ => 1 } qw(marionette);
752
    if ($options->{use_strace} && $t{$test->{type}}) {
boklm's avatar
boklm committed
753 754
        return ff_strace_wrapper($tbbinfos, $test);
    }
boklm's avatar
boklm committed
755
    return $tbbinfos->{ffbin} if $OSNAME eq 'darwin';
756
    return ff_wrapper($tbbinfos, $test);
757 758
}

759 760 761 762 763 764 765 766 767 768 769 770
sub marionette_export_options {
    my ($tbbinfos, $test) = @_;
    my $options_file = File::Temp->new();
    my $json = {
        options  => clone_strip_coderef($options),
        test     => clone_strip_coderef($test),
        tbbinfos => clone_strip_coderef({ %$tbbinfos, tests => undef }),
    };
    write_file($options_file, encode_json($json));
    return $options_file;
}

771 772 773 774 775 776 777
sub marionette_run {
    my ($tbbinfos, $test) = @_;
    if ($test->{tried} && $test->{use_net}) {
        TBBTestSuite::Tests::TorBootstrap::send_newnym($tbbinfos);
    }
    set_test_prefs($tbbinfos, $test);

778 779
    my $options_file = marionette_export_options($tbbinfos, $test);
    $ENV{TESTSUITE_DATA_FILE} = winpath($options_file);
780 781
    my $result_file_html = "$tbbinfos->{'results-dir'}/$test->{name}.html";
    my $result_file_txt = "$tbbinfos->{'results-dir'}/$test->{name}.txt";
782 783
    $test->{workspace} = "$tbbinfos->{'results-dir'}/$test->{name}_ws";
    mkdir $test->{workspace};
784 785
    #--log-unittest  ./res.txt --log-html ./res.html
    my $bin = $OSNAME eq 'cygwin' ? 'Scripts' : 'bin';
786
    my $marionette_test = $test->{marionette_test} // $test->{name};
787
    my $pypath = $ENV{PYTHONPATH};
boklm's avatar
boklm committed
788 789 790 791
    my $old_pypath = $ENV{PYTHONPATH};
    $ENV{PYTHONPATH} = winpath("$FindBin::Bin/marionette/tor_browser_tests/lib");
    my $sep = $OSNAME eq 'cygwin' ? ';' : ':';
    $ENV{PYTHONPATH} .= $sep . $old_pypath if $old_pypath;
792 793 794
    $test->{screenshots} = [];
    my $screenshots_tmp = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
    $ENV{'MARIONETTE_SCREENSHOTS'} = winpath($screenshots_tmp);
795 796 797 798 799
    system(xvfb_run($test), "$FindBin::Bin/virtualenv-marionette/$bin/tor-browser-tests",
        '--log-unittest', winpath($result_file_txt),
        '--log-html', winpath($result_file_html),
        '--binary', ffbin_path($tbbinfos, $test),
        '--profile', winpath($tbbinfos->{ffprofiledir}),
800
        $OSNAME eq 'cygwin' ? () : ('--workspace', $test->{workspace}),
801
        winpath("$FindBin::Bin/marionette/tor_browser_tests/test_${marionette_test}.py"));
802
    $ENV{PYTHONPATH} = $pypath;
803
    my @txt_log = -f $result_file_txt ? read_file($result_file_txt) : ('NoFile');
804 805 806
    my $res_line = shift @txt_log;
    $test->{results}{success} = $res_line eq ".\n" || $res_line eq ".\r\n";
    $test->{results}{log} = join '', @txt_log;
807 808 809
    my $i = 0;
    for my $screenshot_file (sort glob "$screenshots_tmp/*.png") {
        move($screenshot_file, "$tbbinfos->{'results-dir'}/$test->{name}-$i.png");
810
        screenshot_thumbnail($tbbinfos->{'results-dir'}, "$test->{name}-$i.png");
811 812 813
        push @{$test->{screenshots}}, "$test->{name}-$i.png";
        $i++;
    }
814 815 816 817
    reset_test_prefs($tbbinfos, $test);
    parse_strace($tbbinfos, $test);
    check_opened_connections($tbbinfos, $test);
    check_modified_files($tbbinfos, $test);
818
    clean_strace($tbbinfos, $test);
819 820
}

821 822
sub set_tbbpaths {
    my ($tbbinfos) = @_;
823 824 825
    $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/firefox";
    $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/TorBrowser/Tor";
    $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/TorBrowser/Data";
boklm's avatar
boklm committed
826 827
    if ($tbbinfos->{os} eq 'MacOSX') {
        $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/Contents/MacOS/firefox";
828 829 830 831 832 833 834 835 836
        unless ($tbbinfos->{version} =~ m/^5./) {
            $tbbinfos->{ffprofiledir} = "$tbbinfos->{tbbdir}/Contents/Resources/distribution";
            $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/Contents/Resources/TorBrowser/Tor";
            $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/../TorBrowser-data";
            $tbbinfos->{torrcdefaults} = "$tbbinfos->{tordir}/torrc-defaults";
            $tbbinfos->{torgeoip} = "$tbbinfos->{tordir}/geoip";
            mkdir $tbbinfos->{datadir} unless -d $tbbinfos->{datadir};
            mkdir "$tbbinfos->{datadir}/Tor" unless -d "$tbbinfos->{datadir}/Tor";
        }
boklm's avatar
boklm committed
837
    }
838 839
    $tbbinfos->{torrcdefaults} //= "$tbbinfos->{datadir}/Tor/torrc-defaults";
    $tbbinfos->{torgeoip} //= "$tbbinfos->{datadir}/Tor/geoip";
840
    $tbbinfos->{torbin} = "$tbbinfos->{tordir}/tor";
boklm's avatar
boklm committed
841
    $tbbinfos->{ptdir} = winpath("$tbbinfos->{tordir}/PluggableTransports");
842
    $tbbinfos->{ffprofiledir} //= "$tbbinfos->{datadir}/Browser/profile.default";
843 844
}

boklm's avatar
boklm committed
845 846 847 848
sub new {
    my ($ts, $testsuite) = @_;
    $testsuite->{type} = 'browserbundle';
    $testsuite->{tests} = [ map { { %$_ } } @tests ];
849 850
    return undef unless $testsuite->{os} eq $options->{os};
    return undef unless $testsuite->{arch} eq $options->{arch};
boklm's avatar
boklm committed
851 852 853
    return bless $testsuite, $ts;
}

854 855 856 857 858 859 860 861 862 863
sub pre_tests {
    my ($tbbinfos) = @_;
    get_tbbfile($tbbinfos);
    if ($tbbinfos->{sha256sum} &&
        $tbbinfos->{sha256sum} ne sha256_hex(read_file($tbbinfos->{tbbfile}))) {
        exit_error "Wrong sha256sum for $tbbinfos->{tbbfile}";
    }
    $tbbinfos->{sha256sum} //= sha256_hex(read_file($tbbinfos->{tbbfile}));
    extract_tbb($tbbinfos);
    set_tbbpaths($tbbinfos);
864 865 866 867
    my $prefs_file = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    open(my $prefs_fh, '>>', $prefs_file);
    print $prefs_fh 'pref("extensions.torbutton.prompted_language", true);', "\n";
    close $prefs_fh;
868
    chdir $tbbinfos->{tbbdir} || exit_error "Can't enter directory $tbbinfos->{tbbdir}";
boklm's avatar
boklm committed
869
    copy "$FindBin::Bin/data/cert_override.txt",
boklm's avatar
boklm committed
870
          "$tbbinfos->{ffprofiledir}/cert_override.txt";
871
    $ENV{TOR_SKIP_LAUNCH} = 1;
872 873
    $ENV{TOR_SOCKS_PORT} = $options->{'tor-socks-port'};
    $ENV{TOR_CONTROL_PORT} = $options->{'tor-control-port'};
boklm's avatar
boklm committed
874 875 876
    if ($options->{xdummy}) {
        $tbbinfos->{Xdisplay} = start_X("$tbbinfos->{'results-dir'}/xorg.log");
    }
877 878 879 880 881
}

sub post_tests {
    my ($tbbinfos) = @_;
    TBBTestSuite::Tests::TorBootstrap::stop_tor($tbbinfos);
boklm's avatar
boklm committed
882
    stop_X($tbbinfos->{Xdisplay}) if $options->{xdummy};
883 884 885
}

1;