Merge lp://qastaging/~cmiller/desktopcouch/add-running-context-as-parameter into lp://qastaging/desktopcouch
- add-running-context-as-parameter
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Eric Casteleijn |
Approved revision: | 106 |
Merged at revision: | not available |
Proposed branch: | lp://qastaging/~cmiller/desktopcouch/add-running-context-as-parameter |
Merge into: | lp://qastaging/desktopcouch |
Diff against target: |
1259 lines (+337/-228) 18 files modified
desktopcouch/__init__.py (+3/-3) desktopcouch/contacts/contactspicker.py (+3/-2) desktopcouch/contacts/testing/create.py (+4/-3) desktopcouch/contacts/tests/test_contactspicker.py (+4/-4) desktopcouch/local_files.py (+92/-60) desktopcouch/pair/couchdb_pairing/couchdb_io.py (+37/-26) desktopcouch/pair/tests/test_couchdb_io.py (+27/-19) desktopcouch/records/couchgrid.py (+6/-4) desktopcouch/records/doc/records.txt (+1/-1) desktopcouch/records/server.py (+9/-6) desktopcouch/records/tests/test_couchgrid.py (+19/-14) desktopcouch/records/tests/test_record.py (+5/-1) desktopcouch/records/tests/test_server.py (+3/-3) desktopcouch/start_local_couchdb.py (+41/-25) desktopcouch/tests/__init__.py (+42/-41) desktopcouch/tests/test_local_files.py (+9/-7) desktopcouch/tests/test_replication.py (+21/-0) desktopcouch/tests/test_start_local_couchdb.py (+11/-9) |
To merge this branch: | bzr merge lp://qastaging/~cmiller/desktopcouch/add-running-context-as-parameter |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn (community) | Approve | ||
Nicola Larosa (community) | Abstain | ||
Guillermo Gonzalez | Approve | ||
Review via email:
|
Commit message
Use an explicit test context for testing. Add an implicit context for normal usage.
Add test context to pairing test functions.
Add a context to the update_
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Nicola Larosa (teknico) wrote : | # |
I cannot find a way to test this, please document it somewhere.
First I run "setup.py build".
Then I run "setup.py check", pylint cannot find a config file, so it uses defaults, and spews out a *huge* amount of notices.
Last I run "setup.py test", it says "running test" but does not seem to do anything.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Nicola Larosa (teknico) wrote : | # |
Ok, it's "trial desktopcouch", there was some fog this morning. Tests pass, but I'm not looking at the code, I'll let Eric do that.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) wrote : | # |
After updating trunk and merging this in I get a fairly trivial conflict in desktopcouch/
Also in that file a default argument is used:
local_files.
but local_files is never imported. Is this a kind of magic that I forgot about, or are no errors triggered since the tests always pass an argument? In that case, we should add a test that does not pass a kw argument (but does not manipulate anything in the context, since that was what we set out to prevent)
Looks good, but when running;
PYTHONPATH=. trial desktopcouch
I get asked for access to the keyring 4 times, which I'm pretty sure is wrong, because the tests should not be using the tokens from there, unless I'm mistaken. (denying access makes the tests still pass)
Tests all pass.
- 106. By Chad Miller
-
Merge from trunk.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chad Miller (cmiller) wrote : | # |
> Also in __init__.py a default argument is used:
>
> local_files.
>
> but local_files is never imported. Is this a kind of magic that I forgot
> about, or are no errors triggered since the tests always pass an argument? In
> that case, we should add a test that does not pass a kw argument (but does not
> manipulate anything in the context, since that was what we set out to prevent)
It's the __init__ for this module. Other parts of this module are available. We don't need to import ourselves, e.g., and access desktopcouch.
Try it. In __init__.py, add "print local_files" and start a python shell and "import desktopcouch" .
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) wrote : | # |
> Try it. In __init__.py, add "print local_files" and start a python shell and
> "import desktopcouch" .
Doh! I usually avoid using these kinds of imports and it has softened my brain into not recognizing them when I see them. NM
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) wrote : | # |
All issues resolved!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) : | # |
Preview Diff
1 | === modified file 'desktopcouch/__init__.py' |
2 | --- desktopcouch/__init__.py 2009-10-29 21:45:19 +0000 |
3 | +++ desktopcouch/__init__.py 2009-11-18 18:51:14 +0000 |
4 | @@ -40,14 +40,14 @@ |
5 | logging.getLogger('').setLevel(logging.DEBUG) |
6 | log = logging.getLogger('') |
7 | |
8 | -def find_pid(start_if_not_running=True): |
9 | +def find_pid(start_if_not_running=True, ctx=local_files.DEFAULT_CONTEXT): |
10 | # Work out whether CouchDB is running by looking at its pid file |
11 | - pid = read_pidfile() |
12 | + pid = read_pidfile(ctx=ctx) |
13 | if not process_is_couchdb(pid) and start_if_not_running: |
14 | # start CouchDB by running the startup script |
15 | log.info("Desktop CouchDB is not running; starting it.") |
16 | from desktopcouch import start_local_couchdb |
17 | - pid = start_local_couchdb.start_couchdb() |
18 | + pid = start_local_couchdb.start_couchdb(ctx=ctx) |
19 | # now load the design documents, because it's started |
20 | start_local_couchdb.update_design_documents() |
21 | |
22 | |
23 | === modified file 'desktopcouch/contacts/contactspicker.py' |
24 | --- desktopcouch/contacts/contactspicker.py 2009-08-24 20:35:25 +0000 |
25 | +++ desktopcouch/contacts/contactspicker.py 2009-11-18 18:51:14 +0000 |
26 | @@ -19,6 +19,7 @@ |
27 | """A widget to allow users to pick contacts""" |
28 | |
29 | import gtk |
30 | +import desktopcouch |
31 | from desktopcouch.contacts.record import CONTACT_RECORD_TYPE |
32 | from desktopcouch.records.couchgrid import CouchGrid |
33 | |
34 | @@ -26,7 +27,7 @@ |
35 | class ContactsPicker(gtk.VBox): |
36 | """A contacts picker""" |
37 | |
38 | - def __init__(self, uri=None): |
39 | + def __init__(self, uri=None, ctx=desktopcouch.local_files.DEFAULT_CONTEXT): |
40 | """Create a new ContactsPicker widget.""" |
41 | |
42 | gtk.VBox.__init__(self) |
43 | @@ -43,7 +44,7 @@ |
44 | hbox.pack_start(self.search_button, False, False, 3) |
45 | |
46 | # Create CouchGrid to contain list of contacts |
47 | - self.contacts_list = CouchGrid('contacts', uri=uri) |
48 | + self.contacts_list = CouchGrid('contacts', uri=uri, ctx=ctx) |
49 | self.contacts_list.editable = False |
50 | self.contacts_list.keys = [ "first_name", "last_name" ] |
51 | self.contacts_list.record_type = CONTACT_RECORD_TYPE |
52 | |
53 | === modified file 'desktopcouch/contacts/testing/create.py' |
54 | --- desktopcouch/contacts/testing/create.py 2009-09-14 23:42:59 +0000 |
55 | +++ desktopcouch/contacts/testing/create.py 2009-11-18 18:51:14 +0000 |
56 | @@ -21,6 +21,7 @@ |
57 | |
58 | import random, string, uuid |
59 | |
60 | +import desktopcouch.tests as test_environment |
61 | from desktopcouch.records.server import OAuthCapableServer |
62 | import desktopcouch |
63 | |
64 | @@ -163,10 +164,10 @@ |
65 | app_annots=None, server_class=OAuthCapableServer): |
66 | """Make many contacts and create their records""" |
67 | if port is None: |
68 | - desktopcouch.find_pid() |
69 | - port = desktopcouch.find_port() |
70 | + pid = desktopcouch.find_pid(ctx=test_environment.test_context) |
71 | + port = desktopcouch.find_port(pid) |
72 | server_url = 'http://%s:%s/' % (host, port) |
73 | - server = server_class(server_url) |
74 | + server = server_class(server_url, ctx=test_environment.test_context) |
75 | db = server[db_name] if db_name in server else server.create(db_name) |
76 | record_ids = [] |
77 | for maincount in range(1, num_contacts + 1): |
78 | |
79 | === modified file 'desktopcouch/contacts/tests/test_contactspicker.py' |
80 | --- desktopcouch/contacts/tests/test_contactspicker.py 2009-09-01 22:29:01 +0000 |
81 | +++ desktopcouch/contacts/tests/test_contactspicker.py 2009-11-18 18:51:14 +0000 |
82 | @@ -21,7 +21,7 @@ |
83 | import testtools |
84 | import gtk |
85 | |
86 | -from desktopcouch.tests import xdg_cache |
87 | +import desktopcouch.tests as test_environment |
88 | |
89 | from desktopcouch.contacts.contactspicker import ContactsPicker |
90 | from desktopcouch.records.server import CouchDatabase |
91 | @@ -32,9 +32,9 @@ |
92 | def setUp(self): |
93 | """setup each test""" |
94 | # Connect to CouchDB server |
95 | - self.assert_(xdg_cache) |
96 | self.dbname = 'contacts' |
97 | - self.database = CouchDatabase(self.dbname, create=True) |
98 | + self.database = CouchDatabase(self.dbname, create=True, |
99 | + ctx=test_environment.test_context) |
100 | |
101 | def tearDown(self): |
102 | """tear down each test""" |
103 | @@ -49,6 +49,6 @@ |
104 | win.resize(300, 450) |
105 | |
106 | # Create the contacts picker widget |
107 | - picker = ContactsPicker() |
108 | + picker = ContactsPicker(ctx=test_environment.test_context) |
109 | win.get_content_area().pack_start(picker, True, True, 3) |
110 | self.assert_(picker.get_contacts_list()) |
111 | |
112 | === modified file 'desktopcouch/local_files.py' |
113 | --- desktopcouch/local_files.py 2009-09-30 19:54:16 +0000 |
114 | +++ desktopcouch/local_files.py 2009-11-18 18:51:14 +0000 |
115 | @@ -24,7 +24,7 @@ |
116 | """ |
117 | from __future__ import with_statement |
118 | import os |
119 | -import xdg.BaseDirectory |
120 | +import xdg.BaseDirectory as xdg_base_dirs |
121 | import subprocess |
122 | import logging |
123 | try: |
124 | @@ -32,23 +32,6 @@ |
125 | except ImportError: |
126 | import configparser |
127 | |
128 | -def mkpath(rootdir, path): |
129 | - "Remove .. from paths" |
130 | - return os.path.realpath(os.path.join(rootdir, path)) |
131 | - |
132 | -rootdir = os.path.join(xdg.BaseDirectory.xdg_cache_home, "desktop-couch") |
133 | -if not os.path.isdir(rootdir): |
134 | - os.mkdir(rootdir) |
135 | - |
136 | -config_dir = xdg.BaseDirectory.save_config_path("desktop-couch") |
137 | - |
138 | -FILE_INI = os.path.join(config_dir, "desktop-couchdb.ini") |
139 | -DIR_DB = xdg.BaseDirectory.save_data_path("desktop-couch") |
140 | - |
141 | -FILE_PID = mkpath(rootdir, "desktop-couchdb.pid") |
142 | -FILE_LOG = mkpath(rootdir, "desktop-couchdb.log") |
143 | -FILE_STDOUT = mkpath(rootdir, "desktop-couchdb.stdout") |
144 | -FILE_STDERR = mkpath(rootdir, "desktop-couchdb.stderr") |
145 | |
146 | COUCH_EXE = os.environ.get('COUCHDB') |
147 | if not COUCH_EXE: |
148 | @@ -58,30 +41,85 @@ |
149 | if not COUCH_EXE: |
150 | raise ImportError("Could not find couchdb") |
151 | |
152 | -def couch_chain_ini_files(): |
153 | - process = subprocess.Popen([COUCH_EXE, '-V'], shell=False, |
154 | - stdout=subprocess.PIPE) |
155 | - line = process.stdout.read().split('\n')[0] |
156 | - couchversion = line.split()[-1] |
157 | - |
158 | - # Explicitly add default ini file |
159 | - ini_files = ["/etc/couchdb/default.ini"] |
160 | - |
161 | - # find all ini files in the desktopcouch XDG_CONFIG_DIRS and add them to |
162 | - # the chain |
163 | - xdg_config_dirs = xdg.BaseDirectory.load_config_paths("desktop-couch") |
164 | - # Reverse the list because it's in most-important-first order |
165 | - for folder in reversed(list(xdg_config_dirs)): |
166 | - ini_files.extend([os.path.join(folder, x) |
167 | - for x in sorted(os.listdir(folder)) |
168 | - if x.endswith(".ini")]) |
169 | - |
170 | - if FILE_INI not in ini_files: |
171 | - ini_files.append(FILE_INI) |
172 | - |
173 | - chain = "-n -a %s " % " -a ".join(ini_files) |
174 | - |
175 | - return chain |
176 | + |
177 | +class Context(): |
178 | + """A mimic of xdg BaseDirectory, with overridable values that do not |
179 | + depend on environment variables.""" |
180 | + |
181 | + def __init__(self, run_dir, db_dir, config_dir): # (cache, data, config) |
182 | + |
183 | + self.couchdb_log_level = 'info' |
184 | + |
185 | + for d in (run_dir, db_dir, config_dir): |
186 | + if not os.path.isdir(d): |
187 | + os.makedirs(d, 0700) |
188 | + else: |
189 | + os.chmod(d, 0700) |
190 | + |
191 | + self.run_dir = os.path.realpath(run_dir) |
192 | + self.config_dir = os.path.realpath(config_dir) |
193 | + self.db_dir = os.path.realpath(db_dir) |
194 | + |
195 | + self.file_ini = os.path.join(config_dir, "desktop-couchdb.ini") |
196 | + self.file_pid = os.path.join(run_dir, "desktop-couchdb.pid") |
197 | + self.file_log = os.path.join(run_dir, "desktop-couchdb.log") |
198 | + self.file_stdout = os.path.join(run_dir, "desktop-couchdb.stdout") |
199 | + self.file_stderr = os.path.join(run_dir, "desktop-couchdb.stderr") |
200 | + |
201 | + # You will need to add -b or -k on the end of this |
202 | + self.couch_exec_command = [COUCH_EXE, self.couch_chain_ini_files(), |
203 | + '-p', self.file_pid, |
204 | + '-o', self.file_stdout, |
205 | + '-e', self.file_stderr] |
206 | + |
207 | + |
208 | + def ensure_files_not_readable(self): |
209 | + for descr in ("ini", "pid", "log", "stdout", "stderr",): |
210 | + f = getattr(self, "file_" + descr) |
211 | + if os.path.isfile(f): |
212 | + os.chmod(f, 0600) |
213 | + |
214 | + def load_config_paths(self): |
215 | + """This is xdg/BaseDirectory.py load_config_paths() with hard-code to |
216 | + use desktop-couch resource and read from this context.""" |
217 | + yield self.config_dir |
218 | + for config_dir in \ |
219 | + os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':'): |
220 | + path = os.path.join(config_dir, "desktop-couch") |
221 | + if os.path.exists(path): |
222 | + yield path |
223 | + |
224 | + def couch_chain_ini_files(self): |
225 | + process = subprocess.Popen([COUCH_EXE, '-V'], shell=False, |
226 | + stdout=subprocess.PIPE) |
227 | + line = process.stdout.read().split('\n')[0] |
228 | + couchversion = line.split()[-1] |
229 | + |
230 | + # Explicitly add default ini file |
231 | + ini_files = ["/etc/couchdb/default.ini"] |
232 | + |
233 | + # find all ini files in the desktopcouch XDG_CONFIG_DIRS and add them to |
234 | + # the chain |
235 | + config_dirs = self.load_config_paths() |
236 | + # Reverse the list because it's in most-important-first order |
237 | + for folder in reversed(list(config_dirs)): |
238 | + ini_files.extend([os.path.join(folder, x) |
239 | + for x in sorted(os.listdir(folder)) |
240 | + if x.endswith(".ini")]) |
241 | + |
242 | + if self.file_ini not in ini_files: |
243 | + ini_files.append(self.file_ini) |
244 | + |
245 | + chain = "-n -a %s " % " -a ".join(ini_files) |
246 | + |
247 | + return chain |
248 | + |
249 | + |
250 | +DEFAULT_CONTEXT = Context( |
251 | + os.path.join(xdg_base_dirs.xdg_cache_home, "desktop-couch"), |
252 | + xdg_base_dirs.save_data_path("desktop-couch"), |
253 | + xdg_base_dirs.save_config_path("desktop-couch")) |
254 | + |
255 | |
256 | class NoOAuthTokenException(Exception): |
257 | def __init__(self, file_name): |
258 | @@ -91,7 +129,7 @@ |
259 | return "OAuth details were not found in the ini file (%s)" % ( |
260 | self.file_name) |
261 | |
262 | -def get_oauth_tokens(config_file_name=FILE_INI): |
263 | +def get_oauth_tokens(config_file_name=None): |
264 | """Return the OAuth tokens from the desktop Couch ini file. |
265 | CouchDB OAuth is two-legged OAuth (not three-legged like most OAuth). |
266 | We have one "consumer", defined by a consumer_key and a secret, |
267 | @@ -101,6 +139,9 @@ |
268 | (More traditional 3-legged OAuth starts with a "request token" which is |
269 | then used to procure an "access token". We do not require this.) |
270 | """ |
271 | + if config_file_name is None: |
272 | + config_file_name = DEFAULT_CONTEXT.file_ini |
273 | + |
274 | c = configparser.ConfigParser() |
275 | # monkeypatch ConfigParser to stop it lower-casing option names |
276 | c.optionxform = lambda s: s |
277 | @@ -123,9 +164,11 @@ |
278 | raise NoOAuthTokenException(config_file_name) |
279 | return out |
280 | |
281 | - |
282 | -def get_bind_address(config_file_name=FILE_INI): |
283 | +def get_bind_address(config_file_name=None): |
284 | """Retreive a string if it exists, or None if it doesn't.""" |
285 | + if config_file_name is None: |
286 | + config_file_name = DEFAULT_CONTEXT.file_ini |
287 | + |
288 | c = configparser.ConfigParser() |
289 | try: |
290 | c.read(config_file_name) |
291 | @@ -134,7 +177,10 @@ |
292 | logging.warn("config file %r error. %s", config_file_name, e) |
293 | return None |
294 | |
295 | -def set_bind_address(address, config_file_name=FILE_INI): |
296 | +def set_bind_address(address, config_file_name=None): |
297 | + if config_file_name is None: |
298 | + config_file_name = DEFAULT_CONTEXT.file_ini |
299 | + |
300 | c = configparser.SafeConfigParser() |
301 | # monkeypatch ConfigParser to stop it lower-casing option names |
302 | c.optionxform = lambda s: s |
303 | @@ -145,17 +191,3 @@ |
304 | with open(config_file_name, 'wb') as configfile: |
305 | c.write(configfile) |
306 | |
307 | - |
308 | -# You will need to add -b or -k on the end of this |
309 | -COUCH_EXEC_COMMAND = [COUCH_EXE, couch_chain_ini_files(), '-p', FILE_PID, |
310 | - '-o', FILE_STDOUT, '-e', FILE_STDERR] |
311 | - |
312 | - |
313 | -# Set appropriate permissions on relevant files and folders |
314 | -for fn in [FILE_PID, FILE_STDOUT, FILE_STDERR, FILE_INI, FILE_LOG]: |
315 | - if os.path.exists(fn): |
316 | - os.chmod(fn, 0600) |
317 | -for dn in [rootdir, config_dir, DIR_DB]: |
318 | - if os.path.isdir(dn): |
319 | - os.chmod(dn, 0700) |
320 | - |
321 | |
322 | === modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py' |
323 | --- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-10-27 19:00:33 +0000 |
324 | +++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-11-18 18:51:14 +0000 |
325 | @@ -24,7 +24,7 @@ |
326 | import datetime |
327 | from itertools import cycle |
328 | |
329 | -from desktopcouch import find_pid, find_port as desktopcouch_find_port |
330 | +from desktopcouch import find_pid, find_port as desktopcouch_find_port, local_files |
331 | from desktopcouch.records import server |
332 | from desktopcouch.records.record import Record |
333 | |
334 | @@ -56,11 +56,13 @@ |
335 | port = str(port) |
336 | return "%s://%s%s:%s/%s" % (protocol, auth, hostname, port, path) |
337 | |
338 | -def _get_db(name, create=True, uri=None): |
339 | +def _get_db(name, create=True, uri=None, |
340 | + ctx=local_files.DEFAULT_CONTEXT): |
341 | """Get (and create?) a database.""" |
342 | - return server.CouchDatabase(name, create=create, uri=uri) |
343 | + return server.CouchDatabase(name, create=create, uri=uri, ctx=ctx) |
344 | |
345 | -def put_paired_host(oauth_data, uri=None, **kwargs): |
346 | +def put_paired_host(oauth_data, uri=None, |
347 | + ctx=local_files.DEFAULT_CONTEXT, **kwargs): |
348 | """Create a new paired-host record. OAuth information is required, and |
349 | after the uri, keyword parameters are added to the record.""" |
350 | pairing_id = str(uuid.uuid4()) |
351 | @@ -77,25 +79,28 @@ |
352 | "token_secret": str(oauth_data["token_secret"]), |
353 | } |
354 | data.update(kwargs) |
355 | - d = _get_db("management", uri=uri) |
356 | + d = _get_db("management", uri=uri, ctx=ctx) |
357 | r = Record(data) |
358 | record_id = d.put_record(r) |
359 | return record_id |
360 | |
361 | -def put_static_paired_service(oauth_data, service_name, uri=None): |
362 | +def put_static_paired_service(oauth_data, service_name, uri=None, |
363 | + ctx=local_files.DEFAULT_CONTEXT): |
364 | """Create a new service record.""" |
365 | return put_paired_host(oauth_data, uri=uri, service_name=service_name, |
366 | - pull_from_server=True, push_to_server=True) |
367 | + pull_from_server=True, push_to_server=True, ctx=ctx) |
368 | |
369 | -def put_dynamic_paired_host(hostname, remote_uuid, oauth_data, uri=None): |
370 | +def put_dynamic_paired_host(hostname, remote_uuid, oauth_data, uri=None, |
371 | + ctx=local_files.DEFAULT_CONTEXT): |
372 | """Create a new dynamic-host record.""" |
373 | return put_paired_host(oauth_data, uri=uri, pairing_identifier=remote_uuid, |
374 | - push_to_server=True, server=hostname) |
375 | + push_to_server=True, server=hostname, ctx=ctx) |
376 | |
377 | -def get_static_paired_hosts(uri=None): |
378 | +def get_static_paired_hosts(uri=None, |
379 | + ctx=local_files.DEFAULT_CONTEXT): |
380 | """Retreive a list of static hosts' information in the form of |
381 | (ID, service name, to_push, to_pull) .""" |
382 | - db = _get_db("management", uri=uri) |
383 | + db = _get_db("management", uri=uri, ctx=ctx) |
384 | results = db.get_records(create_view=True) |
385 | found = dict() |
386 | for row in results[PAIRED_SERVER_RECORD_TYPE]: |
387 | @@ -113,14 +118,16 @@ |
388 | logging.debug("static pairings are %s", unique_hosts) |
389 | return unique_hosts |
390 | |
391 | -def get_database_names_replicatable(uri, oauth_tokens=None, service=False): |
392 | +def get_database_names_replicatable(uri, oauth_tokens=None, service=False, |
393 | + ctx=local_files.DEFAULT_CONTEXT): |
394 | """Find a list of local databases, minus dbs that we do not want to |
395 | replicate (explicitly or implicitly).""" |
396 | if not uri: |
397 | - find_pid() |
398 | - port = desktopcouch_find_port() |
399 | + pid = find_pid(ctx=ctx) |
400 | + port = desktopcouch_find_port(pid=pid) |
401 | uri = "http://localhost:%s" % port |
402 | - couchdb_server = server.OAuthCapableServer(uri, oauth_tokens=oauth_tokens) |
403 | + couchdb_server = server.OAuthCapableServer(uri, oauth_tokens=oauth_tokens, |
404 | + ctx=ctx) |
405 | try: |
406 | all_dbs = set([db_name for db_name in couchdb_server]) |
407 | except socket.error, e: |
408 | @@ -132,18 +139,19 @@ |
409 | excluded.add("users") |
410 | if not service: |
411 | excluded_msets = _get_management_data(PAIRED_SERVER_RECORD_TYPE, |
412 | - "excluded_names", uri=uri) |
413 | + "excluded_names", uri=uri, ctx=ctx) |
414 | for excluded_mset in excluded_msets: |
415 | excluded.update(excluded_mset) |
416 | |
417 | return all_dbs - excluded |
418 | |
419 | -def get_my_host_unique_id(uri=None, create=True): |
420 | +def get_my_host_unique_id(uri=None, create=True, |
421 | + ctx=local_files.DEFAULT_CONTEXT): |
422 | """Returns a list of ids we call ourselves. We complain in the log if it's |
423 | more than one, but it's really no error. If there are zero (id est, we've |
424 | never paired with anyone), then returns None.""" |
425 | |
426 | - db = _get_db("management", uri=uri) |
427 | + db = _get_db("management", uri=uri, ctx=ctx) |
428 | ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity", uri=uri) |
429 | ids = list(set(ids)) # uniqify |
430 | if len(ids) > 1: |
431 | @@ -165,11 +173,12 @@ |
432 | logging.debug("set new self-identity value: %r", data["self_identity"]) |
433 | return [data["self_identity"]] |
434 | |
435 | -def get_all_known_pairings(uri=None): |
436 | +def get_all_known_pairings(uri=None, |
437 | + ctx=local_files.DEFAULT_CONTEXT): |
438 | """Info dicts about all pairings, even if marked "unpaired", keyed on |
439 | hostid with another dict as the value.""" |
440 | d = {} |
441 | - db = _get_db("management", uri=uri) |
442 | + db = _get_db("management", uri=uri, ctx=ctx) |
443 | for row in db.get_records(PAIRED_SERVER_RECORD_TYPE): |
444 | v = dict() |
445 | v["record_id"] = row.id |
446 | @@ -182,8 +191,9 @@ |
447 | d[hostid] = v |
448 | return d |
449 | |
450 | -def _get_management_data(record_type, key, uri=None): |
451 | - db = _get_db("management", uri=uri) |
452 | +def _get_management_data(record_type, key, uri=None, |
453 | + ctx=local_files.DEFAULT_CONTEXT): |
454 | + db = _get_db("management", uri=uri, ctx=ctx) |
455 | results = db.get_records(create_view=True) |
456 | values = list() |
457 | for record in results[record_type]: |
458 | @@ -267,9 +277,9 @@ |
459 | logging.exception("can't replicate %r %r <== %r", source_database, |
460 | url, obsfuscate(record)) |
461 | |
462 | -def get_pairings(uri=None): |
463 | +def get_pairings(uri=None, ctx=local_files.DEFAULT_CONTEXT): |
464 | """Get a list of paired servers.""" |
465 | - db = _get_db("management", create=True, uri=None) |
466 | + db = _get_db("management", create=True, uri=None, ctx=ctx) |
467 | |
468 | design_doc = "paired_servers" |
469 | if not db.view_exists("paired_servers", design_doc): |
470 | @@ -292,10 +302,11 @@ |
471 | |
472 | return db.execute_view("paired_servers") |
473 | |
474 | -def remove_pairing(record_id, is_reconciled, uri=None): |
475 | +def remove_pairing(record_id, is_reconciled, uri=None, |
476 | + ctx=local_files.DEFAULT_CONTEXT): |
477 | """Remove a pairing record (or mark it as dead so it can be cleaned up |
478 | properly later).""" |
479 | - db = _get_db("management", create=True, uri=None) |
480 | + db = _get_db("management", create=True, uri=None, ctx=ctx) |
481 | if is_reconciled: |
482 | db.delete_record(record_id) |
483 | else: |
484 | |
485 | === modified file 'desktopcouch/pair/tests/test_couchdb_io.py' |
486 | --- desktopcouch/pair/tests/test_couchdb_io.py 2009-10-27 19:00:33 +0000 |
487 | +++ desktopcouch/pair/tests/test_couchdb_io.py 2009-11-18 18:51:14 +0000 |
488 | @@ -18,23 +18,26 @@ |
489 | import pygtk |
490 | pygtk.require('2.0') |
491 | |
492 | -import desktopcouch.tests as dctests |
493 | +import desktopcouch.tests as test_environment |
494 | |
495 | from desktopcouch.pair.couchdb_pairing import couchdb_io |
496 | from desktopcouch.records.server import CouchDatabase |
497 | from desktopcouch.records.record import Record |
498 | -import unittest |
499 | +from twisted.trial import unittest |
500 | import uuid |
501 | import os |
502 | import httplib2 |
503 | +import socket |
504 | URI = None # use autodiscovery that desktopcouch.tests permits. |
505 | |
506 | class TestCouchdbIo(unittest.TestCase): |
507 | |
508 | def setUp(self): |
509 | """setup each test""" |
510 | - self.mgt_database = CouchDatabase('management', create=True, uri=URI) |
511 | - self.foo_database = CouchDatabase('foo', create=True, uri=URI) |
512 | + self.mgt_database = CouchDatabase('management', create=True, uri=URI, |
513 | + ctx=test_environment.test_context) |
514 | + self.foo_database = CouchDatabase('foo', create=True, uri=URI, |
515 | + ctx=test_environment.test_context) |
516 | #create some records to pull out and test |
517 | self.foo_database.put_record(Record({ |
518 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
519 | @@ -71,8 +74,8 @@ |
520 | "token": str("opqrst"), |
521 | "token_secret": str("uvwxyz"), |
522 | } |
523 | - couchdb_io.put_static_paired_service(oauth_data, service_name, uri=URI) |
524 | - pairings = list(couchdb_io.get_pairings()) |
525 | + couchdb_io.put_static_paired_service(oauth_data, service_name, uri=URI, ctx=test_environment.test_context) |
526 | + pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
527 | |
528 | def test_put_dynamic_paired_host(self): |
529 | hostname = "host%d" % (os.getpid(),) |
530 | @@ -85,43 +88,48 @@ |
531 | } |
532 | |
533 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
534 | - uri=URI) |
535 | - couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
536 | - uri=URI) |
537 | - couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
538 | - uri=URI) |
539 | + uri=URI, ctx=test_environment.test_context) |
540 | + couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
541 | + uri=URI, ctx=test_environment.test_context) |
542 | + couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
543 | + uri=URI, ctx=test_environment.test_context) |
544 | |
545 | - pairings = list(couchdb_io.get_pairings()) |
546 | + pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
547 | self.assertEqual(3, len(pairings)) |
548 | self.assertEqual(pairings[0].value["oauth"], oauth_data) |
549 | self.assertEqual(pairings[0].value["server"], hostname) |
550 | self.assertEqual(pairings[0].value["pairing_identifier"], remote_uuid) |
551 | |
552 | for i, row in enumerate(pairings): |
553 | - couchdb_io.remove_pairing(row.id, i == 1) |
554 | + couchdb_io.remove_pairing(row.id, i == 1, ctx=test_environment.test_context) |
555 | |
556 | - pairings = list(couchdb_io.get_pairings()) |
557 | + pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
558 | self.assertEqual(0, len(pairings)) |
559 | |
560 | |
561 | def test_get_database_names_replicatable_bad_server(self): |
562 | - # If this resolves, FIRE YOUR DNS PROVIDER. |
563 | + hostname = "test.desktopcouch.example.com" |
564 | + try: |
565 | + socket.gethostbyname(hostname) |
566 | + raise unittest.SkipTest("nxdomain hijacked") |
567 | + except socket.gaierror: |
568 | + pass |
569 | |
570 | try: |
571 | names = couchdb_io.get_database_names_replicatable( |
572 | - uri='http://test.desktopcouch.example.com:9/') |
573 | + uri='http://' + hostname + ':9/') |
574 | self.assertEqual(set(), names) |
575 | except httplib2.ServerNotFoundError: |
576 | pass |
577 | |
578 | def test_get_database_names_replicatable(self): |
579 | - names = couchdb_io.get_database_names_replicatable(uri=URI) |
580 | + names = couchdb_io.get_database_names_replicatable(uri=URI, ctx=test_environment.test_context) |
581 | self.assertFalse('management' in names) |
582 | self.assertTrue('foo' in names) |
583 | |
584 | def test_get_my_host_unique_id(self): |
585 | - got = couchdb_io.get_my_host_unique_id(uri=URI) |
586 | - again = couchdb_io.get_my_host_unique_id(uri=URI) |
587 | + got = couchdb_io.get_my_host_unique_id(uri=URI, ctx=test_environment.test_context) |
588 | + again = couchdb_io.get_my_host_unique_id(uri=URI, ctx=test_environment.test_context) |
589 | self.assertEquals(len(got), 1) |
590 | self.assertEquals(got, again) |
591 | |
592 | |
593 | === modified file 'desktopcouch/records/couchgrid.py' |
594 | --- desktopcouch/records/couchgrid.py 2009-10-10 23:56:45 +0000 |
595 | +++ desktopcouch/records/couchgrid.py 2009-11-18 18:51:14 +0000 |
596 | @@ -22,11 +22,12 @@ |
597 | import gobject |
598 | from server import CouchDatabase |
599 | from record import Record |
600 | +import desktopcouch |
601 | |
602 | class CouchGrid(gtk.TreeView): |
603 | |
604 | - def __init__( |
605 | - self, database_name, record_type=None, keys=None, uri=None): |
606 | + def __init__(self, database_name, record_type=None, keys=None, uri=None, |
607 | + ctx=desktopcouch.local_files.DEFAULT_CONTEXT): |
608 | """Create a new Couchwidget |
609 | arguments: |
610 | database_name - specify the name of the database in the desktop |
611 | @@ -63,6 +64,7 @@ |
612 | self.__keys = keys |
613 | self.__editable = False |
614 | self.uri = uri |
615 | + self.ctx = ctx |
616 | |
617 | #set the datatabase |
618 | self.database = database_name |
619 | @@ -122,9 +124,9 @@ |
620 | @database.setter |
621 | def database(self, db_name): |
622 | if self.uri: |
623 | - self.__db = CouchDatabase(db_name, create=True, uri=self.uri) |
624 | + self.__db = CouchDatabase(db_name, create=True, uri=self.uri, ctx=self.ctx) |
625 | else: |
626 | - self.__db = CouchDatabase(db_name, create=True) |
627 | + self.__db = CouchDatabase(db_name, create=True, ctx=self.ctx) |
628 | if self.record_type != None: |
629 | self.__populate_treeview()(self.record_type) |
630 | |
631 | |
632 | === modified file 'desktopcouch/records/doc/records.txt' |
633 | --- desktopcouch/records/doc/records.txt 2009-10-05 13:39:38 +0000 |
634 | +++ desktopcouch/records/doc/records.txt 2009-11-18 18:51:14 +0000 |
635 | @@ -6,7 +6,7 @@ |
636 | Create a database object. Your database needs to exist. If it doesn't, you |
637 | can create it by passing create=True. |
638 | |
639 | ->>> db = CouchDatabase('testing', create=True) |
640 | + >> db = CouchDatabase('testing', create=True) |
641 | |
642 | Create a Record object. Records have a record type, which should be a |
643 | URL. The URL should point to a human-readable document which |
644 | |
645 | === modified file 'desktopcouch/records/server.py' |
646 | --- desktopcouch/records/server.py 2009-10-14 17:28:43 +0000 |
647 | +++ desktopcouch/records/server.py 2009-11-18 18:51:14 +0000 |
648 | @@ -28,13 +28,15 @@ |
649 | import urlparse |
650 | |
651 | class OAuthCapableServer(Server): |
652 | - def __init__(self, uri, oauth_tokens=None): |
653 | + def __init__(self, uri, oauth_tokens=None, ctx=None): |
654 | """Subclass of couchdb.client.Server which creates a custom |
655 | httplib2.Http subclass which understands OAuth""" |
656 | http = server_base.OAuthCapableHttp(scheme=urlparse.urlparse(uri)[0]) |
657 | http.force_exception_to_status_code = False |
658 | + if ctx is None: |
659 | + ctx = desktopcouch.local_files.DEFAULT_CONTEXT |
660 | if oauth_tokens is None: |
661 | - oauth_tokens = desktopcouch.local_files.get_oauth_tokens() |
662 | + oauth_tokens = desktopcouch.local_files.get_oauth_tokens(ctx.file_ini) |
663 | (consumer_key, consumer_secret, token, token_secret) = ( |
664 | oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"], |
665 | oauth_tokens["token"], oauth_tokens["token_secret"]) |
666 | @@ -45,11 +47,12 @@ |
667 | """An small records specific abstraction over a couch db database.""" |
668 | |
669 | def __init__(self, database, uri=None, record_factory=None, create=False, |
670 | - server_class=OAuthCapableServer, oauth_tokens=None): |
671 | + server_class=OAuthCapableServer, oauth_tokens=None, |
672 | + ctx=desktopcouch.local_files.DEFAULT_CONTEXT): |
673 | if not uri: |
674 | - desktopcouch.find_pid() |
675 | - port = desktopcouch.find_port() |
676 | + pid = desktopcouch.find_pid(ctx=ctx) |
677 | + port = desktopcouch.find_port(pid) |
678 | uri = "http://localhost:%s" % port |
679 | super(CouchDatabase, self).__init__( |
680 | database, uri, record_factory=record_factory, create=create, |
681 | - server_class=server_class, oauth_tokens=oauth_tokens) |
682 | + server_class=server_class, oauth_tokens=oauth_tokens, ctx=ctx) |
683 | |
684 | === modified file 'desktopcouch/records/tests/test_couchgrid.py' |
685 | --- desktopcouch/records/tests/test_couchgrid.py 2009-10-10 23:52:01 +0000 |
686 | +++ desktopcouch/records/tests/test_couchgrid.py 2009-11-18 18:51:14 +0000 |
687 | @@ -20,7 +20,7 @@ |
688 | |
689 | from testtools import TestCase |
690 | |
691 | -from desktopcouch.tests import xdg_cache |
692 | +import desktopcouch.tests as test_environment |
693 | |
694 | from desktopcouch.records.record import Record |
695 | from desktopcouch.records.server import CouchDatabase |
696 | @@ -31,9 +31,9 @@ |
697 | """Test the CouchGrid functionality""" |
698 | |
699 | def setUp(self): |
700 | - self.assert_(xdg_cache) |
701 | self.dbname = self._testMethodName |
702 | - self.db = CouchDatabase(self.dbname, create=True) |
703 | + self.db = CouchDatabase(self.dbname, create=True, |
704 | + ctx=test_environment.test_context) |
705 | self.record_type = "test_record_type" |
706 | |
707 | def tearDown(self): |
708 | @@ -46,7 +46,7 @@ |
709 | database name. |
710 | """ |
711 | try: |
712 | - cw = CouchGrid(None) |
713 | + cw = CouchGrid(None, ctx=test_environment.test_context) |
714 | except TypeError, inst: |
715 | self.assertEqual( |
716 | inst.args[0],"database_name is required and must be a string") |
717 | @@ -55,7 +55,7 @@ |
718 | """Test a simple creating a CouchGrid """ |
719 | |
720 | #create a test widget with test database values |
721 | - cw = CouchGrid(self.dbname) |
722 | + cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
723 | |
724 | #allow editing |
725 | cw.editable = True |
726 | @@ -87,7 +87,7 @@ |
727 | |
728 | try: |
729 | #create a test widget with test database values |
730 | - cw = CouchGrid(self.dbname) |
731 | + cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
732 | |
733 | #set the record_type for the TreeView |
734 | #it will not populate without this value being set |
735 | @@ -113,7 +113,8 @@ |
736 | |
737 | def test_all_from_database(self): |
738 | #create some records |
739 | - db = CouchDatabase(self.dbname, create=True) |
740 | + db = CouchDatabase(self.dbname, create=True, |
741 | + ctx=test_environment.test_context) |
742 | db.put_record(Record({ |
743 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
744 | "record_type": self.record_type})) |
745 | @@ -122,7 +123,7 @@ |
746 | "record_type": self.record_type})) |
747 | |
748 | #build the CouchGrid |
749 | - cw = CouchGrid(self.dbname) |
750 | + cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
751 | cw.record_type = self.record_type |
752 | #make sure there are three columns and two rows |
753 | self.assertEqual(cw.get_model().get_n_columns(),4) |
754 | @@ -130,7 +131,8 @@ |
755 | |
756 | def test_selected_id_property(self): |
757 | #create some records |
758 | - db = CouchDatabase(self.dbname, create=True) |
759 | + db = CouchDatabase(self.dbname, create=True, |
760 | + ctx=test_environment.test_context) |
761 | id1 = db.put_record(Record({ |
762 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
763 | "record_type": self.record_type})) |
764 | @@ -139,7 +141,7 @@ |
765 | "record_type": self.record_type})) |
766 | |
767 | #build the CouchGrid |
768 | - cw = CouchGrid(self.dbname) |
769 | + cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
770 | cw.record_type = self.record_type |
771 | |
772 | #make sure the record ids are selected properly |
773 | @@ -158,7 +160,7 @@ |
774 | "key1_1": "val2_1", "key1_2": "val2_2", "key1_3": "val2_3", |
775 | "record_type": self.record_type})) |
776 | #build the CouchGrid |
777 | - cw = CouchGrid(self.dbname) |
778 | + cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
779 | cw.keys = ["key1_1"] |
780 | cw.record_type = self.record_type |
781 | #make sure there are three columns and two rows |
782 | @@ -176,7 +178,8 @@ |
783 | "record_type": self.record_type})) |
784 | |
785 | #create a test widget with test database values |
786 | - cw = CouchGrid(self.dbname, record_type=self.record_type) |
787 | + cw = CouchGrid(self.dbname, record_type=self.record_type, |
788 | + ctx=test_environment.test_context) |
789 | |
790 | #make sure there are three columns and two rows |
791 | self.assertEqual(cw.get_model().get_n_columns(),4) |
792 | @@ -188,7 +191,8 @@ |
793 | #create a test widget with test database values |
794 | cw = CouchGrid( |
795 | self.dbname, record_type=self.record_type, |
796 | - keys=["Key1", "Key2", "Key3", "Key4"]) |
797 | + keys=["Key1", "Key2", "Key3", "Key4"], |
798 | + ctx=test_environment.test_context) |
799 | |
800 | #create a row with all four columns set |
801 | cw.append_row(["val1", "val2", "val3", "val4"]) |
802 | @@ -214,7 +218,8 @@ |
803 | "record_type": self.record_type})) |
804 | |
805 | #create a test widget with test database values |
806 | - cw = CouchGrid(self.dbname, record_type=self.record_type) |
807 | + cw = CouchGrid(self.dbname, record_type=self.record_type, |
808 | + ctx=test_environment.test_context) |
809 | |
810 | #allow editing |
811 | cw.append_row(["boo", "ray"]) |
812 | |
813 | === modified file 'desktopcouch/records/tests/test_record.py' |
814 | --- desktopcouch/records/tests/test_record.py 2009-11-17 22:03:11 +0000 |
815 | +++ desktopcouch/records/tests/test_record.py 2009-11-18 18:51:14 +0000 |
816 | @@ -23,8 +23,10 @@ |
817 | |
818 | # pylint does not like relative imports from containing packages |
819 | # pylint: disable-msg=F0401 |
820 | +from desktopcouch.records.server import CouchDatabase |
821 | from desktopcouch.records.record import (Record, RecordDict, MergeableList, |
822 | record_factory, IllegalKeyException, validate, NoRecordTypeSpecified) |
823 | +import desktopcouch.tests as test_environment |
824 | |
825 | |
826 | class TestRecords(TestCase): |
827 | @@ -202,7 +204,9 @@ |
828 | self.record.record_type) |
829 | |
830 | def test_run_doctests(self): |
831 | - results = doctest.testfile('../doc/records.txt') |
832 | + ctx = test_environment.test_context |
833 | + globs = { "db": CouchDatabase('testing', create=True, ctx=ctx) } |
834 | + results = doctest.testfile('../doc/records.txt', globs=globs) |
835 | self.assertEqual(0, results.failed) |
836 | |
837 | |
838 | |
839 | === modified file 'desktopcouch/records/tests/test_server.py' |
840 | --- desktopcouch/records/tests/test_server.py 2009-11-18 18:29:04 +0000 |
841 | +++ desktopcouch/records/tests/test_server.py 2009-11-18 18:51:14 +0000 |
842 | @@ -19,7 +19,7 @@ |
843 | """testing database/contact.py module""" |
844 | import testtools |
845 | |
846 | -from desktopcouch.tests import xdg_cache |
847 | +import desktopcouch.tests as test_environment |
848 | from desktopcouch.records.server import CouchDatabase |
849 | from desktopcouch.records.server_base import row_is_deleted, NoSuchDatabase |
850 | from desktopcouch.records.record import Record |
851 | @@ -41,9 +41,9 @@ |
852 | def setUp(self): |
853 | """setup each test""" |
854 | # Connect to CouchDB server |
855 | - self.assert_(xdg_cache) |
856 | self.dbname = self._testMethodName |
857 | - self.database = CouchDatabase(self.dbname, create=True) |
858 | + self.database = CouchDatabase(self.dbname, create=True, |
859 | + ctx=test_environment.test_context) |
860 | #create some records to pull out and test |
861 | self.database.put_record(Record({ |
862 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
863 | |
864 | === modified file 'desktopcouch/start_local_couchdb.py' |
865 | --- desktopcouch/start_local_couchdb.py 2009-11-15 21:11:21 +0000 |
866 | +++ desktopcouch/start_local_couchdb.py 2009-11-18 18:51:14 +0000 |
867 | @@ -41,6 +41,8 @@ |
868 | import errno |
869 | import time, gnomekeyring |
870 | from desktopcouch.records.server import CouchDatabase |
871 | +import logging |
872 | +import itertools |
873 | |
874 | ACCEPTABLE_USERNAME_PASSWORD_CHARS = string.lowercase + string.uppercase |
875 | |
876 | @@ -58,9 +60,12 @@ |
877 | fd.write("\n") |
878 | fd.close() |
879 | |
880 | -def create_ini_file(port="0"): |
881 | +def create_ini_file(port="0", ctx=local_files.DEFAULT_CONTEXT): |
882 | """Write CouchDB ini file if not already present""" |
883 | - if os.path.exists(local_files.FILE_INI): |
884 | + |
885 | + print "ini file is at", ctx.file_ini |
886 | + |
887 | + if os.path.exists(ctx.file_ini): |
888 | # load the username and password from the keyring |
889 | try: |
890 | data = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, |
891 | @@ -85,19 +90,18 @@ |
892 | consumer_secret = make_random_string(10) |
893 | token = make_random_string(10) |
894 | token_secret = make_random_string(10) |
895 | - db_dir = local_files.DIR_DB |
896 | local = { |
897 | 'couchdb': { |
898 | - 'database_dir': db_dir, |
899 | - 'view_index_dir': db_dir, |
900 | + 'database_dir': ctx.db_dir, |
901 | + 'view_index_dir': ctx.db_dir, |
902 | }, |
903 | 'httpd': { |
904 | 'bind_address': '127.0.0.1', |
905 | 'port': port, |
906 | }, |
907 | 'log': { |
908 | - 'file': local_files.FILE_LOG, |
909 | - 'level': 'info', |
910 | + 'file': ctx.file_log, |
911 | + 'level': ctx.couchdb_log_level, |
912 | }, |
913 | 'admins': { |
914 | admin_account_username: admin_account_basic_auth_password |
915 | @@ -116,7 +120,9 @@ |
916 | } |
917 | } |
918 | |
919 | - dump_ini(local, local_files.FILE_INI) |
920 | + dump_ini(local, ctx.file_ini) |
921 | + ctx.ensure_files_not_readable() |
922 | + |
923 | # save admin account details in keyring |
924 | item_id = gnomekeyring.item_create_sync( |
925 | None, |
926 | @@ -169,25 +175,31 @@ |
927 | except KeyError: |
928 | raise NotImplementedError("os %r is not yet supported" % (os_name,)) |
929 | |
930 | -def read_pidfile(): |
931 | +def read_pidfile(ctx=local_files.DEFAULT_CONTEXT): |
932 | try: |
933 | - pid_file = local_files.FILE_PID |
934 | + pid_file = ctx.file_pid |
935 | + if not os.path.exists(pid_file): |
936 | + return None |
937 | with open(pid_file) as fp: |
938 | try: |
939 | contents = fp.read() |
940 | + if contents == "\n": |
941 | + return None # not yet written to pid file |
942 | return int(contents) |
943 | except ValueError: |
944 | + logging.warn("Pid file does not contain int: %r", contents) |
945 | return None |
946 | - except IOError: |
947 | + except IOError, e: |
948 | + logging.warn("Reading pid file caused error. %s", e) |
949 | return None |
950 | |
951 | -def run_couchdb(): |
952 | +def run_couchdb(ctx=local_files.DEFAULT_CONTEXT): |
953 | """Actually start the CouchDB process. Return its PID.""" |
954 | - pid = read_pidfile() |
955 | + pid = read_pidfile(ctx) |
956 | if pid is not None and not process_is_couchdb(pid): |
957 | print "Removing stale, deceptive pid file." |
958 | - os.remove(local_files.FILE_PID) |
959 | - local_exec = local_files.COUCH_EXEC_COMMAND + ['-b'] |
960 | + os.remove(ctx.file_pid) |
961 | + local_exec = ctx.couch_exec_command + ['-b'] |
962 | try: |
963 | # subprocess is buggy. Chad patched, but that takes time to propagate. |
964 | proc = subprocess.Popen(local_exec) |
965 | @@ -209,14 +221,15 @@ |
966 | |
967 | # give the process a chance to start |
968 | for timeout in xrange(1000): |
969 | - pid = read_pidfile() |
970 | + pid = read_pidfile(ctx=ctx) |
971 | time.sleep(0.3) |
972 | if pid is not None and process_is_couchdb(pid): |
973 | break |
974 | print "...waiting for couchdb to start..." |
975 | + ctx.ensure_files_not_readable() |
976 | return pid |
977 | |
978 | -def update_design_documents(): |
979 | +def update_design_documents(ctx=local_files.DEFAULT_CONTEXT): |
980 | """Check system design documents and update any that need updating |
981 | |
982 | A database should be created if |
983 | @@ -225,17 +238,20 @@ |
984 | $XDG_DATA_DIRs/desktop-couch/databases/dbname/_design/designdocname/views/viewname/map.js |
985 | reduce.js may also exist in the same folder. |
986 | """ |
987 | - for base in xdg.BaseDirectory.xdg_data_dirs: |
988 | + ctx_data_dir = os.path.split(ctx.db_dir)[0] |
989 | + for base in itertools.chain([ctx_data_dir], xdg.BaseDirectory.xdg_data_dirs): |
990 | + # FIXME: base may have magic chars. assert not glob.has_magic(base) ? |
991 | db_spec = os.path.join( |
992 | base, "desktop-couch", "databases", "*", "database.cfg") |
993 | for database_path in glob.glob(db_spec): |
994 | database_root = os.path.split(database_path)[0] |
995 | database_name = os.path.split(database_root)[1] |
996 | # Just the presence of database.cfg is enough to create the database |
997 | - db = CouchDatabase(database_name, create=True) |
998 | + db = CouchDatabase(database_name, create=True, ctx=ctx) |
999 | # look for design documents |
1000 | dd_spec = os.path.join( |
1001 | database_root, "_design", "*", "views", "*", "map.js") |
1002 | + # FIXME: dd_path may have magic chars. |
1003 | for dd_path in glob.glob(dd_spec): |
1004 | view_root = os.path.split(dd_path)[0] |
1005 | view_name = os.path.split(view_root)[1] |
1006 | @@ -258,9 +274,9 @@ |
1007 | # than inefficiently just overwriting it regardless |
1008 | db.add_view(view_name, mapjs, reducejs, dd_name) |
1009 | |
1010 | -def write_bookmark_file(username, password, pid): |
1011 | +def write_bookmark_file(username, password, pid, ctx=local_files.DEFAULT_CONTEXT): |
1012 | """Write out an HTML document that the user can bookmark to find their DB""" |
1013 | - bookmark_file = os.path.join(local_files.DIR_DB, "couchdb.html") |
1014 | + bookmark_file = os.path.join(ctx.db_dir, "couchdb.html") |
1015 | |
1016 | if os.path.exists( |
1017 | os.path.join(os.path.split(__file__)[0], "../data/couchdb.tmpl")): |
1018 | @@ -292,16 +308,16 @@ |
1019 | finally: |
1020 | fp.close() |
1021 | |
1022 | -def start_couchdb(): |
1023 | +def start_couchdb(ctx=local_files.DEFAULT_CONTEXT): |
1024 | """Execute each step to start a desktop CouchDB.""" |
1025 | - username, password = create_ini_file() |
1026 | - pid = run_couchdb() |
1027 | + username, password = create_ini_file(ctx=ctx) |
1028 | + pid = run_couchdb(ctx=ctx) |
1029 | # Note that we do not call update_design_documents here. This is because |
1030 | # Couch won't actually have started yet, so when update_design_documents |
1031 | # calls the Records API, that will call back into get_port and we end up |
1032 | # starting Couch again. Instead, get_port calls update_design_documents |
1033 | # *after* Couch startup has occurred. |
1034 | - write_bookmark_file(username, password, pid) |
1035 | + write_bookmark_file(username, password, pid, ctx=ctx) |
1036 | return pid |
1037 | |
1038 | |
1039 | |
1040 | === modified file 'desktopcouch/tests/__init__.py' |
1041 | --- desktopcouch/tests/__init__.py 2009-09-14 15:56:42 +0000 |
1042 | +++ desktopcouch/tests/__init__.py 2009-11-18 18:51:14 +0000 |
1043 | @@ -1,47 +1,48 @@ |
1044 | """Tests for Desktop CouchDB""" |
1045 | |
1046 | import os, tempfile, atexit, shutil |
1047 | +from desktopcouch.start_local_couchdb import start_couchdb, read_pidfile |
1048 | from desktopcouch.stop_local_couchdb import stop_couchdb |
1049 | - |
1050 | -def stop_test_couch(): |
1051 | - from desktopcouch.start_local_couchdb import read_pidfile |
1052 | - pid = read_pidfile() |
1053 | - stop_couchdb(pid=pid) |
1054 | - shutil.rmtree(basedir) |
1055 | - |
1056 | -atexit.register(stop_test_couch) |
1057 | - |
1058 | -basedir = tempfile.mkdtemp() |
1059 | -if not os.path.exists(basedir): |
1060 | - os.mkdir(basedir) |
1061 | - |
1062 | -xdg_cache = os.path.join(basedir, 'xdg_cache') |
1063 | -if not os.path.exists(xdg_cache): |
1064 | - os.mkdir(xdg_cache) |
1065 | - |
1066 | -xdg_data = os.path.join(basedir, 'xdg_data') |
1067 | -if not os.path.exists(xdg_data): |
1068 | - os.mkdir(xdg_data) |
1069 | - |
1070 | -xdg_config = os.path.join(basedir, 'xdg_config') |
1071 | -if not os.path.exists(xdg_config): |
1072 | - os.mkdir(xdg_config) |
1073 | - |
1074 | -# Add etc folder to config |
1075 | -SOURCE_TREE_ETC_FOLDER = os.path.realpath( |
1076 | - os.path.join(os.path.split(__file__)[0], "..", "..", "config") |
1077 | -) |
1078 | -if os.path.isdir(SOURCE_TREE_ETC_FOLDER): |
1079 | - os.environ["XDG_CONFIG_DIRS"] = SOURCE_TREE_ETC_FOLDER |
1080 | - |
1081 | -os.environ['XDG_CACHE_HOME'] = xdg_cache |
1082 | -os.environ['XDG_DATA_HOME'] = xdg_data |
1083 | -os.environ['XDG_CONFIG_HOME'] = xdg_config |
1084 | +from desktopcouch import local_files |
1085 | + |
1086 | +import gobject |
1087 | +gobject.set_application_name("desktopcouch testing") |
1088 | + |
1089 | + |
1090 | +def create_new_test_environment(): |
1091 | + |
1092 | + basedir = tempfile.mkdtemp() |
1093 | + if not os.path.exists(basedir): |
1094 | + os.mkdir(basedir) |
1095 | + |
1096 | + cache = os.path.join(basedir, 'cache') |
1097 | + data = os.path.join(basedir, 'data') |
1098 | + config = os.path.join(basedir, 'config') |
1099 | + new_context = local_files.Context(cache, data, config) |
1100 | + new_context.couchdb_log_level = 'debug' |
1101 | + |
1102 | + # Add etc folder to config |
1103 | + SOURCE_TREE_ETC_FOLDER = os.path.realpath( |
1104 | + os.path.join(os.path.split(__file__)[0], "..", "..", "config") |
1105 | + ) |
1106 | + if os.path.isdir(SOURCE_TREE_ETC_FOLDER): |
1107 | + os.environ["XDG_CONFIG_DIRS"] = SOURCE_TREE_ETC_FOLDER |
1108 | + |
1109 | + def stop_test_couch(temp_dir, ctx): |
1110 | + pid = read_pidfile(ctx) |
1111 | + stop_couchdb(pid=pid) |
1112 | + shutil.rmtree(temp_dir) |
1113 | + |
1114 | + start_couchdb(ctx=new_context) |
1115 | + atexit.register(stop_test_couch, basedir, new_context) |
1116 | + |
1117 | + return new_context |
1118 | + |
1119 | + |
1120 | +# TODO: Remove these after you're sure nothing we care about uses these env. |
1121 | +os.environ['XDG_CACHE_HOME'] = "/cachehome" |
1122 | +os.environ['XDG_DATA_HOME'] = "/datahome" |
1123 | +os.environ['XDG_CONFIG_HOME'] = "/confighome" |
1124 | os.environ['XDG_DATA_DIRS'] = '' |
1125 | |
1126 | -# Force reload packages, so that the correct dekstopcouch will be |
1127 | -# started. |
1128 | -import xdg.BaseDirectory |
1129 | -reload(xdg.BaseDirectory) |
1130 | -from desktopcouch import local_files |
1131 | -reload(local_files) |
1132 | +test_context = create_new_test_environment() |
1133 | |
1134 | === modified file 'desktopcouch/tests/test_local_files.py' |
1135 | --- desktopcouch/tests/test_local_files.py 2009-11-17 22:03:11 +0000 |
1136 | +++ desktopcouch/tests/test_local_files.py 2009-11-18 18:51:14 +0000 |
1137 | @@ -1,7 +1,7 @@ |
1138 | """testing desktopcouch/local_files.py module""" |
1139 | |
1140 | import testtools |
1141 | -import desktopcouch.tests |
1142 | +import desktopcouch.tests as test_environment |
1143 | import desktopcouch |
1144 | import os |
1145 | |
1146 | @@ -11,19 +11,21 @@ |
1147 | "Does local_files list all the files that it needs to?" |
1148 | import desktopcouch.local_files |
1149 | for required in [ |
1150 | - "FILE_LOG", "FILE_INI", "FILE_PID", "FILE_STDOUT", |
1151 | - "FILE_STDERR", "DIR_DB", "COUCH_EXE", "COUCH_EXEC_COMMAND"]: |
1152 | - self.assertTrue(required in dir(desktopcouch.local_files)) |
1153 | + "file_log", "file_ini", "file_pid", "file_stdout", |
1154 | + "file_stderr", "db_dir"]: |
1155 | + #"couch_exe", "couch_exec_command" |
1156 | + self.assertTrue(required in dir(test_environment.test_context)) |
1157 | |
1158 | def test_xdg_overwrite_works(self): |
1159 | # this should really check that it's in os.environ["TMP"] |
1160 | - self.assertTrue(desktopcouch.local_files.FILE_INI.startswith("/tmp")) |
1161 | + self.assertTrue(test_environment.test_context.file_ini.startswith("/tmp")) |
1162 | |
1163 | def test_couch_chain_ini_files(self): |
1164 | "Is compulsory-auth.ini picked up by the ini file finder?" |
1165 | import desktopcouch.local_files |
1166 | - ok = [x for x in desktopcouch.local_files.couch_chain_ini_files().split() |
1167 | - if x.endswith("compulsory-auth.ini")] |
1168 | + ok = [x for x |
1169 | + in test_environment.test_context.couch_chain_ini_files().split() |
1170 | + if x.endswith("compulsory-auth.ini")] |
1171 | self.assertTrue(len(ok) > 0) |
1172 | |
1173 | def test_bind_address(self): |
1174 | |
1175 | === added file 'desktopcouch/tests/test_replication.py' |
1176 | --- desktopcouch/tests/test_replication.py 1970-01-01 00:00:00 +0000 |
1177 | +++ desktopcouch/tests/test_replication.py 2009-11-18 18:51:14 +0000 |
1178 | @@ -0,0 +1,21 @@ |
1179 | +"""testing desktopcouch/start_local_couchdb.py module""" |
1180 | + |
1181 | +import testtools |
1182 | +import os, sys |
1183 | +import desktopcouch.tests as test_environment |
1184 | +import desktopcouch |
1185 | +sys.path.append( |
1186 | + os.path.join(os.path.split(desktopcouch.__file__)[0], "..", "contrib")) |
1187 | + |
1188 | +class TestReplication(testtools.TestCase): |
1189 | + """Testing that the database/designdoc filesystem loader works""" |
1190 | + |
1191 | + def setUp(self): |
1192 | + self.db_apple = desktopcouch.records.server.CouchDatabase("apple", |
1193 | + create=True, ctx=test_environment.test_context) |
1194 | + banana = test_environment.create_new_test_environment() |
1195 | + self.db_banana = desktopcouch.records.server.CouchDatabase("banana", |
1196 | + create=True, ctx=banana) |
1197 | + |
1198 | + def test_creation(self): |
1199 | + pass |
1200 | |
1201 | === modified file 'desktopcouch/tests/test_start_local_couchdb.py' |
1202 | --- desktopcouch/tests/test_start_local_couchdb.py 2009-09-14 15:56:42 +0000 |
1203 | +++ desktopcouch/tests/test_start_local_couchdb.py 2009-11-18 18:51:14 +0000 |
1204 | @@ -2,7 +2,7 @@ |
1205 | |
1206 | import testtools |
1207 | import os, sys |
1208 | -from desktopcouch.tests import xdg_data |
1209 | +import desktopcouch.tests as test_environment |
1210 | import desktopcouch |
1211 | sys.path.append( |
1212 | os.path.join(os.path.split(desktopcouch.__file__)[0], "..", "contrib")) |
1213 | @@ -71,6 +71,7 @@ |
1214 | |
1215 | def setUp(self): |
1216 | # create temp folder with databases and design documents in |
1217 | + xdg_data = os.path.split(test_environment.test_context.db_dir)[0] |
1218 | try: |
1219 | os.mkdir(os.path.join(xdg_data, "desktop-couch")) |
1220 | except OSError: |
1221 | @@ -90,21 +91,22 @@ |
1222 | couchdb = mocker.replace("desktopcouch.records.server.CouchDatabase") |
1223 | |
1224 | # databases that should be created |
1225 | - couchdb("cfg", create=True) |
1226 | - couchdb("cfg_and_empty_design", create=True) |
1227 | - couchdb("cfg_and_design_no_views", create=True) |
1228 | - couchdb("cfg_and_design_one_view_no_map", create=True) |
1229 | - couchdb("cfg_and_design_one_view_map_no_reduce", create=True) |
1230 | + couchdb("cfg", create=True, ctx=test_environment.test_context) |
1231 | + couchdb("cfg_and_empty_design", create=True, ctx=test_environment.test_context) |
1232 | + couchdb("cfg_and_design_no_views", create=True, ctx=test_environment.test_context) |
1233 | + couchdb("cfg_and_design_one_view_no_map", create=True, ctx=test_environment.test_context) |
1234 | + couchdb("cfg_and_design_one_view_map_no_reduce", create=True, ctx=test_environment.test_context) |
1235 | + |
1236 | dbmock1 = mocker.mock() |
1237 | mocker.result(dbmock1) |
1238 | dbmock1.add_view("view1", "cfg_and_design_one_view_map_no_reduce:map", |
1239 | None, "doc1") |
1240 | - couchdb("cfg_and_design_one_view_map_reduce", create=True) |
1241 | + couchdb("cfg_and_design_one_view_map_reduce", create=True, ctx=test_environment.test_context) |
1242 | dbmock2 = mocker.mock() |
1243 | mocker.result(dbmock2) |
1244 | dbmock2.add_view("view1", "cfg_and_design_one_view_map_reduce:map", |
1245 | "cfg_and_design_one_view_map_reduce:reduce", "doc1") |
1246 | - couchdb("cfg_and_design_two_views_map_reduce", create=True) |
1247 | + couchdb("cfg_and_design_two_views_map_reduce", create=True, ctx=test_environment.test_context) |
1248 | dbmock3 = mocker.mock() |
1249 | mocker.result(dbmock3) |
1250 | dbmock3.add_view("view1", "cfg_and_design_two_views_map_reduce:map1", |
1251 | @@ -116,7 +118,7 @@ |
1252 | # all the right things |
1253 | mocker.replay() |
1254 | from desktopcouch.start_local_couchdb import update_design_documents |
1255 | - update_design_documents() |
1256 | + update_design_documents(ctx=test_environment.test_context) |
1257 | |
1258 | mocker.restore() |
1259 | mocker.verify() |
looks good, all tests [OK]