Merge lp:~zyga/lava-core/workspace into lp:lava-core

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
Reviewer Review Type Date Requested Status
Linaro Validation Team Pending
Review via email: mp+107585@code.launchpad.net

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')

Subscribers

People subscribed via source and target branches