The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / tools / test.py @ 40c0f755

History | View | Annotate | Download (36.8 KB)

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

    
31
import imp
32
import optparse
33
import os
34
from os.path import join, dirname, abspath, basename, isdir, exists
35
import platform
36
import re
37
import signal
38
import subprocess
39
import sys
40
import tempfile
41
import time
42
import threading
43
import utils
44
from Queue import Queue, Empty
45

    
46

    
47
VERBOSE = False
48

    
49

    
50
# ---------------------------------------------
51
# --- P r o g r e s s   I n d i c a t o r s ---
52
# ---------------------------------------------
53

    
54

    
55
class ProgressIndicator(object):
56

    
57
  def __init__(self, cases):
58
    self.cases = cases
59
    self.queue = Queue(len(cases))
60
    for case in cases:
61
      self.queue.put_nowait(case)
62
    self.succeeded = 0
63
    self.remaining = len(cases)
64
    self.total = len(cases)
65
    self.failed = [ ]
66
    self.crashed = 0
67
    self.terminate = False
68
    self.lock = threading.Lock()
69

    
70
  def PrintFailureHeader(self, test):
71
    if test.IsNegative():
72
      negative_marker = '[negative] '
73
    else:
74
      negative_marker = ''
75
    print "=== %(label)s %(negative)s===" % {
76
      'label': test.GetLabel(),
77
      'negative': negative_marker
78
    }
79
    print "Path: %s" % "/".join(test.path)
80

    
81
  def Run(self, tasks):
82
    self.Starting()
83
    threads = []
84
    # Spawn N-1 threads and then use this thread as the last one.
85
    # That way -j1 avoids threading altogether which is a nice fallback
86
    # in case of threading problems.
87
    for i in xrange(tasks - 1):
88
      thread = threading.Thread(target=self.RunSingle, args=[])
89
      threads.append(thread)
90
      thread.start()
91
    try:
92
      self.RunSingle()
93
      # Wait for the remaining threads
94
      for thread in threads:
95
        # Use a timeout so that signals (ctrl-c) will be processed.
96
        thread.join(timeout=10000000)
97
    except Exception, e:
98
      # If there's an exception we schedule an interruption for any
99
      # remaining threads.
100
      self.terminate = True
101
      # ...and then reraise the exception to bail out
102
      raise
103
    self.Done()
104
    return not self.failed
105

    
106
  def RunSingle(self):
107
    while not self.terminate:
108
      try:
109
        test = self.queue.get_nowait()
110
      except Empty:
111
        return
112
      case = test.case
113
      self.lock.acquire()
114
      self.AboutToRun(case)
115
      self.lock.release()
116
      try:
117
        start = time.time()
118
        output = case.Run()
119
        case.duration = (time.time() - start)
120
      except IOError, e:
121
        assert self.terminate
122
        return
123
      if self.terminate:
124
        return
125
      self.lock.acquire()
126
      if output.UnexpectedOutput():
127
        self.failed.append(output)
128
        if output.HasCrashed():
129
          self.crashed += 1
130
      else:
131
        self.succeeded += 1
132
      self.remaining -= 1
133
      self.HasRun(output)
134
      self.lock.release()
135

    
136

    
137
def EscapeCommand(command):
138
  parts = []
139
  for part in command:
140
    if ' ' in part:
141
      # Escape spaces.  We may need to escape more characters for this
142
      # to work properly.
143
      parts.append('"%s"' % part)
144
    else:
145
      parts.append(part)
146
  return " ".join(parts)
147

    
148

    
149
class SimpleProgressIndicator(ProgressIndicator):
150

    
151
  def Starting(self):
152
    print 'Running %i tests' % len(self.cases)
153

    
154
  def Done(self):
155
    print
156
    for failed in self.failed:
157
      self.PrintFailureHeader(failed.test)
158
      if failed.output.stderr:
159
        print "--- stderr ---"
160
        print failed.output.stderr.strip()
161
      if failed.output.stdout:
162
        print "--- stdout ---"
163
        print failed.output.stdout.strip()
164
      print "Command: %s" % EscapeCommand(failed.command)
165
      if failed.HasCrashed():
166
        print "--- CRASHED ---"
167
    if len(self.failed) == 0:
168
      print "==="
169
      print "=== All tests succeeded"
170
      print "==="
171
    else:
172
      print
173
      print "==="
174
      print "=== %i tests failed" % len(self.failed)
175
      if self.crashed > 0:
176
        print "=== %i tests CRASHED" % self.crashed
177
      print "==="
178

    
179

    
180
class VerboseProgressIndicator(SimpleProgressIndicator):
181

    
182
  def AboutToRun(self, case):
183
    print 'Starting %s...' % case.GetLabel()
184
    sys.stdout.flush()
185

    
186
  def HasRun(self, output):
187
    if output.UnexpectedOutput():
188
      if output.HasCrashed():
189
        outcome = 'CRASH'
190
      else:
191
        outcome = 'FAIL'
192
    else:
193
      outcome = 'pass'
194
    print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
195

    
196

    
197
class DotsProgressIndicator(SimpleProgressIndicator):
198

    
199
  def AboutToRun(self, case):
200
    pass
201

    
202
  def HasRun(self, output):
203
    total = self.succeeded + len(self.failed)
204
    if (total > 1) and (total % 50 == 1):
205
      sys.stdout.write('\n')
