Repository: avocado-framework/avocado

Issue:
Running test synctest inside a virtual machine crashes Avocado
Running test synctest inside a virtual machine and with option `--archive` turned on
crashes Avocado:

<pre>
Traceback (most recent call last):

  File "/usr/lib/python2.7/site-packages/avocado/job.py", line 237, in _run
    archive.create_zip(name, self.debugdir)

  File "/usr/lib/python2.7/site-packages/avocado/utils/archive.py", line 242, in create_zip
    with zipfile.ZipFile(name, 'w') as zf:

  File "/usr/lib64/python2.7/zipfile.py", line 752, in __init__
    self.fp = open(file, modeDict[mode])

IOError: [Errno 2] No such file or directory: 'run-2014-05-24-09.28.48.zip'
</pre>



Discussion:

Resolving patch:
diff --git a/avocado/job.py b/avocado/job.py
--- a/avocado/job.py
+++ b/avocado/job.py
@@ -222,11 +222,10 @@ def _run(self, urls=None, multiplex_file=None):
             self.status = 'PASS'
         # Let's clean up test artifacts
         if self.args is not None:
+            if self.args.archive:
+                archive.create_zip(self.debugdir, self.debugdir)
             if not self.args.keep_tmp_files:
                 data_dir.clean_tmp_files()
-            if self.args.archive:
-                name = os.path.basename(self.debugdir)
-                archive.create_zip(name, self.debugdir)
 
         tests_status = not bool(failures)
         if tests_status:
diff --git a/avocado/plugins/vm.py b/avocado/plugins/vm.py
--- a/avocado/plugins/vm.py
+++ b/avocado/plugins/vm.py
@@ -52,7 +52,12 @@ def run_test(self, urls):
         """
         avocado_cmd = 'avocado --json run --archive "%s"' % urls
         stdout = self.result.vm.remote.run(avocado_cmd)
-        results = json.loads(stdout)
+        try:
+            results = json.loads(stdout)
+        except Exception, details:
+            raise ValueError('Error loading JSON '
+                             '(full output below): %s\n"""\n%s\n"""' %
+                             (details, stdout))
         return results
 
     def run(self, params_list):
@@ -74,13 +79,14 @@ def run(self, params_list):
             test = Test(name=tst['test'],
                         time=tst['time'],
                         status=tst['status'])
+            self.result.start_test(test)
             self.result.check_test(test)
             if not status.mapping[test.status]:
                 failures.append(test.tagged_name)
         self.result.end_tests()
         local_log_dir = os.path.dirname(self.result.stream.debuglog)
-        zip_filename = os.path.basename(remote_log_dir) + '.zip'
-        zip_path_filename = os.path.join(local_log_dir, zip_filename)
+        zip_filename = remote_log_dir + '.zip'
+        zip_path_filename = os.path.join(local_log_dir, os.path.basename(zip_filename))
         self.result.vm.remote.receive_files(local_log_dir, zip_filename)
         archive.uncompress_zip(zip_path_filename,
                                local_log_dir)
@@ -115,13 +121,19 @@ def _copy_tests(self):
 
     def setup(self):
         if self.args.vm_domain is None:
-            self.stream.error('Please, set Virtual Machine Domain with option --vm-domain.')
-            raise exceptions.TestSetupFail()
+            e_msg = ('Please set Virtual Machine Domain with option '
+                     '--vm-domain.')
+            self.stream.error(e_msg)
+            raise exceptions.TestSetupFail(e_msg)
         if self.args.vm_hostname is None:
-            self.stream.error('Please, set Virtual Machine hostname with option --vm-hostname.')
-            raise exceptions.TestSetupFail()
-        self.stream.log_header("REMOTE TESTS: Virtual Machine Domain '%s'" % self.args.vm_domain)
-        self.stream.log_header("REMOTE TESTS: Host login '%s@%s'" % (self.args.vm_username, self.args.vm_hostname))
+            e_msg = ('Please set Virtual Machine hostname with option '
+                     '--vm-hostname.')
+            self.stream.error(e_msg)
+            raise exceptions.TestSetupFail(e_msg)
+        self.stream.log_header("REMOTE TESTS: Virtual Machine Domain '%s'" %
+                               self.args.vm_domain)
+        self.stream.log_header("REMOTE TESTS: Host login '%s@%s'" %
+                               (self.args.vm_username, self.args.vm_hostname))
         self.vm = virt.vm_connect(self.args.vm_domain,
                                   self.args.vm_hypervisor_uri)
         if self.vm is None:


Test patch:
diff --git a/selftests/all/functional/avocado/basic_tests.py b/selftests/all/functional/avocado/basic_tests.py
--- a/selftests/all/functional/avocado/basic_tests.py
+++ b/selftests/all/functional/avocado/basic_tests.py
@@ -14,6 +14,7 @@
 # Copyright: Red Hat Inc. 2013-2014
 # Author: Lucas Meneghel Rodrigues <lmr@redhat.com>
 
+import json
 import unittest
 import os
 import shutil
@@ -205,5 +206,58 @@ def test_xunit_plugin_mixedtest(self):
         self.run_and_check('"sleeptest failtest skiptest errortest"',
                            1, 4, 1, 1, 1)
 
+
+class ParseJSONError(Exception):
+    pass
+
+
+class PluginsJSONTest(PluginsSysinfoTest):
+
+    def run_and_check(self, testname, e_rc, e_ntests, e_nerrors,
+                      e_nfailures, e_nskip):
+        os.chdir(basedir)
+        cmd_line = './scripts/avocado --json run --archive %s' % testname
+        result = process.run(cmd_line, ignore_status=True)
+        json_output = result.stdout
+        self.assertEqual(result.exit_status, e_rc,
+                         "Avocado did not return rc %d:\n%s" %
+                         (e_rc, result))
+        try:
+            json_data = json.loads(json_output)
+        except Exception, detail:
+            raise ParseJSONError("Failed to parse content: %s\n%s" %
+                                 (detail, json_output))
+        self.assertTrue(json_data, "Empty JSON result:\n%s" % json_output)
+        self.assertIsInstance(json_data['tests'], list,
+                              "JSON result lacks 'tests' list")
+        n_tests = len(json_data['tests'])
+        self.assertEqual(n_tests, e_ntests,
+                         "Different number of expected tests")
+        n_errors = json_data['errors']
+        self.assertEqual(n_errors, e_nerrors,
+                         "Different number of expected tests")
+        n_failures = json_data['failures']
+        self.assertEqual(n_failures, e_nfailures,
+                         "Different number of expected tests")
+        n_skip = json_data['skip']
+        self.assertEqual(n_skip, e_nskip,
+                         "Different number of skipped tests")
+
+    def test_json_plugin_sleeptest(self):
+        self.run_and_check('sleeptest', 0, 1, 0, 0, 0)
+
+    def test_json_plugin_failtest(self):
+        self.run_and_check('failtest', 1, 1, 0, 1, 0)
+
+    def test_json_plugin_skiptest(self):
+        self.run_and_check('skiptest', 0, 1, 0, 0, 1)
+
+    def test_json_plugin_errortest(self):
+        self.run_and_check('errortest', 1, 1, 1, 0, 0)
+
+    def test_json_plugin_mixedtest(self):
+        self.run_and_check('"sleeptest failtest skiptest errortest"',
+                           1, 4, 1, 1, 1)
+
 if __name__ == '__main__':
     unittest.main()