Merge lp:~milo/lava-tool/lava-167 into lp:~linaro-validation/lava-tool/trunk
- lava-167
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Antonio Terceiro | Needs Fixing | ||
Linaro Automation & Validation | Pending | ||
Review via email:
|
Commit message
Description of the change
This is work in progress for Jira card LAVA-167. Early work to take a look if I'm on the right track.

Milo Casagrande (milo) wrote : | # |
- 216. By Milo Casagrande
-
Added missing dependencies to run tests.
* pep8 and pyflakes depends on testtools, since it gives a nicer
output on errors.

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.
- 217. By Milo Casagrande
-
Fixed config command test.
- 218. By Milo Casagrande
-
Refactored command tests: defined device name on setup.

Zygmunt Krynicki (zyga) wrote : | # |
9 +versiontools-
You probably want to ignore *.egg

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.
- 219. By Milo Casagrande
-
Fixed ignored .egg pattern.

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
- 220. By Milo Casagrande
-
Removed pep8 and pyflakes tests.
* At this stage better have them as part of a pre-commit hook.

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/

Antonio Terceiro (terceiro) wrote : | # |
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=
> +test_id_
>
> === 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=
> +
> set -e
>
> if test -z "$VIRTUAL_ENV"; then
> set -x
> - virtualenv ci-build-venv
> - . ci-build-
> + virtualenv $VENV_DIR
> + . $VENV_DIR/
> 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=
>
> 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/
> --- lava/device/
> +++ lava/device/
> @@ -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://
> +
> +import re
> +
> +from lava.device.
> +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 ...
- 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.

Milo Casagrande (milo) wrote : | # |
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/
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(
>
> 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(
>> +}
>
> 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.
> 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.
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...
- 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.

Antonio Terceiro (terceiro) wrote : | # |
> == added file 'lava/device/
> -- lava/device/
> ++ lava/device/
> @ -0,0 +1,237 @@
[snip]
> # Default lava-dispatcher paths.
> DEFAULT_
> USER_DISPATCHER
> "lava-dispatcher")
It would be better to
from lava_dispatcher
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_
> DEVICE_FILE_SUFFIX = "conf"
>
>
> class device(
> """LAVA devices handling."""
>
> namespace = "lava.device.
>
>
> class BaseCommand(
> """Base command for device commands."""
> def __init__(self, parser, args):
> super(BaseCommand, self)._
> self.config = InteractiveConfig(
> force_interacti
>
> @classmethod
> def register_
> super(BaseCommand, cls).register_
> parser.
> action=
> help=("Forces asking for input parameters even if "
> "we already have them cached."))
>
> @classmethod
> def _get_dispatcher
> """Tries to get the dispatcher from lava-dispatcher."""
> global_paths = []
> try:
> from lava_dispatcher
>
> 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_
> """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.
> print >> sys.stdout, "".join(out_list)
> while True:
> try:
> choice = raw_input(
> 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...
- 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.

Milo Casagrande (milo) wrote : | # |
On Thu, Jun 13, 2013 at 2:46 AM, Antonio Terceiro
<email address hidden> wrote:
>> == added file 'lava/device/
>> -- lava/device/
>> ++ lava/device/
>> @ -0,0 +1,237 @@
> [snip]
>> # Default lava-dispatcher paths.
>> DEFAULT_
>> USER_DISPATCHER
>> "lava-dispatcher")
>
> It would be better to
>
> from lava_dispatcher
>
> 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
methods that deal with picking the correct path, I think it is not
necessary to import "user_config_path" and "system_
the dispatcher.
Since we get the paths via lava_dispatcher
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_
>
> 2) finding an existing configuration file to edit:
>
> from lava_dispatcher
> [...]
>
> conf_file = get_config_
> 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
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
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
file that is not writable by the user (in particular for the "config"
command).
> I think something l...
- 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.

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_
> >
> > 2) finding an existing configuration file to edit:
> >
> > from lava_dispatcher
> > [...]
> >
> > conf_file = get_config_
> > 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
> 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
> 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/
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_
Maybe we could just export [system_
lava_dispatcher
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
> file that is not writable by the user (in particular for the "config"
> command).
Yep.
--
Antonio Terceiro
Software Engineer - Linaro
http://
- 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.

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/
> 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_
>
> Maybe we could just export [system_
> lava_dispatcher
> 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
- 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
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) |
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).