Merge lp:~milo/lava-tool/lava-168 into lp:~linaro-validation/lava-tool/trunk
- lava-168
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Antonio Terceiro | Needs Fixing | ||
Linaro Automation & Validation | Pending | ||
Review via email:
|
Commit message
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]
- 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.

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