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

Proposed by Milo Casagrande
Status: Merged
Merged at revision: 190
Proposed branch: lp:~milo/lava-tool/bug1206579
Merge into: lp:~linaro-validation/lava-tool/trunk
Diff against target: 397 lines (+168/-45)
6 files modified
integration-tests.d/lava-job-new-with-config.sh (+1/-1)
lava/config.py (+47/-3)
lava/helper/command.py (+4/-2)
lava/testdef/tests/test_commands.py (+9/-4)
lava/tests/test_config.py (+105/-34)
setup.py (+2/-1)
To merge this branch: bzr merge lp:~milo/lava-tool/bug1206579
Reviewer Review Type Date Requested Status
Antonio Terceiro Approve
Linaro Validation Team Pending
Review via email: mp+177829@code.launchpad.net

Description of the change

Added initial support for XDG directories.

The Config and InteractiveConfig class now will default to use $XDG_CONFIG_HOME as the directory where to store configurations. The dir tree will be as follows: $XDG_CONFIG_HOME/linaro/lava-tool/. The filename used here is "lava-tool.ini".

The "linaro" part of the path, if somebody is using linaro-image-tools or other CLI tools, is already in place.

A new class, that inherits from InteractiveConfig has been introduced: InteractiveCache. This is exactly the same class as the other config ones, only it stores values in $XDG_CACHE_HOME/linaro/lava-tool/. The filename used here is "parameters.ini".

The BaseCommand class has been fixed to use the new InteractiveCache class.

Tests have been added and fixed where necessary.

To post a comment you must log in.
Revision history for this message
Antonio Terceiro (terceiro) wrote :

Looks good to me. :-)

 review approve

> The BaseCommand class has been fixed to use the new InteractiveCache
> class.

So effectively InteractiveConfig in itself is now unused, apart from
being the base class for InteractiveCache. Config was already not used
directly ...

Maybe we could collapse the entire Config ← InteractiveConfig ←
InteractiveCache hierarchy into a single class?

Or do we want to create a scheme where the user *can* create an actual
config file that will provide defaults when prompting? Maybe a single
config object could load both config and cache files, and when something
is not found on cache, it falls back to the value in the config as
default when prompting ...

(not suggesting to do this for this MP though - just food for thought)

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

On Thu, Aug 1, 2013 at 2:25 AM, Antonio Terceiro
<email address hidden> wrote:
> Review: Approve
>
> Looks good to me. :-)
>
> review approve
>
>> The BaseCommand class has been fixed to use the new InteractiveCache
>> class.
>
> So effectively InteractiveConfig in itself is now unused, apart from
> being the base class for InteractiveCache. Config was already not used
> directly ...
>
> Maybe we could collapse the entire Config ← InteractiveConfig ←
> InteractiveCache hierarchy into a single class?

I still value separating Config and InteractiveConfig, but I agree we
should find a better way for InteractiveCache.

> Or do we want to create a scheme where the user *can* create an actual
> config file that will provide defaults when prompting? Maybe a single
> config object could load both config and cache files, and when something
> is not found on cache, it falls back to the value in the config as
> default when prompting ...

