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

Proposed by Milo Casagrande
Status: Merged
Merged at revision: 188
Proposed branch: lp:~milo/lava-tool/lava-169
Merge into: lp:~linaro-validation/lava-tool/trunk
Prerequisite: lp:~milo/lava-tool/lava-165
Diff against target: 1110 lines (+461/-129)
21 files modified
entry_points.ini (+9/-0)
lava/commands.py (+34/-47)
lava/config.py (+13/-3)
lava/device/__init__.py (+7/-3)
lava/device/templates.py (+12/-0)
lava/helper/command.py (+80/-16)
lava/job/__init__.py (+12/-0)
lava/job/commands.py (+21/-0)
lava/job/templates.py (+6/-6)
lava/job/tests/test_commands.py (+5/-4)
lava/parameter.py (+0/-6)
lava/script/__init__.py (+51/-0)
lava/script/commands.py (+115/-0)
lava/testdef/__init__.py (+24/-2)
lava/testdef/commands.py (+40/-8)
lava/testdef/templates.py (+4/-27)
lava/testdef/tests/test_commands.py (+3/-3)
lava/tests/test_commands.py (+0/-1)
lava/tests/test_config.py (+9/-0)
lava/tests/test_parameter.py (+2/-1)
lava_tool/utils.py (+14/-2)
To merge this branch: bzr merge lp:~milo/lava-tool/lava-169
Reviewer Review Type Date Requested Status
Antonio Terceiro Approve
Linaro Validation Team Pending
Review via email: mp+175622@code.launchpad.net

Description of the change

This is the final step in completing card LAVA-15.
Here there is the "status" command that polls a LAVA server and prints information about the provided job id.

The command can be invoked as:

lava status [ID]
lava job status [ID]

The latter is the actual command and the former just calls it.
If ID is not provided, it will read the stored job IDs from the .lavaconfig file, and ask the user which job to choose.

To post a comment you must log in.
lp:~milo/lava-tool/lava-169 updated
412. By Milo Casagrande

Added test for public Config method.

413. By Milo Casagrande

Fixed missing argument in method call.

414. By Milo Casagrande

Removed a job id from the config file once it is not running anymore.

415. By Milo Casagrande

Fixed logic when removing job ids.

416. By Milo Casagrande

Merged from parent branch.

417. By Milo Casagrande

Merged from parent branch.

418. By Milo Casagrande

Fixed logic for URL creation.

419. By Milo Casagrande

Fixed problem with value read from config and not deserialized.

420. By Milo Casagrande

Merged from parent.

421. By Milo Casagrande

Fixed test.

Revision history for this message
Antonio Terceiro (terceiro) wrote :
Download full text (11.6 KiB)

Another great job here, thanks! I have some comments below.

 review needs-fixing

On Thu, Jul 18, 2013 at 04:04:26PM -0000, Milo Casagrande wrote:
> Milo Casagrande has proposed merging lp:~milo/lava-tool/lava-169 into lp:lava-tool with lp:~milo/lava-tool/lava-165 as a prerequisite.
>
> Requested reviews:
> Antonio Terceiro (terceiro)
> Linaro Validation Team (linaro-validation)
>
> For more details, see:
> https://code.launchpad.net/~milo/lava-tool/lava-169/+merge/175622
>
> This is the final step in completing card LAVA-15.
> Here there is the "status" command that polls a LAVA server and prints information about the provided job id.
>
> The command can be invoked as:
>
> lava status [ID]
> lava job status [ID]
>
> The latter is the actual command and the former just calls it.

I'm not sure about the first form. `lava status` might be too generic for
people to memorize that it should be use to get status of jobs. I think we will
be able to do a better UI review after we get all the code merged in though.

> If ID is not provided, it will read the stored job IDs from the .lavaconfig file, and ask the user which job to choose.

(what follows is an updated diff from a few hours ago, not the original diff
from when the MP was created)

> === modified file 'entry_points.ini'
> --- entry_points.ini 2013-07-24 14:41:29 +0000
> +++ entry_points.ini 2013-07-24 14:41:29 +0000
> @@ -13,6 +13,7 @@
> init = lava.commands:init
> submit = lava.commands:submit
> run = lava.commands:run
> +status = lava.commands:status

you can just reference lava.job.commands:status here, you don't need to write a
separate command.

BTW this also applies to a similar comment I did in the previous MP, about
cascading commands: if the arguments are the same, you can just point to the
original command from another scope in entry_points.ini and you don't have to
wrap one command inside another

> update = lava.commands:update
>
> [lava_tool.commands]
> @@ -77,6 +78,7 @@
> [lava.job.commands]
> new = lava.job.commands:new
> submit = lava.job.commands:submit
> +status = lava.job.commands:status
> run = lava.job.commands:run
>
> [lava.device.commands]
>
> === modified file 'lava/commands.py'
> --- lava/commands.py 2013-07-24 14:41:29 +0000
> +++ lava/commands.py 2013-07-24 14:41:29 +0000
> @@ -251,6 +251,29 @@
> submit_cmd.invoke()
>
>
> +class status(BaseCommand):
> +
> + """Checks the status of a job."""
> +
> + @classmethod
> + def register_arguments(cls, parser):
> + super(status, cls).register_arguments(parser)
> + parser.add_argument("JOB_ID",
> + help=("Prints a job status information."),
> + nargs="?",
> + default="-1")
> +
> + def invoke(self):
> + """Runs the job.status command."""
> + from lava.job.commands import status as job_status
> +
> + args = copy.copy(self.args)
> + args.JOB_ID = self.args.JOB_ID
> +
> + status_cmd = job_status(self.parser, args)
> + status_cmd.invoke()
> +
> +
> class update(BaseCommand):
> """Updates a job file with the correct data."""

ditto - this s...

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

Merged from parent branch.

423. By Milo Casagrande

Merged from parent branch.

424. By Milo Casagrande

Merged from parent branch, fixed conflicts.

425. By Milo Casagrande

Merged from parent brach.

426. By Milo Casagrande

Merged from parent branch.

427. By Milo Casagrande

Merged from parent branch.

428. By Milo Casagrande

Merged from parent.

