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

Proposed by Milo Casagrande
Status: Merged
Merged at revision: 188
Proposed branch: lp:~milo/lava-tool/lava-167
Merge into: lp:~linaro-validation/lava-tool/trunk
Prerequisite: lp:~terceiro/lava-tool/helper
Diff against target: 972 lines (+798/-22)
15 files modified
.bzrignore (+2/-0)
.testr.conf (+3/-0)
ci-build (+8/-2)
entry_points.ini (+6/-0)
lava/config.py (+12/-1)
lava/device/__init__.py (+124/-0)
lava/device/commands.py (+250/-0)
lava/device/templates.py (+42/-0)
lava/device/tests/test_commands.py (+175/-0)
lava/device/tests/test_device.py (+123/-0)
lava/job/templates.py (+1/-5)
lava_tool/tests/__init__.py (+15/-12)
lava_tool/tests/test_authtoken.py (+0/-1)
lava_tool/utils.py (+33/-0)
setup.py (+4/-1)
To merge this branch: bzr merge lp:~milo/lava-tool/lava-167
Reviewer Review Type Date Requested Status
Antonio Terceiro Needs Fixing
Linaro Automation & Validation Pending
Review via email: mp+167727@code.launchpad.net

Description of the change

This is work in progress for Jira card LAVA-167. Early work to take a look if I'm on the right track.

To post a comment you must log in.
Revision history for this message
Milo Casagrande (milo) wrote :

Updated status to be in need review, code should be ready for general review.

Added also here are pep8 and pyflakes tests (although they give errors while running via the ci-build script).

lp:~milo/lava-tool/lava-167 updated
216. By Milo Casagrande

Added missing dependencies to run tests.

    * pep8 and pyflakes depends on testtools, since it gives a nicer
      output on errors.

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

With commit #216 even the ci-build script now works. pep8 and pyflakes tests depend on testtools package since it provides a better error output on test runs.

lp:~milo/lava-tool/lava-167 updated
217. By Milo Casagrande

Fixed config command test.

218. By Milo Casagrande

Refactored command tests: defined device name on setup.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

9 +versiontools-1.9.1-py2.7.egg

You probably want to ignore *.egg

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

All the stuff around testing. You'd better just depend on py.test or something like that, it has that exact stuff integrated and ready. You may also find that just blindly enabling pyflakes and pep8 will throw lots of errors at you (unless lava is perfect, I doubt that). The better approach would be to use a diff mode and only run that at merge request time. Then you at least don't introduce _new_ issues.

lp:~milo/lava-tool/lava-167 updated
219. By Milo Casagrande

Fixed ignored .egg pattern.

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

On Mon, Jun 10, 2013 at 3:00 PM, Zygmunt Krynicki
<email address hidden> wrote:
> All the stuff around testing. You'd better just depend on py.test or something like that, it has that exact stuff integrated and ready.

Never heard about that, will give it a look. Thanks for the pointer!
Thanks also for the *.egg in bzrignore.

> You may also find that just blindly enabling pyflakes and pep8 will throw lots of errors at you (unless lava is perfect, I doubt that). The better approach would be to use a diff mode and only run that at merge request time. Then you at least don't introduce _new_ issues.

I agree, I'm not that happy with that too, I was thinking about a
git/bzr pre-[commit|push] hook, but at least for now (and locally,
testing only the lava-tool) I prefer having those error thrown at me.
For lava-tool they shouldn't even be too hard to fix.

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

lp:~milo/lava-tool/lava-167 updated
220. By Milo Casagrande

Removed pep8 and pyflakes tests.

    * At this stage better have them as part of a pre-commit hook.

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

I removed the pep8 and pyflakes tests from the project. Fixing them would not take too much, but there are some, in particular doctest ones, that might be a trickier to fix.

At this stage it makes more sense to provide pre-commit or pre-push hooks for git/bzr to run pep8/pyflakes/pylint/...

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

Hi Milo,

Very nice progress here! I have some comments below to help improving it.

 review needs-fixing

Also I wonder whether we should merge my branch into trunk already so
you don't keep developing off the official places ... it's not like
there will be a release before this work is done with.

> === modified file '.bzrignore'
> --- .bzrignore 2013-06-10 15:38:27 +0000
> +++ .bzrignore 2013-06-10 15:38:27 +0000
> @@ -3,3 +3,6 @@
> ./build
> ./dist
> /tags
> +.testrepository
> +*.egg
> +ci-build-venv

you won't need the last one anymore.

> === added file '.testr.conf'
> --- .testr.conf 1970-01-01 00:00:00 +0000
> +++ .testr.conf 2013-06-10 15:38:27 +0000
> @@ -0,0 +1,3 @@
> +[DEFAULT]
> +test_command=python -m subunit.run $IDLIST
> +test_id_list_default=lava_tool.tests.test_suite
>
> === modified file 'ci-build'
> --- ci-build 2013-06-10 15:38:27 +0000
> +++ ci-build 2013-06-10 15:38:27 +0000
> @@ -1,11 +1,13 @@
> #!/bin/sh
>
> +VENV_DIR="/tmp/ci-build-venv"
> +
> set -e
>
> if test -z "$VIRTUAL_ENV"; then
> set -x
> - virtualenv ci-build-venv
> - . ci-build-venv/bin/activate
> + virtualenv $VENV_DIR
> + . $VENV_DIR/bin/activate
> python setup.py develop
> fi
>
> @@ -21,6 +23,22 @@
> pip install mocker
> fi
>
> +if ! pip show pep8 | grep -q pep8; then
> + pip install pep8
> +fi
> +
> +if ! pip show pyflakes | grep -q pyflakes; then
> + pip install pyflakes
> +fi
> +
> +if ! pip show testtools | grep -q testtools; then
> + pip install testtools
> +fi
> +
> +if ! pip show mock | grep -q mock; then
> + pip install mock
> +fi
> +
> export LAVACONFIG=/dev/null
>
> if test -z "$DISPLAY"; then
>

if you dropped pyflakes/pep8 tests there is no point in installing them
here.

> === added directory 'lava/device'
> === added file 'lava/device/__init__.py'
> --- lava/device/__init__.py 1970-01-01 00:00:00 +0000
> +++ lava/device/__init__.py 2013-06-10 15:38:27 +0000
> @@ -0,0 +1,112 @@
> +# Copyright (C) 2013 Linaro Limited
> +#
> +# Author: Milo Casagrande <email address hidden>
> +#
> +# This file is part of lava-tool.
> +#
> +# lava-tool is free software: you can redistribute it and/or modify
> +# it under the terms of the GNU Lesser General Public License version 3
> +# as published by the Free Software Foundation
> +#
> +# lava-tool is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
> +
> +import re
> +
> +from lava.device.templates import DEFAULT_TEMPLATE
> +from lava.tool.errors import CommandError
> +
> +
> +class Device(object):
> + """A generic device."""
> + def __init__(self, name):
> + super(Device, self).__init__()

I don't think object has anything useful in its constructor.

