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