Revision history for this message
Milo Casagrande (milo) wrote :

Hi Antonio,

I updated the code.
I also pushed here the "script" commands. Please, take a look at them.

The big part of the refactoring happened on the lava-165 merge.

On Wed, Jul 24, 2013 at 10:46 PM, Antonio Terceiro
<email address hidden> wrote:
>
> I'm not sure about the first form. `lava status` might be too generic for
> people to memorize that it should be use to get status of jobs. I think we will
> be able to do a better UI review after we get all the code merged in though.

Indeed. Also, it would be better to do it with someone that will
actually use the tool.

>> === modified file 'entry_points.ini'
>> --- entry_points.ini 2013-07-24 14:41:29 +0000
>> +++ entry_points.ini 2013-07-24 14:41:29 +0000
>> @@ -13,6 +13,7 @@
>> init = lava.commands:init
>> submit = lava.commands:submit
>> run = lava.commands:run
>> +status = lava.commands:status
>
> you can just reference lava.job.commands:status here, you don't need to write a
> separate command.
>
> BTW this also applies to a similar comment I did in the previous MP, about
> cascading commands: if the arguments are the same, you can just point to the
> original command from another scope in entry_points.ini and you don't have to
> wrap one command inside another

There is actually only the status one, the run and submit are all a
little bit different, in particular when you start from a testdef or a
script file.

> these constants are used in very few places so I think it's better to just use
> the literals directly.

Cleared them out.

> I would use default=None instead, since that -1 is not even a proper number.

Fixed.

> instead of printing the Bundle SHA1 hash, it would be more useful to provide a
> URL to that bundle in the dashboard. Even more useful would be to present the
> results of the tests ... I don't know exactly yet how to do any of those,
> though - presenting the URL should be a lot easier in the short term.

> maybe we could provide a way of listing the latest N jobs submitted by the
> user, together with their results. This would probably require some work on the
> server API side.

I actually removed this from both lava-165 and lava-169.
I want to address that with another merge proposal, but after
everything has been merged.

Will file a bug to keep track of it, and fix the config/cache
"problem" via pyxdg once this code is landed.

Also for the "bundle" one: I prefer to not fix it now, but keep track
of it with a bug. It is tied to the config/cache "problem" since we
need to store information of that job to make the URL (we need to keep
track of the server URL, the stream, and those might change even from
job to job).

--
Milo Casagrande | Automation Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs

lp:~milo/lava-tool/lava-169 updated
429. By Milo Casagrande

Merged from parent.

430. By Milo Casagrande

Added script commands, completed testdef ones.

    * Refactored and fixes some of the other methods.

431. By Milo Casagrande

Added missing entry point commands.

432. By Milo Casagrande

Removed TODO, unused import.

433. By Milo Casagrande

Fixed string.

434. By Milo Casagrande

Fixed the status command.

435. By Milo Casagrande

Fixed script entry point.

436. By Milo Casagrande

Fixed import.

437. By Milo Casagrande

Fixed another import.

438. By Milo Casagrande

Fixed template.

439. By Milo Casagrande

Fixed error: used def instead of class.

440. By Milo Casagrande

PEP8 and pylint fixes.

441. By Milo Casagrande

Added qemu device type.

442. By Milo Casagrande

Completed script and testdef run and submit commands.

    * Refactored some code.

443. By Milo Casagrande

Removed refactored method, added missing edit_file call.

444. By Milo Casagrande

Fixed commands, added missing functionalities.

445. By Milo Casagrande

Fixed method call.

446. By Milo Casagrande

Fixed problem with config saving.

447. By Milo Casagrande

Fixed check on non existing files.

448. By Milo Casagrande

Fixed error message.

Revision history for this message
Antonio Terceiro (terceiro) wrote :

The changes you did seem nice. I don't have any specific remarks.

Let's go ahead and merge this, it's getting too big. Then we
start iterating on the code with all the features in.

 review approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'entry_points.ini'