> + self.device_type = None
> + self.hostname = name
> + self.template = DEFAULT_TEMPLATE

In _update you are changing this object, you might want to ...

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

Removed unnecessary ignore rule.

222. By Milo Casagrande

Cleaned up dependencies.

223. By Milo Casagrande

Renamed variable name.

224. By Milo Casagrande

Reworked the Device class.

    * Removed PandaDevice class, fixed tests.
    * Added couple more tests.
    * Added a panda template, a vexpress one and refactored how the
      known devices dictionary is built.
    * Some PEP8 and pylint cleaning.

225. By Milo Casagrande

Fixed subprocess call.

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

Hi Antonio!

Thanks for the review.

On Tue, Jun 11, 2013 at 2:31 AM, Antonio Terceiro
<email address hidden> wrote:
> Review: Needs Fixing
>
> Hi Milo,
>
> Very nice progress here! I have some comments below to help improving it.
>
> review needs-fixing
>
> Also I wonder whether we should merge my branch into trunk already so
> you don't keep developing off the official places ... it's not like
> there will be a release before this work is done with.

As you prefer, I based my branch on yours so the diff would make sense here.
As soon as we merge that we can switch to trunk.

> if you dropped pyflakes/pep8 tests there is no point in installing them
> here.

Forgot to clean up those dependencies. Cleared them all out, and
cleaned also the bzrignore file.

> I don't think object has anything useful in its constructor.

Default code snippet/autocompletion when I create classes. I usually
do not even pay attention to that.
Cleared out.

> In _update you are changing this object, you might want to make a copy instead.

Makes sense, indeed.

>> +
>> + def write(self, where):
>> + """Writes the object to file.
>> +
>> + :param where: The full path of the file where to write."""
>> + with open(where, 'w') as f:
>> + f.write(self.__str__())
>
> renaming `where` to `file` will make the code a lot more obvious.

I used `conf_file`, I do not like using the same name as the built-in
"file" for a variable even if it is as simple as in this case (plus
there is the description just above/below).

>> +# Dictionary with key the name of a know device, and value a tuple composed of
>> +# a matcher used to guess the device type, and its associated Device class.
>> +known_devices = {
>> + 'panda': (re.compile('^.*panda.*', re.I), PandaDevice),
>> +}
>
> you might create this dynamically from a list of device type names. I don't
> see us needing one class per device type, so perhaps the last element in the
> value tuple should be a template, which could be looked up by device type name
> in lava.device.templates, or a default if the device type name does not bring
> anything.

I cleaned up this part, removing the specialized PandaDevice class and
creating "custom" templates.
Take a look of what I did was how you were thinking about.

The regex matcher is also create "on-the-fly".

> I think you cannot always assume an instance here. If you have lava-tool
> installed with Debian packages, then you don't have a local instance. The
> problem is that the dispatcher does not expose a list of configuration
> directories that we can reference. I will work on this tomorrow, and will let
> you know.

Yeah, I was looking into getting as much info as possible out of LAVA,
but for what I needed I didn't have much luck.
Indeed this part here needs a better logic, but I wanted to have the
code out there for a first review.

> I think we could keep a dictionary of templates, indexed by device type (see
> also my comments above wrt lava.device.__init__.py)

This should have been addressed. Please take a look and let me know.

> CalledProcessError will never be raised here. It's only raised by check_call
> and check_output.
>
> try th...

Read more...

lp:~milo/lava-tool/lava-167 updated
226. By Milo Casagrande

Reworked docstring for command line help.

227. By Milo Casagrande

Reworked how new templates are created.

228. By Milo Casagrande

Used dict copy instead of importing copy module.

229. By Milo Casagrande

Removed rm from the entry points: it showed up as remove in the command list.

230. By Milo Casagrande

Refactored string formatting to be more Python3 compliant.

231. By Milo Casagrande

Refactoring and tests fixed.

    * Added function to get from lava-dispatcher the config paths.
    * Some cleanups and refactoring.

232. By Milo Casagrande

Made the user choose the correct path to use.

    * Added logic to handle user input.
    * Fixed tests.

233. By Milo Casagrande

Added new test.

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

> == added file 'lava/device/commands.py'
> -- lava/device/commands.py 1970-01-01 00:00:00 +0000
> ++ lava/device/commands.py 2013-06-12 13:21:33 +0000
> @ -0,0 +1,237 @@
[snip]
> # Default lava-dispatcher paths.
> DEFAULT_DISPATCHER_PATH = os.path.join("etc", "lava-dispatcher")
> USER_DISPATCHER_PATH = os.path.join(os.path.expanduser("~"), ".config",
> "lava-dispatcher")

It would be better to

  from lava_dispatcher.import user_config_path, system_config_path

and use those instead, so you don't need to duplicate the logic here. We don't
want to make the dispatcher a hard dependency of lava-tool, but if we don't
have the dispatcher available, just fail the entire thing.

> # Default devices path, has to be joined with the dispatcher path.
> DEFAULT_DEVICES_PATH = "devices"
> DEVICE_FILE_SUFFIX = "conf"
>
>
> class device(CommandGroup):
> """LAVA devices handling."""
>
> namespace = "lava.device.commands"
>
>
> class BaseCommand(Command):
> """Base command for device commands."""
> def __init__(self, parser, args):
> super(BaseCommand, self).__init__(parser, args)
> self.config = InteractiveConfig(
> force_interactive=self.args.interactive)
>
> @classmethod
> def register_arguments(cls, parser):
> super(BaseCommand, cls).register_arguments(parser)
> parser.add_argument("-i", "--interactive",
> action='store_true',
> help=("Forces asking for input parameters even if "
> "we already have them cached."))
>
> @classmethod
> def _get_dispatcher_paths(cls):
> """Tries to get the dispatcher from lava-dispatcher."""
> global_paths = []
> try:
> from lava_dispatcher.config import search_path
>
> global_paths += search_path()
> except ImportError:
> print >> sys.stderr, "Cannot import lava_dispatcher."

Right here. We should baild out if the dispatcher is not available for device
management. After all without a local dispatcher there is no point in managing
devices.

> return global_paths
>
> @classmethod
> def _choose_dispatcher_path(cls, paths):
> """Lets the user choose the path to use.
>
> :param paths: A list of paths.
> :return The path at the user selected index.
> """
> print >> sys.stdout, ("Multiple dispatcher paths found. Please "
> "select one between:\n")
> out_list = []
> len_paths = range(1, len(paths) + 1)
> for index, dispatcher_path in zip(len_paths, paths):
> out_list.append("\t{0}. {1}\n".format(index, dispatcher_path))
> print >> sys.stdout, "".join(out_list)
> while True:
> try:
> choice = raw_input("Dispatcher path to use: ").strip()
> if choice in [str(x) for x in len_paths]:
> return paths[int(choice) - 1]
> else:
> continue
> except KeyboardInterrupt:
> sys.exit(-1)

I think we should not g...

Read more...

lp:~milo/lava-tool/lava-167 updated
234. By Milo Casagrande

Fixed templates.

235. By Milo Casagrande

