aboutsummaryrefslogtreecommitdiff
path: root/bundle/scripts/smoke-package.pl
blob: 919e9252f65e7200824ddd6b3da452044eddf6cd (plain)
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

#!/usr/bin/env perl
# Check a portable desktop package without relying on an installed toolchain.
use strict;
use warnings;
use Cwd qw(abs_path);
use File::Basename qw(dirname);
use File::Find ();
use File::Spec;
use File::Temp qw(tempdir);
use IPC::Open3 qw(open3);
use POSIX qw(WNOHANG);
use Time::HiRes qw(sleep time);

my $IS_WINDOWS = $^O eq 'MSWin32';

sub write_file {
    my ($path, $layer, $data) = @_;
    open my $fh, ">$layer", $path or die "cannot write $path: $!\n";
    print {$fh} $data;
    close $fh or die "cannot write $path: $!\n";
}

sub read_file {
    my ($path) = @_;
    open my $fh, '<:raw', $path or die "cannot read $path: $!\n";
    my $data = do { local $/; <$fh> };
    close $fh;
    return defined($data) ? $data : '';
}

# Reap a child within $seconds, killing it on timeout. Returns its exit code.
sub finish {
    my ($pid, $seconds, $what) = @_;
    my $deadline = time() + $seconds;
    while (waitpid($pid, WNOHANG) == 0) {
        if (time() >= $deadline) {
            kill 'KILL', $pid;
            waitpid($pid, 0);
            die "$what timed out\n";
        }
        sleep 0.1;
    }
    return $? >> 8;
}

my $root = abs_path($ARGV[0]);
die "Missing package path\n" unless defined $root;
# Children inherit %ENV; Win32 %ENV lookups ignore case.
$ENV{PATH} = $IS_WINDOWS
    ? join(';', File::Spec->catdir($ENV{SystemRoot}, 'System32'), $ENV{SystemRoot})
    : '/nonexistent';
# Children also inherit the cwd. Exit cleanup leaves it before removing it.
my $work = tempdir('cpsc bundle smoke XXXXXXXX', TMPDIR => 1, CLEANUP => 1);
chdir $work or die "cannot chdir to $work: $!\n";
my $compiler;
if ($IS_WINDOWS) {
    die "Expected a single portable .exe\n" unless -f $root && $root =~ /\.exe\z/;
    # Extraction happens before WebView startup and publishes a complete file by rename.
    my $desktop = system 1, $root;
    die "cannot start $root: $!\n" unless $desktop > 0;
    $compiler = File::Spec->catfile($work, 'cpsc.exe');
    my $extracted = eval {
        my $deadline = time() + 30;
        until (-f $compiler) {
            die "Desktop exited before extracting its compiler\n" if waitpid($desktop, WNOHANG) != 0;
            die "Compiler extraction timed out\n" if time() >= $deadline;
            sleep 0.1;
        }
        1;
    };
    my $error = $@;
    kill 'KILL', $desktop if waitpid($desktop, WNOHANG) == 0;
    finish($desktop, 10, 'Desktop shutdown');
    die $error unless $extracted;
} else {
    my @compilers;
    File::Find::find(sub { push @compilers, $File::Find::name if $_ eq 'cpsc' }, $root);
    die "Expected one packaged compiler, found [@compilers]\n" unless @compilers == 1;
    $compiler = $compilers[0];
    my $directory = dirname($compiler);
    die "Missing desktop executable\n" unless -f File::Spec->catfile($directory, 'cpsc-bundle');
    my $resources = $^O eq 'darwin' ? File::Spec->catdir(dirname($directory), 'Resources') : $directory;
    for my $filename ('LICENSE', 'ThirdPartyNotices.md', 'licenses/vscode.txt', 'licenses/plugin.md') {
        die "Missing $filename\n" unless -f File::Spec->catfile($resources, $filename);
    }
}
# Text mode: Windows sources and data files use CRLF.
write_file('data.txt', '', "relative file\n");
my $source = File::Spec->catfile($work, 'source file.cps');
write_file($source, '', qq{DECLARE N : INTEGER\nINPUT N\nOUTPUT N\nDECLARE S : STRING\nOPENFILE "data.txt" FOR READ\nREADFILE "data.txt", S\nCLOSEFILE "data.txt"\nOUTPUT S\n});
# Files instead of pipes: Windows cannot poll pipes, and files cannot fill up.
write_file('stdin', ':raw', "17\n");
open my $in, '<:raw', 'stdin' or die "cannot read stdin: $!\n";
open my $out, '>:raw', 'stdout' or die "cannot write stdout: $!\n";
open my $err, '>:raw', 'stderr' or die "cannot write stderr: $!\n";
my $pid = open3('<&' . fileno($in), '>&' . fileno($out), '>&' . fileno($err), $compiler, $source, '-r');
my $status = finish($pid, 30, 'Compiler run');
close $out;
close $err;
my $stdout = read_file('stdout');
my $stderr = read_file('stderr');
die "Compiler exited with $status: '$stderr'\n" unless $status == 0;
(my $normalized = $stdout) =~ s/\r\n/\n/g;
die "Unexpected output: '$stdout'\n" unless $normalized eq "17\nrelative file\n";
die "Unexpected diagnostics: '$stderr'\n" unless $stderr eq '';
print "Desktop package smoke passed: portable application, compiler, stdin and relative files.\n";