206
    if output.UnexpectedOutput():
207
      if output.HasCrashed():
208
        sys.stdout.write('C')
209
        sys.stdout.flush()
210
      else:
211
        sys.stdout.write('F')
212
        sys.stdout.flush()
213
    else:
214
      sys.stdout.write('.')
215
      sys.stdout.flush()
216

    
217

    
218
class CompactProgressIndicator(ProgressIndicator):
219

    
220
  def __init__(self, cases, templates):
221
    super(CompactProgressIndicator, self).__init__(cases)
222
    self.templates = templates
223
    self.last_status_length = 0
224
    self.start_time = time.time()
225

    
226
  def Starting(self):
227
    pass
228

    
229
  def Done(self):
230
    self.PrintProgress('Done')
231

    
232
  def AboutToRun(self, case):
233
    self.PrintProgress(case.GetLabel())
234

    
235
  def HasRun(self, output):
236
    if output.UnexpectedOutput():
237
      self.ClearLine(self.last_status_length)
238
      self.PrintFailureHeader(output.test)
239
      stdout = output.output.stdout.strip()
240
      if len(stdout):
241
        print self.templates['stdout'] % stdout
242
      stderr = output.output.stderr.strip()
243
      if len(stderr):
244
        print self.templates['stderr'] % stderr
245
      print "Command: %s" % EscapeCommand(output.command)
246
      if output.HasCrashed():
247
        print "--- CRASHED ---"
248

    
249
  def Truncate(self, str, length):
250
    if length and (len(str) > (length - 3)):
251
      return str[:(length-3)] + "..."
252
    else:
253
      return str
254

    
255
  def PrintProgress(self, name):
256
    self.ClearLine(self.last_status_length)
257
    elapsed = time.time() - self.start_time
