diff --git a/taskflow/examples/fake_boot_vm.py b/taskflow/examples/fake_boot_vm.py index 29b1de36..78936ab8 100644 --- a/taskflow/examples/fake_boot_vm.py +++ b/taskflow/examples/fake_boot_vm.py @@ -3,7 +3,6 @@ import os import random import sys import time -import traceback import uuid logging.basicConfig(level=logging.ERROR) @@ -173,12 +172,12 @@ print '-' * 7 try: flo.run(context) except Exception as e: - traceback.print_exc() + print 'Flow failed: %r' % e print '-- Flow state %s' % (flo.state) print '-' * 11 print 'All results' print '-' * 11 -for (tid, v) in flo.results.items(): +for (tid, v) in sorted(flo.results.items()): print '%s => %s' % (tid, v) diff --git a/taskflow/examples/reverting_linear.out.txt b/taskflow/examples/reverting_linear.out.txt new file mode 100644 index 00000000..06bf7aae --- /dev/null +++ b/taskflow/examples/reverting_linear.out.txt @@ -0,0 +1,7 @@ +Calling jim. +Context = {'joe_number': 444, 'jim_number': 555} +Calling joe. +Context = {'joe_number': 444, 'jim_number': 555} +Calling 444 and apologizing. +Calling 555 and apologizing. +Flow failed: IOError('Suzzie not home right now.',) diff --git a/taskflow/examples/reverting_linear.py b/taskflow/examples/reverting_linear.py index 2eb2fc2d..4f58fde4 100644 --- a/taskflow/examples/reverting_linear.py +++ b/taskflow/examples/reverting_linear.py @@ -30,6 +30,7 @@ def call_joe(context): return context['joe_number'] +@decorators.task def call_suzzie(context): raise IOError("Suzzie not home right now.") @@ -47,4 +48,4 @@ context = { try: flow.run(context) except Exception as e: - print("Flow failed: %s" % (e)) + print "Flow failed: %r" % e diff --git a/taskflow/examples/simple_linear.out.txt b/taskflow/examples/simple_linear.out.txt new file mode 100644 index 00000000..4ff65019 --- /dev/null +++ b/taskflow/examples/simple_linear.out.txt @@ -0,0 +1,4 @@ +Calling jim. +Context = {'joe_number': 444, 'jim_number': 555} +Calling joe. +Context = {'joe_number': 444, 'jim_number': 555} diff --git a/taskflow/examples/simple_linear.py b/taskflow/examples/simple_linear.py index 7938aa1a..cc190d88 100644 --- a/taskflow/examples/simple_linear.py +++ b/taskflow/examples/simple_linear.py @@ -8,14 +8,17 @@ my_dir_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(os.path.join(my_dir_path, os.pardir), os.pardir)) +from taskflow import decorators from taskflow.patterns import linear_flow as lf +@decorators.task def call_jim(context): print("Calling jim.") print("Context = %s" % (context)) +@decorators.task def call_joe(context): print("Calling joe.") print("Context = %s" % (context)) diff --git a/taskflow/examples/simple_linear_listening.out.txt b/taskflow/examples/simple_linear_listening.out.txt new file mode 100644 index 00000000..7dccba15 --- /dev/null +++ b/taskflow/examples/simple_linear_listening.out.txt @@ -0,0 +1,18 @@ +Flow "Call-them": PENDING => STARTED +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} +Flow "Call-them": STARTED => RUNNING +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} +Flow "Call-them": runner "__main__.call_jim" +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} +Calling jim. +Context = {'joe_number': 444, 'jim_number': 555} +Flow "Call-them": runner "__main__.call_jim" +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} +Flow "Call-them": runner "__main__.call_joe" +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} +Calling joe. +Context = {'joe_number': 444, 'jim_number': 555} +Flow "Call-them": runner "__main__.call_joe" +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} +Flow "Call-them": RUNNING => SUCCESS +Flow "Call-them": context={'joe_number': 444, 'jim_number': 555} diff --git a/taskflow/examples/simple_linear_listening.py b/taskflow/examples/simple_linear_listening.py index c53cc62e..533f424e 100644 --- a/taskflow/examples/simple_linear_listening.py +++ b/taskflow/examples/simple_linear_listening.py @@ -8,19 +8,23 @@ my_dir_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(os.path.join(my_dir_path, os.pardir), os.pardir)) +from taskflow import decorators from taskflow.patterns import linear_flow as lf +@decorators.task def call_jim(context): print("Calling jim.") print("Context = %s" % (context)) +@decorators.task def call_joe(context): print("Calling joe.") print("Context = %s" % (context)) +@decorators.task def flow_watch(state, details): flow = details['flow'] old_state = details['old_state'] @@ -29,6 +33,7 @@ def flow_watch(state, details): print('Flow "%s": context=%s' % (flow.name, context)) +@decorators.task def task_watch(state, details): flow = details['flow'] runner = details['runner'] @@ -43,6 +48,7 @@ flow.add(call_joe) flow.notifier.register('*', flow_watch) flow.task_notifier.register('*', task_watch) + context = { "joe_number": 444, "jim_number": 555, diff --git a/taskflow/tests/test_examples.py b/taskflow/tests/test_examples.py new file mode 100644 index 00000000..d7bf16b3 --- /dev/null +++ b/taskflow/tests/test_examples.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +"""Run examples as unit tests + +This module executes examples as unit tests, thus ensuring they at least +can be executed with current taskflow. For examples with deterministic +output, the output can be put to file with same name and '.out.txt' +extension; then it will be checked that output did not change. + +When this module is used as main module, output for all examples are +generated. Please note that this will break tests as output for most +examples is indeterministic. +""" + + +import os +import re +import subprocess +import sys +import taskflow.test + +ROOT_DIR = os.path.abspath( + os.path.dirname( + os.path.dirname( + os.path.dirname(__file__)))) + + +def root_path(*args): + return os.path.join(ROOT_DIR, *args) + + +def run_example(name): + path = root_path('taskflow', 'examples', '%s.py' % name) + obj = subprocess.Popen( + [sys.executable, path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = obj.communicate() + if output[1]: + raise RuntimeError('Example wrote to stderr:\n%s' % output[1]) + return output[0] + + +def expected_output_path(name): + return root_path('taskflow', 'examples', + '%s.out.txt' % name) + + +def list_examples(): + ext = '.py' + examples_dir = root_path('taskflow', 'examples') + for filename in os.listdir(examples_dir): + if filename.endswith(ext): + yield filename[:-len(ext)] + + +class ExamplesTestCase(taskflow.test.TestCase): + maxDiff = None # sky's the limit + + uuid_re = re.compile('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' + .replace('X', '[0-9a-f]')) + + @classmethod + def update(cls): + def add_test_method(name, method_name): + def test_example(self): + self._check_example(name) + test_example.__name__ = method_name + setattr(cls, method_name, test_example) + + for name in list_examples(): + add_test_method(name, 'test_%s' % name) + + def _check_example(self, name): + output = run_example(name) + eop = expected_output_path(name) + if os.path.isfile(eop): + with open(eop) as f: + expected_output = f.read() + # NOTE(imelnikov): on each run new uuid is generated, so we just + # replace them with some constant string + output = self.uuid_re.sub('', output) + expected_output = self.uuid_re.sub('', expected_output) + self.assertMultiLineEqual(output, expected_output) + +ExamplesTestCase.update() + + +def make_output_files(): + """Generate output files for all examples""" + for name in list_examples(False): + output = run_example(name) + with open(expected_output_path(name), 'w') as f: + f.write(output) + + +if __name__ == '__main__': + make_output_files()