Merge lp:~milo/lava-tool/device-parameters into lp:~linaro-validation/lava-tool/trunk
- device-parameters
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 188 |
Proposed branch: | lp:~milo/lava-tool/device-parameters |
Merge into: | lp:~linaro-validation/lava-tool/trunk |
Prerequisite: | lp:~milo/lava-tool/lava-168 |
Diff against target: |
1648 lines (+1001/-234) 23 files modified
.bzrignore (+1/-0) .coveragerc (+13/-0) HACKING (+17/-0) ci-build (+12/-0) lava/config.py (+176/-75) lava/device/__init__.py (+25/-56) lava/device/commands.py (+3/-3) lava/device/templates.py (+37/-9) lava/device/tests/test_commands.py (+79/-4) lava/device/tests/test_device.py (+52/-46) lava/helper/command.py (+1/-1) lava/helper/template.py (+44/-0) lava/helper/tests/test_command.py (+70/-0) lava/helper/tests/test_dispatcher.py (+27/-2) lava/job/__init__.py (+2/-16) lava/job/commands.py (+3/-2) lava/job/templates.py (+1/-1) lava/job/tests/test_commands.py (+6/-6) lava/job/tests/test_job.py (+26/-13) lava/parameter.py (+75/-0) lava/tests/test_config.py (+278/-0) lava/tests/test_parameter.py (+51/-0) lava_tool/tests/__init__.py (+2/-0) |
To merge this branch: | bzr merge lp:~milo/lava-tool/device-parameters |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Antonio Terceiro | Needs Fixing | ||
Linaro Validation Team | Pending | ||
Review via email: mp+170653@code.launchpad.net |
Commit message
Description of the change
This new branch addresses some of the concerns raised while reviewing lp:~milo/lava-tool/lava-168.
It is in a separate branch since the previous merge proposal was already quite big.
* Refactored the Config class: now there are a generic Config class with most of the logic to store values, and an InteractiveConfig class that overrides only a subset of methods from Config in order to handle user input.
* The Parameter class has been refactored and moved into its own file. Two new attributes have been added: 'value', mostly needed for the Device templates; 'asked', needed when using the InteractiveConfig class in order not to ask twice the same parameter value.
* Tests have been added and old ones refactored.
- 226. By Milo Casagrande
-
Refactored constructor.
- 227. By Milo Casagrande
-
Fixed the device templates.
* Needed to specialized some of the template parameters due to
object references. - 228. By Milo Casagrande
-
Fixed the Config classes.
* Parameter values can also be empty.
* If the user presses Enter with an old value, keep the old value,
otherwise value will be empty. - 229. By Milo Casagrande
-
Fixed tests.
- 230. By Milo Casagrande
-
Added two more tests.
- 231. By Milo Casagrande
-
Fixed typos.
Milo Casagrande (milo) wrote : | # |
Hello Antonio!
Thanks for going through this!
On Fri, Jun 21, 2013 at 1:40 PM, Antonio Terceiro
<email address hidden> wrote:
>
>> +class InteractiveConf
>> + """An interactive config.
>> +
>> + If a value is not found in the config file, it will ask it and then stores
>> + it.
>> + """
>> + def __init__(self, force_interacti
>> + super(Interacti
>> + self._force_
>> +
>> + def _calculate_
>> + """Calculates the config section of the specified parameter.
>> +
>> + :param parameter: The parameter to calculate the section of.
>> + :type Parameter
>> + :return The config section.
>> + """
>> + section = DEFAULT_SECTION
>> + if parameter.depends:
>> + # This is mostly relevant to the InteractiveConfig class.
>> + # If a parameter has a dependency we do as follows:
>> + # - Get the dependency cached value
>> + # - Get the dependency value from the config file
>> + # - If both are None, it means the dependency has not been inserted
>> + # yet, and we ask for it.
>
> I have the impression that just calling get() would already to this.
>
>> + depend_section = self._calculate
>> +
>> + cached_value = self._get_
>> + depend_section)
>> + config_value = self._get_
>> + depend_section)
>> +
>> + # Honor the cached value.
>> + value = cached_value or config_value
>> + if not value:
>> + value = self.get(
>> + parameter.
>> + section = "{0}={1}
>> + return section
>
> Also, I don't understand why you need to override this method. I can't see why
> the logic for determining the section to write a config entry to would be
> different on interactive config wrt to plain config - as long as get() does the
> right thing.
Yeah, this is something I forgot to remove.
I had a problem in the device template and how I was "copying" the
default template, and when trying to figure out what was happening, I
override that method.
The two gets (from cache and config) were done to avoid multiple calls
to the _calculate_
section parameter to get()).
>> -
>> - def _update(self):
>> - """Updates the template with the values specified for this class.
>> -
>> - Subclasses need to override this when they add more specific
>> - attributes.
>> + write_file.
>> +
>> + def update(self, config):
>> + """Updates the Device object values based on the provided config.
>> +
>> + :param config: A Config instance.
>> """
>> - # This is needed for the 'default' behavior. If we matched a known
>> - # device, we do not need...
- 232. By Milo Casagrande
-
Use copy instead of deepcopy.
* Used copy instead of deepcopy to avoid wrong references
when updating the template. - 233. By Milo Casagrande
-
Removed overridden method.
* The overridden method is not necessary.
- 234. By Milo Casagrande
-
Fixed typo and refactore function name.
- 235. By Milo Casagrande
-
Removed not necessary test.
- 236. By Milo Casagrande
-
Refactored where user input is taken.
* Moved raw_input logic from the Config into the Parameter.
* Fixed old tests.
* Added new test class for the Parameter class. - 237. By Milo Casagrande
-
Fixed how the prompt is built.
- 238. By Milo Casagrande
-
Merged missing commit.
- 239. By Milo Casagrande
-
Readded asked attribute.
- 240. By Milo Casagrande
-
Refactored the expand_template function.
* Created a helper module for template.
* Moved the expand_template function in the new module.
* Updated lava job command to use the new helper function. - 241. By Milo Casagrande
-
Removed unused import statement.
- 242. By Milo Casagrande
-
Added two more tests for the dispatcher helper functions.
- 243. By Milo Casagrande
-
Fixed list creation.
- 244. By Milo Casagrande
-
Added more tests for the helper command functions.
- 245. By Milo Casagrande
-
Fixed file test.
- 246. By Milo Casagrande
-
Added more tests to the device commands.
- 247. By Milo Casagrande
-
Added one more test for device commands.
- 248. By Milo Casagrande
-
Fixed import.
Preview Diff
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2013-06-27 12:48:26 +0000 | |||
3 | +++ .bzrignore 2013-06-27 12:48:26 +0000 | |||
4 | @@ -5,3 +5,4 @@ | |||
5 | 5 | /tags | 5 | /tags |
6 | 6 | .testrepository | 6 | .testrepository |
7 | 7 | *.egg | 7 | *.egg |
8 | 8 | lava_tool_coverage | ||
9 | 8 | 9 | ||
10 | === added file '.coveragerc' | |||
11 | --- .coveragerc 1970-01-01 00:00:00 +0000 | |||
12 | +++ .coveragerc 2013-06-27 12:48:26 +0000 | |||
13 | @@ -0,0 +1,13 @@ | |||
14 | 1 | [run] | ||
15 | 2 | branch = True | ||
16 | 3 | source = . | ||
17 | 4 | omit = | ||
18 | 5 | setup* | ||
19 | 6 | |||
20 | 7 | [report] | ||
21 | 8 | precision = 2 | ||
22 | 9 | show_missing = True | ||
23 | 10 | |||
24 | 11 | [html] | ||
25 | 12 | title = Code Coverage of lava-tool | ||
26 | 13 | directory = lava_tool_coverage | ||
27 | 0 | 14 | ||
28 | === added file 'HACKING' | |||
29 | --- HACKING 1970-01-01 00:00:00 +0000 | |||
30 | +++ HACKING 2013-06-27 12:48:26 +0000 | |||
31 | @@ -0,0 +1,17 @@ | |||
32 | 1 | Tests Code Coverage | ||
33 | 2 | =================== | ||
34 | 3 | |||
35 | 4 | To have a nicely HTML viewable report on tests code coverage, do as follows: | ||
36 | 5 | |||
37 | 6 | * Install `python-coverage` (`pip install coverage` in case you use pip) | ||
38 | 7 | * Run the following command: | ||
39 | 8 | |||
40 | 9 | python-coverage run -m unittest lava_tool.tests.test_suite 2>/dev/null && python-coverage html | ||
41 | 10 | |||
42 | 11 | * The report will be saved in a directory called `lava_tool_coverage`: open | ||
43 | 12 | the `index.html` file in there to see the report. | ||
44 | 13 | |||
45 | 14 | Notes: | ||
46 | 15 | |||
47 | 16 | * To re-run the coverage report, you have to delete the `lava_tool_coverage` | ||
48 | 17 | directory first, otherwise `python-coverage` will fail. | ||
49 | 0 | 18 | ||
50 | === modified file 'ci-build' | |||
51 | --- ci-build 2013-06-27 12:48:26 +0000 | |||
52 | +++ ci-build 2013-06-27 12:48:26 +0000 | |||
53 | @@ -1,6 +1,8 @@ | |||
54 | 1 | #!/bin/sh | 1 | #!/bin/sh |
55 | 2 | 2 | ||
56 | 3 | VENV_DIR="/tmp/ci-build-venv" | 3 | VENV_DIR="/tmp/ci-build-venv" |
57 | 4 | # Directory where coverage HTML report will be written. | ||
58 | 5 | COVERAGE_REPORT_DIR="lava_tool_coverage" | ||
59 | 4 | 6 | ||
60 | 5 | set -e | 7 | set -e |
61 | 6 | 8 | ||
62 | @@ -26,6 +28,10 @@ | |||
63 | 26 | if ! pip show mock | grep -q mock; then | 28 | if ! pip show mock | grep -q mock; then |
64 | 27 | pip install mock | 29 | pip install mock |
65 | 28 | fi | 30 | fi |
66 | 31 | # Requirement to run code coverage tests. | ||
67 | 32 | if ! pip show coverage | grep -q coverage; then | ||
68 | 33 | pip install coverage | ||
69 | 34 | fi | ||
70 | 29 | 35 | ||
71 | 30 | export LAVACONFIG=/dev/null | 36 | export LAVACONFIG=/dev/null |
72 | 31 | 37 | ||
73 | @@ -45,4 +51,10 @@ | |||
74 | 45 | python -m unittest lava_tool.tests.test_suite < /dev/null | 51 | python -m unittest lava_tool.tests.test_suite < /dev/null |
75 | 46 | fi | 52 | fi |
76 | 47 | 53 | ||
77 | 54 | if test -d $COVERAGE_REPORT_DIR; then | ||
78 | 55 | rm -rf $COVERAGE_REPORT_DIR | ||
79 | 56 | fi | ||
80 | 57 | # Runs python-coverage. | ||
81 | 58 | python-coverage run -m unittest lava_tool.tests.test_suite 2>/dev/null && python-coverage html | ||
82 | 59 | |||
83 | 48 | ./integration-tests | 60 | ./integration-tests |
84 | 49 | 61 | ||
85 | === modified file 'lava/config.py' | |||
86 | --- lava/config.py 2013-06-27 12:48:26 +0000 | |||
87 | +++ lava/config.py 2013-06-27 12:48:26 +0000 | |||
88 | @@ -16,91 +16,192 @@ | |||
89 | 16 | # You should have received a copy of the GNU Lesser General Public License | 16 | # You should have received a copy of the GNU Lesser General Public License |
90 | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. |
91 | 18 | 18 | ||
92 | 19 | """ | ||
93 | 20 | Config class. | ||
94 | 21 | """ | ||
95 | 22 | |||
96 | 19 | import atexit | 23 | import atexit |
97 | 20 | from ConfigParser import ConfigParser, NoOptionError, NoSectionError | ||
98 | 21 | import os | 24 | import os |
99 | 22 | import readline | 25 | import readline |
100 | 23 | 26 | ||
104 | 24 | __all__ = ['InteractiveConfig', 'NonInteractiveConfig'] | 27 | from ConfigParser import ( |
105 | 25 | 28 | ConfigParser, | |
106 | 26 | history = os.path.join(os.path.expanduser("~"), ".lava_history") | 29 | NoOptionError, |
107 | 30 | NoSectionError, | ||
108 | 31 | ) | ||
109 | 32 | |||
110 | 33 | from lava.tool.errors import CommandError | ||
111 | 34 | |||
112 | 35 | __all__ = ['Config', 'InteractiveConfig'] | ||
113 | 36 | |||
114 | 37 | # Store for function calls to be made at exit time. | ||
115 | 38 | AT_EXIT_CALLS = set() | ||
116 | 39 | # Config default section. | ||
117 | 40 | DEFAULT_SECTION = "DEFAULT" | ||
118 | 41 | |||
119 | 42 | HISTORY = os.path.join(os.path.expanduser("~"), ".lava_history") | ||
120 | 27 | try: | 43 | try: |
122 | 28 | readline.read_history_file(history) | 44 | readline.read_history_file(HISTORY) |
123 | 29 | except IOError: | 45 | except IOError: |
124 | 30 | pass | 46 | pass |
150 | 31 | atexit.register(readline.write_history_file, history) | 47 | atexit.register(readline.write_history_file, HISTORY) |
151 | 32 | 48 | ||
152 | 33 | config_file = (os.environ.get('LAVACONFIG') or | 49 | |
153 | 34 | os.path.join(os.path.expanduser('~'), '.lavaconfig')) | 50 | def _run_at_exit(): |
154 | 35 | config_backend = ConfigParser() | 51 | """Runs all the function at exit.""" |
155 | 36 | config_backend.read([config_file]) | 52 | for call in list(AT_EXIT_CALLS): |
156 | 37 | 53 | call() | |
157 | 38 | 54 | atexit.register(_run_at_exit) | |
158 | 39 | def save_config(): | 55 | |
159 | 40 | with open(config_file, 'w') as f: | 56 | |
160 | 41 | config_backend.write(f) | 57 | class Config(object): |
161 | 42 | atexit.register(save_config) | 58 | """A generic config object.""" |
162 | 43 | 59 | def __init__(self): | |
163 | 44 | 60 | # The cache where to store parameters. | |
139 | 45 | class Parameter(object): | ||
140 | 46 | |||
141 | 47 | def __init__(self, id, depends=None): | ||
142 | 48 | self.id = id | ||
143 | 49 | self.depends = depends | ||
144 | 50 | |||
145 | 51 | |||
146 | 52 | class InteractiveConfig(object): | ||
147 | 53 | |||
148 | 54 | def __init__(self, force_interactive=False): | ||
149 | 55 | self._force_interactive = force_interactive | ||
164 | 56 | self._cache = {} | 61 | self._cache = {} |
169 | 57 | 62 | self._config_file = (os.environ.get('LAVACONFIG') or | |
170 | 58 | def get(self, parameter): | 63 | os.path.join(os.path.expanduser('~'), |
171 | 59 | key = parameter.id | 64 | '.lavaconfig')) |
172 | 60 | value = None | 65 | self._config_backend = ConfigParser() |
173 | 66 | self._config_backend.read([self._config_file]) | ||
174 | 67 | AT_EXIT_CALLS.add(self.save) | ||
175 | 68 | |||
176 | 69 | def _calculate_config_section(self, parameter): | ||
177 | 70 | """Calculates the config section of the specified parameter. | ||
178 | 71 | |||
179 | 72 | :param parameter: The parameter to calculate the section of. | ||
180 | 73 | :type Parameter | ||
181 | 74 | :return The config section. | ||
182 | 75 | """ | ||
183 | 76 | section = DEFAULT_SECTION | ||
184 | 61 | if parameter.depends: | 77 | if parameter.depends: |
187 | 62 | pass | 78 | section = "{0}={1}".format(parameter.depends.id, |
188 | 63 | config_section = parameter.depends.id + '=' + self.get(parameter.depends) | 79 | self.get(parameter.depends)) |
189 | 80 | return section | ||
190 | 81 | |||
191 | 82 | def get(self, parameter, section=None): | ||
192 | 83 | """Retrieves a Parameter value. | ||
193 | 84 | |||
194 | 85 | The value is taken either from the Parameter itself, or from the cache, | ||
195 | 86 | or from the config file. | ||
196 | 87 | |||
197 | 88 | :param parameter: The parameter to search. | ||
198 | 89 | :type Parameter | ||
199 | 90 | :return The parameter value, or None if it is not found. | ||
200 | 91 | """ | ||
201 | 92 | if not section: | ||
202 | 93 | section = self._calculate_config_section(parameter) | ||
203 | 94 | # Try to get the parameter value first if it has one. | ||
204 | 95 | if parameter.value: | ||
205 | 96 | value = parameter.value | ||
206 | 64 | else: | 97 | else: |
215 | 65 | config_section = "DEFAULT" | 98 | value = self._get_from_cache(parameter, section) |
216 | 66 | 99 | ||
217 | 67 | if config_section in self._cache: | 100 | if value is None: |
218 | 68 | if key in self._cache[config_section]: | 101 | value = self._get_from_backend(parameter, section) |
219 | 69 | return self._cache[config_section][key] | 102 | return value |
220 | 70 | 103 | ||
221 | 71 | prompt = '%s: ' % key | 104 | def _get_from_backend(self, parameter, section): |
222 | 72 | 105 | """Gets the parameter value from the config backend. | |
223 | 106 | |||
224 | 107 | :param parameter: The Parameter to look up. | ||
225 | 108 | :param section: The section in the Config. | ||
226 | 109 | """ | ||
227 | 110 | value = None | ||
228 | 73 | try: | 111 | try: |
230 | 74 | value = config_backend.get(config_section, key) | 112 | value = self._config_backend.get(section, parameter.id) |
231 | 75 | except (NoOptionError, NoSectionError): | 113 | except (NoOptionError, NoSectionError): |
232 | 114 | # Ignore, we return None. | ||
233 | 76 | pass | 115 | pass |
264 | 77 | if value: | 116 | return value |
265 | 78 | if self._force_interactive: | 117 | |
266 | 79 | prompt = "%s[%s]: " % (key, value) | 118 | def _get_from_cache(self, parameter, section): |
267 | 80 | else: | 119 | """Looks for the specified parameter in the internal cache. |
268 | 81 | return value | 120 | |
269 | 82 | try: | 121 | :param parameter: The parameter to search. |
270 | 83 | user_input = raw_input(prompt).strip() | 122 | :type Parameter |
271 | 84 | except EOFError: | 123 | :return The parameter value, of None if it is not found. |
272 | 85 | user_input = None | 124 | """ |
273 | 86 | if user_input: | 125 | value = None |
274 | 87 | value = user_input | 126 | if section in self._cache.keys(): |
275 | 88 | if not config_backend.has_section(config_section) and config_section != 'DEFAULT': | 127 | if parameter.id in self._cache[section].keys(): |
276 | 89 | config_backend.add_section(config_section) | 128 | value = self._cache[section][parameter.id] |
277 | 90 | config_backend.set(config_section, key, value) | 129 | return value |
278 | 91 | 130 | ||
279 | 92 | if value: | 131 | def _put_in_cache(self, key, value, section=DEFAULT_SECTION): |
280 | 93 | if config_section not in self._cache: | 132 | """Insert the passed parameter in the internal cache. |
281 | 94 | self._cache[config_section] = {} | 133 | |
282 | 95 | self._cache[config_section][key] = value | 134 | :param parameter: The parameter to insert. |
283 | 96 | return value | 135 | :type Parameter |
284 | 97 | else: | 136 | :param section: The name of the section in the config file. |
285 | 98 | raise KeyError(key) | 137 | :type str |
286 | 99 | 138 | """ | |
287 | 100 | class NonInteractiveConfig(object): | 139 | if section not in self._cache.keys(): |
288 | 101 | 140 | self._cache[section] = {} | |
289 | 102 | def __init__(self, data): | 141 | self._cache[section][key] = value |
290 | 103 | self.data = data | 142 | |
291 | 104 | 143 | def put(self, key, value, section=DEFAULT_SECTION): | |
292 | 105 | def get(self, parameter): | 144 | """Adds a parameter to the config file. |
293 | 106 | return self.data[parameter.id] | 145 | |
294 | 146 | :param key: The key to add. | ||
295 | 147 | :param value: The value to add. | ||
296 | 148 | :param section: The name of the section as in the config file. | ||
297 | 149 | """ | ||
298 | 150 | if (not self._config_backend.has_section(section) and | ||
299 | 151 | section != DEFAULT_SECTION): | ||
300 | 152 | self._config_backend.add_section(section) | ||
301 | 153 | self._config_backend.set(section, key, value) | ||
302 | 154 | # Store in the cache too. | ||
303 | 155 | self._put_in_cache(key, value, section) | ||
304 | 156 | |||
305 | 157 | def put_parameter(self, parameter, value=None, section=None): | ||
306 | 158 | """Adds a Parameter to the config file and cache. | ||
307 | 159 | |||
308 | 160 | :param Parameter: The parameter to add. | ||
309 | 161 | :param value: The value of the parameter. Defaults to None. | ||
310 | 162 | :param section: The section where this parameter should be stored. | ||
311 | 163 | Defaults to None. | ||
312 | 164 | """ | ||
313 | 165 | if not section: | ||
314 | 166 | section = self._calculate_config_section(parameter) | ||
315 | 167 | |||
316 | 168 | if value is None and parameter.value is not None: | ||
317 | 169 | value = parameter.value | ||
318 | 170 | elif value is None: | ||
319 | 171 | raise CommandError("No value assigned to '{0}'.".format( | ||
320 | 172 | parameter.id)) | ||
321 | 173 | self.put(parameter.id, value, section) | ||
322 | 174 | |||
323 | 175 | def save(self): | ||
324 | 176 | """Saves the config to file.""" | ||
325 | 177 | with open(self._config_file, "w") as write_file: | ||
326 | 178 | self._config_backend.write(write_file) | ||
327 | 179 | |||
328 | 180 | |||
329 | 181 | class InteractiveConfig(Config): | ||
330 | 182 | """An interactive config. | ||
331 | 183 | |||
332 | 184 | If a value is not found in the config file, it will ask it and then stores | ||
333 | 185 | it. | ||
334 | 186 | """ | ||
335 | 187 | def __init__(self, force_interactive=True): | ||
336 | 188 | super(InteractiveConfig, self).__init__() | ||
337 | 189 | self._force_interactive = force_interactive | ||
338 | 190 | |||
339 | 191 | def get(self, parameter, section=None): | ||
340 | 192 | """Overrides the parent one. | ||
341 | 193 | |||
342 | 194 | The only difference with the parent one, is that it will ask to type | ||
343 | 195 | a parameter value in case it is not found. | ||
344 | 196 | """ | ||
345 | 197 | if not section: | ||
346 | 198 | section = self._calculate_config_section(parameter) | ||
347 | 199 | value = super(InteractiveConfig, self).get(parameter, section) | ||
348 | 200 | |||
349 | 201 | if not (value is not None and parameter.asked): | ||
350 | 202 | if not value or self._force_interactive: | ||
351 | 203 | value = parameter.prompt(old_value=value) | ||
352 | 204 | |||
353 | 205 | if value is not None: | ||
354 | 206 | self.put(parameter.id, value, section) | ||
355 | 207 | return value | ||
356 | 107 | 208 | ||
357 | === modified file 'lava/device/__init__.py' | |||
358 | --- lava/device/__init__.py 2013-06-27 12:48:26 +0000 | |||
359 | +++ lava/device/__init__.py 2013-06-27 12:48:26 +0000 | |||
360 | @@ -18,11 +18,14 @@ | |||
361 | 18 | 18 | ||
362 | 19 | import re | 19 | import re |
363 | 20 | 20 | ||
364 | 21 | from copy import deepcopy | ||
365 | 22 | |||
366 | 21 | from lava.device.templates import ( | 23 | from lava.device.templates import ( |
367 | 24 | DEFAULT_TEMPLATE, | ||
368 | 25 | HOSTNAME_PARAMETER, | ||
369 | 22 | KNOWN_TEMPLATES, | 26 | KNOWN_TEMPLATES, |
370 | 23 | DEFAULT_TEMPLATE, | ||
371 | 24 | ) | 27 | ) |
373 | 25 | from lava.tool.errors import CommandError | 28 | from lava.helper.template import expand_template |
374 | 26 | 29 | ||
375 | 27 | 30 | ||
376 | 28 | def __re_compile(name): | 31 | def __re_compile(name): |
377 | @@ -44,60 +47,37 @@ | |||
378 | 44 | 47 | ||
379 | 45 | class Device(object): | 48 | class Device(object): |
380 | 46 | """A generic device.""" | 49 | """A generic device.""" |
383 | 47 | def __init__(self, hostname, template): | 50 | def __init__(self, template, hostname=None): |
384 | 48 | self.device_type = None | 51 | self.data = deepcopy(template) |
385 | 49 | self.hostname = hostname | 52 | self.hostname = hostname |
386 | 50 | self.template = template.copy() | ||
387 | 51 | 53 | ||
388 | 52 | def write(self, conf_file): | 54 | def write(self, conf_file): |
389 | 53 | """Writes the object to file. | 55 | """Writes the object to file. |
390 | 54 | 56 | ||
391 | 55 | :param conf_file: The full path of the file where to write.""" | 57 | :param conf_file: The full path of the file where to write.""" |
392 | 56 | with open(conf_file, 'w') as write_file: | 58 | with open(conf_file, 'w') as write_file: |
400 | 57 | write_file.write(self.__str__()) | 59 | write_file.write(str(self)) |
401 | 58 | 60 | ||
402 | 59 | def _update(self): | 61 | def update(self, config): |
403 | 60 | """Updates the template with the values specified for this class. | 62 | """Updates the Device object values based on the provided config. |
404 | 61 | 63 | ||
405 | 62 | Subclasses need to override this when they add more specific | 64 | :param config: A Config instance. |
399 | 63 | attributes. | ||
406 | 64 | """ | 65 | """ |
415 | 65 | # This is needed for the 'default' behavior. If we matched a known | 66 | # We should always have a hostname, since it defaults to the name |
416 | 66 | # device, we do not need to update its device_type, since its already | 67 | # given on the command line for the config file. |
417 | 67 | # defined in the template. | 68 | if self.hostname is not None: |
418 | 68 | if self.device_type: | 69 | # We do not ask the user again this parameter. |
419 | 69 | self.template.update(hostname=self.hostname, | 70 | self.data[HOSTNAME_PARAMETER.id].asked = True |
420 | 70 | device_type=self.device_type) | 71 | config.put(HOSTNAME_PARAMETER.id, self.hostname) |
421 | 71 | else: | 72 | |
422 | 72 | self.template.update(hostname=self.hostname) | 73 | expand_template(self.data, config) |
423 | 73 | 74 | ||
424 | 74 | def __str__(self): | 75 | def __str__(self): |
425 | 75 | self._update() | ||
426 | 76 | string_list = [] | 76 | string_list = [] |
430 | 77 | for key, value in self.template.iteritems(): | 77 | for key, value in self.data.iteritems(): |
428 | 78 | if not value: | ||
429 | 79 | value = '' | ||
431 | 80 | string_list.append("{0} = {1}\n".format(str(key), str(value))) | 78 | string_list.append("{0} = {1}\n".format(str(key), str(value))) |
432 | 81 | return "".join(string_list) | 79 | return "".join(string_list) |
433 | 82 | 80 | ||
434 | 83 | def __repr__(self): | ||
435 | 84 | self._update() | ||
436 | 85 | return str(self.template) | ||
437 | 86 | |||
438 | 87 | |||
439 | 88 | def _get_device_type_from_user(): | ||
440 | 89 | """Makes the user write what kind of device this is. | ||
441 | 90 | |||
442 | 91 | If something goes wrong, raises CommandError. | ||
443 | 92 | """ | ||
444 | 93 | try: | ||
445 | 94 | dev_type = raw_input("Please specify the device type: ").strip() | ||
446 | 95 | except (EOFError, KeyboardInterrupt): | ||
447 | 96 | dev_type = None | ||
448 | 97 | if not dev_type: | ||
449 | 98 | raise CommandError("DEVICE name not specified or not correct.") | ||
450 | 99 | return dev_type | ||
451 | 100 | |||
452 | 101 | 81 | ||
453 | 102 | def get_known_device(name): | 82 | def get_known_device(name): |
454 | 103 | """Tries to match a device name with a known device type. | 83 | """Tries to match a device name with a known device type. |
455 | @@ -105,20 +85,9 @@ | |||
456 | 105 | :param name: The name of the device we want matched to a real device. | 85 | :param name: The name of the device we want matched to a real device. |
457 | 106 | :return A Device instance. | 86 | :return A Device instance. |
458 | 107 | """ | 87 | """ |
460 | 108 | instance = None | 88 | instance = Device(DEFAULT_TEMPLATE, name) |
461 | 109 | for known_dev, (matcher, dev_template) in KNOWN_DEVICES.iteritems(): | 89 | for known_dev, (matcher, dev_template) in KNOWN_DEVICES.iteritems(): |
462 | 110 | if matcher.match(name): | 90 | if matcher.match(name): |
476 | 111 | instance = Device(name, dev_template) | 91 | instance = Device(dev_template, name) |
477 | 112 | if not instance: | 92 | break |
465 | 113 | dev_type = _get_device_type_from_user() | ||
466 | 114 | known_dev = KNOWN_DEVICES.get(dev_type, None) | ||
467 | 115 | if known_dev: | ||
468 | 116 | instance = Device(name, known_dev[1]) | ||
469 | 117 | else: | ||
470 | 118 | print ("Device '{0}' does not match a known " | ||
471 | 119 | "device.".format(dev_type)) | ||
472 | 120 | instance = Device(name, DEFAULT_TEMPLATE) | ||
473 | 121 | # Not stricly necessary, users can fill up the field later. | ||
474 | 122 | instance.device_type = dev_type | ||
475 | 123 | |||
478 | 124 | return instance | 93 | return instance |
479 | 125 | 94 | ||
480 | === modified file 'lava/device/commands.py' | |||
481 | --- lava/device/commands.py 2013-06-27 12:48:26 +0000 | |||
482 | +++ lava/device/commands.py 2013-06-27 12:48:26 +0000 | |||
483 | @@ -56,7 +56,7 @@ | |||
484 | 56 | def invoke(self): | 56 | def invoke(self): |
485 | 57 | real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX]) | 57 | real_file_name = ".".join([self.args.DEVICE, DEVICE_FILE_SUFFIX]) |
486 | 58 | 58 | ||
488 | 59 | if get_device_file(real_file_name): | 59 | if get_device_file(real_file_name) is not None: |
489 | 60 | print >> sys.stdout, ("A device configuration file named '{0}' " | 60 | print >> sys.stdout, ("A device configuration file named '{0}' " |
490 | 61 | "already exists.".format(real_file_name)) | 61 | "already exists.".format(real_file_name)) |
491 | 62 | print >> sys.stdout, ("Use 'lava device config {0}' to edit " | 62 | print >> sys.stdout, ("Use 'lava device config {0}' to edit " |
492 | @@ -68,6 +68,7 @@ | |||
493 | 68 | real_file_name)) | 68 | real_file_name)) |
494 | 69 | 69 | ||
495 | 70 | device = get_known_device(self.args.DEVICE) | 70 | device = get_known_device(self.args.DEVICE) |
496 | 71 | device.update(self.config) | ||
497 | 71 | device.write(device_conf_file) | 72 | device.write(device_conf_file) |
498 | 72 | 73 | ||
499 | 73 | print >> sys.stdout, ("Created device file '{0}' in: {1}".format( | 74 | print >> sys.stdout, ("Created device file '{0}' in: {1}".format( |
500 | @@ -116,5 +117,4 @@ | |||
501 | 116 | if device_conf and self.can_edit_file(device_conf): | 117 | if device_conf and self.can_edit_file(device_conf): |
502 | 117 | self.edit_file(device_conf) | 118 | self.edit_file(device_conf) |
503 | 118 | else: | 119 | else: |
506 | 119 | raise CommandError("Cannot edit file '{0}' at: " | 120 | raise CommandError("Cannot edit file '{0}'".format(real_file_name)) |
505 | 120 | "{1}.".format(real_file_name, device_conf)) | ||
507 | 121 | 121 | ||
508 | === modified file 'lava/device/templates.py' | |||
509 | --- lava/device/templates.py 2013-06-27 12:48:26 +0000 | |||
510 | +++ lava/device/templates.py 2013-06-27 12:48:26 +0000 | |||
511 | @@ -21,22 +21,50 @@ | |||
512 | 21 | will be used to serialize a Device object. | 21 | will be used to serialize a Device object. |
513 | 22 | """ | 22 | """ |
514 | 23 | 23 | ||
515 | 24 | from copy import copy | ||
516 | 25 | |||
517 | 26 | from lava.parameter import Parameter | ||
518 | 27 | |||
519 | 28 | # The hostname parameter is always in the DEFAULT config section. | ||
520 | 29 | HOSTNAME_PARAMETER = Parameter("hostname") | ||
521 | 30 | DEVICE_TYPE_PARAMETER = Parameter("device_type", depends=HOSTNAME_PARAMETER) | ||
522 | 31 | CONNECTION_COMMAND_PARMAETER = Parameter("connection_command", | ||
523 | 32 | depends=DEVICE_TYPE_PARAMETER) | ||
524 | 33 | |||
525 | 24 | DEFAULT_TEMPLATE = { | 34 | DEFAULT_TEMPLATE = { |
529 | 25 | 'device_type': None, | 35 | 'hostname': HOSTNAME_PARAMETER, |
530 | 26 | 'hostname': None, | 36 | 'device_type': DEVICE_TYPE_PARAMETER, |
531 | 27 | 'connection_command': None, | 37 | 'connection_command': CONNECTION_COMMAND_PARMAETER, |
532 | 28 | } | 38 | } |
533 | 29 | 39 | ||
534 | 40 | # Specialized copies of the parameters. | ||
535 | 41 | # We need this or we might end up asking the user twice the same parameter due | ||
536 | 42 | # to different object references when one Parameter depends on a "specialized" | ||
537 | 43 | # one, different from the defaults. | ||
538 | 44 | PANDA_DEVICE_TYPE = copy(DEVICE_TYPE_PARAMETER) | ||
539 | 45 | PANDA_DEVICE_TYPE.value = "panda" | ||
540 | 46 | PANDA_DEVICE_TYPE.asked = True | ||
541 | 47 | |||
542 | 48 | PANDA_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER) | ||
543 | 49 | PANDA_CONNECTION_COMMAND.depends = PANDA_DEVICE_TYPE | ||
544 | 50 | |||
545 | 51 | VEXPRESS_DEVICE_TYPE = copy(DEVICE_TYPE_PARAMETER) | ||
546 | 52 | VEXPRESS_DEVICE_TYPE.value = "vexpress" | ||
547 | 53 | VEXPRESS_DEVICE_TYPE.asked = True | ||
548 | 54 | |||
549 | 55 | VEXPRESS_CONNECTION_COMMAND = copy(CONNECTION_COMMAND_PARMAETER) | ||
550 | 56 | VEXPRESS_CONNECTION_COMMAND.depends = VEXPRESS_DEVICE_TYPE | ||
551 | 57 | |||
552 | 30 | # Dictionary with templates of known devices. | 58 | # Dictionary with templates of known devices. |
553 | 31 | KNOWN_TEMPLATES = { | 59 | KNOWN_TEMPLATES = { |
554 | 32 | 'panda': { | 60 | 'panda': { |
558 | 33 | 'device_type': 'panda', | 61 | 'hostname': HOSTNAME_PARAMETER, |
559 | 34 | 'hostname': None, | 62 | 'device_type': PANDA_DEVICE_TYPE, |
560 | 35 | 'connection_command': None, | 63 | 'connection_command': PANDA_CONNECTION_COMMAND, |
561 | 36 | }, | 64 | }, |
562 | 37 | 'vexpress': { | 65 | 'vexpress': { |
566 | 38 | 'device_type': 'vexpress', | 66 | 'hostname': HOSTNAME_PARAMETER, |
567 | 39 | 'hostname': None, | 67 | 'device_type': VEXPRESS_DEVICE_TYPE, |
568 | 40 | 'connection_command': None, | 68 | 'connection_command': VEXPRESS_CONNECTION_COMMAND, |
569 | 41 | }, | 69 | }, |
570 | 42 | } | 70 | } |
571 | 43 | 71 | ||
572 | === modified file 'lava/device/tests/test_commands.py' | |||
573 | --- lava/device/tests/test_commands.py 2013-06-27 12:48:26 +0000 | |||
574 | +++ lava/device/tests/test_commands.py 2013-06-27 12:48:26 +0000 | |||
575 | @@ -22,7 +22,11 @@ | |||
576 | 22 | 22 | ||
577 | 23 | import os | 23 | import os |
578 | 24 | 24 | ||
580 | 25 | from mock import MagicMock, patch | 25 | from mock import ( |
581 | 26 | MagicMock, | ||
582 | 27 | call, | ||
583 | 28 | patch, | ||
584 | 29 | ) | ||
585 | 26 | 30 | ||
586 | 27 | from lava.device.commands import ( | 31 | from lava.device.commands import ( |
587 | 28 | add, | 32 | add, |
588 | @@ -33,12 +37,25 @@ | |||
589 | 33 | from lava.tool.errors import CommandError | 37 | from lava.tool.errors import CommandError |
590 | 34 | 38 | ||
591 | 35 | 39 | ||
594 | 36 | class CommandsTest(HelperTest): | 40 | class AddCommandTest(HelperTest): |
595 | 37 | 41 | ||
596 | 42 | def test_register_argument(self): | ||
597 | 43 | # Make sure that the parser add_argument is called and we have the | ||
598 | 44 | # correct argument. | ||
599 | 45 | add_command = add(self.parser, self.args) | ||
600 | 46 | add_command.register_arguments(self.parser) | ||
601 | 47 | name, args, kwargs = self.parser.method_calls[0] | ||
602 | 48 | self.assertIn("--non-interactive", args) | ||
603 | 49 | |||
604 | 50 | name, args, kwargs = self.parser.method_calls[1] | ||
605 | 51 | self.assertIn("DEVICE", args) | ||
606 | 52 | |||
607 | 53 | @patch("lava.device.Device.__str__", new=MagicMock(return_value="")) | ||
608 | 54 | @patch("lava.device.Device.update", new=MagicMock()) | ||
609 | 38 | @patch("lava.device.commands.get_device_file", | 55 | @patch("lava.device.commands.get_device_file", |
610 | 39 | new=MagicMock(return_value=None)) | 56 | new=MagicMock(return_value=None)) |
611 | 40 | @patch("lava.device.commands.get_devices_path") | 57 | @patch("lava.device.commands.get_devices_path") |
613 | 41 | def test_add_invoke(self, get_devices_path_mock): | 58 | def test_add_invoke_0(self, get_devices_path_mock): |
614 | 42 | # Tests invocation of the add command. Verifies that the conf file is | 59 | # Tests invocation of the add command. Verifies that the conf file is |
615 | 43 | # written to disk. | 60 | # written to disk. |
616 | 44 | get_devices_path_mock.return_value = self.temp_dir | 61 | get_devices_path_mock.return_value = self.temp_dir |
617 | @@ -51,6 +68,37 @@ | |||
618 | 51 | ".".join([self.device, "conf"])) | 68 | ".".join([self.device, "conf"])) |
619 | 52 | self.assertTrue(os.path.isfile(expected_path)) | 69 | self.assertTrue(os.path.isfile(expected_path)) |
620 | 53 | 70 | ||
621 | 71 | @patch("lava.device.commands.get_known_device") | ||
622 | 72 | @patch("lava.device.commands.get_devices_path") | ||
623 | 73 | @patch("lava.device.commands.sys.exit") | ||
624 | 74 | @patch("lava.device.commands.get_device_file") | ||
625 | 75 | def test_add_invoke_1(self, mocked_get_device_file, mocked_sys_exit, | ||
626 | 76 | mocked_get_devices_path, mocked_get_known_device): | ||
627 | 77 | mocked_get_devices_path.return_value = self.temp_dir | ||
628 | 78 | mocked_get_device_file.return_value = self.temp_file.name | ||
629 | 79 | |||
630 | 80 | add_command = add(self.parser, self.args) | ||
631 | 81 | add_command.edit_file = MagicMock() | ||
632 | 82 | add_command.invoke() | ||
633 | 83 | |||
634 | 84 | self.assertTrue(mocked_sys_exit.called) | ||
635 | 85 | |||
636 | 86 | |||
637 | 87 | class RemoveCommandTests(HelperTest): | ||
638 | 88 | |||
639 | 89 | def test_register_argument(self): | ||
640 | 90 | # Make sure that the parser add_argument is called and we have the | ||
641 | 91 | # correct argument. | ||
642 | 92 | command = remove(self.parser, self.args) | ||
643 | 93 | command.register_arguments(self.parser) | ||
644 | 94 | name, args, kwargs = self.parser.method_calls[0] | ||
645 | 95 | self.assertIn("--non-interactive", args) | ||
646 | 96 | |||
647 | 97 | name, args, kwargs = self.parser.method_calls[1] | ||
648 | 98 | self.assertIn("DEVICE", args) | ||
649 | 99 | |||
650 | 100 | @patch("lava.device.Device.__str__", new=MagicMock(return_value="")) | ||
651 | 101 | @patch("lava.device.Device.update", new=MagicMock()) | ||
652 | 54 | @patch("lava.device.commands.get_device_file") | 102 | @patch("lava.device.commands.get_device_file") |
653 | 55 | @patch("lava.device.commands.get_devices_path") | 103 | @patch("lava.device.commands.get_devices_path") |
654 | 56 | def test_remove_invoke(self, get_devices_path_mock, get_device_file_mock): | 104 | def test_remove_invoke(self, get_devices_path_mock, get_device_file_mock): |
655 | @@ -83,6 +131,33 @@ | |||
656 | 83 | remove_command = remove(self.parser, self.args) | 131 | remove_command = remove(self.parser, self.args) |
657 | 84 | self.assertRaises(CommandError, remove_command.invoke) | 132 | self.assertRaises(CommandError, remove_command.invoke) |
658 | 85 | 133 | ||
659 | 134 | |||
660 | 135 | class ConfigCommanTests(HelperTest): | ||
661 | 136 | |||
662 | 137 | def test_register_argument(self): | ||
663 | 138 | # Make sure that the parser add_argument is called and we have the | ||
664 | 139 | # correct argument. | ||
665 | 140 | command = config(self.parser, self.args) | ||
666 | 141 | command.register_arguments(self.parser) | ||
667 | 142 | name, args, kwargs = self.parser.method_calls[0] | ||
668 | 143 | self.assertIn("--non-interactive", args) | ||
669 | 144 | |||
670 | 145 | name, args, kwargs = self.parser.method_calls[1] | ||
671 | 146 | self.assertIn("DEVICE", args) | ||
672 | 147 | |||
673 | 148 | @patch("lava.device.commands.get_device_file") | ||
674 | 149 | def test_config_invoke_0(self, mocked_get_device_file): | ||
675 | 150 | command = config(self.parser, self.args) | ||
676 | 151 | |||
677 | 152 | mocked_get_device_file.return_value = self.temp_file.name | ||
678 | 153 | command.can_edit_file = MagicMock(return_value=True) | ||
679 | 154 | command.edit_file = MagicMock() | ||
680 | 155 | command.invoke() | ||
681 | 156 | |||
682 | 157 | self.assertTrue(command.edit_file.called) | ||
683 | 158 | self.assertEqual([call(self.temp_file.name)], | ||
684 | 159 | command.edit_file.call_args_list) | ||
685 | 160 | |||
686 | 86 | @patch("lava.device.commands.get_device_file", | 161 | @patch("lava.device.commands.get_device_file", |
687 | 87 | new=MagicMock(return_value=None)) | 162 | new=MagicMock(return_value=None)) |
688 | 88 | def test_config_invoke_raises_0(self): | 163 | def test_config_invoke_raises_0(self): |
689 | 89 | 164 | ||
690 | === modified file 'lava/device/tests/test_device.py' | |||
691 | --- lava/device/tests/test_device.py 2013-06-27 12:48:26 +0000 | |||
692 | +++ lava/device/tests/test_device.py 2013-06-27 12:48:26 +0000 | |||
693 | @@ -20,10 +20,13 @@ | |||
694 | 20 | Device class unit tests. | 20 | Device class unit tests. |
695 | 21 | """ | 21 | """ |
696 | 22 | 22 | ||
701 | 23 | import sys | 23 | from lava.parameter import Parameter |
702 | 24 | 24 | from lava.device.templates import ( | |
703 | 25 | from StringIO import StringIO | 25 | HOSTNAME_PARAMETER, |
704 | 26 | 26 | PANDA_DEVICE_TYPE, | |
705 | 27 | PANDA_CONNECTION_COMMAND, | ||
706 | 28 | ) | ||
707 | 29 | from lava.tests.test_config import MockedConfig | ||
708 | 27 | from lava.device import ( | 30 | from lava.device import ( |
709 | 28 | Device, | 31 | Device, |
710 | 29 | get_known_device, | 32 | get_known_device, |
711 | @@ -38,70 +41,73 @@ | |||
712 | 38 | # User creates a new device with a guessable name for a device. | 41 | # User creates a new device with a guessable name for a device. |
713 | 39 | instance = get_known_device('panda_new_01') | 42 | instance = get_known_device('panda_new_01') |
714 | 40 | self.assertIsInstance(instance, Device) | 43 | self.assertIsInstance(instance, Device) |
717 | 41 | self.assertEqual(instance.template['device_type'], 'panda') | 44 | self.assertEqual(instance.data['device_type'].value, 'panda') |
716 | 42 | self.assertIsNone(instance.device_type) | ||
718 | 43 | 45 | ||
719 | 44 | def test_get_known_device_panda_1(self): | 46 | def test_get_known_device_panda_1(self): |
720 | 45 | # User creates a new device with a guessable name for a device. | 47 | # User creates a new device with a guessable name for a device. |
721 | 46 | # Name passed has capital letters. | 48 | # Name passed has capital letters. |
722 | 47 | instance = get_known_device('new_PanDa_02') | 49 | instance = get_known_device('new_PanDa_02') |
723 | 48 | self.assertIsInstance(instance, Device) | 50 | self.assertIsInstance(instance, Device) |
726 | 49 | self.assertEqual(instance.template['device_type'], 'panda') | 51 | self.assertEqual(instance.data['device_type'].value, 'panda') |
725 | 50 | self.assertIsNone(instance.device_type) | ||
727 | 51 | 52 | ||
728 | 52 | def test_get_known_device_vexpress_0(self): | 53 | def test_get_known_device_vexpress_0(self): |
729 | 53 | # User creates a new device with a guessable name for a device. | 54 | # User creates a new device with a guessable name for a device. |
730 | 54 | # Name passed has capital letters. | 55 | # Name passed has capital letters. |
731 | 55 | instance = get_known_device('a_VexPress_Device') | 56 | instance = get_known_device('a_VexPress_Device') |
732 | 56 | self.assertIsInstance(instance, Device) | 57 | self.assertIsInstance(instance, Device) |
735 | 57 | self.assertEqual(instance.template['device_type'], 'vexpress') | 58 | self.assertEqual(instance.data['device_type'].value, 'vexpress') |
734 | 58 | self.assertIsNone(instance.device_type) | ||
736 | 59 | 59 | ||
737 | 60 | def test_get_known_device_vexpress_1(self): | 60 | def test_get_known_device_vexpress_1(self): |
738 | 61 | # User creates a new device with a guessable name for a device. | 61 | # User creates a new device with a guessable name for a device. |
739 | 62 | instance = get_known_device('another-vexpress') | 62 | instance = get_known_device('another-vexpress') |
740 | 63 | self.assertIsInstance(instance, Device) | 63 | self.assertIsInstance(instance, Device) |
774 | 64 | self.assertEqual(instance.template['device_type'], 'vexpress') | 64 | self.assertIsInstance(instance.data['device_type'], Parameter) |
775 | 65 | self.assertIsNone(instance.device_type) | 65 | self.assertEqual(instance.data['device_type'].value, 'vexpress') |
776 | 66 | 66 | ||
777 | 67 | def test_instance_update(self): | 67 | def test_device_update_1(self): |
778 | 68 | # Tests that when calling the _update() function with an known device | 68 | # Tests that when calling update() on a Device, the template gets |
779 | 69 | # it does not update the device_type instance attribute, and that the | 69 | # updated with the correct values from a Config instance. |
780 | 70 | # template contains the correct name. | 70 | hostname = "panda_device" |
781 | 71 | instance = get_known_device('Another_PanDa_device') | 71 | |
782 | 72 | instance._update() | 72 | config = MockedConfig(self.temp_file.name) |
783 | 73 | self.assertIsInstance(instance, Device) | 73 | config.put_parameter(HOSTNAME_PARAMETER, hostname) |
784 | 74 | self.assertEqual(instance.template['device_type'], 'panda') | 74 | config.put_parameter(PANDA_DEVICE_TYPE, "panda") |
785 | 75 | self.assertIsNone(instance.device_type) | 75 | config.put_parameter(PANDA_CONNECTION_COMMAND, "test") |
786 | 76 | 76 | ||
787 | 77 | def test_get_known_device_unknown(self): | 77 | expected = { |
788 | 78 | # User tries to create a new device with an unknown device type. She | 78 | "hostname": hostname, |
789 | 79 | # is asked to insert the device type and types 'a_fake_device'. | 79 | "device_type": "panda", |
790 | 80 | sys.stdin = StringIO('a_fake_device') | 80 | "connection_command": "test" |
791 | 81 | instance = get_known_device('a_fake_device') | 81 | } |
792 | 82 | self.assertIsInstance(instance, Device) | 82 | |
793 | 83 | self.assertEqual(instance.device_type, 'a_fake_device') | 83 | instance = get_known_device(hostname) |
794 | 84 | 84 | instance.update(config) | |
795 | 85 | def test_get_known_device_known(self): | 85 | |
796 | 86 | # User tries to create a new device with a not recognizable name. | 86 | self.assertEqual(expected, instance.data) |
764 | 87 | # She is asked to insert the device type and types 'panda'. | ||
765 | 88 | sys.stdin = StringIO("panda") | ||
766 | 89 | instance = get_known_device("another_fake_device") | ||
767 | 90 | self.assertIsInstance(instance, Device) | ||
768 | 91 | self.assertEqual(instance.template["device_type"], "panda") | ||
769 | 92 | |||
770 | 93 | def test_get_known_device_raises(self): | ||
771 | 94 | # User tries to create a new device, but in some way nothing is passed | ||
772 | 95 | # on the command line when asked. | ||
773 | 96 | self.assertRaises(CommandError, get_known_device, 'a_fake_device') | ||
797 | 97 | 87 | ||
798 | 98 | def test_device_write(self): | 88 | def test_device_write(self): |
799 | 99 | # User tries to create a new panda device. The conf file is written | 89 | # User tries to create a new panda device. The conf file is written |
800 | 100 | # and contains the expected results. | 90 | # and contains the expected results. |
802 | 101 | expected = ("hostname = panda02\nconnection_command = \n" | 91 | hostname = "panda_device" |
803 | 92 | |||
804 | 93 | config = MockedConfig(self.temp_file.name) | ||
805 | 94 | config.put_parameter(HOSTNAME_PARAMETER, hostname) | ||
806 | 95 | config.put_parameter(PANDA_DEVICE_TYPE, "panda") | ||
807 | 96 | config.put_parameter(PANDA_CONNECTION_COMMAND, "test") | ||
808 | 97 | |||
809 | 98 | expected = { | ||
810 | 99 | "hostname": hostname, | ||
811 | 100 | "device_type": "panda", | ||
812 | 101 | "connection_command": "test" | ||
813 | 102 | } | ||
814 | 103 | |||
815 | 104 | instance = get_known_device(hostname) | ||
816 | 105 | instance.update(config) | ||
817 | 106 | instance.write(self.temp_file.name) | ||
818 | 107 | |||
819 | 108 | expected = ("hostname = panda_device\nconnection_command = test\n" | ||
820 | 102 | "device_type = panda\n") | 109 | "device_type = panda\n") |
823 | 103 | instance = get_known_device("panda02") | 110 | |
822 | 104 | instance.write(self.temp_file.name) | ||
824 | 105 | obtained = "" | 111 | obtained = "" |
825 | 106 | with open(self.temp_file.name) as f: | 112 | with open(self.temp_file.name) as f: |
826 | 107 | obtained = f.read() | 113 | obtained = f.read() |
827 | 108 | 114 | ||
828 | === modified file 'lava/helper/command.py' | |||
829 | --- lava/helper/command.py 2013-06-27 12:48:26 +0000 | |||
830 | +++ lava/helper/command.py 2013-06-27 12:48:26 +0000 | |||
831 | @@ -93,7 +93,7 @@ | |||
832 | 93 | :return The command execution return code. | 93 | :return The command execution return code. |
833 | 94 | """ | 94 | """ |
834 | 95 | if not isinstance(cmd_args, list): | 95 | if not isinstance(cmd_args, list): |
836 | 96 | cmd_args = list(cmd_args) | 96 | cmd_args = [cmd_args] |
837 | 97 | try: | 97 | try: |
838 | 98 | return subprocess.check_call(cmd_args) | 98 | return subprocess.check_call(cmd_args) |
839 | 99 | except subprocess.CalledProcessError: | 99 | except subprocess.CalledProcessError: |
840 | 100 | 100 | ||
841 | === added file 'lava/helper/template.py' | |||
842 | --- lava/helper/template.py 1970-01-01 00:00:00 +0000 | |||
843 | +++ lava/helper/template.py 2013-06-27 12:48:26 +0000 | |||
844 | @@ -0,0 +1,44 @@ | |||
845 | 1 | # Copyright (C) 2013 Linaro Limited | ||
846 | 2 | # | ||
847 | 3 | # Author: Milo Casagrande <milo.casagrande@linaro.org> | ||
848 | 4 | # | ||
849 | 5 | # This file is part of lava-tool. | ||
850 | 6 | # | ||
851 | 7 | # lava-tool is free software: you can redistribute it and/or modify | ||
852 | 8 | # it under the terms of the GNU Lesser General Public License version 3 | ||
853 | 9 | # as published by the Free Software Foundation | ||
854 | 10 | # | ||
855 | 11 | # lava-tool is distributed in the hope that it will be useful, | ||
856 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
857 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
858 | 14 | # GNU General Public License for more details. | ||
859 | 15 | # | ||
860 | 16 | # You should have received a copy of the GNU Lesser General Public License | ||
861 | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. | ||
862 | 18 | |||
863 | 19 | from lava.parameter import Parameter | ||
864 | 20 | |||
865 | 21 | |||
866 | 22 | def expand_template(template, config): | ||
867 | 23 | """Updates a template based on the values from the provided config. | ||
868 | 24 | |||
869 | 25 | :param template: A template to be updated. | ||
870 | 26 | :param config: A Config instance where values should be taken. | ||
871 | 27 | """ | ||
872 | 28 | |||
873 | 29 | def update(data): | ||
874 | 30 | """Internal recursive function.""" | ||
875 | 31 | if isinstance(data, dict): | ||
876 | 32 | keys = data.keys() | ||
877 | 33 | elif isinstance(data, list): | ||
878 | 34 | keys = range(len(data)) | ||
879 | 35 | else: | ||
880 | 36 | return | ||
881 | 37 | for key in keys: | ||
882 | 38 | entry = data[key] | ||
883 | 39 | if isinstance(entry, Parameter): | ||
884 | 40 | data[key] = config.get(entry) | ||
885 | 41 | else: | ||
886 | 42 | update(entry) | ||
887 | 43 | |||
888 | 44 | update(template) | ||
889 | 0 | 45 | ||
890 | === modified file 'lava/helper/tests/test_command.py' | |||
891 | --- lava/helper/tests/test_command.py 2013-06-27 12:48:26 +0000 | |||
892 | +++ lava/helper/tests/test_command.py 2013-06-27 12:48:26 +0000 | |||
893 | @@ -18,6 +18,14 @@ | |||
894 | 18 | 18 | ||
895 | 19 | """lava.herlp.command module tests.""" | 19 | """lava.herlp.command module tests.""" |
896 | 20 | 20 | ||
897 | 21 | import subprocess | ||
898 | 22 | from mock import ( | ||
899 | 23 | MagicMock, | ||
900 | 24 | call, | ||
901 | 25 | patch, | ||
902 | 26 | ) | ||
903 | 27 | |||
904 | 28 | from lava.tool.errors import CommandError | ||
905 | 21 | from lava.helper.command import BaseCommand | 29 | from lava.helper.command import BaseCommand |
906 | 22 | from lava.helper.tests.helper_test import HelperTest | 30 | from lava.helper.tests.helper_test import HelperTest |
907 | 23 | 31 | ||
908 | @@ -51,3 +59,65 @@ | |||
909 | 51 | obtained = f.read() | 59 | obtained = f.read() |
910 | 52 | 60 | ||
911 | 53 | self.assertEqual(expected, obtained) | 61 | self.assertEqual(expected, obtained) |
912 | 62 | |||
913 | 63 | @patch("lava.helper.command.subprocess") | ||
914 | 64 | def test_run_0(self, mocked_subprocess): | ||
915 | 65 | mocked_subprocess.check_call = MagicMock() | ||
916 | 66 | BaseCommand.run("foo") | ||
917 | 67 | self.assertEqual(mocked_subprocess.check_call.call_args_list, | ||
918 | 68 | [call(["foo"])]) | ||
919 | 69 | self.assertTrue(mocked_subprocess.check_call.called) | ||
920 | 70 | |||
921 | 71 | @patch("lava.helper.command.subprocess.check_call") | ||
922 | 72 | def test_run_1(self, mocked_check_call): | ||
923 | 73 | mocked_check_call.side_effect = subprocess.CalledProcessError(1, "foo") | ||
924 | 74 | self.assertRaises(CommandError, BaseCommand.run, ["foo"]) | ||
925 | 75 | |||
926 | 76 | @patch("lava.helper.command.subprocess") | ||
927 | 77 | @patch("lava.helper.command.has_command", return_value=False) | ||
928 | 78 | @patch("lava.helper.command.os.environ.get", return_value=None) | ||
929 | 79 | @patch("lava.helper.command.sys.exit") | ||
930 | 80 | def test_edit_file_0(self, mocked_sys_exit, mocked_env_get, | ||
931 | 81 | mocked_has_command, mocked_subprocess): | ||
932 | 82 | BaseCommand.edit_file(self.temp_file.name) | ||
933 | 83 | self.assertTrue(mocked_sys_exit.called) | ||
934 | 84 | |||
935 | 85 | @patch("lava.helper.command.subprocess") | ||
936 | 86 | @patch("lava.helper.command.has_command", side_effect=[True, False]) | ||
937 | 87 | @patch("lava.helper.command.os.environ.get", return_value=None) | ||
938 | 88 | def test_edit_file_1(self, mocked_env_get, mocked_has_command, | ||
939 | 89 | mocked_subprocess): | ||
940 | 90 | mocked_subprocess.Popen = MagicMock() | ||
941 | 91 | BaseCommand.edit_file(self.temp_file.name) | ||
942 | 92 | expected = [call(["sensible-editor", self.temp_file.name])] | ||
943 | 93 | self.assertEqual(expected, mocked_subprocess.Popen.call_args_list) | ||
944 | 94 | |||
945 | 95 | @patch("lava.helper.command.subprocess") | ||
946 | 96 | @patch("lava.helper.command.has_command", side_effect=[False, True]) | ||
947 | 97 | @patch("lava.helper.command.os.environ.get", return_value=None) | ||
948 | 98 | def test_edit_file_2(self, mocked_env_get, mocked_has_command, | ||
949 | 99 | mocked_subprocess): | ||
950 | 100 | mocked_subprocess.Popen = MagicMock() | ||
951 | 101 | BaseCommand.edit_file(self.temp_file.name) | ||
952 | 102 | expected = [call(["xdg-open", self.temp_file.name])] | ||
953 | 103 | self.assertEqual(expected, mocked_subprocess.Popen.call_args_list) | ||
954 | 104 | |||
955 | 105 | @patch("lava.helper.command.subprocess") | ||
956 | 106 | @patch("lava.helper.command.has_command", return_value=False) | ||
957 | 107 | @patch("lava.helper.command.os.environ.get", return_value="vim") | ||
958 | 108 | def test_edit_file_3(self, mocked_env_get, mocked_has_command, | ||
959 | 109 | mocked_subprocess): | ||
960 | 110 | mocked_subprocess.Popen = MagicMock() | ||
961 | 111 | BaseCommand.edit_file(self.temp_file.name) | ||
962 | 112 | expected = [call(["vim", self.temp_file.name])] | ||
963 | 113 | self.assertEqual(expected, mocked_subprocess.Popen.call_args_list) | ||
964 | 114 | |||
965 | 115 | @patch("lava.helper.command.subprocess") | ||
966 | 116 | @patch("lava.helper.command.has_command", return_value=False) | ||
967 | 117 | @patch("lava.helper.command.os.environ.get", return_value="vim") | ||
968 | 118 | def test_edit_file_4(self, mocked_env_get, mocked_has_command, | ||
969 | 119 | mocked_subprocess): | ||
970 | 120 | mocked_subprocess.Popen = MagicMock() | ||
971 | 121 | mocked_subprocess.Popen.side_effect = Exception() | ||
972 | 122 | self.assertRaises(CommandError, BaseCommand.edit_file, | ||
973 | 123 | self.temp_file.name) | ||
974 | 54 | 124 | ||
975 | === modified file 'lava/helper/tests/test_dispatcher.py' | |||
976 | --- lava/helper/tests/test_dispatcher.py 2013-06-27 12:48:26 +0000 | |||
977 | +++ lava/helper/tests/test_dispatcher.py 2013-06-27 12:48:26 +0000 | |||
978 | @@ -19,10 +19,12 @@ | |||
979 | 19 | """lava.helper.dispatcher tests.""" | 19 | """lava.helper.dispatcher tests.""" |
980 | 20 | 20 | ||
981 | 21 | import os | 21 | import os |
982 | 22 | import tempfile | ||
983 | 23 | |||
984 | 24 | from mock import patch | ||
985 | 25 | |||
986 | 22 | from lava.tool.errors import CommandError | 26 | from lava.tool.errors import CommandError |
987 | 23 | |||
988 | 24 | from lava.helper.tests.helper_test import HelperTest | 27 | from lava.helper.tests.helper_test import HelperTest |
989 | 25 | |||
990 | 26 | from lava.helper.dispatcher import ( | 28 | from lava.helper.dispatcher import ( |
991 | 27 | choose_devices_path, | 29 | choose_devices_path, |
992 | 28 | ) | 30 | ) |
993 | @@ -30,6 +32,15 @@ | |||
994 | 30 | 32 | ||
995 | 31 | class DispatcherTests(HelperTest): | 33 | class DispatcherTests(HelperTest): |
996 | 32 | 34 | ||
997 | 35 | def setUp(self): | ||
998 | 36 | super(DispatcherTests, self).setUp() | ||
999 | 37 | self.devices_dir = os.path.join(tempfile.gettempdir(), "devices") | ||
1000 | 38 | os.makedirs(self.devices_dir) | ||
1001 | 39 | |||
1002 | 40 | def tearDown(self): | ||
1003 | 41 | super(DispatcherTests, self).tearDown() | ||
1004 | 42 | os.removedirs(self.devices_dir) | ||
1005 | 43 | |||
1006 | 33 | def test_choose_devices_path_0(self): | 44 | def test_choose_devices_path_0(self): |
1007 | 34 | # Tests that when passing more than one path, the first writable one | 45 | # Tests that when passing more than one path, the first writable one |
1008 | 35 | # is returned. | 46 | # is returned. |
1009 | @@ -50,3 +61,17 @@ | |||
1010 | 50 | obtained = choose_devices_path([self.temp_dir]) | 61 | obtained = choose_devices_path([self.temp_dir]) |
1011 | 51 | self.assertEqual(expected_path, obtained) | 62 | self.assertEqual(expected_path, obtained) |
1012 | 52 | self.assertTrue(os.path.isdir(expected_path)) | 63 | self.assertTrue(os.path.isdir(expected_path)) |
1013 | 64 | |||
1014 | 65 | def test_choose_devices_path_3(self): | ||
1015 | 66 | # Tests that returns the already existing devices path. | ||
1016 | 67 | obtained = choose_devices_path([tempfile.gettempdir()]) | ||
1017 | 68 | self.assertEqual(self.devices_dir, obtained) | ||
1018 | 69 | |||
1019 | 70 | @patch("__builtin__.open") | ||
1020 | 71 | def test_choose_devices_path_4(self, mocked_open): | ||
1021 | 72 | # Tests that when IOError is raised and we pass only one dir | ||
1022 | 73 | # CommandError is raised. | ||
1023 | 74 | mocked_open.side_effect = IOError() | ||
1024 | 75 | self.assertRaises(CommandError, choose_devices_path, | ||
1025 | 76 | [tempfile.gettempdir()]) | ||
1026 | 77 | self.assertTrue(mocked_open.called) | ||
1027 | 53 | 78 | ||
1028 | === modified file 'lava/job/__init__.py' | |||
1029 | --- lava/job/__init__.py 2013-06-27 12:48:26 +0000 | |||
1030 | +++ lava/job/__init__.py 2013-06-27 12:48:26 +0000 | |||
1031 | @@ -19,7 +19,7 @@ | |||
1032 | 19 | from copy import deepcopy | 19 | from copy import deepcopy |
1033 | 20 | import json | 20 | import json |
1034 | 21 | 21 | ||
1036 | 22 | from lava.job.templates import Parameter | 22 | from lava.helper.template import expand_template |
1037 | 23 | 23 | ||
1038 | 24 | 24 | ||
1039 | 25 | class Job: | 25 | class Job: |
1040 | @@ -27,21 +27,7 @@ | |||
1041 | 27 | self.data = deepcopy(template) | 27 | self.data = deepcopy(template) |
1042 | 28 | 28 | ||
1043 | 29 | def fill_in(self, config): | 29 | def fill_in(self, config): |
1059 | 30 | 30 | expand_template(self.data, config) | |
1045 | 31 | def insert_data(data): | ||
1046 | 32 | if isinstance(data, dict): | ||
1047 | 33 | keys = data.keys() | ||
1048 | 34 | elif isinstance(data, list): | ||
1049 | 35 | keys = range(len(data)) | ||
1050 | 36 | else: | ||
1051 | 37 | return | ||
1052 | 38 | for key in keys: | ||
1053 | 39 | entry = data[key] | ||
1054 | 40 | if isinstance(entry, Parameter): | ||
1055 | 41 | data[key] = config.get(entry) | ||
1056 | 42 | else: | ||
1057 | 43 | insert_data(entry) | ||
1058 | 44 | insert_data(self.data) | ||
1060 | 45 | 31 | ||
1061 | 46 | def write(self, stream): | 32 | def write(self, stream): |
1062 | 47 | stream.write(json.dumps(self.data, indent=4)) | 33 | stream.write(json.dumps(self.data, indent=4)) |
1063 | 48 | 34 | ||
1064 | === modified file 'lava/job/commands.py' | |||
1065 | --- lava/job/commands.py 2013-06-27 12:48:26 +0000 | |||
1066 | +++ lava/job/commands.py 2013-06-27 12:48:26 +0000 | |||
1067 | @@ -25,12 +25,13 @@ | |||
1068 | 25 | import xmlrpclib | 25 | import xmlrpclib |
1069 | 26 | 26 | ||
1070 | 27 | from lava.helper.command import BaseCommand | 27 | from lava.helper.command import BaseCommand |
1071 | 28 | from lava.helper.dispatcher import get_devices | ||
1072 | 28 | 29 | ||
1073 | 29 | from lava.config import Parameter | ||
1074 | 30 | from lava.job import Job | 30 | from lava.job import Job |
1075 | 31 | from lava.job.templates import ( | 31 | from lava.job.templates import ( |
1076 | 32 | BOOT_TEST, | 32 | BOOT_TEST, |
1077 | 33 | ) | 33 | ) |
1078 | 34 | from lava.parameter import Parameter | ||
1079 | 34 | from lava.tool.command import CommandGroup | 35 | from lava.tool.command import CommandGroup |
1080 | 35 | from lava.tool.errors import CommandError | 36 | from lava.tool.errors import CommandError |
1081 | 36 | from lava_tool.authtoken import AuthenticatingServerProxy, KeyringAuthBackend | 37 | from lava_tool.authtoken import AuthenticatingServerProxy, KeyringAuthBackend |
1082 | @@ -126,7 +127,7 @@ | |||
1083 | 126 | def invoke(self): | 127 | def invoke(self): |
1084 | 127 | if os.path.isfile(self.args.FILE): | 128 | if os.path.isfile(self.args.FILE): |
1085 | 128 | if has_command("lava-dispatch"): | 129 | if has_command("lava-dispatch"): |
1087 | 129 | devices = self.get_devices() | 130 | devices = get_devices() |
1088 | 130 | if devices: | 131 | if devices: |
1089 | 131 | if len(devices) > 1: | 132 | if len(devices) > 1: |
1090 | 132 | device = self._choose_device(devices) | 133 | device = self._choose_device(devices) |
1091 | 133 | 134 | ||
1092 | === modified file 'lava/job/templates.py' | |||
1093 | --- lava/job/templates.py 2013-06-27 12:48:26 +0000 | |||
1094 | +++ lava/job/templates.py 2013-06-27 12:48:26 +0000 | |||
1095 | @@ -16,7 +16,7 @@ | |||
1096 | 16 | # You should have received a copy of the GNU Lesser General Public License | 16 | # You should have received a copy of the GNU Lesser General Public License |
1097 | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. |
1098 | 18 | 18 | ||
1100 | 19 | from lava.config import Parameter | 19 | from lava.parameter import Parameter |
1101 | 20 | 20 | ||
1102 | 21 | device_type = Parameter("device_type") | 21 | device_type = Parameter("device_type") |
1103 | 22 | prebuilt_image = Parameter("prebuilt_image", depends=device_type) | 22 | prebuilt_image = Parameter("prebuilt_image", depends=device_type) |
1104 | 23 | 23 | ||
1105 | === modified file 'lava/job/tests/test_commands.py' | |||
1106 | --- lava/job/tests/test_commands.py 2013-06-27 12:48:26 +0000 | |||
1107 | +++ lava/job/tests/test_commands.py 2013-06-27 12:48:26 +0000 | |||
1108 | @@ -25,15 +25,14 @@ | |||
1109 | 25 | 25 | ||
1110 | 26 | from mock import MagicMock, patch | 26 | from mock import MagicMock, patch |
1111 | 27 | 27 | ||
1114 | 28 | from lava.config import NonInteractiveConfig, Parameter | 28 | from lava.config import Config |
1115 | 29 | 29 | from lava.helper.tests.helper_test import HelperTest | |
1116 | 30 | from lava.job.commands import ( | 30 | from lava.job.commands import ( |
1117 | 31 | new, | 31 | new, |
1118 | 32 | run, | 32 | run, |
1119 | 33 | submit, | 33 | submit, |
1120 | 34 | ) | 34 | ) |
1123 | 35 | 35 | from lava.parameter import Parameter | |
1122 | 36 | from lava.helper.tests.helper_test import HelperTest | ||
1124 | 37 | from lava.tool.errors import CommandError | 36 | from lava.tool.errors import CommandError |
1125 | 38 | 37 | ||
1126 | 39 | 38 | ||
1127 | @@ -46,8 +45,9 @@ | |||
1128 | 46 | self.device_type = Parameter('device_type') | 45 | self.device_type = Parameter('device_type') |
1129 | 47 | self.prebuilt_image = Parameter('prebuilt_image', | 46 | self.prebuilt_image = Parameter('prebuilt_image', |
1130 | 48 | depends=self.device_type) | 47 | depends=self.device_type) |
1133 | 49 | self.config = NonInteractiveConfig( | 48 | self.config = Config() |
1134 | 50 | {'device_type': 'foo', 'prebuilt_image': 'bar'}) | 49 | self.config.put_parameter(self.device_type, 'foo') |
1135 | 50 | self.config.put_parameter(self.prebuilt_image, 'bar') | ||
1136 | 51 | 51 | ||
1137 | 52 | def tmp(self, filename): | 52 | def tmp(self, filename): |
1138 | 53 | """Returns a path to a non existent file. | 53 | """Returns a path to a non existent file. |
1139 | 54 | 54 | ||
1140 | === modified file 'lava/job/tests/test_job.py' | |||
1141 | --- lava/job/tests/test_job.py 2013-05-28 22:08:12 +0000 | |||
1142 | +++ lava/job/tests/test_job.py 2013-06-27 12:48:26 +0000 | |||
1143 | @@ -21,15 +21,29 @@ | |||
1144 | 21 | """ | 21 | """ |
1145 | 22 | 22 | ||
1146 | 23 | import json | 23 | import json |
1147 | 24 | import os | ||
1148 | 25 | import tempfile | ||
1149 | 26 | |||
1150 | 27 | from StringIO import StringIO | ||
1151 | 24 | from unittest import TestCase | 28 | from unittest import TestCase |
1152 | 25 | from StringIO import StringIO | ||
1153 | 26 | 29 | ||
1156 | 27 | from lava.config import NonInteractiveConfig | 30 | from lava.config import Config |
1155 | 28 | from lava.job.templates import * | ||
1157 | 29 | from lava.job import Job | 31 | from lava.job import Job |
1158 | 32 | from lava.job.templates import BOOT_TEST | ||
1159 | 33 | from lava.parameter import Parameter | ||
1160 | 34 | |||
1161 | 30 | 35 | ||
1162 | 31 | class JobTest(TestCase): | 36 | class JobTest(TestCase): |
1163 | 32 | 37 | ||
1164 | 38 | def setUp(self): | ||
1165 | 39 | self.config_file = tempfile.NamedTemporaryFile(delete=False) | ||
1166 | 40 | self.config = Config() | ||
1167 | 41 | self.config._config_file = self.config_file.name | ||
1168 | 42 | |||
1169 | 43 | def tearDown(self): | ||
1170 | 44 | if os.path.isfile(self.config_file.name): | ||
1171 | 45 | os.unlink(self.config_file.name) | ||
1172 | 46 | |||
1173 | 33 | def test_from_template(self): | 47 | def test_from_template(self): |
1174 | 34 | template = {} | 48 | template = {} |
1175 | 35 | job = Job(template) | 49 | job = Job(template) |
1176 | @@ -37,21 +51,20 @@ | |||
1177 | 37 | self.assertIsNot(job.data, template) | 51 | self.assertIsNot(job.data, template) |
1178 | 38 | 52 | ||
1179 | 39 | def test_fill_in_data(self): | 53 | def test_fill_in_data(self): |
1180 | 54 | image = "/path/to/panda.img" | ||
1181 | 55 | param1 = Parameter("device_type") | ||
1182 | 56 | param2 = Parameter("prebuilt_image", depends=param1) | ||
1183 | 57 | self.config.put_parameter(param1, "panda") | ||
1184 | 58 | self.config.put_parameter(param2, image) | ||
1185 | 59 | |||
1186 | 40 | job = Job(BOOT_TEST) | 60 | job = Job(BOOT_TEST) |
1195 | 41 | image = "/path/to/panda.img" | 61 | job.fill_in(self.config) |
1188 | 42 | config = NonInteractiveConfig( | ||
1189 | 43 | { | ||
1190 | 44 | "device_type": "panda", | ||
1191 | 45 | "prebuilt_image": image, | ||
1192 | 46 | } | ||
1193 | 47 | ) | ||
1194 | 48 | job.fill_in(config) | ||
1196 | 49 | 62 | ||
1197 | 50 | self.assertEqual(job.data['device_type'], "panda") | 63 | self.assertEqual(job.data['device_type'], "panda") |
1198 | 51 | self.assertEqual(job.data['actions'][0]["parameters"]["image"], image) | 64 | self.assertEqual(job.data['actions'][0]["parameters"]["image"], image) |
1199 | 52 | 65 | ||
1200 | 53 | def test_write(self): | 66 | def test_write(self): |
1202 | 54 | orig_data = { "foo": "bar" } | 67 | orig_data = {"foo": "bar"} |
1203 | 55 | job = Job(orig_data) | 68 | job = Job(orig_data) |
1204 | 56 | output = StringIO() | 69 | output = StringIO() |
1205 | 57 | job.write(output) | 70 | job.write(output) |
1206 | @@ -60,7 +73,7 @@ | |||
1207 | 60 | self.assertEqual(data, orig_data) | 73 | self.assertEqual(data, orig_data) |
1208 | 61 | 74 | ||
1209 | 62 | def test_writes_nicely_formatted_json(self): | 75 | def test_writes_nicely_formatted_json(self): |
1211 | 63 | orig_data = { "foo": "bar" } | 76 | orig_data = {"foo": "bar"} |
1212 | 64 | job = Job(orig_data) | 77 | job = Job(orig_data) |
1213 | 65 | output = StringIO() | 78 | output = StringIO() |
1214 | 66 | job.write(output) | 79 | job.write(output) |
1215 | 67 | 80 | ||
1216 | === added file 'lava/parameter.py' | |||
1217 | --- lava/parameter.py 1970-01-01 00:00:00 +0000 | |||
1218 | +++ lava/parameter.py 2013-06-27 12:48:26 +0000 | |||
1219 | @@ -0,0 +1,75 @@ | |||
1220 | 1 | # Copyright (C) 2013 Linaro Limited | ||
1221 | 2 | # | ||
1222 | 3 | # Author: Antonio Terceiro <antonio.terceiro@linaro.org> | ||
1223 | 4 | # | ||
1224 | 5 | # This file is part of lava-tool. | ||
1225 | 6 | # | ||
1226 | 7 | # lava-tool is free software: you can redistribute it and/or modify | ||
1227 | 8 | # it under the terms of the GNU Lesser General Public License version 3 | ||
1228 | 9 | # as published by the Free Software Foundation | ||
1229 | 10 | # | ||
1230 | 11 | # lava-tool is distributed in the hope that it will be useful, | ||
1231 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1232 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1233 | 14 | # GNU General Public License for more details. | ||
1234 | 15 | # | ||
1235 | 16 | # You should have received a copy of the GNU Lesser General Public License | ||
1236 | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. | ||
1237 | 18 | |||
1238 | 19 | """ | ||
1239 | 20 | Parameter class and its accessory methods/functions. | ||
1240 | 21 | """ | ||
1241 | 22 | |||
1242 | 23 | import sys | ||
1243 | 24 | |||
1244 | 25 | |||
1245 | 26 | class Parameter(object): | ||
1246 | 27 | """A parameter with an optional dependency.""" | ||
1247 | 28 | def __init__(self, id, value=None, depends=None): | ||
1248 | 29 | """Creates a new parameter. | ||
1249 | 30 | |||
1250 | 31 | :param id: The name of this parameter. | ||
1251 | 32 | :param value: The value of this parameter. Defaults to None. | ||
1252 | 33 | :param depends: If this Parameter depends on another one. Defaults | ||
1253 | 34 | to None. | ||
1254 | 35 | :type Parameter | ||
1255 | 36 | """ | ||
1256 | 37 | self.id = id | ||
1257 | 38 | self.value = value | ||
1258 | 39 | self.depends = depends | ||
1259 | 40 | self.asked = False | ||
1260 | 41 | |||
1261 | 42 | def prompt(self, old_value=None): | ||
1262 | 43 | """Gets the parameter value from the user. | ||
1263 | 44 | |||
1264 | 45 | To get user input, the builtin `raw_input` function will be used. Input | ||
1265 | 46 | will also be stripped of possible whitespace chars. If Enter or any | ||
1266 | 47 | sort of whitespace chars in typed, the old Parameter value will be | ||
1267 | 48 | returned. | ||
1268 | 49 | |||
1269 | 50 | :param old_value: The old parameter value. | ||
1270 | 51 | :return The input as typed by the user, or the old value. | ||
1271 | 52 | """ | ||
1272 | 53 | if old_value is not None: | ||
1273 | 54 | prompt = "{0} [{1}]: ".format(self.id, old_value) | ||
1274 | 55 | else: | ||
1275 | 56 | prompt = "{0}: ".format(self.id) | ||
1276 | 57 | |||
1277 | 58 | user_input = None | ||
1278 | 59 | try: | ||
1279 | 60 | user_input = raw_input(prompt).strip() | ||
1280 | 61 | except EOFError: | ||
1281 | 62 | pass | ||
1282 | 63 | except KeyboardInterrupt: | ||
1283 | 64 | sys.exit(-1) | ||
1284 | 65 | |||
1285 | 66 | if user_input is not None: | ||
1286 | 67 | if len(user_input) == 0 and old_value: | ||
1287 | 68 | # Keep the old value when user press enter or another | ||
1288 | 69 | # whitespace char. | ||
1289 | 70 | self.value = old_value | ||
1290 | 71 | else: | ||
1291 | 72 | self.value = user_input | ||
1292 | 73 | |||
1293 | 74 | self.asked = True | ||
1294 | 75 | return self.value | ||
1295 | 0 | 76 | ||
1296 | === added directory 'lava/tests' | |||
1297 | === added file 'lava/tests/__init__.py' | |||
1298 | === added file 'lava/tests/test_config.py' | |||
1299 | --- lava/tests/test_config.py 1970-01-01 00:00:00 +0000 | |||
1300 | +++ lava/tests/test_config.py 2013-06-27 12:48:26 +0000 | |||
1301 | @@ -0,0 +1,278 @@ | |||
1302 | 1 | # Copyright (C) 2013 Linaro Limited | ||
1303 | 2 | # | ||
1304 | 3 | # Author: Milo Casagrande <milo.casagrande@linaro.org> | ||
1305 | 4 | # | ||
1306 | 5 | # This file is part of lava-tool. | ||
1307 | 6 | # | ||
1308 | 7 | # lava-tool is free software: you can redistribute it and/or modify | ||
1309 | 8 | # it under the terms of the GNU Lesser General Public License version 3 | ||
1310 | 9 | # as published by the Free Software Foundation | ||
1311 | 10 | # | ||
1312 | 11 | # lava-tool is distributed in the hope that it will be useful, | ||
1313 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1314 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1315 | 14 | # GNU General Public License for more details. | ||
1316 | 15 | # | ||
1317 | 16 | # You should have received a copy of the GNU Lesser General Public License | ||
1318 | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. | ||
1319 | 18 | |||
1320 | 19 | """ | ||
1321 | 20 | lava.config unit tests. | ||
1322 | 21 | """ | ||
1323 | 22 | |||
1324 | 23 | import os | ||
1325 | 24 | import sys | ||
1326 | 25 | import tempfile | ||
1327 | 26 | |||
1328 | 27 | from StringIO import StringIO | ||
1329 | 28 | from mock import MagicMock, patch, call | ||
1330 | 29 | |||
1331 | 30 | from lava.config import ( | ||
1332 | 31 | Config, | ||
1333 | 32 | InteractiveConfig, | ||
1334 | 33 | ConfigParser, | ||
1335 | 34 | ) | ||
1336 | 35 | from lava.helper.tests.helper_test import HelperTest | ||
1337 | 36 | from lava.parameter import Parameter | ||
1338 | 37 | from lava.tool.errors import CommandError | ||
1339 | 38 | |||
1340 | 39 | |||
1341 | 40 | class MockedConfig(Config): | ||
1342 | 41 | """A subclass of the original Config class. | ||
1343 | 42 | |||
1344 | 43 | Used to test the Config class, but to not have the same constructor in | ||
1345 | 44 | order to use temporary files for the configuration. | ||
1346 | 45 | """ | ||
1347 | 46 | def __init__(self, config_file): | ||
1348 | 47 | self._cache = {} | ||
1349 | 48 | self._config_file = config_file | ||
1350 | 49 | self._config_backend = ConfigParser() | ||
1351 | 50 | self._config_backend.read([self._config_file]) | ||
1352 | 51 | |||
1353 | 52 | |||
1354 | 53 | class MockedInteractiveConfig(InteractiveConfig): | ||
1355 | 54 | def __init__(self, config_file, force_interactive=False): | ||
1356 | 55 | self._cache = {} | ||
1357 | 56 | self._config_file = config_file | ||
1358 | 57 | self._config_backend = ConfigParser() | ||
1359 | 58 | self._config_backend.read([self._config_file]) | ||
1360 | 59 | self._force_interactive = force_interactive | ||
1361 | 60 | |||
1362 | 61 | |||
1363 | 62 | class ConfigTestCase(HelperTest): | ||
1364 | 63 | """General test case class for the different Config classes.""" | ||
1365 | 64 | def setUp(self): | ||
1366 | 65 | super(ConfigTestCase, self).setUp() | ||
1367 | 66 | self.config_file = tempfile.NamedTemporaryFile(delete=False) | ||
1368 | 67 | |||
1369 | 68 | self.param1 = Parameter("foo") | ||
1370 | 69 | self.param2 = Parameter("bar", depends=self.param1) | ||
1371 | 70 | |||
1372 | 71 | def tearDown(self): | ||
1373 | 72 | super(ConfigTestCase, self).tearDown() | ||
1374 | 73 | if os.path.isfile(self.config_file.name): | ||
1375 | 74 | os.unlink(self.config_file.name) | ||
1376 | 75 | |||
1377 | 76 | |||
1378 | 77 | class ConfigTest(ConfigTestCase): | ||
1379 | 78 | |||
1380 | 79 | def setUp(self): | ||
1381 | 80 | super(ConfigTest, self).setUp() | ||
1382 | 81 | self.config = MockedConfig(self.config_file.name) | ||
1383 | 82 | |||
1384 | 83 | def test_assert_temp_config_file(self): | ||
1385 | 84 | # Dummy test to make sure we are overriding correctly the Config class. | ||
1386 | 85 | self.assertEqual(self.config._config_file, self.config_file.name) | ||
1387 | 86 | |||
1388 | 87 | def test_config_put_in_cache_0(self): | ||
1389 | 88 | self.config._put_in_cache("key", "value", "section") | ||
1390 | 89 | self.assertEqual(self.config._cache["section"]["key"], "value") | ||
1391 | 90 | |||
1392 | 91 | def test_config_get_from_cache_0(self): | ||
1393 | 92 | self.config._put_in_cache("key", "value", "section") | ||
1394 | 93 | obtained = self.config._get_from_cache(Parameter("key"), "section") | ||
1395 | 94 | self.assertEqual("value", obtained) | ||
1396 | 95 | |||
1397 | 96 | def test_config_get_from_cache_1(self): | ||
1398 | 97 | self.config._put_in_cache("key", "value", "DEFAULT") | ||
1399 | 98 | obtained = self.config._get_from_cache(Parameter("key"), "DEFAULT") | ||
1400 | 99 | self.assertEqual("value", obtained) | ||
1401 | 100 | |||
1402 | 101 | def test_config_put_0(self): | ||
1403 | 102 | # Puts a value in the DEFAULT section. | ||
1404 | 103 | self.config._put_in_cache = MagicMock() | ||
1405 | 104 | self.config.put("foo", "foo") | ||
1406 | 105 | expected = "foo" | ||
1407 | 106 | obtained = self.config._config_backend.get("DEFAULT", "foo") | ||
1408 | 107 | self.assertEqual(expected, obtained) | ||
1409 | 108 | |||
1410 | 109 | def test_config_put_1(self): | ||
1411 | 110 | # Puts a value in a new section. | ||
1412 | 111 | self.config._put_in_cache = MagicMock() | ||
1413 | 112 | self.config.put("foo", "foo", "bar") | ||
1414 | 113 | expected = "foo" | ||
1415 | 114 | obtained = self.config._config_backend.get("bar", "foo") | ||
1416 | 115 | self.assertEqual(expected, obtained) | ||
1417 | 116 | |||
1418 | 117 | def test_config_put_parameter_0(self): | ||
1419 | 118 | self.config._calculate_config_section = MagicMock(return_value="") | ||
1420 | 119 | self.assertRaises(CommandError, self.config.put_parameter, self.param1) | ||
1421 | 120 | |||
1422 | 121 | @patch("lava.config.Config.put") | ||
1423 | 122 | def test_config_put_parameter_1(self, mocked_config_put): | ||
1424 | 123 | self.config._calculate_config_section = MagicMock( | ||
1425 | 124 | return_value="DEFAULT") | ||
1426 | 125 | |||
1427 | 126 | self.param1.value = "bar" | ||
1428 | 127 | self.config.put_parameter(self.param1) | ||
1429 | 128 | |||
1430 | 129 | self.assertEqual(mocked_config_put.mock_calls, | ||
1431 | 130 | [call("foo", "bar", "DEFAULT")]) | ||
1432 | 131 | |||
1433 | 132 | def test_config_get_0(self): | ||
1434 | 133 | # Tests that with a non existing parameter, it returns None. | ||
1435 | 134 | param = Parameter("baz") | ||
1436 | 135 | self.config._get_from_cache = MagicMock(return_value=None) | ||
1437 | 136 | self.config._calculate_config_section = MagicMock( | ||
1438 | 137 | return_value="DEFAULT") | ||
1439 | 138 | |||
1440 | 139 | expected = None | ||
1441 | 140 | obtained = self.config.get(param) | ||
1442 | 141 | self.assertEqual(expected, obtained) | ||
1443 | 142 | |||
1444 | 143 | def test_config_get_1(self): | ||
1445 | 144 | self.config.put_parameter(self.param1, "foo") | ||
1446 | 145 | self.config._get_from_cache = MagicMock(return_value=None) | ||
1447 | 146 | self.config._calculate_config_section = MagicMock( | ||
1448 | 147 | return_value="DEFAULT") | ||
1449 | 148 | |||
1450 | 149 | expected = "foo" | ||
1451 | 150 | obtained = self.config.get(self.param1) | ||
1452 | 151 | self.assertEqual(expected, obtained) | ||
1453 | 152 | |||
1454 | 153 | def test_calculate_config_section_0(self): | ||
1455 | 154 | expected = "DEFAULT" | ||
1456 | 155 | obtained = self.config._calculate_config_section(self.param1) | ||
1457 | 156 | self.assertEqual(expected, obtained) | ||
1458 | 157 | |||
1459 | 158 | def test_calculate_config_section_1(self): | ||
1460 | 159 | self.config.put_parameter(self.param1, "foo") | ||
1461 | 160 | expected = "foo=foo" | ||
1462 | 161 | obtained = self.config._calculate_config_section(self.param2) | ||
1463 | 162 | self.assertEqual(expected, obtained) | ||
1464 | 163 | |||
1465 | 164 | def test_config_save(self): | ||
1466 | 165 | self.config.put_parameter(self.param1, "foo") | ||
1467 | 166 | self.config.save() | ||
1468 | 167 | |||
1469 | 168 | expected = "[DEFAULT]\nfoo = foo\n\n" | ||
1470 | 169 | obtained = "" | ||
1471 | 170 | with open(self.config_file.name) as tmp_file: | ||
1472 | 171 | obtained = tmp_file.read() | ||
1473 | 172 | self.assertEqual(expected, obtained) | ||
1474 | 173 | |||
1475 | 174 | @patch("lava.config.AT_EXIT_CALLS", spec=set) | ||
1476 | 175 | def test_config_atexit_call_list(self, mocked_calls): | ||
1477 | 176 | # Tests that the save() method is added to the set of atexit calls. | ||
1478 | 177 | config = Config() | ||
1479 | 178 | config._config_file = self.config_file.name | ||
1480 | 179 | config.put_parameter(self.param1, "foo") | ||
1481 | 180 | |||
1482 | 181 | expected = [call.add(config.save)] | ||
1483 | 182 | |||
1484 | 183 | self.assertEqual(expected, mocked_calls.mock_calls) | ||
1485 | 184 | |||
1486 | 185 | |||
1487 | 186 | class InteractiveConfigTest(ConfigTestCase): | ||
1488 | 187 | |||
1489 | 188 | def setUp(self): | ||
1490 | 189 | super(InteractiveConfigTest, self).setUp() | ||
1491 | 190 | self.config = MockedInteractiveConfig( | ||
1492 | 191 | config_file=self.config_file.name) | ||
1493 | 192 | |||
1494 | 193 | @patch("lava.config.Config.get", new=MagicMock(return_value=None)) | ||
1495 | 194 | def test_non_interactive_config_0(self): | ||
1496 | 195 | # Mocked config default is not to be interactive. | ||
1497 | 196 | # Try to get a value that does not exists, users just press enter when | ||
1498 | 197 | # asked for a value. Value will be empty. | ||
1499 | 198 | sys.stdin = StringIO("\n") | ||
1500 | 199 | value = self.config.get(Parameter("foo")) | ||
1501 | 200 | self.assertEqual("", value) | ||
1502 | 201 | |||
1503 | 202 | @patch("lava.config.Config.get", new=MagicMock(return_value="value")) | ||
1504 | 203 | def test_non_interactive_config_1(self): | ||
1505 | 204 | # Parent class config returns a value, but we are not interactive. | ||
1506 | 205 | value = self.config.get(Parameter("foo")) | ||
1507 | 206 | self.assertEqual("value", value) | ||
1508 | 207 | |||
1509 | 208 | @patch("lava.config.Config.get", new=MagicMock(return_value=None)) | ||
1510 | 209 | def test_non_interactive_config_2(self): | ||
1511 | 210 | expected = "bar" | ||
1512 | 211 | sys.stdin = StringIO(expected) | ||
1513 | 212 | value = self.config.get(Parameter("foo")) | ||
1514 | 213 | self.assertEqual(expected, value) | ||
1515 | 214 | |||
1516 | 215 | @patch("lava.config.Config.get", new=MagicMock(return_value="value")) | ||
1517 | 216 | def test_interactive_config_0(self): | ||
1518 | 217 | # We force to be interactive, meaning that even if a value is found, | ||
1519 | 218 | # it will be asked anyway. | ||
1520 | 219 | self.config._force_interactive = True | ||
1521 | 220 | expected = "a_new_value" | ||
1522 | 221 | sys.stdin = StringIO(expected) | ||
1523 | 222 | value = self.config.get(Parameter("foo")) | ||
1524 | 223 | self.assertEqual(expected, value) | ||
1525 | 224 | |||
1526 | 225 | @patch("lava.config.Config.get", new=MagicMock(return_value="value")) | ||
1527 | 226 | def test_interactive_config_1(self): | ||
1528 | 227 | # Force to be interactive, but when asked for the new value press | ||
1529 | 228 | # Enter. The old value should be returned. | ||
1530 | 229 | self.config._force_interactive = True | ||
1531 | 230 | sys.stdin = StringIO("\n") | ||
1532 | 231 | value = self.config.get(Parameter("foo")) | ||
1533 | 232 | self.assertEqual("value", value) | ||
1534 | 233 | |||
1535 | 234 | def test_calculate_config_section_0(self): | ||
1536 | 235 | self.config._force_interactive = True | ||
1537 | 236 | obtained = self.config._calculate_config_section(self.param1) | ||
1538 | 237 | expected = "DEFAULT" | ||
1539 | 238 | self.assertEqual(expected, obtained) | ||
1540 | 239 | |||
1541 | 240 | def test_calculate_config_section_1(self): | ||
1542 | 241 | self.param2.depends.asked = True | ||
1543 | 242 | self.config._force_interactive = True | ||
1544 | 243 | self.config.put(self.param1.id, "foo") | ||
1545 | 244 | obtained = self.config._calculate_config_section(self.param2) | ||
1546 | 245 | expected = "foo=foo" | ||
1547 | 246 | self.assertEqual(expected, obtained) | ||
1548 | 247 | |||
1549 | 248 | def test_calculate_config_section_2(self): | ||
1550 | 249 | self.config._force_interactive = True | ||
1551 | 250 | self.config._config_backend.get = MagicMock(return_value=None) | ||
1552 | 251 | sys.stdin = StringIO("baz") | ||
1553 | 252 | expected = "foo=baz" | ||
1554 | 253 | obtained = self.config._calculate_config_section(self.param2) | ||
1555 | 254 | self.assertEqual(expected, obtained) | ||
1556 | 255 | |||
1557 | 256 | def test_calculate_config_section_3(self): | ||
1558 | 257 | # Tests that when a parameter has its value in the cache and also on | ||
1559 | 258 | # file, we honor the cached version. | ||
1560 | 259 | self.param2.depends.asked = True | ||
1561 | 260 | self.config._force_interactive = True | ||
1562 | 261 | self.config._get_from_cache = MagicMock(return_value="bar") | ||
1563 | 262 | self.config._config_backend.get = MagicMock(return_value="baz") | ||
1564 | 263 | expected = "foo=bar" | ||
1565 | 264 | obtained = self.config._calculate_config_section(self.param2) | ||
1566 | 265 | self.assertEqual(expected, obtained) | ||
1567 | 266 | |||
1568 | 267 | @patch("lava.config.Config.get", new=MagicMock(return_value=None)) | ||
1569 | 268 | @patch("lava.parameter.sys.exit") | ||
1570 | 269 | @patch("lava.parameter.raw_input", create=True) | ||
1571 | 270 | def test_interactive_config_exit(self, mocked_raw, mocked_sys_exit): | ||
1572 | 271 | self.config._calculate_config_section = MagicMock( | ||
1573 | 272 | return_value="DEFAULT") | ||
1574 | 273 | |||
1575 | 274 | mocked_raw.side_effect = KeyboardInterrupt() | ||
1576 | 275 | |||
1577 | 276 | self.config._force_interactive = True | ||
1578 | 277 | self.config.get(self.param1) | ||
1579 | 278 | self.assertTrue(mocked_sys_exit.called) | ||
1580 | 0 | 279 | ||
1581 | === added file 'lava/tests/test_parameter.py' | |||
1582 | --- lava/tests/test_parameter.py 1970-01-01 00:00:00 +0000 | |||
1583 | +++ lava/tests/test_parameter.py 2013-06-27 12:48:26 +0000 | |||
1584 | @@ -0,0 +1,51 @@ | |||
1585 | 1 | # Copyright (C) 2013 Linaro Limited | ||
1586 | 2 | # | ||
1587 | 3 | # Author: Milo Casagrande <milo.casagrande@linaro.org> | ||
1588 | 4 | # | ||
1589 | 5 | # This file is part of lava-tool. | ||
1590 | 6 | # | ||
1591 | 7 | # lava-tool is free software: you can redistribute it and/or modify | ||
1592 | 8 | # it under the terms of the GNU Lesser General Public License version 3 | ||
1593 | 9 | # as published by the Free Software Foundation | ||
1594 | 10 | # | ||
1595 | 11 | # lava-tool is distributed in the hope that it will be useful, | ||
1596 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1597 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1598 | 14 | # GNU General Public License for more details. | ||
1599 | 15 | # | ||
1600 | 16 | # You should have received a copy of the GNU Lesser General Public License | ||
1601 | 17 | # along with lava-tool. If not, see <http://www.gnu.org/licenses/>. | ||
1602 | 18 | |||
1603 | 19 | """ | ||
1604 | 20 | lava.parameter unit tests. | ||
1605 | 21 | """ | ||
1606 | 22 | |||
1607 | 23 | import sys | ||
1608 | 24 | |||
1609 | 25 | from StringIO import StringIO | ||
1610 | 26 | from mock import patch | ||
1611 | 27 | |||
1612 | 28 | from lava.helper.tests.helper_test import HelperTest | ||
1613 | 29 | from lava.parameter import Parameter | ||
1614 | 30 | |||
1615 | 31 | |||
1616 | 32 | class ParameterTest(HelperTest): | ||
1617 | 33 | |||
1618 | 34 | def setUp(self): | ||
1619 | 35 | super(ParameterTest, self).setUp() | ||
1620 | 36 | self.parameter1 = Parameter("foo", value="baz") | ||
1621 | 37 | |||
1622 | 38 | def test_prompt_0(self): | ||
1623 | 39 | # Tests that when we have a value in the parameters and the user press | ||
1624 | 40 | # Enter, we get the old value back. | ||
1625 | 41 | sys.stdin = StringIO("\n") | ||
1626 | 42 | obtained = self.parameter1.prompt() | ||
1627 | 43 | self.assertEqual(self.parameter1.value, obtained) | ||
1628 | 44 | |||
1629 | 45 | @patch("lava.parameter.raw_input", create=True) | ||
1630 | 46 | def test_prompt_1(self, mocked_raw_input): | ||
1631 | 47 | # Tests that with a value stored in the parameter, if and EOFError is | ||
1632 | 48 | # raised when getting user input, we get back the old value. | ||
1633 | 49 | mocked_raw_input.side_effect = EOFError() | ||
1634 | 50 | obtained = self.parameter1.prompt() | ||
1635 | 51 | self.assertEqual(self.parameter1.value, obtained) | ||
1636 | 0 | 52 | ||
1637 | === modified file 'lava_tool/tests/__init__.py' | |||
1638 | --- lava_tool/tests/__init__.py 2013-06-27 12:48:26 +0000 | |||
1639 | +++ lava_tool/tests/__init__.py 2013-06-27 12:48:26 +0000 | |||
1640 | @@ -44,6 +44,8 @@ | |||
1641 | 44 | 'lava.job.tests.test_commands', | 44 | 'lava.job.tests.test_commands', |
1642 | 45 | 'lava.device.tests.test_device', | 45 | 'lava.device.tests.test_device', |
1643 | 46 | 'lava.device.tests.test_commands', | 46 | 'lava.device.tests.test_commands', |
1644 | 47 | 'lava.tests.test_config', | ||
1645 | 48 | 'lava.tests.test_parameter', | ||
1646 | 47 | 'lava.helper.tests.test_command', | 49 | 'lava.helper.tests.test_command', |
1647 | 48 | 'lava.helper.tests.test_dispatcher', | 50 | 'lava.helper.tests.test_dispatcher', |
1648 | 49 | ] | 51 | ] |
Hi Milo,
Thanks for your efforts with this. I am amazed by the amount of tests you have
been adding. :-)
I have some comments below
review needs-fixing
> === added file 'HACKING' ======= ====== tests.test_ suite 2>/dev/null && python-coverage html coverage` : open
> --- HACKING 1970-01-01 00:00:00 +0000
> +++ HACKING 2013-06-20 15:51:34 +0000
> @@ -0,0 +1,17 @@
> +Tests Code Coverage
> +======
> +
> +To have a nicely HTML viewable report on tests code coverage, do as follows:
> +
> +* Install `python-coverage` (`pip install coverage` in case you use pip)
> +* Run the following command:
> +
> + python-coverage run -m unittest lava_tool.
> +
> +* The report will be save in a directory called `lava_tool_
typo: save → saved
> +the `index.html` file in there to see the report. coverage` www.gnu. org/licenses/>. nfig', 'NonInteractive Config' ] join(os. path.expanduser ("~"), ".lava_history") fig'] join(os. path.expanduser ("~"), ".lava_history") read_history_ file(history) read_history_ file(HISTORY) register( readline. write_history_ file, history) get('LAVACONFIG ') or join(os. path.expanduser ('~'), '.lavaconfig')) backend. read([config_ file]) backend. write(f) register( save_config) ig(object) : ve=False) : interactive = force_interactive register( readline. write_history_ file, HISTORY) EXIT_CALLS) : register( _run_at_ exit)
> +
> +Notes:
> +
> + * To re-run the coverage report, you have to delete the `lava_tool_
> +directory first, otherwise `python-coverage` will fail.
>
> === modified file 'lava/config.py'
> --- lava/config.py 2013-06-20 15:51:34 +0000
> +++ lava/config.py 2013-06-20 15:51:34 +0000
> @@ -16,91 +16,224 @@
> # You should have received a copy of the GNU Lesser General Public License
> # along with lava-tool. If not, see <http://
>
> +"""
> +Config class.
> +"""
> +
> import atexit
> -from ConfigParser import ConfigParser, NoOptionError, NoSectionError
> import os
> import readline
> -
> -__all__ = ['InteractiveCo
> -
> -history = os.path.
> +import sys
> +
> +
> +from ConfigParser import ConfigParser, NoOptionError, NoSectionError
> +
> +__all__ = ['Config', 'InteractiveCon
> +
> +# Store for function calls to be made at exit time.
> +AT_EXIT_CALLS = set()
> +# Config default section.
> +DEFAULT_SECTION = "DEFAULT"
> +
> +HISTORY = os.path.
> try:
> - readline.
> + readline.
> except IOError:
> pass
> -atexit.
> -
> -config_file = (os.environ.
> - os.path.
> -config_backend = ConfigParser()
> -config_
> -
> -
> -def save_config():
> - with open(config_file, 'w') as f:
> - config_
> -atexit.
> -
> -
> -class Parameter(object):
> -
> - def __init__(self, id, depends=None):
> - self.id = id
> - self.depends = depends
> -
> -
> -class InteractiveConf
> -
> - def __init__(self, force_interacti
> - self._force_
> +atexit.
> +
> +
> +def _run_at_exit():
> + """Runs all the function at exit."""
> + for call in list(AT_
> + call()
> +atexit.
> +
> +
> +class Config(object):
> + """A generic config object."""
> + def __init__(self):
> + # The cache where to store parameters.
> self._cache = {}
> -
> - def get(self, parameter):
> - key = parameter.id
> - ...