Merge lp:~milo/lava-tool/lava-168 into lp:~linaro-validation/lava-tool/trunk

Proposed by Milo Casagrande
Status: Merged
Merged at revision: 188
Proposed branch: lp:~milo/lava-tool/lava-168
Merge into: lp:~linaro-validation/lava-tool/trunk
Prerequisite: lp:~milo/lava-tool/lava-167
Diff against target: 1306 lines (+631/-360)
15 files modified
lava/config.py (+1/-1)
lava/device/commands.py (+17/-147)
lava/device/tests/test_commands.py (+30/-105)
lava/device/tests/test_device.py (+5/-19)
lava/helper/command.py (+101/-0)
lava/helper/dispatcher.py (+110/-0)
lava/helper/tests/helper_test.py (+61/-0)
lava/helper/tests/test_command.py (+53/-0)
lava/helper/tests/test_dispatcher.py (+52/-0)
lava/job/__init__.py (+2/-2)
lava/job/commands.py (+83/-36)
lava/job/tests/test_commands.py (+70/-49)
lava_tool/tests/__init__.py (+3/-0)
lava_tool/tests/test_utils.py (+41/-0)
lava_tool/utils.py (+2/-1)
To merge this branch: bzr merge lp:~milo/lava-tool/lava-168
Reviewer Review Type Date Requested Status
Antonio Terceiro Needs Fixing
Linaro Automation & Validation Pending
Review via email: mp+169785@code.launchpad.net

Description of the change

This branch adds the 'lava job run' command to run a job file in the local dispatcher.

[updated: resubmitted, was using wrong target and no prerequisite]

To post a comment you must log in.
Revision history for this message
Antonio Terceiro (terceiro) wrote :
Download full text (15.9 KiB)

Hi Milo,

Thanks for this. Some of my comments are related to the questions we already
talked about earlier today, but I will also mention them here so others can
also follow.

 review needs-fixing

> === added file 'lava/base_command.py'
> --- lava/base_command.py 1970-01-01 00:00:00 +0000
> +++ lava/base_command.py 2013-06-17 13:33:27 +0000

I would put this somewhere like lava/helper/command.py instead

Maybe when we start working on the other items (testdef/script) we realize that
we can move all of them in the lava.helper namespace instead of creating
lava.testdef and lava.script.

> @@ -0,0 +1,80 @@
> +# Copyright (C) 2013 Linaro Limited
> +#
> +# Author: Milo Casagrande <email address hidden>
> +#
> +# This file is part of lava-tool.
> +#
> +# lava-tool is free software: you can redistribute it and/or modify
> +# it under the terms of the GNU Lesser General Public License version 3
> +# as published by the Free Software Foundation
> +#
> +# lava-tool is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
> +
> +"""Base command class common to lava commands series."""
> +
> +import subprocess
> +
> +from lava.config import InteractiveConfig
> +from lava.tool.command import Command
> +from lava.tool.errors import CommandError
> +
> +
> +class BaseCommand(Command):
> + """Base command class for all lava commands."""
> + def __init__(self, parser, args):
> + super(BaseCommand, self).__init__(parser, args)
> + self.config = InteractiveConfig(
> + force_interactive=self.args.interactive)
> +
> + @classmethod
> + def register_arguments(cls, parser):
> + super(BaseCommand, cls).register_arguments(parser)
> + parser.add_argument("-i", "--interactive",
> + action='store_true',
> + help=("Forces asking for input parameters even if "
> + "we already have them cached."))

As we talked ealier, I think it's better to do it the other way around: assume
interactivity always, and have a --non-interactive option to force not asking
for input.

> +
> + @classmethod
> + def get_dispatcher_paths(cls):
> + """Tries to get the dispatcher paths from lava-dispatcher.
> +
> + :return A list of paths.
> + """
> + try:
> + from lava_dispatcher.config import write_path
> + return write_path()
> + except ImportError:
> + raise CommandError("Cannot find lava-dispatcher installation.")
> +
> + @classmethod
> + def get_devices(cls):
> + """Gets the devices list from the dispatcher.
> +
> + :return A list of DeviceConfig.
> + """
> + try:
> + from lava_dispatcher.config import get_devices
> + return get_devices()
> + except ImportError:
> + raise Comman...

review: Needs Fixing
lp:~milo/lava-tool/lava-168 updated
196. By Milo Casagrande

Merged latests chanes from parent branch.

197. By Milo Casagrande

Refactored the BaseCommand class.

    * Moved BaseCommand in a helper package and in its own class.
    * Refactored method names.
    * Added a dispatcher helper class for all function calls that interact
      or deal with the LAVA dispatcher.

198. By Milo Casagrande

Refactored tests.

    * First batch of changes.
    * Added test helper function to collect setUp and tearDown methods.
    * Moved BaseCommand and the new dispatcher method tests here.

199. By Milo Casagrande

Refactored tests and commands.

    * Use the new test helper class.
    * Use the new base command class.

200. By Milo Casagrande

Use the new base command class.

201. By Milo Casagrande

Use os.path.devnull.

202. By Milo Casagrande

Added test for the utils module.

203. By Milo Casagrande

Added tests to the test suite.

204. By Milo Casagrande

Added dispatcher helper tests.

205. By Milo Casagrande

Fixed command help.

206. By Milo Casagrande

Fixed tests, use new test helper class.

Revision history for this message
Milo Casagrande (milo) wrote :
Download full text (4.2 KiB)

Hi Antonio,

thanks for going through the review!

On Tue, Jun 18, 2013 at 9:13 PM, Antonio Terceiro
<email address hidden> wrote:
> Review: Needs Fixing
>
> Hi Milo,
>
> Thanks for this. Some of my comments are related to the questions we already
> talked about earlier today, but I will also mention them here so others can
> also follow.
>
> review needs-fixing
>
>> === added file 'lava/base_command.py'
>> --- lava/base_command.py 1970-01-01 00:00:00 +0000
>> +++ lava/base_command.py 2013-06-17 13:33:27 +0000
>
> I would put this somewhere like lava/helper/command.py instead

Agree.
This is already done with the latest push.

I also refactored all the calls that made use of the lava-dispatcher
into lava/helper/dispatcher.py, and updated all the tests too.

