mirror of
https://github.com/darlinghq/darling-objc4.git
synced 2024-11-26 21:50:26 +00:00
1898 lines
56 KiB
Perl
Executable File
1898 lines
56 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
# test.pl
|
|
# Run unit tests.
|
|
|
|
use strict;
|
|
use File::Basename;
|
|
|
|
use Config;
|
|
my $supportsParallelBuilds = $Config{useithreads};
|
|
|
|
if ($supportsParallelBuilds) {
|
|
require threads;
|
|
import threads;
|
|
require Thread::Queue;
|
|
import Thread::Queue;
|
|
}
|
|
|
|
# We use encode_json() to write BATS plist files.
|
|
# JSON::PP does not exist on iOS devices, but we need not write plists there.
|
|
# So we simply load JSON:PP if it exists.
|
|
if (eval { require JSON::PP; 1; }) {
|
|
JSON::PP->import();
|
|
}
|
|
|
|
# iOS also doesn't have Text::Glob. We don't need it there.
|
|
my $has_match_glob = 0;
|
|
if (eval { require Text::Glob; 1; }) {
|
|
Text::Glob->import();
|
|
$has_match_glob = 1;
|
|
}
|
|
|
|
|
|
chdir dirname $0;
|
|
chomp (my $DIR = `pwd`);
|
|
|
|
if (scalar(@ARGV) == 1) {
|
|
my $arg = $ARGV[0];
|
|
if ($arg eq "-h" || $arg eq "-H" || $arg eq "-help" || $arg eq "help") {
|
|
print(<<END);
|
|
usage: $0 [options] [testname ...]
|
|
$0 help
|
|
|
|
testname:
|
|
`testname` runs a specific test. If no testnames are given, runs all tests.
|
|
|
|
options:
|
|
ARCH=<arch>
|
|
OS=<sdk name>[sdk version][-<deployment target>[-<run target>]]
|
|
ROOT=/path/to/project.roots/
|
|
HOST=<test device hostname>
|
|
DEVICE=<simulator test device name>
|
|
|
|
CC=<compiler name>
|
|
|
|
LANGUAGE=c,c++,objective-c,objective-c++,swift
|
|
MEM=mrc,arc
|
|
GUARDMALLOC=0|1|before|after
|
|
|
|
BUILD=0|1 (build the tests?)
|
|
RUN=0|1 (run the tests?)
|
|
VERBOSE=0|1|2 (0=quieter 1=print commands executed 2=full test output)
|
|
BATS=0|1 (build for and/or run in BATS?)
|
|
BUILD_SHARED_CACHE=0|1 (build a dyld shared cache with the root and test against that)
|
|
DYLD=2|3 (test in dyld 2 or dyld 3 mode)
|
|
PARALLELBUILDS=N (number of parallel builds to run simultaneously)
|
|
SHAREDCACHEDIR=/path/to/custom/shared/cache/directory
|
|
|
|
examples:
|
|
|
|
test installed library, x86_64
|
|
$0
|
|
|
|
test buildit-built root, i386 and x86_64, MRC and ARC, clang compiler
|
|
$0 ARCH=i386,x86_64 ROOT=/tmp/objc4.roots MEM=mrc,arc CC=clang
|
|
|
|
test buildit-built root with iOS simulator, deploy to iOS 7, run on iOS 8
|
|
$0 ARCH=x86_64 ROOT=/tmp/objc4.roots OS=iphonesimulator-7.0-8.0
|
|
|
|
test buildit-built root on attached iOS device
|
|
$0 ARCH=arm64 ROOT=/tmp/objc4.roots OS=iphoneos
|
|
END
|
|
exit 0;
|
|
}
|
|
}
|
|
|
|
#########################################################################
|
|
## Tests
|
|
|
|
# Maps test name => test's filename extension.
|
|
# ex: "msgSend" => "m"
|
|
# `keys %ALL_TESTS` is also used as the list of all tests found on disk.
|
|
my %ALL_TESTS;
|
|
|
|
#########################################################################
|
|
## Variables for use in complex build and run rules
|
|
|
|
# variable # example value
|
|
|
|
# things you can multiplex on the command line
|
|
# ARCH=i386,x86_64,armv6,armv7
|
|
# OS=macosx,iphoneos,iphonesimulator (plus sdk/deployment/run versions)
|
|
# LANGUAGE=c,c++,objective-c,objective-c++,swift
|
|
# CC=clang
|
|
# MEM=mrc,arc
|
|
# GUARDMALLOC=0,1,before,after
|
|
|
|
# things you can set once on the command line
|
|
# ROOT=/path/to/project.roots
|
|
# BUILD=0|1
|
|
# RUN=0|1
|
|
# VERBOSE=0|1|2
|
|
# BATS=0|1
|
|
|
|
# environment variables from the command line
|
|
# DSTROOT
|
|
# OBJROOT
|
|
# (SRCROOT is ignored; test sources are assumed to
|
|
# be in the same directory as the test script itself.)
|
|
# fixme SYMROOT for dsymutil output?
|
|
|
|
|
|
# Some arguments as read from the command line.
|
|
my %args;
|
|
my $BUILD;
|
|
my $RUN;
|
|
my $VERBOSE;
|
|
my $BATS;
|
|
|
|
my $HOST;
|
|
my $PORT;
|
|
my $DEVICE;
|
|
|
|
my $PARALLELBUILDS;
|
|
|
|
my $SHAREDCACHEDIR;
|
|
|
|
my @TESTLIBNAMES = ("libobjc.A.dylib", "libobjc-trampolines.dylib");
|
|
my $TESTLIBDIR = "/usr/lib";
|
|
|
|
# Top level directory for intermediate and final build products.
|
|
# Intermediate files must be kept separate for XBS BATS builds.
|
|
my $OBJROOT = $ENV{OBJROOT} || "";
|
|
my $DSTROOT = $ENV{DSTROOT} || "";
|
|
|
|
# Build product directory inside DSTROOT and OBJROOT.
|
|
# Each test config gets its own build directory inside this.
|
|
my $BUILDDIR;
|
|
|
|
# Local top-level directory.
|
|
# This is the default value for $BUILDDIR.
|
|
my $LOCALBASE = "/tmp/test-$TESTLIBNAMES[0]-build";
|
|
|
|
# Device-side top-level directory.
|
|
# This replaces $DSTROOT$BUILDDIR/ for on-device execution.
|
|
my $REMOTEBASE = "/AppleInternal/objctest";
|
|
|
|
# BATS top-level directory.
|
|
# This replaces $DSTROOT$BUILDDIR/ for BATS execution.
|
|
my $BATSBASE = "/AppleInternal/CoreOS/tests/objc4";
|
|
|
|
|
|
my $crashcatch = <<'END';
|
|
// interpose-able code to catch crashes, print, and exit cleanly
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
// from dyld-interposing.h
|
|
#define DYLD_INTERPOSE(_replacement,_replacee) __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };
|
|
|
|
static void catchcrash(int sig)
|
|
{
|
|
const char *msg;
|
|
switch (sig) {
|
|
case SIGILL: msg = "CRASHED: SIGILL"; break;
|
|
case SIGBUS: msg = "CRASHED: SIGBUS"; break;
|
|
case SIGSYS: msg = "CRASHED: SIGSYS"; break;
|
|
case SIGSEGV: msg = "CRASHED: SIGSEGV"; break;
|
|
case SIGTRAP: msg = "CRASHED: SIGTRAP"; break;
|
|
case SIGABRT: msg = "CRASHED: SIGABRT"; break;
|
|
default: msg = "unknown signal"; break;
|
|
}
|
|
write(STDERR_FILENO, msg, strlen(msg));
|
|
|
|
// avoid backslash-n newline due to escaping differences somewhere
|
|
// in BATS versus local execution (perhaps different perl versions?)
|
|
char newline = 0xa;
|
|
write(STDERR_FILENO, &newline, 1);
|
|
|
|
_exit(1);
|
|
}
|
|
|
|
static void setupcrash(void) __attribute__((constructor));
|
|
static void setupcrash(void)
|
|
{
|
|
signal(SIGILL, &catchcrash);
|
|
signal(SIGBUS, &catchcrash);
|
|
signal(SIGSYS, &catchcrash);
|
|
signal(SIGSEGV, &catchcrash);
|
|
signal(SIGTRAP, &catchcrash);
|
|
signal(SIGABRT, &catchcrash);
|
|
}
|
|
|
|
|
|
static int hacked = 0;
|
|
ssize_t hacked_write(int fildes, const void *buf, size_t nbyte)
|
|
{
|
|
if (!hacked) {
|
|
setupcrash();
|
|
hacked = 1;
|
|
}
|
|
return write(fildes, buf, nbyte);
|
|
}
|
|
|
|
DYLD_INTERPOSE(hacked_write, write);
|
|
|
|
END
|
|
|
|
|
|
#########################################################################
|
|
## Harness
|
|
|
|
|
|
# map language to buildable extensions for that language
|
|
my %extensions_for_language = (
|
|
"c" => ["c"],
|
|
"objective-c" => ["c", "m"],
|
|
"c++" => ["c", "cc", "cp", "cpp", "cxx", "c++"],
|
|
"objective-c++" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm"],
|
|
"swift" => ["swift"],
|
|
|
|
"any" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm", "swift"],
|
|
);
|
|
|
|
# map extension to languages
|
|
my %languages_for_extension = (
|
|
"c" => ["c", "objective-c", "c++", "objective-c++"],
|
|
"m" => ["objective-c", "objective-c++"],
|
|
"mm" => ["objective-c++"],
|
|
"cc" => ["c++", "objective-c++"],
|
|
"cp" => ["c++", "objective-c++"],
|
|
"cpp" => ["c++", "objective-c++"],
|
|
"cxx" => ["c++", "objective-c++"],
|
|
"c++" => ["c++", "objective-c++"],
|
|
"swift" => ["swift"],
|
|
);
|
|
|
|
# Run some newline-separated commands like `make` would, stopping if any fail
|
|
# run("cmd1 \n cmd2 \n cmd3")
|
|
sub make {
|
|
my ($cmdstr, $cwd) = @_;
|
|
my $output = "";
|
|
my @cmds = split("\n", $cmdstr);
|
|
die if scalar(@cmds) == 0;
|
|
$? = 0;
|
|
foreach my $cmd (@cmds) {
|
|
chomp $cmd;
|
|
next if $cmd =~ /^\s*$/;
|
|
$cmd .= " 2>&1";
|
|
if (defined $cwd) {
|
|
$cmd = "cd $cwd; $cmd";
|
|
}
|
|
print "$cmd\n" if $VERBOSE;
|
|
$output .= `$cmd`;
|
|
last if $?;
|
|
}
|
|
print "$output\n" if $VERBOSE;
|
|
return $output;
|
|
}
|
|
|
|
sub chdir_verbose {
|
|
my $dir = shift || die;
|
|
print "cd $dir\n" if $VERBOSE;
|
|
chdir $dir || die "couldn't cd $dir";
|
|
}
|
|
|
|
sub rm_rf_verbose {
|
|
my $dir = shift || die;
|
|
print "rm -rf $dir\n" if $VERBOSE;
|
|
`rm -rf '$dir'`;
|
|
die "couldn't rm -rf $dir" if $?;
|
|
}
|
|
|
|
sub mkdir_verbose {
|
|
my $dir = shift || die;
|
|
print "mkdir -p $dir\n" if $VERBOSE;
|
|
`mkdir -p '$dir'`;
|
|
die "couldn't mkdir $dir" if $?;
|
|
}
|
|
|
|
|
|
# xterm colors
|
|
my $red = "\e[41;37m";
|
|
my $yellow = "\e[43;30m";
|
|
my $nocolor = "\e[0m";
|
|
if (! -t STDIN) {
|
|
# Not isatty. Don't use colors.
|
|
$red = "";
|
|
$yellow = "";
|
|
$nocolor = "";
|
|
}
|
|
|
|
# print text with a colored prefix on each line
|
|
# fixme some callers pass an array of lines and some don't
|
|
sub colorprefix {
|
|
my $color = shift;
|
|
while (defined(my $lines = shift)) {
|
|
$lines = "\n" if ($lines eq "");
|
|
for my $line (split(/^/, $lines)) {
|
|
chomp $line;
|
|
print "$color $nocolor$line\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
# print text colored
|
|
# fixme some callers pass an array of lines and some don't
|
|
sub colorprint {
|
|
my $color = shift;
|
|
while (defined(my $lines = shift)) {
|
|
$lines = "\n" if ($lines eq "");
|
|
for my $line (split(/^/, $lines)) {
|
|
chomp $line;
|
|
print "$color$line$nocolor\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
# Return test names from the command line.
|
|
# Returns all tests if no tests were named.
|
|
sub gettests {
|
|
my @tests;
|
|
|
|
foreach my $arg (@ARGV) {
|
|
push @tests, $arg if ($arg !~ /=/ && $arg !~ /^-/);
|
|
}
|
|
|
|
opendir(my $dir, $DIR) || die;
|
|
while (my $file = readdir($dir)) {
|
|
my ($name, $ext) = ($file =~ /^([^.]+)\.([^.]+)$/);
|
|
next if ! $languages_for_extension{$ext};
|
|
|
|
open(my $in, "< $file") || die "$file";
|
|
my $contents = join "", <$in>;
|
|
if (defined $ALL_TESTS{$name}) {
|
|
colorprint $yellow, "SKIP: multiple tests named '$name'; skipping file '$file'.";
|
|
} else {
|
|
$ALL_TESTS{$name} = $ext if ($contents =~ m#^[/*\s]*TEST_#m);
|
|
}
|
|
close($in);
|
|
}
|
|
closedir($dir);
|
|
|
|
if (scalar(@tests) == 0) {
|
|
@tests = keys %ALL_TESTS;
|
|
}
|
|
|
|
@tests = sort @tests;
|
|
|
|
return @tests;
|
|
}
|
|
|
|
|
|
# Turn a C compiler name into a C++ compiler name.
|
|
sub cplusplus {
|
|
my ($c) = @_;
|
|
if ($c =~ /cc/) {
|
|
$c =~ s/cc/\+\+/;
|
|
return $c;
|
|
}
|
|
return $c . "++"; # e.g. clang => clang++
|
|
}
|
|
|
|
# Turn a C compiler name into a Swift compiler name
|
|
sub swift {
|
|
my ($c) = @_;
|
|
$c =~ s#[^/]*$#swift#;
|
|
return $c;
|
|
}
|
|
|
|
# Returns an array of all sdks from `xcodebuild -showsdks`
|
|
my @sdks_memo;
|
|
sub getsdks {
|
|
if (!@sdks_memo) {
|
|
@sdks_memo = (`xcodebuild -showsdks` =~ /-sdk (.+)$/mg);
|
|
}
|
|
return @sdks_memo;
|
|
}
|
|
|
|
my %sdk_path_memo = {};
|
|
sub getsdkpath {
|
|
my ($sdk) = @_;
|
|
if (!defined $sdk_path_memo{$sdk}) {
|
|
($sdk_path_memo{$sdk}) = (`xcodebuild -version -sdk '$sdk' Path` =~ /^\s*(.+?)\s*$/);
|
|
}
|
|
return $sdk_path_memo{$sdk};
|
|
}
|
|
|
|
# Extract a version number from a string.
|
|
# Ignore trailing "internal".
|
|
sub versionsuffix {
|
|
my ($str) = @_;
|
|
my ($vers) = ($str =~ /([0-9]+\.[0-9]+)(?:\.?internal)?$/);
|
|
return $vers;
|
|
}
|
|
sub majorversionsuffix {
|
|
my ($str) = @_;
|
|
my ($vers) = ($str =~ /([0-9]+)\.[0-9]+(?:\.?internal)?$/);
|
|
return $vers;
|
|
}
|
|
sub minorversionsuffix {
|
|
my ($str) = @_;
|
|
my ($vers) = ($str =~ /[0-9]+\.([0-9]+)(?:\.?internal)?$/);
|
|
return $vers;
|
|
}
|
|
|
|
# Compares two SDK names and returns the newer one.
|
|
# Assumes the two SDKs are the same OS.
|
|
sub newersdk {
|
|
my ($lhs, $rhs) = @_;
|
|
|
|
# Major version wins.
|
|
my $lhsMajor = majorversionsuffix($lhs);
|
|
my $rhsMajor = majorversionsuffix($rhs);
|
|
if ($lhsMajor > $rhsMajor) { return $lhs; }
|
|
if ($lhsMajor < $rhsMajor) { return $rhs; }
|
|
|
|
# Minor version wins.
|
|
my $lhsMinor = minorversionsuffix($lhs);
|
|
my $rhsMinor = minorversionsuffix($rhs);
|
|
if ($lhsMinor > $rhsMinor) { return $lhs; }
|
|
if ($lhsMinor < $rhsMinor) { return $rhs; }
|
|
|
|
# Lexically-last wins (i.e. internal is better than not internal)
|
|
if ($lhs gt $rhs) { return $lhs; }
|
|
return $rhs;
|
|
}
|
|
|
|
sub rewind {
|
|
seek($_[0], 0, 0);
|
|
}
|
|
|
|
# parse name=value,value pairs
|
|
sub readconditions {
|
|
my ($conditionstring) = @_;
|
|
|
|
my %results;
|
|
my @conditions = ($conditionstring =~ /\w+=(?:[^\s,]+,?)+/g);
|
|
for my $condition (@conditions) {
|
|
my ($name, $values) = ($condition =~ /(\w+)=(.+)/);
|
|
$results{$name} = [split ',', $values];
|
|
}
|
|
|
|
return %results;
|
|
}
|
|
|
|
sub check_output {
|
|
my %C = %{shift()};
|
|
my $name = shift;
|
|
my @output = @_;
|
|
|
|
my %T = %{$C{"TEST_$name"}};
|
|
|
|
# Quietly strip MallocScribble before saving the "original" output
|
|
# because it is distracting.
|
|
filter_malloc(\@output);
|
|
|
|
my @original_output = @output;
|
|
|
|
# Run result-checking passes, reducing @output each time
|
|
my $xit = 1;
|
|
my $bad = "";
|
|
my $warn = "";
|
|
my $runerror = $T{TEST_RUN_OUTPUT};
|
|
filter_hax(\@output);
|
|
filter_verbose(\@output);
|
|
filter_simulator(\@output);
|
|
$warn = filter_warn(\@output);
|
|
$bad |= filter_guardmalloc(\@output) if ($C{GUARDMALLOC});
|
|
$bad |= filter_valgrind(\@output) if ($C{VALGRIND});
|
|
$bad = filter_expected(\@output, \%C, $name) if ($bad eq "");
|
|
$bad = filter_bad(\@output) if ($bad eq "");
|
|
|
|
# OK line should be the only one left
|
|
$bad = "(output not 'OK: $name')" if ($bad eq "" && (scalar(@output) != 1 || $output[0] !~ /^OK: $name/));
|
|
|
|
if ($bad ne "") {
|
|
colorprint $red, "FAIL: /// test '$name' \\\\\\";
|
|
colorprefix $red, @original_output;
|
|
colorprint $red, "FAIL: \\\\\\ test '$name' ///";
|
|
colorprint $red, "FAIL: $name: $bad";
|
|
$xit = 0;
|
|
}
|
|
elsif ($warn ne "") {
|
|
colorprint $yellow, "PASS: /// test '$name' \\\\\\";
|
|
colorprefix $yellow, @original_output;
|
|
colorprint $yellow, "PASS: \\\\\\ test '$name' ///";
|
|
print "PASS: $name (with warnings)\n";
|
|
}
|
|
else {
|
|
print "PASS: $name\n";
|
|
}
|
|
return $xit;
|
|
}
|
|
|
|
sub filter_expected
|
|
{
|
|
my $outputref = shift;
|
|
my %C = %{shift()};
|
|
my $name = shift;
|
|
|
|
my %T = %{$C{"TEST_$name"}};
|
|
my $runerror = $T{TEST_RUN_OUTPUT} || return "";
|
|
|
|
my $bad = "";
|
|
|
|
my $output = join("\n", @$outputref) . "\n";
|
|
if ($output !~ /$runerror/) {
|
|
$bad = "(run output does not match TEST_RUN_OUTPUT)";
|
|
@$outputref = ("FAIL: $name");
|
|
} else {
|
|
@$outputref = ("OK: $name"); # pacify later filter
|
|
}
|
|
|
|
return $bad;
|
|
}
|
|
|
|
sub filter_bad
|
|
{
|
|
my $outputref = shift;
|
|
my $bad = "";
|
|
|
|
my @new_output;
|
|
for my $line (@$outputref) {
|
|
if ($line =~ /^BAD: (.*)/) {
|
|
$bad = "(failed)";
|
|
} else {
|
|
push @new_output, $line;
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
return $bad;
|
|
}
|
|
|
|
sub filter_warn
|
|
{
|
|
my $outputref = shift;
|
|
my $warn = "";
|
|
|
|
my @new_output;
|
|
for my $line (@$outputref) {
|
|
if ($line !~ /^WARN: (.*)/) {
|
|
push @new_output, $line;
|
|
} else {
|
|
$warn = "(warned)";
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
return $warn;
|
|
}
|
|
|
|
sub filter_verbose
|
|
{
|
|
my $outputref = shift;
|
|
|
|
my @new_output;
|
|
for my $line (@$outputref) {
|
|
if ($line !~ /^VERBOSE: (.*)/) {
|
|
push @new_output, $line;
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
}
|
|
|
|
sub filter_simulator
|
|
{
|
|
my $outputref = shift;
|
|
|
|
my @new_output;
|
|
for my $line (@$outputref) {
|
|
if (($line !~ /No simulator devices appear to be running/) &&
|
|
($line !~ /CoreSimulator is attempting to unload a stale CoreSimulatorService job/) &&
|
|
($line !~ /Failed to locate a valid instance of CoreSimulatorService/))
|
|
{
|
|
push @new_output, $line;
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
}
|
|
|
|
sub filter_hax
|
|
{
|
|
my $outputref = shift;
|
|
|
|
my @new_output;
|
|
for my $line (@$outputref) {
|
|
if ($line !~ /Class OS_tcp_/) {
|
|
push @new_output, $line;
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
}
|
|
|
|
sub filter_valgrind
|
|
{
|
|
my $outputref = shift;
|
|
my $errors = 0;
|
|
my $leaks = 0;
|
|
|
|
my @new_output;
|
|
for my $line (@$outputref) {
|
|
if ($line =~ /^Approx: do_origins_Dirty\([RW]\): missed \d bytes$/) {
|
|
# --track-origins warning (harmless)
|
|
next;
|
|
}
|
|
if ($line =~ /^UNKNOWN __disable_threadsignal is unsupported. This warning will not be repeated.$/) {
|
|
# signals unsupported (harmless)
|
|
next;
|
|
}
|
|
if ($line =~ /^UNKNOWN __pthread_sigmask is unsupported. This warning will not be repeated.$/) {
|
|
# signals unsupported (harmless)
|
|
next;
|
|
}
|
|
if ($line !~ /^^\.*==\d+==/) {
|
|
# not valgrind output
|
|
push @new_output, $line;
|
|
next;
|
|
}
|
|
|
|
my ($errcount) = ($line =~ /==\d+== ERROR SUMMARY: (\d+) errors/);
|
|
if (defined $errcount && $errcount > 0) {
|
|
$errors = 1;
|
|
}
|
|
|
|
(my $leakcount) = ($line =~ /==\d+==\s+(?:definitely|possibly) lost:\s+([0-9,]+)/);
|
|
if (defined $leakcount && $leakcount > 0) {
|
|
$leaks = 1;
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
|
|
my $bad = "";
|
|
$bad .= "(valgrind errors)" if ($errors);
|
|
$bad .= "(valgrind leaks)" if ($leaks);
|
|
return $bad;
|
|
}
|
|
|
|
|
|
|
|
sub filter_malloc
|
|
{
|
|
my $outputref = shift;
|
|
my $errors = 0;
|
|
|
|
my @new_output;
|
|
my $count = 0;
|
|
for my $line (@$outputref) {
|
|
# Ignore MallocScribble prologue.
|
|
# Ignore MallocStackLogging prologue.
|
|
if ($line =~ /malloc: enabling scribbling to detect mods to free/ ||
|
|
$line =~ /Deleted objects will be dirtied by the collector/ ||
|
|
$line =~ /malloc: stack logs being written into/ ||
|
|
$line =~ /malloc: stack logs deleted from/ ||
|
|
$line =~ /malloc: process \d+ no longer exists/ ||
|
|
$line =~ /malloc: recording malloc and VM allocation stacks/)
|
|
{
|
|
next;
|
|
}
|
|
|
|
# not malloc output
|
|
push @new_output, $line;
|
|
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
}
|
|
|
|
sub filter_guardmalloc
|
|
{
|
|
my $outputref = shift;
|
|
my $errors = 0;
|
|
|
|
my @new_output;
|
|
my $count = 0;
|
|
for my $line (@$outputref) {
|
|
if ($line !~ /^GuardMalloc\[[^\]]+\]: /) {
|
|
# not guardmalloc output
|
|
push @new_output, $line;
|
|
next;
|
|
}
|
|
|
|
# Ignore 4 lines of guardmalloc prologue.
|
|
# Anything further is a guardmalloc error.
|
|
if (++$count > 4) {
|
|
$errors = 1;
|
|
}
|
|
}
|
|
|
|
@$outputref = @new_output;
|
|
|
|
my $bad = "";
|
|
$bad .= "(guardmalloc errors)" if ($errors);
|
|
return $bad;
|
|
}
|
|
|
|
# TEST_SOMETHING
|
|
# text
|
|
# text
|
|
# END
|
|
sub extract_multiline {
|
|
my ($flag, $contents, $name) = @_;
|
|
if ($contents =~ /$flag\n/) {
|
|
my ($output) = ($contents =~ /$flag\n(.*?\n)END[ *\/]*\n/s);
|
|
die "$name used $flag without END\n" if !defined($output);
|
|
return $output;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
|
|
# TEST_SOMETHING
|
|
# text
|
|
# OR
|
|
# text
|
|
# END
|
|
sub extract_multiple_multiline {
|
|
my ($flag, $contents, $name) = @_;
|
|
if ($contents =~ /$flag\n/) {
|
|
my ($output) = ($contents =~ /$flag\n(.*?\n)END[ *\/]*\n/s);
|
|
die "$name used $flag without END\n" if !defined($output);
|
|
|
|
$output =~ s/\nOR\n/\n|/sg;
|
|
$output = "^(" . $output . ")\$";
|
|
return $output;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
|
|
sub gather_simple {
|
|
my $CREF = shift;
|
|
my %C = %{$CREF};
|
|
my $name = shift;
|
|
chdir_verbose $DIR;
|
|
|
|
my $ext = $ALL_TESTS{$name};
|
|
my $file = "$name.$ext";
|
|
return 0 if !$file;
|
|
|
|
# search file for 'TEST_CONFIG' or '#include "test.h"'
|
|
# also collect other values:
|
|
# TEST_DISABLED disable test with an optional message
|
|
# TEST_CRASHES test is expected to crash
|
|
# TEST_CONFIG test conditions
|
|
# TEST_ENV environment prefix
|
|
# TEST_CFLAGS compile flags
|
|
# TEST_BUILD build instructions
|
|
# TEST_BUILD_OUTPUT expected build stdout/stderr
|
|
# TEST_RUN_OUTPUT expected run stdout/stderr
|
|
# TEST_ENTITLEMENTS path to entitlements file
|
|
open(my $in, "< $file") || die;
|
|
my $contents = join "", <$in>;
|
|
|
|
my $test_h = ($contents =~ /^\s*#\s*(include|import)\s*"test\.h"/m);
|
|
my ($disabled) = ($contents =~ /\b(TEST_DISABLED\b.*)$/m);
|
|
my $crashes = ($contents =~ /\bTEST_CRASHES\b/m);
|
|
my ($conditionstring) = ($contents =~ /\bTEST_CONFIG\b(.*)$/m);
|
|
my ($envstring) = ($contents =~ /\bTEST_ENV\b(.*)$/m);
|
|
my ($cflags) = ($contents =~ /\bTEST_CFLAGS\b(.*)$/m);
|
|
my ($entitlements) = ($contents =~ /\bTEST_ENTITLEMENTS\b(.*)$/m);
|
|
$entitlements =~ s/^\s+|\s+$//g;
|
|
my ($buildcmd) = extract_multiline("TEST_BUILD", $contents, $name);
|
|
my ($builderror) = extract_multiple_multiline("TEST_BUILD_OUTPUT", $contents, $name);
|
|
my ($runerror) = extract_multiple_multiline("TEST_RUN_OUTPUT", $contents, $name);
|
|
|
|
return 0 if !$test_h && !$disabled && !$crashes && !defined($conditionstring)
|
|
&& !defined($envstring) && !defined($cflags) && !defined($buildcmd)
|
|
&& !defined($builderror) && !defined($runerror) && !defined($entitlements);
|
|
|
|
if ($disabled) {
|
|
colorprint $yellow, "SKIP: $name (disabled by $disabled)";
|
|
return 0;
|
|
}
|
|
|
|
# check test conditions
|
|
|
|
my $run = 1;
|
|
my %conditions = readconditions($conditionstring);
|
|
if (! $conditions{LANGUAGE}) {
|
|
# implicit language restriction from file extension
|
|
$conditions{LANGUAGE} = $languages_for_extension{$ext};
|
|
}
|
|
for my $condkey (keys %conditions) {
|
|
my @condvalues = @{$conditions{$condkey}};
|
|
|
|
# special case: RUN=0 does not affect build
|
|
if ($condkey eq "RUN" && @condvalues == 1 && $condvalues[0] == 0) {
|
|
$run = 0;
|
|
next;
|
|
}
|
|
|
|
my $testvalue = $C{$condkey};
|
|
next if !defined($testvalue);
|
|
# testvalue is the configuration being run now
|
|
# condvalues are the allowed values for this test
|
|
|
|
my $ok = 0;
|
|
for my $condvalue (@condvalues) {
|
|
|
|
# special case: objc and objc++
|
|
if ($condkey eq "LANGUAGE") {
|
|
$condvalue = "objective-c" if $condvalue eq "objc";
|
|
$condvalue = "objective-c++" if $condvalue eq "objc++";
|
|
}
|
|
|
|
$ok = 1 if ($testvalue eq $condvalue);
|
|
|
|
# special case: CC and CXX allow substring matches
|
|
if ($condkey eq "CC" || $condkey eq "CXX") {
|
|
$ok = 1 if ($testvalue =~ /$condvalue/);
|
|
}
|
|
|
|
last if $ok;
|
|
}
|
|
|
|
if (!$ok) {
|
|
my $plural = (@condvalues > 1) ? "one of: " : "";
|
|
print "SKIP: $name ($condkey=$testvalue, but test requires $plural", join(' ', @condvalues), ")\n";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# save some results for build and run phases
|
|
$$CREF{"TEST_$name"} = {
|
|
TEST_BUILD => $buildcmd,
|
|
TEST_BUILD_OUTPUT => $builderror,
|
|
TEST_CRASHES => $crashes,
|
|
TEST_RUN_OUTPUT => $runerror,
|
|
TEST_CFLAGS => $cflags,
|
|
TEST_ENV => $envstring,
|
|
TEST_RUN => $run,
|
|
DSTDIR => "$C{DSTDIR}/$name.build",
|
|
OBJDIR => "$C{OBJDIR}/$name.build",
|
|
ENTITLEMENTS => $entitlements,
|
|
};
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
# Test description plist to write when building for BATS execution.
|
|
my %bats_plist;
|
|
$bats_plist{'Project'} = "objc4";
|
|
$bats_plist{'Tests'} = []; # populated by append_bats_test()
|
|
|
|
# Saves run instructions for a single test in all configurations as a BATS test.
|
|
sub append_bats_test {
|
|
my $name = shift;
|
|
|
|
my $arch = join(',', @{$args{ARCH}});
|
|
my $os = join(',', @{$args{OSVERSION}});
|
|
my $mem = join(',', @{$args{MEM}});
|
|
my $language = join(',', @{$args{LANGUAGE}});
|
|
|
|
push @{$bats_plist{'Tests'}}, {
|
|
"TestName" => "$name",
|
|
"Command" => [
|
|
"/usr/bin/perl",
|
|
"$BATSBASE/test/test.pl",
|
|
$name,
|
|
"ARCH=$arch",
|
|
"OS=$os",
|
|
"MEM=$mem",
|
|
"LANGUAGE=$language",
|
|
"BUILD=0",
|
|
"RUN=1",
|
|
"VERBOSE=1",
|
|
"BATS=1",
|
|
]
|
|
};
|
|
}
|
|
|
|
|
|
# Builds a simple test
|
|
sub build_simple {
|
|
my %C = %{shift()};
|
|
my $name = shift;
|
|
my %T = %{$C{"TEST_$name"}};
|
|
|
|
my $dstdir = $T{DSTDIR};
|
|
if (-e "$dstdir/build-succeeded") {
|
|
# We delete the whole test directory before building (if it existed),
|
|
# so if this file exists now, that means another configuration already
|
|
# did an equivalent build.
|
|
print "note: $name is already built at $dstdir, skipping the build\n" if $VERBOSE;
|
|
return 1;
|
|
}
|
|
|
|
mkdir_verbose $dstdir;
|
|
# we don't mkdir $T{OBJDIR} because most tests don't use it
|
|
|
|
my $ext = $ALL_TESTS{$name};
|
|
my $file = "$DIR/$name.$ext";
|
|
|
|
if ($T{TEST_CRASHES}) {
|
|
`echo '$crashcatch' > $dstdir/crashcatch.c`;
|
|
my $output = make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c", $dstdir);
|
|
if ($?) {
|
|
colorprint $red, "FAIL: building crashcatch.c";
|
|
colorprefix $red, $output;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
my $cmd = $T{TEST_BUILD} ? eval "return \"$T{TEST_BUILD}\"" : "$C{COMPILE} $T{TEST_CFLAGS} $file -o $name.exe";
|
|
|
|
my $output = make($cmd, $dstdir);
|
|
|
|
# ignore out-of-date text-based stubs (caused by ditto into SDK)
|
|
$output =~ s/ld: warning: text-based stub file.*\n//g;
|
|
# rdar://10163155
|
|
$output =~ s/ld: warning: could not create compact unwind for [^\n]+: does not use standard frame\n//g;
|
|
# rdar://37937122
|
|
$output =~ s/^warning: Cannot lower [^\n]+\n//g;
|
|
$output =~ s/^warning: key: [^\n]+\n//g;
|
|
$output =~ s/^warning: discriminator: [^\n]+\n//g;
|
|
$output =~ s/^warning: callee: [^\n]+\n//g;
|
|
# rdar://38710948
|
|
$output =~ s/ld: warning: ignoring file [^\n]*libclang_rt\.bridgeos\.a[^\n]*\n//g;
|
|
$output =~ s/ld: warning: building for iOS Simulator, but[^\n]*\n//g;
|
|
# ignore compiler logging of CCC_OVERRIDE_OPTIONS effects
|
|
if (defined $ENV{CCC_OVERRIDE_OPTIONS}) {
|
|
$output =~ s/### (CCC_OVERRIDE_OPTIONS:|Adding argument|Deleting argument|Replacing) [^\n]*\n//g;
|
|
}
|
|
|
|
my $ok;
|
|
if (my $builderror = $T{TEST_BUILD_OUTPUT}) {
|
|
# check for expected output and ignore $?
|
|
if ($output =~ /$builderror/) {
|
|
$ok = 1;
|
|
} elsif (defined $ENV{CCC_OVERRIDE_OPTIONS} && $builderror =~ /warning:/) {
|
|
# CCC_OVERRIDE_OPTIONS manipulates compiler diagnostics.
|
|
# Don't enforce any TEST_BUILD_OUTPUT that looks for warnings.
|
|
colorprint $yellow, "WARN: /// test '$name' \\\\\\";
|
|
colorprefix $yellow, $output;
|
|
colorprint $yellow, "WARN: \\\\\\ test '$name' ///";
|
|
colorprint $yellow, "WARN: $name (build output does not match TEST_BUILD_OUTPUT; not fatal because CCC_OVERRIDE_OPTIONS is set)";
|
|
$ok = 1;
|
|
} else {
|
|
colorprint $red, "FAIL: /// test '$name' \\\\\\";
|
|
colorprefix $red, $output;
|
|
colorprint $red, "FAIL: \\\\\\ test '$name' ///";
|
|
colorprint $red, "FAIL: $name (build output does not match TEST_BUILD_OUTPUT)";
|
|
$ok = 0;
|
|
}
|
|
} elsif ($?) {
|
|
colorprint $red, "FAIL: /// test '$name' \\\\\\";
|
|
colorprefix $red, $output;
|
|
colorprint $red, "FAIL: \\\\\\ test '$name' ///";
|
|
colorprint $red, "FAIL: $name (build failed)";
|
|
$ok = 0;
|
|
} elsif ($output ne "") {
|
|
colorprint $red, "FAIL: /// test '$name' \\\\\\";
|
|
colorprefix $red, $output;
|
|
colorprint $red, "FAIL: \\\\\\ test '$name' ///";
|
|
colorprint $red, "FAIL: $name (unexpected build output)";
|
|
$ok = 0;
|
|
} else {
|
|
$ok = 1;
|
|
}
|
|
|
|
if ($ok) {
|
|
foreach my $file (glob("$dstdir/*.exe $dstdir/*.dylib $dstdir/*.bundle")) {
|
|
if (!$BATS) {
|
|
# not for BATS to save space and build time
|
|
# fixme use SYMROOT?
|
|
make("xcrun dsymutil $file", $dstdir);
|
|
}
|
|
if ($C{OS} eq "macosx" || $C{OS} =~ /simulator/) {
|
|
# setting any entitlements disables dyld environment variables
|
|
} else {
|
|
# get-task-allow entitlement is required
|
|
# to enable dyld environment variables
|
|
if (!$T{ENTITLEMENTS}) {
|
|
$T{ENTITLEMENTS} = "get_task_allow_entitlement.plist";
|
|
}
|
|
my $output = make("xcrun codesign -s - --entitlements $DIR/$T{ENTITLEMENTS} $file", $dstdir);
|
|
if ($?) {
|
|
colorprint $red, "FAIL: codesign $file";
|
|
colorprefix $red, $output;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Mark the build as successful so other configs with the same build
|
|
# requirements can skip buildiing.
|
|
if ($ok) {
|
|
make("touch build-succeeded", $dstdir);
|
|
}
|
|
|
|
return $ok;
|
|
}
|
|
|
|
# Run a simple test (testname.exe, with error checking of stdout and stderr)
|
|
sub run_simple {
|
|
my %C = %{shift()};
|
|
my $name = shift;
|
|
my %T = %{$C{"TEST_$name"}};
|
|
|
|
if (! $T{TEST_RUN}) {
|
|
print "PASS: $name (build only)\n";
|
|
return 1;
|
|
}
|
|
|
|
my $testdir = $T{DSTDIR};
|
|
chdir_verbose $testdir;
|
|
|
|
my $env = "$C{ENV} $T{TEST_ENV}";
|
|
|
|
if ($T{TEST_CRASHES}) {
|
|
$env .= " OBJC_DEBUG_DONT_CRASH=YES";
|
|
}
|
|
|
|
if ($C{DYLD} eq "2") {
|
|
$env .= " DYLD_USE_CLOSURES=0";
|
|
}
|
|
elsif ($C{DYLD} eq "3") {
|
|
$env .= " DYLD_USE_CLOSURES=1";
|
|
}
|
|
else {
|
|
die "unknown DYLD setting $C{DYLD}";
|
|
}
|
|
|
|
if ($SHAREDCACHEDIR) {
|
|
$env .= " DYLD_SHARED_REGION=private DYLD_SHARED_CACHE_DIR=$SHAREDCACHEDIR";
|
|
}
|
|
|
|
my $output;
|
|
|
|
if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) {
|
|
# run on iOS or watchos or tvos device
|
|
# fixme device selection and verification
|
|
my $remotedir = "$REMOTEBASE/" . basename($C{DSTDIR}) . "/$name.build";
|
|
|
|
# Add test dir and libobjc's dir to DYLD_LIBRARY_PATH.
|
|
# Insert libcrashcatch.dylib if necessary.
|
|
$env .= " DYLD_LIBRARY_PATH=$remotedir";
|
|
$env .= ":$REMOTEBASE" if ($C{TESTLIBDIR} ne $TESTLIBDIR);
|
|
if ($T{TEST_CRASHES}) {
|
|
$env .= " DYLD_INSERT_LIBRARIES=$remotedir/libcrashcatch.dylib";
|
|
}
|
|
|
|
my $cmd = "ssh $PORT $HOST 'cd $remotedir && env $env ./$name.exe'";
|
|
$output = make("$cmd");
|
|
}
|
|
elsif ($C{OS} =~ /simulator/) {
|
|
# run locally in a simulator
|
|
my $sim = "xcrun -sdk iphonesimulator simctl spawn '$DEVICE'";
|
|
# Add test dir and libobjc's dir to DYLD_LIBRARY_PATH.
|
|
# Insert libcrashcatch.dylib if necessary.
|
|
$env .= " DYLD_LIBRARY_PATH=$testdir";
|
|
$env .= ":" . $C{TESTLIBDIR} if ($C{TESTLIBDIR} ne $TESTLIBDIR);
|
|
if ($T{TEST_CRASHES}) {
|
|
$env .= " DYLD_INSERT_LIBRARIES=$testdir/libcrashcatch.dylib";
|
|
}
|
|
|
|
my $simenv = "";
|
|
foreach my $keyvalue (split(' ', $env)) {
|
|
$simenv .= "SIMCTL_CHILD_$keyvalue ";
|
|
}
|
|
# Use the full path here so hack_cwd in test.h works.
|
|
$output = make("env $simenv $sim $testdir/$name.exe");
|
|
}
|
|
else {
|
|
# run locally
|
|
|
|
# Add test dir and libobjc's dir to DYLD_LIBRARY_PATH.
|
|
# Insert libcrashcatch.dylib if necessary.
|
|
$env .= " DYLD_LIBRARY_PATH=$testdir";
|
|
$env .= ":" . $C{TESTLIBDIR} if ($C{TESTLIBDIR} ne $TESTLIBDIR);
|
|
if ($T{TEST_CRASHES}) {
|
|
$env .= " DYLD_INSERT_LIBRARIES=$testdir/libcrashcatch.dylib";
|
|
}
|
|
|
|
$output = make("sh -c '$env ./$name.exe'");
|
|
}
|
|
|
|
return check_output(\%C, $name, split("\n", $output));
|
|
}
|
|
|
|
|
|
my %compiler_memo;
|
|
sub find_compiler {
|
|
my ($cc, $toolchain, $sdk_path) = @_;
|
|
|
|
# memoize
|
|
my $key = $cc . ':' . $toolchain;
|
|
my $result = $compiler_memo{$key};
|
|
return $result if defined $result;
|
|
|
|
$result = make("xcrun -toolchain $toolchain -find $cc 2>/dev/null");
|
|
|
|
chomp $result;
|
|
$compiler_memo{$key} = $result;
|
|
return $result;
|
|
}
|
|
|
|
sub dirContainsAllTestLibs {
|
|
my $dir = shift;
|
|
|
|
foreach my $testlib (@TESTLIBNAMES) {
|
|
my $found = (-e "$dir/$testlib");
|
|
my $foundstr = ($found ? "found" : "didn't find");
|
|
print "note: $foundstr $testlib in $dir\n" if ($VERBOSE);
|
|
return 0 if (!$found);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub findIncludeDir {
|
|
my ($root, $includePath) = @_;
|
|
|
|
foreach my $candidate ("$root/../SDKContentRoot/$includePath", "$root/$includePath") {
|
|
my $found = -e $candidate;
|
|
my $foundstr = ($found ? "found" : "didn't find");
|
|
print "note: $foundstr $includePath at $candidate\n" if $VERBOSE;
|
|
return $candidate if $found;
|
|
}
|
|
|
|
die "Unable to find $includePath in $root.\n";
|
|
}
|
|
|
|
sub buildSharedCache {
|
|
my $Cref = shift;
|
|
my %C = %$Cref;
|
|
|
|
make("update_dyld_shared_cache -verbose -cache_dir $BUILDDIR -overlay $C{TESTLIBDIR}/../..");
|
|
}
|
|
|
|
sub make_one_config {
|
|
my $configref = shift;
|
|
my $root = shift;
|
|
my %C = %{$configref};
|
|
|
|
# Aliases
|
|
$C{LANGUAGE} = "objective-c" if $C{LANGUAGE} eq "objc";
|
|
$C{LANGUAGE} = "objective-c++" if $C{LANGUAGE} eq "objc++";
|
|
|
|
# Interpret OS version string from command line.
|
|
my ($sdk_arg, $deployment_arg, $run_arg, undef) = split('-', $C{OSVERSION});
|
|
delete $C{OSVERSION};
|
|
my ($os_arg) = ($sdk_arg =~ /^([^\.0-9]+)/);
|
|
$deployment_arg = "default" if !defined($deployment_arg);
|
|
$run_arg = "default" if !defined($run_arg);
|
|
|
|
my %allowed_os_args = (
|
|
"macosx" => "macosx", "osx" => "macosx", "macos" => "macosx",
|
|
"iphoneos" => "iphoneos", "ios" => "iphoneos",
|
|
"iphonesimulator" => "iphonesimulator", "iossimulator" => "iphonesimulator",
|
|
"watchos" => "watchos",
|
|
"watchsimulator" => "watchsimulator", "watchossimulator" => "watchsimulator",
|
|
"appletvos" => "appletvos", "tvos" => "appletvos",
|
|
"appletvsimulator" => "appletvsimulator", "tvsimulator" => "appletvsimulator",
|
|
"bridgeos" => "bridgeos",
|
|
);
|
|
|
|
$C{OS} = $allowed_os_args{$os_arg} || die "unknown OS '$os_arg' (expected " . join(', ', sort keys %allowed_os_args) . ")\n";
|
|
|
|
# set the config name now, after massaging the language and OS versions,
|
|
# but before adding other settings
|
|
my $configdirname = config_dir_name(%C);
|
|
die if ($configdirname =~ /'/);
|
|
die if ($configdirname =~ / /);
|
|
($C{NAME} = $configdirname) =~ s/~/ /g;
|
|
(my $configdir = $configdirname) =~ s#/##g;
|
|
$C{DSTDIR} = "$DSTROOT$BUILDDIR/$configdir";
|
|
$C{OBJDIR} = "$OBJROOT$BUILDDIR/$configdir";
|
|
|
|
# Allow tests to see BATS-edness in TEST_CONFIG.
|
|
$C{BATS} = $BATS;
|
|
|
|
if ($C{OS} eq "iphoneos" || $C{OS} eq "iphonesimulator") {
|
|
$C{TOOLCHAIN} = "ios";
|
|
} elsif ($C{OS} eq "watchos" || $C{OS} eq "watchsimulator") {
|
|
$C{TOOLCHAIN} = "watchos";
|
|
} elsif ($C{OS} eq "appletvos" || $C{OS} eq "appletvsimulator") {
|
|
$C{TOOLCHAIN} = "appletvos";
|
|
} elsif ($C{OS} eq "bridgeos") {
|
|
$C{TOOLCHAIN} = "bridgeos";
|
|
} elsif ($C{OS} eq "macosx") {
|
|
$C{TOOLCHAIN} = "osx";
|
|
} else {
|
|
colorprint $yellow, "WARN: don't know toolchain for OS $C{OS}";
|
|
$C{TOOLCHAIN} = "default";
|
|
}
|
|
|
|
if ($BUILD) {
|
|
# Look up SDK.
|
|
# Try exact match first.
|
|
# Then try lexically-last prefix match
|
|
# (so "macosx" => "macosx10.7internal")
|
|
|
|
$sdk_arg =~ s/$os_arg/$C{OS}/;
|
|
|
|
my @sdks = getsdks();
|
|
if ($VERBOSE) {
|
|
print "note: Installed SDKs: @sdks\n";
|
|
}
|
|
my $exactsdk = undef;
|
|
my $prefixsdk = undef;
|
|
foreach my $sdk (@sdks) {
|
|
$exactsdk = $sdk if ($sdk eq $sdk_arg);
|
|
$prefixsdk = newersdk($sdk, $prefixsdk) if ($sdk =~ /^$sdk_arg/);
|
|
}
|
|
|
|
my $sdk;
|
|
if ($exactsdk) {
|
|
$sdk = $exactsdk;
|
|
} elsif ($prefixsdk) {
|
|
$sdk = $prefixsdk;
|
|
} else {
|
|
die "unknown SDK '$sdk_arg'\nInstalled SDKs: @sdks\n";
|
|
}
|
|
|
|
# Set deployment target.
|
|
# fixme can't enforce version when run_arg eq "default"
|
|
# because we don't know it yet
|
|
$deployment_arg = versionsuffix($sdk) if $deployment_arg eq "default";
|
|
if ($run_arg ne "default") {
|
|
die "Deployment target '$deployment_arg' is newer than run target '$run_arg'\n" if $deployment_arg > $run_arg;
|
|
}
|
|
$C{DEPLOYMENT_TARGET} = $deployment_arg;
|
|
$C{SDK_PATH} = getsdkpath($sdk);
|
|
} else {
|
|
# not $BUILD
|
|
$C{DEPLOYMENT_TARGET} = "unknown_deployment_target";
|
|
$C{SDK_PATH} = "/unknown/sdk";
|
|
}
|
|
|
|
# Set run target.
|
|
$C{RUN_TARGET} = $run_arg;
|
|
|
|
# Look up test library (possible in root or SDK_PATH)
|
|
|
|
my $rootarg = $root;
|
|
my $symroot;
|
|
my @sympaths = ( (glob "$root/*~sym")[0],
|
|
(glob "$root/BuildRecords/*_install/Symbols")[0],
|
|
"$root/Symbols" );
|
|
my @dstpaths = ( (glob "$root/*~dst")[0],
|
|
(glob "$root/BuildRecords/*_install/Root")[0],
|
|
"$root/Root" );
|
|
for(my $i = 0; $i < scalar(@sympaths); $i++) {
|
|
if (-e $sympaths[$i] && -e $dstpaths[$i]) {
|
|
$symroot = $sympaths[$i];
|
|
$root = $dstpaths[$i];
|
|
last;
|
|
}
|
|
}
|
|
|
|
if ($root ne "") {
|
|
# Root specified. Require that it contain our dylibs.
|
|
if (dirContainsAllTestLibs("$root$C{SDK_PATH}$TESTLIBDIR")) {
|
|
$C{TESTLIBDIR} = "$root$C{SDK_PATH}$TESTLIBDIR";
|
|
} elsif (dirContainsAllTestLibs("$root$TESTLIBDIR")) {
|
|
$C{TESTLIBDIR} = "$root$TESTLIBDIR";
|
|
} elsif (dirContainsAllTestLibs($root)) {
|
|
$C{TESTLIBDIR} = "$root";
|
|
} else {
|
|
die "Didn't find some libs in root '$rootarg' for sdk '$C{SDK_PATH}'\n";
|
|
}
|
|
}
|
|
else {
|
|
# No root specified. Use the SDK or / for our dylibs.
|
|
if (dirContainsAllTestLibs("$C{SDK_PATH}$TESTLIBDIR")) {
|
|
$C{TESTLIBDIR} = "$C{SDK_PATH}$TESTLIBDIR";
|
|
} else {
|
|
# We don't actually check in / because on devices
|
|
# there are no dylib files there.
|
|
$C{TESTLIBDIR} = $TESTLIBDIR;
|
|
}
|
|
}
|
|
|
|
@{$C{TESTLIBS}} = map { "$C{TESTLIBDIR}/$_" } @TESTLIBNAMES;
|
|
# convenience for tests that want libobjc.dylib's path
|
|
$C{TESTLIB} = @{$C{TESTLIBS}}[0];
|
|
|
|
foreach my $testlibname (@TESTLIBNAMES) {
|
|
if (-e "$symroot/$testlibname.dSYM") {
|
|
push(@{$C{TESTDSYMS}}, "$symroot/$testlibname.dSYM");
|
|
}
|
|
}
|
|
|
|
if ($VERBOSE) {
|
|
foreach my $testlib (@{$C{TESTLIBS}}) {
|
|
my @uuids = `/usr/bin/dwarfdump -u '$testlib'`;
|
|
while (my $uuid = shift @uuids) {
|
|
print "note: $uuid";
|
|
}
|
|
}
|
|
}
|
|
|
|
# Look up compilers
|
|
my $cc = $C{CC};
|
|
my $cxx = cplusplus($C{CC});
|
|
my $swift = swift($C{CC});
|
|
if (! $BUILD) {
|
|
$C{CC} = $cc;
|
|
$C{CXX} = $cxx;
|
|
$C{SWIFT} = $swift
|
|
} else {
|
|
$C{CC} = find_compiler($cc, $C{TOOLCHAIN}, $C{SDK_PATH});
|
|
$C{CXX} = find_compiler($cxx, $C{TOOLCHAIN}, $C{SDK_PATH});
|
|
$C{SWIFT} = find_compiler($swift, $C{TOOLCHAIN}, $C{SDK_PATH});
|
|
|
|
die "No C compiler '$cc' ('$C{CC}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{CC};
|
|
die "No C++ compiler '$cxx' ('$C{CXX}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{CXX};
|
|
die "No Swift compiler '$swift' ('$C{SWIFT}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{SWIFT};
|
|
}
|
|
|
|
if ($C{ARCH} eq "i386" && $C{OS} eq "macosx") {
|
|
# libarclite no longer available on i386
|
|
# fixme need an archived copy for bincompat testing
|
|
$C{FORCE_LOAD_ARCLITE} = "";
|
|
} elsif ($C{OS} eq "bridgeos") {
|
|
# no libarclite on bridgeOS
|
|
$C{FORCE_LOAD_ARCLITE} = "";
|
|
} else {
|
|
$C{FORCE_LOAD_ARCLITE} = "-Xlinker -force_load -Xlinker " . dirname($C{CC}) . "/../lib/arc/libarclite_$C{OS}.a";
|
|
}
|
|
|
|
# Populate cflags
|
|
|
|
my $cflags = "-I$DIR -W -Wall -Wno-objc-weak-compat -Wno-arc-bridge-casts-disallowed-in-nonarc -Wshorten-64-to-32 -Qunused-arguments -fno-caret-diagnostics -Os -arch $C{ARCH} ";
|
|
if (!$BATS) {
|
|
# save-temps so dsymutil works so debug info works.
|
|
# Disabled in BATS to save disk space.
|
|
# rdar://45656803 -save-temps causes bad -Wstdlibcxx-not-found warnings
|
|
$cflags .= "-g -save-temps -Wno-stdlibcxx-not-found";
|
|
}
|
|
my $objcflags = "";
|
|
my $swiftflags = "-g ";
|
|
|
|
$cflags .= " -isysroot '$C{SDK_PATH}'";
|
|
$cflags .= " '-Wl,-syslibroot,$C{SDK_PATH}'";
|
|
$swiftflags .= " -sdk '$C{SDK_PATH}'";
|
|
|
|
# Set deployment target cflags
|
|
my $target = undef;
|
|
die "No deployment target" if $C{DEPLOYMENT_TARGET} eq "";
|
|
if ($C{OS} eq "iphoneos") {
|
|
$cflags .= " -mios-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-ios$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
elsif ($C{OS} eq "iphonesimulator") {
|
|
$cflags .= " -mios-simulator-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-ios$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
elsif ($C{OS} eq "watchos") {
|
|
$cflags .= " -mwatchos-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-watchos$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
elsif ($C{OS} eq "watchsimulator") {
|
|
$cflags .= " -mwatchos-simulator-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-watchos$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
elsif ($C{OS} eq "appletvos") {
|
|
$cflags .= " -mtvos-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-tvos$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
elsif ($C{OS} eq "appletvsimulator") {
|
|
$cflags .= " -mtvos-simulator-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-tvos$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
elsif ($C{OS} eq "bridgeos") {
|
|
$cflags .= " -mbridgeos-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-bridgeos$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
else {
|
|
$cflags .= " -mmacosx-version-min=$C{DEPLOYMENT_TARGET}";
|
|
$target = "$C{ARCH}-apple-macosx$C{DEPLOYMENT_TARGET}";
|
|
}
|
|
$swiftflags .= " -target $target";
|
|
|
|
$C{TESTINCLUDEDIR} = "$C{SDK_PATH}/usr/include";
|
|
$C{TESTLOCALINCLUDEDIR} = "$C{SDK_PATH}/usr/local/include";
|
|
if ($root ne "") {
|
|
if ($C{SDK_PATH} ne "/") {
|
|
$cflags .= " -isystem '$root$C{SDK_PATH}/usr/include'";
|
|
$cflags .= " -isystem '$root$C{SDK_PATH}/usr/local/include'";
|
|
}
|
|
|
|
my $library_path = $C{TESTLIBDIR};
|
|
$cflags .= " -L$library_path";
|
|
$C{TESTINCLUDEDIR} = findIncludeDir($root, "usr/include");
|
|
$C{TESTLOCALINCLUDEDIR} = findIncludeDir($root, "usr/local/include");
|
|
$cflags .= " -isystem '$C{TESTINCLUDEDIR}'";
|
|
$cflags .= " -isystem '$C{TESTLOCALINCLUDEDIR}'";
|
|
}
|
|
|
|
|
|
# Populate objcflags
|
|
|
|
$objcflags .= " -lobjc";
|
|
if ($C{MEM} eq "arc") {
|
|
$objcflags .= " -fobjc-arc";
|
|
}
|
|
elsif ($C{MEM} eq "mrc") {
|
|
# nothing
|
|
}
|
|
else {
|
|
die "unrecognized MEM '$C{MEM}'\n";
|
|
}
|
|
|
|
# Populate ENV_PREFIX
|
|
$C{ENV} = "LANG=C MallocScribble=1";
|
|
$C{ENV} .= " VERBOSE=$VERBOSE" if $VERBOSE;
|
|
if ($root ne "") {
|
|
die "no spaces allowed in root" if $C{TESTLIBDIR} =~ /\s+/;
|
|
}
|
|
if ($C{GUARDMALLOC}) {
|
|
$C{ENV} .= " GUARDMALLOC=1"; # checked by tests and errcheck.pl
|
|
$C{ENV} .= " DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib";
|
|
if ($C{GUARDMALLOC} eq "before") {
|
|
$C{ENV} .= " MALLOC_PROTECT_BEFORE=1";
|
|
} elsif ($C{GUARDMALLOC} eq "after") {
|
|
# protect after is the default
|
|
} else {
|
|
die "Unknown guard malloc mode '$C{GUARDMALLOC}'\n";
|
|
}
|
|
}
|
|
|
|
# Populate compiler commands
|
|
$C{XCRUN} = "env LANG=C /usr/bin/xcrun -toolchain '$C{TOOLCHAIN}'";
|
|
|
|
$C{COMPILE_C} = "$C{XCRUN} '$C{CC}' $cflags -x c -std=gnu99";
|
|
$C{COMPILE_CXX} = "$C{XCRUN} '$C{CXX}' $cflags -x c++ -std=gnu++17";
|
|
$C{COMPILE_M} = "$C{XCRUN} '$C{CC}' $cflags $objcflags -x objective-c -std=gnu99";
|
|
$C{COMPILE_MM} = "$C{XCRUN} '$C{CXX}' $cflags $objcflags -x objective-c++ -std=gnu++17";
|
|
$C{COMPILE_SWIFT} = "$C{XCRUN} '$C{SWIFT}' $swiftflags";
|
|
|
|
$C{COMPILE} = $C{COMPILE_C} if $C{LANGUAGE} eq "c";
|
|
$C{COMPILE} = $C{COMPILE_CXX} if $C{LANGUAGE} eq "c++";
|
|
$C{COMPILE} = $C{COMPILE_M} if $C{LANGUAGE} eq "objective-c";
|
|
$C{COMPILE} = $C{COMPILE_MM} if $C{LANGUAGE} eq "objective-c++";
|
|
$C{COMPILE} = $C{COMPILE_SWIFT} if $C{LANGUAGE} eq "swift";
|
|
die "unknown language '$C{LANGUAGE}'\n" if !defined $C{COMPILE};
|
|
|
|
($C{COMPILE_NOMEM} = $C{COMPILE}) =~ s/ -fobjc-arc\S*//g;
|
|
($C{COMPILE_NOLINK} = $C{COMPILE}) =~ s/ '?-(?:Wl,|l)\S*//g;
|
|
($C{COMPILE_NOLINK_NOMEM} = $C{COMPILE_NOMEM}) =~ s/ '?-(?:Wl,|l)\S*//g;
|
|
|
|
|
|
# Reject some self-inconsistent and disallowed configurations
|
|
if ($C{MEM} !~ /^(mrc|arc)$/) {
|
|
die "unknown MEM=$C{MEM} (expected one of mrc arc)\n";
|
|
}
|
|
|
|
if ($C{MEM} eq "arc" && $C{CC} !~ /clang/) {
|
|
print "note: skipping configuration $C{NAME}\n";
|
|
print "note: because CC=$C{CC} does not support MEM=$C{MEM}\n";
|
|
return 0;
|
|
}
|
|
|
|
if ($C{ARCH} eq "i386" && $C{OS} eq "macosx") {
|
|
colorprint $yellow, "WARN: skipping configuration $C{NAME}\n";
|
|
colorprint $yellow, "WARN: because 32-bit Mac is dead\n";
|
|
return 0;
|
|
}
|
|
|
|
# fixme
|
|
if ($C{LANGUAGE} eq "swift" && $C{ARCH} =~ /^arm/) {
|
|
print "note: skipping configuration $C{NAME}\n";
|
|
print "note: because ARCH=$C{ARCH} does not support LANGUAGE=SWIFT\n";
|
|
return 0;
|
|
}
|
|
|
|
# fixme unimplemented run targets
|
|
if ($C{RUN_TARGET} ne "default" && $C{OS} !~ /simulator/) {
|
|
colorprint $yellow, "WARN: skipping configuration $C{NAME}";
|
|
colorprint $yellow, "WARN: because OS=$C{OS} does not yet implement RUN_TARGET=$C{RUN_TARGET}";
|
|
}
|
|
|
|
%$configref = %C;
|
|
}
|
|
|
|
sub make_configs {
|
|
my ($root, %args) = @_;
|
|
|
|
my @results = ({}); # start with one empty config
|
|
|
|
for my $key (keys %args) {
|
|
my @newresults;
|
|
my @values = @{$args{$key}};
|
|
for my $configref (@results) {
|
|
my %config = %{$configref};
|
|
for my $value (@values) {
|
|
my %newconfig = %config;
|
|
$newconfig{$key} = $value;
|
|
push @newresults, \%newconfig;
|
|
}
|
|
}
|
|
@results = @newresults;
|
|
}
|
|
|
|
my @newresults;
|
|
for my $configref(@results) {
|
|
if (make_one_config($configref, $root)) {
|
|
push @newresults, $configref;
|
|
}
|
|
}
|
|
|
|
return @newresults;
|
|
}
|
|
|
|
sub config_dir_name {
|
|
my %config = @_;
|
|
my $name = "";
|
|
for my $key (sort keys %config) {
|
|
# Exclude settings that only influence the run, not the build.
|
|
next if $key eq "DYLD" || $key eq "GUARDMALLOC";
|
|
|
|
$name .= '~' if $name ne "";
|
|
$name .= "$key=$config{$key}";
|
|
}
|
|
return $name;
|
|
}
|
|
|
|
sub rsync_ios {
|
|
my ($src, $timeout) = @_;
|
|
for (my $i = 0; $i < 10; $i++) {
|
|
make("$DIR/timeout.pl $timeout rsync -e 'ssh $PORT' -av $src $HOST:/$REMOTEBASE/");
|
|
return if $? == 0;
|
|
colorprint $yellow, "WARN: RETRY\n" if $VERBOSE;
|
|
}
|
|
die "Couldn't rsync tests to device. Check: device is connected; tcprelay is running; device trusts your Mac; device is unlocked; filesystem is mounted r/w\n";
|
|
}
|
|
|
|
sub build_and_run_one_config {
|
|
my %C = %{shift()};
|
|
my @tests = @_;
|
|
|
|
# Build and run
|
|
my $testcount = 0;
|
|
my $failcount = 0;
|
|
my $skipconfig = 0;
|
|
|
|
my @gathertests;
|
|
foreach my $test (@tests) {
|
|
if ($VERBOSE) {
|
|
print "\nGATHER $test\n";
|
|
}
|
|
|
|
if ($ALL_TESTS{$test}) {
|
|
gather_simple(\%C, $test) || next; # not pass, not fail
|
|
push @gathertests, $test;
|
|
} elsif ($has_match_glob) {
|
|
my @matched = Text::Glob::match_glob($test, (keys %ALL_TESTS));
|
|
if (not @matched) {
|
|
die "No test matched '$test'\n";
|
|
}
|
|
foreach my $match (@matched) {
|
|
gather_simple(\%C, $match) || next; # not pass, not fail
|
|
push @gathertests, $match;
|
|
}
|
|
}
|
|
}
|
|
|
|
my @builttests;
|
|
if (!$BUILD) {
|
|
@builttests = @gathertests;
|
|
$testcount = scalar(@gathertests);
|
|
} elsif ($PARALLELBUILDS > 1 && $supportsParallelBuilds) {
|
|
my $workQueue = Thread::Queue->new();
|
|
my $resultsQueue = Thread::Queue->new();
|
|
my @threads = map {
|
|
threads->create(sub {
|
|
while (defined(my $test = $workQueue->dequeue())) {
|
|
local *STDOUT;
|
|
local *STDERR;
|
|
my $output;
|
|
open STDOUT, '>>', \$output;
|
|
open STDERR, '>>', \$output;
|
|
|
|
my $success = build_simple(\%C, $test);
|
|
$resultsQueue->enqueue({ test => $test, success => $success, output => $output });
|
|
}
|
|
});
|
|
} (1 .. $PARALLELBUILDS);
|
|
|
|
foreach my $test (@gathertests) {
|
|
if ($VERBOSE) {
|
|
print "\nBUILD $test\n";
|
|
}
|
|
if ($ALL_TESTS{$test}) {
|
|
$testcount++;
|
|
$workQueue->enqueue($test);
|
|
} else {
|
|
die "No test named '$test'\n";
|
|
}
|
|
}
|
|
$workQueue->end();
|
|
foreach (@gathertests) {
|
|
my $result = $resultsQueue->dequeue();
|
|
my $test = $result->{test};
|
|
my $success = $result->{success};
|
|
my $output = $result->{output};
|
|
|
|
print $output;
|
|
if ($success) {
|
|
push @builttests, $test;
|
|
} else {
|
|
$failcount++;
|
|
}
|
|
}
|
|
foreach my $thread (@threads) {
|
|
$thread->join();
|
|
}
|
|
} else {
|
|
if ($PARALLELBUILDS > 1) {
|
|
print "WARNING: requested parallel builds, but this perl interpreter does not support threads. Falling back to sequential builds.\n";
|
|
}
|
|
foreach my $test (@gathertests) {
|
|
if ($VERBOSE) {
|
|
print "\nBUILD $test\n";
|
|
}
|
|
|
|
if ($ALL_TESTS{$test}) {
|
|
$testcount++;
|
|
if (!build_simple(\%C, $test)) {
|
|
$failcount++;
|
|
} else {
|
|
push @builttests, $test;
|
|
}
|
|
} else {
|
|
die "No test named '$test'\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$RUN || !scalar(@builttests)) {
|
|
# nothing to do
|
|
}
|
|
else {
|
|
if ($HOST && $C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) {
|
|
# upload timeout - longer for slow watch devices
|
|
my $timeout = ($C{OS} =~ /watch/) ? 120 : 20;
|
|
|
|
# upload all tests to iOS device
|
|
rsync_ios($C{DSTDIR}, $timeout);
|
|
|
|
# upload library to iOS device
|
|
if ($C{TESTLIBDIR} ne $TESTLIBDIR) {
|
|
foreach my $thing (@{$C{TESTLIBS}}, @{$C{TESTDSYMS}}) {
|
|
rsync_ios($thing, $timeout);
|
|
}
|
|
}
|
|
}
|
|
elsif ($C{OS} =~ /simulator/) {
|
|
# run locally in a simulator
|
|
}
|
|
else {
|
|
# run locally
|
|
if ($BATS) {
|
|
# BATS execution tries to run architectures that
|
|
# aren't supported by the device. Skip those configs here.
|
|
my $machine = `machine`;
|
|
chomp $machine;
|
|
# unsupported:
|
|
# running arm64e on non-arm64e device
|
|
# running arm64 on non-arm64* device
|
|
# running armv7k on non-armv7k device
|
|
# running arm64_32 on armv7k device
|
|
# We don't need to handle all mismatches here,
|
|
# only mismatches that arise within a single OS.
|
|
$skipconfig =
|
|
(($C{ARCH} eq "arm64e" && $machine ne "arm64e") ||
|
|
($C{ARCH} eq "arm64" && $machine !~ /^arm64/) ||
|
|
($C{ARCH} eq "armv7k" && $machine ne "armv7k") ||
|
|
($C{ARCH} eq "arm64_32" && $machine eq "armv7k"));
|
|
if ($skipconfig) {
|
|
print "note: skipping configuration $C{NAME}\n";
|
|
print "note: because test arch $C{ARCH} is not " .
|
|
"supported on device arch $machine\n";
|
|
$testcount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$skipconfig) {
|
|
foreach my $test (@builttests) {
|
|
print "\nRUN $test\n" if ($VERBOSE);
|
|
|
|
if ($ALL_TESTS{$test}) {
|
|
if (!run_simple(\%C, $test)) {
|
|
$failcount++;
|
|
}
|
|
} else {
|
|
die "No test named '$test'\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ($testcount, $failcount, $skipconfig);
|
|
}
|
|
|
|
|
|
|
|
# Return value if set by "$argname=value" on the command line
|
|
# Return $default if not set.
|
|
sub getargs {
|
|
my ($argname, $default) = @_;
|
|
|
|
foreach my $arg (@ARGV) {
|
|
my ($value) = ($arg =~ /^$argname=(.+)$/);
|
|
return [split ',', $value] if defined $value;
|
|
}
|
|
|
|
return [split ',', $default];
|
|
}
|
|
|
|
# Return 1 or 0 if set by "$argname=1" or "$argname=0" on the
|
|
# command line. Return $default if not set.
|
|
sub getbools {
|
|
my ($argname, $default) = @_;
|
|
|
|
my @values = @{getargs($argname, $default)};
|
|
return [( map { ($_ eq "0") ? 0 : 1 } @values )];
|
|
}
|
|
|
|
# Return an integer if set by "$argname=value" on the
|
|
# command line. Return $default if not set.
|
|
sub getints {
|
|
my ($argname, $default) = @_;
|
|
|
|
my @values = @{getargs($argname, $default)};
|
|
return [( map { int($_) } @values )];
|
|
}
|
|
|
|
sub getarg {
|
|
my ($argname, $default) = @_;
|
|
my @values = @{getargs($argname, $default)};
|
|
die "Only one value allowed for $argname\n" if @values > 1;
|
|
return $values[0];
|
|
}
|
|
|
|
sub getbool {
|
|
my ($argname, $default) = @_;
|
|
my @values = @{getbools($argname, $default)};
|
|
die "Only one value allowed for $argname\n" if @values > 1;
|
|
return $values[0];
|
|
}
|
|
|
|
sub getint {
|
|
my ($argname, $default) = @_;
|
|
my @values = @{getints($argname, $default)};
|
|
die "Only one value allowed for $argname\n" if @values > 1;
|
|
return $values[0];
|
|
}
|
|
|
|
|
|
my $default_arch = "x86_64";
|
|
$args{ARCH} = getargs("ARCH", 0);
|
|
$args{ARCH} = getargs("ARCHS", $default_arch) if !@{$args{ARCH}}[0];
|
|
|
|
$args{OSVERSION} = getargs("OS", "macosx-default-default");
|
|
|
|
$args{MEM} = getargs("MEM", "mrc,arc");
|
|
$args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "c,objective-c,c++,objective-c++")} ];
|
|
|
|
$args{BUILD_SHARED_CACHE} = getargs("BUILD_SHARED_CACHE", 0);
|
|
|
|
$args{DYLD} = getargs("DYLD", "2,3");
|
|
|
|
$args{CC} = getargs("CC", "clang");
|
|
|
|
$HOST = getarg("HOST", 0);
|
|
$PORT = getarg("PORT", "");
|
|
if ($PORT) {
|
|
$PORT = "-p $PORT";
|
|
}
|
|
$DEVICE = getarg("DEVICE", "booted");
|
|
|
|
$PARALLELBUILDS = getarg("PARALLELBUILDS", `sysctl -n hw.ncpu`);
|
|
|
|
$SHAREDCACHEDIR = getarg("SHAREDCACHEDIR", "");
|
|
|
|
{
|
|
my $guardmalloc = getargs("GUARDMALLOC", 0);
|
|
# GUARDMALLOC=1 is the same as GUARDMALLOC=before,after
|
|
my @guardmalloc2 = ();
|
|
for my $arg (@$guardmalloc) {
|
|
if ($arg == 1) { push @guardmalloc2, "before";
|
|
push @guardmalloc2, "after"; }
|
|
else { push @guardmalloc2, $arg }
|
|
}
|
|
$args{GUARDMALLOC} = \@guardmalloc2;
|
|
}
|
|
|
|
$BUILD = getbool("BUILD", 1);
|
|
$RUN = getbool("RUN", 1);
|
|
$VERBOSE = getint("VERBOSE", 0);
|
|
$BATS = getbool("BATS", 0);
|
|
$BUILDDIR = getarg("BUILDDIR", $BATS ? $BATSBASE : $LOCALBASE);
|
|
|
|
my $root = getarg("ROOT", "");
|
|
$root =~ s#/*$##;
|
|
|
|
my @tests = gettests();
|
|
|
|
if ($BUILD) {
|
|
rm_rf_verbose "$DSTROOT$BUILDDIR";
|
|
rm_rf_verbose "$OBJROOT$BUILDDIR";
|
|
}
|
|
|
|
print "note: -----\n";
|
|
print "note: testing root '$root'\n";
|
|
|
|
my @configs = make_configs($root, %args);
|
|
|
|
print "note: -----\n";
|
|
print "note: testing ", scalar(@configs), " configurations:\n";
|
|
for my $configref (@configs) {
|
|
my $configname = $$configref{NAME};
|
|
print "note: configuration $configname\n";
|
|
}
|
|
|
|
my $failed = 0;
|
|
|
|
my $testconfigs = @configs;
|
|
my $failconfigs = 0;
|
|
my $skipconfigs = 0;
|
|
my $testcount = 0;
|
|
my $failcount = 0;
|
|
for my $configref (@configs) {
|
|
my $configname = $$configref{NAME};
|
|
print "note: -----\n";
|
|
print "note: \nnote: $configname\nnote: \n";
|
|
|
|
(my $t, my $f, my $skipconfig) =
|
|
eval { build_and_run_one_config($configref, @tests); };
|
|
$skipconfigs += $skipconfig;
|
|
if ($@) {
|
|
chomp $@;
|
|
colorprint $red, "FAIL: $configname";
|
|
colorprint $red, "FAIL: $@";
|
|
$failconfigs++;
|
|
} else {
|
|
my $color = ($f ? $red : "");
|
|
print "note:\n";
|
|
colorprint $color, "note: $configname\n";
|
|
colorprint $color, "note: $t tests, $f failures";
|
|
$testcount += $t;
|
|
$failcount += $f;
|
|
$failconfigs++ if ($f);
|
|
}
|
|
}
|
|
|
|
make("find $DSTROOT$BUILDDIR -name build-succeeded -delete", "/");
|
|
|
|
print "note: -----\n";
|
|
my $color = ($failconfigs ? $red : "");
|
|
colorprint $color, "note: $testconfigs configurations, " .
|
|
"$failconfigs with failures, $skipconfigs skipped";
|
|
colorprint $color, "note: $testcount tests, $failcount failures";
|
|
|
|
$failed = ($failconfigs ? 1 : 0);
|
|
|
|
|
|
if ($BUILD && $BATS && !$failed) {
|
|
# Collect BATS execution instructions for all tests.
|
|
# Each BATS "test" is all configurations together of one of our tests.
|
|
for my $testname (@tests) {
|
|
append_bats_test($testname);
|
|
}
|
|
|
|
# Write the BATS plist to disk.
|
|
my $json = encode_json(\%bats_plist);
|
|
my $filename = "$DSTROOT$BATSBASE/objc4.plist";
|
|
print "note: writing BATS config to $filename\n";
|
|
open(my $file, '>', $filename);
|
|
print $file $json;
|
|
close $file;
|
|
}
|
|
|
|
exit ($failed ? 1 : 0);
|