Fixed command logic to use lava_dispatcher.

    * We now soft depend on lava_dispatcher: if it is not installed, execution
      will not continue.
    * Fixed tests.

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

On Thu, Jun 13, 2013 at 2:46 AM, Antonio Terceiro
<email address hidden> wrote:
>> == added file 'lava/device/commands.py'
>> -- lava/device/commands.py 1970-01-01 00:00:00 +0000
>> ++ lava/device/commands.py 2013-06-12 13:21:33 +0000
>> @ -0,0 +1,237 @@
> [snip]
>> # Default lava-dispatcher paths.
>> DEFAULT_DISPATCHER_PATH = os.path.join("etc", "lava-dispatcher")
>> USER_DISPATCHER_PATH = os.path.join(os.path.expanduser("~"), ".config",
>> "lava-dispatcher")
>
> It would be better to
>
> from lava_dispatcher.import user_config_path, system_config_path
>
> and use those instead, so you don't need to duplicate the logic here. We don't
> want to make the dispatcher a hard dependency of lava-tool, but if we don't
> have the dispatcher available, just fail the entire thing.

Modifying the logic in the _get_dispatcher_paths() and the other
methods that deal with picking the correct path, I think it is not
necessary to import "user_config_path" and "system_config_path" from
the dispatcher.

Since we get the paths via lava_dispatcher.config.search_path(), it
will return those paths already, and if we cannot import it we will
fail.

> Right here. We should baild out if the dispatcher is not available for device
> management. After all without a local dispatcher there is no point in managing
> devices.

Sounds cool.
I was trying to be a little bit more "gentle" and fallback to let
users create devices anyway. :-)

> I think we should not give too many options here: if the user is capable of
> choosing the right directory to write to, he doesn't need lava-tool to do it
> for him; he would just create a file with $EDITOR there.
>
> Instead of this, I think you should assume that dispatcher branch of mine, and
> use the following strategy:
>
> 1) finding where to create a new configurarion file:
>
> the first of [system_dispatcher_path, user_config_path] that is writable
>
> 2) finding an existing configuration file to edit:
>
> from lava_dispatcher.config import get_config_file
> [...]
>
> conf_file = get_config_file("devices/%s.conf", device_name)
> if conf_file:
> # edit
> else:
> print("No device %s found" % device_name)
> exit(-1)
>
> I think that doing this will simplify a lot things in this file.

OK, I reworked this part, probably in a slighter different way.
This is how it will work now for the add command:

- We check if a device with that config file name already exists: this
is now done with lava_dispatcher.config.get_config_file. If we cannot
import the function, we fail.
- If the device already exists, we exit printing to use "lava device
config DEVICE" (as was before)
- Otherwise, get the paths from the dispatcher using
lava_dispatcher.config.search_path() (again, if it gives errors we
fail)
- Check if one of those paths is writable and pick the first one that
is, otherwise fail
- At this stage we are sure that the device doesn't exists and we can create it.

There might be another check we want to do though: if
lava_dispatcher.config.get_config_file() returns a path to an existing
file that is not writable by the user (in particular for the "config"
command).

> I think something l...

Read more...

lp:~milo/lava-tool/lava-167 updated
236. By Milo Casagrande

Fixed print statements, added one on remove command.

237. By Milo Casagrande

Minor fixes on printing statements.

238. By Milo Casagrande

Minor fixes on printing statements: fixed typos.

239. By Milo Casagrande

Fixed two tests.

240. By Milo Casagrande

Fixed one test.

241. By Milo Casagrande

Fixed logic when choosing devices path.

242. By Milo Casagrande

Better output on add command.

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

On Thu, Jun 13, 2013 at 10:35:00AM -0000, Milo Casagrande wrote:
> > I think we should not give too many options here: if the user is capable of
> > choosing the right directory to write to, he doesn't need lava-tool to do it
> > for him; he would just create a file with $EDITOR there.
> >
> > Instead of this, I think you should assume that dispatcher branch of mine, and
> > use the following strategy:
> >
> > 1) finding where to create a new configurarion file:
> >
> > the first of [system_dispatcher_path, user_config_path] that is writable
> >
> > 2) finding an existing configuration file to edit:
> >
> > from lava_dispatcher.config import get_config_file
> > [...]
> >
> > conf_file = get_config_file("devices/%s.conf", device_name)
> > if conf_file:
> > # edit
> > else:
> > print("No device %s found" % device_name)
> > exit(-1)
> >
> > I think that doing this will simplify a lot things in this file.
>
> OK, I reworked this part, probably in a slighter different way.
> This is how it will work now for the add command:
>
> - We check if a device with that config file name already exists: this
> is now done with lava_dispatcher.config.get_config_file. If we cannot
> import the function, we fail.
> - If the device already exists, we exit printing to use "lava device
> config DEVICE" (as was before)
> - Otherwise, get the paths from the dispatcher using
> lava_dispatcher.config.search_path() (again, if it gives errors we
> fail)
> - Check if one of those paths is writable and pick the first one that
> is, otherwise fail

It cannot be the first of search_path. Say you write to user_config_path
(~user/.config/...). For some(most?) devices, at the moment you need to
run the dispatcher as root. The dispatcher will then look at
~root/config/... , and will not find anything the device.

Also, picking some path in search_path involves the (small, but
non-zero) risk of writing to the default_config_path, what we don't want.

Maybe we could just export [system_config_path, user_config_path] as
lava_dispatcher.config.write_search_path, then, keep your logic of find
the first?

> - At this stage we are sure that the device doesn't exists and we can create it.
>
> There might be another check we want to do though: if
> lava_dispatcher.config.get_config_file() returns a path to an existing
> file that is not writable by the user (in particular for the "config"
> command).

Yep.

--
Antonio Terceiro
Software Engineer - Linaro
http://www.linaro.org

lp:~milo/lava-tool/lava-167 updated
243. By Milo Casagrande

Fixed use of lava_dispatcher function to retrieve config write path.

244. By Milo Casagrande

Added test for editing device file.

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

On Thu, Jun 13, 2013 at 4:24 PM, Antonio Terceiro
<email address hidden> wrote:
>
> It cannot be the first of search_path. Say you write to user_config_path
> (~user/.config/...). For some(most?) devices, at the moment you need to
> run the dispatcher as root. The dispatcher will then look at
> ~root/config/... , and will not find anything the device.
>
> Also, picking some path in search_path involves the (small, but
> non-zero) risk of writing to the default_config_path, what we don't want.
>
> Maybe we could just export [system_config_path, user_config_path] as
> lava_dispatcher.config.write_search_path, then, keep your logic of find
> the first?

I fixed this using your latest changes in the dispatcher.
I also added a test when editing the config file to check if it is
writable or not.

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

lp:~milo/lava-tool/lava-167 updated
245. By Milo Casagrande

Fixed method name error.

246. By Milo Casagrande

Added another test.

247. By Milo Casagrande

Fixed opening of file: opening with 'w' ereases the file.

248. By Milo Casagrande

Use append mode also in another case for file opening.