258
    status = self.templates['status_line'] % {
259
      'passed': self.succeeded,
260
      'remaining': (((self.total - self.remaining) * 100) // self.total),
261
      'failed': len(self.failed),
262
      'test': name,
263
      'mins': int(elapsed) / 60,
264
      'secs': int(elapsed) % 60
265
    }
266
    status = self.Truncate(status, 78)
267
    self.last_status_length = len(status)
268
    print status,
269
    sys.stdout.flush()
270

    
271

    
272
class ColorProgressIndicator(CompactProgressIndicator):
273

    
274
  def __init__(self, cases):
275
    templates = {
276
      '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",
277
      'stdout': "\033[1m%s\033[0m",
278
      'stderr': "\033[31m%s\033[0m",
279
    }
280
    super(ColorProgressIndicator, self).__init__(cases, templates)
281

    
282
  def ClearLine(self, last_line_length):
283
    print "\033[1K\r",
284

    
285

    
286
class MonochromeProgressIndicator(CompactProgressIndicator):
287

    
288
  def __init__(self, cases):
289
    templates = {
290
      'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
291
      'stdout': '%s',
292
      'stderr': '%s',
293
      'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
294
      'max_length': 78
295
    }
296
    super(MonochromeProgressIndicator, self).__init__(cases, templates)
297

    
298
  def ClearLine(self, last_line_length):
299
    print ("\r" + (" " * last_line_length) + "\r"),
300

    
301

    
302
PROGRESS_INDICATORS = {
303
  'verbose': VerboseProgressIndicator,
304
  'dots': DotsProgressIndicator,
305
  'color': ColorProgressIndicator,
306
  'mono': MonochromeProgressIndicator
307
}
308

    
309

    
310
# -------------------------
311
# --- F r a m e w o r k ---
312
# -------------------------
313

    
314

    
315
class CommandOutput(object):
316

    
317
  def __init__(self, exit_code, timed_out, stdout, stderr):
318
    self.exit_code = exit_code
319
    self.timed_out = timed_out
320
    self.stdout = stdout
321
    self.stderr = stderr
322

    
323

    
324
class TestCase(object):
325

    
326
  def __init__(self, context, path):
327
    self.path = path
328
    self.context = context
329
    self.failed = None
330
    self.duration = None
331

    
332
  def IsNegative(self):
333
    return False
334

    
335
  def CompareTime(self, other):
336
    return cmp(other.duration, self.duration)
337

    
338
  def DidFail(self, output):
339
    if self.failed is None:
340
      self.failed = self.IsFailureOutput(output)
341
    return self.failed
342

    
343
  def IsFailureOutput(self, output):
344
    return output.exit_code != 0
345

    
346
  def GetSource(self):
347
    return "(no source available)"
348

    
349
  def RunCommand(self, command):
350
    full_command = self.context.processor(command)
351
    output = Execute(full_command, self.context, self.context.timeout)
352
    return TestOutput(self, full_command, output)
353

    
354
  def Run(self):
355
    return self.RunCommand(self.GetCommand())
356

    
357

    
358
class TestOutput(object):
359

    
360
  def __init__(self, test, command, output):
361
    self.test = test
362
    self.command = command
363
    self.output = output
364

    
365
  def UnexpectedOutput(self):
366
    if self.HasCrashed():
367
      outcome = CRASH
368
    elif self.HasFailed():
369
      outcome = FAIL
370
    else:
371
      outcome = PASS
372
    return not outcome in self.test.outcomes
373

    
374
  def HasCrashed(self):
375
    if utils.IsWindows():
376
      return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
377
    else:
378
      # Timed out tests will have exit_code -signal.SIGTERM.
379
      if self.output.timed_out:
380
        return False
381
      return self.output.exit_code < 0 and \
382
             self.output.exit_code != -signal.SIGABRT
383

    
384
  def HasFailed(self):
385
    execution_failed = self.test.DidFail(self.output)
386
    if self.test.IsNegative():
387
      return not execution_failed
388
    else:
389
      return execution_failed
390

    
391

    
392
def KillProcessWithID(pid):
393
  if utils.IsWindows():
394
    os.popen('taskkill /T /F /PID %d' % pid)
395
  else:
396
    os.kill(pid, signal.SIGTERM)
397

    
398

    
399
MAX_SLEEP_TIME = 0.1
400
INITIAL_SLEEP_TIME = 0.0001
401
SLEEP_TIME_FACTOR = 1.25
402

    
403
SEM_INVALID_VALUE = -1
404
SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
405

    
406
def Win32SetErrorMode(mode):
407
  prev_error_mode = SEM_INVALID_VALUE
408
  try:
409
    import ctypes
410
    prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
411
  except ImportError:
412
    pass
413
  return prev_error_mode
414

    
415
def RunProcess(context, timeout, args, **rest):
416
  if context.verbose: print "#", " ".join(args)
417
  popen_args = args
418
  prev_error_mode = SEM_INVALID_VALUE;
419
  if utils.IsWindows():
420
    popen_args = '"' + subprocess.list2cmdline(args) + '"'
421
    if context.suppress_dialogs:
422
      # Try to change the error mode to avoid dialogs on fatal errors. Don't
423
      # touch any existing error mode flags by merging the existing error mode.
424
      # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
425
      error_mode = SEM_NOGPFAULTERRORBOX;
426
      prev_error_mode = Win32SetErrorMode(error_mode);
427
      Win32SetErrorMode(error_mode | prev_error_mode);
428
  process = subprocess.Popen(
429
    shell = utils.IsWindows(),
430
    args = popen_args,
431
    **rest
432
  )
433
  if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
434
    Win32SetErrorMode(prev_error_mode)
435
  # Compute the end time - if the process crosses this limit we
436
  # consider it timed out.
437
  if timeout is None: end_time = None
438
  else: end_time = time.time() + timeout
439
  timed_out = False
440
  # Repeatedly check the exit code from the process in a
441
  # loop and keep track of whether or not it times out.
442
  exit_code = None
443
  sleep_time = INITIAL_SLEEP_TIME
444
  while exit_code is None:
445
    if (not end_time is None) and (time.time() >= end_time):
446
      # Kill the process and wait for it to exit.
447
      KillProcessWithID(process.pid)
448
      exit_code = process.wait()
449
      timed_out = True
450
    else:
451
      exit_code = process.poll()
452
      time.sleep(sleep_time)
453
      sleep_time = sleep_time * SLEEP_TIME_FACTOR
454
      if sleep_time > MAX_SLEEP_TIME:
455
        sleep_time = MAX_SLEEP_TIME
456
  return (process, exit_code, timed_out)
457

    
458

    
459
def PrintError(str):
460
  sys.stderr.write(str)
461
  sys.stderr.write('\n')
462

    
463

    
464
def Execute(args, context, timeout=None):
465
  (fd_out, outname) = tempfile.mkstemp()
466
  (fd_err, errname) = tempfile.mkstemp()
467
  (process, exit_code, timed_out) = RunProcess(
468
    context,
469
    timeout,
470
    args = args,
471
    stdout = fd_out,
472
    stderr = fd_err,
473
  )
474
  os.close(fd_out)
475
  os.close(fd_err)
476
  output = file(outname).read()
477
  errors = file(errname).read()
478
  def CheckedUnlink(name):
479
    try:
480
      os.unlink(name)
481
    except OSError, e:
482
      PrintError("os.unlink() " + str(e))
483
  CheckedUnlink(outname)
484
  CheckedUnlink(errname)
485
  return CommandOutput(exit_code, timed_out, output, errors)
486

    
487

    
488
def ExecuteNoCapture(args, context, timeout=None):
489
  (process, exit_code, timed_out) = RunProcess(
490
    context,
491
    timeout,
492
    args = args,
493
  )
494
  return CommandOutput(exit_code, False, "", "")
495

    
496

    
497
def CarCdr(path):
498
  if len(path) == 0:
499
    return (None, [ ])
500
  else:
501
    return (path[0], path[1:])
502

    
503

    
504
class TestConfiguration(object):
505

    
506
  def __init__(self, context, root):
507
    self.context = context
508
    self.root = root
509

    
510
  def Contains(self, path, file):
511
    if len(path) > len(file):
512
      return False
513
    for i in xrange(len(path)):
514
      if not path[i].match(file[i]):
515
        return False
516
    return True
517

    
518
  def GetTestStatus(self, sections, defs):
519
    pass
520

    
521

    
522
class TestSuite(object):
523

    
524
  def __init__(self, name):
525
    self.name = name
526

    
527
  def GetName(self):
528
    return self.name
529

    
530

    
531
class TestRepository(TestSuite):
532

    
533
  def __init__(self, path):
534
    normalized_path = abspath(path)
535
    super(TestRepository, self).__init__(basename(normalized_path))
536
    self.path = normalized_path
537
    self.is_loaded = False
538
    self.config = None
539

    
540
  def GetConfiguration(self, context):
541
    if self.is_loaded:
542
      return self.config
543
    self.is_loaded = True
544
    file = None
545
    try:
546
      (file, pathname, description) = imp.find_module('testcfg', [ self.path ])
547
      module = imp.load_module('testcfg', file, pathname, description)
548
      self.config = module.GetConfiguration(context, self.path)
549
    finally:
550
      if file:
551
        file.close()
552
    return self.config
553

    
554
  def GetBuildRequirements(self, path, context):
555
    return self.GetConfiguration(context).GetBuildRequirements()
556

    
557
  def ListTests(self, current_path, path, context, mode):
558
    return self.GetConfiguration(context).ListTests(current_path, path, mode)
559

    
560
  def GetTestStatus(self, context, sections, defs):
561
    self.GetConfiguration(context).GetTestStatus(sections, defs)
562

    
563

    
564
class LiteralTestSuite(TestSuite):
565

    
566
  def __init__(self, tests):
567
    super(LiteralTestSuite, self).__init__('root')
568
    self.tests = tests
569

    
570
  def GetBuildRequirements(self, path, context):
571
    (name, rest) = CarCdr(path)
572
    result = [ ]
573
    for test in self.tests:
574
      if not name or name.match(test.GetName()):
575
        result += test.GetBuildRequirements(rest, context)
576
    return result
577

    
578
  def ListTests(self, current_path, path, context, mode):
579
    (name, rest) = CarCdr(path)
580
    result = [ ]
581
    for test in self.tests:
582
      test_name = test.GetName()
583
      if not name or name.match(test_name):
584
        full_path = current_path + [test_name]
585
        result += test.ListTests(full_path, path, context, mode)
586
    return result
587

    
588
  def GetTestStatus(self, context, sections, defs):
589
    for test in self.tests:
590
      test.GetTestStatus(context, sections, defs)
591

    
592

    
593
SUFFIX = {'debug': '_g', 'release': ''}
594

    
595

    
596
class Context(object):
597

    
598
  def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, suppress_dialogs):
