From 5371a9e497ef1c14bc13adf2d83e6868375da778 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sun, 18 Mar 2012 19:25:56 -0400 Subject: [PATCH] Regression tests for playbooks, logging the events they call via callbacks. --- bin/ansible-playbook | 2 +- lib/ansible/playbook.py | 7 +- lib/ansible/runner.py | 6 ++ test/TestPlayBook.py | 27 ++++-- test/playbook1.events | 203 ++++++++++++++++++++++++++++++++++++++++ test/playbook1.yml | 27 +++++- 6 files changed, 259 insertions(+), 13 deletions(-) create mode 100644 test/playbook1.events diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 562e0fe89bd..daa64fecf82 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -49,7 +49,7 @@ class PlaybookCallbacks(object): def on_failed(self, host, results): print "failed: [%s] => %s\n" % (host, utils.smjson(results)) - def on_ok(self, host): + def on_ok(self, host, host_result): print "ok: [%s]\n" % (host) def on_play_start(self, pattern): diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 549d1842f5e..ea2294ebb74 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -83,7 +83,6 @@ class PlayBook(object): # playbook file can be passed in as a path or # as file contents (to support API usage) - print "DEBUG: playbook=%s" % playbook self.basedir = os.path.dirname(playbook) self.playbook = self._parse_playbook(playbook) @@ -384,7 +383,7 @@ class PlayBook(object): else: self.failures[host] = self.failures[host] + 1 else: - self.callbacks.on_ok(host) + self.callbacks.on_ok(host, results) if not host in self.invocations: self.invocations[host] = 1 else: @@ -418,7 +417,9 @@ class PlayBook(object): # for this particular pattern group for x in handlers: - name = x['name'] + name = x.get('name', None) + if name is None: + raise errors.AnsibleError('handler is missing a name') if match_name == name: # flag the handler with the list of hosts # it needs to be run on, it will be run later diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 02705f3ebc5..f423ac26806 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -385,6 +385,12 @@ class Runner(object): and then run the async module wrapping the other module ''' + # hack to make the 'shell' module keyword really be executed + # by the command module + if self.module_name == 'shell': + self.module_name = 'command' + self.module_args.append("#USE_SHELL") + async = self._transfer_module(conn, tmp, 'async_wrapper') module = self._transfer_module(conn, tmp, self.module_name) result = self._execute_module(conn, tmp, async, self.module_args, diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index b82134ae8d7..ecfce412a84 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -35,9 +35,12 @@ class TestCallbacks(object): def on_failed(self, host, results): self.events.append([ 'failed', [ host, results ]]) - # FIXME: this callback should get results too! - def on_ok(self, host): - self.events.append([ 'ok', [ host ]]) + def on_ok(self, host, host_result): + # delete certain info from host_result to make test comparisons easier + for k in [ 'ansible_job_id', 'md5sum', 'delta', 'start', 'end' ]: + if k in host_result: + del host_result[k] + self.events.append([ 'ok', [ host, host_result ]]) def on_play_start(self, pattern): self.events.append([ 'play start', [ pattern ]]) @@ -46,13 +49,11 @@ class TestCallbacks(object): self.events.append([ 'async confused', [ msg ]]) def on_async_poll(self, jid, host, clock, host_result): - self.events.append([ 'async poll', [ host, jid ]]) + self.events.append([ 'async poll', [ host ]]) def on_dark_host(self, host, msg): self.events.append([ 'failed/dark', [ host, msg ]]) - # FIXME: callbacks need to be fired on notifiers as well - class TestRunner(unittest.TestCase): @@ -62,6 +63,11 @@ class TestRunner(unittest.TestCase): self.test_dir = os.path.join(self.cwd, 'test') self.stage_dir = self._prepare_stage_dir() + if os.path.exists('/tmp/ansible_test_data_copy.out'): + os.unlink('/tmp/ansible_test_data_copy.out') + if os.path.exists('/tmp/ansible_test_data_template.out'): + os.unlink('/tmp/ansible_test_data_template.out') + def _prepare_stage_dir(self): stage_path = os.path.join(self.test_dir, 'test_data') if os.path.exists(stage_path): @@ -104,5 +110,10 @@ class TestRunner(unittest.TestCase): def test_one(self): pb = os.path.join(self.test_dir, 'playbook1.yml') - print utils.bigjson(self._run(pb)) - assert False, "this test works, but we need to check the results values to complete it" + expected = os.path.join(self.test_dir, 'playbook1.events') + expected = utils.json_loads(file(expected).read()) + actual = self._run(pb) + # if different, this will output to screen + print utils.bigjson(actual) + assert cmp(expected, actual) == 0, "expected events match actual events" + diff --git a/test/playbook1.events b/test/playbook1.events new file mode 100644 index 00000000000..9a0a2a82f8b --- /dev/null +++ b/test/playbook1.events @@ -0,0 +1,203 @@ +{ + "events": [ + "start", + [ + "play start", + [ + "all" + ] + ], + [ + "task start", + [ + "test basic success command", + false + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "cmd": [ + "/bin/true" + ], + "rc": 0, + "stderr": "", + "stdout": "" + } + ] + ], + [ + "task start", + [ + "test basic success command 2", + false + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "cmd": [ + "/bin/true" + ], + "rc": 0, + "stderr": "", + "stdout": "" + } + ] + ], + [ + "task start", + [ + "test basic shell", + false + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "cmd": "echo $HOME ", + "rc": 0, + "stderr": "", + "stdout": "/root" + } + ] + ], + [ + "task start", + [ + "test copy", + false + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "changed": true, + "group": "root", + "mode": 420, + "path": "/tmp/ansible_test_data_copy.out", + "state": "file", + "user": "root" + } + ] + ], + [ + "task start", + [ + "test template", + false + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "changed": true, + "group": "root", + "mode": 420, + "path": "/tmp/ansible_test_data_template.out", + "state": "file", + "user": "root" + } + ] + ], + [ + "task start", + [ + "async poll test", + false + ] + ], + [ + "async poll", + [ + "127.0.0.1" + ] + ], + [ + "async poll", + [ + "127.0.0.1" + ] + ], + [ + "async poll", + [ + "127.0.0.1" + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "cmd": "sleep 5 ", + "finished": 1, + "rc": 0, + "stderr": "", + "stdout": "" + } + ] + ], + [ + "task start", + [ + "on change 1", + true + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "cmd": [ + "/bin/true" + ], + "rc": 0, + "stderr": "", + "stdout": "" + } + ] + ], + [ + "task start", + [ + "on change 2", + true + ] + ], + [ + "ok", + [ + "127.0.0.1", + { + "cmd": [ + "/bin/true" + ], + "rc": 0, + "stderr": "", + "stdout": "" + } + ] + ] + ], + "results": { + "127.0.0.1": { + "changed": 2, + "dark": 0, + "failed": 0, + "resources": 8 + } + } +} + diff --git a/test/playbook1.yml b/test/playbook1.yml index 60f228fef25..5894348ecd3 100644 --- a/test/playbook1.yml +++ b/test/playbook1.yml @@ -16,19 +16,44 @@ - name: test basic shell action: shell echo $HOME +# in the command below, the test file should contain a valid template +# and trigger the change handler + - name: test copy action: copy src=sample.j2 dest=/tmp/ansible_test_data_copy.out + notify: + - on change 1 + +# this should trigger two change handlers, but the 2nd should +# not be triggered twice because it's already triggered - name: test template action: template src=sample.j2 dest=/tmp/ansible_test_data_template.out + notify: + - on change 1 + - on change 2 + +# there should be various poll events within the range + + - name: async poll test + action: shell sleep 5 + async: 10 + poll: 3 handlers: +# in the above test example, this should fire ONCE (at the end) - name: on change 1 action: command /bin/true + +# in the above test example, this should fire ONCE (at the end) + - name: on change 2 action: command /bin/true - - action: on change 3 + +# in the above test example, this should NOT FIRE + + - name: on change 3 action: command /bin/true