2--- entry_points.ini 2013-07-26 14:14:25 +0000
3+++ entry_points.ini 2013-07-26 14:14:25 +0000
4@@ -13,7 +13,9 @@
5 init = lava.commands:init
6 submit = lava.commands:submit
7 run = lava.commands:run
8+status = lava.job.commands:status
9 update = lava.commands:update
10+script = lava.script.commands:script
11
12 [lava_tool.commands]
13 help = lava.tool.commands.help:help
14@@ -77,6 +79,7 @@
15 [lava.job.commands]
16 new = lava.job.commands:new
17 submit = lava.job.commands:submit
18+status = lava.job.commands:status
19 run = lava.job.commands:run
20
21 [lava.device.commands]
22@@ -86,3 +89,9 @@
23
24 [lava.testdef.commands]
25 new = lava.testdef.commands:new
26+run = lava.testdef.commands:run
27+submit = lava.testdef.commands:submit
28+
29+[lava.script.commands]
30+run = lava.script.commands:run
31+submit = lava.script.commands:submit
32
33=== modified file 'lava/commands.py'
34--- lava/commands.py 2013-07-26 14:14:25 +0000
35+++ lava/commands.py 2013-07-26 14:14:25 +0000
36@@ -46,7 +46,6 @@
37 import copy
38 import json
39 import os
40-import stat
41 import sys
42
43 from lava.helper.command import BaseCommand
44@@ -63,16 +62,16 @@
45 from lava.parameter import (
46 Parameter,
47 )
48-from lava.testdef.templates import (
49- DEFAULT_TESTDEF_FILE,
50- DEFAULT_TESTDEF_SCRIPT,
51- DEFAULT_TESTDEF_SCRIPT_CONTENT,
52+from lava.testdef import (
53+ DEFAULT_TESTDEF_FILENAME,
54 )
55 from lava.tool.errors import CommandError
56 from lava_tool.utils import (
57+ base64_encode,
58+ create_dir,
59+ create_tar,
60 edit_file,
61 retrieve_file,
62- create_dir,
63 write_file,
64 )
65
66@@ -110,15 +109,17 @@
67 "file.".format(self.args.DIR))
68
69 create_dir(full_path)
70-
71 data = self._update_data()
72
73+ # Create the directory that will contain the test definition and
74+ # shell script.
75 test_path = create_dir(full_path, TESTS_DIR)
76- # TODO
77- self._create_script(test_path)
78+ shell_script = self.create_shell_script(test_path)
79+ # Let the user modify the file.
80+ edit_file(shell_script)
81
82 testdef_file = self.create_test_definition(
83- os.path.join(test_path, DEFAULT_TESTDEF_FILE))
84+ os.path.join(test_path, DEFAULT_TESTDEF_FILENAME))
85
86 job = data[JOBFILE_ID]
87 self.create_tar_repo_job(
88@@ -137,25 +138,6 @@
89
90 return data
91
92- def _create_script(self, test_path):
93- # This is the default script file as defined in the testdef template.
94- default_script = os.path.join(test_path, DEFAULT_TESTDEF_SCRIPT)
95-
96- if not os.path.isfile(default_script):
97- # We do not have the default testdef script. Create it, but
98- # remind the user to update it.
99- print >> sys.stdout, ("\nCreating default test script "
100- "'{0}'.".format(DEFAULT_TESTDEF_SCRIPT))
101-
102- write_file(default_script, DEFAULT_TESTDEF_SCRIPT_CONTENT)
103-
104- # Prompt the user to write the script file.
105- edit_file(default_script)
106-
107- # Make sure the script is executable.
108- os.chmod(default_script,
109- stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
110-
111
112 class run(BaseCommand):
113 """Runs a job on the local dispatcher."""
114@@ -194,7 +176,7 @@
115
116 def invoke(self):
117 full_path = os.path.abspath(self.args.JOB)
118- job_file = self.retrieve_file(full_path, JOB_FILE_EXTENSIONS)
119+ job_file = retrieve_file(full_path, JOB_FILE_EXTENSIONS)
120
121 super(submit, self).submit(job_file)
122
123@@ -219,22 +201,27 @@
124 tests_dir = os.path.join(job_dir, TESTS_DIR)
125
126 if os.path.isdir(tests_dir):
127- # TODO
128- encoded_tests = None
129-
130- json_data = None
131- with open(job_file, "r") as json_file:
132- try:
133- json_data = json.load(json_file)
134- set_value(
135- json_data, LAVA_TEST_SHELL_TAR_REPO_KEY, encoded_tests)
136- except Exception:
137- raise CommandError("Cannot read job file '{0}'.".format(
138- job_file))
139-
140- content = json.dumps(json_data, indent=4)
141- write_file(job_file, content)
142-
143- print >> sys.stdout, "Job definition updated."
144+ tar_repo = None
145+ try:
146+ tar_repo = create_tar(tests_dir)
147+ encoded_tests = base64_encode(tar_repo)
148+
149+ json_data = None
150+ with open(job_file, "r") as json_file:
151+ try:
152+ json_data = json.load(json_file)
153+ set_value(json_data, LAVA_TEST_SHELL_TAR_REPO_KEY,
154+ encoded_tests)
155+ except Exception:
156+ raise CommandError("Cannot read job file "
157+ "'{0}'.".format(job_file))
158+
159+ content = json.dumps(json_data, indent=4)
160+ write_file(job_file, content)
161+
162+ print >> sys.stdout, "Job definition updated."
163+ finally:
164+ if tar_repo and os.path.isfile(tar_repo):
165+ os.unlink(tar_repo)
166 else:
167 raise CommandError("Cannot find tests directory.")
168
169=== modified file 'lava/config.py'
170--- lava/config.py 2013-07-26 14:14:25 +0000
171+++ lava/config.py 2013-07-26 14:14:25 +0000
172@@ -33,7 +33,6 @@
173 from lava.parameter import Parameter
174 from lava.tool.errors import CommandError
175
176-
177 __all__ = ['Config', 'InteractiveConfig']
178
179 # Store for function calls to be made at exit time.
180@@ -55,6 +54,7 @@
181 call()
182 atexit.register(_run_at_exit)
183
184+
185 class Config(object):
186 """A generic config object."""
187
188@@ -119,6 +119,12 @@
189 value = self._get_from_backend(parameter, section)
190 return value
191
192+ def get_from_backend(self, parameter, section=None):
193+ """Gets a configuration parameter directly from the config file."""
194+ if not section:
195+ section = self._calculate_config_section(parameter)
196+ return self._get_from_backend(parameter, section)
197+
198 def _get_from_backend(self, parameter, section):
199 """Gets the parameter value from the config backend.
200
201@@ -200,8 +206,12 @@
202
203 def save(self):
204 """Saves the config to file."""
205- with open(self.config_file, "w") as write_file:
206- self.config_backend.write(write_file)
207+ # Since we lazy load the config_backend property, this check is needed
208+ # when a user enters a wrong command or it will overwrite the 'config'
209+ # file with empty contents.
210+ if self._config_backend:
211+ with open(self.config_file, "w") as write_file:
212+ self.config_backend.write(write_file)
213
214
215 class InteractiveConfig(Config):
216
217=== modified file 'lava/device/__init__.py'
218--- lava/device/__init__.py 2013-07-26 14:14:25 +0000
219+++ lava/device/__init__.py 2013-07-26 14:14:25 +0000
220@@ -16,6 +16,8 @@
221 # You should have received a copy of the GNU Lesser General Public License
222 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
223
224+"""Device class."""
225+
226 import re
227
228 from copy import deepcopy
229@@ -46,9 +48,11 @@
230
231
232 class Device(object):
233+
234 """A generic device."""
235- def __init__(self, template, hostname=None):
236- self.data = deepcopy(template)
237+
238+ def __init__(self, data, hostname=None):
239+ self.data = deepcopy(data)
240 self.hostname = hostname
241
242 def write(self, conf_file):
243@@ -86,7 +90,7 @@
244 :return A Device instance.
245 """
246 instance = Device(DEFAULT_TEMPLATE, hostname=name)
247- for known_dev, (matcher, dev_template) in KNOWN_DEVICES.iteritems():
248+ for _, (matcher, dev_template) in KNOWN_DEVICES.iteritems():
249 if matcher.match(name):
250 instance = Device(dev_template, hostname=name)
251 break
252
253=== modified file 'lava/device/templates.py'
254--- lava/device/templates.py 2013-07-26 14:14:25 +0000
255+++ lava/device/templates.py 2013-07-26 14:14:25 +0000
256@@ -55,6 +55,13 @@
257 VEXPRESS_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER)
258 VEXPRESS_CONNECTION_COMMAND.depends = VEXPRESS_DEVICE_TYPE
259
260+QEMU_DEVICE_TYPE = copy(DEVICE_TYPE_PARAMETER)
261+QEMU_DEVICE_TYPE.value = "qemu"
262+QEMU_DEVICE_TYPE.asked = True
263+
264+QEMU_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER)
265+QEMU_CONNECTION_COMMAND.depends = QEMU_DEVICE_TYPE
266+
267 # Dictionary with templates of known devices.
268 KNOWN_TEMPLATES = {
269 'panda': {
270@@ -67,4 +74,9 @@
271 'device_type': VEXPRESS_DEVICE_TYPE,
272 'connection_command': VEXPRESS_CONNECTION_COMMAND,
273 },
274+ 'qemu': {
275+ 'hostname': HOSTNAME_PARAMETER,
276+ 'device_type': QEMU_DEVICE_TYPE,
277+ 'connection_command': QEMU_CONNECTION_COMMAND,
278+ }
279 }
280
281=== modified file 'lava/helper/command.py'
282--- lava/helper/command.py 2013-07-26 14:14:25 +0000
283+++ lava/helper/command.py 2013-07-26 14:14:25 +0000
284@@ -24,10 +24,25 @@
285
286 from lava.config import InteractiveConfig
287 from lava.helper.dispatcher import get_devices
288+from lava.job import Job
289+from lava.job.templates import (
290+ LAVA_TEST_SHELL_TAR_REPO,
291+ LAVA_TEST_SHELL_TAR_REPO_KEY,
292+ LAVA_TEST_SHELL_TESDEF_KEY,
293+)
294 from lava.parameter import (
295 Parameter,
296 SingleChoiceParameter,
297 )
298+from lava.script import (
299+ ShellScript,
300+ DEFAULT_TESTDEF_SCRIPT,
301+)
302+from lava.testdef import TestDefinition
303+from lava.testdef.templates import (
304+ TESTDEF_STEPS_KEY,
305+ TESTDEF_TEMPLATE,
306+)
307 from lava.tool.command import Command
308 from lava.tool.errors import CommandError
309 from lava_tool.authtoken import (
310@@ -35,22 +50,13 @@
311 KeyringAuthBackend
312 )
313 from lava_tool.utils import (
314+ base64_encode,
315+ create_tar,
316 has_command,
317+ to_list,
318 verify_and_create_url,
319- create_tar,
320- base64_encode,
321-)
322-from lava.job import Job
323-from lava.job.templates import (
324- LAVA_TEST_SHELL_TAR_REPO,
325- LAVA_TEST_SHELL_TAR_REPO_KEY,
326- LAVA_TEST_SHELL_TESDEF_KEY,
327 )
328
329-from lava.testdef import TestDefinition
330-from lava.testdef.templates import (
331- TESTDEF_TEMPLATE,
332-)
333 CONFIG = InteractiveConfig()
334
335
336@@ -66,7 +72,7 @@
337 @classmethod
338 def register_arguments(cls, parser):
339 super(BaseCommand, cls).register_arguments(parser)
340- parser.add_argument("--non-interactive",
341+ parser.add_argument("--non-interactive", "-n",
342 action='store_false',
343 help=("Do not ask for input parameters."))
344
345@@ -76,6 +82,8 @@
346 It will ask the user the necessary parameters to establish the
347 connection.
348 """
349+ print >> sys.stdout, "\nServer connection parameters:"
350+
351 server_name_parameter = Parameter("server")
352 rpc_endpoint_parameter = Parameter("rpc_endpoint",
353 depends=server_name_parameter)
354@@ -134,6 +142,26 @@
355 raise CommandError("Job file '{0}' does not exists, or it is not "
356 "a file.".format(job_file))
357
358+ def status(self, job_id):
359+ """Retrieves the status of a LAVA job.
360+
361+ :param job_id: The ID of the job to look up.
362+ """
363+ job_id = str(job_id)
364+
365+ try:
366+ server = self.authenticated_server()
367+ job_status = server.scheduler.job_status(job_id)
368+
369+ status = job_status["job_status"].lower()
370+ bundle = job_status["bundle_sha1"]
371+
372+ print >> sys.stdout, "\nJob id: {0}".format(job_id)
373+ print >> sys.stdout, "Status: {0}".format(status)
374+ print >> sys.stdout, "Bundle: {0}".format(bundle)
375+ except xmlrpclib.Fault, exc:
376+ raise CommandError(str(exc))
377+
378 def create_tar_repo_job(self, job_file, testdef_file, tar_content):
379 """Creates a job file based on the tar-repo template.
380
381@@ -144,6 +172,9 @@
382 :param tar_content: What should go into the tarball repository.
383 :return The path of the job file created.
384 """
385+
386+ print >> sys.stdout, "\nCreating job file..."
387+
388 try:
389 tar_repo = create_tar(tar_content)
390
391@@ -157,22 +188,55 @@
392
393 job_instance.write()
394
395+ basename = os.path.basename(job_instance.file_name)
396+ print >> sys.stdout, ("\nCreated job file "
397+ "'{0}'.".format(basename))
398+
399 return job_instance.file_name
400 finally:
401 if os.path.isfile(tar_repo):
402 os.unlink(tar_repo)
403
404- def create_test_definition(self, testdef_file, template=TESTDEF_TEMPLATE):
405+ def create_test_definition(self, testdef_file, template=TESTDEF_TEMPLATE,
406+ steps=None):
407 """Creates a test definition YAML file.
408
409 :param testdef_file: The file to create.
410 :return The path of the file created.
411 """
412+
413+ print >> sys.stdout, "\nCreating test definition file..."
414+
415 testdef = TestDefinition(template, testdef_file)
416+ if steps:
417+ steps = to_list(steps)
418+ testdef.set(TESTDEF_STEPS_KEY, steps)
419 testdef.update(self.config)
420 testdef.write()
421
422- print >> sys.stdout, ("Create test definition "
423- "'{0}'.".format(testdef.file_name))
424+ basename = os.path.basename(testdef.file_name)
425+ print >> sys.stdout, ("\nCreated test definition "
426+ "'{0}'.".format(basename))
427
428 return testdef.file_name
429+
430+ def create_shell_script(self, test_path,
431+ script_name=DEFAULT_TESTDEF_SCRIPT):
432+ """Creates a shell script with some default content.
433+
434+ :param test_path: The directory where to create the script.
435+ :param script_name: The name of the script.
436+ :return The full path to the script file.
437+ """
438+ default_script = os.path.join(test_path, script_name)
439+
440+ if not os.path.isfile(default_script):
441+ print >> sys.stdout, "Creating shell script..."
442+
443+ shell_script = ShellScript(default_script)
444+ shell_script.write()
445+
446+ print >> sys.stdout, ("\nCreated shell script "
447+ "'{0}'.".format(script_name))
448+
449+ return default_script
450
451=== modified file 'lava/job/__init__.py'
452--- lava/job/__init__.py 2013-07-26 14:14:25 +0000
453+++ lava/job/__init__.py 2013-07-26 14:14:25 +0000
454@@ -16,6 +16,8 @@
455 # You should have received a copy of the GNU Lesser General Public License
456 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
457
458+"""Job class."""
459+
460 import json
461
462 from copy import deepcopy
463@@ -30,6 +32,8 @@
464 write_file
465 )
466
467+# A default name for job files.
468+DEFAULT_JOB_FILENAME = "lava-tool-job.json"
469 # Default job file extension.
470 DEFAULT_JOB_EXTENSION = "json"
471 # Possible extension for a job file.
472@@ -37,6 +41,14 @@
473
474
475 class Job(object):
476+
477+ """A Job object.
478+
479+ This class should be used to create new job files. The initialization
480+ enforces a default file name extension, and makes sure that the file is
481+ not already present on the file system.
482+ """
483+
484 def __init__(self, data, file_name):
485 self.file_name = verify_file_extension(file_name,
486 DEFAULT_JOB_EXTENSION,
487
488=== modified file 'lava/job/commands.py'
489--- lava/job/commands.py 2013-07-26 14:14:25 +0000
490+++ lava/job/commands.py 2013-07-26 14:14:25 +0000
491@@ -29,6 +29,7 @@
492 JOB_TYPES,
493 )
494 from lava.tool.command import CommandGroup
495+from lava.tool.errors import CommandError
496
497
498 class job(CommandGroup):
499@@ -84,3 +85,23 @@
500
501 def invoke(self):
502 super(run, self).run(self.args.FILE)
503+
504+
505+class status(BaseCommand):
506+
507+ """Retrieves the status of a job."""
508+
509+ @classmethod
510+ def register_arguments(cls, parser):
511+ super(status, cls).register_arguments(parser)
512+ parser.add_argument("JOB_ID",
513+ help=("Prints status information about the "
514+ "provided job id."),
515+ nargs="?",
516+ default=None)
517+
518+ def invoke(self):
519+ if self.args.JOB_ID:
520+ super(status, self).status(self.args.JOB_ID)
521+ else:
522+ raise CommandError("It is necessary to specify a job id.")
523
524=== modified file 'lava/job/templates.py'
525--- lava/job/templates.py 2013-07-26 14:14:25 +0000
526+++ lava/job/templates.py 2013-07-26 14:14:25 +0000
527@@ -71,7 +71,7 @@
528 },
529 {
530 "command": "submit_results",
531- "parameters" : {
532+ "parameters": {
533 "stream": STREAM_PARAMETER,
534 "server": SERVER_PARAMETER
535 }
536@@ -97,16 +97,16 @@
537 "parameters": {
538 "timeout": 1800,
539 "testdef_repos": [
540- {
541- LAVA_TEST_SHELL_TESDEF_KEY: None,
542- LAVA_TEST_SHELL_TAR_REPO_KEY: None,
543- }
544+ {
545+ LAVA_TEST_SHELL_TESDEF_KEY: None,
546+ LAVA_TEST_SHELL_TAR_REPO_KEY: None,
547+ }
548 ]
549 }
550 },
551 {
552 "command": "submit_results",
553- "parameters" : {
554+ "parameters": {
555 "stream": STREAM_PARAMETER,
556 "server": SERVER_PARAMETER
557 }
558
559=== modified file 'lava/job/tests/test_commands.py'
560--- lava/job/tests/test_commands.py 2013-07-26 14:14:25 +0000
561+++ lava/job/tests/test_commands.py 2013-07-26 14:14:25 +0000
562@@ -23,7 +23,7 @@
563 import json
564 import os
565
566-from mock import MagicMock, patch
567+from mock import patch
568
569 from lava.config import Config
570 from lava.helper.tests.helper_test import HelperTest
571@@ -99,9 +99,10 @@
572 command = run(self.parser, self.args)
573 self.assertRaises(CommandError, command.invoke)
574
575- @patch("lava_tool.utils.has_command", new=MagicMock(return_value=False))
576- def test_invoke_raises_1(self):
577- # Users passes a valid file to the run command, but she does not have
578+ @patch("lava.helper.command.has_command", create=True)
579+ def test_invoke_raises_1(self, mocked_has_command):
580+ # User passes a valid file to the run command, but she does not have
581 # the dispatcher installed.
582+ mocked_has_command.return_value = False
583 command = run(self.parser, self.args)
584 self.assertRaises(CommandError, command.invoke)
585
586=== modified file 'lava/parameter.py'
587--- lava/parameter.py 2013-07-26 14:14:25 +0000
588+++ lava/parameter.py 2013-07-26 14:14:25 +0000
589@@ -20,15 +20,9 @@
590 Parameter class and its accessory methods/functions.
591 """
592
593-import StringIO
594-import base64
595-import os
596 import sys
597-import tarfile
598-import tempfile
599 import types
600
601-from lava.tool.errors import CommandError
602 from lava_tool.utils import to_list
603
604 # Character used to join serialized list parameters.
605
606=== added directory 'lava/script'
607=== added file 'lava/script/__init__.py'
608--- lava/script/__init__.py 1970-01-01 00:00:00 +0000
609+++ lava/script/__init__.py 2013-07-26 14:14:25 +0000
610@@ -0,0 +1,51 @@
611+# Copyright (C) 2013 Linaro Limited
612+#
613+# Author: Milo Casagrande <milo.casagrande@linaro.org>
614+#
615+# This file is part of lava-tool.
616+#
617+# lava-tool is free software: you can redistribute it and/or modify
618+# it under the terms of the GNU Lesser General Public License version 3
619+# as published by the Free Software Foundation
620+#
621+# lava-tool is distributed in the hope that it will be useful,
622+# but WITHOUT ANY WARRANTY; without even the implied warranty of
623+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
624+# GNU General Public License for more details.
625+#
626+# You should have received a copy of the GNU Lesser General Public License
627+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
628+
629+"""Scripts handling class."""
630+
631+import os
632+import stat
633+
634+from lava_tool.utils import write_file
635+
636+
637+DEFAULT_MOD = stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH
638+DEFAULT_TESTDEF_SCRIPT_CONTENT = """#!/bin/sh
639+# Automatic generated content by lava-tool.
640+# Please add your own instructions.
641+#
642+# You can use all the avialable Bash commands.
643+#
644+# For the available LAVA commands, see:
645+# http://lava.readthedocs.org/
646+#
647+"""
648+DEFAULT_TESTDEF_SCRIPT = "mytest.sh"
649+
650+
651+class ShellScript(object):
652+
653+ """Creates a shell script on the file system with some content."""
654+
655+ def __init__(self, file_name):
656+ self.file_name = file_name
657+
658+ def write(self):
659+ write_file(self.file_name, DEFAULT_TESTDEF_SCRIPT_CONTENT)
660+ # Make sure the script is executable.
661+ os.chmod(self.file_name, DEFAULT_MOD)
662
663=== added file 'lava/script/commands.py'
664--- lava/script/commands.py 1970-01-01 00:00:00 +0000
665+++ lava/script/commands.py 2013-07-26 14:14:25 +0000
666@@ -0,0 +1,115 @@
667+# Copyright (C) 2013 Linaro Limited
668+#
669+# Author: Milo Casagrande <milo.casagrande@linaro.org>
670+#
671+# This file is part of lava-tool.
672+#
673+# lava-tool is free software: you can redistribute it and/or modify
674+# it under the terms of the GNU Lesser General Public License version 3
675+# as published by the Free Software Foundation
676+#
677+# lava-tool is distributed in the hope that it will be useful,
678+# but WITHOUT ANY WARRANTY; without even the implied warranty of
679+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
680+# GNU General Public License for more details.
681+#
682+# You should have received a copy of the GNU Lesser General Public License
683+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
684+
685+"""Commands to run or submit a script."""
686+
687+import os
688+import tempfile
689+
690+from lava.helper.command import BaseCommand
691+from lava.job import DEFAULT_JOB_FILENAME
692+from lava.testdef import DEFAULT_TESTDEF_FILENAME
693+from lava.tool.command import CommandGroup
694+from lava_tool.utils import verify_path_non_existance
695+
696+
697+class script(CommandGroup):
698+
699+ """LAVA script file handling."""
700+
701+ namespace = "lava.script.commands"
702+
703+
704+class ScriptBaseCommand(BaseCommand):
705+
706+ def _create_tmp_job_file(self, script_file):
707+ """Creates a temporary job file to run or submit the passed file.
708+
709+ The temporary job file and its accessory test definition file are
710+ not removed by this method.
711+
712+ :param script_file: The script file that has to be run or submitted.
713+ :return A tuple with the job file path, and the test definition path.
714+ """
715+ script_file = os.path.abspath(script_file)
716+ verify_path_non_existance(script_file)
717+
718+ temp_dir = tempfile.gettempdir()
719+
720+ # The name of the job and testdef files.
721+ job_file = os.path.join(temp_dir, DEFAULT_JOB_FILENAME)
722+ testdef_file = os.path.join(temp_dir, DEFAULT_TESTDEF_FILENAME)
723+
724+ # The steps that the testdef file should have. We need to change it
725+ # from the default one, since the users are passing their own file.
726+ steps = "./" + os.path.basename(script_file)
727+ testdef_file = self.create_test_definition(testdef_file,
728+ steps=steps)
729+
730+ # The content of the tar file.
731+ tar_content = [script_file, testdef_file]
732+ job_file = self.create_tar_repo_job(job_file, testdef_file,
733+ tar_content)
734+
735+ return (job_file, testdef_file)
736+
737+
738+class run(ScriptBaseCommand):
739+
740+ """Runs the specified shell script on a local device."""
741+
742+ @classmethod
743+ def register_arguments(cls, parser):
744+ super(run, cls).register_arguments(parser)
745+ parser.add_argument("FILE", help="Shell script file to run.")
746+
747+ def invoke(self):
748+ job_file = ""
749+ testdef_file = ""
750+
751+ try:
752+ job_file, testdef_file = self._create_tmp_job_file(self.args.FILE)
753+ super(run, self).run(job_file)
754+ finally:
755+ if os.path.isfile(job_file):
756+ os.unlink(job_file)
757+ if os.path.isfile(testdef_file):
758+ os.unlink(testdef_file)
759+
760+
761+class submit(ScriptBaseCommand):
762+
763+ """Submits the specified shell script to a LAVA server."""
764+
765+ @classmethod
766+ def register_arguments(cls, parser):
767+ super(submit, cls).register_arguments(parser)
768+ parser.add_argument("FILE", help="Shell script file to send.")
769+
770+ def invoke(self):
771+ job_file = ""
772+ testdef_file = ""
773+
774+ try:
775+ job_file, testdef_file = self._create_tmp_job_file(self.args.FILE)
776+ super(submit, self).submit(job_file)
777+ finally:
778+ if os.path.isfile(job_file):
779+ os.unlink(job_file)
780+ if os.path.isfile(testdef_file):
781+ os.unlink(testdef_file)
782
783=== modified file 'lava/testdef/__init__.py'
784--- lava/testdef/__init__.py 2013-07-26 14:14:25 +0000
785+++ lava/testdef/__init__.py 2013-07-26 14:14:25 +0000
786@@ -16,17 +16,24 @@
787 # You should have received a copy of the GNU Lesser General Public License
788 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
789
790+"""TestDefinition class."""
791+
792 import yaml
793
794 from copy import deepcopy
795
796-from lava.helper.template import expand_template
797+from lava.helper.template import (
798+ expand_template,
799+ set_value,
800+)
801 from lava_tool.utils import (
802 write_file,
803 verify_path_existance,
804- verify_file_extension
805+ verify_file_extension,
806 )
807
808+# Default name for a test definition.
809+DEFAULT_TESTDEF_FILENAME = "lavatest.yaml"
810 # Default test def file extension.
811 DEFAULT_TESTDEF_EXTENSION = "yaml"
812 # Possible extensions for a test def file.
813@@ -35,6 +42,13 @@
814
815 class TestDefinition(object):
816
817+ """A test definition object.
818+
819+ This class should be used to create test definitions. The initialization
820+ enforces a default file name extension, and makes sure that the file is
821+ not already present on the file system.
822+ """
823+
824 def __init__(self, data, file_name):
825 """Initialize the object.
826
827@@ -50,6 +64,14 @@
828
829 self.data = deepcopy(data)
830
831+ def set(self, key, value):
832+ """Set key to the specified value.
833+
834+ :param key: The key to look in the object data.
835+ :param value: The value to set.
836+ """
837+ set_value(self.data, key, value)
838+
839 def write(self):
840 """Writes the test definition to file."""
841 content = yaml.dump(self.data, default_flow_style=False, indent=4)
842
843=== modified file 'lava/testdef/commands.py'
844--- lava/testdef/commands.py 2013-07-26 14:14:25 +0000
845+++ lava/testdef/commands.py 2013-07-26 14:14:25 +0000
846@@ -21,9 +21,12 @@
847 """
848
849 import os
850+import tempfile
851
852 from lava.helper.command import BaseCommand
853+from lava.job import DEFAULT_JOB_FILENAME
854 from lava.tool.command import CommandGroup
855+from lava_tool.utils import verify_path_non_existance
856
857
858 class testdef(CommandGroup):
859@@ -33,7 +36,23 @@
860 namespace = "lava.testdef.commands"
861
862
863-class new(BaseCommand):
864+class TestdefBaseCommand(BaseCommand):
865+
866+ def _create_tmp_job_file(self, testdef_file):
867+ testdef_file = os.path.abspath(testdef_file)
868+ verify_path_non_existance(testdef_file)
869+
870+ job_file = os.path.join(tempfile.gettempdir(),
871+ DEFAULT_JOB_FILENAME)
872+
873+ tar_content = [testdef_file]
874+ job_file = self.create_tar_repo_job(job_file, testdef_file,
875+ tar_content)
876+
877+ return job_file
878+
879+
880+class new(TestdefBaseCommand):
881
882 """Creates a new test definition file."""
883
884@@ -47,7 +66,7 @@
885 self.create_test_definition(full_path)
886
887
888-class run(BaseCommand):
889+class run(TestdefBaseCommand):
890
891 """Runs the specified test definition on a local device."""
892
893@@ -57,11 +76,18 @@
894 parser.add_argument("FILE", help="Test definition file to run.")
895
896 def invoke(self):
897- pass
898-
899-
900-def submit(BaseCommand):
901- """Submits the specified test definition to a remove LAVA server."""
902+ job_file = ""
903+ try:
904+ job_file = self._create_tmp_job_file(self.args.FILE)
905+ super(run, self).run(job_file)
906+ finally:
907+ if os.path.isfile(job_file):
908+ os.unlink(job_file)
909+
910+
911+class submit(TestdefBaseCommand):
912+
913+ """Submits the specified test definition to a LAVA server."""
914
915 @classmethod
916 def register_arguments(cls, parser):
917@@ -69,4 +95,10 @@
918 parser.add_argument("FILE", help="Test definition file to send.")
919
920 def invoke(self):
921- pass
922+ job_file = ""
923+ try:
924+ job_file = self._create_tmp_job_file(self.args.FILE)
925+ super(submit, self).submit(job_file)
926+ finally:
927+ if os.path.isfile(job_file):
928+ os.unlink(job_file)
929
930=== modified file 'lava/testdef/templates.py'
931--- lava/testdef/templates.py 2013-07-26 14:14:25 +0000
932+++ lava/testdef/templates.py 2013-07-26 14:14:25 +0000
933@@ -19,23 +19,11 @@
934 """Test definition templates."""
935
936 from lava.parameter import (
937- ListParameter,
938 Parameter,
939 )
940
941-DEFAULT_TESTDEF_FILE = "lavatest.yaml"
942-
943 DEFAULT_TESTDEF_VERSION = "1.0"
944 DEFAULT_TESTDEF_FORMAT = "Lava-Test Test Definition 1.0"
945-
946-# This is what will be called by default by the test definition yaml file.
947-DEFAULT_TESTDEF_SCRIPT = "mytest.sh"
948-DEFAULT_TESTDEF_SCRIPT_CONTENT = """#!/bin/sh
949-# Automatic generated content by lava-tool.
950-# Please add your own instructions.
951-"""
952-DEFAULT_TESTDEF_STEP = "./mytest.sh"
953-
954 DEFAULT_ENVIRONMET_VALUE = "lava_test_shell"
955
956 # All these parameters will not be stored on the local config file.
957@@ -45,18 +33,7 @@
958 DESCRIPTION_PARAMETER = Parameter("description", depends=NAME_PARAMETER)
959 DESCRIPTION_PARAMETER.store = False
960
961-ENVIRONMENT_PARAMETER = ListParameter("environment",
962- depends=NAME_PARAMETER)
963-ENVIRONMENT_PARAMETER.add(DEFAULT_ENVIRONMET_VALUE)
964-ENVIRONMENT_PARAMETER.asked = True
965-ENVIRONMENT_PARAMETER.store = False
966-
967-# Steps parameter. Default to a local shell script that the user defines.
968-# We do not ask this parameter, and we do not store it either.
969-STEPS_PARAMETER = ListParameter("steps", depends=NAME_PARAMETER)
970-STEPS_PARAMETER.add(DEFAULT_TESTDEF_STEP)
971-STEPS_PARAMETER.asked = True
972-STEPS_PARAMETER.store = False
973+TESTDEF_STEPS_KEY = "steps"
974
975 TESTDEF_TEMPLATE = {
976 "metadata": {
977@@ -64,12 +41,12 @@
978 "format": DEFAULT_TESTDEF_FORMAT,
979 "version": DEFAULT_TESTDEF_VERSION,
980 "description": DESCRIPTION_PARAMETER,
981- "environment": ENVIRONMENT_PARAMETER,
982+ "environment": [DEFAULT_ENVIRONMET_VALUE],
983 },
984 "run": {
985- "steps": STEPS_PARAMETER,
986+ TESTDEF_STEPS_KEY: ["./mytest.sh"]
987 },
988 "parse": {
989- "pattern": '^\s*(?P<test_case_id>\w+)=(?P<result>\w+)\s*$'
990+ "pattern": r'^\s*(?P<test_case_id>\w+)=(?P<result>\w+)\s*$'
991 }
992 }
993
994=== modified file 'lava/testdef/tests/test_commands.py'
995--- lava/testdef/tests/test_commands.py 2013-07-26 14:14:25 +0000
996+++ lava/testdef/tests/test_commands.py 2013-07-26 14:14:25 +0000
997@@ -47,7 +47,7 @@
998 self.args.FILE = self.file_path
999
1000 self.temp_yaml = tempfile.NamedTemporaryFile(suffix=".yaml",
1001- delete=False)
1002+ delete=False)
1003
1004 self.config_file = tempfile.NamedTemporaryFile(delete=False)
1005 self.config = InteractiveConfig()
1006@@ -110,7 +110,7 @@
1007 'name': ''},
1008 'parse': {
1009 'pattern':
1010- '^\\s*(?P<test_case_id>\\w+)=(?P<result>\\w+)\\s*$'
1011+ '^\\s*(?P<test_case_id>\\w+)=(?P<result>\\w+)\\s*$'
1012 },
1013 }
1014 obtained = None
1015@@ -144,7 +144,7 @@
1016 },
1017 'parse': {
1018 'pattern':
1019- '^\\s*(?P<test_case_id>\\w+)=(?P<result>\\w+)\\s*$'
1020+ '^\\s*(?P<test_case_id>\\w+)=(?P<result>\\w+)\\s*$'
1021 },
1022 }
1023 obtained = None
1024
1025=== modified file 'lava/tests/test_commands.py'
1026--- lava/tests/test_commands.py 2013-07-26 14:14:25 +0000
1027+++ lava/tests/test_commands.py 2013-07-26 14:14:25 +0000
1028@@ -64,7 +64,6 @@
1029 _, args, _ = self.parser.method_calls[1]
1030 self.assertIn("DIR", args)
1031
1032-
1033 @patch("lava.commands.edit_file", create=True)
1034 def test_command_invoke_0(self, mocked_edit_file):
1035 # Invoke the init command passing a path to a file. Should raise an
1036
1037=== modified file 'lava/tests/test_config.py'
1038--- lava/tests/test_config.py 2013-07-26 14:14:25 +0000
1039+++ lava/tests/test_config.py 2013-07-26 14:14:25 +0000
1040@@ -148,6 +148,15 @@
1041 obtained = tmp_file.read()
1042 self.assertEqual(expected, obtained)
1043
1044+ def test_config_get_from_backend_public(self):
1045+ # Need to to this, since we want a clean Config instance, with
1046+ # a config_file with some content.
1047+ with open(self.config.config_file, "w") as write_config:
1048+ write_config.write("[DEFAULT]\nfoo=bar\n")
1049+ param = Parameter("foo")
1050+ obtained = self.config.get_from_backend(param)
1051+ self.assertEquals("bar", obtained)
1052+
1053
1054 class InteractiveConfigTest(ConfigTestCase):
1055
1056
1057=== modified file 'lava/tests/test_parameter.py'
1058--- lava/tests/test_parameter.py 2013-07-26 14:14:25 +0000
1059+++ lava/tests/test_parameter.py 2013-07-26 14:14:25 +0000
1060@@ -27,7 +27,6 @@
1061 ListParameter,
1062 Parameter,
1063 )
1064-
1065 from lava_tool.utils import to_list
1066
1067
1068@@ -78,7 +77,9 @@
1069 self.assertIsInstance(obtained, list)
1070 self.assertEquals(expected, obtained)
1071
1072+
1073 class ListParameterTest(GeneralParameterTest):
1074+
1075 """Tests for the specialized ListParameter class."""
1076
1077 def setUp(self):
1078
1079=== modified file 'lava_tool/utils.py'
1080--- lava_tool/utils.py 2013-07-26 14:14:25 +0000
1081+++ lava_tool/utils.py 2013-07-26 14:14:25 +0000
1082@@ -191,15 +191,27 @@
1083
1084
1085 def verify_path_existance(path):
1086- """Verifies if a given path exists or not on the file system.
1087+ """Verifies if a given path exists on the file system.
1088
1089 Raises a CommandError in case it exists.
1090
1091- :param path: The path to verify."""
1092+ :param path: The path to verify.
1093+ """
1094 if os.path.exists(path):
1095 raise CommandError("{0} already exists.".format(path))
1096
1097
1098+def verify_path_non_existance(path):
1099+ """Verifies if a given path does not exist on the file system.
1100+
1101+ Raises a CommandError in case it does not exist.
1102+
1103+ :param path: The path to verify.
1104+ """
1105+ if not os.path.exists(path):
1106+ raise CommandError("{0} does not exists.".format(path))
1107+
1108+
1109 def write_file(path, content):
1110 """Creates a file with the specified content.
1111

Subscribers

People subscribed via source and target branches