> Maybe when we start working on the other items (testdef/script) we realize that
> we can move all of them in the lava.helper namespace instead of creating
> lava.testdef and lava.script.
>
>> @@ -0,0 +1,80 @@
>> +# Copyright (C) 2013 Linaro Limited
>> +#
>> +# Author: Milo Casagrande <email address hidden>
>> +#
>> +# This file is part of lava-tool.
>> +#
>> +# lava-tool is free software: you can redistribute it and/or modify
>> +# it under the terms of the GNU Lesser General Public License version 3
>> +# as published by the Free Software Foundation
>> +#
>> +# lava-tool is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU Lesser General Public License
>> +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
>> +
>> +"""Base command class common to lava commands series."""
>> +
>> +import subprocess
>> +
>> +from lava.config import InteractiveConfig
>> +from lava.tool.command import Command
>> +from lava.tool.errors import CommandError
>> +
>> +
>> +class BaseCommand(Command):
>> + """Base command class for all lava commands."""
>> + def __init__(self, parser, args):
>> + super(BaseCommand, self).__init__(parser, args)
>> + self.config = InteractiveConfig(
>> + force_interactive=self.args.interactive)
>> +
>> + @classmethod
>> + def register_arguments(cls, parser):
>> + super(BaseCommand, cls).register_arguments(parser)
>> + parser.add_argument("-i", "--interactive",
>> + action='store_true',
>> + help=("Forces asking for input parameters even if "
>> + "we already have them cached."))
>
> As we talked ealier, I think it's better to do it the other way around: assume
> interactivity always, and have a --non-interactive option to force not asking
> for input.

This one is done too.

> I think we should move all the user interaction asking for input to the
> config/parameter classes. This way we use the existing mechanism to store
> answers in configuration and ease users' life.

Agree here too, but I guess there will be some refactoring involved,
and the code review here might...

Read more...

lp:~milo/lava-tool/lava-168 updated
207. By Milo Casagrande

PEP8 fixes.

208. By Milo Casagrande

Fixed problem with one test.

209. By Milo Casagrande

