1#!/usr/bin/env python 2# 3# Copyright 2008 the V8 project authors. All rights reserved. 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are 6# met: 7# 8# * Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# * Redistributions in binary form must reproduce the above 11# copyright notice, this list of conditions and the following 12# disclaimer in the documentation and/or other materials provided 13# with the distribution. 14# * Neither the name of Google Inc. nor the names of its 15# contributors may be used to endorse or promote products derived 16# from this software without specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 31from __future__ import print_function 32import logging 33import optparse 34import os 35import re 36import signal 37import subprocess 38import sys 39import tempfile 40import time 41import threading 42import utils 43import multiprocessing 44import errno 45import copy 46 47 48if sys.version_info >= (3, 5): 49 from importlib import machinery, util 50 def get_module(name, path): 51 loader_details = (machinery.SourceFileLoader, machinery.SOURCE_SUFFIXES) 52 spec = machinery.FileFinder(path, loader_details).find_spec(name) 53 module = util.module_from_spec(spec) 54 spec.loader.exec_module(module) 55 return module 56else: 57 import imp 58 def get_module(name, path): 59 file = None 60 try: 61 (file, pathname, description) = imp.find_module(name, [path]) 62 return imp.load_module(name, file, pathname, description) 63 finally: 64 if file: 65 file.close() 66 67 68from io import open 69from os.path import join, dirname, abspath, basename, isdir, exists 70from datetime import datetime 71try: 72 from queue import Queue, Empty # Python 3 73except ImportError: 74 from Queue import Queue, Empty # Python 2 75 76from functools import reduce 77 78try: 79 from urllib.parse import unquote # Python 3 80except ImportError: 81 from urllib import unquote # Python 2 82 83 84logger = logging.getLogger('testrunner') 85skip_regex = re.compile(r'# SKIP\S*\s+(.*)', re.IGNORECASE) 86 87VERBOSE = False 88 89os.umask(0o022) 90os.environ['NODE_OPTIONS'] = '' 91 92# --------------------------------------------- 93# --- P r o g r e s s I n d i c a t o r s --- 94# --------------------------------------------- 95 96 97class ProgressIndicator(object): 98 99 def __init__(self, cases, flaky_tests_mode): 100 self.cases = cases 101 self.flaky_tests_mode = flaky_tests_mode 102 self.parallel_queue = Queue(len(cases)) 103 self.sequential_queue = Queue(len(cases)) 104 for case in cases: 105 if case.parallel: 106 self.parallel_queue.put_nowait(case) 107 else: 108 self.sequential_queue.put_nowait(case) 109 self.succeeded = 0 110 self.remaining = len(cases) 111 self.total = len(cases) 112 self.failed = [ ] 113 self.flaky_failed = [ ] 114 self.crashed = 0 115 self.lock = threading.Lock() 116 self.shutdown_event = threading.Event() 117 118 def PrintFailureHeader(self, test): 119 if test.IsNegative(): 120 negative_marker = '[negative] ' 121 else: 122 negative_marker = '' 123 print("=== %(label)s %(negative)s===" % { 124 'label': test.GetLabel(), 125 'negative': negative_marker 126 }) 127 print("Path: %s" % "/".join(test.path)) 128 129 def Run(self, tasks): 130 self.Starting() 131 threads = [] 132 # Spawn N-1 threads and then use this thread as the last one. 133 # That way -j1 avoids threading altogether which is a nice fallback 134 # in case of threading problems. 135 for i in range(tasks - 1): 136 thread = threading.Thread(target=self.RunSingle, args=[True, i + 1]) 137 threads.append(thread) 138 thread.start() 139 try: 140 self.RunSingle(False, 0) 141 # Wait for the remaining threads 142 for thread in threads: 143 # Use a timeout so that signals (ctrl-c) will be processed. 144 thread.join(timeout=1000000) 145 except (KeyboardInterrupt, SystemExit): 146 self.shutdown_event.set() 147 except Exception: 148 # If there's an exception we schedule an interruption for any 149 # remaining threads. 150 self.shutdown_event.set() 151 # ...and then reraise the exception to bail out 152 raise 153 self.Done() 154 return not self.failed 155 156 def RunSingle(self, parallel, thread_id): 157 while not self.shutdown_event.is_set(): 158 try: 159 test = self.parallel_queue.get_nowait() 160 except Empty: 161 if parallel: 162 return 163 try: 164 test = self.sequential_queue.get_nowait() 165 except Empty: 166 return 167 case = test 168 case.thread_id = thread_id 169 self.lock.acquire() 170 self.AboutToRun(case) 171 self.lock.release() 172 try: 173 start = datetime.now() 174 output = case.Run() 175 # SmartOS has a bug that causes unexpected ECONNREFUSED errors. 176 # See https://smartos.org/bugview/OS-2767 177 # If ECONNREFUSED on SmartOS, retry the test one time. 178 if (output.UnexpectedOutput() and 179 sys.platform == 'sunos5' and 180 'ECONNREFUSED' in output.output.stderr): 181 output = case.Run() 182 output.diagnostic.append('ECONNREFUSED received, test retried') 183 case.duration = (datetime.now() - start) 184 except IOError: 185 return 186 if self.shutdown_event.is_set(): 187 return 188 self.lock.acquire() 189 if output.UnexpectedOutput(): 190 if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE: 191 self.flaky_failed.append(output) 192 else: 193 self.failed.append(output) 194 if output.HasCrashed(): 195 self.crashed += 1 196 else: 197 self.succeeded += 1 198 self.remaining -= 1 199 self.HasRun(output) 200 self.lock.release() 201 202 203def EscapeCommand(command): 204 parts = [] 205 for part in command: 206 if ' ' in part: 207 # Escape spaces. We may need to escape more characters for this 208 # to work properly. 209 parts.append('"%s"' % part) 210 else: 211 parts.append(part) 212 return " ".join(parts) 213 214 215class SimpleProgressIndicator(ProgressIndicator): 216 217 def Starting(self): 218 print('Running %i tests' % len(self.cases)) 219 220 def Done(self): 221 print() 222 for failed in self.failed: 223 self.PrintFailureHeader(failed.test) 224 if failed.output.stderr: 225 print("--- stderr ---") 226 print(failed.output.stderr.strip()) 227 if failed.output.stdout: 228 print("--- stdout ---") 229 print(failed.output.stdout.strip()) 230 print("Command: %s" % EscapeCommand(failed.command)) 231 if failed.HasCrashed(): 232 print("--- %s ---" % PrintCrashed(failed.output.exit_code)) 233 if failed.HasTimedOut(): 234 print("--- TIMEOUT ---") 235 if len(self.failed) == 0: 236 print("===") 237 print("=== All tests succeeded") 238 print("===") 239 else: 240 print() 241 print("===") 242 print("=== %i tests failed" % len(self.failed)) 243 if self.crashed > 0: 244 print("=== %i tests CRASHED" % self.crashed) 245 print("===") 246 247 248class VerboseProgressIndicator(SimpleProgressIndicator): 249 250 def AboutToRun(self, case): 251 print('Starting %s...' % case.GetLabel()) 252 sys.stdout.flush() 253 254 def HasRun(self, output): 255 if output.UnexpectedOutput(): 256 if output.HasCrashed(): 257 outcome = 'CRASH' 258 else: 259 outcome = 'FAIL' 260 else: 261 outcome = 'pass' 262 print('Done running %s: %s' % (output.test.GetLabel(), outcome)) 263 264 265class DotsProgressIndicator(SimpleProgressIndicator): 266 267 def AboutToRun(self, case): 268 pass 269 270 def HasRun(self, output): 271 total = self.succeeded + len(self.failed) 272 if (total > 1) and (total % 50 == 1): 273 sys.stdout.write('\n') 274 if output.UnexpectedOutput(): 275 if output.HasCrashed(): 276 sys.stdout.write('C') 277 sys.stdout.flush() 278 elif output.HasTimedOut(): 279 sys.stdout.write('T') 280 sys.stdout.flush() 281 else: 282 sys.stdout.write('F') 283 sys.stdout.flush() 284 else: 285 sys.stdout.write('.') 286 sys.stdout.flush() 287 288 289class TapProgressIndicator(SimpleProgressIndicator): 290 291 def _printDiagnostic(self): 292 logger.info(' severity: %s', self.severity) 293 self.exitcode and logger.info(' exitcode: %s', self.exitcode) 294 logger.info(' stack: |-') 295 296 for l in self.traceback.splitlines(): 297 logger.info(' ' + l) 298 299 def Starting(self): 300 logger.info('TAP version 13') 301 logger.info('1..%i' % len(self.cases)) 302 self._done = 0 303 304 def AboutToRun(self, case): 305 pass 306 307 def HasRun(self, output): 308 self._done += 1 309 self.traceback = '' 310 self.severity = 'ok' 311 self.exitcode = '' 312 313 # Print test name as (for example) "parallel/test-assert". Tests that are 314 # scraped from the addons documentation are all named test.js, making it 315 # hard to decipher what test is running when only the filename is printed. 316 prefix = abspath(join(dirname(__file__), '../test')) + os.sep 317 command = output.command[-1] 318 command = NormalizePath(command, prefix) 319 320 if output.UnexpectedOutput(): 321 status_line = 'not ok %i %s' % (self._done, command) 322 self.severity = 'fail' 323 self.exitcode = output.output.exit_code 324 self.traceback = output.output.stdout + output.output.stderr 325 326 if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE: 327 status_line = status_line + ' # TODO : Fix flaky test' 328 self.severity = 'flaky' 329 330 logger.info(status_line) 331 332 if output.HasCrashed(): 333 self.severity = 'crashed' 334 335 elif output.HasTimedOut(): 336 self.severity = 'fail' 337 338 else: 339 skip = skip_regex.search(output.output.stdout) 340 if skip: 341 logger.info( 342 'ok %i %s # skip %s' % (self._done, command, skip.group(1))) 343 else: 344 status_line = 'ok %i %s' % (self._done, command) 345 if FLAKY in output.test.outcomes: 346 status_line = status_line + ' # TODO : Fix flaky test' 347 logger.info(status_line) 348 349 if output.diagnostic: 350 self.severity = 'ok' 351 self.traceback = output.diagnostic 352 353 354 duration = output.test.duration 355 356 # total_seconds() was added in 2.7 357 total_seconds = (duration.microseconds + 358 (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6 359 360 # duration_ms is measured in seconds and is read as such by TAP parsers. 361 # It should read as "duration including ms" rather than "duration in ms" 362 logger.info(' ---') 363 logger.info(' duration_ms: %d.%d' % 364 (total_seconds, duration.microseconds / 1000)) 365 if self.severity != 'ok' or self.traceback != '': 366 if output.HasTimedOut(): 367 self.traceback = 'timeout\n' + output.output.stdout + output.output.stderr 368 self._printDiagnostic() 369 logger.info(' ...') 370 371 def Done(self): 372 pass 373 374class DeoptsCheckProgressIndicator(SimpleProgressIndicator): 375 376 def Starting(self): 377 pass 378 379 def AboutToRun(self, case): 380 pass 381 382 def HasRun(self, output): 383 # Print test name as (for example) "parallel/test-assert". Tests that are 384 # scraped from the addons documentation are all named test.js, making it 385 # hard to decipher what test is running when only the filename is printed. 386 prefix = abspath(join(dirname(__file__), '../test')) + os.sep 387 command = output.command[-1] 388 command = NormalizePath(command, prefix) 389 390 stdout = output.output.stdout.strip() 391 printed_file = False 392 for line in stdout.splitlines(): 393 if ( 394 (line.startswith("[aborted optimiz") or line.startswith("[disabled optimiz")) and 395 ("because:" in line or "reason:" in line) 396 ): 397 if not printed_file: 398 printed_file = True 399 print('==== %s ====' % command) 400 self.failed.append(output) 401 print(' %s' % line) 402 403 def Done(self): 404 pass 405 406 407class CompactProgressIndicator(ProgressIndicator): 408 409 def __init__(self, cases, flaky_tests_mode, templates): 410 super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode) 411 self.templates = templates 412 self.last_status_length = 0 413 self.start_time = time.time() 414 415 def Starting(self): 416 pass 417 418 def Done(self): 419 self.PrintProgress('Done\n') 420 421 def AboutToRun(self, case): 422 self.PrintProgress(case.GetLabel()) 423 424 def HasRun(self, output): 425 if output.UnexpectedOutput(): 426 self.ClearLine(self.last_status_length) 427 self.PrintFailureHeader(output.test) 428 stdout = output.output.stdout.strip() 429 if len(stdout): 430 print(self.templates['stdout'] % stdout) 431 stderr = output.output.stderr.strip() 432 if len(stderr): 433 print(self.templates['stderr'] % stderr) 434 print("Command: %s" % EscapeCommand(output.command)) 435 if output.HasCrashed(): 436 print("--- %s ---" % PrintCrashed(output.output.exit_code)) 437 if output.HasTimedOut(): 438 print("--- TIMEOUT ---") 439 440 def Truncate(self, str, length): 441 if length and (len(str) > (length - 3)): 442 return str[:(length-3)] + "..." 443 else: 444 return str 445 446 def PrintProgress(self, name): 447 self.ClearLine(self.last_status_length) 448 elapsed = time.time() - self.start_time 449 status = self.templates['status_line'] % { 450 'passed': self.succeeded, 451 'remaining': (((self.total - self.remaining) * 100) // self.total), 452 'failed': len(self.failed), 453 'test': name, 454 'mins': int(elapsed) / 60, 455 'secs': int(elapsed) % 60 456 } 457 status = self.Truncate(status, 78) 458 self.last_status_length = len(status) 459 print(status, end='') 460 sys.stdout.flush() 461 462 463class ColorProgressIndicator(CompactProgressIndicator): 464 465 def __init__(self, cases, flaky_tests_mode): 466 templates = { 467 'status_line': "[%(mins)02i:%(secs)02i|\033[34m%%%(remaining) 4d\033[0m|\033[32m+%(passed) 4d\033[0m|\033[31m-%(failed) 4d\033[0m]: %(test)s", 468 'stdout': "\033[1m%s\033[0m", 469 'stderr': "\033[31m%s\033[0m", 470 } 471 super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, templates) 472 473 def ClearLine(self, last_line_length): 474 print("\033[1K\r", end='') 475 476 477class MonochromeProgressIndicator(CompactProgressIndicator): 478 479 def __init__(self, cases, flaky_tests_mode): 480 templates = { 481 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s", 482 'stdout': '%s', 483 'stderr': '%s', 484 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"), 485 'max_length': 78 486 } 487 super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, templates) 488 489 def ClearLine(self, last_line_length): 490 print(("\r" + (" " * last_line_length) + "\r"), end='') 491 492 493PROGRESS_INDICATORS = { 494 'verbose': VerboseProgressIndicator, 495 'dots': DotsProgressIndicator, 496 'color': ColorProgressIndicator, 497 'tap': TapProgressIndicator, 498 'mono': MonochromeProgressIndicator, 499 'deopts': DeoptsCheckProgressIndicator 500} 501 502 503# ------------------------- 504# --- F r a m e w o r k --- 505# ------------------------- 506 507 508class CommandOutput(object): 509 510 def __init__(self, exit_code, timed_out, stdout, stderr): 511 self.exit_code = exit_code 512 self.timed_out = timed_out 513 self.stdout = stdout 514 self.stderr = stderr 515 self.failed = None 516 517 518class TestCase(object): 519 520 def __init__(self, context, path, arch, mode): 521 self.path = path 522 self.context = context 523 self.duration = None 524 self.arch = arch 525 self.mode = mode 526 self.parallel = False 527 self.disable_core_files = False 528 self.thread_id = 0 529 530 def IsNegative(self): 531 return self.context.expect_fail 532 533 def DidFail(self, output): 534 if output.failed is None: 535 output.failed = self.IsFailureOutput(output) 536 return output.failed 537 538 def IsFailureOutput(self, output): 539 return output.exit_code != 0 540 541 def GetSource(self): 542 return "(no source available)" 543 544 def RunCommand(self, command, env): 545 full_command = self.context.processor(command) 546 output = Execute(full_command, 547 self.context, 548 self.context.GetTimeout(self.mode), 549 env, 550 disable_core_files = self.disable_core_files) 551 return TestOutput(self, 552 full_command, 553 output, 554 self.context.store_unexpected_output) 555 556 def Run(self): 557 try: 558 result = self.RunCommand(self.GetCommand(), { 559 "TEST_THREAD_ID": "%d" % self.thread_id, 560 "TEST_PARALLEL" : "%d" % self.parallel 561 }) 562 finally: 563 # Tests can leave the tty in non-blocking mode. If the test runner 564 # tries to print to stdout/stderr after that and the tty buffer is 565 # full, it'll die with a EAGAIN OSError. Ergo, put the tty back in 566 # blocking mode before proceeding. 567 if sys.platform != 'win32': 568 from fcntl import fcntl, F_GETFL, F_SETFL 569 from os import O_NONBLOCK 570 for fd in 0,1,2: fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL)) 571 572 return result 573 574 575class TestOutput(object): 576 577 def __init__(self, test, command, output, store_unexpected_output): 578 self.test = test 579 self.command = command 580 self.output = output 581 self.store_unexpected_output = store_unexpected_output 582 self.diagnostic = [] 583 584 def UnexpectedOutput(self): 585 if self.HasCrashed(): 586 outcome = CRASH 587 elif self.HasTimedOut(): 588 outcome = TIMEOUT 589 elif self.HasFailed(): 590 outcome = FAIL 591 else: 592 outcome = PASS 593 return not outcome in self.test.outcomes 594 595 def HasCrashed(self): 596 if utils.IsWindows(): 597 return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code) 598 else: 599 # Timed out tests will have exit_code -signal.SIGTERM. 600 if self.output.timed_out: 601 return False 602 return self.output.exit_code < 0 603 604 def HasTimedOut(self): 605 return self.output.timed_out 606 607 def HasFailed(self): 608 execution_failed = self.test.DidFail(self.output) 609 if self.test.IsNegative(): 610 return not execution_failed 611 else: 612 return execution_failed 613 614 615def KillProcessWithID(pid, signal_to_send=signal.SIGTERM): 616 if utils.IsWindows(): 617 os.popen('taskkill /T /F /PID %d' % pid) 618 else: 619 os.kill(pid, signal_to_send) 620 621 622MAX_SLEEP_TIME = 0.1 623INITIAL_SLEEP_TIME = 0.0001 624SLEEP_TIME_FACTOR = 1.25 625 626SEM_INVALID_VALUE = -1 627SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h 628 629def Win32SetErrorMode(mode): 630 prev_error_mode = SEM_INVALID_VALUE 631 try: 632 import ctypes 633 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode) 634 except ImportError: 635 pass 636 return prev_error_mode 637 638 639def KillTimedOutProcess(context, pid): 640 signal_to_send = signal.SIGTERM 641 if context.abort_on_timeout: 642 # Using SIGABRT here allows the OS to generate a core dump that can be 643 # looked at post-mortem, which helps for investigating failures that are 644 # difficult to reproduce. 645 signal_to_send = signal.SIGABRT 646 KillProcessWithID(pid, signal_to_send) 647 648 649def RunProcess(context, timeout, args, **rest): 650 if context.verbose: print("#", " ".join(args)) 651 popen_args = args 652 prev_error_mode = SEM_INVALID_VALUE 653 if utils.IsWindows(): 654 if context.suppress_dialogs: 655 # Try to change the error mode to avoid dialogs on fatal errors. Don't 656 # touch any existing error mode flags by merging the existing error mode. 657 # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx. 658 error_mode = SEM_NOGPFAULTERRORBOX 659 prev_error_mode = Win32SetErrorMode(error_mode) 660 Win32SetErrorMode(error_mode | prev_error_mode) 661 662 process = subprocess.Popen( 663 args = popen_args, 664 **rest 665 ) 666 if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE: 667 Win32SetErrorMode(prev_error_mode) 668 # Compute the end time - if the process crosses this limit we 669 # consider it timed out. 670 if timeout is None: end_time = None 671 else: end_time = time.time() + timeout 672 timed_out = False 673 # Repeatedly check the exit code from the process in a 674 # loop and keep track of whether or not it times out. 675 exit_code = None 676 sleep_time = INITIAL_SLEEP_TIME 677 678 while exit_code is None: 679 if (not end_time is None) and (time.time() >= end_time): 680 # Kill the process and wait for it to exit. 681 KillTimedOutProcess(context, process.pid) 682 exit_code = process.wait() 683 timed_out = True 684 else: 685 exit_code = process.poll() 686 time.sleep(sleep_time) 687 sleep_time = sleep_time * SLEEP_TIME_FACTOR 688 if sleep_time > MAX_SLEEP_TIME: 689 sleep_time = MAX_SLEEP_TIME 690 return (process, exit_code, timed_out) 691 692 693def PrintError(str): 694 sys.stderr.write(str) 695 sys.stderr.write('\n') 696 697 698def CheckedUnlink(name): 699 while True: 700 try: 701 os.unlink(name) 702 except OSError as e: 703 # On Windows unlink() fails if another process (typically a virus scanner 704 # or the indexing service) has the file open. Those processes keep a 705 # file open for a short time only, so yield and try again; it'll succeed. 706 if sys.platform == 'win32' and e.errno == errno.EACCES: 707 time.sleep(0) 708 continue 709 PrintError("os.unlink() " + str(e)) 710 break 711 712def Execute(args, context, timeout=None, env=None, disable_core_files=False, stdin=None): 713 (fd_out, outname) = tempfile.mkstemp() 714 (fd_err, errname) = tempfile.mkstemp() 715 716 if env is None: 717 env = {} 718 env_copy = os.environ.copy() 719 720 # Remove NODE_PATH 721 if "NODE_PATH" in env_copy: 722 del env_copy["NODE_PATH"] 723 724 # Remove NODE_REPL_EXTERNAL_MODULE 725 if "NODE_REPL_EXTERNAL_MODULE" in env_copy: 726 del env_copy["NODE_REPL_EXTERNAL_MODULE"] 727 728 # Extend environment 729 for key, value in env.items(): 730 env_copy[key] = value 731 732 preexec_fn = None 733 734 if disable_core_files and not utils.IsWindows(): 735 def disableCoreFiles(): 736 import resource 737 resource.setrlimit(resource.RLIMIT_CORE, (0,0)) 738 preexec_fn = disableCoreFiles 739 740 (process, exit_code, timed_out) = RunProcess( 741 context, 742 timeout, 743 args = args, 744 stdin = stdin, 745 stdout = fd_out, 746 stderr = fd_err, 747 env = env_copy, 748 preexec_fn = preexec_fn 749 ) 750 os.close(fd_out) 751 os.close(fd_err) 752 output = open(outname, encoding='utf8').read() 753 errors = open(errname, encoding='utf8').read() 754 CheckedUnlink(outname) 755 CheckedUnlink(errname) 756 757 return CommandOutput(exit_code, timed_out, output, errors) 758 759 760def CarCdr(path): 761 if len(path) == 0: 762 return (None, [ ]) 763 else: 764 return (path[0], path[1:]) 765 766 767class TestConfiguration(object): 768 def __init__(self, context, root, section): 769 self.context = context 770 self.root = root 771 self.section = section 772 773 def Contains(self, path, file): 774 if len(path) > len(file): 775 return False 776 for i in range(len(path)): 777 if not path[i].match(NormalizePath(file[i])): 778 return False 779 return True 780 781 def GetTestStatus(self, sections, defs): 782 status_file = join(self.root, '%s.status' % self.section) 783 if exists(status_file): 784 ReadConfigurationInto(status_file, sections, defs) 785 786 787class TestSuite(object): 788 789 def __init__(self, name): 790 self.name = name 791 792 def GetName(self): 793 return self.name 794 795 796class TestRepository(TestSuite): 797 798 def __init__(self, path): 799 normalized_path = abspath(path) 800 super(TestRepository, self).__init__(basename(normalized_path)) 801 self.path = normalized_path 802 self.is_loaded = False 803 self.config = None 804 805 def GetConfiguration(self, context): 806 if self.is_loaded: 807 return self.config 808 self.is_loaded = True 809 810 module = get_module('testcfg', self.path) 811 self.config = module.GetConfiguration(context, self.path) 812 if hasattr(self.config, 'additional_flags'): 813 self.config.additional_flags += context.node_args 814 else: 815 self.config.additional_flags = context.node_args 816 return self.config 817 818 def GetBuildRequirements(self, path, context): 819 return self.GetConfiguration(context).GetBuildRequirements() 820 821 def AddTestsToList(self, result, current_path, path, context, arch, mode): 822 tests = self.GetConfiguration(context).ListTests(current_path, path, 823 arch, mode) 824 result += tests 825 for i in range(1, context.repeat): 826 result += copy.deepcopy(tests) 827 828 def GetTestStatus(self, context, sections, defs): 829 self.GetConfiguration(context).GetTestStatus(sections, defs) 830 831 832class LiteralTestSuite(TestSuite): 833 def __init__(self, tests_repos, test_root): 834 super(LiteralTestSuite, self).__init__('root') 835 self.tests_repos = tests_repos 836 self.test_root = test_root 837 838 def GetBuildRequirements(self, path, context): 839 (name, rest) = CarCdr(path) 840 result = [ ] 841 for test in self.tests_repos: 842 if not name or name.match(test.GetName()): 843 result += test.GetBuildRequirements(rest, context) 844 return result 845 846 def ListTests(self, current_path, path, context, arch, mode): 847 (name, rest) = CarCdr(path) 848 result = [ ] 849 for test in self.tests_repos: 850 test_name = test.GetName() 851 if not name or name.match(test_name): 852 full_path = current_path + [test_name] 853 test.AddTestsToList(result, full_path, path, context, arch, mode) 854 result.sort(key=lambda x: x.GetName()) 855 return result 856 857 def GetTestStatus(self, context, sections, defs): 858 # Just read the test configuration from root_path/root.status. 859 root = TestConfiguration(context, self.test_root, 'root') 860 root.GetTestStatus(sections, defs) 861 for tests_repos in self.tests_repos: 862 tests_repos.GetTestStatus(context, sections, defs) 863 864 865TIMEOUT_SCALEFACTOR = { 866 'armv6' : { 'debug' : 12, 'release' : 3 }, # The ARM buildbots are slow. 867 'arm' : { 'debug' : 8, 'release' : 2 }, 868 'ia32' : { 'debug' : 4, 'release' : 1 }, 869 'ppc' : { 'debug' : 4, 'release' : 1 }, 870 's390' : { 'debug' : 4, 'release' : 1 } } 871 872 873class Context(object): 874 875 def __init__(self, workspace, verbose, vm, args, expect_fail, 876 timeout, processor, suppress_dialogs, 877 store_unexpected_output, repeat, abort_on_timeout): 878 self.workspace = workspace 879 self.verbose = verbose 880 self.vm = vm 881 self.node_args = args 882 self.expect_fail = expect_fail 883 self.timeout = timeout 884 self.processor = processor 885 self.suppress_dialogs = suppress_dialogs 886 self.store_unexpected_output = store_unexpected_output 887 self.repeat = repeat 888 self.abort_on_timeout = abort_on_timeout 889 self.v8_enable_inspector = True 890 self.node_has_crypto = True 891 892 def GetVm(self, arch, mode): 893 if self.vm is not None: 894 return self.vm 895 if arch == 'none': 896 name = 'out/Debug/node' if mode == 'debug' else 'out/Release/node' 897 else: 898 name = 'out/%s.%s/node' % (arch, mode) 899 900 # Currently GYP does not support output_dir for MSVS. 901 # http://code.google.com/p/gyp/issues/detail?id=40 902 # It will put the builds into Release/node.exe or Debug/node.exe 903 if utils.IsWindows(): 904 if not exists(name + '.exe'): 905 name = name.replace('out/', '') 906 name = os.path.abspath(name + '.exe') 907 908 if not exists(name): 909 raise ValueError('Could not find executable. Should be ' + name) 910 911 return name 912 913 def GetTimeout(self, mode): 914 return self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode] 915 916def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode): 917 progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode) 918 return progress.Run(tasks) 919 920# ------------------------------------------- 921# --- T e s t C o n f i g u r a t i o n --- 922# ------------------------------------------- 923 924 925RUN = 'run' 926SKIP = 'skip' 927FAIL = 'fail' 928PASS = 'pass' 929OKAY = 'okay' 930TIMEOUT = 'timeout' 931CRASH = 'crash' 932SLOW = 'slow' 933FLAKY = 'flaky' 934DONTCARE = 'dontcare' 935 936class Expression(object): 937 pass 938 939 940class Constant(Expression): 941 942 def __init__(self, value): 943 self.value = value 944 945 def Evaluate(self, env, defs): 946 return self.value 947 948 949class Variable(Expression): 950 951 def __init__(self, name): 952 self.name = name 953 954 def GetOutcomes(self, env, defs): 955 if self.name in env: return set([env[self.name]]) 956 else: return set() 957 958 959class Outcome(Expression): 960 961 def __init__(self, name): 962 self.name = name 963 964 def GetOutcomes(self, env, defs): 965 if self.name in defs: 966 return defs[self.name].GetOutcomes(env, defs) 967 else: 968 return set([self.name]) 969 970 971class Operation(Expression): 972 973 def __init__(self, left, op, right): 974 self.left = left 975 self.op = op 976 self.right = right 977 978 def Evaluate(self, env, defs): 979 if self.op == '||' or self.op == ',': 980 return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs) 981 elif self.op == 'if': 982 return False 983 elif self.op == '==': 984 inter = self.left.GetOutcomes(env, defs) & self.right.GetOutcomes(env, defs) 985 return bool(inter) 986 else: 987 assert self.op == '&&' 988 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs) 989 990 def GetOutcomes(self, env, defs): 991 if self.op == '||' or self.op == ',': 992 return self.left.GetOutcomes(env, defs) | self.right.GetOutcomes(env, defs) 993 elif self.op == 'if': 994 if self.right.Evaluate(env, defs): 995 return self.left.GetOutcomes(env, defs) 996 else: 997 return set() 998 else: 999 assert self.op == '&&' 1000 return self.left.GetOutcomes(env, defs) & self.right.GetOutcomes(env, defs) 1001 1002 1003def IsAlpha(str): 1004 for char in str: 1005 if not (char.isalpha() or char.isdigit() or char == '_'): 1006 return False 1007 return True 1008 1009 1010class Tokenizer(object): 1011 """A simple string tokenizer that chops expressions into variables, 1012 parens and operators""" 1013 1014 def __init__(self, expr): 1015 self.index = 0 1016 self.expr = expr 1017 self.length = len(expr) 1018 self.tokens = None 1019 1020 def Current(self, length = 1): 1021 if not self.HasMore(length): return "" 1022 return self.expr[self.index:self.index+length] 1023 1024 def HasMore(self, length = 1): 1025 return self.index < self.length + (length - 1) 1026 1027 def Advance(self, count = 1): 1028 self.index = self.index + count 1029 1030 def AddToken(self, token): 1031 self.tokens.append(token) 1032 1033 def SkipSpaces(self): 1034 while self.HasMore() and self.Current().isspace(): 1035 self.Advance() 1036 1037 def Tokenize(self): 1038 self.tokens = [ ] 1039 while self.HasMore(): 1040 self.SkipSpaces() 1041 if not self.HasMore(): 1042 return None 1043 if self.Current() == '(': 1044 self.AddToken('(') 1045 self.Advance() 1046 elif self.Current() == ')': 1047 self.AddToken(')') 1048 self.Advance() 1049 elif self.Current() == '$': 1050 self.AddToken('$') 1051 self.Advance() 1052 elif self.Current() == ',': 1053 self.AddToken(',') 1054 self.Advance() 1055 elif IsAlpha(self.Current()): 1056 buf = "" 1057 while self.HasMore() and IsAlpha(self.Current()): 1058 buf += self.Current() 1059 self.Advance() 1060 self.AddToken(buf) 1061 elif self.Current(2) == '&&': 1062 self.AddToken('&&') 1063 self.Advance(2) 1064 elif self.Current(2) == '||': 1065 self.AddToken('||') 1066 self.Advance(2) 1067 elif self.Current(2) == '==': 1068 self.AddToken('==') 1069 self.Advance(2) 1070 else: 1071 return None 1072 return self.tokens 1073 1074 1075class Scanner(object): 1076 """A simple scanner that can serve out tokens from a given list""" 1077 1078 def __init__(self, tokens): 1079 self.tokens = tokens 1080 self.length = len(tokens) 1081 self.index = 0 1082 1083 def HasMore(self): 1084 return self.index < self.length 1085 1086 def Current(self): 1087 return self.tokens[self.index] 1088 1089 def Advance(self): 1090 self.index = self.index + 1 1091 1092 1093def ParseAtomicExpression(scan): 1094 if scan.Current() == "true": 1095 scan.Advance() 1096 return Constant(True) 1097 elif scan.Current() == "false": 1098 scan.Advance() 1099 return Constant(False) 1100 elif IsAlpha(scan.Current()): 1101 name = scan.Current() 1102 scan.Advance() 1103 return Outcome(name.lower()) 1104 elif scan.Current() == '$': 1105 scan.Advance() 1106 if not IsAlpha(scan.Current()): 1107 return None 1108 name = scan.Current() 1109 scan.Advance() 1110 return Variable(name.lower()) 1111 elif scan.Current() == '(': 1112 scan.Advance() 1113 result = ParseLogicalExpression(scan) 1114 if (not result) or (scan.Current() != ')'): 1115 return None 1116 scan.Advance() 1117 return result 1118 else: 1119 return None 1120 1121 1122BINARIES = ['=='] 1123def ParseOperatorExpression(scan): 1124 left = ParseAtomicExpression(scan) 1125 if not left: return None 1126 while scan.HasMore() and (scan.Current() in BINARIES): 1127 op = scan.Current() 1128 scan.Advance() 1129 right = ParseOperatorExpression(scan) 1130 if not right: 1131 return None 1132 left = Operation(left, op, right) 1133 return left 1134 1135 1136def ParseConditionalExpression(scan): 1137 left = ParseOperatorExpression(scan) 1138 if not left: return None 1139 while scan.HasMore() and (scan.Current() == 'if'): 1140 scan.Advance() 1141 right = ParseOperatorExpression(scan) 1142 if not right: 1143 return None 1144 left= Operation(left, 'if', right) 1145 return left 1146 1147 1148LOGICALS = ["&&", "||", ","] 1149def ParseLogicalExpression(scan): 1150 left = ParseConditionalExpression(scan) 1151 if not left: return None 1152 while scan.HasMore() and (scan.Current() in LOGICALS): 1153 op = scan.Current() 1154 scan.Advance() 1155 right = ParseConditionalExpression(scan) 1156 if not right: 1157 return None 1158 left = Operation(left, op, right) 1159 return left 1160 1161 1162def ParseCondition(expr): 1163 """Parses a logical expression into an Expression object""" 1164 tokens = Tokenizer(expr).Tokenize() 1165 if not tokens: 1166 print("Malformed expression: '%s'" % expr) 1167 return None 1168 scan = Scanner(tokens) 1169 ast = ParseLogicalExpression(scan) 1170 if not ast: 1171 print("Malformed expression: '%s'" % expr) 1172 return None 1173 if scan.HasMore(): 1174 print("Malformed expression: '%s'" % expr) 1175 return None 1176 return ast 1177 1178 1179class Configuration(object): 1180 """The parsed contents of a configuration file""" 1181 1182 def __init__(self, sections, defs): 1183 self.sections = sections 1184 self.defs = defs 1185 1186 def ClassifyTests(self, cases, env): 1187 sections = [ s for s in self.sections if s.condition.Evaluate(env, self.defs) ] 1188 all_rules = reduce(list.__add__, [s.rules for s in sections], []) 1189 unused_rules = set(all_rules) 1190 result = [] 1191 for case in cases: 1192 matches = [ r for r in all_rules if r.Contains(case.path) ] 1193 outcomes_list = [ r.GetOutcomes(env, self.defs) for r in matches ] 1194 outcomes = reduce(set.union, outcomes_list, set()) 1195 unused_rules.difference_update(matches) 1196 case.outcomes = set(outcomes) or set([PASS]) 1197 # slow tests may also just pass. 1198 if SLOW in case.outcomes: 1199 case.outcomes.add(PASS) 1200 result.append(case) 1201 return result, unused_rules 1202 1203 1204class Section(object): 1205 """A section of the configuration file. Sections are enabled or 1206 disabled prior to running the tests, based on their conditions""" 1207 1208 def __init__(self, condition): 1209 self.condition = condition 1210 self.rules = [ ] 1211 1212 def AddRule(self, rule): 1213 self.rules.append(rule) 1214 1215 1216class Rule(object): 1217 """A single rule that specifies the expected outcome for a single 1218 test.""" 1219 1220 def __init__(self, raw_path, path, value): 1221 self.raw_path = raw_path 1222 self.path = path 1223 self.value = value 1224 1225 def GetOutcomes(self, env, defs): 1226 return self.value.GetOutcomes(env, defs) 1227 1228 def Contains(self, path): 1229 if len(self.path) > len(path): 1230 return False 1231 for i in range(len(self.path)): 1232 if not self.path[i].match(path[i]): 1233 return False 1234 return True 1235 1236 1237HEADER_PATTERN = re.compile(r'\[([^]]+)\]') 1238RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)') 1239DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$') 1240PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w_.\-/]+)$') 1241 1242 1243def ReadConfigurationInto(path, sections, defs): 1244 current_section = Section(Constant(True)) 1245 sections.append(current_section) 1246 prefix = [] 1247 for line in utils.ReadLinesFrom(path): 1248 header_match = HEADER_PATTERN.match(line) 1249 if header_match: 1250 condition_str = header_match.group(1).strip() 1251 condition = ParseCondition(condition_str) 1252 new_section = Section(condition) 1253 sections.append(new_section) 1254 current_section = new_section 1255 continue 1256 rule_match = RULE_PATTERN.match(line) 1257 if rule_match: 1258 path = prefix + SplitPath(rule_match.group(1).strip()) 1259 value_str = rule_match.group(2).strip() 1260 value = ParseCondition(value_str) 1261 if not value: 1262 return False 1263 current_section.AddRule(Rule(rule_match.group(1), path, value)) 1264 continue 1265 def_match = DEF_PATTERN.match(line) 1266 if def_match: 1267 name = def_match.group(1).lower() 1268 value = ParseCondition(def_match.group(2).strip()) 1269 if not value: 1270 return False 1271 defs[name] = value 1272 continue 1273 prefix_match = PREFIX_PATTERN.match(line) 1274 if prefix_match: 1275 prefix = SplitPath(prefix_match.group(1).strip()) 1276 continue 1277 raise Exception("Malformed line: '%s'." % line) 1278 1279 1280# --------------- 1281# --- M a i n --- 1282# --------------- 1283 1284 1285ARCH_GUESS = utils.GuessArchitecture() 1286 1287 1288def BuildOptions(): 1289 result = optparse.OptionParser() 1290 result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)", 1291 default='release') 1292 result.add_option("-v", "--verbose", help="Verbose output", 1293 default=False, action="store_true") 1294 result.add_option('--logfile', dest='logfile', 1295 help='write test output to file. NOTE: this only applies the tap progress indicator') 1296 result.add_option("-p", "--progress", 1297 help="The style of progress indicator (verbose, dots, color, mono, tap)", 1298 choices=list(PROGRESS_INDICATORS.keys()), default="mono") 1299 result.add_option("--report", help="Print a summary of the tests to be run", 1300 default=False, action="store_true") 1301 result.add_option("-s", "--suite", help="A test suite", 1302 default=[], action="append") 1303 result.add_option("-t", "--timeout", help="Timeout in seconds", 1304 default=120, type="int") 1305 result.add_option("--arch", help='The architecture to run tests for', 1306 default='none') 1307 result.add_option("--snapshot", help="Run the tests with snapshot turned on", 1308 default=False, action="store_true") 1309 result.add_option("--special-command", default=None) 1310 result.add_option("--node-args", dest="node_args", help="Args to pass through to Node", 1311 default=[], action="append") 1312 result.add_option("--expect-fail", dest="expect_fail", 1313 help="Expect test cases to fail", default=False, action="store_true") 1314 result.add_option("--valgrind", help="Run tests through valgrind", 1315 default=False, action="store_true") 1316 result.add_option("--worker", help="Run parallel tests inside a worker context", 1317 default=False, action="store_true") 1318 result.add_option("--check-deopts", help="Check tests for permanent deoptimizations", 1319 default=False, action="store_true") 1320 result.add_option("--cat", help="Print the source of the tests", 1321 default=False, action="store_true") 1322 result.add_option("--flaky-tests", 1323 help="Regard tests marked as flaky (run|skip|dontcare)", 1324 default="run") 1325 result.add_option("--skip-tests", 1326 help="Tests that should not be executed (comma-separated)", 1327 default="") 1328 result.add_option("--warn-unused", help="Report unused rules", 1329 default=False, action="store_true") 1330 result.add_option("-j", help="The number of parallel tasks to run", 1331 default=1, type="int") 1332 result.add_option("-J", help="Run tasks in parallel on all cores", 1333 default=False, action="store_true") 1334 result.add_option("--time", help="Print timing information after running", 1335 default=False, action="store_true") 1336 result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests", 1337 dest="suppress_dialogs", default=True, action="store_true") 1338 result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests", 1339 dest="suppress_dialogs", action="store_false") 1340 result.add_option("--shell", help="Path to node executable", default=None) 1341 result.add_option("--store-unexpected-output", 1342 help="Store the temporary JS files from tests that fails", 1343 dest="store_unexpected_output", default=True, action="store_true") 1344 result.add_option("--no-store-unexpected-output", 1345 help="Deletes the temporary JS files from tests that fails", 1346 dest="store_unexpected_output", action="store_false") 1347 result.add_option("-r", "--run", 1348 help="Divide the tests in m groups (interleaved) and run tests from group n (--run=n,m with n < m)", 1349 default="") 1350 result.add_option('--temp-dir', 1351 help='Optional path to change directory used for tests', default=False) 1352 result.add_option('--test-root', 1353 help='Optional path to change test directory', dest='test_root', default=None) 1354 result.add_option('--repeat', 1355 help='Number of times to repeat given tests', 1356 default=1, type="int") 1357 result.add_option('--abort-on-timeout', 1358 help='Send SIGABRT instead of SIGTERM to kill processes that time out', 1359 default=False, action="store_true", dest="abort_on_timeout") 1360 result.add_option("--type", 1361 help="Type of build (simple, fips, coverage)", 1362 default=None) 1363 return result 1364 1365 1366def ProcessOptions(options): 1367 global VERBOSE 1368 VERBOSE = options.verbose 1369 options.arch = options.arch.split(',') 1370 options.mode = options.mode.split(',') 1371 options.run = options.run.split(',') 1372 # Split at commas and filter out all the empty strings. 1373 options.skip_tests = [test for test in options.skip_tests.split(',') if test] 1374 if options.run == [""]: 1375 options.run = None 1376 elif len(options.run) != 2: 1377 print("The run argument must be two comma-separated integers.") 1378 return False 1379 else: 1380 try: 1381 options.run = [int(level) for level in options.run] 1382 except ValueError: 1383 print("Could not parse the integers from the run argument.") 1384 return False 1385 if options.run[0] < 0 or options.run[1] < 0: 1386 print("The run argument cannot have negative integers.") 1387 return False 1388 if options.run[0] >= options.run[1]: 1389 print("The test group to run (n) must be smaller than number of groups (m).") 1390 return False 1391 if options.J: 1392 # inherit JOBS from environment if provided. some virtualised systems 1393 # tends to exaggerate the number of available cpus/cores. 1394 cores = os.environ.get('JOBS') 1395 options.j = int(cores) if cores is not None else multiprocessing.cpu_count() 1396 if options.flaky_tests not in [RUN, SKIP, DONTCARE]: 1397 print("Unknown flaky-tests mode %s" % options.flaky_tests) 1398 return False 1399 return True 1400 1401 1402REPORT_TEMPLATE = """\ 1403Total: %(total)i tests 1404 * %(skipped)4d tests will be skipped 1405 * %(pass)4d tests are expected to pass 1406 * %(fail_ok)4d tests are expected to fail that we won't fix 1407 * %(fail)4d tests are expected to fail that we should fix\ 1408""" 1409 1410 1411class Pattern(object): 1412 1413 def __init__(self, pattern): 1414 self.pattern = pattern 1415 self.compiled = None 1416 1417 def match(self, str): 1418 if not self.compiled: 1419 pattern = "^" + self.pattern.replace('*', '.*') + "$" 1420 self.compiled = re.compile(pattern) 1421 return self.compiled.match(str) 1422 1423 def __str__(self): 1424 return self.pattern 1425 1426 1427def SplitPath(path_arg): 1428 stripped = [c.strip() for c in path_arg.split('/')] 1429 return [Pattern(s) for s in stripped if len(s) > 0] 1430 1431def NormalizePath(path, prefix='test/'): 1432 # strip the extra path information of the specified test 1433 prefix = prefix.replace('\\', '/') 1434 path = path.replace('\\', '/') 1435 if path.startswith(prefix): 1436 path = path[len(prefix):] 1437 if path.endswith('.js'): 1438 path = path[:-3] 1439 elif path.endswith('.mjs'): 1440 path = path[:-4] 1441 return path 1442 1443def GetSpecialCommandProcessor(value): 1444 if (not value) or (value.find('@') == -1): 1445 def ExpandCommand(args): 1446 return args 1447 return ExpandCommand 1448 else: 1449 prefix, _, suffix = value.partition('@') 1450 prefix = unquote(prefix).split() 1451 suffix = unquote(suffix).split() 1452 def ExpandCommand(args): 1453 return prefix + args + suffix 1454 return ExpandCommand 1455 1456def GetSuites(test_root): 1457 def IsSuite(path): 1458 return isdir(path) and exists(join(path, 'testcfg.py')) 1459 return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ] 1460 1461 1462def FormatTime(d): 1463 millis = round(d * 1000) % 1000 1464 return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis) 1465 1466 1467def FormatTimedelta(td): 1468 if hasattr(td, 'total_seconds'): 1469 d = td.total_seconds() 1470 else: # python2.6 compat 1471 d = td.seconds + (td.microseconds / 10.0**6) 1472 return FormatTime(d) 1473 1474 1475def PrintCrashed(code): 1476 if utils.IsWindows(): 1477 return "CRASHED" 1478 else: 1479 return "CRASHED (Signal: %d)" % -code 1480 1481 1482# these suites represent special cases that should not be run as part of the 1483# default JavaScript test-run, e.g., internet/ requires a network connection, 1484# addons/ requires compilation. 1485IGNORED_SUITES = [ 1486 'addons', 1487 'benchmark', 1488 'doctool', 1489 'embedding', 1490 'internet', 1491 'js-native-api', 1492 'node-api', 1493 'pummel', 1494 'tick-processor', 1495 'v8-updates' 1496] 1497 1498 1499def ArgsToTestPaths(test_root, args, suites): 1500 if len(args) == 0 or 'default' in args: 1501 def_suites = [s for s in suites if s not in IGNORED_SUITES] 1502 args = [a for a in args if a != 'default'] + def_suites 1503 subsystem_regex = re.compile(r'^[a-zA-Z-]*$') 1504 check = lambda arg: subsystem_regex.match(arg) and (arg not in suites) 1505 mapped_args = ["*/test*-%s-*" % arg if check(arg) else arg for arg in args] 1506 paths = [SplitPath(NormalizePath(a)) for a in mapped_args] 1507 return paths 1508 1509 1510def get_env_type(vm, options_type, context): 1511 if options_type is not None: 1512 env_type = options_type 1513 else: 1514 # 'simple' is the default value for 'env_type'. 1515 env_type = 'simple' 1516 ssl_ver = Execute([vm, '-p', 'process.versions.openssl'], context).stdout 1517 if 'fips' in ssl_ver: 1518 env_type = 'fips' 1519 return env_type 1520 1521 1522def Main(): 1523 parser = BuildOptions() 1524 (options, args) = parser.parse_args() 1525 if not ProcessOptions(options): 1526 parser.print_help() 1527 return 1 1528 1529 ch = logging.StreamHandler(sys.stdout) 1530 logger.addHandler(ch) 1531 logger.setLevel(logging.INFO) 1532 if options.logfile: 1533 fh = logging.FileHandler(options.logfile, encoding='utf-8', mode='w') 1534 logger.addHandler(fh) 1535 1536 workspace = abspath(join(dirname(sys.argv[0]), '..')) 1537 test_root = join(workspace, 'test') 1538 if options.test_root is not None: 1539 test_root = options.test_root 1540 suites = GetSuites(test_root) 1541 repositories = [TestRepository(join(test_root, name)) for name in suites] 1542 repositories += [TestRepository(a) for a in options.suite] 1543 1544 root = LiteralTestSuite(repositories, test_root) 1545 paths = ArgsToTestPaths(test_root, args, suites) 1546 1547 # Check for --valgrind option. If enabled, we overwrite the special 1548 # command flag with a command that uses the run-valgrind.py script. 1549 if options.valgrind: 1550 run_valgrind = join(workspace, "tools", "run-valgrind.py") 1551 options.special_command = "python -u " + run_valgrind + " @" 1552 1553 if options.check_deopts: 1554 options.node_args.append("--trace-opt") 1555 options.node_args.append("--trace-file-names") 1556 # --always-opt is needed because many tests do not run long enough for the 1557 # optimizer to kick in, so this flag will force it to run. 1558 options.node_args.append("--always-opt") 1559 options.progress = "deopts" 1560 1561 if options.worker: 1562 run_worker = join(workspace, "tools", "run-worker.js") 1563 options.node_args.append(run_worker) 1564 1565 processor = GetSpecialCommandProcessor(options.special_command) 1566 1567 context = Context(workspace, 1568 VERBOSE, 1569 options.shell, 1570 options.node_args, 1571 options.expect_fail, 1572 options.timeout, 1573 processor, 1574 options.suppress_dialogs, 1575 options.store_unexpected_output, 1576 options.repeat, 1577 options.abort_on_timeout) 1578 1579 # Get status for tests 1580 sections = [ ] 1581 defs = { } 1582 root.GetTestStatus(context, sections, defs) 1583 config = Configuration(sections, defs) 1584 1585 # List the tests 1586 all_cases = [ ] 1587 all_unused = [ ] 1588 unclassified_tests = [ ] 1589 globally_unused_rules = None 1590 for path in paths: 1591 for arch in options.arch: 1592 for mode in options.mode: 1593 vm = context.GetVm(arch, mode) 1594 if not exists(vm): 1595 print("Can't find shell executable: '%s'" % vm) 1596 continue 1597 archEngineContext = Execute([vm, "-p", "process.arch"], context) 1598 vmArch = archEngineContext.stdout.rstrip() 1599 if archEngineContext.exit_code != 0 or vmArch == "undefined": 1600 print("Can't determine the arch of: '%s'" % vm) 1601 print(archEngineContext.stderr.rstrip()) 1602 continue 1603 env = { 1604 'mode': mode, 1605 'system': utils.GuessOS(), 1606 'arch': vmArch, 1607 'type': get_env_type(vm, options.type, context), 1608 } 1609 test_list = root.ListTests([], path, context, arch, mode) 1610 unclassified_tests += test_list 1611 cases, unused_rules = config.ClassifyTests(test_list, env) 1612 if globally_unused_rules is None: 1613 globally_unused_rules = set(unused_rules) 1614 else: 1615 globally_unused_rules = ( 1616 globally_unused_rules.intersection(unused_rules)) 1617 all_cases += cases 1618 all_unused.append(unused_rules) 1619 1620 # We want to skip the inspector tests if node was built without the inspector. 1621 has_inspector = Execute([vm, 1622 '-p', 'process.features.inspector'], context) 1623 if has_inspector.stdout.rstrip() == 'false': 1624 context.v8_enable_inspector = False 1625 1626 has_crypto = Execute([vm, 1627 '-p', 'process.versions.openssl'], context) 1628 if has_crypto.stdout.rstrip() == 'undefined': 1629 context.node_has_crypto = False 1630 1631 if options.cat: 1632 visited = set() 1633 for test in unclassified_tests: 1634 key = tuple(test.path) 1635 if key in visited: 1636 continue 1637 visited.add(key) 1638 print("--- begin source: %s ---" % test.GetLabel()) 1639 source = test.GetSource().strip() 1640 print(source) 1641 print("--- end source: %s ---" % test.GetLabel()) 1642 return 0 1643 1644 if options.warn_unused: 1645 for rule in globally_unused_rules: 1646 print("Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path])) 1647 1648 tempdir = os.environ.get('NODE_TEST_DIR') or options.temp_dir 1649 if tempdir: 1650 os.environ['NODE_TEST_DIR'] = tempdir 1651 try: 1652 os.makedirs(tempdir) 1653 except OSError as exception: 1654 if exception.errno != errno.EEXIST: 1655 print("Could not create the temporary directory", options.temp_dir) 1656 sys.exit(1) 1657 1658 def should_keep(case): 1659 if any((s in case.file) for s in options.skip_tests): 1660 return False 1661 elif SKIP in case.outcomes: 1662 return False 1663 elif (options.flaky_tests == SKIP) and (set([SLOW, FLAKY]) & case.outcomes): 1664 return False 1665 else: 1666 return True 1667 1668 cases_to_run = [ 1669 test_case for test_case in all_cases if should_keep(test_case) 1670 ] 1671 1672 if options.report: 1673 print(REPORT_TEMPLATE % { 1674 'total': len(all_cases), 1675 'skipped': len(all_cases) - len(cases_to_run), 1676 'pass': len([t for t in cases_to_run if PASS in t.outcomes]), 1677 'fail_ok': len([t for t in cases_to_run if t.outcomes == set([FAIL, OKAY])]), 1678 'fail': len([t for t in cases_to_run if t.outcomes == set([FAIL])]) 1679 }) 1680 1681 if options.run is not None: 1682 # Must ensure the list of tests is sorted before selecting, to avoid 1683 # silent errors if this file is changed to list the tests in a way that 1684 # can be different in different machines 1685 cases_to_run.sort(key=lambda c: (c.arch, c.mode, c.file)) 1686 cases_to_run = [ cases_to_run[i] for i 1687 in range(options.run[0], 1688 len(cases_to_run), 1689 options.run[1]) ] 1690 if len(cases_to_run) == 0: 1691 print("No tests to run.") 1692 return 1 1693 else: 1694 try: 1695 start = time.time() 1696 if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests): 1697 result = 0 1698 else: 1699 result = 1 1700 duration = time.time() - start 1701 except KeyboardInterrupt: 1702 print("Interrupted") 1703 return 1 1704 1705 if options.time: 1706 # Write the times to stderr to make it easy to separate from the 1707 # test output. 1708 print() 1709 sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration)) 1710 timed_tests = [ t for t in cases_to_run if not t.duration is None ] 1711 timed_tests.sort(key=lambda x: x.duration) 1712 for i, entry in enumerate(timed_tests[:20], start=1): 1713 t = FormatTimedelta(entry.duration) 1714 sys.stderr.write("%4i (%s) %s\n" % (i, t, entry.GetLabel())) 1715 1716 return result 1717 1718 1719if __name__ == '__main__': 1720 sys.exit(Main()) 1721