249. By Milo Casagrande

Fixed output.

250. By Milo Casagrande

Fixed tests.

251. By Milo Casagrande

Added a new test.

252. By Milo Casagrande

Fixed the can_edit_file test.

253. By Milo Casagrande

Merged from trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2013-05-24 17:37:52 +0000
3+++ .bzrignore 2013-06-19 06:51:26 +0000
4@@ -3,3 +3,5 @@
5 ./build
6 ./dist
7 /tags
8+.testrepository
9+*.egg
10
11=== added file '.testr.conf'
12--- .testr.conf 1970-01-01 00:00:00 +0000
13+++ .testr.conf 2013-06-19 06:51:26 +0000
14@@ -0,0 +1,3 @@
15+[DEFAULT]
16+test_command=python -m subunit.run $IDLIST
17+test_id_list_default=lava_tool.tests.test_suite
18
19=== modified file 'ci-build'
20--- ci-build 2013-06-03 20:56:10 +0000
21+++ ci-build 2013-06-19 06:51:26 +0000
22@@ -1,11 +1,13 @@
23 #!/bin/sh
24
25+VENV_DIR="/tmp/ci-build-venv"
26+
27 set -e
28
29 if test -z "$VIRTUAL_ENV"; then
30 set -x
31- virtualenv ci-build-venv
32- . ci-build-venv/bin/activate
33+ virtualenv $VENV_DIR
34+ . $VENV_DIR/bin/activate
35 python setup.py develop
36 fi
37
38@@ -21,6 +23,10 @@
39 pip install mocker
40 fi
41
42+if ! pip show mock | grep -q mock; then
43+ pip install mock
44+fi
45+
46 export LAVACONFIG=/dev/null
47
48 if test -z "$DISPLAY"; then
49
50=== modified file 'entry_points.ini'
51--- entry_points.ini 2013-05-27 20:51:39 +0000
52+++ entry_points.ini 2013-06-19 06:51:26 +0000
53@@ -8,6 +8,7 @@
54 scheduler = lava_scheduler_tool.commands:scheduler
55 dashboard = lava_dashboard_tool.commands:dashboard
56 job = lava.job.commands:job
57+device = lava.device.commands:device
58
59 [lava_tool.commands]
60 help = lava.tool.commands.help:help
61@@ -70,3 +71,8 @@
62 new = lava.job.commands:new
63 submit = lava.job.commands:submit
64 run = lava.job.commands:run
65+
66+[lava.device.commands]
67+add = lava.device.commands:add
68+remove = lava.device.commands:remove
69+config = lava.device.commands:config
70
71=== modified file 'lava/config.py'
72--- lava/config.py 2013-05-28 22:08:12 +0000
73+++ lava/config.py 2013-06-19 06:51:26 +0000
74@@ -30,14 +30,25 @@
75 pass
76 atexit.register(readline.write_history_file, history)
77
78-config_file = os.environ.get('LAVACONFIG') or os.path.join(os.path.expanduser('~'), '.lavaconfig')
79+config_file = (os.environ.get('LAVACONFIG') or
80+ os.path.join(os.path.expanduser('~'), '.lavaconfig'))
81 config_backend = ConfigParser()
82 config_backend.read([config_file])
83+
84+
85 def save_config():
86 with open(config_file, 'w') as f:
87 config_backend.write(f)
88 atexit.register(save_config)
89
90+
91+class Parameter(object):
92+
93+ def __init__(self, id, depends=None):
94+ self.id = id
95+ self.depends = depends
96+
97+
98 class InteractiveConfig(object):
99
100 def __init__(self, force_interactive=False):
101
102=== added directory 'lava/device'
103=== added file 'lava/device/__init__.py'
104--- lava/device/__init__.py 1970-01-01 00:00:00 +0000
105+++ lava/device/__init__.py 2013-06-19 06:51:26 +0000
106@@ -0,0 +1,124 @@
107+# Copyright (C) 2013 Linaro Limited
108+#
109+# Author: Milo Casagrande <milo.casagrande@linaro.org>
110+#
111+# This file is part of lava-tool.
112+#
113+# lava-tool is free software: you can redistribute it and/or modify
114+# it under the terms of the GNU Lesser General Public License version 3
115+# as published by the Free Software Foundation
116+#
117+# lava-tool is distributed in the hope that it will be useful,
118+# but WITHOUT ANY WARRANTY; without even the implied warranty of
119+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
120+# GNU General Public License for more details.
121+#
122+# You should have received a copy of the GNU Lesser General Public License
123+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
124+
125+import re
126+
127+from lava.device.templates import (
128+ KNOWN_TEMPLATES,
129+ DEFAULT_TEMPLATE,
130+)
131+from lava.tool.errors import CommandError
132+
133+
134+def __re_compile(name):
135+ """Creates a generic regex for the specified device name.
136+
137+ :param name: The name of the device.
138+ :return A Pattern object.
139+ """
140+ return re.compile('^.*{0}.*'.format(name), re.I)
141+
142+
143+# Dictionary of know devices.
144+# Keys are the general device name taken from lava.device.templates, values
145+# are tuples of: a regex matcher to match the device, and the device associated
146+# template.
147+KNOWN_DEVICES = dict([(device, (__re_compile(device), template))
148+ for device, template in KNOWN_TEMPLATES.iteritems()])
149+
150+
151+class Device(object):
152+ """A generic device."""
153+ def __init__(self, hostname, template):
154+ self.device_type = None
155+ self.hostname = hostname
156+ self.template = template.copy()
157+
158+ def write(self, conf_file):
159+ """Writes the object to file.
160+
161+ :param conf_file: The full path of the file where to write."""
162+ with open(conf_file, 'w') as write_file:
163+ write_file.write(self.__str__())
164+
165+ def _update(self):
166+ """Updates the template with the values specified for this class.
167+
168+ Subclasses need to override this when they add more specific
169+ attributes.
170+ """
171+ # This is needed for the 'default' behavior. If we matched a known
172+ # device, we do not need to update its device_type, since its already
173+ # defined in the template.
174+ if self.device_type:
175+ self.template.update(hostname=self.hostname,
176+ device_type=self.device_type)
177+ else:
178+ self.template.update(hostname=self.hostname)
179+
180+ def __str__(self):
181+ self._update()
182+ string_list = []
183+ for key, value in self.template.iteritems():
184+ if not value:
185+ value = ''
186+ string_list.append("{0} = {1}\n".format(str(key), str(value)))
187+ return "".join(string_list)
188+
189+ def __repr__(self):
190+ self._update()
191+ return str(self.template)
192+
193+
194+def _get_device_type_from_user():
195+ """Makes the user write what kind of device this is.
196+
197+ If something goes wrong, raises CommandError.
198+ """
199+ try:
200+ dev_type = raw_input("Please specify the device type: ").strip()
201+ except (EOFError, KeyboardInterrupt):
202+ dev_type = None
203+ if not dev_type:
204+ raise CommandError("DEVICE name not specified or not correct.")
205+ return dev_type
206+
207+
208+def get_known_device(name):
209+ """Tries to match a device name with a known device type.
210+
211+ :param name: The name of the device we want matched to a real device.
212+ :return A Device instance.
213+ """
214+ instance = None
215+ for known_dev, (matcher, dev_template) in KNOWN_DEVICES.iteritems():
216+ if matcher.match(name):
217+ instance = Device(name, dev_template)
218+ if not instance:
219+ dev_type = _get_device_type_from_user()
220+ known_dev = KNOWN_DEVICES.get(dev_type, None)
221+ if known_dev:
222+ instance = Device(name, known_dev[1])
223+ else:
224+ print ("Device '{0}' does not match a known "
225+ "device.".format(dev_type))
226+ instance = Device(name, DEFAULT_TEMPLATE)
227+ # Not stricly necessary, users can fill up the field later.
228+ instance.device_type = dev_type
229+
230+ return instance
231
232=== added file 'lava/device/commands.py'
233--- lava/device/commands.py 1970-01-01 00:00:00 +0000
234+++ lava/device/commands.py 2013-06-19 06:51:26 +0000
235@@ -0,0 +1,250 @@
236+# Copyright (C) 2013 Linaro Limited
237+#
238+# Author: Milo Casagrande <milo.casagrande@linaro.org>
239+#
240+# This file is part of lava-tool.
241+#
242+# lava-tool is free software: you can redistribute it and/or modify
243+# it under the terms of the GNU Lesser General Public License version 3
244+# as published by the Free Software Foundation
245+#
246+# lava-tool is distributed in the hope that it will be useful,
247+# but WITHOUT ANY WARRANTY; without even the implied warranty of
248+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
249+# GNU General Public License for more details.
250+#
251+# You should have received a copy of the GNU Lesser General Public License
252+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
253+
254+"""
255+Device specific commands class.
256+"""
257+
258+import os
259+import subprocess
260+import sys
261+import random
262+import string
263+
264+from lava.config import InteractiveConfig
265+from lava.device import get_known_device
266+from lava.tool.command import Command, CommandGroup
267+from lava.tool.errors import CommandError
268+from lava_tool.utils import has_command
269+
270+# Default lava-dispatcher paths.
271+DEFAULT_DISPATCHER_PATH = os.path.join("etc", "lava-dispatcher")
272+USER_DISPATCHER_PATH = os.path.join(os.path.expanduser("~"), ".config",
273+ "lava-dispatcher")
274+# Default devices path, has to be joined with the dispatcher path.
275+DEFAULT_DEVICES_PATH = "devices"
276+DEVICE_FILE_SUFFIX = "conf"
277+
278+
279+class device(CommandGroup):
280+ """LAVA devices handling."""
281+
282+ namespace = "lava.device.commands"
283+
284+
285+class BaseCommand(Command):
286+ """Base command for device commands."""
287+ def __init__(self, parser, args):
288+ super(BaseCommand, self).__init__(parser, args)
289+ self.config = InteractiveConfig(
290+ force_interactive=self.args.interactive)
291+
292+ @classmethod
293+ def register_arguments(cls, parser):
294+ super(BaseCommand, cls).register_arguments(parser)
295+ parser.add_argument("-i", "--interactive",
296+ action='store_true',
297+ help=("Forces asking for input parameters even if "
298+ "we already have them cached."))
299+
300+ @classmethod
301+ def _get_dispatcher_paths(cls):
302+ """Tries to get the dispatcher from lava-dispatcher."""
303+ try:
304+ from lava_dispatcher.config import write_path
305+ return write_path()
306+ except ImportError:
307+ raise CommandError("Cannot find lava-dispatcher installation.")
308+
309+ @classmethod
310+ def _choose_devices_path(cls, paths):
311+ """Picks the first path that is writable by the user.
312+
313+ :param paths: A list of paths.
314+ :return The first path where it is possible to write.
315+ """
316+ valid_path = None
317+ for path in paths:
318+ path = os.path.join(path, DEFAULT_DEVICES_PATH)
319+ if os.path.exists(path):
320+ name = "".join(random.choice(string.ascii_letters)
321+ for x in range(6))
322+ test_file = os.path.join(path, name)
323+ try:
324+ fp = open(test_file, 'a')
325+ fp.close()
326+ except IOError:
327+ # Cannot write here.
328+ continue
329+ else:
330+ valid_path = path
331+ if os.path.isfile(test_file):
332+ os.unlink(test_file)
333+ break
334+ else:
335+ try:
336+ os.makedirs(path)
337+ except OSError:
338+ # Cannot write here either.
339+ continue
340+ else:
341+ valid_path = path
342+ break
343+ else:
344+ raise CommandError("Insufficient permissions to create new "
345+ "devices.")
346+ return valid_path
347+
348+ @classmethod
349+ def _get_devices_path(cls):
350+ """Gets the path to the devices in the LAVA dispatcher."""
351+ dispatcher_paths = cls._get_dispatcher_paths()
352+ return cls._choose_devices_path(dispatcher_paths)
353+
354+ @classmethod
355+ def edit_config_file(cls, config_file):
356+ """Opens the specified file with the default file editor.
357+
358+ :param config_file: The file to edit.
359+ """
360+ editor = os.environ.get("EDITOR", None)
361+ if editor is None:
362+ if has_command("sensible-editor"):
363+ editor = "sensible-editor"
364+ elif has_command("xdg-open"):
365+ editor = "xdg-open"
366+ else:
367+ # We really do not know how to open a file.
368+ print >> sys.stdout, ("Cannot find an editor to open the "
369+ "file '{0}'.".format(config_file))
370+ print >> sys.stdout, ("Either set the 'EDITOR' environment "
371+ "variable, or install 'sensible-editor' "
372+ "or 'xdg-open'.")
373+ sys.exit(-1)
374+
375+ try:
376+ subprocess.Popen([editor, config_file]).wait()
377+ except Exception:
378+ raise CommandError("Error opening the file '{0}' with the "
379+ "following editor: {1}.".format(config_file,
380+ editor))
381+
382+ @classmethod
383+ def _get_device_file(cls, file_name):
384+ """Retrieves the config file name specified, if it exists.
385+
386+ :param file_name: The config file name to search.
387+ :return The path to the file, or None if it does not exist.
388+ """
389+ try:
390+ from lava_dispatcher.config import get_config_file
391+
392+ return get_config_file(os.path.join(DEFAULT_DEVICES_PATH,
393+ file_name))
394+ except ImportError:
395+ raise CommandError("Cannot find lava-dispatcher installation.")
396+
397+
398+class add(BaseCommand):
399+ """Adds a new device."""
400+
401+ @classmethod
402+ def register_arguments(cls, parser):
403+ super(add, cls).register_arguments(parser)
404+ parser.add_argument("DEVICE", help="The name of the device to add.")
405+
406+ def invoke(self):
407+ real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
408+
409+ if self._get_device_file(real_file_name):
410+ print >> sys.stdout, ("A device configuration file named '{0}' "
411+ "already exists.".format(real_file_name))
412+ print >> sys.stdout, ("Use 'lava device config {0}' to edit "
413+ "it.".format(self.args.DEVICE))
414+ sys.exit(-1)
415+
416+ devices_path = self._get_devices_path()
417+ device_conf_file = os.path.abspath(os.path.join(devices_path,
418+ real_file_name))
419+
420+ device = get_known_device(self.args.DEVICE)
421+ device.write(device_conf_file)
422+
423+ print >> sys.stdout, ("Created device file '{0}' in: {1}".format(
424+ real_file_name, devices_path))
425+ self.edit_config_file(device_conf_file)
426+
427+
428+class remove(BaseCommand):
429+ """Removes the specified device."""
430+
431+ @classmethod
432+ def register_arguments(cls, parser):
433+ super(remove, cls).register_arguments(parser)
434+ parser.add_argument("DEVICE",
435+ help="The name of the device to remove.")
436+
437+ def invoke(self):
438+ real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
439+ device_conf = self._get_device_file(real_file_name)
440+
441+ if device_conf:
442+ try:
443+ os.remove(device_conf)
444+ print >> sys.stdout, ("Device configuration file '{0}' "
445+ "removed.".format(real_file_name))
446+ except OSError:
447+ raise CommandError("Cannot remove file '{0}' at: {1}.".format(
448+ real_file_name, os.path.dirname(device_conf)))
449+ else:
450+ print >> sys.stdout, ("No device configuration file '{0}' "
451+ "found.".format(real_file_name))
452+
453+
454+class config(BaseCommand):
455+ """Opens the specified device config file."""
456+ @classmethod
457+ def register_arguments(cls, parser):
458+ super(config, cls).register_arguments(parser)
459+ parser.add_argument("DEVICE",
460+ help="The name of the device to edit.")
461+
462+ @classmethod
463+ def can_edit_file(cls, conf_file):
464+ """Checks if a file can be opend in write mode.
465+
466+ :param conf_file: The path to the file.
467+ :return True if it is possible to write on the file, False otherwise.
468+ """
469+ can_edit = True
470+ try:
471+ fp = open(conf_file, "a")
472+ fp.close()
473+ except IOError:
474+ can_edit = False
475+ return can_edit
476+
477+ def invoke(self):
478+ real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX])
479+
480+ device_conf = self._get_device_file(real_file_name)
481+ if device_conf and self.can_edit_file(device_conf):
482+ self.edit_config_file(device_conf)
483+ else:
484+ raise CommandError("Cannot edit file '{0}' at: "
485+ "{1}.".format(real_file_name, device_conf))
486
487=== added file 'lava/device/templates.py'
488--- lava/device/templates.py 1970-01-01 00:00:00 +0000
489+++ lava/device/templates.py 2013-06-19 06:51:26 +0000
490@@ -0,0 +1,42 @@
491+# Copyright (C) 2013 Linaro Limited
492+#
493+# Author: Milo Casagrande <milo.casagrande@linaro.org>
494+#
495+# This file is part of lava-tool.
496+#
497+# lava-tool is free software: you can redistribute it and/or modify
498+# it under the terms of the GNU Lesser General Public License version 3
499+# as published by the Free Software Foundation
500+#
501+# lava-tool is distributed in the hope that it will be useful,
502+# but WITHOUT ANY WARRANTY; without even the implied warranty of
503+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
504+# GNU General Public License for more details.
505+#
506+# You should have received a copy of the GNU Lesser General Public License
507+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
508+
509+"""
510+This is just a place where to store a template like dictionary that
511+will be used to serialize a Device object.
512+"""
513+
514+DEFAULT_TEMPLATE = {
515+ 'device_type': None,
516+ 'hostname': None,
517+ 'connection_command': None,
518+}
519+
520+# Dictionary with templates of known devices.
521+KNOWN_TEMPLATES = {
522+ 'panda': {
523+ 'device_type': 'panda',
524+ 'hostname': None,
525+ 'connection_command': None,
526+ },
527+ 'vexpress': {
528+ 'device_type': 'vexpress',
529+ 'hostname': None,
530+ 'connection_command': None,
531+ },
532+}
533
534=== added directory 'lava/device/tests'
535=== added file 'lava/device/tests/__init__.py'
536=== added file 'lava/device/tests/test_commands.py'
537--- lava/device/tests/test_commands.py 1970-01-01 00:00:00 +0000
538+++ lava/device/tests/test_commands.py 2013-06-19 06:51:26 +0000
539@@ -0,0 +1,175 @@
540+# Copyright (C) 2013 Linaro Limited
541+#
542+# Author: Milo Casagrande <milo.casagrande@linaro.org>
543+#
544+# This file is part of lava-tool.
545+#
546+# lava-tool is free software: you can redistribute it and/or modify
547+# it under the terms of the GNU Lesser General Public License version 3
548+# as published by the Free Software Foundation
549+#
550+# lava-tool is distributed in the hope that it will be useful,
551+# but WITHOUT ANY WARRANTY; without even the implied warranty of
552+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
553+# GNU General Public License for more details.
554+#
555+# You should have received a copy of the GNU Lesser General Public License
556+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
557+
558+"""
559+Commands class unit tests.
560+"""
561+
562+import os
563+import shutil
564+import sys
565+import tempfile
566+
567+from mock import MagicMock
568+from unittest import TestCase
569+
570+from lava.device.commands import (
571+ BaseCommand,
572+ add,
573+ config,
574+ remove,
575+)
576+
577+from lava.tool.errors import CommandError
578+
579+
580+class CommandsTest(TestCase):
581+ def setUp(self):
582+ # Fake the stdout.
583+ self.original_stdout = sys.stdout
584+ sys.stdout = open("/dev/null", "w")
585+ self.original_stderr = sys.stderr
586+ sys.stderr = open("/dev/null", "w")
587+ self.original_stdin = sys.stdin
588+
589+ self.device = "a_fake_panda02"
590+
591+ self.tempdir = tempfile.mkdtemp()
592+ self.parser = MagicMock()
593+ self.args = MagicMock()
594+ self.args.interactive = MagicMock(return_value=False)
595+ self.args.DEVICE = self.device
596+
597+ self.config = MagicMock()
598+ self.config.get = MagicMock(return_value=self.tempdir)
599+
600+ def tearDown(self):
601+ sys.stdin = self.original_stdin
602+ sys.stdout = self.original_stdout
603+ sys.stderr = self.original_stderr
604+ shutil.rmtree(self.tempdir)
605+
606+ def test_get_devices_path_0(self):
607+ # Tests that the correct devices path is returned and created.
608+ base_command = BaseCommand(self.parser, self.args)
609+ base_command.config = self.config
610+ BaseCommand._get_dispatcher_paths = MagicMock(return_value=[
611+ self.tempdir])
612+ obtained = base_command._get_devices_path()
613+ expected_path = os.path.join(self.tempdir, "devices")
614+ self.assertEqual(expected_path, obtained)
615+ self.assertTrue(os.path.isdir(expected_path))
616+
617+ def test_get_devices_path_1(self):
618+ # Tests that when passing a path that is not writable, CommandError
619+ # is raised.
620+ base_command = BaseCommand(self.parser, self.args)
621+ base_command.config = self.config
622+ BaseCommand._get_dispatcher_paths = MagicMock(
623+ return_value=["/", "/root", "/root/tmpdir"])
624+ self.assertRaises(CommandError, base_command._get_devices_path)
625+
626+ def test_choose_devices_path(self):
627+ # Tests that when passing more than one path, the first writable one
628+ # is returned.
629+ base_command = BaseCommand(self.parser, self.args)
630+ base_command.config = self.config
631+ obtained = base_command._choose_devices_path(
632+ ["/", "/root", self.tempdir, os.path.expanduser("~")])
633+ expected = os.path.join(self.tempdir, "devices")
634+ self.assertEqual(expected, obtained)
635+
636+ def test_add_invoke(self):
637+ # Tests invocation of the add command. Verifies that the conf file is
638+ # written to disk.
639+ add_command = add(self.parser, self.args)
640+ add_command.edit_config_file = MagicMock()
641+ add_command._get_device_file = MagicMock(return_value=None)
642+ add_command._get_devices_path = MagicMock(return_value=self.tempdir)
643+ add_command.invoke()
644+
645+ expected_path = os.path.join(self.tempdir,
646+ ".".join([self.device, "conf"]))
647+ self.assertTrue(os.path.isfile(expected_path))
648+
649+ def test_remove_invoke(self):
650+ # Tests invocation of the remove command. Verifies that the conf file
651+ # has been correctly removed.
652+ # First we add a new conf file, then we remove it.
653+ add_command = add(self.parser, self.args)
654+ add_command.edit_config_file = MagicMock()
655+ add_command._get_device_file = MagicMock(return_value=None)
656+ add_command._get_devices_path = MagicMock(return_value=self.tempdir)
657+ add_command.invoke()
658+
659+ expected_path = os.path.join(self.tempdir,
660+ ".".join([self.device, "conf"]))
661+
662+ remove_command = remove(self.parser, self.args)
663+ remove_command._get_device_file = MagicMock(return_value=expected_path)
664+ remove_command._get_devices_path = MagicMock(return_value=self.tempdir)
665+ remove_command.invoke()
666+
667+ self.assertFalse(os.path.isfile(expected_path))
668+
669+ def test_remove_invoke_raises(self):
670+ # Tests invocation of the remove command, with a non existent device
671+ # configuration file.
672+ remove_command = remove(self.parser, self.args)
673+ remove_command._get_device_file = MagicMock(return_value="/root")
674+
675+ self.assertRaises(CommandError, remove_command.invoke)
676+
677+ def test_config_invoke_raises_0(self):
678+ # Tests invocation of the config command, with a non existent device
679+ # configuration file.
680+ config_command = config(self.parser, self.args)
681+ config_command._get_device_file = MagicMock(return_value=None)
682+
683+ self.assertRaises(CommandError, config_command.invoke)
684+
685+ def test_config_invoke_raises_1(self):
686+ # Tests invocation of the config command, with a non writable file.
687+ # Hopefully tests are not run as root.
688+ config_command = config(self.parser, self.args)
689+ config_command._get_device_file = MagicMock(return_value="/etc/passwd")
690+
691+ self.assertRaises(CommandError, config_command.invoke)
692+
693+ def test_can_edit_file(self):
694+ # Tests the can_edit_file method of the config command.
695+ # This is to make sure the device config file is not erased when
696+ # checking if it is possible to open it.
697+ expected = ("hostname = a_fake_panda02\nconnection_command = \n"
698+ "device_type = panda\n")
699+
700+ config_command = config(self.parser, self.args)
701+ try:
702+ conf_file = tempfile.NamedTemporaryFile(delete=False)
703+
704+ with open(conf_file.name, "w") as f:
705+ f.write(expected)
706+
707+ self.assertTrue(config_command.can_edit_file(conf_file.name))
708+ obtained = ""
709+ with open(conf_file.name) as f:
710+ obtained = f.read()
711+
712+ self.assertEqual(expected, obtained)
713+ finally:
714+ os.unlink(conf_file.name)
715
716=== added file 'lava/device/tests/test_device.py'
717--- lava/device/tests/test_device.py 1970-01-01 00:00:00 +0000
718+++ lava/device/tests/test_device.py 2013-06-19 06:51:26 +0000
719@@ -0,0 +1,123 @@
720+# Copyright (C) 2013 Linaro Limited
721+#
722+# Author: Milo Casagrande <milo.casagrande@linaro.org>
723+#
724+# This file is part of lava-tool.
725+#
726+# lava-tool is free software: you can redistribute it and/or modify
727+# it under the terms of the GNU Lesser General Public License version 3
728+# as published by the Free Software Foundation
729+#
730+# lava-tool is distributed in the hope that it will be useful,
731+# but WITHOUT ANY WARRANTY; without even the implied warranty of
732+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
733+# GNU General Public License for more details.
734+#
735+# You should have received a copy of the GNU Lesser General Public License
736+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
737+
738+"""
739+Device class unit tests.
740+"""
741+
742+import os
743+import sys
744+import tempfile
745+
746+from StringIO import StringIO
747+from unittest import TestCase
748+
749+from lava.device import (
750+ Device,
751+ get_known_device,
752+)
753+from lava.tool.errors import CommandError
754+
755+
756+class DeviceTest(TestCase):
757+
758+ def setUp(self):
759+ # Fake the stdin and the stdout.
760+ self.original_stdout = sys.stdout
761+ sys.stdout = open("/dev/null", "w")
762+ self.original_stdin = sys.stdin
763+ sys.stdin = StringIO()
764+ self.temp_file = tempfile.NamedTemporaryFile(delete=False)
765+
766+ def tearDown(self):
767+ sys.stdout = self.original_stdout
768+ sys.stdin = self.original_stdin
769+ os.unlink(self.temp_file.name)
770+
771+ def test_get_known_device_panda_0(self):
772+ # User creates a new device with a guessable name for a device.
773+ instance = get_known_device('panda_new_01')
774+ self.assertIsInstance(instance, Device)
775+ self.assertEqual(instance.template['device_type'], 'panda')
776+ self.assertIsNone(instance.device_type)
777+
778+ def test_get_known_device_panda_1(self):
779+ # User creates a new device with a guessable name for a device.
780+ # Name passed has capital letters.
781+ instance = get_known_device('new_PanDa_02')
782+ self.assertIsInstance(instance, Device)
783+ self.assertEqual(instance.template['device_type'], 'panda')
784+ self.assertIsNone(instance.device_type)
785+
786+ def test_get_known_device_vexpress_0(self):
787+ # User creates a new device with a guessable name for a device.
788+ # Name passed has capital letters.
789+ instance = get_known_device('a_VexPress_Device')
790+ self.assertIsInstance(instance, Device)
791+ self.assertEqual(instance.template['device_type'], 'vexpress')
792+ self.assertIsNone(instance.device_type)
793+
794+ def test_get_known_device_vexpress_1(self):
795+ # User creates a new device with a guessable name for a device.
796+ instance = get_known_device('another-vexpress')
797+ self.assertIsInstance(instance, Device)
798+ self.assertEqual(instance.template['device_type'], 'vexpress')
799+ self.assertIsNone(instance.device_type)
800+
801+ def test_instance_update(self):
802+ # Tests that when calling the _update() function with an known device
803+ # it does not update the device_type instance attribute, and that the
804+ # template contains the correct name.
805+ instance = get_known_device('Another_PanDa_device')
806+ instance._update()
807+ self.assertIsInstance(instance, Device)
808+ self.assertEqual(instance.template['device_type'], 'panda')
809+ self.assertIsNone(instance.device_type)
810+
811+ def test_get_known_device_unknown(self):
812+ # User tries to create a new device with an unknown device type. She
813+ # is asked to insert the device type and types 'a_fake_device'.
814+ sys.stdin = StringIO('a_fake_device')
815+ instance = get_known_device('a_fake_device')
816+ self.assertIsInstance(instance, Device)
817+ self.assertEqual(instance.device_type, 'a_fake_device')
818+
819+ def test_get_known_device_known(self):
820+ # User tries to create a new device with a not recognizable name.
821+ # She is asked to insert the device type and types 'panda'.
822+ sys.stdin = StringIO("panda")
823+ instance = get_known_device("another_fake_device")
824+ self.assertIsInstance(instance, Device)
825+ self.assertEqual(instance.template["device_type"], "panda")
826+
827+ def test_get_known_device_raises(self):
828+ # User tries to create a new device, but in some way nothing is passed
829+ # on the command line when asked.
830+ self.assertRaises(CommandError, get_known_device, 'a_fake_device')
831+
832+ def test_device_write(self):
833+ # User tries to create a new panda device. The conf file is written
834+ # and contains the expected results.
835+ expected = ("hostname = panda02\nconnection_command = \n"
836+ "device_type = panda\n")
837+ instance = get_known_device("panda02")
838+ instance.write(self.temp_file.name)
839+ obtained = ""
840+ with open(self.temp_file.name) as f:
841+ obtained = f.read()
842+ self.assertEqual(expected, obtained)
843
844=== modified file 'lava/job/templates.py'
845--- lava/job/templates.py 2013-05-28 22:08:12 +0000
846+++ lava/job/templates.py 2013-06-19 06:51:26 +0000
847@@ -16,11 +16,7 @@
848 # You should have received a copy of the GNU Lesser General Public License
849 # along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
850
851-class Parameter(object):
852-
853- def __init__(self, id, depends=None):
854- self.id = id
855- self.depends = depends
856+from lava.config import Parameter
857
858 device_type = Parameter("device_type")
859 prebuilt_image = Parameter("prebuilt_image", depends=device_type)
860
861=== modified file 'lava_tool/tests/__init__.py'
862--- lava_tool/tests/__init__.py 2013-05-27 20:51:39 +0000
863+++ lava_tool/tests/__init__.py 2013-06-19 06:51:26 +0000
864@@ -26,22 +26,24 @@
865
866 def app_modules():
867 return [
868- 'lava_tool.commands',
869- 'lava_tool.dispatcher',
870- 'lava_tool.interface',
871- 'lava_dashboard_tool.commands',
872- ]
873+ 'lava_tool.commands',
874+ 'lava_tool.dispatcher',
875+ 'lava_tool.interface',
876+ 'lava_dashboard_tool.commands',
877+ ]
878
879
880 def test_modules():
881 return [
882- 'lava_tool.tests.test_authtoken',
883- 'lava_tool.tests.test_auth_commands',
884- 'lava_tool.tests.test_commands',
885- 'lava_dashboard_tool.tests.test_commands',
886- 'lava.job.tests.test_job',
887- 'lava.job.tests.test_commands',
888- ]
889+ 'lava_tool.tests.test_authtoken',
890+ 'lava_tool.tests.test_auth_commands',
891+ 'lava_tool.tests.test_commands',
892+ 'lava_dashboard_tool.tests.test_commands',
893+ 'lava.job.tests.test_job',
894+ 'lava.job.tests.test_commands',
895+ 'lava.device.tests.test_device',
896+ 'lava.device.tests.test_commands',
897+ ]
898
899
900 def test_suite():
901@@ -52,6 +54,7 @@
902 modules = app_modules() + test_modules()
903 suite = unittest.TestSuite()
904 loader = unittest.TestLoader()
905+
906 for name in modules:
907 unit_suite = loader.loadTestsFromName(name)
908 suite.addTests(unit_suite)
909
910=== modified file 'lava_tool/tests/test_authtoken.py'
911--- lava_tool/tests/test_authtoken.py 2013-05-22 13:45:44 +0000
912+++ lava_tool/tests/test_authtoken.py 2013-06-19 06:51:26 +0000
913@@ -24,7 +24,6 @@
914 import StringIO
915 from unittest import TestCase
916 import urlparse
917-import sys
918 import xmlrpclib
919
920 from mocker import ARGS, KWARGS, Mocker
921
922=== added file 'lava_tool/utils.py'
923--- lava_tool/utils.py 1970-01-01 00:00:00 +0000
924+++ lava_tool/utils.py 2013-06-19 06:51:26 +0000
925@@ -0,0 +1,33 @@
926+# Copyright (C) 2013 Linaro Limited
927+#
928+# Author: Milo Casagrande <milo.casagrande@linaro.org>
929+#
930+# This file is part of lava-tool.
931+#
932+# lava-tool is free software: you can redistribute it and/or modify
933+# it under the terms of the GNU Lesser General Public License version 3
934+# as published by the Free Software Foundation
935+#
936+# lava-tool is distributed in the hope that it will be useful,
937+# but WITHOUT ANY WARRANTY; without even the implied warranty of
938+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
939+# GNU General Public License for more details.
940+#
941+# You should have received a copy of the GNU Lesser General Public License
942+# along with lava-tool. If not, see <http://www.gnu.org/licenses/>.
943+
944+import subprocess
945+
946+
947+def has_command(command):
948+ """Checks that the given command is available.
949+
950+ :param command: The name of the command to check availability.
951+ """
952+ command_available = True
953+ try:
954+ subprocess.check_call(["which", command],
955+ stdout=open('/dev/null', 'w'))
956+ except subprocess.CalledProcessError:
957+ command_available = False
958+ return command_available
959
960=== modified file 'setup.py'
961--- setup.py 2013-05-17 19:21:51 +0000
962+++ setup.py 2013-06-19 06:51:26 +0000
963@@ -53,5 +53,8 @@
964 'versiontools >= 1.3.1'
965 ],
966 setup_requires=['versiontools >= 1.3.1'],
967- tests_require=['mocker >= 1.0'],
968+ tests_require=[
969+ 'mocker >= 1.0',
970+ 'mock >= 0.7.2'
971+ ],
972 zip_safe=True)

Subscribers

People subscribed via source and target branches