Merge lp:~zyga/lava-core/workspace into lp:lava-core
- workspace
- Merge into trunk
Proposed by
Zygmunt Krynicki
Status: | Merged |
---|---|
Merged at revision: | 16 |
Proposed branch: | lp:~zyga/lava-core/workspace |
Merge into: | lp:lava-core |
Diff against target: |
367 lines (+340/-2) 3 files modified
lava/core/environment.py (+4/-2) lava/core/tests/test_workspace.py (+198/-0) lava/core/workspace.py (+138/-0) |
To merge this branch: | bzr merge lp:~zyga/lava-core/workspace |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Linaro Validation Team | Pending | ||
Review via email: mp+107585@code.launchpad.net |
Commit message
Description of the change
This branch adds the workspace concept and a few tests
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lava/core/environment.py' |
2 | --- lava/core/environment.py 2012-05-07 09:11:28 +0000 |
3 | +++ lava/core/environment.py 2012-05-28 06:43:19 +0000 |
4 | @@ -48,7 +48,9 @@ |
5 | "type": "object", |
6 | "properties": { |
7 | "name": { |
8 | - "type": "string" |
9 | + "type": "string", |
10 | + "optional": True, |
11 | + "default": None |
12 | }, |
13 | } |
14 | } |
15 | @@ -57,4 +59,4 @@ |
16 | def name(self): |
17 | """ |
18 | Human readable name of the environment |
19 | - """ |
20 | \ No newline at end of file |
21 | + """ |
22 | |
23 | === added file 'lava/core/tests/test_workspace.py' |
24 | --- lava/core/tests/test_workspace.py 1970-01-01 00:00:00 +0000 |
25 | +++ lava/core/tests/test_workspace.py 2012-05-28 06:43:19 +0000 |
26 | @@ -0,0 +1,198 @@ |
27 | +# Copyright (C) 2011-2012 Linaro Limited |
28 | +# |
29 | +# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org> |
30 | +# |
31 | +# This file is part of lava-core |
32 | +# |
33 | +# lava-core is free software: you can redistribute it and/or modify |
34 | +# it under the terms of the GNU Lesser General Public License version 3 |
35 | +# as published by the Free Software Foundation |
36 | +# |
37 | +# lava-core is distributed in the hope that it will be useful, |
38 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
39 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
40 | +# GNU General Public License for more details. |
41 | +# |
42 | +# You should have received a copy of the GNU Lesser General Public License |
43 | +# along with lava-core. If not, see <http://www.gnu.org/licenses/>. |
44 | + |
45 | +""" |
46 | +lava.core.tests.test_workspace |
47 | +============================== |
48 | + |
49 | +Tests for lava.core.workspace |
50 | +""" |
51 | + |
52 | +import os |
53 | + |
54 | +from unittest2 import TestCase |
55 | +from mockfs import MockedFileSystem |
56 | + |
57 | +from lava.core.workspace import Workspace |
58 | +from lava.core.environment import Environment |
59 | + |
60 | + |
61 | +class WorkspaceTests(TestCase): |
62 | + |
63 | + _location = "location" |
64 | + _location_nested = "location/nested/somewhere" |
65 | + _name = "name" |
66 | + |
67 | + def setUp(self): |
68 | + super(WorkspaceTests, self).setUp() |
69 | + self.fs = MockedFileSystem() |
70 | + |
71 | + def create_workspace(self, location=".", environment_text="{ }"): |
72 | + """ |
73 | + Create a workspace with at the specified location and add the |
74 | + environment json with the specified text to it. |
75 | + """ |
76 | + lava_dir = os.path.join(location, ".lava") |
77 | + if not os.path.exists(lava_dir): |
78 | + os.makedirs(lava_dir) |
79 | + env_path = os.path.join(lava_dir, "environment.json") |
80 | + with file(env_path, "wt") as stream: |
81 | + stream.write(environment_text) |
82 | + |
83 | + def test__init__with_workspace(self): |
84 | + """ |
85 | + Workspace() works when .lava directory is found |
86 | + """ |
87 | + with self.fs.record(): |
88 | + self.create_workspace(self._location) |
89 | + with self.fs.replay(): |
90 | + Workspace(self._location) |
91 | + self.assertTrue(True) |
92 | + |
93 | + def test__init__without_workspace(self): |
94 | + """ |
95 | + Workspace() raises EnvironmentError when .lava is not found |
96 | + """ |
97 | + with self.assertRaises(EnvironmentError): |
98 | + Workspace(self._location) |
99 | + |
100 | + def test_location(self): |
101 | + """ |
102 | + Workspace().location returns the location of the workspace |
103 | + """ |
104 | + with self.fs.record(): |
105 | + self.create_workspace(self._location) |
106 | + with self.fs.replay(): |
107 | + workspace = Workspace(self._location) |
108 | + self.assertEqual(workspace.location, self._location) |
109 | + |
110 | + def test_environment_returns_Environment(self): |
111 | + """ |
112 | + Workspace().environment returns an Environment object |
113 | + """ |
114 | + with self.fs.record(): |
115 | + self.create_workspace(self._location) |
116 | + with self.fs.replay(): |
117 | + workspace = Workspace(self._location) |
118 | + self.assertIsInstance(workspace.environment, Environment) |
119 | + |
120 | + def test_environment_is_initially_empty(self): |
121 | + """ |
122 | + Workspace().environment is initially empty ({}) |
123 | + |
124 | + Despite .lava/environment.json having content, the environment is empty |
125 | + before we call .load_environment() |
126 | + """ |
127 | + with self.fs.record(): |
128 | + self.create_workspace( |
129 | + self._location, environment_text='{"I_am_not": "empty" }') |
130 | + with self.fs.replay(): |
131 | + workspace = Workspace(self._location) |
132 | + self.assertEqual(workspace.environment.value, {}) |
133 | + |
134 | + def test_load_environment_loads_json(self): |
135 | + """ |
136 | + Workspace().load_environment() loads stuff from disk |
137 | + """ |
138 | + with self.fs.record(): |
139 | + self.create_workspace( |
140 | + self._location, environment_text='{"name": "Unit test" }') |
141 | + with self.fs.replay(): |
142 | + workspace = Workspace(self._location) |
143 | + workspace.load_environment() |
144 | + self.assertEqual(workspace.environment.value, |
145 | + {"name": "Unit test"}) |
146 | + |
147 | + def test_save_environment_saves_json(self): |
148 | + """ |
149 | + Workspace().save_environment() saves stuff to disk |
150 | + |
151 | + (it's actually only doing that when modified but I'm not going to test |
152 | + for that here) |
153 | + """ |
154 | + with self.fs.record(): |
155 | + # NOTE: it's sad but MockedFileSystem cannot open a file twice (it |
156 | + # raises an exception when that is attempted), it's a tradeoff |
157 | + # between ease-of-implementation and features. Once I finish fakefs |
158 | + # we can just call self.create_workspace() here |
159 | + os.makedirs(os.path.join(self._location, ".lava")) |
160 | + workspace = Workspace(self._location) |
161 | + workspace.environment.name = self._name |
162 | + workspace.save_environment() |
163 | + with self.fs.replay(): |
164 | + # NOTE: for clarity, it's worth pointing out that replay() puts the |
165 | + # filesystem in read-only mode while record() does not allow one to |
166 | + # read what was written (this was a poor choice from |
167 | + # retrospective). Without those limits the test would have been |
168 | + # less complicated. |
169 | + workspace = Workspace(self._location) |
170 | + workspace.load_environment() |
171 | + self.assertEqual(workspace.environment.name, self._name) |
172 | + |
173 | + def test_create(self): |
174 | + """ |
175 | + Workspace.create() creates and returns a workspace |
176 | + """ |
177 | + with self.fs.record(): |
178 | + workspace = Workspace.create(self._location) |
179 | + with self.fs.replay(): |
180 | + # Create makes both the location |
181 | + self.assertTrue(os.path.exists(workspace.location)) |
182 | + # and the .lava directory inside |
183 | + self.assertTrue(os.path.exists(workspace._lava_dir)) |
184 | + # ... and even a stub environment.json file |
185 | + self.assertTrue(os.path.exists(workspace._environment_pathname)) |
186 | + |
187 | + def test_create_output_works(self): |
188 | + """ |
189 | + Workspace.create() creates something that can be used later |
190 | + """ |
191 | + with self.fs.record(): |
192 | + Workspace.create(self._location) |
193 | + with self.fs.replay(): |
194 | + workspace = Workspace(self._location) |
195 | + workspace.load_environment() |
196 | + # how I wish TestCase had provided assertNotRaises() |
197 | + self.assertEqual(workspace.environment.validate(), None) |
198 | + |
199 | + def test_find_without_workspace(self): |
200 | + """ |
201 | + Workspace.find() raises LookupError if there is no workspace anywhere |
202 | + _above_ |
203 | + """ |
204 | + with self.fs.replay(): |
205 | + with self.assertRaises(LookupError): |
206 | + Workspace.find() |
207 | + with self.fs.record(): |
208 | + os.makedirs('foo/.lava/') |
209 | + with self.fs.replay(): |
210 | + with self.assertRaises(LookupError): |
211 | + Workspace.find('foo') |
212 | + |
213 | + def test_find_with_workspace(self): |
214 | + """ |
215 | + Workspace.find() correctly finds workspaces in the current directory |
216 | + and above |
217 | + """ |
218 | + with self.fs.record(): |
219 | + os.mkdir('/home/user/.lava') |
220 | + os.mkdir('/home/user/.lava/something/') |
221 | + with self.fs.replay(): |
222 | + workspace = Workspace.find('/home/user/.lava/something/') |
223 | + self.assertEqual(workspace.location, '/home/user') |
224 | + self.assertEqual(workspace._lava_dir, '/home/user/.lava') |
225 | |
226 | === added file 'lava/core/workspace.py' |
227 | --- lava/core/workspace.py 1970-01-01 00:00:00 +0000 |
228 | +++ lava/core/workspace.py 2012-05-28 06:43:19 +0000 |
229 | @@ -0,0 +1,138 @@ |
230 | +# Copyright (C) 2011-2012 Linaro Limited |
231 | +# |
232 | +# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org> |
233 | +# |
234 | +# This file is part of lava-core |
235 | +# |
236 | +# lava-core is free software: you can redistribute it and/or modify |
237 | +# it under the terms of the GNU Lesser General Public License version 3 |
238 | +# as published by the Free Software Foundation |
239 | +# |
240 | +# lava-core is distributed in the hope that it will be useful, |
241 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
242 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
243 | +# GNU General Public License for more details. |
244 | +# |
245 | +# You should have received a copy of the GNU Lesser General Public License |
246 | +# along with lava-core. If not, see <http://www.gnu.org/licenses/>. |
247 | + |
248 | +from __future__ import absolute_import |
249 | + |
250 | +""" |
251 | +lava.core.workspace |
252 | +=================== |
253 | +""" |
254 | + |
255 | +import os |
256 | + |
257 | +from json_document.shortcuts import persistence |
258 | + |
259 | +from lava.core.environment import Environment |
260 | +from lava.core.logging import LoggingMixIn |
261 | + |
262 | + |
263 | +class Workspace(LoggingMixIn): |
264 | + """ |
265 | + Container for LAVA configuration and other runtime data. Also know as the |
266 | + .lava directory |
267 | + """ |
268 | + |
269 | + def __init__(self, location): |
270 | + """ |
271 | + Initialize a workspace with an existing on-disk location. location |
272 | + must point to a directory that has the '.lava' sub-directory. |
273 | + """ |
274 | + # self._location = os.path.abspath(location) |
275 | + self._location = location |
276 | + if not os.path.exists(self._lava_dir): |
277 | + raise EnvironmentError("Workspace must have a .lava directory") |
278 | + self._environment = Environment({}) |
279 | + self._env_persistence = persistence( |
280 | + self._environment, self._environment_pathname) |
281 | + |
282 | + @property |
283 | + def location(self): |
284 | + """ |
285 | + location of this workspace |
286 | + |
287 | + The location does not include the .lava directory |
288 | + The location is an absolute pathname, regardless of |
289 | + how it was passed to the constructor. |
290 | + """ |
291 | + return self._location |
292 | + |
293 | + @property |
294 | + def environment(self): |
295 | + """ |
296 | + :class:`lava.core.environment.Environment` object of this workspace |
297 | + |
298 | + .. note:: |
299 | + This object is initially empty (empty environment), to use an |
300 | + actual environment you must call load_environment() and deal with |
301 | + any errors. |
302 | + """ |
303 | + return self._environment |
304 | + |
305 | + def load_environment(self): |
306 | + """ |
307 | + Load and return the workspace environment |
308 | + """ |
309 | + # TODO: fs lock |
310 | + self._env_persistence.load() |
311 | + return self._environment |
312 | + |
313 | + def save_environment(self): |
314 | + """ |
315 | + Save environment back to the filesystem, if needed |
316 | + """ |
317 | + # TODO: fs lock |
318 | + self._env_persistence.save() |
319 | + |
320 | + @classmethod |
321 | + def create(cls, location='.'): |
322 | + """ |
323 | + Create a new workspace in the specified location. |
324 | + |
325 | + Parent workspace lookup is _not_ performed. This is a filesystem |
326 | + synchronization/race point, if the caller looses an OSError is raised. |
327 | + """ |
328 | + os.makedirs(os.path.join(location, '.lava')) |
329 | + self = cls(location) |
330 | + with open(self._environment_pathname, 'wt') as stream: |
331 | + stream.write("{ }") |
332 | + return self |
333 | + |
334 | + @classmethod |
335 | + def find(cls, start_dir='.'): |
336 | + """ |
337 | + Find a Workspace, starting the search at start_dir and working up the |
338 | + directory tree |
339 | + """ |
340 | + location = start_dir |
341 | + last_location = None |
342 | + # Keep on "going" as long as we're moving |
343 | + while last_location is None or (os.path.abspath(location) != |
344 | + os.path.abspath(last_location)): |
345 | + candidate = os.path.join(location, '.lava') |
346 | + candidate = os.path.normpath(candidate) |
347 | + # XXX: replace with os.path.isdir once mockfs supports that |
348 | + if os.path.exists(candidate): |
349 | + return cls(location) |
350 | + last_location = location |
351 | + location = os.path.join(location, "..") |
352 | + location = os.path.normpath(location) |
353 | + raise LookupError(".lava directory not found") |
354 | + |
355 | + @property |
356 | + def _lava_dir(self): |
357 | + """ |
358 | + pathname of the .lava directory |
359 | + """ |
360 | + return os.path.join(self._location, '.lava') |
361 | + |
362 | + @property |
363 | + def _environment_pathname(self): |
364 | + """ |
365 | + pathname of the 'environment.json' file |
366 | + """ |
367 | + return os.path.join(self._lava_dir, 'environment.json') |