Index: scripts/slave/unittests/expect_tests/type_definitions.py |
diff --git a/scripts/slave/unittests/expect_tests/type_definitions.py b/scripts/slave/unittests/expect_tests/type_definitions.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4959876dcf9dbea4d22909848c25785391295301 |
--- /dev/null |
+++ b/scripts/slave/unittests/expect_tests/type_definitions.py |
@@ -0,0 +1,183 @@ |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import os |
+ |
+from collections import namedtuple |
+ |
+UnknownError = namedtuple('UnknownError', 'message') |
+TestError = namedtuple('TestError', 'test message') |
+Result = namedtuple('Result', 'data') |
+ |
+class ResultStageAbort(Exception): |
+ pass |
+ |
+ |
+class Failure(object): |
+ pass |
+ |
+ |
+_Test = namedtuple( |
+ 'Test', 'name func args kwargs expect_dir expect_base ext breakpoints') |
+ |
+class Test(_Test): # pylint: disable=W0232 |
+ def __new__(cls, name, func, args=(), kwargs=None, expect_dir=None, |
+ expect_base=None, ext='json', breakpoints=None, break_funcs=()): |
+ """Create a new test. |
+ |
+ @param name: The name of the test. Will be used as the default expect_base |
+ |
+ @param func: The function to execute to run this test. Must be pickleable. |
+ @param args: *args for |func| |
+ @param kwargs: **kwargs for |func| |
+ |
+ @param expect_dir: The directory which holds the expectation file for this |
+ Test. |
+ @param expect_base: The basename (without extension) of the expectation |
+ file. Defaults to |name|. |
+ @param ext: The extension of the expectation file. Affects the serializer |
+ used to write the expectations to disk. Valid values are |
+ 'json' and 'yaml' (Keys in SERIALIZERS). |
+ |
+ @param breakpoints: A list of (path, lineno, func_name) tuples. These will |
+ turn into breakpoints when the tests are run in 'debug' |
+ mode. See |break_funcs| for an easier way to set this. |
+ @param break_funcs: A list of functions for which to set breakpoints. |
+ """ |
+ # pylint: disable=E1002 |
+ kwargs = kwargs or {} |
+ |
+ breakpoints = breakpoints or [] |
+ if not breakpoints or break_funcs: |
+ for f in (break_funcs or (func,)): |
+ if hasattr(f, 'im_func'): |
+ f = f.im_func |
+ breakpoints.append((f.func_code.co_filename, |
+ f.func_code.co_firstlineno, |
+ f.func_code.co_name)) |
+ |
+ return super(Test, cls).__new__(cls, name, func, args, kwargs, expect_dir, |
+ expect_base, ext, breakpoints) |
+ |
+ def expect_path(self, ext=None): |
+ name = self.expect_base or self.name |
+ name = ''.join('_' if c in '<>:"\\/|?*\0' else c for c in name) |
+ return os.path.join(self.expect_dir, name + ('.%s' % (ext or self.ext))) |
+ |
+ def run(self): |
+ return self.func(*self.args, **self.kwargs) |
+ |
+ |
+class Handler(object): |
+ """Handler object. |
+ |
+ Defines 3 handler methods for each stage of the test pipeline. The pipeline |
+ looks like: |
+ |
+ -> -> |
+ -> jobs -> (main) |
+ GenStage -> test_queue -> * -> result_queue -> ResultStage |
+ -> RunStage -> |
+ -> -> |
+ |
+ Each process will have an instance of one of the nested handler classes, which |
+ will be called on each test / result. |
+ |
+ You can skip the RunStage phase by setting SKIP_RUNLOOP to True on your |
+ implementation class. |
+ |
+ Tips: |
+ * Only do printing in ResultStage, since it's running on the main process. |
+ """ |
+ SKIP_RUNLOOP = False |
+ |
+ @classmethod |
+ def add_options(cls, parser): |
+ """ |
+ @type parser: argparse.ArgumentParser() |
+ """ |
+ pass |
+ |
+ @classmethod |
+ def gen_stage_loop(cls, _opts, tests, put_next_stage, _put_result_stage): |
+ """Called in the GenStage portion of the pipeline. |
+ |
+ @param opts: Parsed CLI options |
+ @param tests: Iteraterable of type_definitions.Test objects |
+ @param put_next_stage: Function to push an object to the next stage of the |
+ pipeline (RunStage). |
+ @param put_result_stage: Function to push an object to the result stage of |
+ the pipeline. |
+ """ |
+ for test in tests: |
+ put_next_stage(test) |
+ |
+ @classmethod |
+ def run_stage_loop(cls, _opts, tests_results, put_next_stage): |
+ """Called in the RunStage portion of the pipeline. |
+ |
+ @param opts: Parsed CLI options |
+ @param tests_results: Iteraterable of (type_definitions.Test, |
+ type_definitions.Result) objects |
+ @param put_next_stage: Function to push an object to the next stage of the |
+ pipeline (ResultStage). |
+ """ |
+ for _, result in tests_results: |
+ put_next_stage(result) |
+ |
+ @classmethod |
+ def result_stage_loop(cls, opts, objects): |
+ """Called in the ResultStage portion of the pipeline. |
+ |
+ Consider subclassing ResultStageHandler instead as it provides a more |
+ flexible interface for dealing with |objects|. |
+ |
+ @param opts: Parsed CLI options |
+ @param objects: Iteraterable of objects from GenStage and RunStage. |
+ """ |
+ error = False |
+ aborted = False |
+ handler = cls.ResultStageHandler(opts) |
+ try: |
+ for obj in objects: |
+ error |= isinstance(handler(obj), Failure) |
+ except ResultStageAbort: |
+ aborted = True |
+ handler.finalize(aborted) |
+ return error |
+ |
+ class ResultStageHandler(object): |
+ """SAX-like event handler dispatches to self.handle_{type(obj).__name__} |
+ |
+ So if |obj| is a Test, this would call self.handle_Test(obj). |
+ |
+ self.__unknown is called to handle objects which have no defined handler. |
+ |
+ self.finalize is called after all objects are processed. |
+ """ |
+ def __init__(self, opts): |
+ self.opts = opts |
+ |
+ def __call__(self, obj): |
+ """Called to handle each object in the ResultStage |
+ |
+ @type obj: Anything passed to put_result in GenStage or RunStage. |
+ |
+ @return: If the handler method returns Failure(), then it will |
+ cause the entire test run to ultimately return an error code. |
+ """ |
+ return getattr(self, 'handle_' + type(obj).__name__, self.__unknown)(obj) |
+ |
+ def __unknown(self, obj): |
+ if self.opts.verbose: |
+ print 'UNHANDLED:', obj |
+ return Failure() |
+ |
+ def finalize(self, aborted): |
+ """Called after __call__() has been called for all results. |
+ |
+ @param aborted: True if the user aborted the run. |
+ @type aborted: bool |
+ """ |
+ pass |