599
    self.workspace = workspace
600
    self.buildspace = buildspace
601
    self.verbose = verbose
602
    self.vm_root = vm
603
    self.timeout = timeout
604
    self.processor = processor
605
    self.suppress_dialogs = suppress_dialogs
606

    
607
  def GetVm(self, mode):
608
    name = self.vm_root + SUFFIX[mode]
609
    if utils.IsWindows() and not name.endswith('.exe'):
610
      name = name + '.exe'
611
    return name
612

    
613
def RunTestCases(all_cases, progress, tasks):
614
  def DoSkip(case):
615
    return SKIP in c.outcomes or SLOW in c.outcomes
616
  cases_to_run = [ c for c in all_cases if not DoSkip(c) ]
617
  progress = PROGRESS_INDICATORS[progress](cases_to_run)
618
  return progress.Run(tasks)
619

    
620

    
621
def BuildRequirements(context, requirements, mode, scons_flags):
622
  command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
623
                  + requirements
624
                  + scons_flags)
625
  output = ExecuteNoCapture(command_line, context)
626
  return output.exit_code == 0
627

    
628

    
629
# -------------------------------------------
630
# --- T e s t   C o n f i g u r a t i o n ---
631
# -------------------------------------------
632

    
633

    
634
SKIP = 'skip'
635
FAIL = 'fail'
636
PASS = 'pass'
637
OKAY = 'okay'
638
TIMEOUT = 'timeout'
639
CRASH = 'crash'
640
SLOW = 'slow'
641

    
642

    
643
class Expression(object):
644
  pass
645

    
646

    
647
class Constant(Expression):
648

    
649
  def __init__(self, value):
650
    self.value = value
651

    
652
  def Evaluate(self, env, defs):
653
    return self.value
654

    
655

    
656
class Variable(Expression):
657

    
658
  def __init__(self, name):
659
    self.name = name
660

    
661
  def GetOutcomes(self, env, defs):
662
    if self.name in env: return ListSet([env[self.name]])
663
    else: return Nothing()
664

    
665

    
666
class Outcome(Expression):
667

    
668
  def __init__(self, name):
669
    self.name = name
670

    
671
  def GetOutcomes(self, env, defs):
672
    if self.name in defs:
673
      return defs[self.name].GetOutcomes(env, defs)
674
    else:
675
      return ListSet([self.name])
676

    
677

    
678
class Set(object):
679
  pass
680

    
681

    
682
class ListSet(Set):
683

    
684
  def __init__(self, elms):
685
    self.elms = elms
686

    
687
  def __str__(self):
688
    return "ListSet%s" % str(self.elms)
689

    
690
  def Intersect(self, that):
691
    if not isinstance(that, ListSet):
692
      return that.Intersect(self)
693
    return ListSet([ x for x in self.elms if x in that.elms ])
694

    
695
  def Union(self, that):
696
    if not isinstance(that, ListSet):
697
      return that.Union(self)
698
    return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ])
699

    
700
  def IsEmpty(self):
701
    return len(self.elms) == 0
702

    
703

    
704
class Everything(Set):
705

    
706
  def Intersect(self, that):
707
    return that
708

    
709
  def Union(self, that):
710
    return self
711

    
712
  def IsEmpty(self):
713
    return False
714

    
715

    
716
class Nothing(Set):
717

    
718
  def Intersect(self, that):
719
    return self