Sounds as a reasonable trade-off to me.
If I have a little bit of time I can work on it. Will file a bug.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'integration-tests.d/lava-job-new-with-config.sh'
2--- integration-tests.d/lava-job-new-with-config.sh 2013-05-28 22:08:12 +0000
3+++ integration-tests.d/lava-job-new-with-config.sh 2013-07-31 12:49:45 +0000
4@@ -5,6 +5,6 @@
5 [device_type=panda]
6 prebuilt_image = file:///path/to/panda.img
7 EOF
8-LAVACONFIG="${tmpdir}/config" lava job new "${tmpdir}/job.json"
9+LAVACACHE="${tmpdir}/config" lava job new "${tmpdir}/job.json"
10 cat "${tmpdir}/job.json"
11 grep "device_type.*panda" "${tmpdir}/job.json" && grep "image.*path.to.panda.img" "${tmpdir}/job.json"
12
13=== modified file 'lava/config.py'
14--- lava/config.py 2013-07-26 13:48:06 +0000
15+++ lava/config.py 2013-07-31 12:49:45 +0000
16@@ -23,6 +23,7 @@
17 import atexit
18 import os
19 import readline
20+import xdg.BaseDirectory as xdgBaseDir
21
22 from ConfigParser import (
23 ConfigParser,
24@@ -33,12 +34,16 @@
25 from lava.parameter import Parameter
26 from lava.tool.errors import CommandError
27
28-__all__ = ['Config', 'InteractiveConfig']
29+__all__ = ['Config', 'InteractiveCache', 'InteractiveConfig']
30
31 # Store for function calls to be made at exit time.
32 AT_EXIT_CALLS = set()
33 # Config default section.
34 DEFAULT_SECTION = "DEFAULT"
35+# This is the default base name used to create XDG resources.
36+DEFAULT_XDG_RESOURCE = "linaro"
37+# This is the default name for lava-tool resources.
38+DEFAULT_LAVA_TOOL_RESOURCE = "lava-tool"
39
40 HISTORY = os.path.join(os.path.expanduser("~"), ".lava_history")
41 try:
42@@ -69,8 +74,8 @@
43 def config_file(self):
44 if self._config_file is None:
45 self._config_file = (os.environ.get('LAVACONFIG') or
46- os.path.join(os.path.expanduser('~'),
47- '.lavaconfig'))
48+ os.path.join(self._ensure_xdg_dirs(),
49+ 'lava-tool.ini'))
50 return self._config_file
51
52 @config_file.setter
53@@ -84,6 +89,14 @@
54 self._config_backend.read([self.config_file])
55 return self._config_backend
56
57+ def _ensure_xdg_dirs(self):
58+ """Make sure we have the default resource.
59+
60+ :return The path to the XDG resource.
61+ """
62+ return xdgBaseDir.save_config_path(DEFAULT_XDG_RESOURCE,
63+ DEFAULT_LAVA_TOOL_RESOURCE)
64+
65 def _calculate_config_section(self, parameter):
66 """Calculates the config section of the specified parameter.
67
68@@ -248,3 +261,34 @@
69 if value is not None and parameter.store:
70 self.put(parameter.id, value, section)
71 return value
72+
73+
74+class InteractiveCache(InteractiveConfig):
75+
76+ """An interactive cache where parameters that can change are stored.
77+
78+ This class is basically the same as the Confing and InteractiveConfig ones,
79+ only the base directory where the cache file is stored is different.
80+
81+ In this case it will use the $XDG_CACHE_HOME value as defined in XDG.
82+ """
83+
84+ @property
85+ def config_file(self):
86+ if self._config_file is None:
87+ self._config_file = (os.environ.get('LAVACACHE') or
88+ os.path.join(self._ensure_xdg_dirs(),
89+ 'parameters.ini'))
90+ return self._config_file
91+
92+ @config_file.setter
93+ def config_file(self, value):
94+ self._config_file = value
95+
96+ def _ensure_xdg_dirs(self):
97+ """Make sure we have the default resource.
98+
99+ :return The path to the XDG resource.
100+ """
101+ return xdgBaseDir.save_cache_path(DEFAULT_XDG_RESOURCE,
102+ DEFAULT_LAVA_TOOL_RESOURCE)
103
104=== modified file 'lava/helper/command.py'
105--- lava/helper/command.py 2013-07-29 10:09:09 +0000
106+++ lava/helper/command.py 2013-07-31 12:49:45 +0000
107@@ -22,7 +22,9 @@
108 import sys
109 import xmlrpclib
110
111-from lava.config import InteractiveConfig
112+from lava.config import (
113+ InteractiveCache,
114+)
115 from lava.helper.dispatcher import get_devices
116 from lava.job import Job
117 from lava.job.templates import (
118@@ -58,7 +60,7 @@
119 verify_and_create_url,
120 )
121
122-CONFIG = InteractiveConfig()
123+CONFIG = InteractiveCache()
124
125
126 class BaseCommand(Command):
127
128=== modified file 'lava/testdef/tests/test_commands.py'
129--- lava/testdef/tests/test_commands.py 2013-07-26 08:10:16 +0000
130+++ lava/testdef/tests/test_commands.py 2013-07-31 12:49:45 +0000
131@@ -25,10 +25,11 @@
132 import yaml
133
134 from mock import (
135+ MagicMock,
136 patch,
137 )
138
139-from lava.config import InteractiveConfig
140+from lava.config import InteractiveCache
141 from lava.helper.tests.helper_test import HelperTest
142 from lava.testdef.commands import (
143 new,
144@@ -39,8 +40,7 @@
145 class NewCommandTest(HelperTest):
146 """Class for the lava.testdef new command tests."""
147
148- @patch("lava.config.Config.save")
149- def setUp(self, mocked_save):
150+ def setUp(self):
151 super(NewCommandTest, self).setUp()
152 self.file_name = "fake_testdef.yaml"
153 self.file_path = os.path.join(tempfile.gettempdir(), self.file_name)
154@@ -50,7 +50,8 @@
155 delete=False)
156
157 self.config_file = tempfile.NamedTemporaryFile(delete=False)
158- self.config = InteractiveConfig()
159+ self.config = InteractiveCache()
160+ self.config.save = MagicMock()
161 self.config.config_file = self.config_file.name
162 # Patch class raw_input, start it, and stop it on tearDown.
163 self.patcher1 = patch("lava.parameter.raw_input", create=True)
164@@ -84,6 +85,7 @@
165 # file system.
166 self.mocked_raw_input.return_value = "\n"
167 new_command = new(self.parser, self.args)
168+ new_command.config = self.config
169 new_command.invoke()
170 self.assertTrue(os.path.exists(self.file_path))
171
172@@ -92,6 +94,7 @@
173 # thrown.
174 self.args.FILE = self.temp_yaml.name
175 new_command = new(self.parser, self.args)
176+ new_command.config = self.config
177 self.assertRaises(CommandError, new_command.invoke)
178
179 def test_invoke_2(self):
180@@ -124,6 +127,7 @@
181 self.args.FILE = "/test_file.yaml"
182 self.mocked_raw_input.return_value = "\n"
183 new_command = new(self.parser, self.args)
184+ new_command.config = self.config
185 self.assertRaises(CommandError, new_command.invoke)
186 self.assertFalse(os.path.exists(self.args.FILE))
187
188@@ -133,6 +137,7 @@
189 self.mocked_raw_input.side_effect = ["foo", "\n", "\n", "\n", "\n",
190 "\n"]
191 new_command = new(self.parser, self.args)
192+ new_command.config = self.config
193 new_command.invoke()
194 expected = {'run': {'steps': ["./mytest.sh"]},
195 'metadata': {
196
197=== modified file 'lava/tests/test_config.py'
198--- lava/tests/test_config.py 2013-07-26 08:10:16 +0000
199+++ lava/tests/test_config.py 2013-07-31 12:49:45 +0000
200@@ -20,6 +20,8 @@
201 lava.config unit tests.
202 """
203
204+import os
205+import shutil
206 import sys
207
208 from StringIO import StringIO
209@@ -31,6 +33,7 @@
210
211 from lava.config import (
212 Config,
213+ InteractiveCache,
214 InteractiveConfig,
215 )
216 from lava.helper.tests.helper_test import HelperTest
217@@ -49,17 +52,75 @@
218 self.param2 = Parameter("bar", depends=self.param1)
219
220
221+class TestConfigSave(ConfigTestCase):
222+
223+ """Used to test the save() method of config class.
224+
225+ Done here since in the other tests we want to mock the atexit save call
226+ in order not to write the file, or accidentaly overwrite the real
227+ user file.
228+ """
229+
230+ def setUp(self):
231+ super(TestConfigSave, self).setUp()
232+ self.config = Config()
233+ self.config.config_file = self.temp_file.name
234+
235+ def test_config_save(self):
236+ self.config.put_parameter(self.param1, "foo")
237+ self.config.save()
238+
239+ expected = "[DEFAULT]\nfoo = foo\n\n"
240+ obtained = ""
241+ with open(self.temp_file.name) as tmp_file:
242+ obtained = tmp_file.read()
243+ self.assertEqual(expected, obtained)
244+
245+ def test_save_list_param(self):
246+ # Tests that when saved to file, the ListParameter parameter is stored
247+ # correctly.
248+ param_values = ["foo", "more than one words", "bar"]
249+ list_param = ListParameter("list")
250+ list_param.set(param_values)
251+
252+ self.config.put_parameter(list_param, param_values)
253+ self.config.save()
254+
255+ expected = "[DEFAULT]\nlist = " + ",".join(param_values) + "\n\n"
256+ obtained = ""
257+ with open(self.temp_file.name, "r") as read_file:
258+ obtained = read_file.read()
259+ self.assertEqual(expected, obtained)
260+
261+
262 class ConfigTest(ConfigTestCase):
263
264- @patch("lava.config.Config.save")
265- def setUp(self, mocked_save):
266+ def setUp(self):
267 super(ConfigTest, self).setUp()
268+ self.patcher = patch("lava.config.DEFAULT_XDG_RESOURCE", "a_temp_dir")
269+ self.patcher.start()
270+ self.xdg_resource = os.path.join(
271+ os.path.expanduser("~"), ".config/a_temp_dir")
272+ self.lavatool_resource = os.path.join(self.xdg_resource, "lava-tool")
273 self.config = Config()
274- self.config.config_file = self.temp_file.name
275-
276- def test_assert_temp_config_file(self):
277- # Dummy test to make sure we are overriding correctly the Config class.
278- self.assertEqual(self.config.config_file, self.temp_file.name)
279+ self.config.save = MagicMock()
280+
281+ def tearDown(self):
282+ super(ConfigTest, self).tearDown()
283+ self.patcher.stop()
284+ if os.path.isdir(self.xdg_resource):
285+ shutil.rmtree(self.xdg_resource)
286+
287+ def test_ensure_xdg_dirs(self):
288+ # Test that xdg can create the correct cache path, we remove it
289+ # at the end since we patch the default value.
290+ obtained = self.config._ensure_xdg_dirs()
291+ self.assertEquals(self.lavatool_resource, obtained)
292+
293+ def test_config_file(self):
294+ expected = os.path.join(self.lavatool_resource, "lava-tool.ini")
295+ obtained = self.config.config_file
296+ self.assertEquals(expected, obtained)
297
298 def test_config_put_in_cache_0(self):
299 self.config._put_in_cache("key", "value", "section")
300@@ -138,16 +199,6 @@
301 obtained = self.config._calculate_config_section(self.param2)
302 self.assertEqual(expected, obtained)
303
304- def test_config_save(self):
305- self.config.put_parameter(self.param1, "foo")
306- self.config.save()
307-
308- expected = "[DEFAULT]\nfoo = foo\n\n"
309- obtained = ""
310- with open(self.temp_file.name) as tmp_file:
311- obtained = tmp_file.read()
312- self.assertEqual(expected, obtained)
313-
314 def test_config_get_from_backend_public(self):
315 # Need to to this, since we want a clean Config instance, with
316 # a config_file with some content.
317@@ -160,10 +211,10 @@
318
319 class InteractiveConfigTest(ConfigTestCase):
320
321- @patch("lava.config.Config.save")
322- def setUp(self, mocked_save):
323+ def setUp(self):
324 super(InteractiveConfigTest, self).setUp()
325 self.config = InteractiveConfig()
326+ self.config.save = MagicMock()
327 self.config.config_file = self.temp_file.name
328
329 @patch("lava.config.Config.get", new=MagicMock(return_value=None))
330@@ -264,18 +315,38 @@
331 self.assertIsInstance(obtained, list)
332 self.assertEqual(expected, obtained)
333
334- def test_interactive_save_list_param(self):
335- # Tests that when saved to file, the ListParameter parameter is stored
336- # correctly.
337- param_values = ["foo", "more than one words", "bar"]
338- list_param = ListParameter("list")
339- list_param.set(param_values)
340-
341- self.config.put_parameter(list_param, param_values)
342- self.config.save()
343-
344- expected = "[DEFAULT]\nlist = " + ",".join(param_values) + "\n\n"
345- obtained = ""
346- with open(self.temp_file.name, "r") as read_file:
347- obtained = read_file.read()
348- self.assertEqual(expected, obtained)
349+
350+class TestInteractiveCache(HelperTest):
351+
352+ def setUp(self):
353+ super(TestInteractiveCache, self).setUp()
354+ self.patcher = patch("lava.config.DEFAULT_XDG_RESOURCE", "a_temp_dir")
355+ self.patcher.start()
356+ self.cache = InteractiveCache()
357+ self.cache.save = MagicMock()
358+ self.xdg_resource = os.path.join(
359+ os.path.expanduser("~"), ".cache/a_temp_dir")
360+ self.lavatool_resource = os.path.join(self.xdg_resource, "lava-tool")
361+
362+ def tearDown(self):
363+ super(TestInteractiveCache, self).tearDown()
364+ self.patcher.stop()
365+ if os.path.isdir(self.xdg_resource):
366+ shutil.rmtree(self.xdg_resource)
367+
368+ def test_default_xdg(self):
369+ # Dummy test, only to make sure patching the module attribute works.
370+ from lava.config import DEFAULT_XDG_RESOURCE
371+ self.assertEquals(DEFAULT_XDG_RESOURCE, "a_temp_dir")
372+
373+ def test_ensure_xdg_dirs(self):
374+ # Test that xdg can create the correct cache path, we remove it
375+ # at the end since we patch the default value.
376+ obtained = self.cache._ensure_xdg_dirs()
377+ self.assertEquals(self.lavatool_resource, obtained)
378+
379+ def test_config_file(self):
380+ expected = os.path.join(self.lavatool_resource, "parameters.ini")
381+ obtained = self.cache.config_file
382+ self.assertEquals(expected, obtained)
383+
384
385=== modified file 'setup.py'
386--- setup.py 2013-07-17 14:30:42 +0000
387+++ setup.py 2013-07-31 12:49:45 +0000
388@@ -51,7 +51,8 @@
389 'argcomplete >= 0.3',
390 'keyring',
391 'json-schema-validator >= 2.0',
392- 'versiontools >= 1.3.1'
393+ 'versiontools >= 1.3.1',
394+ 'pyxdg == 0.25',
395 ],
396 setup_requires=['versiontools >= 1.3.1'],
397 tests_require=[

Subscribers

People subscribed via source and target branches