Skip to content
This repository
Browse code

Use v8's test runner

  • Loading branch information...
commit 3fed1a095434877f6abe946ff6d788410b1dc66b 1 parent dce072a
ry ry authored

Showing 27 changed files with 1,455 additions and 229 deletions. Show diff stats Hide diff stats

  1. +4 11 configure
  2. 0  test/{ → mjsunit}/fixtures/a.js
  3. 0  test/{ → mjsunit}/fixtures/b/c.js
  4. 0  test/{ → mjsunit}/fixtures/b/d.js
  5. 0  test/{ → mjsunit}/fixtures/x.txt
  6. 0  test/{ → mjsunit}/mjsunit.js
  7. 0  test/{ → mjsunit}/test-file-cat-noexist.js
  8. 0  test/{ → mjsunit}/test-file-open.js
  9. 0  test/{ → mjsunit}/test-http-cat.js
  10. 0  test/{ → mjsunit}/test-http-client-race.js
  11. 0  test/{ → mjsunit}/test-http-proxy.js
  12. 0  test/{ → mjsunit}/test-http-server.js
  13. 0  test/{ → mjsunit}/test-http.js
  14. 0  test/{ → mjsunit}/test-module-loading.js
  15. 0  test/{ → mjsunit}/test-node-cat.js
  16. 0  test/{ → mjsunit}/test-process-kill.js
  17. 0  test/{ → mjsunit}/test-process-simple.js
  18. 0  test/{ → mjsunit}/test-reconnecting-socket.js
  19. 0  test/{ → mjsunit}/test-remote-module-loading.js
  20. 0  test/{ → mjsunit}/test-tcp-pingpong.js
  21. 0  test/{ → mjsunit}/test-timers.js
  22. +105 0 test/mjsunit/testcfg.py
  23. +0 218 tools/jsmin.py
  24. +1 0  tools/jsmin.py
  25. +1 0  tools/run-valgrind.py
  26. +1,343 0 tools/test.py
  27. +1 0  tools/utils.py
15 configure
@@ -93,19 +93,12 @@ uninstall:
93 93 else \\
94 94 $WAF uninstall ; \\
95 95 fi;
96   -
97   -FAIL=python -c 'print("\033[1;31mFAIL\033[m")'
98   -PASS=python -c 'print("\033[1;32mPASS\033[m")'
99 96
100 97 test: all
101   - @for i in test/test*.js; do \\
102   - echo "default \$\$i: "; \\
103   - build/default/node \$\$i && \$(PASS) || \$(FAIL); \\
104   - if [ -e build/debug/node ]; then \\
105   - echo "debug \$\$i: "; \\
106   - build/debug/node \$\$i && \$(PASS) || \$(FAIL); \\
107   - fi; \\
108   - done
  98 + python tools/test.py --mode=release
  99 +
  100 +test-debug: all
  101 + python tools/test.py --mode=debug