720

    
721
  def Union(self, that):
722
    return that
723

    
724
  def IsEmpty(self):
725
    return True
726

    
727

    
728
class Operation(Expression):
729

    
730
  def __init__(self, left, op, right):
731
    self.left = left
732
    self.op = op
733
    self.right = right
734

    
735
  def Evaluate(self, env, defs):
736
    if self.op == '||' or self.op == ',':
737
      return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
738
    elif self.op == 'if':
739
      return False
740
    elif self.op == '==':
741
      inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
742
      return not inter.IsEmpty()
743
    else:
744
      assert self.op == '&&'
745
      return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
746

    
747
  def GetOutcomes(self, env, defs):
748
    if self.op == '||' or self.op == ',':
749
      return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env, defs))
750
    elif self.op == 'if':
751
      if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs)
752
      else: return Nothing()
753
    else:
754
      assert self.op == '&&'
755
      return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
756

    
757

    
758
def IsAlpha(str):
759
  for char in str:
760
    if not (char.isalpha() or char.isdigit() or char == '_'):
761
      return False
762
  return True
763

    
764

    
765
class Tokenizer(object):
766
  """A simple string tokenizer that chops expressions into variables,
767
  parens and operators"""
768

    
769
  def __init__(self, expr):
770
    self.index = 0
771
    self.expr = expr
772
    self.length = len(expr)
773
    self.tokens = None
774

    
775
  def Current(self, length = 1):
776
    if not self.HasMore(length): return ""
777
    return self.expr[self.index:self.index+length]
778

    
779
  def HasMore(self, length = 1):
780
    return self.index < self.length + (length - 1)
781

    
782
  def Advance(self, count = 1):
783
    self.index = self.index + count
784

    
785
  def AddToken(self, token):
786
    self.tokens.append(token)
787

    
788
  def SkipSpaces(self):
789
    while self.HasMore() and self.Current().isspace():
790
      self.Advance()
791

    
792
  def Tokenize(self):
793
    self.tokens = [ ]
794
    while self.HasMore():
795
      self.SkipSpaces()
796
      if not self.HasMore():
797
        return None
798
      if self.Current() == '(':
799
        self.AddToken('(')
800
        self.Advance()
801
      elif self.Current() == ')':
802
        self.AddToken(')')
803
        self.Advance()
804
      elif self.Current() == '$':
805
        self.AddToken('$')
806
        self.Advance()
807
      elif self.Current() == ',':
808
        self.AddToken(',')
809
        self.Advance()
810
      elif IsAlpha(self.Current()):
811
        buf = ""
812
        while self.HasMore() and IsAlpha(self.Current()):
813
          buf += self.Current()
814
          self.Advance()
815
        self.AddToken(buf)
816
      elif self.Current(2) == '&&':
817
        self.AddToken('&&')
818
        self.Advance(2)
819
      elif self.Current(2) == '||':
820
        self.AddToken('||')
821
        self.Advance(2)
822
      elif self.Current(2) == '==':
823
        self.AddToken('==')
824
        self.Advance(2)
825
      else:
826
        return None
827
    return self.tokens
828

    
829

    
830
class Scanner(object):
831
  """A simple scanner that can serve out tokens from a given list"""
832

    
833
  def __init__(self, tokens):
834
    self.tokens = tokens
835
    self.length = len(tokens)
836
    self.index = 0
837

    
838
  def HasMore(self):
839
    return self.index < self.length
840

    
841
  def Current(self):
842
    return self.tokens[self.index]
843

    
844
  def Advance(self):
845
    self.index = self.index + 1
846

    
847

    
848
def ParseAtomicExpression(scan):
849
  if scan.Current() == "true":
850
    scan.Advance()
851
    return Constant(True)
852
  elif scan.Current() == "false":
853
    scan.Advance()
854
    return Constant(False)
855
  elif IsAlpha(scan.Current()):
856
    name = scan.Current()
857
    scan.Advance()
858
    return Outcome(name.lower())
859
  elif scan.Current() == '$':
860
    scan.Advance()
861
    if not IsAlpha(scan.Current()):
862
      return None
863
    name = scan.Current()
864
    scan.Advance()
865
    return Variable(name.lower())
866
  elif scan.Current() == '(':
867
    scan.Advance()
868
    result = ParseLogicalExpression(scan)
869
    if (not result) or (scan.Current() != ')'):
870
      return None
871
    scan.Advance()
872
    return result
873
  else:
874
    return None
875

    
876

    
877
BINARIES = ['==']
878
def ParseOperatorExpression(scan):
879
  left = ParseAtomicExpression(scan)
880
  if not left: return None
881
  while scan.HasMore() and (scan.Current() in BINARIES):
882
    op = scan.Current()
883
    scan.Advance()
884
    right = ParseOperatorExpression(scan)
885
    if not right:
886
      return None
887
    left = Operation(left, op, right)
888
  return left
889

    
890

    
891
def ParseConditionalExpression(scan):
892
  left = ParseOperatorExpression(scan)
893
  if not left: return None
894
  while scan.HasMore() and (scan.Current() == 'if'):
895
    scan.Advance()
896
    right = ParseOperatorExpression(scan)
897
    if not right:
898
      return None
899
    left=  Operation(left, 'if', right)
900
  return left
901

    
902

    
903
LOGICALS = ["&&", "||", ","]
904
def ParseLogicalExpression(scan):
905
  left = ParseConditionalExpression(scan)
