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
on 2012-01-17
| Status: | Merged |
|---|---|
| Approved by: | dobey on 2012-01-17 |
| 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 on 2012-01-17 | ||
| Brian Curtin (community) | Approve on 2012-01-17 | ||
| Diego Sarmentero (community) | Approve on 2012-01-17 | ||
|
Review via email:
|
|||
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.
| 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
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