1 # -*- coding: utf-8 -*-
4 ## This file is part of Indico.
5 ## Copyright (C) 2002 - 2012 European Organization for Nuclear Research (CERN).
7 ## Indico is free software; you can redistribute it and/or
8 ## modify it under the terms of the GNU General Public License as
9 ## published by the Free Software Foundation; either version 3 of the
10 ## License, or (at your option) any later version.
12 ## Indico is distributed in the hope that it will be useful, but
13 ## WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ## General Public License for more details.
17 ## You should have received a copy of the GNU General Public License
18 ## along with Indico;if not, see <http://www.gnu.org/licenses/>.
21 This module provides a skeleton of a test runner (BaseTestRunner) that all the
22 other TestRunners should inherit from.
27 from threading
import Thread
32 from smtpd
import SMTPServer
37 from indico
.util
.console
import colored
38 from indico
.tests
.util
import TeeStringIO
39 from indico
.tests
.config
import TestConfig
42 class TestOptionException(Exception):
44 Raised when a particular runner doesn't support an option
47 class IOMixin(object):
49 Mixin class that provides some simple utility functions
50 for error/info messages in the console
54 def _info(cls
, message
):
56 Prints an info message
58 print colored("** %s" % message
, 'blue')
61 def _success(cls
, message
):
63 Prints an info message
65 print colored("** %s" % message
, 'green')
68 def _error(cls
, message
):
70 Prints an error message
72 print colored("** %s" % message
, 'red')
75 class OptionProxy(object):
77 Encapsulates all the options present in a TestRunner,
78 providing a common access point, and controlling some
82 def __init__(self
, allowedOptions
):
83 self
._optionTable
= allowedOptions
86 def call(self
, runner
, event
, *args
):
88 Invoked from a code hot spot, so that the option can
92 for option
in self
._options
.values():
93 if hasattr(option
, event
) and option
.shouldExecute():
94 getattr(option
, event
)(runner
, *args
)
96 def configure(self
, **kwargs
):
98 Initializes the options based on command line parameters
101 for optName
, optClass
in self
._optionTable
.iteritems():
102 if optName
in kwargs
:
103 self
._options
[optName
] = optClass(kwargs
[optName
])
105 self
._options
[optName
] = optClass(None)
107 for optName
in kwargs
:
108 if optName
not in self
._optionTable
:
109 raise TestOptionException("Option '%s' not allowed here!" %
113 def valueOf(self
, optName
, default
=None):
115 Returns the direct value of an option
117 if optName
in self
._options
:
118 return self
._options
[optName
].value
123 class Option(IOMixin
):
125 Represents an option for a TestRunner
128 def __init__(self
, value
):
131 def shouldExecute(self
):
133 Determines if the Option should be taken into account (hot spots),
134 depending on the context
139 class BaseTestRunner(IOMixin
):
141 Base class for all other TestRunners.
142 A TestRunner runs a specific kind of test (i.e. UnitTestRunner)
145 # overloaded for each runner, contains allowed options for each runner
148 # * silent - True if the output shouldn't be redirected to the console
150 _runnerOptions
= {'silent': Option
}
152 # path to this current file
153 setupDir
= os
.path
.dirname(__file__
)
155 def __init__(self
, **kwargs
):
157 Options can be passed as kwargs, currently the following is supported:
164 # make a TestConfig instance available everywhere
165 self
.config
= TestConfig
.getInstance()
167 # initialize allowed options
168 self
.options
= OptionProxy(self
._runnerOptions
)
169 self
.options
.configure(**kwargs
)
170 self
._logger
= logging
.getLogger('test')
174 This method should be overloaded by inheriting classes.
175 It should provide the code that executes the actual tests,
176 returning output information.
182 Executes the actual test code
184 # get the description from the first lines
186 description
= self
.__doc
__.strip().split('\n')[0]
188 self
._startIOCapture
()
190 self
._info
("Running %s" % description
)
192 self
._callOptions
('pre_run')
194 self
._callOptions
('post_run')
197 self
._success
("%s successful!" % description
)
199 self
._error
("%s failed!" % description
)
201 # ask the option handlers to compute a final message
202 self
._callOptions
('final_message')
203 self
._writeReport
(self
.__class
__.__name
__,
204 self
._finishIOCapture
())
208 def _startIOCapture(self
):
210 Start capturing stdout and stderr to StringIOs
211 If options['verbose'] has been set, the data will be output to the
212 stdout/stderr as well
215 if self
.options
.valueOf('silent'):
217 self
.err
= StringIO
.StringIO()
220 # capture I/O but display it as well
221 self
.out
= TeeStringIO(sys
.stdout
)
222 self
.err
= TeeStringIO(sys
.stderr
, targetStream
= self
.out
)
223 sys
.stderr
= self
.err
224 sys
.stdout
= self
.out
228 def _finishIOCapture(self
):
230 Restore stdout/stderr and return the captured data
232 sys
.stderr
= sys
.__stderr
__
233 sys
.stdout
= sys
.__stdout
__
235 return self
.out
.getvalue()
240 Goes throught the plugin directories, and adds
241 existing unit test dirs
246 for epoint
in pkg_resources
.iter_entry_points('indico.ext_types'):
247 dirs
.append(os
.path
.dirname(epoint
.load().__file
__))
249 for epoint
in pkg_resources
.iter_entry_points('indico.ext'):
250 dirs
.append(os
.path
.dirname(epoint
.load().__file
__))
255 def _redirectPipeToStdout(pipe
):
257 Redirect a given pipe to stdout
260 data
= pipe
.readline()
265 def _writeReport(self
, filename
, content
):
267 Write the test report, using the filename and content that are passed
269 filePath
= os
.path
.join(self
.setupDir
, 'report', filename
+ ".txt")
271 f
= open(filePath
, 'w')
275 return "Unable to write in %s, check your file permissions." % \
276 os
.path
.join(self
.setupDir
, 'report', filename
+ ".txt")
278 self
._info
("report in %s" % filePath
)
281 def walkThroughFolders(rootPath
, foldersPattern
):
283 Scan a directory and return folders which match the pattern
286 rootPluginsPath
= os
.path
.join(rootPath
)
289 for root
, __
, __
in os
.walk(rootPluginsPath
):
290 if root
.endswith(foldersPattern
) > 0:
291 foldersArray
.append(root
)
295 def _callOptions(self
, method
, *args
):
297 Invokes the option proxy, providing the hot spot with name 'method',
298 that options should have extended
301 # invoke the option proxy
302 self
.options
.call(self
, method
, *args
)
307 class FakeMailServer(SMTPServer
):
308 def process_message(self
, peer
, mailfrom
, rcpttos
, data
):
309 logging
.getLogger('indico.test.fake_smtp').info("mail from %s" % mailfrom
)
312 class FakeMailThread(Thread
):
313 def __init__(self
, addr
):
314 super(FakeMailThread
, self
).__init
__()
316 self
.server
= FakeMailServer(self
.addr
, '')
326 return self
.server
.socket
.getsockname()