1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738 |
- #!/usr/bin/perl
- # test.pl
- # Run unit tests.
- use strict;
- use File::Basename;
- # 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();
- }
- 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/
- 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?)
- 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 @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 $output = "";
- my @cmds = split("\n", $_[0]);
- die if scalar(@cmds) == 0;
- $? = 0;
- foreach my $cmd (@cmds) {
- chomp $cmd;
- next if $cmd =~ /^\s*$/;
- $cmd .= " 2>&1";
- 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 "mkdir -p $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
- 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 ($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);
- 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",
- };
- 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"}};
- mkdir_verbose $T{DSTDIR};
- chdir_verbose $T{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' > crashcatch.c`;
- make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c");
- die "$?" if $?;
- }
- my $cmd = $T{TEST_BUILD} ? eval "return \"$T{TEST_BUILD}\"" : "$C{COMPILE} $T{TEST_CFLAGS} $file -o $name.exe";
- my $output = make($cmd);
- # 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;
- # 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("*.exe *.dylib *.bundle")) {
- if (!$BATS) {
- # not for BATS to save space and build time
- # fixme use SYMROOT?
- make("xcrun dsymutil $file");
- }
- 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
- make("xcrun codesign -s - --entitlements $DIR/get_task_allow_entitlement.plist $file");
- die "$?" if $?;
- }
- }
- }
- 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";
- }
- 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 -p $PORT $HOST 'cd $remotedir && env $env ./$name.exe'";
- $output = make("$cmd");
- }
- elsif ($C{OS} =~ /simulator/) {
- # run locally in a simulator
- # fixme selection of simulated OS version
- my $simdevice;
- if ($C{OS} =~ /iphonesimulator/) {
- $simdevice = 'iPhone X';
- } elsif ($C{OS} =~ /watchsimulator/) {
- $simdevice = 'Apple Watch Series 4 - 40mm';
- } elsif ($C{OS} =~ /tvsimulator/) {
- $simdevice = 'Apple TV 1080p';
- } else {
- die "unknown simulator $C{OS}\n";
- }
- my $sim = "xcrun -sdk iphonesimulator simctl spawn '$simdevice'";
- # 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 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 $configname = config_name(%C);
- die if ($configname =~ /'/);
- die if ($configname =~ / /);
- ($C{NAME} = $configname) =~ s/~/ /g;
- (my $configdir = $configname) =~ 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";
- # fixme Root vs SDKContentRoot
- $C{TESTINCLUDEDIR} = "$root/../SDKContentRoot/usr/include";
- $C{TESTLOCALINCLUDEDIR} = "$root/../SDKContentRoot/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++";
- $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++";
- $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_name {
- my %config = @_;
- my $name = "";
- for my $key (sort keys %config) {
- $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 -p $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;
- } else {
- die "No test named '$test'\n";
- }
- }
- my @builttests;
- if (!$BUILD) {
- @builttests = @gathertests;
- $testcount = scalar(@gathertests);
- } else {
- 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 ($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{CC} = getargs("CC", "clang");
- $HOST = getarg("HOST", "iphone");
- $PORT = getarg("PORT", "10022");
- {
- 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);
- }
- }
- 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);
|