906
  if not left: return None
907
  while scan.HasMore() and (scan.Current() in LOGICALS):
908
    op = scan.Current()
909
    scan.Advance()
910
    right = ParseConditionalExpression(scan)
911
    if not right:
912
      return None
913
    left = Operation(left, op, right)
914
  return left
915

    
916

    
917
def ParseCondition(expr):
918
  """Parses a logical expression into an Expression object"""
919
  tokens = Tokenizer(expr).Tokenize()
920
  if not tokens:
921
    print "Malformed expression: '%s'" % expr
922
    return None
923
  scan = Scanner(tokens)
924
  ast = ParseLogicalExpression(scan)
925
  if not ast:
926
    print "Malformed expression: '%s'" % expr
927
    return None
928
  if scan.HasMore():
929
    print "Malformed expression: '%s'" % expr
930
    return None
931
  return ast
932

    
933

    
934
class ClassifiedTest(object):
935

    
936
  def __init__(self, case, outcomes):
937
    self.case = case
938
    self.outcomes = outcomes
939

    
940

    
941
class Configuration(object):
942
  """The parsed contents of a configuration file"""
943

    
944
  def __init__(self, sections, defs):
945
    self.sections = sections
946
    self.defs = defs
947

    
948
  def ClassifyTests(self, cases, env):
949
    sections = [s for s in self.sections if s.condition.Evaluate(env, self.defs)]
950
    all_rules = reduce(list.__add__, [s.rules for s in sections], [])
951
    unused_rules = set(all_rules)
952
    result = [ ]
953
    all_outcomes = set([])
954
    for case in cases:
955
      matches = [ r for r in all_rules if r.Contains(case.path) ]
956
      outcomes = set([])
957
      for rule in matches:
958
        outcomes = outcomes.union(rule.GetOutcomes(env, self.defs))
959
        unused_rules.discard(rule)
960
      if not outcomes:
961
        outcomes = [PASS]
962
      case.outcomes = outcomes
963
      all_outcomes = all_outcomes.union(outcomes)
964
      result.append(ClassifiedTest(case, outcomes))
965
    return (result, list(unused_rules), all_outcomes)
966

    
967

    
968
class Section(object):
969
  """A section of the configuration file.  Sections are enabled or
970
  disabled prior to running the tests, based on their conditions"""
971

    
972
  def __init__(self, condition):
973
    self.condition = condition
974
    self.rules = [ ]
975

    
976
  def AddRule(self, rule):
977
    self.rules.append(rule)
978

    
979

    
980
class Rule(object):
981
  """A single rule that specifies the expected outcome for a single
982
  test."""
983

    
984
  def __init__(self, raw_path, path, value):
985
    self.raw_path = raw_path
986
    self.path = path
987
    self.value = value
988

    
989
  def GetOutcomes(self, env, defs):
990
    set = self.value.GetOutcomes(env, defs)
991
    assert isinstance(set, ListSet)
992
    return set.elms
993

    
994
  def Contains(self, path):
995
    if len(self.path) > len(path):
996
      return False
997
    for i in xrange(len(self.path)):
998
      if not self.path[i].match(path[i]):
999
        return False
1000
    return True
1001

    
1002

    
1003
HEADER_PATTERN = re.compile(r'\[([^]]+)\]')
1004
RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)')
1005
DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$')
1006
PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$')
1007

    
1008

    
1009
def ReadConfigurationInto(path, sections, defs):
1010
  current_section = Section(Constant(True))
1011
  sections.append(current_section)
1012
  prefix = []
1013
  for line in utils.ReadLinesFrom(path):
1014
    header_match = HEADER_PATTERN.match(line)
1015
    if header_match:
1016
      condition_str = header_match.group(1).strip()
1017
      condition = ParseCondition(condition_str)
1018
      new_section = Section(condition)
1019
      sections.append(new_section)
1020
      current_section = new_section
1021
      continue
1022
    rule_match = RULE_PATTERN.match(line)
1023
    if rule_match:
1024
      path = prefix + SplitPath(rule_match.group(1).strip())
1025
      value_str = rule_match.group(2).strip()
1026
      value = ParseCondition(value_str)
1027
      if not value:
1028
        return False
1029
      current_section.AddRule(Rule(rule_match.group(1), path, value))
1030
      continue
1031
    def_match = DEF_PATTERN.match(line)
1032
    if def_match:
1033
      name = def_match.group(1).lower()
1034
      value = ParseCondition(def_match.group(2).strip())
1035
      if not value:
1036
        return False
1037
      defs[name] = value
1038
      continue
1039
    prefix_match = PREFIX_PATTERN.match(line)
1040
    if prefix_match:
1041
      prefix = SplitPath(prefix_match.group(1).strip())
1042
      continue
1043
    print "Malformed line: '%s'." % line
1044
    return False
1045
  return True
1046

    
1047

    
1048
# ---------------
1049
# --- M a i n ---
1050
# ---------------
1051

    
1052

    
1053
ARCH_GUESS = utils.GuessArchitecture()
1054

    
1055

    
1056
def BuildOptions():
1057
  result = optparse.OptionParser()
1058
  result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
1059
      default='release')
1060
  result.add_option("-v", "--verbose", help="Verbose output",
1061
      default=False, action="store_true")
