win,msi: broadcast WM_SETTINGCHANGE after install
[platform/upstream/nodejs.git] / tools / test.py
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 import platform
35 import re
36 import signal
37 import subprocess
38 import sys
39 import tempfile
40 import time
41 import threading
42 import utils
43 import multiprocessing
44 import errno
45
46 from os.path import join, dirname, abspath, basename, isdir, exists
47 from datetime import datetime
48 from Queue import Queue, Empty
49
50 VERBOSE = False
51
52
53 # ---------------------------------------------
54 # --- P r o g r e s s   I n d i c a t o r s ---
55 # ---------------------------------------------
56
57
58 class ProgressIndicator(object):
59
60   def __init__(self, cases, flaky_tests_mode):
61     self.cases = cases
62     self.flaky_tests_mode = flaky_tests_mode
63     self.parallel_queue = Queue(len(cases))
64     self.sequential_queue = Queue(len(cases))
65     for case in cases:
66       if case.parallel:
67         self.parallel_queue.put_nowait(case)
68       else:
69         self.sequential_queue.put_nowait(case)
70     self.succeeded = 0
71     self.remaining = len(cases)
72     self.total = len(cases)
73     self.failed = [ ]
74     self.crashed = 0
75     self.lock = threading.Lock()
76     self.shutdown_event = threading.Event()
77
78   def PrintFailureHeader(self, test):
79     if test.IsNegative():
80       negative_marker = '[negative] '
81     else:
82       negative_marker = ''
83     print "=== %(label)s %(negative)s===" % {
84       'label': test.GetLabel(),
85       'negative': negative_marker
86     }
87     print "Path: %s" % "/".join(test.path)
88
89   def Run(self, tasks):
90     self.Starting()
91     threads = []
92     # Spawn N-1 threads and then use this thread as the last one.
93     # That way -j1 avoids threading altogether which is a nice fallback
94     # in case of threading problems.
95     for i in xrange(tasks - 1):
96       thread = threading.Thread(target=self.RunSingle, args=[True, i + 1])
97       threads.append(thread)
98       thread.start()
99     try:
100       self.RunSingle(False, 0)
101       # Wait for the remaining threads
102       for thread in threads:
103         # Use a timeout so that signals (ctrl-c) will be processed.
104         thread.join(timeout=10000000)
105     except (KeyboardInterrupt, SystemExit), e:
106       self.shutdown_event.set()
107     except Exception, e:
108       # If there's an exception we schedule an interruption for any
109       # remaining threads.
110       self.shutdown_event.set()
111       # ...and then reraise the exception to bail out
112       raise
113     self.Done()
114     return not self.failed
115
116   def RunSingle(self, parallel, thread_id):
117     while not self.shutdown_event.is_set():
118       try:
119         test = self.parallel_queue.get_nowait()
120       except Empty:
121         if parallel:
122           return
123         try:
124           test = self.sequential_queue.get_nowait()
125         except Empty:
126           return
127       case = test.case
128       case.thread_id = thread_id
129       self.lock.acquire()
130       self.AboutToRun(case)
131       self.lock.release()
132       try:
133         start = datetime.now()
134         output = case.Run()
135         case.duration = (datetime.now() - start)
136       except IOError, e:
137         return
138       if self.shutdown_event.is_set():
139         return
140       self.lock.acquire()
141       if output.UnexpectedOutput():
142         self.failed.append(output)
143         if output.HasCrashed():
144           self.crashed += 1
145       else:
146         self.succeeded += 1
147       self.remaining -= 1
148       self.HasRun(output)
149       self.lock.release()
150
151
152 def EscapeCommand(command):
153   parts = []
154   for part in command:
155     if ' ' in part:
156       # Escape spaces.  We may need to escape more characters for this
157       # to work properly.
158       parts.append('"%s"' % part)
159     else:
160       parts.append(part)
161   return " ".join(parts)
162
163
164 class SimpleProgressIndicator(ProgressIndicator):
165
166   def Starting(self):
167     print 'Running %i tests' % len(self.cases)
168
169   def Done(self):
170     print
171     for failed in self.failed:
172       self.PrintFailureHeader(failed.test)
173       if failed.output.stderr:
174         print "--- stderr ---"
175         print failed.output.stderr.strip()
176       if failed.output.stdout:
177         print "--- stdout ---"
178         print failed.output.stdout.strip()
179       print "Command: %s" % EscapeCommand(failed.command)
180       if failed.HasCrashed():
181         print "--- CRASHED ---"
182       if failed.HasTimedOut():
183         print "--- TIMEOUT ---"
184     if len(self.failed) == 0:
185       print "==="
186       print "=== All tests succeeded"
187       print "==="
188     else:
189       print
190       print "==="
191       print "=== %i tests failed" % len(self.failed)
192       if self.crashed > 0:
193         print "=== %i tests CRASHED" % self.crashed
194       print "==="
195
196
197 class VerboseProgressIndicator(SimpleProgressIndicator):
198
199   def AboutToRun(self, case):
200     print 'Starting %s...' % case.GetLabel()
201     sys.stdout.flush()
202
203   def HasRun(self, output):
204     if output.UnexpectedOutput():
205       if output.HasCrashed():
206         outcome = 'CRASH'
207       else:
208         outcome = 'FAIL'
209     else:
210       outcome = 'pass'
211     print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
212
213
214 class DotsProgressIndicator(SimpleProgressIndicator):
215
216   def AboutToRun(self, case):
217     pass
218
219   def HasRun(self, output):
220     total = self.succeeded + len(self.failed)
221     if (total > 1) and (total % 50 == 1):
222       sys.stdout.write('\n')
223     if output.UnexpectedOutput():
224       if output.HasCrashed():
225         sys.stdout.write('C')
226         sys.stdout.flush()
227       elif output.HasTimedOut():
228         sys.stdout.write('T')
229         sys.stdout.flush()
230       else:
231         sys.stdout.write('F')
232         sys.stdout.flush()
233     else:
234       sys.stdout.write('.')
235       sys.stdout.flush()
236
237
238 class TapProgressIndicator(SimpleProgressIndicator):
239
240   def Starting(self):
241     print '1..%i' % len(self.cases)
242     self._done = 0
243
244   def AboutToRun(self, case):
245     pass
246
247   def HasRun(self, output):
248     self._done += 1
249     command = basename(output.command[-1])
250     if output.UnexpectedOutput():
251       status_line = 'not ok %i - %s' % (self._done, command)
252       if FLAKY in output.test.outcomes and self.flaky_tests_mode == "dontcare":
253         status_line = status_line + " # TODO : Fix flaky test"
254       print status_line
255       for l in output.output.stderr.splitlines():
256         print '#' + l
257       for l in output.output.stdout.splitlines():
258         print '#' + l
259     else:
260       status_line = 'ok %i - %s' % (self._done, command)
261       if FLAKY in output.test.outcomes:
262         status_line = status_line + " # TODO : Fix flaky test"
263       print status_line
264
265     duration = output.test.duration
266
267     # total_seconds() was added in 2.7
268     total_seconds = (duration.microseconds +
269       (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6
270
271     print '  ---'
272     print '  duration_ms: %d.%d' % (total_seconds, duration.microseconds / 1000)
273     print '  ...'
274
275   def Done(self):
276     pass
277
278
279 class CompactProgressIndicator(ProgressIndicator):
280
281   def __init__(self, cases, flaky_tests_mode, templates):
282     super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode)
283     self.templates = templates
284     self.last_status_length = 0
285     self.start_time = time.time()
286
287   def Starting(self):
288     pass
289
290   def Done(self):
291     self.PrintProgress('Done')
292
293   def AboutToRun(self, case):
294     self.PrintProgress(case.GetLabel())
295
296   def HasRun(self, output):
297     if output.UnexpectedOutput():
298       self.ClearLine(self.last_status_length)
299       self.PrintFailureHeader(output.test)
300       stdout = output.output.stdout.strip()
301       if len(stdout):
302         print self.templates['stdout'] % stdout
303       stderr = output.output.stderr.strip()
304       if len(stderr):
305         print self.templates['stderr'] % stderr
306       print "Command: %s" % EscapeCommand(output.command)
307       if output.HasCrashed():
308         print "--- CRASHED ---"
309       if output.HasTimedOut():
310         print "--- TIMEOUT ---"
311
312   def Truncate(self, str, length):
313     if length and (len(str) > (length - 3)):
314       return str[:(length-3)] + "..."
315     else:
316       return str
317
318   def PrintProgress(self, name):
319     self.ClearLine(self.last_status_length)
320     elapsed = time.time() - self.start_time
321     status = self.templates['status_line'] % {
322       'passed': self.succeeded,
323       'remaining': (((self.total - self.remaining) * 100) // self.total),
324       'failed': len(self.failed),
325       'test': name,
326       'mins': int(elapsed) / 60,
327       'secs': int(elapsed) % 60
328     }
329     status = self.Truncate(status, 78)
330     self.last_status_length = len(status)
331     print status,
332     sys.stdout.flush()
333
334
335 class ColorProgressIndicator(CompactProgressIndicator):
336
337   def __init__(self, cases, flaky_tests_mode):
338     templates = {
339       '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",
340       'stdout': "\033[1m%s\033[0m",
341       'stderr': "\033[31m%s\033[0m",
342     }
343     super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
344
345   def ClearLine(self, last_line_length):
346     print "\033[1K\r",
347
348
349 class MonochromeProgressIndicator(CompactProgressIndicator):
350
351   def __init__(self, cases, flaky_tests_mode):
352     templates = {
353       'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
354       'stdout': '%s',
355       'stderr': '%s',
356       'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
357       'max_length': 78
358     }
359     super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
360
361   def ClearLine(self, last_line_length):
362     print ("\r" + (" " * last_line_length) + "\r"),
363
364
365 PROGRESS_INDICATORS = {
366   'verbose': VerboseProgressIndicator,
367   'dots': DotsProgressIndicator,
368   'color': ColorProgressIndicator,
369   'tap': TapProgressIndicator,
370   'mono': MonochromeProgressIndicator
371 }
372
373
374 # -------------------------
375 # --- F r a m e w o r k ---
376 # -------------------------
377
378
379 class CommandOutput(object):
380
381   def __init__(self, exit_code, timed_out, stdout, stderr):
382     self.exit_code = exit_code
383     self.timed_out = timed_out
384     self.stdout = stdout
385     self.stderr = stderr
386     self.failed = None
387
388
389 class TestCase(object):
390
391   def __init__(self, context, path, arch, mode):
392     self.path = path
393     self.context = context
394     self.duration = None
395     self.arch = arch
396     self.mode = mode
397     self.parallel = False
398     self.thread_id = 0
399
400   def IsNegative(self):
401     return False
402
403   def CompareTime(self, other):
404     return cmp(other.duration, self.duration)
405
406   def DidFail(self, output):
407     if output.failed is None:
408       output.failed = self.IsFailureOutput(output)
409     return output.failed
410
411   def IsFailureOutput(self, output):
412     return output.exit_code != 0
413
414   def GetSource(self):
415     return "(no source available)"
416
417   def RunCommand(self, command, env):
418     full_command = self.context.processor(command)
419     output = Execute(full_command,
420                      self.context,
421                      self.context.GetTimeout(self.mode),
422                      env)
423     self.Cleanup()
424     return TestOutput(self,
425                       full_command,
426                       output,
427                       self.context.store_unexpected_output)
428
429   def BeforeRun(self):
430     pass
431
432   def AfterRun(self, result):
433     pass
434
435   def Run(self):
436     self.BeforeRun()
437
438     try:
439       result = self.RunCommand(self.GetCommand(), {
440         "TEST_THREAD_ID": "%d" % self.thread_id
441       })
442     finally:
443       # Tests can leave the tty in non-blocking mode. If the test runner
444       # tries to print to stdout/stderr after that and the tty buffer is
445       # full, it'll die with a EAGAIN OSError. Ergo, put the tty back in
446       # blocking mode before proceeding.
447       if sys.platform != 'win32':
448         from fcntl import fcntl, F_GETFL, F_SETFL
449         from os import O_NONBLOCK
450         for fd in 0,1,2: fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL))
451
452     self.AfterRun(result)
453     return result
454
455   def Cleanup(self):
456     return
457
458
459 class TestOutput(object):
460
461   def __init__(self, test, command, output, store_unexpected_output):
462     self.test = test
463     self.command = command
464     self.output = output
465     self.store_unexpected_output = store_unexpected_output
466
467   def UnexpectedOutput(self):
468     if self.HasCrashed():
469       outcome = CRASH
470     elif self.HasTimedOut():
471       outcome = TIMEOUT
472     elif self.HasFailed():
473       outcome = FAIL
474     else:
475       outcome = PASS
476     return not outcome in self.test.outcomes
477
478   def HasPreciousOutput(self):
479     return self.UnexpectedOutput() and self.store_unexpected_output
480
481   def HasCrashed(self):
482     if utils.IsWindows():
483       return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
484     else:
485       # Timed out tests will have exit_code -signal.SIGTERM.
486       if self.output.timed_out:
487         return False
488       return self.output.exit_code < 0 and \
489              self.output.exit_code != -signal.SIGABRT
490
491   def HasTimedOut(self):
492     return self.output.timed_out;
493
494   def HasFailed(self):
495     execution_failed = self.test.DidFail(self.output)
496     if self.test.IsNegative():
497       return not execution_failed
498     else:
499       return execution_failed
500
501
502 def KillProcessWithID(pid):
503   if utils.IsWindows():
504     os.popen('taskkill /T /F /PID %d' % pid)
505   else:
506     os.kill(pid, signal.SIGTERM)
507
508
509 MAX_SLEEP_TIME = 0.1
510 INITIAL_SLEEP_TIME = 0.0001
511 SLEEP_TIME_FACTOR = 1.25
512
513 SEM_INVALID_VALUE = -1
514 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
515
516 def Win32SetErrorMode(mode):
517   prev_error_mode = SEM_INVALID_VALUE
518   try:
519     import ctypes
520     prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
521   except ImportError:
522     pass
523   return prev_error_mode
524
525 def RunProcess(context, timeout, args, **rest):
526   if context.verbose: print "#", " ".join(args)
527   popen_args = args
528   prev_error_mode = SEM_INVALID_VALUE;
529   if utils.IsWindows():
530     if context.suppress_dialogs:
531       # Try to change the error mode to avoid dialogs on fatal errors. Don't
532       # touch any existing error mode flags by merging the existing error mode.
533       # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
534       error_mode = SEM_NOGPFAULTERRORBOX;
535       prev_error_mode = Win32SetErrorMode(error_mode);
536       Win32SetErrorMode(error_mode | prev_error_mode);
537   process = subprocess.Popen(
538     shell = utils.IsWindows(),
539     args = popen_args,
540     **rest
541   )
542   if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
543     Win32SetErrorMode(prev_error_mode)
544   # Compute the end time - if the process crosses this limit we
545   # consider it timed out.
546   if timeout is None: end_time = None
547   else: end_time = time.time() + timeout
548   timed_out = False
549   # Repeatedly check the exit code from the process in a
550   # loop and keep track of whether or not it times out.
551   exit_code = None
552   sleep_time = INITIAL_SLEEP_TIME
553   while exit_code is None:
554     if (not end_time is None) and (time.time() >= end_time):
555       # Kill the process and wait for it to exit.
556       KillProcessWithID(process.pid)
557       exit_code = process.wait()
558       timed_out = True
559     else:
560       exit_code = process.poll()
561       time.sleep(sleep_time)
562       sleep_time = sleep_time * SLEEP_TIME_FACTOR
563       if sleep_time > MAX_SLEEP_TIME:
564         sleep_time = MAX_SLEEP_TIME
565   return (process, exit_code, timed_out)
566
567
568 def PrintError(str):
569   sys.stderr.write(str)
570   sys.stderr.write('\n')
571
572
573 def CheckedUnlink(name):
574   while True:
575     try:
576       os.unlink(name)
577     except OSError, e:
578       # On Windows unlink() fails if another process (typically a virus scanner
579       # or the indexing service) has the file open. Those processes keep a
580       # file open for a short time only, so yield and try again; it'll succeed.
581       if sys.platform == 'win32' and e.errno == errno.EACCES:
582         time.sleep(0)
583         continue
584       PrintError("os.unlink() " + str(e))
585     break
586
587 def Execute(args, context, timeout=None, env={}):
588   (fd_out, outname) = tempfile.mkstemp()
589   (fd_err, errname) = tempfile.mkstemp()
590
591   # Extend environment
592   env_copy = os.environ.copy()
593   for key, value in env.iteritems():
594     env_copy[key] = value
595
596   (process, exit_code, timed_out) = RunProcess(
597     context,
598     timeout,
599     args = args,
600     stdout = fd_out,
601     stderr = fd_err,
602     env = env_copy
603   )
604   os.close(fd_out)
605   os.close(fd_err)
606   output = file(outname).read()
607   errors = file(errname).read()
608   CheckedUnlink(outname)
609   CheckedUnlink(errname)
610   return CommandOutput(exit_code, timed_out, output, errors)
611
612
613 def ExecuteNoCapture(args, context, timeout=None):
614   (process, exit_code, timed_out) = RunProcess(
615     context,
616     timeout,
617     args = args,
618   )
619   return CommandOutput(exit_code, False, "", "")
620
621
622 def CarCdr(path):
623   if len(path) == 0:
624     return (None, [ ])
625   else:
626     return (path[0], path[1:])
627
628
629 class TestConfiguration(object):
630
631   def __init__(self, context, root):
632     self.context = context
633     self.root = root
634
635   def Contains(self, path, file):
636     if len(path) > len(file):
637       return False
638     for i in xrange(len(path)):
639       if not path[i].match(file[i]):
640         return False
641     return True
642
643   def GetTestStatus(self, sections, defs):
644     pass
645
646
647 class TestSuite(object):
648
649   def __init__(self, name):
650     self.name = name
651
652   def GetName(self):
653     return self.name
654
655
656 # Use this to run several variants of the tests, e.g.:
657 # VARIANT_FLAGS = [[], ['--always_compact', '--noflush_code']]
658 VARIANT_FLAGS = [[]]
659
660
661 class TestRepository(TestSuite):
662
663   def __init__(self, path):
664     normalized_path = abspath(path)
665     super(TestRepository, self).__init__(basename(normalized_path))
666     self.path = normalized_path
667     self.is_loaded = False
668     self.config = None
669
670   def GetConfiguration(self, context):
671     if self.is_loaded:
672       return self.config
673     self.is_loaded = True
674     file = None
675     try:
676       (file, pathname, description) = imp.find_module('testcfg', [ self.path ])
677       module = imp.load_module('testcfg', file, pathname, description)
678       self.config = module.GetConfiguration(context, self.path)
679     finally:
680       if file:
681         file.close()
682     return self.config
683
684   def GetBuildRequirements(self, path, context):
685     return self.GetConfiguration(context).GetBuildRequirements()
686
687   def AddTestsToList(self, result, current_path, path, context, arch, mode):
688     for v in VARIANT_FLAGS:
689       tests = self.GetConfiguration(context).ListTests(current_path, path,
690                                                        arch, mode)
691       for t in tests: t.variant_flags = v
692       result += tests
693
694
695   def GetTestStatus(self, context, sections, defs):
696     self.GetConfiguration(context).GetTestStatus(sections, defs)
697
698
699 class LiteralTestSuite(TestSuite):
700
701   def __init__(self, tests):
702     super(LiteralTestSuite, self).__init__('root')
703     self.tests = tests
704
705   def GetBuildRequirements(self, path, context):
706     (name, rest) = CarCdr(path)
707     result = [ ]
708     for test in self.tests:
709       if not name or name.match(test.GetName()):
710         result += test.GetBuildRequirements(rest, context)
711     return result
712
713   def ListTests(self, current_path, path, context, arch, mode):
714     (name, rest) = CarCdr(path)
715     result = [ ]
716     for test in self.tests:
717       test_name = test.GetName()
718       if not name or name.match(test_name):
719         full_path = current_path + [test_name]
720         test.AddTestsToList(result, full_path, path, context, arch, mode)
721     result.sort(cmp=lambda a, b: cmp(a.GetName(), b.GetName()))
722     return result
723
724   def GetTestStatus(self, context, sections, defs):
725     for test in self.tests:
726       test.GetTestStatus(context, sections, defs)
727
728
729 SUFFIX = {
730     'debug'   : '_g',
731     'release' : '' }
732 FLAGS = {
733     'debug'   : ['--enable-slow-asserts', '--debug-code', '--verify-heap'],
734     'release' : []}
735 TIMEOUT_SCALEFACTOR = {
736     'debug'   : 4,
737     'release' : 1 }
738
739
740 class Context(object):
741
742   def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, suppress_dialogs, store_unexpected_output):
743     self.workspace = workspace
744     self.buildspace = buildspace
745     self.verbose = verbose
746     self.vm_root = vm
747     self.timeout = timeout
748     self.processor = processor
749     self.suppress_dialogs = suppress_dialogs
750     self.store_unexpected_output = store_unexpected_output
751
752   def GetVm(self, arch, mode):
753     if arch == 'none':
754       name = 'out/Debug/iojs' if mode == 'debug' else 'out/Release/iojs'
755     else:
756       name = 'out/%s.%s/iojs' % (arch, mode)
757
758     # Currently GYP does not support output_dir for MSVS.
759     # http://code.google.com/p/gyp/issues/detail?id=40
760     # It will put the builds into Release/iojs.exe or Debug/iojs.exe
761     if utils.IsWindows():
762       out_dir = os.path.join(dirname(__file__), "..", "out")
763       if not exists(out_dir):
764         if mode == 'debug':
765           name = os.path.abspath('Debug/iojs.exe')
766         else:
767           name = os.path.abspath('Release/iojs.exe')
768       else:
769         name = os.path.abspath(name + '.exe')
770
771     return name
772
773   def GetVmFlags(self, testcase, mode):
774     return testcase.variant_flags + FLAGS[mode]
775
776   def GetTimeout(self, mode):
777     return self.timeout * TIMEOUT_SCALEFACTOR[mode]
778
779 def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode):
780   progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode)
781   return progress.Run(tasks)
782
783
784 def BuildRequirements(context, requirements, mode, scons_flags):
785   command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
786                   + requirements
787                   + scons_flags)
788   output = ExecuteNoCapture(command_line, context)
789   return output.exit_code == 0
790
791
792 # -------------------------------------------
793 # --- T e s t   C o n f i g u r a t i o n ---
794 # -------------------------------------------
795
796
797 SKIP = 'skip'
798 FAIL = 'fail'
799 PASS = 'pass'
800 OKAY = 'okay'
801 TIMEOUT = 'timeout'
802 CRASH = 'crash'
803 SLOW = 'slow'
804 FLAKY = 'flaky'
805
806
807 class Expression(object):
808   pass
809
810
811 class Constant(Expression):
812
813   def __init__(self, value):
814     self.value = value
815
816   def Evaluate(self, env, defs):
817     return self.value
818
819
820 class Variable(Expression):
821
822   def __init__(self, name):
823     self.name = name
824
825   def GetOutcomes(self, env, defs):
826     if self.name in env: return ListSet([env[self.name]])
827     else: return Nothing()
828
829
830 class Outcome(Expression):
831
832   def __init__(self, name):
833     self.name = name
834
835   def GetOutcomes(self, env, defs):
836     if self.name in defs:
837       return defs[self.name].GetOutcomes(env, defs)
838     else:
839       return ListSet([self.name])
840
841
842 class Set(object):
843   pass
844
845
846 class ListSet(Set):
847
848   def __init__(self, elms):
849     self.elms = elms
850
851   def __str__(self):
852     return "ListSet%s" % str(self.elms)
853
854   def Intersect(self, that):
855     if not isinstance(that, ListSet):
856       return that.Intersect(self)
857     return ListSet([ x for x in self.elms if x in that.elms ])
858
859   def Union(self, that):
860     if not isinstance(that, ListSet):
861       return that.Union(self)
862     return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ])
863
864   def IsEmpty(self):
865     return len(self.elms) == 0
866
867
868 class Everything(Set):
869
870   def Intersect(self, that):
871     return that
872
873   def Union(self, that):
874     return self
875
876   def IsEmpty(self):
877     return False
878
879
880 class Nothing(Set):
881
882   def Intersect(self, that):
883     return self
884
885   def Union(self, that):
886     return that
887
888   def IsEmpty(self):
889     return True
890
891
892 class Operation(Expression):
893
894   def __init__(self, left, op, right):
895     self.left = left
896     self.op = op
897     self.right = right
898
899   def Evaluate(self, env, defs):
900     if self.op == '||' or self.op == ',':
901       return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
902     elif self.op == 'if':
903       return False
904     elif self.op == '==':
905       inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
906       return not inter.IsEmpty()
907     else:
908       assert self.op == '&&'
909       return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
910
911   def GetOutcomes(self, env, defs):
912     if self.op == '||' or self.op == ',':
913       return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env, defs))
914     elif self.op == 'if':
915       if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs)
916       else: return Nothing()
917     else:
918       assert self.op == '&&'
919       return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
920
921
922 def IsAlpha(str):
923   for char in str:
924     if not (char.isalpha() or char.isdigit() or char == '_'):
925       return False
926   return True
927
928
929 class Tokenizer(object):
930   """A simple string tokenizer that chops expressions into variables,
931   parens and operators"""
932
933   def __init__(self, expr):
934     self.index = 0
935     self.expr = expr
936     self.length = len(expr)
937     self.tokens = None
938
939   def Current(self, length = 1):
940     if not self.HasMore(length): return ""
941     return self.expr[self.index:self.index+length]
942
943   def HasMore(self, length = 1):
944     return self.index < self.length + (length - 1)
945
946   def Advance(self, count = 1):
947     self.index = self.index + count
948
949   def AddToken(self, token):
950     self.tokens.append(token)
951
952   def SkipSpaces(self):
953     while self.HasMore() and self.Current().isspace():
954       self.Advance()
955
956   def Tokenize(self):
957     self.tokens = [ ]
958     while self.HasMore():
959       self.SkipSpaces()
960       if not self.HasMore():
961         return None
962       if self.Current() == '(':
963         self.AddToken('(')
964         self.Advance()
965       elif self.Current() == ')':
966         self.AddToken(')')
967         self.Advance()
968       elif self.Current() == '$':
969         self.AddToken('$')
970         self.Advance()
971       elif self.Current() == ',':
972         self.AddToken(',')
973         self.Advance()
974       elif IsAlpha(self.Current()):
975         buf = ""
976         while self.HasMore() and IsAlpha(self.Current()):
977           buf += self.Current()
978           self.Advance()
979         self.AddToken(buf)
980       elif self.Current(2) == '&&':
981         self.AddToken('&&')
982         self.Advance(2)
983       elif self.Current(2) == '||':
984         self.AddToken('||')
985         self.Advance(2)
986       elif self.Current(2) == '==':
987         self.AddToken('==')
988         self.Advance(2)
989       else:
990         return None
991     return self.tokens
992
993
994 class Scanner(object):
995   """A simple scanner that can serve out tokens from a given list"""
996
997   def __init__(self, tokens):
998     self.tokens = tokens
999     self.length = len(tokens)
1000     self.index = 0
1001
1002   def HasMore(self):
1003     return self.index < self.length
1004
1005   def Current(self):
1006     return self.tokens[self.index]
1007
1008   def Advance(self):
1009     self.index = self.index + 1
1010
1011
1012 def ParseAtomicExpression(scan):
1013   if scan.Current() == "true":
1014     scan.Advance()
1015     return Constant(True)
1016   elif scan.Current() == "false":
1017     scan.Advance()
1018     return Constant(False)
1019   elif IsAlpha(scan.Current()):
1020     name = scan.Current()
1021     scan.Advance()
1022     return Outcome(name.lower())
1023   elif scan.Current() == '$':
1024     scan.Advance()
1025     if not IsAlpha(scan.Current()):
1026       return None
1027     name = scan.Current()
1028     scan.Advance()
1029     return Variable(name.lower())
1030   elif scan.Current() == '(':
1031     scan.Advance()
1032     result = ParseLogicalExpression(scan)
1033     if (not result) or (scan.Current() != ')'):
1034       return None
1035     scan.Advance()
1036     return result
1037   else:
1038     return None
1039
1040
1041 BINARIES = ['==']
1042 def ParseOperatorExpression(scan):
1043   left = ParseAtomicExpression(scan)
1044   if not left: return None
1045   while scan.HasMore() and (scan.Current() in BINARIES):
1046     op = scan.Current()
1047     scan.Advance()
1048     right = ParseOperatorExpression(scan)
1049     if not right:
1050       return None
1051     left = Operation(left, op, right)
1052   return left
1053
1054
1055 def ParseConditionalExpression(scan):
1056   left = ParseOperatorExpression(scan)
1057   if not left: return None
1058   while scan.HasMore() and (scan.Current() == 'if'):
1059     scan.Advance()
1060     right = ParseOperatorExpression(scan)
1061     if not right:
1062       return None
1063     left=  Operation(left, 'if', right)
1064   return left
1065
1066
1067 LOGICALS = ["&&", "||", ","]
1068 def ParseLogicalExpression(scan):
1069   left = ParseConditionalExpression(scan)
1070   if not left: return None
1071   while scan.HasMore() and (scan.Current() in LOGICALS):
1072     op = scan.Current()
1073     scan.Advance()
1074     right = ParseConditionalExpression(scan)
1075     if not right:
1076       return None
1077     left = Operation(left, op, right)
1078   return left
1079
1080
1081 def ParseCondition(expr):
1082   """Parses a logical expression into an Expression object"""
1083   tokens = Tokenizer(expr).Tokenize()
1084   if not tokens:
1085     print "Malformed expression: '%s'" % expr
1086     return None
1087   scan = Scanner(tokens)
1088   ast = ParseLogicalExpression(scan)
1089   if not ast:
1090     print "Malformed expression: '%s'" % expr
1091     return None
1092   if scan.HasMore():
1093     print "Malformed expression: '%s'" % expr
1094     return None
1095   return ast
1096
1097
1098 class ClassifiedTest(object):
1099
1100   def __init__(self, case, outcomes):
1101     self.case = case
1102     self.outcomes = outcomes
1103     self.parallel = self.case.parallel
1104
1105
1106 class Configuration(object):
1107   """The parsed contents of a configuration file"""
1108
1109   def __init__(self, sections, defs):
1110     self.sections = sections
1111     self.defs = defs
1112
1113   def ClassifyTests(self, cases, env):
1114     sections = [s for s in self.sections if s.condition.Evaluate(env, self.defs)]
1115     all_rules = reduce(list.__add__, [s.rules for s in sections], [])
1116     unused_rules = set(all_rules)
1117     result = [ ]
1118     all_outcomes = set([])
1119     for case in cases:
1120       matches = [ r for r in all_rules if r.Contains(case.path) ]
1121       outcomes = set([])
1122       for rule in matches:
1123         outcomes = outcomes.union(rule.GetOutcomes(env, self.defs))
1124         unused_rules.discard(rule)
1125       if not outcomes:
1126         outcomes = [PASS]
1127       case.outcomes = outcomes
1128       all_outcomes = all_outcomes.union(outcomes)
1129       result.append(ClassifiedTest(case, outcomes))
1130     return (result, list(unused_rules), all_outcomes)
1131
1132
1133 class Section(object):
1134   """A section of the configuration file.  Sections are enabled or
1135   disabled prior to running the tests, based on their conditions"""
1136
1137   def __init__(self, condition):
1138     self.condition = condition
1139     self.rules = [ ]
1140
1141   def AddRule(self, rule):
1142     self.rules.append(rule)
1143
1144
1145 class Rule(object):
1146   """A single rule that specifies the expected outcome for a single
1147   test."""
1148
1149   def __init__(self, raw_path, path, value):
1150     self.raw_path = raw_path
1151     self.path = path
1152     self.value = value
1153
1154   def GetOutcomes(self, env, defs):
1155     set = self.value.GetOutcomes(env, defs)
1156     assert isinstance(set, ListSet)
1157     return set.elms
1158
1159   def Contains(self, path):
1160     if len(self.path) > len(path):
1161       return False
1162     for i in xrange(len(self.path)):
1163       if not self.path[i].match(path[i]):
1164         return False
1165     return True
1166
1167
1168 HEADER_PATTERN = re.compile(r'\[([^]]+)\]')
1169 RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)')
1170 DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$')
1171 PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$')
1172
1173
1174 def ReadConfigurationInto(path, sections, defs):
1175   current_section = Section(Constant(True))
1176   sections.append(current_section)
1177   prefix = []
1178   for line in utils.ReadLinesFrom(path):
1179     header_match = HEADER_PATTERN.match(line)
1180     if header_match:
1181       condition_str = header_match.group(1).strip()
1182       condition = ParseCondition(condition_str)
1183       new_section = Section(condition)
1184       sections.append(new_section)
1185       current_section = new_section
1186       continue
1187     rule_match = RULE_PATTERN.match(line)
1188     if rule_match:
1189       path = prefix + SplitPath(rule_match.group(1).strip())
1190       value_str = rule_match.group(2).strip()
1191       value = ParseCondition(value_str)
1192       if not value:
1193         return False
1194       current_section.AddRule(Rule(rule_match.group(1), path, value))
1195       continue
1196     def_match = DEF_PATTERN.match(line)
1197     if def_match:
1198       name = def_match.group(1).lower()
1199       value = ParseCondition(def_match.group(2).strip())
1200       if not value:
1201         return False
1202       defs[name] = value
1203       continue
1204     prefix_match = PREFIX_PATTERN.match(line)
1205     if prefix_match:
1206       prefix = SplitPath(prefix_match.group(1).strip())
1207       continue
1208     print "Malformed line: '%s'." % line
1209     return False
1210   return True
1211
1212
1213 # ---------------
1214 # --- M a i n ---
1215 # ---------------
1216
1217
1218 ARCH_GUESS = utils.GuessArchitecture()
1219
1220
1221 def BuildOptions():
1222   result = optparse.OptionParser()
1223   result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
1224       default='release')
1225   result.add_option("-v", "--verbose", help="Verbose output",
1226       default=False, action="store_true")
1227   result.add_option("-S", dest="scons_flags", help="Flag to pass through to scons",
1228       default=[], action="append")
1229   result.add_option("-p", "--progress",
1230       help="The style of progress indicator (verbose, dots, color, mono, tap)",
1231       choices=PROGRESS_INDICATORS.keys(), default="mono")
1232   result.add_option("--no-build", help="Don't build requirements",
1233       default=True, action="store_true")
1234   result.add_option("--build-only", help="Only build requirements, don't run the tests",
1235       default=False, action="store_true")
1236   result.add_option("--report", help="Print a summary of the tests to be run",
1237       default=False, action="store_true")
1238   result.add_option("-s", "--suite", help="A test suite",
1239       default=[], action="append")
1240   result.add_option("-t", "--timeout", help="Timeout in seconds",
1241       default=60, type="int")
1242   result.add_option("--arch", help='The architecture to run tests for',
1243       default='none')
1244   result.add_option("--snapshot", help="Run the tests with snapshot turned on",
1245       default=False, action="store_true")
1246   result.add_option("--special-command", default=None)
1247   result.add_option("--valgrind", help="Run tests through valgrind",
1248       default=False, action="store_true")
1249   result.add_option("--cat", help="Print the source of the tests",
1250       default=False, action="store_true")
1251   result.add_option("--flaky-tests",
1252       help="Regard tests marked as flaky (run|skip|dontcare)",
1253       default="run")
1254   result.add_option("--warn-unused", help="Report unused rules",
1255       default=False, action="store_true")
1256   result.add_option("-j", help="The number of parallel tasks to run",
1257       default=1, type="int")
1258   result.add_option("-J", help="Run tasks in parallel on all cores",
1259       default=False, action="store_true")
1260   result.add_option("--time", help="Print timing information after running",
1261       default=False, action="store_true")
1262   result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests",
1263         dest="suppress_dialogs", default=True, action="store_true")
1264   result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests",
1265         dest="suppress_dialogs", action="store_false")
1266   result.add_option("--shell", help="Path to V8 shell", default="shell")
1267   result.add_option("--store-unexpected-output", 
1268       help="Store the temporary JS files from tests that fails",
1269       dest="store_unexpected_output", default=True, action="store_true")
1270   result.add_option("--no-store-unexpected-output", 
1271       help="Deletes the temporary JS files from tests that fails",
1272       dest="store_unexpected_output", action="store_false")
1273   return result
1274
1275
1276 def ProcessOptions(options):
1277   global VERBOSE
1278   VERBOSE = options.verbose
1279   options.arch = options.arch.split(',')
1280   options.mode = options.mode.split(',')
1281   if options.J:
1282     options.j = multiprocessing.cpu_count()
1283   def CheckTestMode(name, option):
1284     if not option in ["run", "skip", "dontcare"]:
1285       print "Unknown %s mode %s" % (name, option)
1286       return False
1287     return True
1288   if not CheckTestMode("--flaky-tests", options.flaky_tests):
1289     return False
1290   return True
1291
1292
1293 REPORT_TEMPLATE = """\
1294 Total: %(total)i tests
1295  * %(skipped)4d tests will be skipped
1296  * %(nocrash)4d tests are expected to be flaky but not crash
1297  * %(pass)4d tests are expected to pass
1298  * %(fail_ok)4d tests are expected to fail that we won't fix
1299  * %(fail)4d tests are expected to fail that we should fix\
1300 """
1301
1302 def PrintReport(cases):
1303   def IsFlaky(o):
1304     return (PASS in o) and (FAIL in o) and (not CRASH in o) and (not OKAY in o)
1305   def IsFailOk(o):
1306     return (len(o) == 2) and (FAIL in o) and (OKAY in o)
1307   unskipped = [c for c in cases if not SKIP in c.outcomes]
1308   print REPORT_TEMPLATE % {
1309     'total': len(cases),
1310     'skipped': len(cases) - len(unskipped),
1311     'nocrash': len([t for t in unskipped if IsFlaky(t.outcomes)]),
1312     'pass': len([t for t in unskipped if list(t.outcomes) == [PASS]]),
1313     'fail_ok': len([t for t in unskipped if IsFailOk(t.outcomes)]),
1314     'fail': len([t for t in unskipped if list(t.outcomes) == [FAIL]])
1315   }
1316
1317
1318 class Pattern(object):
1319
1320   def __init__(self, pattern):
1321     self.pattern = pattern
1322     self.compiled = None
1323
1324   def match(self, str):
1325     if not self.compiled:
1326       pattern = "^" + self.pattern.replace('*', '.*') + "$"
1327       self.compiled = re.compile(pattern)
1328     return self.compiled.match(str)
1329
1330   def __str__(self):
1331     return self.pattern
1332
1333
1334 def SplitPath(s):
1335   stripped = [ c.strip() for c in s.split('/') ]
1336   return [ Pattern(s) for s in stripped if len(s) > 0 ]
1337
1338
1339 def GetSpecialCommandProcessor(value):
1340   if (not value) or (value.find('@') == -1):
1341     def ExpandCommand(args):
1342       return args
1343     return ExpandCommand
1344   else:
1345     pos = value.find('@')
1346     import urllib
1347     prefix = urllib.unquote(value[:pos]).split()
1348     suffix = urllib.unquote(value[pos+1:]).split()
1349     def ExpandCommand(args):
1350       return prefix + args + suffix
1351     return ExpandCommand
1352
1353
1354 BUILT_IN_TESTS = [
1355   'sequential',
1356   'parallel',
1357   'pummel',
1358   'message',
1359   'internet',
1360   'addons',
1361   'gc',
1362   'debugger',
1363 ]
1364
1365
1366 def GetSuites(test_root):
1367   def IsSuite(path):
1368     return isdir(path) and exists(join(path, 'testcfg.py'))
1369   return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ]
1370
1371
1372 def FormatTime(d):
1373   millis = round(d * 1000) % 1000
1374   return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1375
1376
1377 def Main():
1378   parser = BuildOptions()
1379   (options, args) = parser.parse_args()
1380   if not ProcessOptions(options):
1381     parser.print_help()
1382     return 1
1383
1384   workspace = abspath(join(dirname(sys.argv[0]), '..'))
1385   suites = GetSuites(join(workspace, 'test'))
1386   repositories = [TestRepository(join(workspace, 'test', name)) for name in suites]
1387   repositories += [TestRepository(a) for a in options.suite]
1388
1389   root = LiteralTestSuite(repositories)
1390   if len(args) == 0:
1391     paths = [SplitPath(t) for t in BUILT_IN_TESTS]
1392   else:
1393     paths = [ ]
1394     for arg in args:
1395       path = SplitPath(arg)
1396       paths.append(path)
1397
1398   # Check for --valgrind option. If enabled, we overwrite the special
1399   # command flag with a command that uses the run-valgrind.py script.
1400   if options.valgrind:
1401     run_valgrind = join(workspace, "tools", "run-valgrind.py")
1402     options.special_command = "python -u " + run_valgrind + " @"
1403
1404   shell = abspath(options.shell)
1405   buildspace = dirname(shell)
1406
1407   processor = GetSpecialCommandProcessor(options.special_command)
1408   context = Context(workspace,
1409                     buildspace,
1410                     VERBOSE,
1411                     shell,
1412                     options.timeout,
1413                     processor,
1414                     options.suppress_dialogs,
1415                     options.store_unexpected_output)
1416   # First build the required targets
1417   if not options.no_build:
1418     reqs = [ ]
1419     for path in paths:
1420       reqs += root.GetBuildRequirements(path, context)
1421     reqs = list(set(reqs))
1422     if len(reqs) > 0:
1423       if options.j != 1:
1424         options.scons_flags += ['-j', str(options.j)]
1425       if not BuildRequirements(context, reqs, options.mode, options.scons_flags):
1426         return 1
1427
1428   # Just return if we are only building the targets for running the tests.
1429   if options.build_only:
1430     return 0
1431
1432   # Get status for tests
1433   sections = [ ]
1434   defs = { }
1435   root.GetTestStatus(context, sections, defs)
1436   config = Configuration(sections, defs)
1437
1438   # List the tests
1439   all_cases = [ ]
1440   all_unused = [ ]
1441   unclassified_tests = [ ]
1442   globally_unused_rules = None
1443   for path in paths:
1444     for arch in options.arch:
1445       for mode in options.mode:
1446         vm = context.GetVm(arch, mode)
1447         if not exists(vm):
1448           print "Can't find shell executable: '%s'" % vm
1449           continue
1450         env = {
1451           'mode': mode,
1452           'system': utils.GuessOS(),
1453           'arch': arch,
1454         }
1455         test_list = root.ListTests([], path, context, arch, mode)
1456         unclassified_tests += test_list
1457         (cases, unused_rules, all_outcomes) = (
1458             config.ClassifyTests(test_list, env))
1459         if globally_unused_rules is None:
1460           globally_unused_rules = set(unused_rules)
1461         else:
1462           globally_unused_rules = (
1463               globally_unused_rules.intersection(unused_rules))
1464         all_cases += cases
1465         all_unused.append(unused_rules)
1466
1467   if options.cat:
1468     visited = set()
1469     for test in unclassified_tests:
1470       key = tuple(test.path)
1471       if key in visited:
1472         continue
1473       visited.add(key)
1474       print "--- begin source: %s ---" % test.GetLabel()
1475       source = test.GetSource().strip()
1476       print source
1477       print "--- end source: %s ---" % test.GetLabel()
1478     return 0
1479
1480   if options.warn_unused:
1481     for rule in globally_unused_rules:
1482       print "Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path])
1483
1484   if options.report:
1485     PrintReport(all_cases)
1486
1487   result = None
1488   def DoSkip(case):
1489     return SKIP in case.outcomes or SLOW in case.outcomes or (FLAKY in case.outcomes and options.flaky_tests == "skip")
1490   cases_to_run = [ c for c in all_cases if not DoSkip(c) ]
1491   if len(cases_to_run) == 0:
1492     print "No tests to run."
1493     return 1
1494   else:
1495     try:
1496       start = time.time()
1497       if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests):
1498         result = 0
1499       else:
1500         result = 1
1501       duration = time.time() - start
1502     except KeyboardInterrupt:
1503       print "Interrupted"
1504       return 1
1505
1506   if options.time:
1507     # Write the times to stderr to make it easy to separate from the
1508     # test output.
1509     print
1510     sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration))
1511     timed_tests = [ t.case for t in cases_to_run if not t.case.duration is None ]
1512     timed_tests.sort(lambda a, b: a.CompareTime(b))
1513     index = 1
1514     for entry in timed_tests[:20]:
1515       t = FormatTime(entry.duration)
1516       sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel()))
1517       index += 1
1518
1519   return result
1520
1521
1522 if __name__ == '__main__':
1523   sys.exit(Main())