11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
1 |
# Copyright 2010 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
11204.7.6
by Jeroen Vermeulen
Bit more docstring. |
4 |
"""Fake, in-process implementation of the Librarian API.
|
5 |
||
6 |
This works in-process only. It does not support exchange of files
|
|
7 |
between processes, or URL access. Nor will it completely support all
|
|
11204.7.7
by Jeroen Vermeulen
Typo. |
8 |
details of the Librarian interface. But where it's enough, this
|
9 |
provides a simple and fast alternative to the full Librarian in unit
|
|
10 |
tests.
|
|
11204.7.6
by Jeroen Vermeulen
Bit more docstring. |
11 |
"""
|
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
12 |
|
13 |
__metaclass__ = type |
|
14 |
__all__ = [ |
|
15 |
'FakeLibrarian', |
|
16 |
]
|
|
17 |
||
18 |
import hashlib |
|
19 |
from StringIO import StringIO |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
20 |
from urlparse import urljoin |
21 |
||
11562.2.2
by Robert Collins
Simplify the fake librarian. |
22 |
from fixtures import Fixture |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
23 |
import transaction |
24 |
from transaction.interfaces import ISynchronizer |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
25 |
import zope.component |
26 |
from zope.interface import implements |
|
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
27 |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
28 |
from canonical.config import config |
29 |
from canonical.librarian.client import get_libraryfilealias_download_path |
|
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
30 |
from canonical.librarian.interfaces import ( |
31 |
ILibrarianClient, |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
32 |
LIBRARIAN_SERVER_DEFAULT_TIMEOUT, |
33 |
)
|
|
14578.2.1
by William Grant
Move librarian stuff from canonical.launchpad to lp.services.librarian. canonical.librarian remains untouched. |
34 |
from lp.services.librarian.interfaces import ILibraryFileAliasSet |
35 |
from lp.services.librarian.model import ( |
|
36 |
LibraryFileAlias, |
|
37 |
LibraryFileContent, |
|
38 |
)
|
|
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
39 |
|
40 |
||
41 |
class InstrumentedLibraryFileAlias(LibraryFileAlias): |
|
42 |
"""A `ILibraryFileAlias` implementation that fakes library access."""
|
|
43 |
||
44 |
file_committed = False |
|
45 |
||
46 |
def checkCommitted(self): |
|
47 |
"""Raise an error if this file has not been committed yet."""
|
|
48 |
if not self.file_committed: |
|
11204.7.3
by Jeroen Vermeulen
Run same tests against both real and fake librarian. |
49 |
raise LookupError( |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
50 |
"Attempting to retrieve file '%s' from the fake " |
51 |
"librarian, but the file has not yet been committed to "
|
|
52 |
"storage." % self.filename) |
|
53 |
||
54 |
def open(self, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT): |
|
55 |
self.checkCommitted() |
|
56 |
self._datafile = StringIO(self.content_string) |
|
57 |
||
58 |
def read(self, chunksize=None, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT): |
|
59 |
return self._datafile.read(chunksize) |
|
60 |
||
61 |
||
11562.2.2
by Robert Collins
Simplify the fake librarian. |
62 |
class FakeLibrarian(Fixture): |
63 |
"""A test double Librarian which works in-process.
|
|
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
64 |
|
65 |
This takes the role of both the librarian client and the LibraryFileAlias
|
|
66 |
utility.
|
|
67 |
"""
|
|
11204.7.3
by Jeroen Vermeulen
Run same tests against both real and fake librarian. |
68 |
provided_utilities = [ILibrarianClient, ILibraryFileAliasSet] |
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
69 |
implements(ISynchronizer, *provided_utilities) |
70 |
||
11562.2.2
by Robert Collins
Simplify the fake librarian. |
71 |
def setUp(self): |
72 |
"""Fixture API: install as the librarian."""
|
|
73 |
Fixture.setUp(self) |
|
74 |
self.aliases = {} |
|
75 |
self.download_url = config.librarian.download_url |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
76 |
transaction.manager.registerSynch(self) |
11562.2.2
by Robert Collins
Simplify the fake librarian. |
77 |
self.addCleanup(transaction.manager.unregisterSynch, self) |
11204.7.4
by Jeroen Vermeulen
Restore original utilities properly, thanks Gary\! |
78 |
|
79 |
site_manager = zope.component.getGlobalSiteManager() |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
80 |
for utility in self.provided_utilities: |
11204.7.4
by Jeroen Vermeulen
Restore original utilities properly, thanks Gary\! |
81 |
original = zope.component.getUtility(utility) |
82 |
if site_manager.unregisterUtility(original, utility): |
|
11562.2.2
by Robert Collins
Simplify the fake librarian. |
83 |
# We really disabled a utility, restore it later.
|
84 |
self.addCleanup( |
|
85 |
zope.component.provideUtility, original, utility) |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
86 |
zope.component.provideUtility(self, utility) |
11562.2.2
by Robert Collins
Simplify the fake librarian. |
87 |
self.addCleanup(site_manager.unregisterUtility, self, utility) |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
88 |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
89 |
def addFile(self, name, size, file, contentType, expires=None): |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
90 |
"""See `IFileUploadClient`."""
|
11496.1.1
by Jeroen Vermeulen
FakeLibrarian.create should return LibraryFileAlias. |
91 |
return self._storeFile( |
92 |
name, size, file, contentType, expires=expires).id |
|
93 |
||
94 |
def _storeFile(self, name, size, file, contentType, expires=None): |
|
95 |
"""Like `addFile`, but returns the `LibraryFileAlias`."""
|
|
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
96 |
content = file.read() |
97 |
real_size = len(content) |
|
98 |
if real_size != size: |
|
11204.7.3
by Jeroen Vermeulen
Run same tests against both real and fake librarian. |
99 |
raise AssertionError( |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
100 |
"Uploading '%s' to the fake librarian with incorrect " |
101 |
"size %d; actual size is %d." % (name, size, real_size)) |
|
102 |
||
103 |
file_ref = self._makeLibraryFileContent(content) |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
104 |
alias = self._makeAlias(file_ref.id, name, content, contentType) |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
105 |
self.aliases[alias.id] = alias |
11496.1.1
by Jeroen Vermeulen
FakeLibrarian.create should return LibraryFileAlias. |
106 |
return alias |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
107 |
|
108 |
def remoteAddFile(self, name, size, file, contentType, expires=None): |
|
109 |
"""See `IFileUploadClient`."""
|
|
110 |
return NotImplementedError() |
|
111 |
||
7675.809.18
by Robert Collins
Extend the private librarian concept to have unique domains to get unique security contexts on browsers. |
112 |
def getURLForAlias(self, aliasID, secure=False): |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
113 |
"""See `IFileDownloadClient`."""
|
11515.1.1
by Jeroen Vermeulen
Make Librarian URLs use LibraryFileAlias objects, not ids. |
114 |
return self.getURLForAliasObject(self.aliases.get(int(aliasID))) |
115 |
||
116 |
def getURLForAliasObject(self, alias): |
|
117 |
"""See `IFileDownloadClient`."""
|
|
118 |
if alias.deleted: |
|
119 |
return None |
|
120 |
path = get_libraryfilealias_download_path(alias.id, alias.filename) |
|
11204.7.2
by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities. |
121 |
return urljoin(self.download_url, path) |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
122 |
|
123 |
def getFileByAlias(self, aliasID, |
|
124 |
timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT): |
|
125 |
"""See `IFileDownloadClient`."""
|
|
126 |
alias = self[aliasID] |
|
127 |
alias.checkCommitted() |
|
128 |
return StringIO(alias.content_string) |
|
129 |
||
11393.2.1
by Jeroen Vermeulen
Let FakeLibrarian-using tests avoid transaction.commit(). |
130 |
def pretendCommit(self): |
131 |
"""Pretend that there's been a commit.
|
|
132 |
||
133 |
When you add a file to the librarian (real or fake), it is not
|
|
134 |
fully available until the transaction that added the file has
|
|
135 |
been committed. Call this method to make the FakeLibrarian act
|
|
136 |
as if there's been a commit, without actually committing a
|
|
137 |
database transaction.
|
|
138 |
"""
|
|
139 |
# Note that all files have been committed to storage.
|
|
140 |
for alias in self.aliases.itervalues(): |
|
141 |
alias.file_committed = True |
|
142 |
||
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
143 |
def _makeAlias(self, file_id, name, content, content_type): |
144 |
"""Create a `LibraryFileAlias`."""
|
|
145 |
alias = InstrumentedLibraryFileAlias( |
|
146 |
contentID=file_id, filename=name, mimetype=content_type) |
|
147 |
alias.content_string = content |
|
148 |
return alias |
|
149 |
||
150 |
def _makeLibraryFileContent(self, content): |
|
151 |
"""Create a `LibraryFileContent`."""
|
|
152 |
size = len(content) |
|
153 |
sha1 = hashlib.sha1(content).hexdigest() |
|
154 |
md5 = hashlib.md5(content).hexdigest() |
|
155 |
||
156 |
content_object = LibraryFileContent(filesize=size, sha1=sha1, md5=md5) |
|
157 |
return content_object |
|
158 |
||
159 |
def create(self, name, size, file, contentType, expires=None, |
|
160 |
debugID=None, restricted=False): |
|
161 |
"See `ILibraryFileAliasSet`."""
|
|
11496.1.1
by Jeroen Vermeulen
FakeLibrarian.create should return LibraryFileAlias. |
162 |
return self._storeFile(name, size, file, contentType, expires=expires) |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
163 |
|
164 |
def __getitem__(self, key): |
|
165 |
"See `ILibraryFileAliasSet`."""
|
|
166 |
alias = self.aliases.get(key) |
|
167 |
if alias is None: |
|
11204.7.3
by Jeroen Vermeulen
Run same tests against both real and fake librarian. |
168 |
raise LookupError( |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
169 |
"Attempting to retrieve file alias %d from the fake " |
170 |
"librarian, who has never heard of it." % key) |
|
171 |
return alias |
|
172 |
||
173 |
def findBySHA1(self, sha1): |
|
174 |
"See `ILibraryFileAliasSet`."""
|
|
175 |
for alias in self.aliases.itervalues(): |
|
176 |
if alias.content.sha1 == sha1: |
|
177 |
return alias |
|
178 |
||
179 |
return None |
|
180 |
||
181 |
def beforeCompletion(self, txn): |
|
182 |
"""See `ISynchronizer`."""
|
|
183 |
||
184 |
def afterCompletion(self, txn): |
|
185 |
"""See `ISynchronizer`."""
|
|
11393.2.1
by Jeroen Vermeulen
Let FakeLibrarian-using tests avoid transaction.commit(). |
186 |
self.pretendCommit() |
11204.7.1
by Jeroen Vermeulen
A fake Librarian for use in tests. |
187 |
|
188 |
def newTransaction(self, txn): |
|
189 |
"""See `ISynchronizer`."""
|