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
1=== modified file 'lava/config.py'
2--- lava/config.py 2013-06-20 08:10:33 +0000
3+++ lava/config.py 2013-06-20 08:10:33 +0000
4@@ -51,7 +51,7 @@
5
6 class InteractiveConfig(object):
7
8- def __init__(self, force_interactive=False):
9+ def __init__(self, force_interactive=True):
10 self._force_interactive = force_interactive
11 self._cache = {}
12
13
14=== modified file 'lava/device/commands.py'
15--- lava/device/commands.py 2013-06-20 08:10:33 +0000
16+++ lava/device/commands.py 2013-06-20 08:10:33 +0000
17@@ -21,23 +21,21 @@
18 """
19
20 import os
21-import subprocess
22 import sys
23-import random
24-import string
25-
26-from lava.config import InteractiveConfig
27+
28+from lava.helper.command import (
29+ BaseCommand,
30+)
31+
32+from lava.helper.dispatcher import (
33+ get_device_file,
34+ get_devices_path,
35+)
36+
37 from lava.device import get_known_device
38-from lava.tool.command import Command, CommandGroup
39+from lava.tool.command import CommandGroup
40 from lava.tool.errors import CommandError
41-from lava_tool.utils import has_command
42
43-# Default lava-dispatcher paths.
44-DEFAULT_DISPATCHER_PATH = os.path.join("etc", "lava-dispatcher")
45-USER_DISPATCHER_PATH = os.path.join(os.path.expanduser("~"), ".config",
46- "lava-dispatcher")
47-# Default devices path, has to be joined with the dispatcher path.
48-DEFAULT_DEVICES_PATH = "devices"
49 DEVICE_FILE_SUFFIX = "conf"
50
51
52@@ -47,119 +45,6 @@
53 namespace = "lava.device.commands"
54
55
56-class BaseCommand(Command):
57- """Base command for device commands."""
58- def __init__(self, parser, args):
59- super(BaseCommand, self).__init__(parser, args)
60- self.config = InteractiveConfig(
61- force_interactive=self.args.interactive)
62-
63- @classmethod
64- def register_arguments(cls, parser):
65- super(BaseCommand, cls).register_arguments(parser)
66- parser.add_argument("-i", "--interactive",
67- action='store_true',
68- help=("Forces asking for input parameters even if "
69- "we already have them cached."))
70-
71- @classmethod
72- def _get_dispatcher_paths(cls):
73- """Tries to get the dispatcher from lava-dispatcher."""
74- try:
75- from lava_dispatcher.config import write_path
76- return write_path()
77- except ImportError:
78- raise CommandError("Cannot find lava-dispatcher installation.")
79-
80- @classmethod
81- def _choose_devices_path(cls, paths):
82- """Picks the first path that is writable by the user.
83-
84- :param paths: A list of paths.
85- :return The first path where it is possible to write.
86- """
87- valid_path = None
88- for path in paths:
89- path = os.path.join(path, DEFAULT_DEVICES_PATH)
90- if os.path.exists(path):
91- name = "".join(random.choice(string.ascii_letters)
92- for x in range(6))
93- test_file = os.path.join(path, name)
94- try:
95- fp = open(test_file, 'a')
96- fp.close()
97- except IOError:
98- # Cannot write here.
99- continue
100- else:
101- valid_path = path
102- if os.path.isfile(test_file):
103- os.unlink(test_file)
104- break
105- else:
106- try:
107- os.makedirs(path)
108- except OSError:
109- # Cannot write here either.
110- continue
111- else:
112- valid_path = path
113- break
114- else:
115- raise CommandError("Insufficient permissions to create new "
116- "devices.")
117- return valid_path
118-
119- @classmethod
120- def _get_devices_path(cls):
121- """Gets the path to the devices in the LAVA dispatcher."""
122- dispatcher_paths = cls._get_dispatcher_paths()
123- return cls._choose_devices_path(dispatcher_paths)
124-
125- @classmethod
126- def edit_config_file(cls, config_file):
127- """Opens the specified file with the default file editor.
128-
129- :param config_file: The file to edit.
130- """
131- editor = os.environ.get("EDITOR", None)
132- if editor is None:
133- if has_command("sensible-editor"):
134- editor = "sensible-editor"
135- elif has_command("xdg-open"):
136- editor = "xdg-open"
137- else:
138- # We really do not know how to open a file.
139- print >> sys.stdout, ("Cannot find an editor to open the "
140- "file '{0}'.".format(config_file))
141- print >> sys.stdout, ("Either set the 'EDITOR' environment "
142- "variable, or install 'sensible-editor' "
143- "or 'xdg-open'.")
144- sys.exit(-1)
145-
146- try:
147- subprocess.Popen([editor, config_file]).wait()
148- except Exception:
149- raise CommandError("Error opening the file '{0}' with the "
150- "following editor: {1}.".format(config_file,
151- editor))
152-
153- @classmethod
154- def _get_device_file(cls, file_name):
155- """Retrieves the config file name specified, if it exists.
156-
157- :param file_name: The config file name to search.
158- :return The path to the file, or None if it does not exist.
159- """
160- try:
161- from lava_dispatcher.config import get_config_file
162-
163- return get_config_file(os.path.join(DEFAULT_DEVICES_PATH,
164- file_name))
165- except ImportError:
166- raise CommandError("Cannot find lava-dispatcher installation.")
167-
168-
169 class add(BaseCommand):
170 """Adds a new device."""
171
172@@ -171,14 +56,14 @@
173 def invoke(self):
174 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
175
176- if self._get_device_file(real_file_name):
177+ if get_device_file(real_file_name):
178 print >> sys.stdout, ("A device configuration file named '{0}' "
179 "already exists.".format(real_file_name))
180 print >> sys.stdout, ("Use 'lava device config {0}' to edit "
181 "it.".format(self.args.DEVICE))
182 sys.exit(-1)
183
184- devices_path = self._get_devices_path()
185+ devices_path = get_devices_path()
186 device_conf_file = os.path.abspath(os.path.join(devices_path,
187 real_file_name))
188
189@@ -187,7 +72,7 @@
190
191 print >> sys.stdout, ("Created device file '{0}' in: {1}".format(
192 real_file_name, devices_path))
193- self.edit_config_file(device_conf_file)
194+ self.edit_file(device_conf_file)
195
196
197 class remove(BaseCommand):
198@@ -201,7 +86,7 @@
199
200 def invoke(self):
201 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
202- device_conf = self._get_device_file(real_file_name)
203+ device_conf = get_device_file(real_file_name)
204
205 if device_conf:
206 try:
207@@ -224,27 +109,12 @@
208 parser.add_argument("DEVICE",
209 help="The name of the device to edit.")
210
211- @classmethod
212- def can_edit_file(cls, conf_file):
213- """Checks if a file can be opend in write mode.
214-
215- :param conf_file: The path to the file.
216- :return True if it is possible to write on the file, False otherwise.
217- """
218- can_edit = True
219- try:
220- fp = open(conf_file, "a")
221- fp.close()
222- except IOError:
223- can_edit = False
224- return can_edit
225-
226 def invoke(self):
227 real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
228+ device_conf = get_device_file(real_file_name)
229
230- device_conf = self._get_device_file(real_file_name)
231 if device_conf and self.can_edit_file(device_conf):
232- self.edit_config_file(device_conf)
233+ self.edit_file(device_conf)
234 else:
235 raise CommandError("Cannot edit file '{0}' at: "
236 "{1}.".format(real_file_name, device_conf))
237
238=== modified file 'lava/device/tests/test_commands.py'
239--- lava/device/tests/test_commands.py 2013-06-20 08:10:33 +0000
240+++ lava/device/tests/test_commands.py 2013-06-20 08:10:33 +0000
241@@ -17,159 +17,84 @@
242 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
243
244 """
245-Commands class unit tests.
246+lava.device.commands unit tests.
247 """
248
249 import os
250-import shutil
251-import sys
252-import tempfile
253
254-from mock import MagicMock
255-from unittest import TestCase
256+from mock import MagicMock, patch
257
258 from lava.device.commands import (
259- BaseCommand,
260 add,
261 config,
262 remove,
263 )
264-
265+from lava.helper.tests.helper_test import HelperTest
266 from lava.tool.errors import CommandError
267
268
269-class CommandsTest(TestCase):
270- def setUp(self):
271- # Fake the stdout.
272- self.original_stdout = sys.stdout
273- sys.stdout = open("/dev/null", "w")
274- self.original_stderr = sys.stderr
275- sys.stderr = open("/dev/null", "w")
276- self.original_stdin = sys.stdin
277-
278- self.device = "a_fake_panda02"
279-
280- self.tempdir = tempfile.mkdtemp()
281- self.parser = MagicMock()
282- self.args = MagicMock()
283- self.args.interactive = MagicMock(return_value=False)
284- self.args.DEVICE = self.device
285-
286- self.config = MagicMock()
287- self.config.get = MagicMock(return_value=self.tempdir)
288-
289- def tearDown(self):
290- sys.stdin = self.original_stdin
291- sys.stdout = self.original_stdout
292- sys.stderr = self.original_stderr
293- shutil.rmtree(self.tempdir)
294-
295- def test_get_devices_path_0(self):
296- # Tests that the correct devices path is returned and created.
297- base_command = BaseCommand(self.parser, self.args)
298- base_command.config = self.config
299- BaseCommand._get_dispatcher_paths = MagicMock(return_value=[
300- self.tempdir])
301- obtained = base_command._get_devices_path()
302- expected_path = os.path.join(self.tempdir, "devices")
303- self.assertEqual(expected_path, obtained)
304- self.assertTrue(os.path.isdir(expected_path))
305-
306- def test_get_devices_path_1(self):
307- # Tests that when passing a path that is not writable, CommandError
308- # is raised.
309- base_command = BaseCommand(self.parser, self.args)
310- base_command.config = self.config
311- BaseCommand._get_dispatcher_paths = MagicMock(
312- return_value=["/", "/root", "/root/tmpdir"])
313- self.assertRaises(CommandError, base_command._get_devices_path)
314-
315- def test_choose_devices_path(self):
316- # Tests that when passing more than one path, the first writable one
317- # is returned.
318- base_command = BaseCommand(self.parser, self.args)
319- base_command.config = self.config
320- obtained = base_command._choose_devices_path(
321- ["/", "/root", self.tempdir, os.path.expanduser("~")])
322- expected = os.path.join(self.tempdir, "devices")
323- self.assertEqual(expected, obtained)
324-
325- def test_add_invoke(self):
326+class CommandsTest(HelperTest):
327+
328+ @patch("lava.device.commands.get_device_file",
329+ new=MagicMock(return_value=None))
330+ @patch("lava.device.commands.get_devices_path")
331+ def test_add_invoke(self, get_devices_path_mock):
332 # Tests invocation of the add command. Verifies that the conf file is
333 # written to disk.
334+ get_devices_path_mock.return_value = self.temp_dir
335+
336 add_command = add(self.parser, self.args)
337- add_command.edit_config_file = MagicMock()
338- add_command._get_device_file = MagicMock(return_value=None)
339- add_command._get_devices_path = MagicMock(return_value=self.tempdir)
340+ add_command.edit_file = MagicMock()
341 add_command.invoke()
342
343- expected_path = os.path.join(self.tempdir,
344+ expected_path = os.path.join(self.temp_dir,
345 ".".join([self.device, "conf"]))
346 self.assertTrue(os.path.isfile(expected_path))
347
348- def test_remove_invoke(self):
349+ @patch("lava.device.commands.get_device_file")
350+ @patch("lava.device.commands.get_devices_path")
351+ def test_remove_invoke(self, get_devices_path_mock, get_device_file_mock):
352 # Tests invocation of the remove command. Verifies that the conf file
353 # has been correctly removed.
354 # First we add a new conf file, then we remove it.
355+ get_device_file_mock.return_value = None
356+ get_devices_path_mock.return_value = self.temp_dir
357+
358 add_command = add(self.parser, self.args)
359- add_command.edit_config_file = MagicMock()
360- add_command._get_device_file = MagicMock(return_value=None)
361- add_command._get_devices_path = MagicMock(return_value=self.tempdir)
362+ add_command.edit_file = MagicMock()
363 add_command.invoke()
364
365- expected_path = os.path.join(self.tempdir,
366+ expected_path = os.path.join(self.temp_dir,
367 ".".join([self.device, "conf"]))
368
369+ # Set new values for the mocked function.
370+ get_device_file_mock.return_value = expected_path
371+
372 remove_command = remove(self.parser, self.args)
373- remove_command._get_device_file = MagicMock(return_value=expected_path)
374- remove_command._get_devices_path = MagicMock(return_value=self.tempdir)
375 remove_command.invoke()
376
377 self.assertFalse(os.path.isfile(expected_path))
378
379+ @patch("lava.device.commands.get_device_file",
380+ new=MagicMock(return_value="/root"))
381 def test_remove_invoke_raises(self):
382 # Tests invocation of the remove command, with a non existent device
383 # configuration file.
384 remove_command = remove(self.parser, self.args)
385- remove_command._get_device_file = MagicMock(return_value="/root")
386-
387 self.assertRaises(CommandError, remove_command.invoke)
388
389+ @patch("lava.device.commands.get_device_file",
390+ new=MagicMock(return_value=None))
391 def test_config_invoke_raises_0(self):
392 # Tests invocation of the config command, with a non existent device
393 # configuration file.
394 config_command = config(self.parser, self.args)
395- config_command._get_device_file = MagicMock(return_value=None)
396-
397 self.assertRaises(CommandError, config_command.invoke)
398
399+ @patch("lava.device.commands.get_device_file",
400+ new=MagicMock(return_value="/etc/password"))
401 def test_config_invoke_raises_1(self):
402 # Tests invocation of the config command, with a non writable file.
403 # Hopefully tests are not run as root.
404 config_command = config(self.parser, self.args)
405- config_command._get_device_file = MagicMock(return_value="/etc/passwd")
406-
407 self.assertRaises(CommandError, config_command.invoke)
408-
409- def test_can_edit_file(self):
410- # Tests the can_edit_file method of the config command.
411- # This is to make sure the device config file is not erased when
412- # checking if it is possible to open it.
413- expected = ("hostname = a_fake_panda02\nconnection_command = \n"
414- "device_type = panda\n")
415-
416- config_command = config(self.parser, self.args)
417- try:
418- conf_file = tempfile.NamedTemporaryFile(delete=False)
419-
420- with open(conf_file.name, "w") as f:
421- f.write(expected)
422-
423- self.assertTrue(config_command.can_edit_file(conf_file.name))
424- obtained = ""
425- with open(conf_file.name) as f:
426- obtained = f.read()
427-
428- self.assertEqual(expected, obtained)
429- finally:
430- os.unlink(conf_file.name)
431
432=== modified file 'lava/device/tests/test_device.py'
433--- lava/device/tests/test_device.py 2013-06-20 08:10:33 +0000
434+++ lava/device/tests/test_device.py 2013-06-20 08:10:33 +0000
435@@ -20,34 +20,19 @@
436 Device class unit tests.
437 """
438
439-import os
440 import sys
441-import tempfile
442
443 from StringIO import StringIO
444-from unittest import TestCase
445
446 from lava.device import (
447 Device,
448 get_known_device,
449 )
450 from lava.tool.errors import CommandError
451-
452-
453-class DeviceTest(TestCase):
454-
455- def setUp(self):
456- # Fake the stdin and the stdout.
457- self.original_stdout = sys.stdout
458- sys.stdout = open("/dev/null", "w")
459- self.original_stdin = sys.stdin
460- sys.stdin = StringIO()
461- self.temp_file = tempfile.NamedTemporaryFile(delete=False)
462-
463- def tearDown(self):
464- sys.stdout = self.original_stdout
465- sys.stdin = self.original_stdin
466- os.unlink(self.temp_file.name)
467+from lava.helper.tests.helper_test import HelperTest
468+
469+
470+class DeviceTest(HelperTest):
471
472 def test_get_known_device_panda_0(self):
473 # User creates a new device with a guessable name for a device.
474@@ -108,6 +93,7 @@
475 def test_get_known_device_raises(self):
476 # User tries to create a new device, but in some way nothing is passed
477 # on the command line when asked.
478+ sys.stdin = StringIO("\n")
479 self.assertRaises(CommandError, get_known_device, 'a_fake_device')
480
481 def test_device_write(self):
482
483=== added directory 'lava/helper'
484=== added file 'lava/helper/__init__.py'
485=== added file 'lava/helper/command.py'
486--- lava/helper/command.py 1970-01-01 00:00:00 +0000
487+++ lava/helper/command.py 2013-06-20 08:10:33 +0000
488@@ -0,0 +1,101 @@
489+# Copyright (C) 2013 Linaro Limited
490+#
491+# Author: Milo Casagrande <milo.casagrande@linaro.org>
492+#
493+# This file is part of lava-tool.
494+#
495+# lava-tool is free software: you can redistribute it and/or modify
496+# it under the terms of the GNU Lesser General Public License version 3
497+# as published by the Free Software Foundation
498+#
499+# lava-tool is distributed in the hope that it will be useful,
500+# but WITHOUT ANY WARRANTY; without even the implied warranty of
501+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
502+# GNU General Public License for more details.
503+#
504+# You should have received a copy of the GNU Lesser General Public License
505+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
506+
507+"""Base command class common to lava commands series."""
508+
509+import os
510+import subprocess
511+import sys
512+
513+
514+from lava.config import InteractiveConfig
515+from lava.tool.command import Command
516+from lava.tool.errors import CommandError
517+from lava_tool.utils import has_command
518+
519+
520+class BaseCommand(Command):
521+ """Base command class for all lava commands."""
522+ def __init__(self, parser, args):
523+ super(BaseCommand, self).__init__(parser, args)
524+ self.config = InteractiveConfig(
525+ force_interactive=self.args.non_interactive)
526+
527+ @classmethod
528+ def register_arguments(cls, parser):
529+ super(BaseCommand, cls).register_arguments(parser)
530+ parser.add_argument("--non-interactive",
531+ action='store_false',
532+ help=("Do not ask for input parameters."))
533+
534+ @classmethod
535+ def can_edit_file(cls, conf_file):
536+ """Checks if a file can be opend in write mode.
537+
538+ :param conf_file: The path to the file.
539+ :return True if it is possible to write on the file, False otherwise.
540+ """
541+ can_edit = True
542+ try:
543+ fp = open(conf_file, "a")
544+ fp.close()
545+ except IOError:
546+ can_edit = False
547+ return can_edit
548+
549+ @classmethod
550+ def edit_file(cls, config_file):
551+ """Opens the specified file with the default file editor.
552+
553+ :param config_file: The file to edit.
554+ """
555+ editor = os.environ.get("EDITOR", None)
556+ if editor is None:
557+ if has_command("sensible-editor"):
558+ editor = "sensible-editor"
559+ elif has_command("xdg-open"):
560+ editor = "xdg-open"
561+ else:
562+ # We really do not know how to open a file.
563+ print >> sys.stdout, ("Cannot find an editor to open the "
564+ "file '{0}'.".format(config_file))
565+ print >> sys.stdout, ("Either set the 'EDITOR' environment "
566+ "variable, or install 'sensible-editor' "
567+ "or 'xdg-open'.")
568+ sys.exit(-1)
569+ try:
570+ subprocess.Popen([editor, config_file]).wait()
571+ except Exception:
572+ raise CommandError("Error opening the file '{0}' with the "
573+ "following editor: {1}.".format(config_file,
574+ editor))
575+
576+ @classmethod
577+ def run(cls, cmd_args):
578+ """Runs the supplied command args.
579+
580+ :param cmd_args: The command, and its optional arguments, to run.
581+ :return The command execution return code.
582+ """
583+ if not isinstance(cmd_args, list):
584+ cmd_args = list(cmd_args)
585+ try:
586+ return subprocess.check_call(cmd_args)
587+ except subprocess.CalledProcessError:
588+ raise CommandError("Error running the following command: "
589+ "{0}".format(" ".join(cmd_args)))
590
591=== added file 'lava/helper/dispatcher.py'
592--- lava/helper/dispatcher.py 1970-01-01 00:00:00 +0000
593+++ lava/helper/dispatcher.py 2013-06-20 08:10:33 +0000
594@@ -0,0 +1,110 @@
595+# Copyright (C) 2013 Linaro Limited
596+#
597+# Author: Milo Casagrande <milo.casagrande@linaro.org>
598+#
599+# This file is part of lava-tool.
600+#
601+# lava-tool is free software: you can redistribute it and/or modify
602+# it under the terms of the GNU Lesser General Public License version 3
603+# as published by the Free Software Foundation
604+#
605+# lava-tool is distributed in the hope that it will be useful,
606+# but WITHOUT ANY WARRANTY; without even the implied warranty of
607+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
608+# GNU General Public License for more details.
609+#
610+# You should have received a copy of the GNU Lesser General Public License
611+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
612+
613+"""Classes and functions to interact with the lava-dispatcher."""
614+
615+import random
616+import string
617+import os
618+
619+from lava.tool.errors import CommandError
620+
621+# Default devices path, has to be joined with the dispatcher path.
622+DEFAULT_DEVICES_PATH = "devices"
623+
624+
625+def get_dispatcher_paths():
626+ """Tries to get the dispatcher paths from lava-dispatcher.
627+
628+ :return A list of paths.
629+ """
630+ try:
631+ from lava_dispatcher.config import write_path
632+ return write_path()
633+ except ImportError:
634+ raise CommandError("Cannot find lava-dispatcher installation.")
635+
636+
637+def get_devices():
638+ """Gets the devices list from the dispatcher.
639+
640+ :return A list of DeviceConfig.
641+ """
642+ try:
643+ from lava_dispatcher.config import get_devices
644+ return get_devices()
645+ except ImportError:
646+ raise CommandError("Cannot find lava-dispatcher installation.")
647+
648+
649+def get_device_file(file_name):
650+ """Retrieves the config file name specified, if it exists.
651+
652+ :param file_name: The config file name to search.
653+ :return The path to the file, or None if it does not exist.
654+ """
655+ try:
656+ from lava_dispatcher.config import get_config_file
657+ return get_config_file(os.path.join(DEFAULT_DEVICES_PATH,
658+ file_name))
659+ except ImportError:
660+ raise CommandError("Cannot find lava-dispatcher installation.")
661+
662+
663+def choose_devices_path(paths):
664+ """Picks the first path that is writable by the user.
665+
666+ :param paths: A list of paths.
667+ :return The first path where it is possible to write.
668+ """
669+ valid_path = None
670+ for path in paths:
671+ path = os.path.join(path, DEFAULT_DEVICES_PATH)
672+ if os.path.exists(path):
673+ name = "".join(random.choice(string.ascii_letters)
674+ for x in range(6))
675+ test_file = os.path.join(path, name)
676+ try:
677+ fp = open(test_file, 'a')
678+ fp.close()
679+ except IOError:
680+ # Cannot write here.
681+ continue
682+ else:
683+ valid_path = path
684+ if os.path.isfile(test_file):
685+ os.unlink(test_file)
686+ break
687+ else:
688+ try:
689+ os.makedirs(path)
690+ except OSError:
691+ # Cannot write here either.
692+ continue
693+ else:
694+ valid_path = path
695+ break
696+ else:
697+ raise CommandError("Insufficient permissions to create new "
698+ "devices.")
699+ return valid_path
700+
701+
702+def get_devices_path():
703+ """Gets the path to the devices in the LAVA dispatcher."""
704+ return choose_devices_path(get_dispatcher_paths())
705
706=== added directory 'lava/helper/tests'
707=== added file 'lava/helper/tests/__init__.py'
708=== added file 'lava/helper/tests/helper_test.py'
709--- lava/helper/tests/helper_test.py 1970-01-01 00:00:00 +0000
710+++ lava/helper/tests/helper_test.py 2013-06-20 08:10:33 +0000
711@@ -0,0 +1,61 @@
712+# Copyright (C) 2013 Linaro Limited
713+#
714+# Author: Milo Casagrande <milo.casagrande@linaro.org>
715+#
716+# This file is part of lava-tool.
717+#
718+# lava-tool is free software: you can redistribute it and/or modify
719+# it under the terms of the GNU Lesser General Public License version 3
720+# as published by the Free Software Foundation
721+#
722+# lava-tool is distributed in the hope that it will be useful,
723+# but WITHOUT ANY WARRANTY; without even the implied warranty of
724+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
725+# GNU General Public License for more details.
726+#
727+# You should have received a copy of the GNU Lesser General Public License
728+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
729+
730+"""
731+A test helper class.
732+
733+Here we define a general test class and its own setUp and tearDown methods that
734+all other test classes can inherit from.
735+"""
736+
737+import os
738+import shutil
739+import sys
740+import tempfile
741+
742+from unittest import TestCase
743+from mock import MagicMock
744+
745+
746+class HelperTest(TestCase):
747+ """Helper test class that all tests under the lava package can inherit."""
748+ def setUp(self):
749+ self.original_stdout = sys.stdout
750+ sys.stdout = open("/dev/null", "w")
751+ self.original_stderr = sys.stderr
752+ sys.stderr = open("/dev/null", "w")
753+ self.original_stdin = sys.stdin
754+
755+ self.device = "a_fake_panda02"
756+
757+ self.temp_file = tempfile.NamedTemporaryFile(delete=False)
758+ self.temp_dir = tempfile.mkdtemp()
759+ self.parser = MagicMock()
760+ self.args = MagicMock()
761+ self.args.interactive = MagicMock(return_value=False)
762+ self.args.DEVICE = self.device
763+
764+ self.config = MagicMock()
765+ self.config.get = MagicMock(return_value=self.temp_dir)
766+
767+ def tearDown(self):
768+ sys.stdin = self.original_stdin
769+ sys.stdout = self.original_stdout
770+ sys.stderr = self.original_stderr
771+ shutil.rmtree(self.temp_dir)
772+ os.unlink(self.temp_file.name)
773
774=== added file 'lava/helper/tests/test_command.py'
775--- lava/helper/tests/test_command.py 1970-01-01 00:00:00 +0000
776+++ lava/helper/tests/test_command.py 2013-06-20 08:10:33 +0000
777@@ -0,0 +1,53 @@
778+# Copyright (C) 2013 Linaro Limited
779+#
780+# Author: Milo Casagrande <milo.casagrande@linaro.org>
781+#
782+# This file is part of lava-tool.
783+#
784+# lava-tool is free software: you can redistribute it and/or modify
785+# it under the terms of the GNU Lesser General Public License version 3
786+# as published by the Free Software Foundation
787+#
788+# lava-tool is distributed in the hope that it will be useful,
789+# but WITHOUT ANY WARRANTY; without even the implied warranty of
790+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
791+# GNU General Public License for more details.
792+#
793+# You should have received a copy of the GNU Lesser General Public License
794+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
795+
796+"""lava.herlp.command module tests."""
797+
798+from lava.helper.command import BaseCommand
799+from lava.helper.tests.helper_test import HelperTest
800+
801+
802+class BaseCommandTests(HelperTest):
803+
804+ def test_register_argument(self):
805+ # Make sure that the parser add_argument is called and we have the
806+ # correct argument.
807+ command = BaseCommand(self.parser, self.args)
808+ command.register_arguments(self.parser)
809+ name, args, kwargs = self.parser.method_calls[0]
810+ self.assertIn("--non-interactive", args)
811+
812+ def test_can_edit_file(self):
813+ # Tests the can_edit_file method of the config command.
814+ # This is to make sure the device config file is not erased when
815+ # checking if it is possible to open it.
816+ expected = ("hostname = a_fake_panda02\nconnection_command = \n"
817+ "device_type = panda\n")
818+
819+ command = BaseCommand(self.parser, self.args)
820+ conf_file = self.temp_file
821+
822+ with open(conf_file.name, "w") as f:
823+ f.write(expected)
824+
825+ self.assertTrue(command.can_edit_file(conf_file.name))
826+ obtained = ""
827+ with open(conf_file.name) as f:
828+ obtained = f.read()
829+
830+ self.assertEqual(expected, obtained)
831
832=== added file 'lava/helper/tests/test_dispatcher.py'
833--- lava/helper/tests/test_dispatcher.py 1970-01-01 00:00:00 +0000
834+++ lava/helper/tests/test_dispatcher.py 2013-06-20 08:10:33 +0000
835@@ -0,0 +1,52 @@
836+# Copyright (C) 2013 Linaro Limited
837+#
838+# Author: Milo Casagrande <milo.casagrande@linaro.org>
839+#
840+# This file is part of lava-tool.
841+#
842+# lava-tool is free software: you can redistribute it and/or modify
843+# it under the terms of the GNU Lesser General Public License version 3
844+# as published by the Free Software Foundation
845+#
846+# lava-tool is distributed in the hope that it will be useful,
847+# but WITHOUT ANY WARRANTY; without even the implied warranty of
848+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
849+# GNU General Public License for more details.
850+#
851+# You should have received a copy of the GNU Lesser General Public License
852+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
853+
854+"""lava.helper.dispatcher tests."""
855+
856+import os
857+from lava.tool.errors import CommandError
858+
859+from lava.helper.tests.helper_test import HelperTest
860+
861+from lava.helper.dispatcher import (
862+ choose_devices_path,
863+)
864+
865+
866+class DispatcherTests(HelperTest):
867+
868+ def test_choose_devices_path_0(self):
869+ # Tests that when passing more than one path, the first writable one
870+ # is returned.
871+ obtained = choose_devices_path(
872+ ["/", "/root", self.temp_dir, os.path.expanduser("~")])
873+ expected = os.path.join(self.temp_dir, "devices")
874+ self.assertEqual(expected, obtained)
875+
876+ def test_choose_devices_path_1(self):
877+ # Tests that when passing a path that is not writable, CommandError
878+ # is raised.
879+ self.assertRaises(CommandError, choose_devices_path,
880+ ["/", "/root", "/root/tmpdir"])
881+
882+ def test_choose_devices_path_2(self):
883+ # Tests that the correct path for devices is created on the filesystem.
884+ expected_path = os.path.join(self.temp_dir, "devices")
885+ obtained = choose_devices_path([self.temp_dir])
886+ self.assertEqual(expected_path, obtained)
887+ self.assertTrue(os.path.isdir(expected_path))
888
889=== modified file 'lava/job/__init__.py'
890--- lava/job/__init__.py 2013-05-28 22:08:12 +0000
891+++ lava/job/__init__.py 2013-06-20 08:10:33 +0000
892@@ -21,12 +21,13 @@
893
894 from lava.job.templates import Parameter
895
896+
897 class Job:
898-
899 def __init__(self, template):
900 self.data = deepcopy(template)
901
902 def fill_in(self, config):
903+
904 def insert_data(data):
905 if isinstance(data, dict):
906 keys = data.keys()
907@@ -44,4 +45,3 @@
908
909 def write(self, stream):
910 stream.write(json.dumps(self.data, indent=4))
911-
912
913=== modified file 'lava/job/commands.py'
914--- lava/job/commands.py 2013-06-03 18:06:49 +0000
915+++ lava/job/commands.py 2013-06-20 08:10:33 +0000
916@@ -16,40 +16,34 @@
917 # You should have received a copy of the GNU Lesser General Public License
918 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
919
920-from os.path import exists
921-
922-from lava.config import InteractiveConfig
923+"""
924+LAVA job commands.
925+"""
926+
927+import os
928+import sys
929+import xmlrpclib
930+
931+from lava.helper.command import BaseCommand
932+
933+from lava.config import Parameter
934 from lava.job import Job
935-from lava.job.templates import *
936-from lava.tool.command import Command, CommandGroup
937+from lava.job.templates import (
938+ BOOT_TEST,
939+)
940+from lava.tool.command import CommandGroup
941 from lava.tool.errors import CommandError
942-
943 from lava_tool.authtoken import AuthenticatingServerProxy, KeyringAuthBackend
944-import xmlrpclib
945+from lava_tool.utils import has_command
946+
947
948 class job(CommandGroup):
949- """
950- LAVA job file handling
951- """
952-
953+ """LAVA job file handling."""
954 namespace = 'lava.job.commands'
955
956-class BaseCommand(Command):
957-
958- def __init__(self, parser, args):
959- super(BaseCommand, self).__init__(parser, args)
960- self.config = InteractiveConfig(force_interactive=self.args.interactive)
961-
962- @classmethod
963- def register_arguments(cls, parser):
964- super(BaseCommand, cls).register_arguments(parser)
965- parser.add_argument(
966- "-i", "--interactive",
967- action='store_true',
968- help=("Forces asking for input parameters even if we already "
969- "have them cached."))
970
971 class new(BaseCommand):
972+ """Creates a new job file."""
973
974 @classmethod
975 def register_arguments(cls, parser):
976@@ -57,20 +51,22 @@
977 parser.add_argument("FILE", help=("Job file to be created."))
978
979 def invoke(self):
980- if exists(self.args.FILE):
981- raise CommandError('%s already exists' % self.args.FILE)
982+ if os.path.exists(self.args.FILE):
983+ raise CommandError('{0} already exists.'.format(self.args.FILE))
984
985- with open(self.args.FILE, 'w') as f:
986- job = Job(BOOT_TEST)
987- job.fill_in(self.config)
988- job.write(f)
989+ with open(self.args.FILE, 'w') as job_file:
990+ job_instance = Job(BOOT_TEST)
991+ job_instance.fill_in(self.config)
992+ job_instance.write(job_file)
993
994
995 class submit(BaseCommand):
996+ """Submits the specified job file."""
997+
998 @classmethod
999 def register_arguments(cls, parser):
1000 super(submit, cls).register_arguments(parser)
1001- parser.add_argument("FILE", help=("The job file to submit"))
1002+ parser.add_argument("FILE", help=("The job file to submit."))
1003
1004 def invoke(self):
1005 jobfile = self.args.FILE
1006@@ -85,10 +81,61 @@
1007 auth_backend=KeyringAuthBackend())
1008 try:
1009 job_id = server.scheduler.submit_job(jobdata)
1010- print "Job submitted with job ID %d" % job_id
1011- except xmlrpclib.Fault, e:
1012- raise CommandError(str(e))
1013+ print >> sys.stdout, "Job submitted with job ID {0}".format(job_id)
1014+ except xmlrpclib.Fault, exc:
1015+ raise CommandError(str(exc))
1016+
1017
1018 class run(BaseCommand):
1019+ """Runs the specified job file on the local dispatcher."""
1020+
1021+ @classmethod
1022+ def register_arguments(cls, parser):
1023+ super(run, cls).register_arguments(parser)
1024+ parser.add_argument("FILE", help=("The job file to submit."))
1025+
1026+ @classmethod
1027+ def _choose_device(cls, devices):
1028+ """Let the user choose the device to use.
1029+
1030+ :param devices: The list of available devices.
1031+ :return The selected device.
1032+ """
1033+ devices_len = len(devices)
1034+ output_list = []
1035+ for device, number in zip(devices, range(1, devices_len + 1)):
1036+ output_list.append("\t{0}. {1}\n".format(number, device.hostname))
1037+
1038+ print >> sys.stdout, ("More than one local device found. "
1039+ "Please choose one:\n")
1040+ print >> sys.stdout, "".join(output_list)
1041+
1042+ while True:
1043+ try:
1044+ user_input = raw_input("Device number to use: ").strip()
1045+
1046+ if user_input in [str(x) for x in range(1, devices_len + 1)]:
1047+ return devices[int(user_input) - 1].hostname
1048+ else:
1049+ continue
1050+ except EOFError:
1051+ user_input = None
1052+ except KeyboardInterrupt:
1053+ sys.exit(-1)
1054+
1055 def invoke(self):
1056- print("hello world")
1057+ if os.path.isfile(self.args.FILE):
1058+ if has_command("lava-dispatch"):
1059+ devices = self.get_devices()
1060+ if devices:
1061+ if len(devices) > 1:
1062+ device = self._choose_device(devices)
1063+ else:
1064+ device = devices[0].hostname
1065+ self.run(["lava-dispatch", "--target", device,
1066+ self.args.FILE])
1067+ else:
1068+ raise CommandError("Cannot find lava-dispatcher installation.")
1069+ else:
1070+ raise CommandError("The file '{0}' does not exists. or is not "
1071+ "a file.".format(self.args.FILE))
1072
1073=== modified file 'lava/job/tests/test_commands.py'
1074--- lava/job/tests/test_commands.py 2013-06-03 18:06:49 +0000
1075+++ lava/job/tests/test_commands.py 2013-06-20 08:10:33 +0000
1076@@ -20,74 +20,95 @@
1077 Unit tests for the commands classes
1078 """
1079
1080-from argparse import ArgumentParser
1081 import json
1082-from os import (
1083- makedirs,
1084- removedirs,
1085-)
1086-from os.path import(
1087- exists,
1088- join,
1089-)
1090-from shutil import(
1091- rmtree,
1092-)
1093-from tempfile import mkdtemp
1094-from unittest import TestCase
1095-
1096-from lava.config import NonInteractiveConfig
1097-from lava.job.commands import *
1098+import os
1099+
1100+from mock import MagicMock, patch
1101+
1102+from lava.config import NonInteractiveConfig, Parameter
1103+
1104+from lava.job.commands import (
1105+ new,
1106+ run,
1107+ submit,
1108+)
1109+
1110+from lava.helper.tests.helper_test import HelperTest
1111 from lava.tool.errors import CommandError
1112
1113-from mocker import Mocker
1114-
1115-def make_command(command, *args):
1116- parser = ArgumentParser(description="fake argument parser")
1117- command.register_arguments(parser)
1118- the_args = parser.parse_args(*args)
1119- cmd = command(parser, the_args)
1120- cmd.config = NonInteractiveConfig({ 'device_type': 'foo', 'prebuilt_image': 'bar' })
1121- return cmd
1122-
1123-class CommandTest(TestCase):
1124+
1125+class CommandTest(HelperTest):
1126
1127 def setUp(self):
1128- self.tmpdir = mkdtemp()
1129+ super(CommandTest, self).setUp()
1130+ self.args.FILE = self.temp_file.name
1131
1132- def tearDown(self):
1133- rmtree(self.tmpdir)
1134+ self.device_type = Parameter('device_type')
1135+ self.prebuilt_image = Parameter('prebuilt_image',
1136+ depends=self.device_type)
1137+ self.config = NonInteractiveConfig(
1138+ {'device_type': 'foo', 'prebuilt_image': 'bar'})
1139
1140 def tmp(self, filename):
1141- return join(self.tmpdir, filename)
1142+ """Returns a path to a non existent file.
1143+
1144+ :param filename: The name the file should have.
1145+ :return A path.
1146+ """
1147+ return os.path.join(self.temp_dir, filename)
1148+
1149
1150 class JobNewTest(CommandTest):
1151
1152+ def setUp(self):
1153+ super(JobNewTest, self).setUp()
1154+ self.args.FILE = self.tmp("new_file.json")
1155+ self.new_command = new(self.parser, self.args)
1156+ self.new_command.config = self.config
1157+
1158+ def tearDown(self):
1159+ super(JobNewTest, self).tearDown()
1160+ if os.path.exists(self.args.FILE):
1161+ os.unlink(self.args.FILE)
1162+
1163 def test_create_new_file(self):
1164- f = self.tmp('file.json')
1165- command = make_command(new, [f])
1166- command.invoke()
1167- self.assertTrue(exists(f))
1168+ self.new_command.invoke()
1169+ self.assertTrue(os.path.exists(self.args.FILE))
1170
1171 def test_fills_in_template_parameters(self):
1172- f = self.tmp('myjob.json')
1173- command = make_command(new, [f])
1174- command.invoke()
1175+ self.new_command.invoke()
1176
1177- data = json.loads(open(f).read())
1178+ data = json.loads(open(self.args.FILE).read())
1179 self.assertEqual(data['device_type'], 'foo')
1180
1181- def test_wont_overwriteexisting_file(self):
1182- existing = self.tmp('existing.json')
1183- with open(existing, 'w') as f:
1184+ def test_wont_overwrite_existing_file(self):
1185+ with open(self.args.FILE, 'w') as f:
1186 f.write("CONTENTS")
1187- command = make_command(new, [existing])
1188- with self.assertRaises(CommandError):
1189- command.invoke()
1190- self.assertEqual("CONTENTS", open(existing).read())
1191+
1192+ self.assertRaises(CommandError, self.new_command.invoke)
1193+ self.assertEqual("CONTENTS", open(self.args.FILE).read())
1194+
1195
1196 class JobSubmitTest(CommandTest):
1197
1198 def test_receives_job_file_in_cmdline(self):
1199- cmd = make_command(new, ['FOO.json'])
1200- self.assertEqual('FOO.json', cmd.args.FILE)
1201+ command = submit(self.parser, self.args)
1202+ command.register_arguments(self.parser)
1203+ name, args, kwargs = self.parser.method_calls[1]
1204+ self.assertIn("FILE", args)
1205+
1206+
1207+class JobRunTest(CommandTest):
1208+
1209+ def test_invoke_raises_0(self):
1210+ # Users passes a non existing job file to the run command.
1211+ self.args.FILE = self.tmp("test_invoke_raises_0.json")
1212+ command = run(self.parser, self.args)
1213+ self.assertRaises(CommandError, command.invoke)
1214+
1215+ @patch("lava.job.commands.has_command", new=MagicMock(return_value=False))
1216+ def test_invoke_raises_1(self):
1217+ # Users passes a valid file to the run command, but she does not have
1218+ # the dispatcher installed.
1219+ command = run(self.parser, self.args)
1220+ self.assertRaises(CommandError, command.invoke)
1221
1222=== modified file 'lava_tool/tests/__init__.py'
1223--- lava_tool/tests/__init__.py 2013-06-20 08:10:33 +0000
1224+++ lava_tool/tests/__init__.py 2013-06-20 08:10:33 +0000
1225@@ -38,11 +38,14 @@
1226 'lava_tool.tests.test_authtoken',
1227 'lava_tool.tests.test_auth_commands',
1228 'lava_tool.tests.test_commands',
1229+ 'lava_tool.tests.test_utils',
1230 'lava_dashboard_tool.tests.test_commands',
1231 'lava.job.tests.test_job',
1232 'lava.job.tests.test_commands',
1233 'lava.device.tests.test_device',
1234 'lava.device.tests.test_commands',
1235+ 'lava.helper.tests.test_command',
1236+ 'lava.helper.tests.test_dispatcher',
1237 ]
1238
1239
1240
1241=== added file 'lava_tool/tests/test_utils.py'
1242--- lava_tool/tests/test_utils.py 1970-01-01 00:00:00 +0000
1243+++ lava_tool/tests/test_utils.py 2013-06-20 08:10:33 +0000
1244@@ -0,0 +1,41 @@
1245+# Copyright (C) 2013 Linaro Limited
1246+#
1247+# Author: Milo Casagrande <milo.casagrande@linaro.org>
1248+#
1249+# This file is part of lava-tool.
1250+#
1251+# lava-tool is free software: you can redistribute it and/or modify
1252+# it under the terms of the GNU Lesser General Public License version 3
1253+# as published by the Free Software Foundation
1254+#
1255+# lava-tool is distributed in the hope that it will be useful,
1256+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1257+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1258+# GNU General Public License for more details.
1259+#
1260+# You should have received a copy of the GNU Lesser General Public License
1261+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
1262+
1263+"""lava_tool.utils tests."""
1264+
1265+import subprocess
1266+
1267+from unittest import TestCase
1268+from mock import patch
1269+
1270+from lava_tool.utils import has_command
1271+
1272+
1273+class UtilTests(TestCase):
1274+
1275+ @patch("lava_tool.utils.subprocess.check_call")
1276+ def test_has_command_0(self, mocked_check_call):
1277+ # Make sure we raise an exception when the subprocess is called.
1278+ mocked_check_call.side_effect = subprocess.CalledProcessError(0, "")
1279+ self.assertFalse(has_command(""))
1280+
1281+ @patch("lava_tool.utils.subprocess.check_call")
1282+ def test_has_command_1(self, mocked_check_call):
1283+ # Check that a "command" exists. The call to subprocess is mocked.
1284+ mocked_check_call.return_value = 0
1285+ self.assertTrue(has_command(""))
1286
1287=== modified file 'lava_tool/utils.py'
1288--- lava_tool/utils.py 2013-06-20 08:10:33 +0000
1289+++ lava_tool/utils.py 2013-06-20 08:10:33 +0000
1290@@ -16,6 +16,7 @@
1291 # You should have received a copy of the GNU Lesser General Public License
1292 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
1293
1294+import os
1295 import subprocess
1296
1297
1298@@ -27,7 +28,7 @@
1299 command_available = True
1300 try:
1301 subprocess.check_call(["which", command],
1302- stdout=open('/dev/null', 'w'))
1303+ stdout=open(os.path.devnull, 'w'))
1304 except subprocess.CalledProcessError:
1305 command_available = False
1306 return command_available

Subscribers

People subscribed via source and target branches