1062
  result.add_option("-S", dest="scons_flags", help="Flag to pass through to scons",
1063
      default=[], action="append")
1064
  result.add_option("-p", "--progress",
1065
      help="The style of progress indicator (verbose, dots, color, mono)",
1066
      choices=PROGRESS_INDICATORS.keys(), default="mono")
1067
  result.add_option("--no-build", help="Don't build requirements",
1068
      default=False, action="store_true")
1069
  result.add_option("--report", help="Print a summary of the tests to be run",
1070
      default=False, action="store_true")
1071
  result.add_option("-s", "--suite", help="A test suite",
1072
      default=[], action="append")
1073
  result.add_option("-t", "--timeout", help="Timeout in seconds",
1074
      default=60, type="int")
1075
  result.add_option("--arch", help='The architecture to run tests for',
1076
      default='none')
1077
  result.add_option("--simulator", help="Run tests with architecture simulator",
1078
      default='none')
1079
  result.add_option("--special-command", default=None)
1080
  result.add_option("--valgrind", help="Run tests through valgrind",
1081
      default=False, action="store_true")
1082
  result.add_option("--cat", help="Print the source of the tests",
1083
      default=False, action="store_true")
1084
  result.add_option("--warn-unused", help="Report unused rules",
1085
      default=False, action="store_true")
1086
  result.add_option("-j", help="The number of parallel tasks to run",
1087
      default=1, type="int")
1088
  result.add_option("--time", help="Print timing information after running",
1089
      default=False, action="store_true")
1090
  result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests",
1091
        dest="suppress_dialogs", default=True, action="store_true")
1092
  result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests",
1093
        dest="suppress_dialogs", action="store_false")
1094
  result.add_option("--shell", help="Path to V8 shell", default="shell");
1095
  return result
1096

    
1097

    
1098
def ProcessOptions(options):
1099
  global VERBOSE
1100
  VERBOSE = options.verbose
1101
  options.mode = options.mode.split(',')
1102
  for mode in options.mode:
1103
    if not mode in ['debug', 'release']:
1104
      print "Unknown mode %s" % mode
1105
      return False
1106
  if options.simulator != 'none':
1107
    # Simulator argument was set. Make sure arch and simulator agree.
1108
    if options.simulator != options.arch:
1109
      if options.arch == 'none':
1110
        options.arch = options.simulator
1111
      else:
1112
        print "Architecture %s does not match sim %s" %(options.arch, options.simulator)
1113
        return False
1114
    # Ensure that the simulator argument is handed down to scons.
1115
    options.scons_flags.append("simulator=" + options.simulator)
1116
  else:
1117
    # If options.arch is not set by the command line and no simulator setting
1118
    # was found, set the arch to the guess.
1119
    if options.arch == 'none':
1120
      options.arch = ARCH_GUESS
1121
  return True
1122

    
1123

    
1124
REPORT_TEMPLATE = """\
1125
Total: %(total)i tests
1126
 * %(skipped)4d tests will be skipped
1127
 * %(nocrash)4d tests are expected to be flaky but not crash
1128
 * %(pass)4d tests are expected to pass
1129
 * %(fail_ok)4d tests are expected to fail that we won't fix
1130
 * %(fail)4d tests are expected to fail that we should fix\
1131
"""
1132

    
1133
def PrintReport(cases):
1134
  def IsFlaky(o):
1135
    return (PASS in o) and (FAIL in o) and (not CRASH in o) and (not OKAY in o)
1136
  def IsFailOk(o):
1137
    return (len(o) == 2) and (FAIL in o) and (OKAY in o)
1138
  unskipped = [c for c in cases if not SKIP in c.outcomes]
1139
  print REPORT_TEMPLATE % {
1140
    'total': len(cases),
1141
    'skipped': len(cases) - len(unskipped),
1142
    'nocrash': len([t for t in unskipped if IsFlaky(t.outcomes)]),
1143
    'pass': len([t for t in unskipped if list(t.outcomes) == [PASS]]),
1144
    'fail_ok': len([t for t in unskipped if IsFailOk(t.outcomes)]),
1145
    'fail': len([t for t in unskipped if list(t.outcomes) == [FAIL]])
1146
  }
1147

    
1148

    
1149
class Pattern(object):
1150

    
1151
  def __init__(self, pattern):
1152
    self.pattern = pattern
1153
    self.compiled = None
1154

    
1155
  def match(self, str):
1156
    if not self.compiled:
1157
      pattern = "^" + self.pattern.replace('*', '.*') + "$"
1158
      self.compiled = re.compile(pattern)
1159
    return self.compiled.match(str)
1160

    
1161
  def __str__(self):
1162
    return self.pattern
1163

    
1164

    
1165
def SplitPath(s):
1166
  stripped = [ c.strip() for c in s.split('/') ]
1167
  return [ Pattern(s) for s in stripped if len(s) > 0 ]
1168

    
1169

    
1170
def GetSpecialCommandProcessor(value):
1171
  if (not value) or (value.find('@') == -1):
1172
    def ExpandCommand(args):
1173
      return args
1174
    return ExpandCommand
1175
  else:
1176
    pos = value.find('@')
1177
    import urllib
1178
    prefix = urllib.unquote(value[:pos]).split()
1179
    suffix = urllib.unquote(value[pos+1:]).split()
1180
    def ExpandCommand(args):
1181
      return prefix + args + suffix
1182
    return ExpandCommand
