Merge lp:~dobey/ubuntuone-dev-tools/updates-2992 into lp:ubuntuone-dev-tools/stable-3-0
- updates-2992
- Merge into stable-3-0
Proposed by
dobey
Status: | Merged |
---|---|
Approved by: | dobey |
Approved revision: | 53 |
Merged at revision: | 53 |
Proposed branch: | lp:~dobey/ubuntuone-dev-tools/updates-2992 |
Merge into: | lp:ubuntuone-dev-tools/stable-3-0 |
Diff against target: |
1307 lines (+1145/-30) 14 files modified
bin/u1lint.bat (+17/-0) bin/u1trial.bat (+17/-0) data/squid.conf.in (+120/-0) run-tests (+1/-1) setup.py (+12/-4) ubuntuone/devtools/services/__init__.py (+53/-0) ubuntuone/devtools/services/dbus.py (+8/-23) ubuntuone/devtools/services/squid.py (+245/-0) ubuntuone/devtools/services/tests/test_dbus.py (+1/-1) ubuntuone/devtools/services/tests/test_squid.py (+334/-0) ubuntuone/devtools/testcases/dbus.py (+1/-1) ubuntuone/devtools/testcases/squid.py (+58/-0) ubuntuone/devtools/testcases/tests/__init__.py (+16/-0) ubuntuone/devtools/testcases/tests/test_squid_testcase.py (+262/-0) |
To merge this branch: | bzr merge lp:~dobey/ubuntuone-dev-tools/updates-2992 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
dobey (community) | Abstain | ||
Brian Curtin (community) | Approve | ||
Diego Sarmentero (community) | Approve | ||
Review via email: mp+88946@code.launchpad.net |
Commit message
[Manuel de la Peña]
Provide batch scripts for u1trial and u1lint on Windows.
Provide a new SquidTestCase for running tests with a private squid instance.
[Rodney Dawes]
Add missing squid.conf.in to setup.py
Description of the change
To post a comment you must log in.
Revision history for this message
Brian Curtin (brian.curtin) wrote : | # |
Minor issue: In your uses of subprocess.Popen, you open stdout and stderr but don't close them.
Otherwise: +1
review:
Approve
Revision history for this message
dobey (dobey) : | # |
review:
Abstain
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'bin/u1lint.bat' |
2 | --- bin/u1lint.bat 1970-01-01 00:00:00 +0000 |
3 | +++ bin/u1lint.bat 2012-01-17 21:43:26 +0000 |
4 | @@ -0,0 +1,17 @@ |
5 | +:: |
6 | +:: Copyright 2012 Canonical Ltd. |
7 | +:: |
8 | +:: This program is free software: you can redistribute it and/or modify it |
9 | +:: under the terms of the GNU General Public License version 3, as published |
10 | +:: by the Free Software Foundation. |
11 | +:: |
12 | +:: This program is distributed in the hope that it will be useful, but |
13 | +:: WITHOUT ANY WARRANTY; without even the implied warranties of |
14 | +:: MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
15 | +:: PURPOSE. See the GNU General Public License for more details. |
16 | +:: |
17 | +:: You should have received a copy of the GNU General Public License along |
18 | +:: with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | + |
20 | +:: Use python to execute the script having the same name as this batch |
21 | +python "%~dpn0" %* |
22 | |
23 | === added file 'bin/u1trial.bat' |
24 | --- bin/u1trial.bat 1970-01-01 00:00:00 +0000 |
25 | +++ bin/u1trial.bat 2012-01-17 21:43:26 +0000 |
26 | @@ -0,0 +1,17 @@ |
27 | +:: |
28 | +:: Copyright 2012 Canonical Ltd. |
29 | +:: |
30 | +:: This program is free software: you can redistribute it and/or modify it |
31 | +:: under the terms of the GNU General Public License version 3, as published |
32 | +:: by the Free Software Foundation. |
33 | +:: |
34 | +:: This program is distributed in the hope that it will be useful, but |
35 | +:: WITHOUT ANY WARRANTY; without even the implied warranties of |
36 | +:: MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
37 | +:: PURPOSE. See the GNU General Public License for more details. |
38 | +:: |
39 | +:: You should have received a copy of the GNU General Public License along |
40 | +:: with this program. If not, see <http://www.gnu.org/licenses/>. |
41 | + |
42 | +:: Use python to execute the script having the same name as this batch |
43 | +python "%~dpn0" %* |
44 | |
45 | === added file 'data/squid.conf.in' |
46 | --- data/squid.conf.in 1970-01-01 00:00:00 +0000 |
47 | +++ data/squid.conf.in 2012-01-17 21:43:26 +0000 |
48 | @@ -0,0 +1,120 @@ |
49 | +auth_param basic casesensitive on |
50 | +# Use a default auth using ncsa and the passed generated file. |
51 | +auth_param basic program ${auth_process} ${auth_file} |
52 | +#Recommended minimum configuration: |
53 | +acl manager proto cache_object |
54 | +acl localhost src 127.0.0.1/32 |
55 | +acl to_localhost dst 127.0.0.0/32 |
56 | +# |
57 | +# Example rule allowing access from your local networks. |
58 | +# Adapt to list your (internal) IP networks from where browsing |
59 | +# should be allowed |
60 | +acl all src all |
61 | +acl localnet src 10.0.0.0/8 # RFC1918 possible internal network |
62 | +acl localnet src 172.16.0.0/12 # RFC1918 possible internal network |
63 | +acl localnet src 192.168.0.0/16 # RFC1918 possible internal network |
64 | +# |
65 | +acl SSL_ports port 443 # https |
66 | +acl SSL_ports port 563 # snews |
67 | +acl SSL_ports port 873 # rsync |
68 | +acl Safe_ports port 80 # http |
69 | +acl Safe_ports port 21 # ftp |
70 | +acl Safe_ports port 443 # https |
71 | +acl Safe_ports port 70 # gopher |
72 | +acl Safe_ports port 210 # wais |
73 | +acl Safe_ports port 1025-65535 # unregistered ports |
74 | +acl Safe_ports port 280 # http-mgmt |
75 | +acl Safe_ports port 488 # gss-http |
76 | +acl Safe_ports port 591 # filemaker |
77 | +acl Safe_ports port 777 # multiling http |
78 | +acl Safe_ports port 631 # cups |
79 | +acl Safe_ports port 873 # rsync |
80 | +acl Safe_ports port 901 # SWAT |
81 | +acl purge method PURGE |
82 | +acl CONNECT method CONNECT |
83 | + |
84 | +# make an acl for users that have auth |
85 | +acl password proxy_auth REQUIRED myportname ${auth_port_number} |
86 | +acl auth_port_connected myportname ${auth_port_number} |
87 | +acl nonauth_port_connected myportname ${noauth_port_number} |
88 | + |
89 | +# Settings used for the tests: |
90 | +# Allow users connected to the nonauth port |
91 | +# Allow users authenticated AND connected to the auth port |
92 | +http_access allow nonauth_port_connected |
93 | +http_access allow password |
94 | + |
95 | +#Recommended minimum configuration: |
96 | +# |
97 | +# Only allow cachemgr access from localhost |
98 | +http_access allow manager localhost |
99 | +http_access deny manager |
100 | +# Only allow purge requests from localhost |
101 | +http_access allow purge localhost |
102 | +http_access deny purge |
103 | +# Deny requests to unknown ports |
104 | +http_access deny !Safe_ports |
105 | +# Deny CONNECT to other than SSL ports |
106 | +http_access deny CONNECT !SSL_ports |
107 | +# Example rule allowing access from your local networks. |
108 | +# Adapt localnet in the ACL section to list your (internal) IP networks |
109 | +# from where browsing should be allowed |
110 | +#http_access allow localnet |
111 | +http_access allow localhost |
112 | + |
113 | +# And finally deny all other access to this proxy |
114 | +http_access deny all |
115 | + |
116 | +icp_access allow localnet |
117 | +icp_access deny all |
118 | + |
119 | +# Squid normally listens to port 3128 but we are going to listento two |
120 | +# different ports, one for auth one for nonauth. |
121 | +http_port ${noauth_port_number} |
122 | +http_port ${auth_port_number} |
123 | + |
124 | +#We recommend you to use at least the following line. |
125 | +hierarchy_stoplist cgi-bin ? |
126 | + |
127 | +# Default cache settings. |
128 | +cache_dir ufs ${spool_temp} 1000 16 256 |
129 | + |
130 | +# access log settings |
131 | +access_log ${squid_temp}/access.log squid |
132 | + |
133 | +# cache log settings |
134 | +cache_log ${squid_temp}/cache.log |
135 | + |
136 | +# cache store log settings |
137 | +cache_store_log ${squid_temp}/store.log |
138 | + |
139 | +# mime table conf |
140 | +# mime_table /usr/share/squid/mime.conf |
141 | + |
142 | +#Default pid file name |
143 | +pid_filename ${squid_temp}/squid.pid |
144 | + |
145 | +# debug options (Full debugging) |
146 | +debug_options ALL,1 |
147 | + |
148 | +#Default netdb_filename |
149 | + |
150 | +#Suggested default: |
151 | +refresh_pattern ^ftp: 1440 20% 10080 |
152 | +refresh_pattern ^gopher: 1440 0% 1440 |
153 | +refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 |
154 | +refresh_pattern (Release|Packages(.gz)*)$ 0 20% 2880 |
155 | +# example line deb packages |
156 | +refresh_pattern . 0 20% 4320 |
157 | + |
158 | +# Don't upgrade ShoutCast responses to HTTP |
159 | +acl shoutcast rep_header X-HTTP09-First-Line ^ICY.[0-9] |
160 | + |
161 | +# Apache mod_gzip and mod_deflate known to be broken so don't trust |
162 | +# Apache to signal ETag correctly on such responses |
163 | +acl apache rep_header Server ^Apache |
164 | + |
165 | +hosts_file /etc/hosts |
166 | + |
167 | +# Leave coredumps in the first cache dir |
168 | +coredump_dir ${spool_temp}/squid |
169 | |
170 | === modified file 'run-tests' |
171 | --- run-tests 2011-11-21 19:58:40 +0000 |
172 | +++ run-tests 2012-01-17 21:43:26 +0000 |
173 | @@ -20,6 +20,6 @@ |
174 | bin/u1trial --reactor=twisted ubuntuone |
175 | echo "Running style checks..." |
176 | bin/u1lint |
177 | -pep8 --repeat . bin/* |
178 | +pep8 --repeat . bin/* --exclude=*.bat |
179 | rm -rf _trial_temp |
180 | rm -rf .coverage |
181 | |
182 | === modified file 'setup.py' |
183 | --- setup.py 2012-01-03 20:29:17 +0000 |
184 | +++ setup.py 2012-01-17 21:43:26 +0000 |
185 | @@ -44,6 +44,15 @@ |
186 | if retcode != 0: |
187 | sys.exit(retcode) |
188 | |
189 | +# pylint: disable=C0103 |
190 | +scripts = ['bin/u1lint', |
191 | + 'bin/u1trial'] |
192 | +# pylint: enable=C0103 |
193 | + |
194 | +if sys.platform == 'win32': |
195 | + # lets add the .bat so that windows users are happy |
196 | + scripts.extend(['bin/u1lint.bat', 'bin/u1trial.bat']) |
197 | + |
198 | setup(name=PACKAGE, |
199 | version=VERSION, |
200 | description='Ubuntu One development tools and utilities', |
201 | @@ -55,12 +64,11 @@ |
202 | 'ubuntuone.devtools.testing', |
203 | 'ubuntuone.devtools.testcases'], |
204 | extra_path='ubuntuone-dev-tools', |
205 | - scripts=['bin/u1lint', |
206 | - 'bin/u1trial', |
207 | - ], |
208 | + scripts=scripts, |
209 | data_files=[('share/%s' % PACKAGE, |
210 | ['pylintrc', |
211 | - 'data/dbus-session.conf.in']), |
212 | + 'data/dbus-session.conf.in', |
213 | + 'data/squid.conf.in']), |
214 | ('share/man/man1', |
215 | ['man/u1lint.1', |
216 | 'man/u1trial.1']), |
217 | |
218 | === modified file 'ubuntuone/devtools/services/__init__.py' |
219 | --- ubuntuone/devtools/services/__init__.py 2010-11-03 21:04:06 +0000 |
220 | +++ ubuntuone/devtools/services/__init__.py 2012-01-17 21:43:26 +0000 |
221 | @@ -1,1 +1,54 @@ |
222 | +# |
223 | +# Copyright 2011 Canonical Ltd. |
224 | +# |
225 | +# This program is free software: you can redistribute it and/or modify it |
226 | +# under the terms of the GNU General Public License version 3, as published |
227 | +# by the Free Software Foundation. |
228 | +# |
229 | +# This program is distributed in the hope that it will be useful, but |
230 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
231 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
232 | +# PURPOSE. See the GNU General Public License for more details. |
233 | +# |
234 | +# You should have received a copy of the GNU General Public License along |
235 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
236 | """Service runners for testing.""" |
237 | + |
238 | +import os |
239 | +import socket |
240 | + |
241 | +from dirspec.basedir import load_data_paths |
242 | + |
243 | + |
244 | +def find_config_file(in_config_file): |
245 | + """Find the first appropriate conf to use.""" |
246 | + # In case we're running from within the source tree |
247 | + path = os.path.abspath(os.path.join(os.path.dirname(__file__), |
248 | + os.path.pardir, os.path.pardir, |
249 | + os.path.pardir, |
250 | + "data", in_config_file)) |
251 | + if not os.path.exists(path): |
252 | + # Use the installed file in $pkgdatadir as source |
253 | + for path in load_data_paths("ubuntuone-dev-tools", |
254 | + in_config_file): |
255 | + if os.path.exists(path): |
256 | + break |
257 | + |
258 | + # Check to make sure we didn't just fall out of the loop |
259 | + if not os.path.exists(path): |
260 | + raise IOError('Could not locate suitable %s' % in_config_file) |
261 | + return path |
262 | + |
263 | + |
264 | +def get_arbitrary_port(): |
265 | + """ |
266 | + Find an unused port, and return it. |
267 | + |
268 | + There might be a small race condition here, but we aren't |
269 | + worried about it. |
270 | + """ |
271 | + sock = socket.socket() |
272 | + sock.bind(('localhost', 0)) |
273 | + _, port = sock.getsockname() |
274 | + sock.close() |
275 | + return port |
276 | |
277 | === modified file 'ubuntuone/devtools/services/dbus.py' |
278 | --- ubuntuone/devtools/services/dbus.py 2011-12-19 22:37:31 +0000 |
279 | +++ ubuntuone/devtools/services/dbus.py 2012-01-17 21:43:26 +0000 |
280 | @@ -20,10 +20,12 @@ |
281 | import signal |
282 | import subprocess |
283 | |
284 | -from dirspec.basedir import load_data_paths |
285 | from distutils.spawn import find_executable |
286 | from urllib import quote |
287 | |
288 | +from ubuntuone.devtools.services import find_config_file |
289 | +DBUS_CONFIG_FILE = 'dbus-session.conf.in' |
290 | + |
291 | |
292 | class DBusLaunchError(Exception): |
293 | """Error while launching dbus-daemon""" |
294 | @@ -44,42 +46,25 @@ |
295 | self.running = False |
296 | self.config_file = None |
297 | |
298 | - def _find_config_file(self, tempdir=None): |
299 | + def _generate_config_file(self, tempdir=None): |
300 | """Find the first appropriate dbus-session.conf to use.""" |
301 | - # In case we're running from within the source tree |
302 | - path = os.path.abspath(os.path.join(os.path.dirname(__file__), |
303 | - os.path.pardir, os.path.pardir, |
304 | - os.path.pardir, |
305 | - "data", "dbus-session.conf.in")) |
306 | - if not os.path.exists(path): |
307 | - # Use the installed file in $pkgdatadir as source |
308 | - for path in load_data_paths("ubuntuone-dev-tools", |
309 | - "dbus-session.conf.in"): |
310 | - if os.path.exists(path): |
311 | - break |
312 | - |
313 | - # Check to make sure we didn't just fall out of the loop |
314 | - if not os.path.exists(path): |
315 | - raise IOError('Could not locate suitable dbus-session.conf.in') |
316 | - |
317 | + # load the config file |
318 | + path = find_config_file(DBUS_CONFIG_FILE) |
319 | + # replace config settings |
320 | self.config_file = os.path.join(tempdir, 'dbus-session.conf') |
321 | dbus_address = 'unix:tmpdir=%s' % quote(tempdir) |
322 | with open(path) as in_file: |
323 | content = in_file.read() |
324 | with open(self.config_file, 'w') as out_file: |
325 | out_file.write(content.replace('@ADDRESS@', dbus_address)) |
326 | - out_file.close() |
327 | - in_file.close() |
328 | |
329 | def start_service(self, tempdir=None): |
330 | """Start our own session bus daemon for testing.""" |
331 | - if not tempdir: |
332 | - tempdir = os.path.join(os.getcwd(), '_trial_temp') |
333 | dbus = find_executable("dbus-daemon") |
334 | if not dbus: |
335 | raise NotFoundError("dbus-daemon was not found.") |
336 | |
337 | - self._find_config_file(tempdir) |
338 | + self._generate_config_file(tempdir) |
339 | |
340 | dbus_args = ["--fork", |
341 | "--config-file=" + self.config_file, |
342 | |
343 | === added file 'ubuntuone/devtools/services/squid.py' |
344 | --- ubuntuone/devtools/services/squid.py 1970-01-01 00:00:00 +0000 |
345 | +++ ubuntuone/devtools/services/squid.py 2012-01-17 21:43:26 +0000 |
346 | @@ -0,0 +1,245 @@ |
347 | +# |
348 | +# Copyright 2011 Canonical Ltd. |
349 | +# |
350 | +# This program is free software: you can redistribute it and/or modify it |
351 | +# under the terms of the GNU General Public License version 3, as published |
352 | +# by the Free Software Foundation. |
353 | +# |
354 | +# This program is distributed in the hope that it will be useful, but |
355 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
356 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
357 | +# PURPOSE. See the GNU General Public License for more details. |
358 | +# |
359 | +# You should have received a copy of the GNU General Public License along |
360 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
361 | +"""Utilities for finding and running a squid proxy for testing.""" |
362 | + |
363 | +import random |
364 | +import signal |
365 | +# pylint:disable=W0402 |
366 | +import string |
367 | +# pylint:enable=W0402 |
368 | +import subprocess |
369 | +import time |
370 | + |
371 | +from json import dumps, loads |
372 | +from os import environ, makedirs, kill, unlink |
373 | +from os.path import abspath, exists, join |
374 | + |
375 | +from distutils.spawn import find_executable |
376 | + |
377 | +from ubuntuone.devtools.services import ( |
378 | + find_config_file, |
379 | + get_arbitrary_port, |
380 | +) |
381 | + |
382 | +SQUID_CONFIG_FILE = 'squid.conf.in' |
383 | +SQUID_DIR = 'squid' |
384 | +AUTH_PROCESS_PATH = '/usr/lib/%s/ncsa_auth' |
385 | +SPOOL_DIR = 'spool' |
386 | +AUTH_FILE = 'htpasswd' |
387 | +PROXY_ENV_VAR = 'SQUID_PROXY_SETTINGS' |
388 | + |
389 | + |
390 | +def get_squid_executable(): |
391 | + """Return the squid executable of the system.""" |
392 | + # try with squid and if not present try with squid3 for newer systems |
393 | + # (Ubuntu P). We also return the path to the auth process so that we can |
394 | + # point to the correct one. |
395 | + squid = find_executable('squid3') |
396 | + auth_process = AUTH_PROCESS_PATH % 'squid3' |
397 | + if squid is None: |
398 | + squid = find_executable('squid') |
399 | + auth_process = AUTH_PROCESS_PATH % 'squid' |
400 | + return squid, auth_process |
401 | + |
402 | + |
403 | +def get_htpasswd_executable(): |
404 | + """Return the htpasswd executable.""" |
405 | + return find_executable('htpasswd') |
406 | + |
407 | + |
408 | +def _make_random_string(count): |
409 | + """Make a random string of the given length.""" |
410 | + entropy = random.SystemRandom() |
411 | + return ''.join([entropy.choice(string.letters) for _ in |
412 | + range(count)]) |
413 | + |
414 | + |
415 | +def _get_basedir(tempdir): |
416 | + """Return the base squid config.""" |
417 | + basedir = join(tempdir, SQUID_DIR) |
418 | + basedir = abspath(basedir) |
419 | + if not exists(basedir): |
420 | + makedirs(basedir) |
421 | + return basedir |
422 | + |
423 | + |
424 | +def _get_spool_temp_path(tempdir=''): |
425 | + """Return the temp dir to be used for spool.""" |
426 | + basedir = _get_basedir(tempdir) |
427 | + path = join(basedir, SPOOL_DIR) |
428 | + path = abspath(path) |
429 | + if not exists(path): |
430 | + makedirs(path) |
431 | + return path |
432 | + |
433 | + |
434 | +def _get_squid_temp_path(tempdir=''): |
435 | + """Return the temp dir to be used by squid.""" |
436 | + basedir = _get_basedir(tempdir) |
437 | + path = join(basedir, SQUID_DIR) |
438 | + path = abspath(path) |
439 | + if not exists(path): |
440 | + makedirs(path) |
441 | + return path |
442 | + |
443 | + |
444 | +def _get_auth_temp_path(tempdir=''): |
445 | + """Return the path for the auth file.""" |
446 | + basedir = _get_basedir(tempdir) |
447 | + auth_file = join(basedir, AUTH_FILE) |
448 | + if not exists(basedir): |
449 | + makedirs(basedir) |
450 | + return auth_file |
451 | + |
452 | + |
453 | +def store_proxy_settings(settings): |
454 | + """Store the proxy setting in an env var.""" |
455 | + environ[PROXY_ENV_VAR] = dumps(settings) |
456 | + |
457 | + |
458 | +def retrieve_proxy_settings(): |
459 | + """Return the proxy settings of the env.""" |
460 | + if PROXY_ENV_VAR in environ: |
461 | + return loads(environ[PROXY_ENV_VAR]) |
462 | + return None |
463 | + |
464 | + |
465 | +def delete_proxy_settings(): |
466 | + """Delete the proxy env settings.""" |
467 | + if PROXY_ENV_VAR in environ: |
468 | + del environ[PROXY_ENV_VAR] |
469 | + |
470 | + |
471 | +class SquidLaunchError(Exception): |
472 | + """Error while launching squid.""" |
473 | + |
474 | + |
475 | +class SquidRunner(object): |
476 | + """Class for running a squid proxy with the local config.""" |
477 | + |
478 | + def __init__(self): |
479 | + """Create a new instance.""" |
480 | + self.squid, self.auth_process = get_squid_executable() |
481 | + if self.squid is None: |
482 | + raise SquidLaunchError('Could not locate "squid".') |
483 | + |
484 | + self.htpasswd = get_htpasswd_executable() |
485 | + if self.htpasswd is None: |
486 | + raise SquidLaunchError('Could not locate "htpasswd".') |
487 | + |
488 | + self.settings = dict(noauth_port=None, auth_port=None, |
489 | + username=None, password=None) |
490 | + self.squid_pid = None |
491 | + self.running = False |
492 | + self.config_file = None |
493 | + self.auth_file = None |
494 | + |
495 | + def _generate_config_file(self, tempdir=''): |
496 | + """Find the first appropiate squid.conf to use.""" |
497 | + # load the config file |
498 | + path = find_config_file(SQUID_CONFIG_FILE) |
499 | + # replace config settings |
500 | + basedir = join(tempdir, 'squid') |
501 | + basedir = abspath(basedir) |
502 | + if not exists(basedir): |
503 | + makedirs(basedir) |
504 | + self.config_file = join(basedir, 'squid.conf') |
505 | + with open(path) as in_file: |
506 | + template = string.Template(in_file.read()) |
507 | + |
508 | + self.settings['noauth_port'] = get_arbitrary_port() |
509 | + self.settings['auth_port'] = get_arbitrary_port() |
510 | + spool_path = _get_spool_temp_path(tempdir) |
511 | + squid_path = _get_squid_temp_path(tempdir) |
512 | + with open(self.config_file, 'w') as out_file: |
513 | + out_file.write(template.safe_substitute( |
514 | + auth_file=self.auth_file, |
515 | + auth_process=self.auth_process, |
516 | + noauth_port_number=self.settings['noauth_port'], |
517 | + auth_port_number=self.settings['auth_port'], |
518 | + spool_temp=spool_path, |
519 | + squid_temp=squid_path)) |
520 | + |
521 | + def _generate_swap(self, config_file): |
522 | + """Generate the squid swap files.""" |
523 | + squid_args = ['-z', '-f', config_file] |
524 | + sp = subprocess.Popen([self.squid] + squid_args, |
525 | + stdout=subprocess.PIPE, |
526 | + stderr=subprocess.PIPE) |
527 | + sp.wait() |
528 | + |
529 | + def _generate_auth_file(self, tempdir=''): |
530 | + """Generates a auth file using htpasswd.""" |
531 | + if self.settings['username'] is None: |
532 | + self.settings['username'] = _make_random_string(10) |
533 | + if self.settings['password'] is None: |
534 | + self.settings['password'] = _make_random_string(10) |
535 | + |
536 | + self.auth_file = _get_auth_temp_path(tempdir) |
537 | + # remove possible old auth file |
538 | + if exists(self.auth_file): |
539 | + unlink(self.auth_file) |
540 | + # create a new htpasswrd |
541 | + htpasswd_args = ['-bc', |
542 | + self.auth_file, |
543 | + self.settings['username'], |
544 | + self.settings['password']] |
545 | + sp = subprocess.Popen([self.htpasswd] + htpasswd_args, |
546 | + stdout=subprocess.PIPE, |
547 | + stderr=subprocess.PIPE) |
548 | + sp.wait() |
549 | + |
550 | + def _is_squid_running(self): |
551 | + """Return if squid is running.""" |
552 | + squid_args = ['-k', 'check', '-f', self.config_file] |
553 | + print 'Starting squid version...' |
554 | + message = 'Waiting for squid to start...' |
555 | + for timeout in (0.4, 0.1, 0.1, 0.2, 0.5, 1, 3, 5): |
556 | + try: |
557 | + # Do not use stdout=PIPE or stderr=PIPE with this function. |
558 | + subprocess.check_call([self.squid] + squid_args, |
559 | + stdout=subprocess.PIPE, |
560 | + stderr=subprocess.PIPE) |
561 | + return True |
562 | + except subprocess.CalledProcessError: |
563 | + message += '.' |
564 | + print message |
565 | + time.sleep(timeout) |
566 | + return False |
567 | + |
568 | + def start_service(self, tempdir=None): |
569 | + """Start our own proxy.""" |
570 | + # generate auth, config and swap dirs |
571 | + self._generate_auth_file(tempdir) |
572 | + self._generate_config_file(tempdir) |
573 | + self._generate_swap(self.config_file) |
574 | + squid_args = ['-N', '-X', '-f', self.config_file] |
575 | + sp = subprocess.Popen([self.squid] + squid_args, |
576 | + stdout=subprocess.PIPE, |
577 | + stderr=subprocess.PIPE) |
578 | + store_proxy_settings(self.settings) |
579 | + if not self._is_squid_running(): |
580 | + raise SquidLaunchError('Could not start squid.') |
581 | + self.squid_pid = sp.pid |
582 | + self.running = True |
583 | + |
584 | + def stop_service(self): |
585 | + """Stop our proxy,""" |
586 | + kill(self.squid_pid, signal.SIGKILL) |
587 | + delete_proxy_settings() |
588 | + self.running = False |
589 | + unlink(self.config_file) |
590 | + unlink(self.auth_file) |
591 | + self.config_file = None |
592 | |
593 | === modified file 'ubuntuone/devtools/services/tests/test_dbus.py' |
594 | --- ubuntuone/devtools/services/tests/test_dbus.py 2011-11-23 11:31:08 +0000 |
595 | +++ ubuntuone/devtools/services/tests/test_dbus.py 2012-01-17 21:43:26 +0000 |
596 | @@ -25,6 +25,6 @@ |
597 | runner = DBusRunner() |
598 | # pylint: disable=W0212 |
599 | os.makedirs(self.tmpdir) |
600 | - runner._find_config_file(tempdir=self.tmpdir) |
601 | + runner._generate_config_file(tempdir=self.tmpdir) |
602 | shutil.rmtree(self.tmpdir) |
603 | self.assertEqual(expected, runner.config_file) |
604 | |
605 | === added file 'ubuntuone/devtools/services/tests/test_squid.py' |
606 | --- ubuntuone/devtools/services/tests/test_squid.py 1970-01-01 00:00:00 +0000 |
607 | +++ ubuntuone/devtools/services/tests/test_squid.py 2012-01-17 21:43:26 +0000 |
608 | @@ -0,0 +1,334 @@ |
609 | +# |
610 | +# Copyright 2011 Canonical Ltd. |
611 | +# |
612 | +# This program is free software: you can redistribute it and/or modify it |
613 | +# under the terms of the GNU General Public License version 3, as published |
614 | +# by the Free Software Foundation. |
615 | +# |
616 | +# This program is distributed in the hope that it will be useful, but |
617 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
618 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
619 | +# PURPOSE. See the GNU General Public License for more details. |
620 | +# |
621 | +# You should have received a copy of the GNU General Public License along |
622 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
623 | +"""Test the squid service.""" |
624 | + |
625 | +import json |
626 | +import os |
627 | + |
628 | +from twisted.internet import defer |
629 | + |
630 | +from ubuntuone.devtools.testcases import BaseTestCase |
631 | +from ubuntuone.devtools.services import squid |
632 | + |
633 | +# yes, we know names with _ are private |
634 | +#pylint: disable=W0212 |
635 | + |
636 | + |
637 | +class PathsTestCase(BaseTestCase): |
638 | + """Test the different path functions.""" |
639 | + |
640 | + @defer.inlineCallbacks |
641 | + def setUp(self): |
642 | + """Set the different tests.""" |
643 | + yield super(PathsTestCase, self).setUp() |
644 | + self.basedir_fn = squid._get_basedir |
645 | + self.basedir = self.mktemp('paths') |
646 | + self.path_exists = False |
647 | + self.created_paths = [] |
648 | + self.called = [] |
649 | + |
650 | + def fake_basedir_fn(tempdir): |
651 | + """Retun the base dir.""" |
652 | + self.called.append(('fake_basedir_fn', tempdir)) |
653 | + return self.basedir |
654 | + |
655 | + def fake_makedirs(path): |
656 | + """Fake the makedirs function.""" |
657 | + self.called.append(('fake_makedirs', path)) |
658 | + self.created_paths.append(path) |
659 | + |
660 | + def fake_exists(path): |
661 | + """Fake the exists method.""" |
662 | + self.called.append(('fake_exists', path)) |
663 | + return path in self.created_paths or self.path_exists |
664 | + |
665 | + self.patch(squid, '_get_basedir', fake_basedir_fn) |
666 | + self.patch(squid, 'makedirs', fake_makedirs) |
667 | + self.patch(squid, 'exists', fake_exists) |
668 | + |
669 | + def test_get_basedir_missing(self): |
670 | + """Test the base dir creation.""" |
671 | + basedir = self.basedir_fn(self.basedir) |
672 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
673 | + self.assertEqual(expected_path, basedir) |
674 | + self.assertTrue(('fake_makedirs', expected_path) in self.called) |
675 | + self.assertTrue(expected_path in self.created_paths) |
676 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
677 | + |
678 | + def test_get_basedir_present(self): |
679 | + """Test the base dir creation.""" |
680 | + self.path_exists = True |
681 | + basedir = self.basedir_fn(self.basedir) |
682 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
683 | + self.assertEqual(expected_path, basedir) |
684 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
685 | + self.assertTrue(('fake_makedirs', expected_path) not in self.called) |
686 | + self.assertTrue(expected_path not in self.created_paths) |
687 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
688 | + |
689 | + def test_get_spool_temp_path_missing(self): |
690 | + """Test the spool path creation.""" |
691 | + expected_path = os.path.join(self.basedir, squid.SPOOL_DIR) |
692 | + result = squid._get_spool_temp_path() |
693 | + self.assertEqual(expected_path, result) |
694 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
695 | + self.assertTrue(('fake_makedirs', expected_path) in self.called) |
696 | + self.assertTrue(expected_path in self.created_paths) |
697 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
698 | + |
699 | + def test_get_spool_temp_path_present(self): |
700 | + """Test the spool path creation.""" |
701 | + self.path_exists = True |
702 | + expected_path = os.path.join(self.basedir, squid.SPOOL_DIR) |
703 | + result = squid._get_spool_temp_path() |
704 | + self.assertEqual(expected_path, result) |
705 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
706 | + self.assertTrue(('fake_makedirs', expected_path) not in self.called) |
707 | + self.assertTrue(expected_path not in self.created_paths) |
708 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
709 | + |
710 | + def test_get_squid_temp_path_missing(self): |
711 | + """Test the squid path creation.""" |
712 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
713 | + result = squid._get_squid_temp_path() |
714 | + self.assertEqual(expected_path, result) |
715 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
716 | + self.assertTrue(('fake_makedirs', expected_path) in self.called) |
717 | + self.assertTrue(expected_path in self.created_paths) |
718 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
719 | + |
720 | + def test_get_squid_temp_path_present(self): |
721 | + """Test the squid path creation.""" |
722 | + self.path_exists = True |
723 | + expected_path = os.path.join(self.basedir, squid.SQUID_DIR) |
724 | + result = squid._get_squid_temp_path() |
725 | + self.assertEqual(expected_path, result) |
726 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
727 | + self.assertTrue(('fake_makedirs', expected_path) not in self.called) |
728 | + self.assertTrue(expected_path not in self.created_paths) |
729 | + self.assertTrue(('fake_exists', expected_path) in self.called) |
730 | + |
731 | + def test_get_auth_temp_path(self): |
732 | + """Test the creation of the auth path.""" |
733 | + self.path_exists = False |
734 | + expected_path = os.path.join(self.basedir, squid.AUTH_FILE) |
735 | + result = squid._get_auth_temp_path() |
736 | + self.assertEqual(expected_path, result) |
737 | + self.assertTrue(('fake_basedir_fn', '') in self.called) |
738 | + self.assertTrue(('fake_makedirs', self.basedir) in self.called) |
739 | + self.assertTrue(self.basedir in self.created_paths) |
740 | + self.assertTrue(('fake_exists', self.basedir) in self.called) |
741 | + |
742 | + |
743 | +class EnvironTestCase(BaseTestCase): |
744 | + """Test the different environ functions.""" |
745 | + |
746 | + @defer.inlineCallbacks |
747 | + def setUp(self): |
748 | + """Set the tests.""" |
749 | + yield super(EnvironTestCase, self).setUp() |
750 | + self.called = [] |
751 | + self.settings = dict(noauth_port=3434, auth_port=232323, |
752 | + username='u1', password='test') |
753 | + |
754 | + def fake_dumps(data): |
755 | + """Fake dumps.""" |
756 | + self.called.append(('dumps', data)) |
757 | + return json.dumps(data) |
758 | + |
759 | + def fake_loads(data): |
760 | + """Fake loads.""" |
761 | + self.called.append(('loads', data)) |
762 | + return json.loads(data) |
763 | + |
764 | + self.patch(squid, 'dumps', fake_dumps) |
765 | + self.patch(squid, 'loads', fake_loads) |
766 | + self.env = {} |
767 | + self.old_env = os.environ |
768 | + squid.environ = self.env |
769 | + self.addCleanup(self.set_back_environ) |
770 | + |
771 | + def set_back_environ(self): |
772 | + """Set back the env.""" |
773 | + squid.environ = self.old_env |
774 | + |
775 | + def test_store_settings(self): |
776 | + """Test the storage of the settings.""" |
777 | + squid.store_proxy_settings(self.settings) |
778 | + self.assertTrue(('dumps', self.settings) in self.called) |
779 | + self.assertEqual(self.env[squid.PROXY_ENV_VAR], |
780 | + json.dumps(self.settings)) |
781 | + |
782 | + def test_retrieve_proxy_settings(self): |
783 | + """Test reading the settings.""" |
784 | + self.env[squid.PROXY_ENV_VAR] = json.dumps(self.settings) |
785 | + self.assertTrue(('loads', self.env[squid.PROXY_ENV_VAR])) |
786 | + self.assertEqual(squid.retrieve_proxy_settings(), self.settings) |
787 | + |
788 | + def test_delete_proxy_settings_present(self): |
789 | + """Delete the proxy settings.""" |
790 | + self.env[squid.PROXY_ENV_VAR] = json.dumps(self.settings) |
791 | + squid.delete_proxy_settings() |
792 | + self.assertFalse(squid.PROXY_ENV_VAR in self.env) |
793 | + |
794 | + |
795 | +class SquidRunnerInitTestCase(BaseTestCase): |
796 | + """Test the creation of the runner.""" |
797 | + |
798 | + @defer.inlineCallbacks |
799 | + def setUp(self): |
800 | + """Set the different tests.""" |
801 | + yield super(SquidRunnerInitTestCase, self).setUp() |
802 | + self.executables = {} |
803 | + self.called = [] |
804 | + |
805 | + def fake_find_executable(executable): |
806 | + """Fake the find executable.""" |
807 | + self.called.append(('fake_find_executable', executable)) |
808 | + return self.executables.get(executable, None) |
809 | + |
810 | + self.patch(squid, 'find_executable', fake_find_executable) |
811 | + |
812 | + def _assert_missing_binary(self, binary): |
813 | + """Perform the assertion when a bin is missing.""" |
814 | + self.assertRaises(squid.SquidLaunchError, squid.SquidRunner) |
815 | + self.assertTrue(('fake_find_executable', binary) in self.called, |
816 | + self.called) |
817 | + |
818 | + def test_squid_missing(self): |
819 | + """Test when squid is missing.""" |
820 | + self.executables['htpasswd'] = 'htpasswd' |
821 | + self._assert_missing_binary('squid') |
822 | + |
823 | + def test_htpasswd_missing(self): |
824 | + """Test when htpasswd is missing.""" |
825 | + self.executables['squid'] = 'squid' |
826 | + self.executables['squid3'] = 'squid' |
827 | + self._assert_missing_binary('htpasswd') |
828 | + |
829 | + |
830 | +class FakeSubprocess(object): |
831 | + """Fake the subprocess module.""" |
832 | + |
833 | + # pylint: disable=C0103 |
834 | + def __init__(self): |
835 | + """Create a new instance.""" |
836 | + self.called = [] |
837 | + self.PIPE = 'PIPE' |
838 | + |
839 | + def Popen(self, args, stdout=None, stderr=None): |
840 | + """Fake Popen.""" |
841 | + self.called.append(('Popen', args, stdout, stderr)) |
842 | + return self |
843 | + # pylint: enable=C0103 |
844 | + |
845 | + def wait(self): |
846 | + """Fake wait from a Popen object.""" |
847 | + self.called.append(('wait',)) |
848 | + |
849 | + |
850 | +class FakeTemplate(object): |
851 | + """Fake the string.Template.""" |
852 | + |
853 | + def __init__(self): |
854 | + """Create a new instance.""" |
855 | + self.data = None |
856 | + self.called = [] |
857 | + |
858 | + def __call__(self, data): |
859 | + """Fake constructor.""" |
860 | + self.data = data |
861 | + return self |
862 | + |
863 | + def safe_substitute(self, *args, **kwargs): |
864 | + """Fake the safe_substitute.""" |
865 | + self.called.append(('safe_substitute', args, kwargs)) |
866 | + return self.data |
867 | + |
868 | + |
869 | +class SquidRunnerTestCase(BaseTestCase): |
870 | + """Test the default test case.""" |
871 | + |
872 | + @defer.inlineCallbacks |
873 | + def setUp(self): |
874 | + """Set the different tests.""" |
875 | + yield super(SquidRunnerTestCase, self).setUp() |
876 | + self.subprocess = FakeSubprocess() |
877 | + self.patch(squid, 'subprocess', self.subprocess) |
878 | + |
879 | + self.called = [] |
880 | + self.executables = dict(squid='squid', htpasswd='htpasswd') |
881 | + |
882 | + def fake_find_executable(executable): |
883 | + """Fake the find executable.""" |
884 | + self.called.append(('fake_find_executable', executable)) |
885 | + return self.executables.get(executable, None) |
886 | + |
887 | + self.patch(squid, 'find_executable', fake_find_executable) |
888 | + |
889 | + self.auth_temp = 'path/to/auth' |
890 | + |
891 | + def fake_get_auth_temp_path(tempdir): |
892 | + """Return the path for the auth file.""" |
893 | + self.called.append(('fake_get_auth_temp_path', tempdir)) |
894 | + return self.auth_temp |
895 | + |
896 | + self.patch(squid, '_get_auth_temp_path', fake_get_auth_temp_path) |
897 | + |
898 | + self.port = 2324 |
899 | + |
900 | + def fake_get_port(): |
901 | + """Fake the methos that returns the ports.""" |
902 | + self.called.append(('fake_get_port',)) |
903 | + return self.port |
904 | + |
905 | + self.patch(squid, 'get_arbitrary_port', fake_get_port) |
906 | + self.template = FakeTemplate() |
907 | + self.patch(squid.string, 'Template', self.template) |
908 | + self.runner = squid.SquidRunner() |
909 | + |
910 | + def test_generate_swap(self): |
911 | + """Test the generation of the squid swap.""" |
912 | + config_file = 'path/to/config' |
913 | + expected_args = ['squid', '-z', '-f', config_file] |
914 | + self.runner._generate_swap(config_file) |
915 | + self.assertEqual(expected_args, self.subprocess.called[0][1]) |
916 | + self.assertTrue('wait' in self.subprocess.called[1]) |
917 | + |
918 | + def test_generate_auth_file(self): |
919 | + """Test the generation of the auth file.""" |
920 | + username = self.runner.settings['username'] = 'mandel' |
921 | + password = self.runner.settings['password'] = 'test' |
922 | + expected_args = ['htpasswd', '-bc', self.auth_temp, |
923 | + username, password] |
924 | + self.patch(squid, 'exists', lambda f: False) |
925 | + self.runner._generate_auth_file() |
926 | + self.assertEqual(expected_args, self.subprocess.called[0][1]) |
927 | + self.assertTrue('wait' in self.subprocess.called[1]) |
928 | + |
929 | + def test_generate_config_file(self): |
930 | + """Test the generation of the config file.""" |
931 | + self.runner.auth_file = self.auth_temp |
932 | + self.runner._generate_config_file(self.tmpdir) |
933 | + # remove the generated file |
934 | + self.addCleanup(os.unlink, self.runner.config_file) |
935 | + expected_parameters = ('safe_substitute', (), |
936 | + dict(auth_file=self.runner.auth_file, |
937 | + noauth_port_number=self.port, |
938 | + auth_port_number=self.port, |
939 | + spool_temp=squid._get_spool_temp_path(self.tmpdir), |
940 | + squid_temp=squid._get_squid_temp_path(self.tmpdir))) |
941 | + self.assertTrue(expected_parameters, self.template.called[0]) |
942 | + self.assertEqual(2, self.called.count(('fake_get_port',))) |
943 | |
944 | === modified file 'ubuntuone/devtools/testcases/dbus.py' |
945 | --- ubuntuone/devtools/testcases/dbus.py 2011-11-23 11:14:00 +0000 |
946 | +++ ubuntuone/devtools/testcases/dbus.py 2012-01-17 21:43:26 +0000 |
947 | @@ -14,7 +14,7 @@ |
948 | # You should have received a copy of the GNU General Public License along |
949 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
950 | |
951 | -"""Base tests cases and test utilities.""" |
952 | +"""Base dbus tests cases and test utilities.""" |
953 | |
954 | from __future__ import absolute_import, with_statement |
955 | |
956 | |
957 | === added file 'ubuntuone/devtools/testcases/squid.py' |
958 | --- ubuntuone/devtools/testcases/squid.py 1970-01-01 00:00:00 +0000 |
959 | +++ ubuntuone/devtools/testcases/squid.py 2012-01-17 21:43:26 +0000 |
960 | @@ -0,0 +1,58 @@ |
961 | +# -*- coding: utf-8 -*- |
962 | +# |
963 | +# Copyright 2011 Canonical Ltd. |
964 | +# |
965 | +# This program is free software: you can redistribute it and/or modify it |
966 | +# under the terms of the GNU General Public License version 3, as published |
967 | +# by the Free Software Foundation. |
968 | +# |
969 | +# This program is distributed in the hope that it will be useful, but |
970 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
971 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
972 | +# PURPOSE. See the GNU General Public License for more details. |
973 | +# |
974 | +# You should have received a copy of the GNU General Public License along |
975 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
976 | + |
977 | +"""Base squid tests cases and test utilities.""" |
978 | + |
979 | +from ubuntuone.devtools.testcase import BaseTestCase, skipIf |
980 | +from ubuntuone.devtools.services.squid import ( |
981 | + SquidRunner, |
982 | + SquidLaunchError, |
983 | + get_squid_executable, |
984 | + get_htpasswd_executable, |
985 | + retrieve_proxy_settings) |
986 | + |
987 | +# pylint: disable=C0103 |
988 | +squid, _ = get_squid_executable() |
989 | +htpasswd = get_htpasswd_executable() |
990 | +# pylint: enable=C0103 |
991 | + |
992 | + |
993 | +@skipIf(squid is None or htpasswd is None, |
994 | + 'The test requires squid and htpasswd.') |
995 | +class SquidTestCase(BaseTestCase): |
996 | + """Test that uses a proxy.""" |
997 | + |
998 | + def required_services(self): |
999 | + """Return the list of required services for DBusTestCase.""" |
1000 | + services = super(SquidTestCase, self).required_services() |
1001 | + services.extend([SquidRunner]) |
1002 | + return services |
1003 | + |
1004 | + def get_nonauth_proxy_settings(self): |
1005 | + """Return the settings of the noneauth proxy.""" |
1006 | + settings = retrieve_proxy_settings() |
1007 | + if settings is None: |
1008 | + raise SquidLaunchError('Proxy is not running.') |
1009 | + return dict(host='localhost', port=settings['noauth_port']) |
1010 | + |
1011 | + def get_auth_proxy_settings(self): |
1012 | + """Return the settings of the auth proxy.""" |
1013 | + settings = retrieve_proxy_settings() |
1014 | + if settings is None: |
1015 | + raise SquidLaunchError('Proxy is not running.') |
1016 | + return dict(host='localhost', port=settings['auth_port'], |
1017 | + username=settings['username'], |
1018 | + password=settings['password']) |
1019 | |
1020 | === added directory 'ubuntuone/devtools/testcases/tests' |
1021 | === added file 'ubuntuone/devtools/testcases/tests/__init__.py' |
1022 | --- ubuntuone/devtools/testcases/tests/__init__.py 1970-01-01 00:00:00 +0000 |
1023 | +++ ubuntuone/devtools/testcases/tests/__init__.py 2012-01-17 21:43:26 +0000 |
1024 | @@ -0,0 +1,16 @@ |
1025 | +# -*- coding: utf-8 -*- |
1026 | +# |
1027 | +# Copyright 2011 Canonical Ltd. |
1028 | +# |
1029 | +# This program is free software: you can redistribute it and/or modify it |
1030 | +# under the terms of the GNU General Public License version 3, as published |
1031 | +# by the Free Software Foundation. |
1032 | +# |
1033 | +# This program is distributed in the hope that it will be useful, but |
1034 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1035 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1036 | +# PURPOSE. See the GNU General Public License for more details. |
1037 | +# |
1038 | +# You should have received a copy of the GNU General Public License along |
1039 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1040 | +"""Tests for the test cases.""" |
1041 | |
1042 | === added file 'ubuntuone/devtools/testcases/tests/test_squid_testcase.py' |
1043 | --- ubuntuone/devtools/testcases/tests/test_squid_testcase.py 1970-01-01 00:00:00 +0000 |
1044 | +++ ubuntuone/devtools/testcases/tests/test_squid_testcase.py 2012-01-17 21:43:26 +0000 |
1045 | @@ -0,0 +1,262 @@ |
1046 | +# -*- coding: utf-8 -*- |
1047 | +# |
1048 | +# Copyright 2011 Canonical Ltd. |
1049 | +# |
1050 | +# This program is free software: you can redistribute it and/or modify it |
1051 | +# under the terms of the GNU General Public License version 3, as published |
1052 | +# by the Free Software Foundation. |
1053 | +# |
1054 | +# This program is distributed in the hope that it will be useful, but |
1055 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1056 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1057 | +# PURPOSE. See the GNU General Public License for more details. |
1058 | +# |
1059 | +# You should have received a copy of the GNU General Public License along |
1060 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1061 | +"""Test the squid test case.""" |
1062 | +import base64 |
1063 | + |
1064 | +from twisted.application import internet, service |
1065 | +from twisted.internet import defer, reactor |
1066 | +from twisted.web import client, error, http, resource, server |
1067 | + |
1068 | +from ubuntuone.devtools.testcases.squid import SquidTestCase |
1069 | + |
1070 | + |
1071 | +SAMPLE_RESOURCE = "<p>Hello World!</p>" |
1072 | +SIMPLERESOURCE = "simpleresource" |
1073 | +THROWERROR = "throwerror" |
1074 | +UNAUTHORIZED = "unauthorized" |
1075 | + |
1076 | +# ignore common twisted lint errors |
1077 | +# pylint: disable=C0103, W0212 |
1078 | + |
1079 | + |
1080 | +class ProxyClientFactory(client.HTTPClientFactory): |
1081 | + """Factory that supports proxy.""" |
1082 | + |
1083 | + def __init__(self, proxy_url, proxy_port, url, headers=None): |
1084 | + # we set the proxy details before the init because the parent __init__ |
1085 | + # calls setURL |
1086 | + self.proxy_url = proxy_url |
1087 | + self.proxy_port = proxy_port |
1088 | + self.disconnected_d = defer.Deferred() |
1089 | + client.HTTPClientFactory.__init__(self, url, headers=headers) |
1090 | + |
1091 | + def setURL(self, url): |
1092 | + self.host = self.proxy_url |
1093 | + self.port = self.proxy_port |
1094 | + self.url = url |
1095 | + self.path = url |
1096 | + |
1097 | + def clientConnectionLost(self, connector, reason, reconnecting=0): |
1098 | + """Connection lost.""" |
1099 | + self.disconnected_d.callback(self) |
1100 | + |
1101 | + |
1102 | +class ProxyWebClient(object): |
1103 | + """Provide useful web methods with proxy.""" |
1104 | + |
1105 | + def __init__(self, proxy_url=None, proxy_port=None, username=None, |
1106 | + password=None): |
1107 | + """Create a new instance with the proxy settings.""" |
1108 | + self.proxy_url = proxy_url |
1109 | + self.proxy_port = proxy_port |
1110 | + self.username = username |
1111 | + self.password = password |
1112 | + self.factory = None |
1113 | + self.connectors = [] |
1114 | + |
1115 | + def _connect(self, url, contextFactory): |
1116 | + """Perform the connection.""" |
1117 | + scheme, _, _, _ = client._parse(url) |
1118 | + # pylint: disable=E1101 |
1119 | + if scheme == 'https': |
1120 | + from twisted.internet import ssl |
1121 | + if contextFactory is None: |
1122 | + contextFactory = ssl.ClientContextFactory() |
1123 | + self.connectors.append(reactor.connectSSL(self.proxy_url, |
1124 | + self.proxy_port, |
1125 | + self.factory, |
1126 | + contextFactory)) |
1127 | + else: |
1128 | + self.connectors.append(reactor.connectTCP(self.proxy_url, |
1129 | + self.proxy_port, |
1130 | + self.factory)) |
1131 | + # pylint: enable=E1101 |
1132 | + |
1133 | + def _process_auth_error(self, failure, url, contextFactory): |
1134 | + """Process an auth failure.""" |
1135 | + failure.trap(error.Error) |
1136 | + if failure.value.status == str(http.PROXY_AUTH_REQUIRED): |
1137 | + # we try to get the page using the basic auth |
1138 | + auth = base64.b64encode('%s:%s' % (self.username, self.password)) |
1139 | + auth_header = 'Basic ' + auth.strip() |
1140 | + self.factory = ProxyClientFactory(self.proxy_url, self.proxy_port, |
1141 | + url, headers={'Proxy-Authorization': auth_header}) |
1142 | + self._connect(url, contextFactory) |
1143 | + return self.factory.deferred |
1144 | + else: |
1145 | + return failure |
1146 | + |
1147 | + def get_page(self, url, contextFactory=None, *args, **kwargs): |
1148 | + """Download a webpage as a string. |
1149 | + |
1150 | + This method relies on the twisted.web.client.getPage but adds and extra |
1151 | + step. If there is an auth error the method will perform a second try |
1152 | + so that the username and password are used. |
1153 | + """ |
1154 | + self.factory = ProxyClientFactory(self.proxy_url, self.proxy_port, url, |
1155 | + headers={'Connection': 'close'}) |
1156 | + self._connect(url, contextFactory) |
1157 | + self.factory.deferred.addErrback(self._process_auth_error, url, |
1158 | + contextFactory) |
1159 | + return self.factory.deferred |
1160 | + |
1161 | + @defer.inlineCallbacks |
1162 | + def shutdown(self): |
1163 | + """Clean all connectors.""" |
1164 | + for connector in self.connectors: |
1165 | + yield connector.disconnect() |
1166 | + defer.returnValue(True) |
1167 | + |
1168 | + |
1169 | +class SimpleResource(resource.Resource): |
1170 | + """A simple web resource.""" |
1171 | + |
1172 | + def render_GET(self, request): |
1173 | + """Make a bit of html out of these resource's |
1174 | + content.""" |
1175 | + return SAMPLE_RESOURCE |
1176 | + |
1177 | + |
1178 | +class SaveHTTPChannel(http.HTTPChannel): |
1179 | + """A save protocol to be used in tests.""" |
1180 | + |
1181 | + protocolInstance = None |
1182 | + |
1183 | + def connectionMade(self): |
1184 | + """Keep track of the given protocol.""" |
1185 | + SaveHTTPChannel.protocolInstance = self |
1186 | + http.HTTPChannel.connectionMade(self) |
1187 | + |
1188 | + |
1189 | +class SaveSite(server.Site): |
1190 | + """A site that let us know when it closed.""" |
1191 | + |
1192 | + protocol = SaveHTTPChannel |
1193 | + |
1194 | + def __init__(self, *args, **kwargs): |
1195 | + """Create a new instance.""" |
1196 | + server.Site.__init__(self, *args, **kwargs) |
1197 | + # we disable the timeout in the tests, we will deal with it manually. |
1198 | + self.timeOut = None |
1199 | + |
1200 | + |
1201 | +class MockWebServer(object): |
1202 | + """A mock webserver for testing""" |
1203 | + |
1204 | + def __init__(self): |
1205 | + """Start up this instance.""" |
1206 | + root = resource.Resource() |
1207 | + root.putChild(SIMPLERESOURCE, SimpleResource()) |
1208 | + |
1209 | + root.putChild(THROWERROR, resource.NoResource()) |
1210 | + |
1211 | + unauthorized_resource = resource.ErrorPage(resource.http.UNAUTHORIZED, |
1212 | + "Unauthorized", "Unauthorized") |
1213 | + root.putChild(UNAUTHORIZED, unauthorized_resource) |
1214 | + |
1215 | + self.site = SaveSite(root) |
1216 | + application = service.Application('web') |
1217 | + self.service_collection = service.IServiceCollection(application) |
1218 | + #pylint: disable=E1101 |
1219 | + self.tcpserver = internet.TCPServer(0, self.site) |
1220 | + self.tcpserver.setServiceParent(self.service_collection) |
1221 | + self.service_collection.startService() |
1222 | + |
1223 | + def get_url(self): |
1224 | + """Build the url for this mock server.""" |
1225 | + #pylint: disable=W0212 |
1226 | + port_num = self.tcpserver._port.getHost().port |
1227 | + return "http://localhost:%d/" % port_num |
1228 | + |
1229 | + @defer.inlineCallbacks |
1230 | + def stop(self): |
1231 | + """Shut it down.""" |
1232 | + #pylint: disable=E1101 |
1233 | + # make the connection time out so that is works with squid3 when |
1234 | + # the connection is kept alive. |
1235 | + if self.site.protocol.protocolInstance: |
1236 | + self.site.protocol.protocolInstance.timeoutConnection() |
1237 | + yield self.service_collection.stopService() |
1238 | + |
1239 | + |
1240 | +class ProxyTestCase(SquidTestCase): |
1241 | + """A squid test with no auth proxy.""" |
1242 | + |
1243 | + @defer.inlineCallbacks |
1244 | + def setUp(self): |
1245 | + """Set the tests.""" |
1246 | + yield super(ProxyTestCase, self).setUp() |
1247 | + self.ws = MockWebServer() |
1248 | + self.proxy_client = None |
1249 | + self.addCleanup(self.teardown_client_server) |
1250 | + self.url = self.ws.get_url() + SIMPLERESOURCE |
1251 | + |
1252 | + def teardown_client_server(self): |
1253 | + """Clean resources.""" |
1254 | + if self.proxy_client is not None: |
1255 | + self.proxy_client.shutdown() |
1256 | + return defer.gatherResults([self.ws.stop(), |
1257 | + self.proxy_client.shutdown(), |
1258 | + self.proxy_client.factory.disconnected_d]) |
1259 | + else: |
1260 | + return self.ws.stop() |
1261 | + |
1262 | + def access_noauth_url(self, address, port): |
1263 | + """Access a url throught the proxy.""" |
1264 | + self.proxy_client = ProxyWebClient(proxy_url=address, proxy_port=port) |
1265 | + return self.proxy_client.get_page(self.url) |
1266 | + |
1267 | + def access_auth_url(self, address, port, username, password): |
1268 | + """Access a url throught the proxy.""" |
1269 | + self.proxy_client = ProxyWebClient(proxy_url=address, proxy_port=port, |
1270 | + username=username, password=password) |
1271 | + return self.proxy_client.get_page(self.url) |
1272 | + |
1273 | + @defer.inlineCallbacks |
1274 | + def test_noauth_url_access(self): |
1275 | + """Test accessing to the url.""" |
1276 | + settings = self.get_nonauth_proxy_settings() |
1277 | + # if there is an exception we fail. |
1278 | + data = yield self.access_noauth_url(settings['host'], |
1279 | + settings['port']) |
1280 | + self.assertEqual(SAMPLE_RESOURCE, data) |
1281 | + |
1282 | + @defer.inlineCallbacks |
1283 | + def test_auth_url_access(self): |
1284 | + """Test accessing to the url.""" |
1285 | + settings = self.get_auth_proxy_settings() |
1286 | + # if there is an exception we fail. |
1287 | + data = yield self.access_auth_url(settings['host'], |
1288 | + settings['port'], |
1289 | + settings['username'], |
1290 | + settings['password']) |
1291 | + self.assertEqual(SAMPLE_RESOURCE, data) |
1292 | + |
1293 | + def test_auth_url_401(self): |
1294 | + """Test failing accessing the url.""" |
1295 | + settings = self.get_auth_proxy_settings() |
1296 | + # swap password for username to fail |
1297 | + d = self.failUnlessFailure(self.access_auth_url(settings['host'], |
1298 | + settings['port'], settings['password'], |
1299 | + settings['username']), error.Error) |
1300 | + return d |
1301 | + |
1302 | + def test_auth_url_407(self): |
1303 | + """Test failing accessing the url.""" |
1304 | + settings = self.get_auth_proxy_settings() |
1305 | + d = self.failUnlessFailure(self.access_noauth_url(settings['host'], |
1306 | + settings['port']), error.Error) |
1307 | + return d |
+1