109 102
110 103 clean:
111 104 @$WAF clean
0  test/fixtures/a.js → test/mjsunit/fixtures/a.js
File renamed without changes
0  test/fixtures/b/c.js → test/mjsunit/fixtures/b/c.js
File renamed without changes
0  test/fixtures/b/d.js → test/mjsunit/fixtures/b/d.js
File renamed without changes
0  test/fixtures/x.txt → test/mjsunit/fixtures/x.txt
File renamed without changes
0  test/mjsunit.js → test/mjsunit/mjsunit.js
File renamed without changes
0  test/test-file-cat-noexist.js → test/mjsunit/test-file-cat-noexist.js
File renamed without changes
0  test/test-file-open.js → test/mjsunit/test-file-open.js
File renamed without changes
0  test/test-http-cat.js → test/mjsunit/test-http-cat.js
File renamed without changes
0  test/test-http-client-race.js → test/mjsunit/test-http-client-race.js
File renamed without changes
0  test/test-http-proxy.js → test/mjsunit/test-http-proxy.js
File renamed without changes
0  test/test-http-server.js → test/mjsunit/test-http-server.js
File renamed without changes
0  test/test-http.js → test/mjsunit/test-http.js
File renamed without changes
0  test/test-module-loading.js → test/mjsunit/test-module-loading.js
File renamed without changes
0  test/test-node-cat.js → test/mjsunit/test-node-cat.js
File renamed without changes
0  test/test-process-kill.js → test/mjsunit/test-process-kill.js
File renamed without changes
0  test/test-process-simple.js → test/mjsunit/test-process-simple.js
File renamed without changes
0  test/test-reconnecting-socket.js → test/mjsunit/test-reconnecting-socket.js
File renamed without changes
0  test/test-remote-module-loading.js → test/mjsunit/test-remote-module-loading.js
File renamed without changes
0  test/test-tcp-pingpong.js → test/mjsunit/test-tcp-pingpong.js
File renamed without changes
0  test/test-timers.js → test/mjsunit/test-timers.js
File renamed without changes
105 test/mjsunit/testcfg.py
... ... @@ -0,0 +1,105 @@
  1 +# Copyright 2008 the V8 project authors. All rights reserved.
  2 +# Redistribution and use in source and binary forms, with or without
  3 +# modification, are permitted provided that the following conditions are
  4 +# met:
  5 +#
  6 +# * Redistributions of source code must retain the above copyright
  7 +# notice, this list of conditions and the following disclaimer.
  8 +# * Redistributions in binary form must reproduce the above
  9 +# copyright notice, this list of conditions and the following
  10 +# disclaimer in the documentation and/or other materials provided
  11 +# with the distribution.
  12 +# * Neither the name of Google Inc. nor the names of its
  13 +# contributors may be used to endorse or promote products derived
  14 +# from this software without specific prior written permission.
  15 +#
  16 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  17 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  18 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  19 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  20 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  21 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  22 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  26 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27 +
  28 +import test
  29 +import os
  30 +from os.path import join, dirname, exists
  31 +import re
  32 +
  33 +
  34 +FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
  35 +FILES_PATTERN = re.compile(r"//\s+Files:(.*)")
  36 +
  37 +
  38 +class MjsunitTestCase(test.TestCase):
  39 +
  40 + def __init__(self, path, file, mode, context, config):
  41 + super(MjsunitTestCase, self).__init__(context, path)
  42 + self.file = file
  43 + self.config = config
  44 + self.mode = mode
  45 +
  46 + def GetLabel(self):
  47 + return "%s %s" % (self.mode, self.GetName())
  48 +
  49 + def GetName(self):
  50 + return self.path[-1]
  51 +
  52 + def GetCommand(self):
  53 + result = [self.config.context.GetVm(self.mode)]
  54 + source = open(self.file).read()
  55 + flags_match = FLAGS_PATTERN.search(source)
  56 + if flags_match:
  57 + result += flags_match.group(1).strip().split()
  58 + files_match = FILES_PATTERN.search(source);
  59 + additional_files = []
  60 + if files_match:
  61 + additional_files += files_match.group(1).strip().split()
  62 + for a_file in additional_files:
  63 + result.append(join(dirname(self.config.root), '..', a_file))
  64 + result += [self.file]
  65 + return result
  66 +
  67 + def GetSource(self):
  68 + return open(self.file).read()
  69 +
  70 +
  71 +class MjsunitTestConfiguration(test.TestConfiguration):
  72 +
  73 + def __init__(self, context, root):
  74 + super(MjsunitTestConfiguration, self).__init__(context, root)
  75 +
  76 + def Ls(self, path):
  77 + def SelectTest(name):
  78 + return name.endswith('.js') and name != 'mjsunit.js'
  79 + return [f[:-3] for f in os.listdir(path) if SelectTest(f)]
  80 +
  81 + def ListTests(self, current_path, path, mode):
  82 + mjsunit = [current_path + [t] for t in self.Ls(self.root)]
  83 + #regress = [current_path + ['regress', t] for t in self.Ls(join(self.root, 'regress'))]
  84 + #bugs = [current_path + ['bugs', t] for t in self.Ls(join(self.root, 'bugs'))]
  85 + #tools = [current_path + ['tools', t] for t in self.Ls(join(self.root, 'tools'))]
  86 + all_tests = mjsunit # + regress + bugs + tools
  87 + result = []
  88 + for test in all_tests:
  89 + if self.Contains(path, test):
  90 + file_path = join(self.root, reduce(join, test[1:], "") + ".js")
  91 + result.append(MjsunitTestCase(test, file_path, mode, self.context, self))
  92 + return result
  93 +
  94 + def GetBuildRequirements(self):
  95 + return ['sample', 'sample=shell']
  96 +
  97 + def GetTestStatus(self, sections, defs):
  98 + status_file = join(self.root, 'mjsunit.status')
  99 + if exists(status_file):
  100 + test.ReadConfigurationInto(status_file, sections, defs)
  101 +
  102 +
  103 +
  104 +def GetConfiguration(context, root):
  105 + return MjsunitTestConfiguration(context, root)