1183

    
1184

    
1185
BUILT_IN_TESTS = ['mjsunit', 'cctest', 'message']
1186

    
1187

    
1188
def GetSuites(test_root):
1189
  def IsSuite(path):
1190
    return isdir(path) and exists(join(path, 'testcfg.py'))
1191
  return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ]
1192

    
1193

    
1194
def FormatTime(d):
1195
  millis = round(d * 1000) % 1000
1196
  return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1197

    
1198

    
1199
def Main():
1200
  parser = BuildOptions()
1201
  (options, args) = parser.parse_args()
1202
  if not ProcessOptions(options):
1203
    parser.print_help()
1204
    return 1
1205

    
1206
  workspace = abspath(join(dirname(sys.argv[0]), '..'))
1207
  suites = GetSuites(join(workspace, 'test'))
1208
  repositories = [TestRepository(join(workspace, 'test', name)) for name in suites]
1209
  repositories += [TestRepository(a) for a in options.suite]
1210

    
1211
  root = LiteralTestSuite(repositories)
1212
  if len(args) == 0:
1213
    paths = [SplitPath(t) for t in BUILT_IN_TESTS]
1214
  else:
1215
    paths = [ ]
1216
    for arg in args:
1217
      path = SplitPath(arg)
1218
      paths.append(path)
1219

    
1220
  # Check for --valgrind option. If enabled, we overwrite the special
1221
  # command flag with a command that uses the run-valgrind.py script.
1222
  if options.valgrind:
1223
    run_valgrind = join(workspace, "tools", "run-valgrind.py")
1224
    options.special_command = "python -u " + run_valgrind + " @"
1225

    
1226
  shell = abspath(options.shell)
1227
  buildspace = dirname(shell)
1228
  context = Context(workspace, buildspace, VERBOSE,
1229
                    shell,
1230
                    options.timeout,
1231
                    GetSpecialCommandProcessor(options.special_command),
1232
                    options.suppress_dialogs)
1233
  # First build the required targets
1234
  if not options.no_build:
1235
    reqs = [ ]
1236
    for path in paths:
1237
      reqs += root.GetBuildRequirements(path, context)
1238
    reqs = list(set(reqs))
1239
    if len(reqs) > 0:
1240
      if options.j != 1:
1241
        options.scons_flags += ['-j', str(options.j)]
1242
      if not BuildRequirements(context, reqs, options.mode, options.scons_flags):
1243
        return 1
1244

    
1245
  # Get status for tests
1246
  sections = [ ]
1247
  defs = { }
1248
  root.GetTestStatus(context, sections, defs)
1249
  config = Configuration(sections, defs)
1250

    
1251
  # List the tests
1252
  all_cases = [ ]
1253
  all_unused = [ ]
1254
  unclassified_tests = [ ]
1255
  globally_unused_rules = None
1256
  for path in paths:
1257
    for mode in options.mode:
1258
      if not exists(context.GetVm(mode)):
1259
        print "Can't find shell executable: '%s'" % context.GetVm(mode)
1260
        continue
1261
      env = {
1262
        'mode': mode,
1263
        'system': utils.GuessOS(),
1264
        'arch': options.arch,
1265
        'simulator': options.simulator
1266
      }
1267
      test_list = root.ListTests([], path, context, mode)
1268
      unclassified_tests += test_list
1269
      (cases, unused_rules, all_outcomes) = config.ClassifyTests(test_list, env)
1270
      if globally_unused_rules is None:
1271
        globally_unused_rules = set(unused_rules)
1272
      else:
1273
        globally_unused_rules = globally_unused_rules.intersection(unused_rules)
1274
      all_cases += cases
1275
      all_unused.append(unused_rules)
1276

    
1277
  if options.cat:
1278
    visited = set()
1279
    for test in unclassified_tests:
1280
      key = tuple(test.path)
1281
      if key in visited:
1282
        continue
1283
      visited.add(key)
1284
      print "--- begin source: %s ---" % test.GetLabel()
1285
      source = test.GetSource().strip()
1286
      print source
1287
      print "--- end source: %s ---" % test.GetLabel()
1288
    return 0
1289

    
1290
  if options.warn_unused:
1291
    for rule in globally_unused_rules:
1292
      print "Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path])
1293

    
1294
  if options.report:
1295
    PrintReport(all_cases)
1296

    
1297
  result = None
1298
  if len(all_cases) == 0:
1299
    print "No tests to run."
1300
    return 0
1301
  else:
1302
    try:
1303
      start = time.time()
1304
      if RunTestCases(all_cases, options.progress, options.j):
1305
        result = 0
1306
      else:
1307
        result = 1
1308
      duration = time.time() - start
1309
    except KeyboardInterrupt:
1310
      print "Interrupted"
1311
      return 1
1312

    
1313
  if options.time:
1314
    # Write the times to stderr to make it easy to separate from the
1315
    # test output.
1316
    print
1317
    sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration))
1318
    timed_tests = [ t.case for t in all_cases if not t.case.duration is None ]
1319
    timed_tests.sort(lambda a, b: a.CompareTime(b))
1320
    index = 1
1321
    for entry in timed_tests[:20]:
1322
      t = FormatTime(entry.duration)
1323
      sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel()))
1324
      index += 1
1325

    
1326
  return result
1327

    
1328

    
1329
if __name__ == '__main__':
1330
  sys.exit(Main())