1#!/usr/bin/perl 2 3# test.pl 4# Run unit tests. 5 6use strict; 7use File::Basename; 8 9chdir dirname $0; 10chomp (my $DIR = `pwd`); 11 12# these names may be adjusted after reading the SDK setting 13my $TESTLIBNAME = "libsystem_blocks.dylib"; 14my $TESTLIBPATH = "/usr/lib/system/$TESTLIBNAME"; 15my $BUILDDIR = "/tmp/test-$TESTLIBNAME-build"; 16 17# xterm colors 18my $red = "\e[41;37m"; 19my $yellow = "\e[43;37m"; 20my $def = "\e[0m"; 21 22# clean, help 23if (scalar(@ARGV) == 1) { 24 my $arg = $ARGV[0]; 25 if ($arg eq "clean") { 26 my $cmd = "rm -rf $BUILDDIR *~"; 27 print "$cmd\n"; 28 `$cmd`; 29 exit 0; 30 } 31 elsif ($arg eq "-h" || $arg eq "-H" || $arg eq "-help" || $arg eq "help") { 32 print(<<END); 33usage: $0 [options] [testname ...] 34 $0 clean 35 $0 help 36 37testname: 38 `testname` runs a specific test. If no testnames are given, runs all tests. 39 40options: 41 ARCH=<arch> 42 SDK=<sdk name> 43 ROOT=/path/to/project.roots/ 44 45 CC=<compiler name> 46 47 MEM=mrc,arc,gc 48 STDLIB=libc++,libstdc++ 49 GUARDMALLOC=0|1 50 51 BUILD=0|1 52 RUN=0|1 53 VERBOSE=0|1 54 55examples: 56 57 test installed library, x86_64, no gc 58 $0 59 60 test buildit-built root, i386 and x86_64, MRC and ARC and GC, clang compiler 61 $0 ARCH=i386,x86_64 ROOT=/tmp/libclosure.roots MEM=mrc,arc,gc CC=clang 62 63 test buildit-built root with iOS simulator 64 $0 ARCH=i386 ROOT=/tmp/libclosure.roots SDK=iphonesimulator 65 66 test buildit-built root on attached iOS device 67 $0 ARCH=armv7 ROOT=/tmp/libclosure.roots SDK=iphoneos 68END 69 exit 0; 70 } 71} 72 73######################################################################### 74## Tests 75 76my %ALL_TESTS; 77 78######################################################################### 79## Variables for use in complex build and run rules 80 81# variable # example value 82 83# things you can multiplex on the command line 84# ARCH=i386,x86_64,armv6,armv7 85# SDK=system,macosx,iphoneos,iphonesimulator 86# LANGUAGE=c,c++,objective-c,objective-c++ 87# CC=clang,gcc-4.2,llvm-gcc-4.2 88# MEM=mrc,arc,gc 89# STDLIB=libc++,libstdc++ 90# GUARDMALLOC=0,1 91 92# things you can set once on the command line 93# ROOT=/path/to/project.roots 94# BUILD=0|1 95# RUN=0|1 96# VERBOSE=0|1 97 98 99 100my $BUILD; 101my $RUN; 102my $VERBOSE; 103 104my $crashcatch = <<'END'; 105// interpose-able code to catch crashes, print, and exit cleanly 106#include <signal.h> 107#include <string.h> 108#include <unistd.h> 109 110// from dyld-interposing.h 111#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 }; 112 113static void catchcrash(int sig) 114{ 115 const char *msg; 116 switch (sig) { 117 case SIGILL: msg = "CRASHED: SIGILL\\n"; break; 118 case SIGBUS: msg = "CRASHED: SIGBUS\\n"; break; 119 case SIGSYS: msg = "CRASHED: SIGSYS\\n"; break; 120 case SIGSEGV: msg = "CRASHED: SIGSEGV\\n"; break; 121 case SIGTRAP: msg = "CRASHED: SIGTRAP\\n"; break; 122 case SIGABRT: msg = "CRASHED: SIGABRT\\n"; break; 123 default: msg = "SIG\?\?\?\?\\n"; break; 124 } 125 write(STDERR_FILENO, msg, strlen(msg)); 126 _exit(0); 127} 128 129static void setupcrash(void) __attribute__((constructor)); 130static void setupcrash(void) 131{ 132 signal(SIGILL, &catchcrash); 133 signal(SIGBUS, &catchcrash); 134 signal(SIGSYS, &catchcrash); 135 signal(SIGSEGV, &catchcrash); 136 signal(SIGTRAP, &catchcrash); 137 signal(SIGABRT, &catchcrash); 138} 139 140 141static int hacked = 0; 142ssize_t hacked_write(int fildes, const void *buf, size_t nbyte) 143{ 144 if (!hacked) { 145 setupcrash(); 146 hacked = 1; 147 } 148 return write(fildes, buf, nbyte); 149} 150 151DYLD_INTERPOSE(hacked_write, write); 152 153END 154 155 156######################################################################### 157## Harness 158 159 160# map language to buildable extensions for that language 161my %extensions_for_language = ( 162 "c" => ["c"], 163 "objective-c" => ["c", "m"], 164 "c++" => ["c", "cc", "cp", "cpp", "cxx", "c++"], 165 "objective-c++" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm"], 166 167 "any" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm"], 168 ); 169 170# map extension to languages 171my %languages_for_extension = ( 172 "c" => ["c", "objective-c", "c++", "objective-c++"], 173 "m" => ["objective-c", "objective-c++"], 174 "mm" => ["objective-c++"], 175 "cc" => ["c++", "objective-c++"], 176 "cp" => ["c++", "objective-c++"], 177 "cpp" => ["c++", "objective-c++"], 178 "cxx" => ["c++", "objective-c++"], 179 "c++" => ["c++", "objective-c++"], 180 ); 181 182# Run some newline-separated commands like `make` would, stopping if any fail 183# run("cmd1 \n cmd2 \n cmd3") 184sub make { 185 my $output = ""; 186 my @cmds = split("\n", $_[0]); 187 die if scalar(@cmds) == 0; 188 $? = 0; 189 foreach my $cmd (@cmds) { 190 chomp $cmd; 191 next if $cmd =~ /^\s*$/; 192 $cmd .= " 2>&1"; 193 print "$cmd\n" if $VERBOSE; 194 $output .= `$cmd`; 195 last if $?; 196 } 197 print "$output\n" if $VERBOSE; 198 return $output; 199} 200 201sub chdir_verbose { 202 my $dir = shift; 203 chdir $dir || die; 204 print "cd $dir\n" if $VERBOSE; 205} 206 207 208# Return test names from the command line. 209# Returns all tests if no tests were named. 210sub gettests { 211 my @tests; 212 213 foreach my $arg (@ARGV) { 214 push @tests, $arg if ($arg !~ /=/ && $arg !~ /^-/); 215 } 216 217 opendir(my $dir, $DIR) || die; 218 while (my $file = readdir($dir)) { 219 my ($name, $ext) = ($file =~ /^([^.]+)\.([^.]+)$/); 220 next if ! $languages_for_extension{$ext}; 221 222 open(my $in, "< $file") || die "$file"; 223 my $contents = join "", <$in>; 224 die if defined $ALL_TESTS{$name}; 225 $ALL_TESTS{$name} = $ext if ($contents =~ m#^[/*\s]*TEST_#m); 226 close($in); 227 } 228 closedir($dir); 229 230 if (scalar(@tests) == 0) { 231 @tests = keys %ALL_TESTS; 232 } 233 234 @tests = sort @tests; 235 236 return @tests; 237} 238 239 240# Turn a C compiler name into a C++ compiler name. 241sub cplusplus { 242 my ($c) = @_; 243 if ($c =~ /cc/) { 244 $c =~ s/cc/\+\+/; 245 return $c; 246 } 247 return $c . "++"; # e.g. clang => clang++ 248} 249 250# Returns an array of all sdks from `xcodebuild -showsdks` 251my @sdks_memo; 252sub getsdks { 253 if (!@sdks_memo) { 254 @sdks_memo = ("system", `xcodebuild -showsdks` =~ /-sdk (.+)$/mg); 255 } 256 return @sdks_memo; 257} 258 259# Returns whether the given sdk supports -lauto 260sub supportslibauto { 261 my ($sdk) = @_; 262 return 1 if $sdk eq "system"; 263 return 1 if $sdk =~ /^macosx/; 264 return 0 if $sdk =~ /^iphone/; 265 die; 266} 267 268# print text with a colored prefix on each line 269sub colorprint { 270 my $color = shift; 271 while (my @lines = split("\n", shift)) { 272 for my $line (@lines) { 273 chomp $line; 274 print "$color $def$line\n"; 275 } 276 } 277} 278 279sub rewind { 280 seek($_[0], 0, 0); 281} 282 283# parse name=value,value pairs 284sub readconditions { 285 my ($conditionstring) = @_; 286 287 my %results; 288 my @conditions = ($conditionstring =~ /\w+=(?:[^\s,]+,?)+/g); 289 for my $condition (@conditions) { 290 my ($name, $values) = ($condition =~ /(\w+)=(.+)/); 291 $results{$name} = [split ',', $values]; 292 } 293 294 return %results; 295} 296 297# Get the name of the system SDK from sw_vers 298sub systemsdkname { 299 my @lines = `/usr/bin/sw_vers`; 300 my $name; 301 my $vers; 302 for my $line (@lines) { 303 ($name) = ($line =~ /^ProductName:\s+(.*)/) if !$name; 304 ($vers) = ($line =~ /^ProductVersion:\s+(.*)/) if !$vers; 305 } 306 307 $name =~ s/ //g; 308 $name = lc($name); 309 my $internal = ""; 310 if (-d "/usr/local/include/objc") { 311 if ($name eq "macosx") { 312 $internal = "internal"; 313 } else { 314 $internal = ".internal"; 315 } 316 } 317 return $name . $vers . $internal; 318} 319 320sub check_output { 321 my %C = %{shift()}; 322 my $name = shift; 323 my @output = @_; 324 325 my %T = %{$C{"TEST_$name"}}; 326 my @original_output = @output; 327 328 # Run result-checking passes, reducing @output each time 329 my $xit = 1; 330 my $bad = ""; 331 my $warn = ""; 332 my $runerror = $T{TEST_RUN_OUTPUT}; 333 filter_verbose(\@output); 334 $warn = filter_warn(\@output); 335 $bad |= filter_guardmalloc(\@output) if ($C{GUARDMALLOC}); 336 $bad |= filter_valgrind(\@output) if ($C{VALGRIND}); 337 $bad = filter_expected(\@output, \%C, $name) if ($bad eq ""); 338 $bad = filter_bad(\@output) if ($bad eq ""); 339 340 # OK line should be the only one left 341 $bad = "(output not 'OK: $name')" if ($bad eq "" && (scalar(@output) != 1 || $output[0] !~ /^OK: $name/)); 342 343 if ($bad ne "") { 344 my $red = "\e[41;37m"; 345 my $def = "\e[0m"; 346 print "${red}FAIL: /// test '$name' \\\\\\$def\n"; 347 colorprint($red, @original_output); 348 print "${red}FAIL: \\\\\\ test '$name' ///$def\n"; 349 print "${red}FAIL: $name: $bad$def\n"; 350 $xit = 0; 351 } 352 elsif ($warn ne "") { 353 my $yellow = "\e[43;37m"; 354 my $def = "\e[0m"; 355 print "${yellow}PASS: /// test '$name' \\\\\\$def\n"; 356 colorprint($yellow, @original_output); 357 print "${yellow}PASS: \\\\\\ test '$name' ///$def\n"; 358 print "PASS: $name (with warnings)\n"; 359 } 360 else { 361 print "PASS: $name\n"; 362 } 363 return $xit; 364} 365 366sub filter_expected 367{ 368 my $outputref = shift; 369 my %C = %{shift()}; 370 my $name = shift; 371 372 my %T = %{$C{"TEST_$name"}}; 373 my $check = $T{TEST_RUN_OUTPUT} || return ""; 374 375 my $bad = ""; 376 377 my $output = join("\n", @$outputref) . "\n"; 378 if ($output !~ /$check/s) { 379 $bad = "(run output does not match TEST_RUN_OUTPUT)"; 380 @$outputref = ("FAIL: $name"); 381 } else { 382 @$outputref = ("OK: $name"); # pacify later filter 383 } 384 385 return $bad; 386} 387 388sub filter_bad 389{ 390 my $outputref = shift; 391 my $bad = ""; 392 393 my @new_output; 394 for my $line (@$outputref) { 395 if ($line =~ /^BAD: (.*)/) { 396 $bad = "(failed)"; 397 } else { 398 push @new_output, $line; 399 } 400 } 401 402 @$outputref = @new_output; 403 return $bad; 404} 405 406sub filter_warn 407{ 408 my $outputref = shift; 409 my $warn = ""; 410 411 my @new_output; 412 for my $line (@$outputref) { 413 if ($line !~ /^WARN: (.*)/) { 414 push @new_output, $line; 415 } else { 416 $warn = "(warned)"; 417 } 418 } 419 420 @$outputref = @new_output; 421 return $warn; 422} 423 424sub filter_verbose 425{ 426 my $outputref = shift; 427 428 my @new_output; 429 for my $line (@$outputref) { 430 if ($line !~ /^VERBOSE: (.*)/) { 431 push @new_output, $line; 432 } 433 } 434 435 @$outputref = @new_output; 436} 437 438sub filter_valgrind 439{ 440 my $outputref = shift; 441 my $errors = 0; 442 my $leaks = 0; 443 444 my @new_output; 445 for my $line (@$outputref) { 446 if ($line =~ /^Approx: do_origins_Dirty\([RW]\): missed \d bytes$/) { 447 # --track-origins warning (harmless) 448 next; 449 } 450 if ($line =~ /^UNKNOWN __disable_threadsignal is unsupported. This warning will not be repeated.$/) { 451 # signals unsupported (harmless) 452 next; 453 } 454 if ($line =~ /^UNKNOWN __pthread_sigmask is unsupported. This warning will not be repeated.$/) { 455 # signals unsupported (harmless) 456 next; 457 } 458 if ($line !~ /^^\.*==\d+==/) { 459 # not valgrind output 460 push @new_output, $line; 461 next; 462 } 463 464 my ($errcount) = ($line =~ /==\d+== ERROR SUMMARY: (\d+) errors/); 465 if (defined $errcount && $errcount > 0) { 466 $errors = 1; 467 } 468 469 (my $leakcount) = ($line =~ /==\d+==\s+(?:definitely|possibly) lost:\s+([0-9,]+)/); 470 if (defined $leakcount && $leakcount > 0) { 471 $leaks = 1; 472 } 473 } 474 475 @$outputref = @new_output; 476 477 my $bad = ""; 478 $bad .= "(valgrind errors)" if ($errors); 479 $bad .= "(valgrind leaks)" if ($leaks); 480 return $bad; 481} 482 483sub filter_guardmalloc 484{ 485 my $outputref = shift; 486 my $errors = 0; 487 488 my @new_output; 489 my $count = 0; 490 for my $line (@$outputref) { 491 if ($line !~ /^GuardMalloc\[[^\]]+\]: /) { 492 # not guardmalloc output 493 push @new_output, $line; 494 next; 495 } 496 497 # Ignore 4 lines of guardmalloc prologue. 498 # Anything further is a guardmalloc error. 499 if (++$count > 4) { 500 $errors = 1; 501 } 502 } 503 504 @$outputref = @new_output; 505 506 my $bad = ""; 507 $bad .= "(guardmalloc errors)" if ($errors); 508 return $bad; 509} 510 511sub gather_simple { 512 my $CREF = shift; 513 my %C = %{$CREF}; 514 my $name = shift; 515 chdir_verbose $DIR; 516 517 my $ext = $ALL_TESTS{$name}; 518 my $file = "$name.$ext"; 519 return 0 if !$file; 520 521 # search file for 'TEST_CONFIG' or '#include "test.h"' 522 # also collect other values: 523 # TEST_CONFIG test conditions 524 # TEST_ENV environment prefix 525 # TEST_CFLAGS compile flags 526 # TEST_BUILD build instructions 527 # TEST_BUILD_OUTPUT expected build stdout/stderr 528 # TEST_RUN_OUTPUT expected run stdout/stderr 529 open(my $in, "< $file") || die; 530 my $contents = join "", <$in>; 531 532 my $test_h = ($contents =~ /^\s*#\s*(include|import)\s*"test\.h"/m); 533 my $disabled = ($contents =~ /\bTEST_DISABLED\b/m); 534 my $crashes = ($contents =~ /\bTEST_CRASHES\b/m); 535 my ($conditionstring) = ($contents =~ /\bTEST_CONFIG\b(.*)$/m); 536 my ($envstring) = ($contents =~ /\bTEST_ENV\b(.*)$/m); 537 my ($cflags) = ($contents =~ /\bTEST_CFLAGS\b(.*)$/m); 538 my ($buildcmd) = ($contents =~ /TEST_BUILD\n(.*?\n)END[ *\/]*\n/s); 539 my ($builderror) = ($contents =~ /TEST_BUILD_OUTPUT\n(.*?\n)END[ *\/]*\n/s); 540 my ($runerror) = ($contents =~ /TEST_RUN_OUTPUT\n(.*?\n)END[ *\/]*\n/s); 541 542 return 0 if !$test_h && !$disabled && !$crashes && !defined($conditionstring) && !defined($envstring) && !defined($cflags) && !defined($buildcmd) && !defined($builderror) && !defined($runerror); 543 544 if ($disabled) { 545 print "${yellow}SKIP: $name (disabled by TEST_DISABLED)$def\n"; 546 return 0; 547 } 548 549 # check test conditions 550 551 my $run = 1; 552 my %conditions = readconditions($conditionstring); 553 if (! $conditions{LANGUAGE}) { 554 # implicit language restriction from file extension 555 $conditions{LANGUAGE} = $languages_for_extension{$ext}; 556 } 557 for my $condkey (keys %conditions) { 558 my @condvalues = @{$conditions{$condkey}}; 559 560 # special case: RUN=0 does not affect build 561 if ($condkey eq "RUN" && @condvalues == 1 && $condvalues[0] == 0) { 562 $run = 0; 563 next; 564 } 565 566 my $testvalue = $C{$condkey}; 567 next if !defined($testvalue); 568 # testvalue is the configuration being run now 569 # condvalues are the allowed values for this test 570 571 # special case: look up the name of SDK "system" 572 if ($condkey eq "SDK" && $testvalue eq "system") { 573 $testvalue = systemsdkname(); 574 } 575 576 my $ok = 0; 577 for my $condvalue (@condvalues) { 578 579 # special case: objc and objc++ 580 if ($condkey eq "LANGUAGE") { 581 $condvalue = "objective-c" if $condvalue eq "objc"; 582 $condvalue = "objective-c++" if $condvalue eq "objc++"; 583 } 584 585 $ok = 1 if ($testvalue eq $condvalue); 586 587 # special case: SDK allows prefixes, and "system" is "macosx" 588 if ($condkey eq "SDK") { 589 $ok = 1 if ($testvalue =~ /^$condvalue/); 590 $ok = 1 if ($testvalue eq "system" && "macosx" =~ /^$condvalue/); 591 } 592 593 # special case: CC and CXX allow substring matches 594 if ($condkey eq "CC" || $condkey eq "CXX") { 595 $ok = 1 if ($testvalue =~ /$condvalue/); 596 } 597 598 last if $ok; 599 } 600 601 if (!$ok) { 602 my $plural = (@condvalues > 1) ? "one of: " : ""; 603 print "SKIP: $name ($condkey=$testvalue, but test requires $plural", join(' ', @condvalues), ")\n"; 604 return 0; 605 } 606 } 607 608 # builderror is multiple REs separated by OR 609 if (defined $builderror) { 610 $builderror =~ s/\nOR\n/\n|/sg; 611 $builderror = "^(" . $builderror . ")\$"; 612 } 613 # runerror is multiple REs separated by OR 614 if (defined $runerror) { 615 $runerror =~ s/\nOR\n/\n|/sg; 616 $runerror = "^(" . $runerror . ")\$"; 617 } 618 619 # save some results for build and run phases 620 $$CREF{"TEST_$name"} = { 621 TEST_BUILD => $buildcmd, 622 TEST_BUILD_OUTPUT => $builderror, 623 TEST_CRASHES => $crashes, 624 TEST_RUN_OUTPUT => $runerror, 625 TEST_CFLAGS => $cflags, 626 TEST_ENV => $envstring, 627 TEST_RUN => $run, 628 }; 629 630 return 1; 631} 632 633# Builds a simple test 634sub build_simple { 635 my %C = %{shift()}; 636 my $name = shift; 637 my %T = %{$C{"TEST_$name"}}; 638 chdir_verbose "$C{DIR}/$name.build"; 639 640 my $ext = $ALL_TESTS{$name}; 641 my $file = "$DIR/$name.$ext"; 642 643 if ($T{TEST_CRASHES}) { 644 `echo '$crashcatch' > crashcatch.c`; 645 make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c"); 646 die "$?" if $?; 647 } 648 649 my $cmd = $T{TEST_BUILD} ? eval "return \"$T{TEST_BUILD}\"" : "$C{COMPILE} $T{TEST_CFLAGS} $file -o $name.out"; 650 651 my $output = make($cmd); 652 653 my $ok; 654 if (my $builderror = $T{TEST_BUILD_OUTPUT}) { 655 # check for expected output and ignore $? 656 if ($output =~ /$builderror/s) { 657 $ok = 1; 658 } else { 659 print "${red}FAIL: /// test '$name' \\\\\\$def\n"; 660 colorprint $red, $output; 661 print "${red}FAIL: \\\\\\ test '$name' ///$def\n"; 662 print "${red}FAIL: $name (build output does not match TEST_BUILD_OUTPUT)$def\n"; 663 $ok = 0; 664 } 665 } elsif ($?) { 666 print "${red}FAIL: /// test '$name' \\\\\\$def\n"; 667 colorprint $red, $output; 668 print "${red}FAIL: \\\\\\ test '$name' ///$def\n"; 669 print "${red}FAIL: $name (build failed)$def\n"; 670 $ok = 0; 671 } elsif ($output ne "") { 672 print "${red}FAIL: /// test '$name' \\\\\\$def\n"; 673 colorprint $red, $output; 674 print "${red}FAIL: \\\\\\ test '$name' ///$def\n"; 675 print "${red}FAIL: $name (unexpected build output)$def\n"; 676 $ok = 0; 677 } else { 678 $ok = 1; 679 } 680 681 682 if ($ok) { 683 foreach my $file (glob("*.out *.dylib *.bundle")) { 684 make("dsymutil $file"); 685 } 686 } 687 688 return $ok; 689} 690 691# Run a simple test (testname.out, with error checking of stdout and stderr) 692sub run_simple { 693 my %C = %{shift()}; 694 my $name = shift; 695 my %T = %{$C{"TEST_$name"}}; 696 697 if (! $T{TEST_RUN}) { 698 print "PASS: $name (build only)\n"; 699 return 1; 700 } 701 else { 702 chdir_verbose "$C{DIR}/$name.build"; 703 } 704 705 my $env = "$C{ENV} $T{TEST_ENV}"; 706 if ($T{TEST_CRASHES}) { 707 $env .= " DYLD_INSERT_LIBRARIES=libcrashcatch.dylib"; 708 } 709 710 my $output; 711 712 if ($C{ARCH} =~ /^arm/ && `unamep -p` !~ /^arm/) { 713 # run on iOS device 714 715 my $remotedir = "/var/root/test/" . basename($C{DIR}) . "/$name.build"; 716 my $remotedyld = " DYLD_LIBRARY_PATH=$remotedir"; 717 $remotedyld .= ":/var/root/test/" if ($C{TESTLIB} ne $TESTLIBPATH); 718 719 # elide host-specific paths 720 $env =~ s/DYLD_LIBRARY_PATH=\S+//; 721 $env =~ s/DYLD_ROOT_PATH=\S+//; 722 723 my $cmd = "ssh iphone 'cd $remotedir && env $env $remotedyld ./$name.out'"; 724 $output = make("$cmd"); 725 } 726 else { 727 # run locally 728 729 my $cmd = "env $env ./$name.out"; 730 $output = make("sh -c '$cmd 2>&1' 2>&1"); 731 # need extra sh level to capture "sh: Illegal instruction" after crash 732 # fixme fail if $? except tests that expect to crash 733 } 734 735 return check_output(\%C, $name, split("\n", $output)); 736} 737 738 739my %compiler_memo; 740sub find_compiler { 741 my ($cc, $sdk, $sdk_path) = @_; 742 743 # memoize 744 my $key = $cc . ':' . $sdk; 745 my $result = $compiler_memo{$key}; 746 return $result if defined $result; 747 748 if (-e $cc) { 749 $result = $cc; 750 } elsif (-e "$sdk_path/$cc") { 751 $result = "$sdk_path/$cc"; 752 } elsif ($sdk eq "system" && -e "/usr/bin/$cc") { 753 $result = "/usr/bin/$cc"; 754 } elsif ($sdk eq "system") { 755 $result = `xcrun -find $cc 2>/dev/null`; 756 } else { 757 $result = `xcrun -sdk $sdk -find $cc 2>/dev/null`; 758 } 759 760 chomp $result; 761 $compiler_memo{$key} = $result; 762 return $result; 763} 764 765sub make_one_config { 766 my $configref = shift; 767 my $root = shift; 768 my %C = %{$configref}; 769 770 # Aliases 771 $C{LANGUAGE} = "objective-c" if $C{LANGUAGE} eq "objc"; 772 $C{LANGUAGE} = "objective-c++" if $C{LANGUAGE} eq "objc++"; 773 774 # Look up SDK 775 # Try exact match first. 776 # Then try lexically-last prefix match (so "macosx" => "macosx10.7internal"). 777 my @sdks = getsdks(); 778 if ($VERBOSE) { 779 print "Installed SDKs: @sdks\n"; 780 } 781 my $exactsdk = undef; 782 my $prefixsdk = undef; 783 foreach my $sdk (@sdks) { 784 my $SDK = $C{SDK}; 785 $exactsdk = $sdk if ($sdk eq $SDK); 786 # check for digits to prevent e.g. "iphone" => "iphonesimulator4.2" 787 $prefixsdk = $sdk if ($sdk =~ /^$SDK[0-9]/ && $sdk gt $prefixsdk); 788 } 789 if ($exactsdk) { 790 $C{SDK} = $exactsdk; 791 } elsif ($prefixsdk) { 792 $C{SDK} = $prefixsdk; 793 } else { 794 die "unknown SDK '$C{SDK}'\nInstalled SDKs: @sdks\n"; 795 } 796 797 # set the config name now, after massaging the language and sdk, 798 # but before adding other settings 799 my $configname = config_name(%C); 800 die if ($configname =~ /'/); 801 die if ($configname =~ / /); 802 ($C{NAME} = $configname) =~ s/~/ /g; 803 (my $configdir = $configname) =~ s#/##g; 804 $C{DIR} = "$BUILDDIR/$configdir"; 805 806 $C{SDK_PATH} = "/"; 807 if ($C{SDK} ne "system") { 808 ($C{SDK_PATH}) = (`xcodebuild -version -sdk $C{SDK} Path` =~ /^\s*(.+?)\s*$/); 809 } 810 811 # Look up test library (possible in root or SDK_PATH) 812 813 if (-e (glob "$root/*~dst")[0]) { 814 $root = (glob "$root/*~dst")[0]; 815 } 816 817 if ($root ne "" && -e "$root$C{SDK_PATH}$TESTLIBPATH") { 818 $C{TESTLIB} = "$root$C{SDK_PATH}$TESTLIBPATH"; 819 } elsif (-e "$root$TESTLIBPATH") { 820 $C{TESTLIB} = "$root$TESTLIBPATH"; 821 } elsif (-e "$root/$TESTLIBNAME") { 822 $C{TESTLIB} = "$root/$TESTLIBNAME"; 823 } else { 824 die "No $TESTLIBNAME in root '$root' and sdk '$C{SDK_PATH}'\n"; 825 } 826 827 if ($VERBOSE) { 828 my @uuids = `/usr/bin/dwarfdump -u '$C{TESTLIB}'`; 829 while (my $uuid = shift @uuids) { 830 print "note: $uuid"; 831 } 832 } 833 834 # Look up compilers 835 my $cc = $C{CC}; 836 my $cxx = cplusplus($C{CC}); 837 if (! $BUILD) { 838 $C{CC} = $cc; 839 $C{CXX} = $cxx; 840 } else { 841 $C{CC} = find_compiler($cc, $C{SDK}, $C{SDK_PATH}); 842 $C{CXX} = find_compiler($cxx, $C{SDK}, $C{SDK_PATH}); 843 844 die "No compiler '$cc' ('$C{CC}') in SDK '$C{SDK}'\n" if !-e $C{CC}; 845 die "No compiler '$cxx' ('$C{CXX}') in SDK '$C{SDK}'\n" if !-e $C{CXX}; 846 } 847 848 # Populate cflags 849 850 # save-temps so dsymutil works so debug info works 851 # fixme not save-temps because rdar://10062179 852 my $cflags = "-I$DIR -W -Wall -Wno-deprecated-declarations -Wshorten-64-to-32 -g -Os -arch $C{ARCH} "; 853 my $objcflags = ""; 854 855 if ($C{SDK} ne "system") { 856 $cflags .= " -isysroot '$C{SDK_PATH}'"; 857 $cflags .= " '-Wl,-syslibroot,$C{SDK_PATH}'"; 858 } 859 860 if ($C{SDK} =~ /^iphoneos[0-9]/ && $cflags !~ /-miphoneos-version-min/) { 861 my ($vers) = ($C{SDK} =~ /^iphoneos([0-9]+\.[0-9+])/); 862 $cflags .= " -miphoneos-version-min=$vers"; 863 } 864 if ($C{SDK} =~ /^iphonesimulator[0-9]/ && $cflags !~ /-D__IPHONE_OS_VERSION_MIN_REQUIRED/) { 865 my ($vers) = ($C{SDK} =~ /^iphonesimulator([0-9]+\.[0-9+])/); 866 $vers = int($vers * 10000); # 4.2 => 42000 867 $cflags .= " -D__IPHONE_OS_VERSION_MIN_REQUIRED=$vers"; 868 } 869 if ($C{SDK} =~ /^iphonesimulator/) { 870 $objcflags .= " -fobjc-abi-version=2 -fobjc-legacy-dispatch"; 871 } 872 873 if ($root ne "") { 874 my $library_path = dirname($C{TESTLIB}); 875 $cflags .= " -L$library_path"; 876 $cflags .= " -isystem '$root/usr/include'"; 877 $cflags .= " -isystem '$root/usr/local/include'"; 878 879 if ($C{SDK_PATH} ne "/") { 880 $cflags .= " -isystem '$root$C{SDK_PATH}/usr/include'"; 881 $cflags .= " -isystem '$root$C{SDK_PATH}/usr/local/include'"; 882 } 883 } 884 885 if ($C{CC} =~ /clang/) { 886 $cflags .= " -Qunused-arguments -fno-caret-diagnostics"; 887 $cflags .= " -stdlib=$C{STDLIB}"; 888 } 889 890 891 # Populate objcflags 892 893 $objcflags .= " -lobjc"; 894 if ($C{MEM} eq "gc") { 895 $objcflags .= " -fobjc-gc"; 896 } 897 elsif ($C{MEM} eq "arc") { 898 $objcflags .= " -fobjc-arc"; 899 } 900 elsif ($C{MEM} eq "mrc") { 901 # nothing 902 } 903 else { 904 die "unrecognized MEM '$C{MEM}'\n"; 905 } 906 907 if (supportslibauto($C{SDK})) { 908 # do this even for non-GC tests 909 $objcflags .= " -lauto"; 910 } 911 912 # Populate ENV_PREFIX 913 $C{ENV} = "LANG=C"; 914 $C{ENV} .= " VERBOSE=1" if $VERBOSE; 915 if ($root ne "") { 916 my $library_path = dirname($C{TESTLIB}); 917 die "no spaces allowed in root" if $library_path =~ /\s+/; 918 $C{ENV} .= " DYLD_LIBRARY_PATH=$library_path" if ($library_path ne "/usr/lib"); 919 } 920 if ($C{SDK_PATH} ne "/") { 921 die "no spaces allowed in sdk" if $C{SDK_PATH} =~ /\s+/; 922 $C{ENV} .= " DYLD_ROOT_PATH=$C{SDK_PATH}"; 923 } 924 if ($C{GUARDMALLOC}) { 925 $ENV{GUARDMALLOC} = "1"; # checked by tests and errcheck.pl 926 $C{ENV} .= " DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib"; 927 } 928 if ($C{SDK} =~ /^iphonesimulator[0-9]/) { 929 my ($vers) = ($C{SDK} =~ /^iphonesimulator([0-9]+\.[0-9+])/); 930 $C{ENV} .= 931 " CFFIXED_USER_HOME=$ENV{HOME}/Library/Application\\ Support/iPhone\\ Simulator/$vers" . 932 " IPHONE_SIMULATOR_ROOT=$C{SDK_PATH}" . 933 " IPHONE_SHARED_RESOURCES_DIRECTORY=$ENV{HOME}/Library/Application\\ Support/iPhone\\ Simulator/$vers"; 934 } 935 936 # Populate compiler commands 937 $C{COMPILE_C} = "env LANG=C '$C{CC}' $cflags -x c -std=gnu99"; 938 $C{COMPILE_CXX} = "env LANG=C '$C{CXX}' $cflags -x c++"; 939 $C{COMPILE_M} = "env LANG=C '$C{CC}' $cflags $objcflags -x objective-c -std=gnu99"; 940 $C{COMPILE_MM} = "env LANG=C '$C{CXX}' $cflags $objcflags -x objective-c++"; 941 942 $C{COMPILE} = $C{COMPILE_C} if $C{LANGUAGE} eq "c"; 943 $C{COMPILE} = $C{COMPILE_CXX} if $C{LANGUAGE} eq "c++"; 944 $C{COMPILE} = $C{COMPILE_M} if $C{LANGUAGE} eq "objective-c"; 945 $C{COMPILE} = $C{COMPILE_MM} if $C{LANGUAGE} eq "objective-c++"; 946 die "unknown language '$C{LANGUAGE}'\n" if !defined $C{COMPILE}; 947 948 ($C{COMPILE_NOMEM} = $C{COMPILE}) =~ s/ -fobjc-(?:gc|arc)\S*//g; 949 ($C{COMPILE_NOLINK} = $C{COMPILE}) =~ s/ '?-(?:Wl,|l)\S*//g; 950 ($C{COMPILE_NOLINK_NOMEM} = $C{COMPILE_NOMEM}) =~ s/ '?-(?:Wl,|l)\S*//g; 951 952 953 # Reject some self-inconsistent configurations 954 if ($C{MEM} !~ /^(mrc|arc|gc)$/) { 955 die "unknown MEM=$C{MEM} (expected one of mrc arc gc)\n"; 956 } 957 958 if ($C{MEM} eq "gc" && $C{SDK} =~ /^iphone/) { 959 print "note: skipping configuration $C{NAME}\n"; 960 print "note: because SDK=$C{SDK} does not support MEM=$C{MEM}\n"; 961 return 0; 962 } 963 if ($C{MEM} eq "arc" && $C{SDK} !~ /^iphone/ && $C{ARCH} eq "i386") { 964 print "note: skipping configuration $C{NAME}\n"; 965 print "note: because 32-bit Mac does not support MEM=$C{MEM}\n"; 966 return 0; 967 } 968 if ($C{MEM} eq "arc" && $C{CC} !~ /clang/) { 969 print "note: skipping configuration $C{NAME}\n"; 970 print "note: because CC=$C{CC} does not support MEM=$C{MEM}\n"; 971 return 0; 972 } 973 974 if ($C{STDLIB} ne "libstdc++" && $C{CC} !~ /clang/) { 975 print "note: skipping configuration $C{NAME}\n"; 976 print "note: because CC=$C{CC} does not support STDLIB=$C{STDLIB}\n"; 977 return 0; 978 } 979 980 %$configref = %C; 981} 982 983sub make_configs { 984 my ($root, %args) = @_; 985 986 my @results = ({}); # start with one empty config 987 988 for my $key (keys %args) { 989 my @newresults; 990 my @values = @{$args{$key}}; 991 for my $configref (@results) { 992 my %config = %{$configref}; 993 for my $value (@values) { 994 my %newconfig = %config; 995 $newconfig{$key} = $value; 996 push @newresults, \%newconfig; 997 } 998 } 999 @results = @newresults; 1000 } 1001 1002 my @newresults; 1003 for my $configref(@results) { 1004 if (make_one_config($configref, $root)) { 1005 push @newresults, $configref; 1006 } 1007 } 1008 1009 return @newresults; 1010} 1011 1012sub config_name { 1013 my %config = @_; 1014 my $name = ""; 1015 for my $key (sort keys %config) { 1016 $name .= '~' if $name ne ""; 1017 $name .= "$key=$config{$key}"; 1018 } 1019 return $name; 1020} 1021 1022sub run_one_config { 1023 my %C = %{shift()}; 1024 my @tests = @_; 1025 1026 # Build and run 1027 my $testcount = 0; 1028 my $failcount = 0; 1029 1030 my @gathertests; 1031 foreach my $test (@tests) { 1032 if ($VERBOSE) { 1033 print "\nGATHER $test\n"; 1034 } 1035 1036 if ($ALL_TESTS{$test}) { 1037 gather_simple(\%C, $test) || next; # not pass, not fail 1038 push @gathertests, $test; 1039 } else { 1040 die "No test named '$test'\n"; 1041 } 1042 } 1043 1044 my @builttests; 1045 if (!$BUILD) { 1046 @builttests = @gathertests; 1047 $testcount = scalar(@gathertests); 1048 } else { 1049 my $configdir = $C{DIR}; 1050 print $configdir, "\n" if $VERBOSE; 1051 mkdir $configdir || die; 1052 1053 foreach my $test (@gathertests) { 1054 if ($VERBOSE) { 1055 print "\nBUILD $test\n"; 1056 } 1057 mkdir "$configdir/$test.build" || die; 1058 1059 if ($ALL_TESTS{$test}) { 1060 $testcount++; 1061 if (!build_simple(\%C, $test)) { 1062 $failcount++; 1063 } else { 1064 push @builttests, $test; 1065 } 1066 } else { 1067 die "No test named '$test'\n"; 1068 } 1069 } 1070 } 1071 1072 if (!$RUN || !scalar(@builttests)) { 1073 # nothing to do 1074 } 1075 else { 1076 if ($C{ARCH} =~ /^arm/ && `unamep -p` !~ /^arm/) { 1077 # upload all tests to iOS device 1078 make("RSYNC_PASSWORD=alpine rsync -av $C{DIR} rsync://root\@localhost:10873/root/var/root/test/"); 1079 die "Couldn't rsync tests to device\n" if ($?); 1080 1081 # upload library to iOS device 1082 if ($C{TESTLIB} ne $TESTLIBPATH) { 1083 # hack - send thin library because device may use lib=armv7 1084 # even though app=armv6, and we want to set the lib's arch 1085 make("lipo -output /tmp/$TESTLIBNAME -thin $C{ARCH} $C{TESTLIB} || cp $C{TESTLIB} /tmp/$TESTLIBNAME"); 1086 die "Couldn't thin $C{TESTLIB} to $C{ARCH}\n" if ($?); 1087 make("RSYNC_PASSWORD=alpine rsync -av /tmp/$TESTLIBNAME rsync://root\@localhost:10873/root/var/root/test/"); 1088 die "Couldn't rsync $C{TESTLIB} to device\n" if ($?); 1089 } 1090 } 1091 1092 foreach my $test (@builttests) { 1093 print "\nRUN $test\n" if ($VERBOSE); 1094 1095 if ($ALL_TESTS{$test}) 1096 { 1097 if (!run_simple(\%C, $test)) { 1098 $failcount++; 1099 } 1100 } else { 1101 die "No test named '$test'\n"; 1102 } 1103 } 1104 } 1105 1106 return ($testcount, $failcount); 1107} 1108 1109 1110 1111# Return value if set by "$argname=value" on the command line 1112# Return $default if not set. 1113sub getargs { 1114 my ($argname, $default) = @_; 1115 1116 foreach my $arg (@ARGV) { 1117 my ($value) = ($arg =~ /^$argname=(.+)$/); 1118 return [split ',', $value] if defined $value; 1119 } 1120 1121 return [$default]; 1122} 1123 1124# Return 1 or 0 if set by "$argname=1" or "$argname=0" on the 1125# command line. Return $default if not set. 1126sub getbools { 1127 my ($argname, $default) = @_; 1128 1129 my @values = @{getargs($argname, $default)}; 1130 return [( map { ($_ eq "0") ? 0 : 1 } @values )]; 1131} 1132 1133sub getarg { 1134 my ($argname, $default) = @_; 1135 my @values = @{getargs($argname, $default)}; 1136 die "Only one value allowed for $argname\n" if @values > 1; 1137 return $values[0]; 1138} 1139 1140sub getbool { 1141 my ($argname, $default) = @_; 1142 my @values = @{getbools($argname, $default)}; 1143 die "Only one value allowed for $argname\n" if @values > 1; 1144 return $values[0]; 1145} 1146 1147 1148# main 1149my %args; 1150 1151 1152my $default_arch = (`/usr/sbin/sysctl hw.optional.x86_64` eq "hw.optional.x86_64: 1\n") ? "x86_64" : "i386"; 1153$args{ARCH} = getargs("ARCH", 0); 1154$args{ARCH} = getargs("ARCHS", $default_arch) if !@{$args{ARCH}}[0]; 1155 1156$args{SDK} = getargs("SDK", "system"); 1157 1158$args{MEM} = getargs("MEM", "mrc"); 1159$args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "objective-c")} ]; 1160$args{STDLIB} = getargs("STDLIB", "libstdc++"); 1161 1162$args{CC} = getargs("CC", "clang"); 1163 1164$args{GUARDMALLOC} = getbools("GUARDMALLOC", 0); 1165 1166$BUILD = getbool("BUILD", 1); 1167$RUN = getbool("RUN", 1); 1168$VERBOSE = getbool("VERBOSE", 0); 1169 1170my $root = getarg("ROOT", ""); 1171$root =~ s#/*$##; 1172 1173my @tests = gettests(); 1174 1175print "note: -----\n"; 1176print "note: testing root '$root'\n"; 1177 1178my @configs = make_configs($root, %args); 1179 1180print "note: -----\n"; 1181print "note: testing ", scalar(@configs), " configurations:\n"; 1182for my $configref (@configs) { 1183 my $configname = $$configref{NAME}; 1184 print "note: configuration $configname\n"; 1185} 1186 1187if ($BUILD) { 1188 `rm -rf '$BUILDDIR'`; 1189 mkdir "$BUILDDIR" || die; 1190} 1191 1192my $failed = 0; 1193 1194my $testconfigs = @configs; 1195my $failconfigs = 0; 1196my $testcount = 0; 1197my $failcount = 0; 1198for my $configref (@configs) { 1199 my $configname = $$configref{NAME}; 1200 print "note: -----\n"; 1201 print "note: \nnote: $configname\nnote: \n"; 1202 1203 (my $t, my $f) = eval { run_one_config($configref, @tests); }; 1204 if ($@) { 1205 chomp $@; 1206 print "${red}FAIL: $configname${def}\n"; 1207 print "${red}FAIL: $@${def}\n"; 1208 $failconfigs++; 1209 } else { 1210 my $color = ($f ? $red : ""); 1211 print "note:\n"; 1212 print "${color}note: $configname$def\n"; 1213 print "${color}note: $t tests, $f failures$def\n"; 1214 $testcount += $t; 1215 $failcount += $f; 1216 $failconfigs++ if ($f); 1217 } 1218} 1219 1220print "note: -----\n"; 1221my $color = ($failconfigs ? $red : ""); 1222print "${color}note: $testconfigs configurations, $failconfigs with failures$def\n"; 1223print "${color}note: $testcount tests, $failcount failures$def\n"; 1224 1225$failed = ($failconfigs ? 1 : 0); 1226 1227exit ($failed ? 1 : 0); 1228