Forced interactiviness to be true.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lava/config.py'
--- lava/config.py 2013-06-20 08:10:33 +0000
+++ lava/config.py 2013-06-20 08:10:33 +0000
@@ -51,7 +51,7 @@
5151
52class InteractiveConfig(object):52class InteractiveConfig(object):
5353
54 def __init__(self, force_interactive=False):54 def __init__(self, force_interactive=True):
55 self._force_interactive = force_interactive55 self._force_interactive = force_interactive
56 self._cache = {}56 self._cache = {}
5757
5858
=== modified file 'lava/device/commands.py'
--- lava/device/commands.py 2013-06-20 08:10:33 +0000
+++ lava/device/commands.py 2013-06-20 08:10:33 +0000
@@ -21,23 +21,21 @@
21"""21"""
2222
23import os23import os
24import subprocess
25import sys24import sys
26import random25
27import string26from lava.helper.command import (
2827 BaseCommand,
29from lava.config import InteractiveConfig28)
29
30from lava.helper.dispatcher import (
31 get_device_file,
32 get_devices_path,
33)
34
30from lava.device import get_known_device35from lava.device import get_known_device
31from lava.tool.command import Command, CommandGroup36from lava.tool.command import CommandGroup
32from lava.tool.errors import CommandError37from lava.tool.errors import CommandError
33from lava_tool.utils import has_command
3438
35# Default lava-dispatcher paths.
36DEFAULT_DISPATCHER_PATH = os.path.join("etc", "lava-dispatcher")
37USER_DISPATCHER_PATH = os.path.join(os.path.expanduser("~"), ".config",
38 "lava-dispatcher")
39# Default devices path, has to be joined with the dispatcher path.
40DEFAULT_DEVICES_PATH = "devices"
41DEVICE_FILE_SUFFIX = "conf"39DEVICE_FILE_SUFFIX = "conf"
4240
4341
@@ -47,119 +45,6 @@
47 namespace = "lava.device.commands"45 namespace = "lava.device.commands"
4846
4947
50class BaseCommand(Command):
51 """Base command for device commands."""
52 def __init__(self, parser, args):
53 super(BaseCommand, self).__init__(parser, args)
54 self.config = InteractiveConfig(
55 force_interactive=self.args.interactive)
56
57 @classmethod
58 def register_arguments(cls, parser):
59 super(BaseCommand, cls).register_arguments(parser)
60 parser.add_argument("-i", "--interactive",
61 action='store_true',
62 help=("Forces asking for input parameters even if "
63 "we already have them cached."))
64
65 @classmethod
66 def _get_dispatcher_paths(cls):
67 """Tries to get the dispatcher from lava-dispatcher."""
68 try:
69 from lava_dispatcher.config import write_path
70 return write_path()
71 except ImportError:
72 raise CommandError("Cannot find lava-dispatcher installation.")
73
74 @classmethod
75 def _choose_devices_path(cls, paths):
76 """Picks the first path that is writable by the user.
77
78 :param paths: A list of paths.
79 :return The first path where it is possible to write.
80 """
81 valid_path = None
82 for path in paths:
83 path = os.path.join(path, DEFAULT_DEVICES_PATH)
84 if os.path.exists(path):
85 name = "".join(random.choice(string.ascii_letters)
86 for x in range(6))
87 test_file = os.path.join(path, name)
88 try:
89 fp = open(test_file, 'a')
90 fp.close()
91 except IOError:
92 # Cannot write here.
93 continue
94 else:
95 valid_path = path
96 if os.path.isfile(test_file):
97 os.unlink(test_file)
98 break
99 else:
100 try:
101 os.makedirs(path)
102 except OSError:
103 # Cannot write here either.
104 continue
105 else:
106 valid_path = path
107 break
108 else:
109 raise CommandError("Insufficient permissions to create new "
110 "devices.")
111 return valid_path
112
113 @classmethod
114 def _get_devices_path(cls):
115 """Gets the path to the devices in the LAVA dispatcher."""
116 dispatcher_paths = cls._get_dispatcher_paths()
117 return cls._choose_devices_path(dispatcher_paths)
118
119 @classmethod
120 def edit_config_file(cls, config_file):
121 """Opens the specified file with the default file editor.
122
123 :param config_file: The file to edit.
124 """
125 editor = os.environ.get("EDITOR", None)
126 if editor is None:
127 if has_command("sensible-editor"):
128 editor = "sensible-editor"
129 elif has_command("xdg-open"):
130 editor = "xdg-open"
131 else:
132 # We really do not know how to open a file.
133 print >> sys.stdout, ("Cannot find an editor to open the "
134 "file '{0}'.".format(config_file))
135 print >> sys.stdout, ("Either set the 'EDITOR' environment "
136 "variable, or install 'sensible-editor' "
137 "or 'xdg-open'.")
138 sys.exit(-1)
139
140 try:
141 subprocess.Popen([editor, config_file]).wait()
142 except Exception:
143 raise CommandError("Error opening the file '{0}' with the "
144 "following editor: {1}.".format(config_file,
145 editor))
146
147 @classmethod
148 def _get_device_file(cls, file_name):
149 """Retrieves the config file name specified, if it exists.
150
151 :param file_name: The config file name to search.
152 :return The path to the file, or None if it does not exist.
153 """
154 try:
155 from lava_dispatcher.config import get_config_file
156
157 return get_config_file(os.path.join(DEFAULT_DEVICES_PATH,
158 file_name))
159 except ImportError:
160 raise CommandError("Cannot find lava-dispatcher installation.")
161
162
163class add(BaseCommand):48class add(BaseCommand):
164 """Adds a new device."""49 """Adds a new device."""
16550
@@ -171,14 +56,14 @@
171 def invoke(self):56 def invoke(self):
172 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])57 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
17358
174 if self._get_device_file(real_file_name):59 if get_device_file(real_file_name):
175 print >> sys.stdout, ("A device configuration file named '{0}' "60 print >> sys.stdout, ("A device configuration file named '{0}' "
176 "already exists.".format(real_file_name))61 "already exists.".format(real_file_name))
177 print >> sys.stdout, ("Use 'lava device config {0}' to edit "62 print >> sys.stdout, ("Use 'lava device config {0}' to edit "
178 "it.".format(self.args.DEVICE))63 "it.".format(self.args.DEVICE))
179 sys.exit(-1)64 sys.exit(-1)
18065
181 devices_path = self._get_devices_path()66 devices_path = get_devices_path()
182 device_conf_file = os.path.abspath(os.path.join(devices_path,67 device_conf_file = os.path.abspath(os.path.join(devices_path,
183 real_file_name))68 real_file_name))
18469
@@ -187,7 +72,7 @@
18772
188 print >> sys.stdout, ("Created device file '{0}' in: {1}".format(73 print >> sys.stdout, ("Created device file '{0}' in: {1}".format(
189 real_file_name, devices_path))74 real_file_name, devices_path))
190 self.edit_config_file(device_conf_file)75 self.edit_file(device_conf_file)
19176
19277
193class remove(BaseCommand):78class remove(BaseCommand):
@@ -201,7 +86,7 @@
20186
202 def invoke(self):87 def invoke(self):
203 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])88 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
204 device_conf = self._get_device_file(real_file_name)89 device_conf = get_device_file(real_file_name)
20590
206 if device_conf:91 if device_conf:
207 try:92 try:
@@ -224,27 +109,12 @@
224 parser.add_argument("DEVICE",109 parser.add_argument("DEVICE",
225 help="The name of the device to edit.")110 help="The name of the device to edit.")
226111
227 @classmethod
228 def can_edit_file(cls, conf_file):
229 """Checks if a file can be opend in write mode.
230
231 :param conf_file: The path to the file.
232 :return True if it is possible to write on the file, False otherwise.
233 """
234 can_edit = True
235 try:
236 fp = open(conf_file, "a")
237 fp.close()
238 except IOError:
239 can_edit = False
240 return can_edit
241
242 def invoke(self):112 def invoke(self):
243 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])113 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
114 device_conf = get_device_file(real_file_name)
244115
245 device_conf = self._get_device_file(real_file_name)
246 if device_conf and self.can_edit_file(device_conf):116 if device_conf and self.can_edit_file(device_conf):
247 self.edit_config_file(device_conf)117 self.edit_file(device_conf)
248 else:118 else:
249 raise CommandError("Cannot edit file '{0}' at: "119 raise CommandError("Cannot edit file '{0}' at: "
250 "{1}.".format(real_file_name, device_conf))120 "{1}.".format(real_file_name, device_conf))
251121
=== modified file 'lava/device/tests/test_commands.py'
--- lava/device/tests/test_commands.py 2013-06-20 08:10:33 +0000
+++ lava/device/tests/test_commands.py 2013-06-20 08:10:33 +0000
@@ -17,159 +17,84 @@
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
1818
19"""19"""
20Commands class unit tests.20lava.device.commands unit tests.
21"""21"""
2222
23import os23import os
24import shutil
25import sys
26import tempfile
2724
28from mock import MagicMock25from mock import MagicMock, patch
29from unittest import TestCase
3026
31from lava.device.commands import (27from lava.device.commands import (
32 BaseCommand,
33 add,28 add,
34 config,29 config,
35 remove,30 remove,
36)31)
3732from lava.helper.tests.helper_test import HelperTest
38from lava.tool.errors import CommandError33from lava.tool.errors import CommandError
3934
4035
41class CommandsTest(TestCase):36class CommandsTest(HelperTest):
42 def setUp(self):37
43 # Fake the stdout.38 @patch("lava.device.commands.get_device_file",
44 self.original_stdout = sys.stdout39 new=MagicMock(return_value=None))
45 sys.stdout = open("/dev/null", "w")40 @patch("lava.device.commands.get_devices_path")
46 self.original_stderr = sys.stderr41 def test_add_invoke(self, get_devices_path_mock):
47 sys.stderr = open("/dev/null", "w")
48 self.original_stdin = sys.stdin
49
50 self.device = "a_fake_panda02"
51
52 self.tempdir = tempfile.mkdtemp()
53 self.parser = MagicMock()
54 self.args = MagicMock()
55 self.args.interactive = MagicMock(return_value=False)
56 self.args.DEVICE = self.device
57
58 self.config = MagicMock()
59 self.config.get = MagicMock(return_value=self.tempdir)
60
61 def tearDown(self):
62 sys.stdin = self.original_stdin
63 sys.stdout = self.original_stdout
64 sys.stderr = self.original_stderr
65 shutil.rmtree(self.tempdir)
66
67 def test_get_devices_path_0(self):
68 # Tests that the correct devices path is returned and created.
69 base_command = BaseCommand(self.parser, self.args)
70 base_command.config = self.config
71 BaseCommand._get_dispatcher_paths = MagicMock(return_value=[
72 self.tempdir])
73 obtained = base_command._get_devices_path()
74 expected_path = os.path.join(self.tempdir, "devices")
75 self.assertEqual(expected_path, obtained)
76 self.assertTrue(os.path.isdir(expected_path))
77
78 def test_get_devices_path_1(self):
79 # Tests that when passing a path that is not writable, CommandError
80 # is raised.
81 base_command = BaseCommand(self.parser, self.args)
82 base_command.config = self.config
83 BaseCommand._get_dispatcher_paths = MagicMock(
84 return_value=["/", "/root", "/root/tmpdir"])
85 self.assertRaises(CommandError, base_command._get_devices_path)
86
87 def test_choose_devices_path(self):
88 # Tests that when passing more than one path, the first writable one
89 # is returned.
90 base_command = BaseCommand(self.parser, self.args)
91 base_command.config = self.config
92 obtained = base_command._choose_devices_path(
93 ["/", "/root", self.tempdir, os.path.expanduser("~")])
94 expected = os.path.join(self.tempdir, "devices")
95 self.assertEqual(expected, obtained)
96
97 def test_add_invoke(self):
98 # Tests invocation of the add command. Verifies that the conf file is42 # Tests invocation of the add command. Verifies that the conf file is
99 # written to disk.43 # written to disk.
44 get_devices_path_mock.return_value = self.temp_dir
45
100 add_command = add(self.parser, self.args)46 add_command = add(self.parser, self.args)
101 add_command.edit_config_file = MagicMock()47 add_command.edit_file = MagicMock()
102 add_command._get_device_file = MagicMock(return_value=None)
103 add_command._get_devices_path = MagicMock(return_value=self.tempdir)
104 add_command.invoke()48 add_command.invoke()
10549
106 expected_path = os.path.join(self.tempdir,50 expected_path = os.path.join(self.temp_dir,
107 ".".join([self.device, "conf"]))51 ".".join([self.device, "conf"]))
108 self.assertTrue(os.path.isfile(expected_path))52 self.assertTrue(os.path.isfile(expected_path))
10953
110 def test_remove_invoke(self):54 @patch("lava.device.commands.get_device_file")
55 @patch("lava.device.commands.get_devices_path")
56 def test_remove_invoke(self, get_devices_path_mock, get_device_file_mock):
111 # Tests invocation of the remove command. Verifies that the conf file57 # Tests invocation of the remove command. Verifies that the conf file
112 # has been correctly removed.58 # has been correctly removed.
113 # First we add a new conf file, then we remove it.59 # First we add a new conf file, then we remove it.
60 get_device_file_mock.return_value = None
61 get_devices_path_mock.return_value = self.temp_dir
62
114 add_command = add(self.parser, self.args)63 add_command = add(self.parser, self.args)
115 add_command.edit_config_file = MagicMock()64 add_command.edit_file = MagicMock()
116 add_command._get_device_file = MagicMock(return_value=None)
117 add_command._get_devices_path = MagicMock(return_value=self.tempdir)
118 add_command.invoke()65 add_command.invoke()
11966
120 expected_path = os.path.join(self.tempdir,67 expected_path = os.path.join(self.temp_dir,
121 ".".join([self.device, "conf"]))68 ".".join([self.device, "conf"]))
12269
70 # Set new values for the mocked function.
71 get_device_file_mock.return_value = expected_path
72
123 remove_command = remove(self.parser, self.args)73 remove_command = remove(self.parser, self.args)
124 remove_command._get_device_file = MagicMock(return_value=expected_path)
125 remove_command._get_devices_path = MagicMock(return_value=self.tempdir)
126 remove_command.invoke()74 remove_command.invoke()
12775
128 self.assertFalse(os.path.isfile(expected_path))76 self.assertFalse(os.path.isfile(expected_path))
12977
78 @patch("lava.device.commands.get_device_file",
79 new=MagicMock(return_value="/root"))
130 def test_remove_invoke_raises(self):80 def test_remove_invoke_raises(self):
131 # Tests invocation of the remove command, with a non existent device81 # Tests invocation of the remove command, with a non existent device
132 # configuration file.82 # configuration file.
133 remove_command = remove(self.parser, self.args)83 remove_command = remove(self.parser, self.args)
134 remove_command._get_device_file = MagicMock(return_value="/root")
135
136 self.assertRaises(CommandError, remove_command.invoke)84 self.assertRaises(CommandError, remove_command.invoke)
13785
86 @patch("lava.device.commands.get_device_file",
87 new=MagicMock(return_value=None))
138 def test_config_invoke_raises_0(self):88 def test_config_invoke_raises_0(self):
139 # Tests invocation of the config command, with a non existent device89 # Tests invocation of the config command, with a non existent device
140 # configuration file.90 # configuration file.
141 config_command = config(self.parser, self.args)91 config_command = config(self.parser, self.args)
142 config_command._get_device_file = MagicMock(return_value=None)
143
144 self.assertRaises(CommandError, config_command.invoke)92 self.assertRaises(CommandError, config_command.invoke)
14593
94 @patch("lava.device.commands.get_device_file",
95 new=MagicMock(return_value="/etc/password"))
146 def test_config_invoke_raises_1(self):96 def test_config_invoke_raises_1(self):
147 # Tests invocation of the config command, with a non writable file.97 # Tests invocation of the config command, with a non writable file.
148 # Hopefully tests are not run as root.98 # Hopefully tests are not run as root.
149 config_command = config(self.parser, self.args)99 config_command = config(self.parser, self.args)
150 config_command._get_device_file = MagicMock(return_value="/etc/passwd")
151
152 self.assertRaises(CommandError, config_command.invoke)100 self.assertRaises(CommandError, config_command.invoke)
153
154 def test_can_edit_file(self):
155 # Tests the can_edit_file method of the config command.
156 # This is to make sure the device config file is not erased when
157 # checking if it is possible to open it.
158 expected = ("hostname = a_fake_panda02\nconnection_command = \n"
159 "device_type = panda\n")
160
161 config_command = config(self.parser, self.args)
162 try:
163 conf_file = tempfile.NamedTemporaryFile(delete=False)
164
165 with open(conf_file.name, "w") as f:
166 f.write(expected)
167
168 self.assertTrue(config_command.can_edit_file(conf_file.name))
169 obtained = ""
170 with open(conf_file.name) as f:
171 obtained = f.read()
172
173 self.assertEqual(expected, obtained)
174 finally:
175 os.unlink(conf_file.name)
176101
=== modified file 'lava/device/tests/test_device.py'
--- lava/device/tests/test_device.py 2013-06-20 08:10:33 +0000
+++ lava/device/tests/test_device.py 2013-06-20 08:10:33 +0000
@@ -20,34 +20,19 @@
20Device class unit tests.20Device class unit tests.
21"""21"""
2222
23import os
24import sys23import sys
25import tempfile
2624
27from StringIO import StringIO25from StringIO import StringIO
28from unittest import TestCase
2926
30from lava.device import (27from lava.device import (
31 Device,28 Device,
32 get_known_device,29 get_known_device,
33)30)
34from lava.tool.errors import CommandError31from lava.tool.errors import CommandError
3532from lava.helper.tests.helper_test import HelperTest
3633
37class DeviceTest(TestCase):34
3835class DeviceTest(HelperTest):
39 def setUp(self):
40 # Fake the stdin and the stdout.
41 self.original_stdout = sys.stdout
42 sys.stdout = open("/dev/null", "w")
43 self.original_stdin = sys.stdin
44 sys.stdin = StringIO()
45 self.temp_file = tempfile.NamedTemporaryFile(delete=False)
46
47 def tearDown(self):
48 sys.stdout = self.original_stdout
49 sys.stdin = self.original_stdin
50 os.unlink(self.temp_file.name)
5136
52 def test_get_known_device_panda_0(self):37 def test_get_known_device_panda_0(self):
53 # User creates a new device with a guessable name for a device.38 # User creates a new device with a guessable name for a device.
@@ -108,6 +93,7 @@
108 def test_get_known_device_raises(self):93 def test_get_known_device_raises(self):
109 # User tries to create a new device, but in some way nothing is passed94 # User tries to create a new device, but in some way nothing is passed
110 # on the command line when asked.95 # on the command line when asked.
96 sys.stdin = StringIO("\n")
111 self.assertRaises(CommandError, get_known_device, 'a_fake_device')97 self.assertRaises(CommandError, get_known_device, 'a_fake_device')
11298
113 def test_device_write(self):99 def test_device_write(self):
114100
=== added directory 'lava/helper'
=== added file 'lava/helper/__init__.py'
=== added file 'lava/helper/command.py'
--- lava/helper/command.py 1970-01-01 00:00:00 +0000
+++ lava/helper/command.py 2013-06-20 08:10:33 +0000
@@ -0,0 +1,101 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Milo Casagrande <milo.casagrande@linaro.org>
4#
5# This file is part of lava-tool.
6#
7# lava-tool is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3
9# as published by the Free Software Foundation
10#
11# lava-tool is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
18
19"""Base command class common to lava commands series."""
20
21import os
22import subprocess
23import sys
24
25
26from lava.config import InteractiveConfig
27from lava.tool.command import Command
28from lava.tool.errors import CommandError
29from lava_tool.utils import has_command
30
31
32class BaseCommand(Command):
33 """Base command class for all lava commands."""
34 def __init__(self, parser, args):
35 super(BaseCommand, self).__init__(parser, args)
36 self.config = InteractiveConfig(
37 force_interactive=self.args.non_interactive)
38
39 @classmethod
40 def register_arguments(cls, parser):
41 super(BaseCommand, cls).register_arguments(parser)
42 parser.add_argument("--non-interactive",
43 action='store_false',
44 help=("Do not ask for input parameters."))
45
46 @classmethod
47 def can_edit_file(cls, conf_file):
48 """Checks if a file can be opend in write mode.
49
50 :param conf_file: The path to the file.
51 :return True if it is possible to write on the file, False otherwise.
52 """
53 can_edit = True
54 try:
55 fp = open(conf_file, "a")
56 fp.close()
57 except IOError:
58 can_edit = False
59 return can_edit
60
61 @classmethod
62 def edit_file(cls, config_file):
63 """Opens the specified file with the default file editor.
64
65 :param config_file: The file to edit.
66 """
67 editor = os.environ.get("EDITOR", None)
68 if editor is None:
69 if has_command("sensible-editor"):
70 editor = "sensible-editor"
71 elif has_command("xdg-open"):
72 editor = "xdg-open"
73 else:
74 # We really do not know how to open a file.
75 print >> sys.stdout, ("Cannot find an editor to open the "
76 "file '{0}'.".format(config_file))
77 print >> sys.stdout, ("Either set the 'EDITOR' environment "
78 "variable, or install 'sensible-editor' "
79 "or 'xdg-open'.")
80 sys.exit(-1)
81 try:
82 subprocess.Popen([editor, config_file]).wait()
83 except Exception:
84 raise CommandError("Error opening the file '{0}' with the "
85 "following editor: {1}.".format(config_file,
86 editor))
87
88 @classmethod
89 def run(cls, cmd_args):
90 """Runs the supplied command args.
91
92 :param cmd_args: The command, and its optional arguments, to run.
93 :return The command execution return code.
94 """
95 if not isinstance(cmd_args, list):
96 cmd_args = list(cmd_args)
97 try:
98 return subprocess.check_call(cmd_args)
99 except subprocess.CalledProcessError:
100 raise CommandError("Error running the following command: "
101 "{0}".format(" ".join(cmd_args)))
0102
=== added file 'lava/helper/dispatcher.py'
--- lava/helper/dispatcher.py 1970-01-01 00:00:00 +0000
+++ lava/helper/dispatcher.py 2013-06-20 08:10:33 +0000
@@ -0,0 +1,110 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Milo Casagrande <milo.casagrande@linaro.org>
4#
5# This file is part of lava-tool.
6#
7# lava-tool is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3
9# as published by the Free Software Foundation
10#
11# lava-tool is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
18
19"""Classes and functions to interact with the lava-dispatcher."""
20
21import random
22import string
23import os
24
25from lava.tool.errors import CommandError
26
27# Default devices path, has to be joined with the dispatcher path.
28DEFAULT_DEVICES_PATH = "devices"
29
30
31def get_dispatcher_paths():
32 """Tries to get the dispatcher paths from lava-dispatcher.
33
34 :return A list of paths.
35 """
36 try:
37 from lava_dispatcher.config import write_path
38 return write_path()
39 except ImportError:
40 raise CommandError("Cannot find lava-dispatcher installation.")
41
42
43def get_devices():
44 """Gets the devices list from the dispatcher.
45
46 :return A list of DeviceConfig.
47 """
48 try:
49 from lava_dispatcher.config import get_devices
50 return get_devices()
51 except ImportError:
52 raise CommandError("Cannot find lava-dispatcher installation.")
53
54
55def get_device_file(file_name):
56 """Retrieves the config file name specified, if it exists.
57
58 :param file_name: The config file name to search.
59 :return The path to the file, or None if it does not exist.
60 """
61 try:
62 from lava_dispatcher.config import get_config_file
63 return get_config_file(os.path.join(DEFAULT_DEVICES_PATH,
64 file_name))
65 except ImportError:
66 raise CommandError("Cannot find lava-dispatcher installation.")
67
68
69def choose_devices_path(paths):
70 """Picks the first path that is writable by the user.
71
72 :param paths: A list of paths.
73 :return The first path where it is possible to write.
74 """
75 valid_path = None
76 for path in paths:
77 path = os.path.join(path, DEFAULT_DEVICES_PATH)
78 if os.path.exists(path):
79 name = "".join(random.choice(string.ascii_letters)
80 for x in range(6))
81 test_file = os.path.join(path, name)
82 try:
83 fp = open(test_file, 'a')
84 fp.close()
85 except IOError:
86 # Cannot write here.
87 continue
88 else:
89 valid_path = path
90 if os.path.isfile(test_file):
91 os.unlink(test_file)
92 break
93 else:
94 try:
95 os.makedirs(path)
96 except OSError:
97 # Cannot write here either.
98 continue
99 else:
100 valid_path = path
101 break
102 else:
103 raise CommandError("Insufficient permissions to create new "
104 "devices.")
105 return valid_path
106
107
108def get_devices_path():
109 """Gets the path to the devices in the LAVA dispatcher."""
110 return choose_devices_path(get_dispatcher_paths())
0111
=== added directory 'lava/helper/tests'
=== added file 'lava/helper/tests/__init__.py'
=== added file 'lava/helper/tests/helper_test.py'
--- lava/helper/tests/helper_test.py 1970-01-01 00:00:00 +0000
+++ lava/helper/tests/helper_test.py 2013-06-20 08:10:33 +0000
@@ -0,0 +1,61 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Milo Casagrande <milo.casagrande@linaro.org>
4#
5# This file is part of lava-tool.
6#
7# lava-tool is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3
9# as published by the Free Software Foundation
10#
11# lava-tool is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
18
19"""
20A test helper class.
21
22Here we define a general test class and its own setUp and tearDown methods that
23all other test classes can inherit from.
24"""
25
26import os
27import shutil
28import sys
29import tempfile
30
31from unittest import TestCase
32from mock import MagicMock
33
34
35class HelperTest(TestCase):
36 """Helper test class that all tests under the lava package can inherit."""
37 def setUp(self):
38 self.original_stdout = sys.stdout
39 sys.stdout = open("/dev/null", "w")
40 self.original_stderr = sys.stderr
41 sys.stderr = open("/dev/null", "w")
42 self.original_stdin = sys.stdin
43
44 self.device = "a_fake_panda02"
45
46 self.temp_file = tempfile.NamedTemporaryFile(delete=False)
47 self.temp_dir = tempfile.mkdtemp()
48 self.parser = MagicMock()
49 self.args = MagicMock()
50 self.args.interactive = MagicMock(return_value=False)
51 self.args.DEVICE = self.device
52
53 self.config = MagicMock()
54 self.config.get = MagicMock(return_value=self.temp_dir)
55
56 def tearDown(self):
57 sys.stdin = self.original_stdin
58 sys.stdout = self.original_stdout
59 sys.stderr = self.original_stderr
60 shutil.rmtree(self.temp_dir)
61 os.unlink(self.temp_file.name)
062
=== added file 'lava/helper/tests/test_command.py'
--- lava/helper/tests/test_command.py 1970-01-01 00:00:00 +0000
+++ lava/helper/tests/test_command.py 2013-06-20 08:10:33 +0000
@@ -0,0 +1,53 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Milo Casagrande <milo.casagrande@linaro.org>
4#
5# This file is part of lava-tool.
6#
7# lava-tool is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3
9# as published by the Free Software Foundation
10#
11# lava-tool is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
18
19"""lava.herlp.command module tests."""
20
21from lava.helper.command import BaseCommand
22from lava.helper.tests.helper_test import HelperTest
23
24
25class BaseCommandTests(HelperTest):
26
27 def test_register_argument(self):
28 # Make sure that the parser add_argument is called and we have the
29 # correct argument.
30 command = BaseCommand(self.parser, self.args)
31 command.register_arguments(self.parser)
32 name, args, kwargs = self.parser.method_calls[0]
33 self.assertIn("--non-interactive", args)
34
35 def test_can_edit_file(self):
36 # Tests the can_edit_file method of the config command.
37 # This is to make sure the device config file is not erased when
38 # checking if it is possible to open it.
39 expected = ("hostname = a_fake_panda02\nconnection_command = \n"
40 "device_type = panda\n")
41
42 command = BaseCommand(self.parser, self.args)
43 conf_file = self.temp_file
44
45 with open(conf_file.name, "w") as f:
46 f.write(expected)
47
48 self.assertTrue(command.can_edit_file(conf_file.name))
49 obtained = ""
50 with open(conf_file.name) as f:
51 obtained = f.read()
52
53 self.assertEqual(expected, obtained)
054
=== added file 'lava/helper/tests/test_dispatcher.py'
--- lava/helper/tests/test_dispatcher.py 1970-01-01 00:00:00 +0000
+++ lava/helper/tests/test_dispatcher.py 2013-06-20 08:10:33 +0000
@@ -0,0 +1,52 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Milo Casagrande <milo.casagrande@linaro.org>
4#
5# This file is part of lava-tool.
6#
7# lava-tool is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3
9# as published by the Free Software Foundation
10#
11# lava-tool is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
18
19"""lava.helper.dispatcher tests."""
20
21import os
22from lava.tool.errors import CommandError
23
24from lava.helper.tests.helper_test import HelperTest
25
26from lava.helper.dispatcher import (
27 choose_devices_path,
28)
29
30
31class DispatcherTests(HelperTest):
32
33 def test_choose_devices_path_0(self):
34 # Tests that when passing more than one path, the first writable one
35 # is returned.
36 obtained = choose_devices_path(
37 ["/", "/root", self.temp_dir, os.path.expanduser("~")])
38 expected = os.path.join(self.temp_dir, "devices")
39 self.assertEqual(expected, obtained)
40
41 def test_choose_devices_path_1(self):
42 # Tests that when passing a path that is not writable, CommandError
43 # is raised.
44 self.assertRaises(CommandError, choose_devices_path,
45 ["/", "/root", "/root/tmpdir"])
46
47 def test_choose_devices_path_2(self):
48 # Tests that the correct path for devices is created on the filesystem.
49 expected_path = os.path.join(self.temp_dir, "devices")
50 obtained = choose_devices_path([self.temp_dir])
51 self.assertEqual(expected_path, obtained)
52 self.assertTrue(os.path.isdir(expected_path))
053
=== modified file 'lava/job/__init__.py'
--- lava/job/__init__.py 2013-05-28 22:08:12 +0000
+++ lava/job/__init__.py 2013-06-20 08:10:33 +0000
@@ -21,12 +21,13 @@
2121
22from lava.job.templates import Parameter22from lava.job.templates import Parameter
2323
24
24class Job:25class Job:
25
26 def __init__(self, template):26 def __init__(self, template):
27 self.data = deepcopy(template)27 self.data = deepcopy(template)
2828
29 def fill_in(self, config):29 def fill_in(self, config):
30
30 def insert_data(data):31 def insert_data(data):
31 if isinstance(data, dict):32 if isinstance(data, dict):
32 keys = data.keys()33 keys = data.keys()
@@ -44,4 +45,3 @@
4445
45 def write(self, stream):46 def write(self, stream):
46 stream.write(json.dumps(self.data, indent=4))47 stream.write(json.dumps(self.data, indent=4))
47
4848
=== modified file 'lava/job/commands.py'
--- lava/job/commands.py 2013-06-03 18:06:49 +0000
+++ lava/job/commands.py 2013-06-20 08:10:33 +0000
@@ -16,40 +16,34 @@
16# You should have received a copy of the GNU Lesser General Public License16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
1818
19from os.path import exists19"""
2020LAVA job commands.
21from lava.config import InteractiveConfig21"""
22
23import os
24import sys
25import xmlrpclib
26
27from lava.helper.command import BaseCommand
28
29from lava.config import Parameter
22from lava.job import Job30from lava.job import Job
23from lava.job.templates import *31from lava.job.templates import (
24from lava.tool.command import Command, CommandGroup32 BOOT_TEST,
33)
34from lava.tool.command import CommandGroup
25from lava.tool.errors import CommandError35from lava.tool.errors import CommandError
26
27from lava_tool.authtoken import AuthenticatingServerProxy, KeyringAuthBackend36from lava_tool.authtoken import AuthenticatingServerProxy, KeyringAuthBackend
28import xmlrpclib37from lava_tool.utils import has_command
38
2939
30class job(CommandGroup):40class job(CommandGroup):
31 """41 """LAVA job file handling."""
32 LAVA job file handling
33 """
34
35 namespace = 'lava.job.commands'42 namespace = 'lava.job.commands'
3643
37class BaseCommand(Command):
38
39 def __init__(self, parser, args):
40 super(BaseCommand, self).__init__(parser, args)
41 self.config = InteractiveConfig(force_interactive=self.args.interactive)
42
43 @classmethod
44 def register_arguments(cls, parser):
45 super(BaseCommand, cls).register_arguments(parser)
46 parser.add_argument(
47 "-i", "--interactive",
48 action='store_true',
49 help=("Forces asking for input parameters even if we already "
50 "have them cached."))
5144
52class new(BaseCommand):45class new(BaseCommand):
46 """Creates a new job file."""
5347
54 @classmethod48 @classmethod
55 def register_arguments(cls, parser):49 def register_arguments(cls, parser):
@@ -57,20 +51,22 @@
57 parser.add_argument("FILE", help=("Job file to be created."))51 parser.add_argument("FILE", help=("Job file to be created."))
5852
59 def invoke(self):53 def invoke(self):
60 if exists(self.args.FILE):54 if os.path.exists(self.args.FILE):
61 raise CommandError('%s already exists' % self.args.FILE)55 raise CommandError('{0} already exists.'.format(self.args.FILE))
6256
63 with open(self.args.FILE, 'w') as f:57 with open(self.args.FILE, 'w') as job_file:
64 job = Job(BOOT_TEST)58 job_instance = Job(BOOT_TEST)
65 job.fill_in(self.config)59 job_instance.fill_in(self.config)
66 job.write(f)60 job_instance.write(job_file)
6761
6862
69class submit(BaseCommand):63class submit(BaseCommand):
64 """Submits the specified job file."""
65
70 @classmethod66 @classmethod
71 def register_arguments(cls, parser):67 def register_arguments(cls, parser):
72 super(submit, cls).register_arguments(parser)68 super(submit, cls).register_arguments(parser)
73 parser.add_argument("FILE", help=("The job file to submit"))69 parser.add_argument("FILE", help=("The job file to submit."))
7470
75 def invoke(self):71 def invoke(self):
76 jobfile = self.args.FILE72 jobfile = self.args.FILE
@@ -85,10 +81,61 @@
85 auth_backend=KeyringAuthBackend())81 auth_backend=KeyringAuthBackend())
86 try:82 try:
87 job_id = server.scheduler.submit_job(jobdata)83 job_id = server.scheduler.submit_job(jobdata)
88 print "Job submitted with job ID %d" % job_id84 print >> sys.stdout, "Job submitted with job ID {0}".format(job_id)
89 except xmlrpclib.Fault, e:85 except xmlrpclib.Fault, exc:
90 raise CommandError(str(e))86 raise CommandError(str(exc))
87
9188
92class run(BaseCommand):89class run(BaseCommand):
90 """Runs the specified job file on the local dispatcher."""
91
92 @classmethod
93 def register_arguments(cls, parser):
94 super(run, cls).register_arguments(parser)
95 parser.add_argument("FILE", help=("The job file to submit."))
96
97 @classmethod
98 def _choose_device(cls, devices):
99 """Let the user choose the device to use.
100
101 :param devices: The list of available devices.
102 :return The selected device.
103 """
104 devices_len = len(devices)
105 output_list = []
106 for device, number in zip(devices, range(1, devices_len + 1)):
107 output_list.append("\t{0}. {1}\n".format(number, device.hostname))
108
109 print >> sys.stdout, ("More than one local device found. "
110 "Please choose one:\n")
111 print >> sys.stdout, "".join(output_list)
112
113 while True:
114 try:
115 user_input = raw_input("Device number to use: ").strip()
116
117 if user_input in [str(x) for x in range(1, devices_len + 1)]:
118 return devices[int(user_input) - 1].hostname
119 else:
120 continue
121 except EOFError:
122 user_input = None
123 except KeyboardInterrupt:
124 sys.exit(-1)
125
93 def invoke(self):126 def invoke(self):
94 print("hello world")127 if os.path.isfile(self.args.FILE):
128 if has_command("lava-dispatch"):
129 devices = self.get_devices()
130 if devices:
131 if len(devices) > 1:
132 device = self._choose_device(devices)
133 else:
134 device = devices[0].hostname
135 self.run(["lava-dispatch", "--target", device,
136 self.args.FILE])
137 else:
138 raise CommandError("Cannot find lava-dispatcher installation.")
139 else:
140 raise CommandError("The file '{0}' does not exists. or is not "
141 "a file.".format(self.args.FILE))
95142
=== modified file 'lava/job/tests/test_commands.py'
--- lava/job/tests/test_commands.py 2013-06-03 18:06:49 +0000
+++ lava/job/tests/test_commands.py 2013-06-20 08:10:33 +0000
@@ -20,74 +20,95 @@
20Unit tests for the commands classes20Unit tests for the commands classes
21"""21"""
2222
23from argparse import ArgumentParser
24import json23import json
25from os import (24import os
26 makedirs,25
27 removedirs,26from mock import MagicMock, patch
28)27
29from os.path import(28from lava.config import NonInteractiveConfig, Parameter
30 exists,29
31 join,30from lava.job.commands import (
32)31 new,
33from shutil import(32 run,
34 rmtree,33 submit,
35)34)
36from tempfile import mkdtemp35
37from unittest import TestCase36from lava.helper.tests.helper_test import HelperTest
38
39from lava.config import NonInteractiveConfig
40from lava.job.commands import *
41from lava.tool.errors import CommandError37from lava.tool.errors import CommandError
4238
43from mocker import Mocker39
4440class CommandTest(HelperTest):
45def make_command(command, *args):
46 parser = ArgumentParser(description="fake argument parser")
47 command.register_arguments(parser)
48 the_args = parser.parse_args(*args)
49 cmd = command(parser, the_args)
50 cmd.config = NonInteractiveConfig({ 'device_type': 'foo', 'prebuilt_image': 'bar' })
51 return cmd
52
53class CommandTest(TestCase):
5441
55 def setUp(self):42 def setUp(self):
56 self.tmpdir = mkdtemp()43 super(CommandTest, self).setUp()
44 self.args.FILE = self.temp_file.name
5745
58 def tearDown(self):46 self.device_type = Parameter('device_type')
59 rmtree(self.tmpdir)47 self.prebuilt_image = Parameter('prebuilt_image',
48 depends=self.device_type)
49 self.config = NonInteractiveConfig(
50 {'device_type': 'foo', 'prebuilt_image': 'bar'})
6051
61 def tmp(self, filename):52 def tmp(self, filename):
62 return join(self.tmpdir, filename)53 """Returns a path to a non existent file.
54
55 :param filename: The name the file should have.
56 :return A path.
57 """
58 return os.path.join(self.temp_dir, filename)
59
6360
64class JobNewTest(CommandTest):61class JobNewTest(CommandTest):
6562
63 def setUp(self):
64 super(JobNewTest, self).setUp()
65 self.args.FILE = self.tmp("new_file.json")
66 self.new_command = new(self.parser, self.args)
67 self.new_command.config = self.config
68
69 def tearDown(self):
70 super(JobNewTest, self).tearDown()
71 if os.path.exists(self.args.FILE):
72 os.unlink(self.args.FILE)
73
66 def test_create_new_file(self):74 def test_create_new_file(self):
67 f = self.tmp('file.json')75 self.new_command.invoke()
68 command = make_command(new, [f])76 self.assertTrue(os.path.exists(self.args.FILE))
69 command.invoke()
70 self.assertTrue(exists(f))
7177
72 def test_fills_in_template_parameters(self):78 def test_fills_in_template_parameters(self):
73 f = self.tmp('myjob.json')79 self.new_command.invoke()
74 command = make_command(new, [f])
75 command.invoke()
7680
77 data = json.loads(open(f).read())81 data = json.loads(open(self.args.FILE).read())
78 self.assertEqual(data['device_type'], 'foo')82 self.assertEqual(data['device_type'], 'foo')
7983
80 def test_wont_overwriteexisting_file(self):84 def test_wont_overwrite_existing_file(self):
81 existing = self.tmp('existing.json')85 with open(self.args.FILE, 'w') as f:
82 with open(existing, 'w') as f:
83 f.write("CONTENTS")86 f.write("CONTENTS")
84 command = make_command(new, [existing])87
85 with self.assertRaises(CommandError):88 self.assertRaises(CommandError, self.new_command.invoke)
86 command.invoke()89 self.assertEqual("CONTENTS", open(self.args.FILE).read())
87 self.assertEqual("CONTENTS", open(existing).read())90
8891
89class JobSubmitTest(CommandTest):92class JobSubmitTest(CommandTest):
9093
91 def test_receives_job_file_in_cmdline(self):94 def test_receives_job_file_in_cmdline(self):
92 cmd = make_command(new, ['FOO.json'])95 command = submit(self.parser, self.args)
93 self.assertEqual('FOO.json', cmd.args.FILE)96 command.register_arguments(self.parser)
97 name, args, kwargs = self.parser.method_calls[1]
98 self.assertIn("FILE", args)
99
100
101class JobRunTest(CommandTest):
102
103 def test_invoke_raises_0(self):
104 # Users passes a non existing job file to the run command.
105 self.args.FILE = self.tmp("test_invoke_raises_0.json")
106 command = run(self.parser, self.args)
107 self.assertRaises(CommandError, command.invoke)
108
109 @patch("lava.job.commands.has_command", new=MagicMock(return_value=False))
110 def test_invoke_raises_1(self):
111 # Users passes a valid file to the run command, but she does not have
112 # the dispatcher installed.
113 command = run(self.parser, self.args)
114 self.assertRaises(CommandError, command.invoke)
94115
=== modified file 'lava_tool/tests/__init__.py'
--- lava_tool/tests/__init__.py 2013-06-20 08:10:33 +0000
+++ lava_tool/tests/__init__.py 2013-06-20 08:10:33 +0000
@@ -38,11 +38,14 @@
38 'lava_tool.tests.test_authtoken',38 'lava_tool.tests.test_authtoken',
39 'lava_tool.tests.test_auth_commands',39 'lava_tool.tests.test_auth_commands',
40 'lava_tool.tests.test_commands',40 'lava_tool.tests.test_commands',
41 'lava_tool.tests.test_utils',
41 'lava_dashboard_tool.tests.test_commands',42 'lava_dashboard_tool.tests.test_commands',
42 'lava.job.tests.test_job',43 'lava.job.tests.test_job',
43 'lava.job.tests.test_commands',44 'lava.job.tests.test_commands',
44 'lava.device.tests.test_device',45 'lava.device.tests.test_device',
45 'lava.device.tests.test_commands',46 'lava.device.tests.test_commands',
47 'lava.helper.tests.test_command',
48 'lava.helper.tests.test_dispatcher',
46 ]49 ]
4750
4851
4952
=== added file 'lava_tool/tests/test_utils.py'
--- lava_tool/tests/test_utils.py 1970-01-01 00:00:00 +0000
+++ lava_tool/tests/test_utils.py 2013-06-20 08:10:33 +0000
@@ -0,0 +1,41 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Milo Casagrande <milo.casagrande@linaro.org>
4#
5# This file is part of lava-tool.
6#
7# lava-tool is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3
9# as published by the Free Software Foundation
10#
11# lava-tool is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
18
19"""lava_tool.utils tests."""
20
21import subprocess
22
23from unittest import TestCase
24from mock import patch
25
26from lava_tool.utils import has_command
27
28
29class UtilTests(TestCase):
30
31 @patch("lava_tool.utils.subprocess.check_call")
32 def test_has_command_0(self, mocked_check_call):
33 # Make sure we raise an exception when the subprocess is called.
34 mocked_check_call.side_effect = subprocess.CalledProcessError(0, "")
35 self.assertFalse(has_command(""))
36
37 @patch("lava_tool.utils.subprocess.check_call")
38 def test_has_command_1(self, mocked_check_call):
39 # Check that a "command" exists. The call to subprocess is mocked.
40 mocked_check_call.return_value = 0
41 self.assertTrue(has_command(""))
042
=== modified file 'lava_tool/utils.py'
--- lava_tool/utils.py 2013-06-20 08:10:33 +0000
+++ lava_tool/utils.py 2013-06-20 08:10:33 +0000
@@ -16,6 +16,7 @@
16# You should have received a copy of the GNU Lesser General Public License16# You should have received a copy of the GNU Lesser General Public License
17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.17# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
1818
19import os
19import subprocess20import subprocess
2021
2122
@@ -27,7 +28,7 @@
27 command_available = True28 command_available = True
28 try:29 try:
29 subprocess.check_call(["which", command],30 subprocess.check_call(["which", command],
30 stdout=open('/dev/null', 'w'))31 stdout=open(os.path.devnull, 'w'))
31 except subprocess.CalledProcessError:32 except subprocess.CalledProcessError:
32 command_available = False33 command_available = False
33 return command_available34 return command_available

Subscribers

People subscribed via source and target branches