218 tools/jsmin.py
... ... @@ -1,218 +0,0 @@
1   -#!/usr/bin/python
2   -
3   -# This code is original from jsmin by Douglas Crockford, it was translated to
4   -# Python by Baruch Even. The original code had the following copyright and
5   -# license.
6   -#
7   -# /* jsmin.c
8   -# 2007-05-22
9   -#
10   -# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
11   -#
12   -# Permission is hereby granted, free of charge, to any person obtaining a copy of
13   -# this software and associated documentation files (the "Software"), to deal in
14   -# the Software without restriction, including without limitation the rights to
15   -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
16   -# of the Software, and to permit persons to whom the Software is furnished to do
17   -# so, subject to the following conditions:
18   -#
19   -# The above copyright notice and this permission notice shall be included in all
20   -# copies or substantial portions of the Software.
21   -#
22   -# The Software shall be used for Good, not Evil.
23   -#
24   -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25   -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26   -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27   -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28   -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29   -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30   -# SOFTWARE.
31   -# */
32   -
33   -from StringIO import StringIO
34   -
35   -def jsmin(js):
36   - ins = StringIO(js)
37   - outs = StringIO()
38   - JavascriptMinify().minify(ins, outs)
39   - str = outs.getvalue()
40   - if len(str) > 0 and str[0] == '\n':
41   - str = str[1:]
42   - return str
43   -
44   -def isAlphanum(c):
45   - """return true if the character is a letter, digit, underscore,
46   - dollar sign, or non-ASCII character.
47   - """
48   - return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
49   - (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
50   -
51   -class UnterminatedComment(Exception):
52   - pass
53   -
54   -class UnterminatedStringLiteral(Exception):
55   - pass
56   -
57   -class UnterminatedRegularExpression(Exception):
58   - pass
59   -
60   -class JavascriptMinify(object):
61   -
62   - def _outA(self):
63   - self.outstream.write(self.theA)
64   - def _outB(self):
65   - self.outstream.write(self.theB)
66   -
67   - def _get(self):
68   - """return the next character from stdin. Watch out for lookahead. If
69   - the character is a control character, translate it to a space or
70   - linefeed.
71   - """
72   - c = self.theLookahead
73   - self.theLookahead = None
74   - if c == None:
75   - c = self.instream.read(1)
76   - if c >= ' ' or c == '\n':
77   - return c
78   - if c == '': # EOF
79   - return '\000'
80   - if c == '\r':
81   - return '\n'
82   - return ' '
83   -
84   - def _peek(self):
85   - self.theLookahead = self._get()
86   - return self.theLookahead
87   -
88   - def _next(self):
89   - """get the next character, excluding comments. peek() is used to see
90   - if an unescaped '/' is followed by a '/' or '*'.
91   - """
92   - c = self._get()
93   - if c == '/' and self.theA != '\\':
94   - p = self._peek()
95   - if p == '/':
96   - c = self._get()
97   - while c > '\n':
98   - c = self._get()
99   - return c
100   - if p == '*':
101   - c = self._get()
102   - while 1:
103   - c = self._get()
104   - if c == '*':
105   - if self._peek() == '/':
106   - self._get()
107   - return ' '
108   - if c == '\000':
109   - raise UnterminatedComment()
110   -
111   - return c
112   -
113   - def _action(self, action):
114   - """do something! What you do is determined by the argument:
115   - 1 Output A. Copy B to A. Get the next B.
116   - 2 Copy B to A. Get the next B. (Delete A).
117   - 3 Get the next B. (Delete B).
118   - action treats a string as a single character. Wow!
119   - action recognizes a regular expression if it is preceded by ( or , or =.
120   - """
121   - if action <= 1:
122   - self._outA()
123   -
124   - if action <= 2:
125   - self.theA = self.theB
126   - if self.theA == "'" or self.theA == '"':
127   - while 1:
128   - self._outA()
129   - self.theA = self._get()
130   - if self.theA == self.theB:
131   - break
132   - if self.theA <= '\n':
133   - raise UnterminatedStringLiteral()
134   - if self.theA == '\\':
135   - self._outA()
136   - self.theA = self._get()
137   -
138   -
139   - if action <= 3:
140   - self.theB = self._next()
141   - if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
142   - self.theA == '=' or self.theA == ':' or
143   - self.theA == '[' or self.theA == '?' or
144   - self.theA == '!' or self.theA == '&' or
145   - self.theA == '|' or self.theA == ';' or
146   - self.theA == '{' or self.theA == '}' or
147   - self.theA == '\n'):
148   - self._outA()
149   - self._outB()
150   - while 1:
151   - self.theA = self._get()
152   - if self.theA == '/':
153   - break
154   - elif self.theA == '\\':
155   - self._outA()
156   - self.theA = self._get()
157   - elif self.theA <= '\n':
158   - raise UnterminatedRegularExpression()
159   - self._outA()
160   - self.theB = self._next()
161   -
162   -
163   - def _jsmin(self):
164   - """Copy the input to the output, deleting the characters which are
165   - insignificant to JavaScript. Comments will be removed. Tabs will be
166   - replaced with spaces. Carriage returns will be replaced with linefeeds.
167   - Most spaces and linefeeds will be removed.
168   - """
169   - self.theA = '\n'
170   - self._action(3)
171   -
172   - while self.theA != '\000':
173   - if self.theA == ' ':
174   - if isAlphanum(self.theB):
175   - self._action(1)
176   - else:
177   - self._action(2)
178   - elif self.theA == '\n':
179   - if self.theB in ['{', '[', '(', '+', '-']:
180   - self._action(1)
181   - elif self.theB == ' ':
182   - self._action(3)
183   - else:
184   - if isAlphanum(self.theB):
185   - self._action(1)
186   - else:
187   - self._action(2)
188   - else:
189   - if self.theB == ' ':
190   - if isAlphanum(self.theA):
191   - self._action(1)
192   - else:
193   - self._action(3)
194   - elif self.theB == '\n':
195   - if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
196   - self._action(1)
197   - else:
198   - if isAlphanum(self.theA):
199   - self._action(1)
200   - else:
201   - self._action(3)
202   - else:
203   - self._action(1)
204   -
205   - def minify(self, instream, outstream):
206   - self.instream = instream
207   - self.outstream = outstream
208   - self.theA = '\n'
209   - self.theB = None
210   - self.theLookahead = None
211   -
212   - self._jsmin()
213   - self.instream.close()
214   -
215   -if __name__ == '__main__':
216   - import sys
217   - jsm = JavascriptMinify()
218   - jsm.minify(sys.stdin, sys.stdout)
1  tools/jsmin.py
1  tools/run-valgrind.py
1,343 tools/test.py
... ... @@ -0,0 +1,1343 @@
  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 failed.HasTimedOut():
  168 + print "--- TIMEOUT ---"
  169 + if len(self.failed) == 0:
  170 + print "==="
  171 + print "=== All tests succeeded"
  172 + print "==="
  173 + else:
  174 + print
  175 + print "==="
  176 + print "=== %i tests failed" % len(self.failed)
  177 + if self.crashed > 0:
  178 + print "=== %i tests CRASHED" % self.crashed
  179 + print "==="
  180 +
  181 +
  182 +class VerboseProgressIndicator(SimpleProgressIndicator):
  183 +
  184 + def AboutToRun(self, case):
  185 + print 'Starting %s...' % case.GetLabel()
  186 + sys.stdout.flush()
  187 +
  188 + def HasRun(self, output):
  189 + if output.UnexpectedOutput():
  190 + if output.HasCrashed():
  191 + outcome = 'CRASH'
  192 + else:
  193 + outcome = 'FAIL'
  194 + else:
  195 + outcome = 'pass'
  196 + print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
  197 +
  198 +
  199 +class DotsProgressIndicator(SimpleProgressIndicator):
  200 +
  201 + def AboutToRun(self, case):
  202 + pass
  203 +
  204 + def HasRun(self, output):
  205 + total = self.succeeded + len(self.failed)
  206 + if (total > 1) and (total % 50 == 1):
  207 + sys.stdout.write('\n')
  208 + if output.UnexpectedOutput():
  209 + if output.HasCrashed():
  210 + sys.stdout.write('C')
  211 + sys.stdout.flush()
  212 + elif output.HasTimedOut():
  213 + sys.stdout.write('T')
  214 + sys.stdout.flush()
  215 + else:
  216 + sys.stdout.write('F')
  217 + sys.stdout.flush()
  218 + else:
  219 + sys.stdout.write('.')
  220 + sys.stdout.flush()
  221 +
  222 +
  223 +class CompactProgressIndicator(ProgressIndicator):
  224 +
  225 + def __init__(self, cases, templates):
  226 + super(CompactProgressIndicator, self).__init__(cases)
  227 + self.templates = templates
  228 + self.last_status_length = 0
  229 + self.start_time = time.time()
  230 +
  231 + def Starting(self):
  232 + pass
  233 +
  234 + def Done(self):
  235 + self.PrintProgress('Done')
  236 +
  237 + def AboutToRun(self, case):
  238 + self.PrintProgress(case.GetLabel())
  239 +
  240 + def HasRun(self, output):
  241 + if output.UnexpectedOutput():
  242 + self.ClearLine(self.last_status_length)
  243 + self.PrintFailureHeader(output.test)
  244 + stdout = output.output.stdout.strip()
  245 + if len(stdout):
  246 + print self.templates['stdout'] % stdout
  247 + stderr = output.output.stderr.strip()
  248 + if len(stderr):
  249 + print self.templates['stderr'] % stderr
  250 + print "Command: %s" % EscapeCommand(output.command)
  251 + if output.HasCrashed():
  252 + print "--- CRASHED ---"
  253 + if output.HasTimedOut():
  254 + print "--- TIMEOUT ---"
  255 +
  256 + def Truncate(self, str, length):
  257 + if length and (len(str) > (length - 3)):
  258 + return str[:(length-3)] + "..."
  259 + else:
  260 + return str
  261 +
  262 + def PrintProgress(self, name):
  263 + self.ClearLine(self.last_status_length)
  264 + elapsed = time.time() - self.start_time
  265 + status = self.templates['status_line'] % {
  266 + 'passed': self.succeeded,
  267 + 'remaining': (((self.total - self.remaining) * 100) // self.total),
  268 + 'failed': len(self.failed),
  269 + 'test': name,
  270 + 'mins': int(elapsed) / 60,
  271 + 'secs': int(elapsed) % 60
  272 + }
  273 + status = self.Truncate(status, 78)
  274 + self.last_status_length = len(status)
  275 + print status,
  276 + sys.stdout.flush()
  277 +
  278 +
  279 +class ColorProgressIndicator(CompactProgressIndicator):
  280 +
  281 + def __init__(self, cases):
  282 + templates = {
  283 + '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",
  284 + 'stdout': "\033[1m%s\033[0m",
  285 + 'stderr': "\033[31m%s\033[0m",
  286 + }
  287 + super(ColorProgressIndicator, self).__init__(cases, templates)
  288 +
  289 + def ClearLine(self, last_line_length):
  290 + print "\033[1K\r",
  291 +
  292 +
  293 +class MonochromeProgressIndicator(CompactProgressIndicator):
  294 +
  295 + def __init__(self, cases):
  296 + templates = {
  297 + 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
  298 + 'stdout': '%s',
  299 + 'stderr': '%s',
  300 + 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
  301 + 'max_length': 78
  302 + }
  303 + super(MonochromeProgressIndicator, self).__init__(cases, templates)
  304 +
  305 + def ClearLine(self, last_line_length):
  306 + print ("\r" + (" " * last_line_length) + "\r"),
  307 +
  308 +
  309 +PROGRESS_INDICATORS = {
  310 + 'verbose': VerboseProgressIndicator,
  311 + 'dots': DotsProgressIndicator,
  312 + 'color': ColorProgressIndicator,
  313 + 'mono': MonochromeProgressIndicator
  314 +}
  315 +
  316 +
  317 +# -------------------------
  318 +# --- F r a m e w o r k ---
  319 +# -------------------------
  320 +
  321 +
  322 +class CommandOutput(object):
  323 +
  324 + def __init__(self, exit_code, timed_out, stdout, stderr):
  325 + self.exit_code = exit_code
  326 + self.timed_out = timed_out
  327 + self.stdout = stdout
  328 + self.stderr = stderr
  329 +
  330 +
  331 +class TestCase(object):
  332 +
  333 + def __init__(self, context, path):
  334 + self.path = path
  335 + self.context = context
  336 + self.failed = None
  337 + self.duration = None
  338 +
  339 + def IsNegative(self):
  340 + return False
  341 +
  342 + def CompareTime(self, other):
  343 + return cmp(other.duration, self.duration)
  344 +
  345 + def DidFail(self, output):
  346 + if self.failed is None:
  347 + self.failed = self.IsFailureOutput(output)
  348 + return self.failed
  349 +
  350 + def IsFailureOutput(self, output):
  351 + return output.exit_code != 0
  352 +
  353 + def GetSource(self):
  354 + return "(no source available)"
  355 +
  356 + def RunCommand(self, command):
  357 + full_command = self.context.processor(command)
  358 + output = Execute(full_command, self.context, self.context.timeout)
  359 + return TestOutput(self, full_command, output)
  360 +
  361 + def Run(self):
  362 + return self.RunCommand(self.GetCommand())
  363 +
  364 +
  365 +class TestOutput(object):
  366 +
  367 + def __init__(self, test, command, output):
  368 + self.test = test
  369 + self.command = command
  370 + self.output = output
  371 +
  372 + def UnexpectedOutput(self):
  373 + if self.HasCrashed():
  374 + outcome = CRASH
  375 + elif self.HasFailed():
  376 + outcome = FAIL
  377 + else:
  378 + outcome = PASS
  379 + return not outcome in self.test.outcomes
  380 +
  381 + def HasCrashed(self):
  382 + if utils.IsWindows():
  383 + return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
  384 + else:
  385 + # Timed out tests will have exit_code -signal.SIGTERM.
  386 + if self.output.timed_out:
  387 + return False
  388 + return self.output.exit_code < 0 and \
  389 + self.output.exit_code != -signal.SIGABRT
  390 +
  391 + def HasTimedOut(self):
  392 + return self.output.timed_out;
  393 +
  394 + def HasFailed(self):
  395 + execution_failed = self.test.DidFail(self.output)
  396 + if self.test.IsNegative():
  397 + return not execution_failed
  398 + else:
  399 + return execution_failed
  400 +
  401 +
  402 +def KillProcessWithID(pid):
  403 + if utils.IsWindows():
  404 + os.popen('taskkill /T /F /PID %d' % pid)
  405 + else:
  406 + os.kill(pid, signal.SIGTERM)
  407 +
  408 +
  409 +MAX_SLEEP_TIME = 0.1
  410 +INITIAL_SLEEP_TIME = 0.0001
  411 +SLEEP_TIME_FACTOR = 1.25
  412 +
  413 +SEM_INVALID_VALUE = -1
  414 +SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
  415 +
  416 +def Win32SetErrorMode(mode):
  417 + prev_error_mode = SEM_INVALID_VALUE
  418 + try:
  419 + import ctypes
  420 + prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
  421 + except ImportError:
  422 + pass
  423 + return prev_error_mode
  424 +
  425 +def RunProcess(context, timeout, args, **rest):
  426 + if context.verbose: print "#", " ".join(args)
  427 + popen_args = args
  428 + prev_error_mode = SEM_INVALID_VALUE;
  429 + if utils.IsWindows():
  430 + popen_args = '"' + subprocess.list2cmdline(args) + '"'
  431 + if context.suppress_dialogs:
  432 + # Try to change the error mode to avoid dialogs on fatal errors. Don't
  433 + # touch any existing error mode flags by merging the existing error mode.
  434 + # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
  435 + error_mode = SEM_NOGPFAULTERRORBOX;
  436 + prev_error_mode = Win32SetErrorMode(error_mode);
  437 + Win32SetErrorMode(error_mode | prev_error_mode);
  438 + process = subprocess.Popen(
  439 + shell = utils.IsWindows(),
  440 + args = popen_args,
  441 + **rest
  442 + )
  443 + if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
  444 + Win32SetErrorMode(prev_error_mode)
  445 + # Compute the end time - if the process crosses this limit we
  446 + # consider it timed out.
  447 + if timeout is None: end_time = None
  448 + else: end_time = time.time() + timeout
  449 + timed_out = False
  450 + # Repeatedly check the exit code from the process in a
  451 + # loop and keep track of whether or not it times out.
  452 + exit_code = None
  453 + sleep_time = INITIAL_SLEEP_TIME
  454 + while exit_code is None:
  455 + if (not end_time is None) and (time.time() >= end_time):
  456 + # Kill the process and wait for it to exit.
  457 + KillProcessWithID(process.pid)
  458 + exit_code = process.wait()
  459 + timed_out = True
  460 + else:
  461 + exit_code = process.poll()
  462 + time.sleep(sleep_time)
  463 + sleep_time = sleep_time * SLEEP_TIME_FACTOR
  464 + if sleep_time > MAX_SLEEP_TIME:
  465 + sleep_time = MAX_SLEEP_TIME
  466 + return (process, exit_code, timed_out)
  467 +
  468 +
  469 +def PrintError(str):
  470 + sys.stderr.write(str)
  471 + sys.stderr.write('\n')
  472 +
  473 +
  474 +def Execute(args, context, timeout=None):
  475 + (fd_out, outname) = tempfile.mkstemp()
  476 + (fd_err, errname) = tempfile.mkstemp()
  477 + (process, exit_code, timed_out) = RunProcess(
  478 + context,
  479 + timeout,
  480 + args = args,
  481 + stdout = fd_out,
  482 + stderr = fd_err,
  483 + )
  484 + os.close(fd_out)
  485 + os.close(fd_err)
  486 + output = file(outname).read()
  487 + errors = file(errname).read()
  488 + def CheckedUnlink(name):
  489 + try:
  490 + os.unlink(name)
  491 + except OSError, e:
  492 + PrintError("os.unlink() " + str(e))
  493 + CheckedUnlink(outname)
  494 + CheckedUnlink(errname)
  495 + return CommandOutput(exit_code, timed_out, output, errors)
  496 +
  497 +
  498 +def ExecuteNoCapture(args, context, timeout=None):
  499 + (process, exit_code, timed_out) = RunProcess(
  500 + context,
  501 + timeout,
  502 + args = args,
  503 + )
  504 + return CommandOutput(exit_code, False, "", "")
  505 +
  506 +
  507 +def CarCdr(path):
  508 + if len(path) == 0:
  509 + return (None, [ ])
  510 + else:
  511 + return (path[0], path[1:])
  512 +
  513 +
  514 +class TestConfiguration(object):
  515 +
  516 + def __init__(self, context, root):
  517 + self.context = context
  518 + self.root = root
  519 +
  520 + def Contains(self, path, file):
  521 + if len(path) > len(file):
  522 + return False
  523 + for i in xrange(len(path)):
  524 + if not path[i].match(file[i]):
  525 + return False
  526 + return True
  527 +
  528 + def GetTestStatus(self, sections, defs):
  529 + pass
  530 +
  531 +
  532 +class TestSuite(object):
  533 +
  534 + def __init__(self, name):
  535 + self.name = name
  536 +
  537 + def GetName(self):
  538 + return self.name
  539 +
  540 +
  541 +class TestRepository(TestSuite):
  542 +
  543 + def __init__(self, path):
  544 + normalized_path = abspath(path)
  545 + super(TestRepository, self).__init__(basename(normalized_path))
  546 + self.path = normalized_path
  547 + self.is_loaded = False
  548 + self.config = None
  549 +
  550 + def GetConfiguration(self, context):
  551 + if self.is_loaded:
  552 + return self.config
  553 + self.is_loaded = True
  554 + file = None
  555 + try:
  556 + (file, pathname, description) = imp.find_module('testcfg', [ self.path ])
  557 + module = imp.load_module('testcfg', file, pathname, description)
  558 + self.config = module.GetConfiguration(context, self.path)
  559 + finally:
  560 + if file:
  561 + file.close()
  562 + return self.config
  563 +
  564 + def GetBuildRequirements(self, path, context):
  565 + return self.GetConfiguration(context).GetBuildRequirements()
  566 +
  567 + def ListTests(self, current_path, path, context, mode):
  568 + return self.GetConfiguration(context).ListTests(current_path, path, mode)
  569 +
  570 + def GetTestStatus(self, context, sections, defs):
  571 + self.GetConfiguration(context).GetTestStatus(sections, defs)
  572 +
  573 +
  574 +class LiteralTestSuite(TestSuite):
  575 +
  576 + def __init__(self, tests):
  577 + super(LiteralTestSuite, self).__init__('root')
  578 + self.tests = tests
  579 +
  580 + def GetBuildRequirements(self, path, context):
  581 + (name, rest) = CarCdr(path)
  582 + result = [ ]
  583 + for test in self.tests:
  584 + if not name or name.match(test.GetName()):
  585 + result += test.GetBuildRequirements(rest, context)
  586 + return result
  587 +
  588 + def ListTests(self, current_path, path, context, mode):
  589 + (name, rest) = CarCdr(path)
  590 + result = [ ]
  591 + for test in self.tests:
  592 + test_name = test.GetName()
  593 + if not name or name.match(test_name):
  594 + full_path = current_path + [test_name]
  595 + result += test.ListTests(full_path, path, context, mode)
  596 + return result
  597 +
  598 + def GetTestStatus(self, context, sections, defs):
  599 + for test in self.tests:
  600 + test.GetTestStatus(context, sections, defs)
  601 +
  602 +
  603 +SUFFIX = {'debug': '_g', 'release': ''}
  604 +
  605 +
  606 +class Context(object):
  607 +
  608 + def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, suppress_dialogs):
  609 + self.workspace = workspace
  610 + self.buildspace = buildspace
  611 + self.verbose = verbose
  612 + self.vm_root = vm
  613 + self.timeout = timeout
  614 + self.processor = processor
  615 + self.suppress_dialogs = suppress_dialogs
  616 +
  617 + def GetVm(self, mode):
  618 + if mode == 'debug':
  619 + name = 'build/debug/node'
  620 + else:
  621 + name = 'build/default/node'
  622 + if utils.IsWindows() and not name.endswith('.exe'):
  623 + name = name + '.exe'
  624 + return name
  625 +
  626 +def RunTestCases(all_cases, progress, tasks):
  627 + def DoSkip(case):
  628 + return SKIP in c.outcomes or SLOW in c.outcomes
  629 + cases_to_run = [ c for c in all_cases if not DoSkip(c) ]
  630 + progress = PROGRESS_INDICATORS[progress](cases_to_run)
  631 + return progress.Run(tasks)
  632 +
  633 +
  634 +def BuildRequirements(context, requirements, mode, scons_flags):
  635 + command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
  636 + + requirements
  637 + + scons_flags)
  638 + output = ExecuteNoCapture(command_line, context)
  639 + return output.exit_code == 0
  640 +
  641 +
  642 +# -------------------------------------------
  643 +# --- T e s t C o n f i g u r a t i o n ---
  644 +# -------------------------------------------
  645 +
  646 +
  647 +SKIP = 'skip'
  648 +FAIL = 'fail'
  649 +PASS = 'pass'
  650 +OKAY = 'okay'
  651 +TIMEOUT = 'timeout'
  652 +CRASH = 'crash'
  653 +SLOW = 'slow'
  654 +
  655 +
  656 +class Expression(object):
  657 + pass
  658 +
  659 +
  660 +class Constant(Expression):
  661 +
  662 + def __init__(self, value):
  663 + self.value = value
  664 +
  665 + def Evaluate(self, env, defs):
  666 + return self.value
  667 +
  668 +
  669 +class Variable(Expression):
  670 +
  671 + def __init__(self, name):
  672 + self.name = name
  673 +
  674 + def GetOutcomes(self, env, defs):
  675 + if self.name in env: return ListSet([env[self.name]])
  676 + else: return Nothing()
  677 +
  678 +
  679 +class Outcome(Expression):
  680 +
  681 + def __init__(self, name):
  682 + self.name = name
  683 +
  684 + def GetOutcomes(self, env, defs):
  685 + if self.name in defs:
  686 + return defs[self.name].GetOutcomes(env, defs)
  687 + else:
  688 + return ListSet([self.name])
  689 +
  690 +
  691 +class Set(object):
  692 + pass
  693 +
  694 +
  695 +class ListSet(Set):
  696 +
  697 + def __init__(self, elms):
  698 + self.elms = elms
  699 +
  700 + def __str__(self):
  701 + return "ListSet%s" % str(self.elms)
  702 +
  703 + def Intersect(self, that):
  704 + if not isinstance(that, ListSet):
  705 + return that.Intersect(self)
  706 + return ListSet([ x for x in self.elms if x in that.elms ])
  707 +
  708 + def Union(self, that):
  709 + if not isinstance(that, ListSet):
  710 + return that.Union(self)
  711 + return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ])
  712 +
  713 + def IsEmpty(self):
  714 + return len(self.elms) == 0
  715 +
  716 +
  717 +class Everything(Set):
  718 +
  719 + def Intersect(self, that):
  720 + return that
  721 +
  722 + def Union(self, that):
  723 + return self
  724 +
  725 + def IsEmpty(self):
  726 + return False
  727 +
  728 +
  729 +class Nothing(Set):
  730 +
  731 + def Intersect(self, that):
  732 + return self
  733 +
  734 + def Union(self, that):
  735 + return that
  736 +
  737 + def IsEmpty(self):
  738 + return True
  739 +
  740 +
  741 +class Operation(Expression):
  742 +
  743 + def __init__(self, left, op, right):
  744 + self.left = left
  745 + self.op = op
  746 + self.right = right
  747 +
  748 + def Evaluate(self, env, defs):
  749 + if self.op == '||' or self.op == ',':
  750 + return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
  751 + elif self.op == 'if':
  752 + return False
  753 + elif self.op == '==':
  754 + inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
  755 + return not inter.IsEmpty()
  756 + else:
  757 + assert self.op == '&&'
  758 + return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
  759 +
  760 + def GetOutcomes(self, env, defs):
  761 + if self.op == '||' or self.op == ',':
  762 + return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env, defs))
  763 + elif self.op == 'if':
  764 + if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs)
  765 + else: return Nothing()
  766 + else:
  767 + assert self.op == '&&'
  768 + return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
  769 +
  770 +
  771 +def IsAlpha(str):
  772 + for char in str:
  773 + if not (char.isalpha() or char.isdigit() or char == '_'):
  774 + return False
  775 + return True
  776 +
  777 +
  778 +class Tokenizer(object):
  779 + """A simple string tokenizer that chops expressions into variables,
  780 + parens and operators"""
  781 +
  782 + def __init__(self, expr):
  783 + self.index = 0
  784 + self.expr = expr
  785 + self.length = len(expr)
  786 + self.tokens = None
  787 +
  788 + def Current(self, length = 1):
  789 + if not self.HasMore(length): return ""
  790 + return self.expr[self.index:self.index+length]
  791 +
  792 + def HasMore(self, length = 1):
  793 + return self.index < self.length + (length - 1)
  794 +
  795 + def Advance(self, count = 1):
  796 + self.index = self.index + count
  797 +
  798 + def AddToken(self, token):
  799 + self.tokens.append(token)
  800 +
  801 + def SkipSpaces(self):
  802 + while self.HasMore() and self.Current().isspace():
  803 + self.Advance()
  804 +
  805 + def Tokenize(self):
  806 + self.tokens = [ ]
  807 + while self.HasMore():
  808 + self.SkipSpaces()
  809 + if not self.HasMore():
  810 + return None
  811 + if self.Current() == '(':
  812 + self.AddToken('(')
  813 + self.Advance()
  814 + elif self.Current() == ')':
  815 + self.AddToken(')')
  816 + self.Advance()
  817 + elif self.Current() == '$':
  818 + self.AddToken('$')
  819 + self.Advance()
  820 + elif self.Current() == ',':
  821 + self.AddToken(',')
  822 + self.Advance()
  823 + elif IsAlpha(self.Current()):
  824 + buf = ""
  825 + while self.HasMore() and IsAlpha(self.Current()):
  826 + buf += self.Current()
  827 + self.Advance()
  828 + self.AddToken(buf)
  829 + elif self.Current(2) == '&&':
  830 + self.AddToken('&&')
  831 + self.Advance(2)
  832 + elif self.Current(2) == '||':
  833 + self.AddToken('||')
  834 + self.Advance(2)
  835 + elif self.Current(2) == '==':
  836 + self.AddToken('==')
  837 + self.Advance(2)
  838 + else:
  839 + return None
  840 + return self.tokens
  841 +
  842 +
  843 +class Scanner(object):
  844 + """A simple scanner that can serve out tokens from a given list"""
  845 +
  846 + def __init__(self, tokens):
  847 + self.tokens = tokens
  848 + self.length = len(tokens)
  849 + self.index = 0
  850 +
  851 + def HasMore(self):
  852 + return self.index < self.length
  853 +
  854 + def Current(self):
  855 + return self.tokens[self.index]