Check examples when running tests
Add testcase that executes all examples from taskflow/examples directory. For examples with somewhat deterministic output the output is also checked. This commit also fixes couple of examples broken by recent decorators refactoring. Change-Id: I3e65eabde43e1dd26b442d8f7dd3f062bf80703d
This commit is contained in:
@@ -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)
|
||||
|
||||
7
taskflow/examples/reverting_linear.out.txt
Normal file
7
taskflow/examples/reverting_linear.out.txt
Normal file
@@ -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.',)
|
||||
@@ -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
|
||||
|
||||
4
taskflow/examples/simple_linear.out.txt
Normal file
4
taskflow/examples/simple_linear.out.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Calling jim.
|
||||
Context = {'joe_number': 444, 'jim_number': 555}
|
||||
Calling joe.
|
||||
Context = {'joe_number': 444, 'jim_number': 555}
|
||||
@@ -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))
|
||||
|
||||
18
taskflow/examples/simple_linear_listening.out.txt
Normal file
18
taskflow/examples/simple_linear_listening.out.txt
Normal file
@@ -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}
|
||||
@@ -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,
|
||||
|
||||
114
taskflow/tests/test_examples.py
Normal file
114
taskflow/tests/test_examples.py
Normal file
@@ -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('<SOME UUID>', output)
|
||||
expected_output = self.uuid_re.sub('<SOME UUID>', 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()
|
||||
Reference in New Issue
Block a user