]> git.saurik.com Git - apple/objc4.git/blobdiff - test/test.pl
objc4-818.2.tar.gz
[apple/objc4.git] / test / test.pl
index 46c3d0350b295885b7da344fdc6b7844e2361b67..88221aa3c2d799404c6e6f607b85442507253dc8 100755 (executable)
@@ -6,6 +6,16 @@
 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.
@@ -13,6 +23,13 @@ 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`);
@@ -31,6 +48,8 @@ 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>
 
@@ -44,6 +63,8 @@ options:
     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:
 
@@ -108,6 +129,11 @@ my $BATS;
 
 my $HOST;
 my $PORT;
+my $DEVICE;
+
+my $PARALLELBUILDS;
+
+my $SHAREDCACHEDIR;
 
 my @TESTLIBNAMES = ("libobjc.A.dylib", "libobjc-trampolines.dylib");
 my $TESTLIBDIR = "/usr/lib";
@@ -223,31 +249,20 @@ my %languages_for_extension = (
 # 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", $_[0]);
+    my @cmds = split("\n", $cmdstr);
     die if scalar(@cmds) == 0;
     $? = 0;
     foreach my $cmd (@cmds) {
         chomp $cmd;
         next if $cmd =~ /^\s*$/;
         $cmd .= " 2>&1";
-        print "$cmd\n" if $VERBOSE;
-        eval {
-            local $SIG{ALRM} = sub { die "alarm\n" };
-            # Timeout after 600 seconds so a deadlocked test doesn't wedge the
-            # entire test suite. Increase to an hour for B&I builds.
-            if (exists $ENV{"RC_XBS"}) {
-                alarm 3600;
-            } else {
-                alarm 600;
-            }
-            $output .= `$cmd`;
-            alarm 0;
-        };
-        if ($@) {
-            die unless $@ eq "alarm\n";
-            $output .= "\nTIMED OUT";
+        if (defined $cwd) {
+            $cmd = "cd $cwd; $cmd";
         }
+        print "$cmd\n" if $VERBOSE;
+        $output .= `$cmd`;
         last if $?;
     }
     print "$output\n" if $VERBOSE;
@@ -262,7 +277,7 @@ sub chdir_verbose {
 
 sub rm_rf_verbose {
     my $dir = shift || die;
-    print "mkdir -p $dir\n" if $VERBOSE;
+    print "rm -rf $dir\n" if $VERBOSE;
     `rm -rf '$dir'`;
     die "couldn't rm -rf $dir" if $?;
 }
@@ -749,6 +764,7 @@ sub gather_simple {
     # 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>;
     
@@ -758,11 +774,15 @@ sub gather_simple {
     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);
+    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)";
@@ -828,6 +848,7 @@ sub gather_simple {
         TEST_RUN => $run,
         DSTDIR => "$C{DSTDIR}/$name.build",
         OBJDIR => "$C{OBJDIR}/$name.build",
+        ENTITLEMENTS => $entitlements,
     };
 
     return 1;
@@ -873,22 +894,34 @@ sub build_simple {
     my $name = shift;
     my %T = %{$C{"TEST_$name"}};
 
-    mkdir_verbose $T{DSTDIR};
-    chdir_verbose $T{DSTDIR};
+    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' > crashcatch.c`;
-        make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c");
-        die "$?" if $?;
+        `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);
+    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;
@@ -901,6 +934,7 @@ sub build_simple {
     $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;
@@ -943,23 +977,36 @@ sub build_simple {
     }
 
     if ($ok) {
-        foreach my $file (glob("*.exe *.dylib *.bundle")) {
+        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");
+                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
-                make("xcrun codesign -s - --entitlements $DIR/get_task_allow_entitlement.plist $file");
-                die "$?" if $?;
+                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;
 }
 
@@ -993,6 +1040,10 @@ sub run_simple {
         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/) {
@@ -1008,23 +1059,12 @@ sub run_simple {
             $env .= " DYLD_INSERT_LIBRARIES=$remotedir/libcrashcatch.dylib";
         }
 
-        my $cmd = "ssh -p $PORT $HOST 'cd $remotedir && env $env ./$name.exe'";
+        my $cmd = "ssh $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'";
+        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";
@@ -1138,11 +1178,11 @@ sub make_one_config {
 
     # 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;
+    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";
 
@@ -1404,9 +1444,9 @@ sub make_one_config {
     $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_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++";
+    $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";
@@ -1483,10 +1523,13 @@ sub make_configs {
     return @newresults;
 }
 
-sub config_name {
+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}";
     }
@@ -1496,7 +1539,7 @@ sub config_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/");
+        make("$DIR/timeout.pl $timeout rsync -e 'ssh $PORT' -av $src $HOST:/$REMOTEBASE/");
         return if $? == 0;
         colorprint $yellow, "WARN: RETRY\n"  if $VERBOSE;
     }
@@ -1521,8 +1564,15 @@ sub build_and_run_one_config {
         if ($ALL_TESTS{$test}) {
             gather_simple(\%C, $test) || next;  # not pass, not fail
             push @gathertests, $test;
-        } else {
-            die "No test named '$test'\n";
+        } 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;
+            }
         }
     }
 
@@ -1530,7 +1580,56 @@ sub build_and_run_one_config {
     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";
@@ -1553,7 +1652,7 @@ sub build_and_run_one_config {
         # nothing to do
     }
     else {
-        if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) {
+        if ($HOST && $C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) {
             # upload timeout - longer for slow watch devices
             my $timeout = ($C{OS} =~ /watch/) ? 120 : 20;
             
@@ -1686,8 +1785,16 @@ $args{DYLD} = getargs("DYLD", "2,3");
 
 $args{CC} = getargs("CC", "clang");
 
-$HOST = getarg("HOST", "iphone");
-$PORT = getarg("PORT", "10022");
+$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);    
@@ -1760,6 +1867,8 @@ for my $configref (@configs) {
     }
 }
 
+make("find $DSTROOT$BUILDDIR -name build-succeeded -delete", "/");
+
 print "note: -----\n";
 my $color = ($failconfigs ? $red : "");
 colorprint $color, "note: $testconfigs configurations, " .