PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/zc/buildout/testing.py

https://github.com/koodaamo/buildout
Python | 511 lines | 464 code | 27 blank | 20 comment | 15 complexity | e75d20c307ecacbae49918cb7246ac09 MD5 | raw file
Possible License(s): GPL-2.0
  1. #############################################################################
  2. #
  3. # Copyright (c) 2004-2009 Zope Foundation and Contributors.
  4. # All Rights Reserved.
  5. #
  6. # This software is subject to the provisions of the Zope Public License,
  7. # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
  8. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
  9. # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  10. # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
  11. # FOR A PARTICULAR PURPOSE.
  12. #
  13. ##############################################################################
  14. """Various test-support utility functions
  15. """
  16. try:
  17. # Python 3
  18. from http.server import HTTPServer, BaseHTTPRequestHandler
  19. from urllib.request import urlopen
  20. except ImportError:
  21. # Python 2
  22. from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
  23. from urllib2 import urlopen
  24. import errno
  25. import logging
  26. import os
  27. import pkg_resources
  28. import random
  29. import re
  30. import shutil
  31. import socket
  32. import subprocess
  33. import sys
  34. import tempfile
  35. import threading
  36. import time
  37. import zc.buildout.buildout
  38. import zc.buildout.easy_install
  39. from zc.buildout.rmtree import rmtree
  40. print_ = zc.buildout.buildout.print_
  41. fsync = getattr(os, 'fsync', lambda fileno: None)
  42. is_win32 = sys.platform == 'win32'
  43. distribute_location = pkg_resources.working_set.find(
  44. pkg_resources.Requirement.parse('distribute')).location
  45. def cat(dir, *names):
  46. path = os.path.join(dir, *names)
  47. if (not os.path.exists(path)
  48. and is_win32
  49. and os.path.exists(path+'-script.py')
  50. ):
  51. path = path+'-script.py'
  52. print_(open(path).read(), end='')
  53. def ls(dir, *subs):
  54. if subs:
  55. dir = os.path.join(dir, *subs)
  56. names = os.listdir(dir)
  57. names.sort()
  58. for name in names:
  59. if os.path.isdir(os.path.join(dir, name)):
  60. print_('d ', end=' ')
  61. elif os.path.islink(os.path.join(dir, name)):
  62. print_('l ', end=' ')
  63. else:
  64. print_('- ', end=' ')
  65. print_(name)
  66. def mkdir(*path):
  67. os.mkdir(os.path.join(*path))
  68. def remove(*path):
  69. path = os.path.join(*path)
  70. if os.path.isdir(path):
  71. shutil.rmtree(path)
  72. else:
  73. os.remove(path)
  74. def rmdir(*path):
  75. shutil.rmtree(os.path.join(*path))
  76. def write(dir, *args):
  77. path = os.path.join(dir, *(args[:-1]))
  78. f = open(path, 'w')
  79. f.write(args[-1])
  80. f.flush()
  81. fsync(f.fileno())
  82. f.close()
  83. def clean_up_pyc(*path):
  84. base, filename = os.path.join(*path[:-1]), path[-1]
  85. if filename.endswith('.py'):
  86. filename += 'c' # .py -> .pyc
  87. for path in (
  88. os.path.join(base, filename),
  89. os.path.join(base, '__pycache__', filename),
  90. ):
  91. if os.path.exists(path):
  92. remove(path)
  93. ## FIXME - check for other platforms
  94. MUST_CLOSE_FDS = not sys.platform.startswith('win')
  95. def system(command, input=''):
  96. p = subprocess.Popen(command,
  97. shell=True,
  98. stdin=subprocess.PIPE,
  99. stdout=subprocess.PIPE,
  100. stderr=subprocess.PIPE,
  101. close_fds=MUST_CLOSE_FDS)
  102. i, o, e = (p.stdin, p.stdout, p.stderr)
  103. if input:
  104. i.write(input.encode())
  105. i.close()
  106. result = o.read() + e.read()
  107. o.close()
  108. e.close()
  109. return result.decode()
  110. def get(url):
  111. return str(urlopen(url).read().decode())
  112. def _runsetup(setup, *args):
  113. if os.path.isdir(setup):
  114. setup = os.path.join(setup, 'setup.py')
  115. args = list(args)
  116. args.insert(0, '-q')
  117. here = os.getcwd()
  118. try:
  119. os.chdir(os.path.dirname(setup))
  120. zc.buildout.easy_install.call_subprocess(
  121. [sys.executable, setup] + args,
  122. env=dict(os.environ, PYTHONPATH=distribute_location))
  123. if os.path.exists('build'):
  124. rmtree('build')
  125. finally:
  126. os.chdir(here)
  127. def sdist(setup, dest):
  128. _runsetup(setup, 'sdist', '-d', dest, '--formats=zip')
  129. def bdist_egg(setup, executable, dest=None):
  130. # Backward compat:
  131. if dest is None:
  132. dest = executable
  133. else:
  134. assert executable == sys.executable, (executable, sys.executable)
  135. _runsetup(setup, 'bdist_egg', '-d', dest)
  136. def wait_until(label, func, *args, **kw):
  137. if 'timeout' in kw:
  138. kw = dict(kw)
  139. timeout = kw.pop('timeout')
  140. else:
  141. timeout = 30
  142. deadline = time.time()+timeout
  143. while time.time() < deadline:
  144. if func(*args, **kw):
  145. return
  146. time.sleep(0.01)
  147. raise ValueError('Timed out waiting for: '+label)
  148. def buildoutSetUp(test):
  149. test.globs['__tear_downs'] = __tear_downs = []
  150. test.globs['register_teardown'] = register_teardown = __tear_downs.append
  151. prefer_final = zc.buildout.easy_install.prefer_final()
  152. register_teardown(
  153. lambda: zc.buildout.easy_install.prefer_final(prefer_final)
  154. )
  155. here = os.getcwd()
  156. register_teardown(lambda: os.chdir(here))
  157. handlers_before_set_up = logging.getLogger().handlers[:]
  158. def restore_root_logger_handlers():
  159. root_logger = logging.getLogger()
  160. for handler in root_logger.handlers[:]:
  161. root_logger.removeHandler(handler)
  162. for handler in handlers_before_set_up:
  163. root_logger.addHandler(handler)
  164. register_teardown(restore_root_logger_handlers)
  165. base = tempfile.mkdtemp('buildoutSetUp')
  166. base = os.path.realpath(base)
  167. register_teardown(lambda base=base: rmtree(base))
  168. old_home = os.environ.get('HOME')
  169. os.environ['HOME'] = os.path.join(base, 'bbbBadHome')
  170. def restore_home():
  171. if old_home is None:
  172. del os.environ['HOME']
  173. else:
  174. os.environ['HOME'] = old_home
  175. register_teardown(restore_home)
  176. base = os.path.join(base, '_TEST_')
  177. os.mkdir(base)
  178. tmp = tempfile.mkdtemp('buildouttests')
  179. register_teardown(lambda: rmtree(tmp))
  180. zc.buildout.easy_install.default_index_url = 'file://'+tmp
  181. os.environ['buildout-testing-index-url'] = (
  182. zc.buildout.easy_install.default_index_url)
  183. def tmpdir(name):
  184. path = os.path.join(base, name)
  185. mkdir(path)
  186. return path
  187. sample = tmpdir('sample-buildout')
  188. os.chdir(sample)
  189. # Create a basic buildout.cfg to avoid a warning from buildout:
  190. open('buildout.cfg', 'w').write(
  191. "[buildout]\nparts =\n"
  192. )
  193. # Use the buildout bootstrap command to create a buildout
  194. zc.buildout.buildout.Buildout(
  195. 'buildout.cfg',
  196. [('buildout', 'log-level', 'WARNING'),
  197. # trick bootstrap into putting the buildout develop egg
  198. # in the eggs dir.
  199. ('buildout', 'develop-eggs-directory', 'eggs'),
  200. ]
  201. ).bootstrap([])
  202. # Create the develop-eggs dir, which didn't get created the usual
  203. # way due to the trick above:
  204. os.mkdir('develop-eggs')
  205. def start_server(path):
  206. port, thread = _start_server(path, name=path)
  207. url = 'http://localhost:%s/' % port
  208. register_teardown(lambda: stop_server(url, thread))
  209. return url
  210. cdpaths = []
  211. def cd(*path):
  212. path = os.path.join(*path)
  213. cdpaths.append(os.path.abspath(os.getcwd()))
  214. os.chdir(path)
  215. def uncd():
  216. os.chdir(cdpaths.pop())
  217. test.globs.update(dict(
  218. sample_buildout = sample,
  219. ls = ls,
  220. cat = cat,
  221. mkdir = mkdir,
  222. rmdir = rmdir,
  223. remove = remove,
  224. tmpdir = tmpdir,
  225. write = write,
  226. system = system,
  227. get = get,
  228. cd = cd, uncd = uncd,
  229. join = os.path.join,
  230. sdist = sdist,
  231. bdist_egg = bdist_egg,
  232. start_server = start_server,
  233. buildout = os.path.join(sample, 'bin', 'buildout'),
  234. wait_until = wait_until,
  235. print_ = print_,
  236. clean_up_pyc = clean_up_pyc,
  237. ))
  238. zc.buildout.easy_install.prefer_final(prefer_final)
  239. def buildoutTearDown(test):
  240. for f in test.globs['__tear_downs']:
  241. f()
  242. class Server(HTTPServer):
  243. def __init__(self, tree, *args):
  244. HTTPServer.__init__(self, *args)
  245. self.tree = os.path.abspath(tree)
  246. __run = True
  247. def serve_forever(self):
  248. while self.__run:
  249. self.handle_request()
  250. def handle_error(self, *_):
  251. self.__run = False
  252. class Handler(BaseHTTPRequestHandler):
  253. Server.__log = False
  254. def __init__(self, request, address, server):
  255. self.__server = server
  256. self.tree = server.tree
  257. BaseHTTPRequestHandler.__init__(self, request, address, server)
  258. def do_GET(self):
  259. if '__stop__' in self.path:
  260. raise SystemExit
  261. def k():
  262. self.send_response(200)
  263. out = '<html><body>k</body></html>\n'.encode()
  264. self.send_header('Content-Length', str(len(out)))
  265. self.send_header('Content-Type', 'text/html')
  266. self.end_headers()
  267. self.wfile.write(out)
  268. if self.path == '/enable_server_logging':
  269. self.__server.__log = True
  270. return k()
  271. if self.path == '/disable_server_logging':
  272. self.__server.__log = False
  273. return k()
  274. path = os.path.abspath(os.path.join(self.tree, *self.path.split('/')))
  275. if not (
  276. ((path == self.tree) or path.startswith(self.tree+os.path.sep))
  277. and
  278. os.path.exists(path)
  279. ):
  280. self.send_response(404, 'Not Found')
  281. #self.send_response(200)
  282. out = '<html><body>Not Found</body></html>'.encode()
  283. #out = '\n'.join(self.tree, self.path, path)
  284. self.send_header('Content-Length', str(len(out)))
  285. self.send_header('Content-Type', 'text/html')
  286. self.end_headers()
  287. self.wfile.write(out)
  288. return
  289. self.send_response(200)
  290. if os.path.isdir(path):
  291. out = ['<html><body>\n']
  292. names = os.listdir(path)
  293. names.sort()
  294. for name in names:
  295. if os.path.isdir(os.path.join(path, name)):
  296. name += '/'
  297. out.append('<a href="%s">%s</a><br>\n' % (name, name))
  298. out.append('</body></html>\n')
  299. out = ''.join(out).encode()
  300. self.send_header('Content-Length', str(len(out)))
  301. self.send_header('Content-Type', 'text/html')
  302. else:
  303. out = open(path, 'rb').read()
  304. self.send_header('Content-Length', len(out))
  305. if path.endswith('.egg'):
  306. self.send_header('Content-Type', 'application/zip')
  307. elif path.endswith('.gz'):
  308. self.send_header('Content-Type', 'application/x-gzip')
  309. elif path.endswith('.zip'):
  310. self.send_header('Content-Type', 'application/x-gzip')
  311. else:
  312. self.send_header('Content-Type', 'text/html')
  313. self.end_headers()
  314. self.wfile.write(out)
  315. def log_request(self, code):
  316. if self.__server.__log:
  317. print_('%s %s %s' % (self.command, code, self.path))
  318. def _run(tree, port):
  319. server_address = ('localhost', port)
  320. httpd = Server(tree, server_address, Handler)
  321. httpd.serve_forever()
  322. def get_port():
  323. for i in range(10):
  324. port = random.randrange(20000, 30000)
  325. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  326. try:
  327. try:
  328. s.connect(('localhost', port))
  329. except socket.error:
  330. return port
  331. finally:
  332. s.close()
  333. raise RuntimeError("Can't find port")
  334. def _start_server(tree, name=''):
  335. port = get_port()
  336. thread = threading.Thread(target=_run, args=(tree, port), name=name)
  337. thread.setDaemon(True)
  338. thread.start()
  339. wait(port, up=True)
  340. return port, thread
  341. def start_server(tree):
  342. return _start_server(tree)[0]
  343. def stop_server(url, thread=None):
  344. try:
  345. urlopen(url+'__stop__')
  346. except Exception:
  347. pass
  348. if thread is not None:
  349. thread.join() # wait for thread to stop
  350. def wait(port, up):
  351. addr = 'localhost', port
  352. for i in range(120):
  353. time.sleep(0.25)
  354. try:
  355. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  356. s.connect(addr)
  357. s.close()
  358. if up:
  359. break
  360. except socket.error:
  361. e = sys.exc_info()[1]
  362. if e[0] not in (errno.ECONNREFUSED, errno.ECONNRESET):
  363. raise
  364. s.close()
  365. if not up:
  366. break
  367. else:
  368. if up:
  369. raise
  370. else:
  371. raise SystemError("Couln't stop server")
  372. def install(project, destination):
  373. if not isinstance(destination, str):
  374. destination = os.path.join(destination.globs['sample_buildout'],
  375. 'eggs')
  376. dist = pkg_resources.working_set.find(
  377. pkg_resources.Requirement.parse(project))
  378. if dist.location.endswith('.egg'):
  379. destination = os.path.join(destination,
  380. os.path.basename(dist.location),
  381. )
  382. if os.path.isdir(dist.location):
  383. shutil.copytree(dist.location, destination)
  384. else:
  385. shutil.copyfile(dist.location, destination)
  386. else:
  387. # copy link
  388. open(os.path.join(destination, project+'.egg-link'), 'w'
  389. ).write(dist.location)
  390. def install_develop(project, destination):
  391. if not isinstance(destination, str):
  392. destination = os.path.join(destination.globs['sample_buildout'],
  393. 'develop-eggs')
  394. dist = pkg_resources.working_set.find(
  395. pkg_resources.Requirement.parse(project))
  396. open(os.path.join(destination, project+'.egg-link'), 'w'
  397. ).write(dist.location)
  398. def _normalize_path(match):
  399. path = match.group(1)
  400. if os.path.sep == '\\':
  401. path = path.replace('\\\\', '/')
  402. if path.startswith('\\'):
  403. path = path[1:]
  404. return '/' + path.replace(os.path.sep, '/')
  405. normalize_path = (
  406. re.compile(
  407. r'''[^'" \t\n\r]+\%(sep)s_[Tt][Ee][Ss][Tt]_\%(sep)s([^"' \t\n\r]+)'''
  408. % dict(sep=os.path.sep)),
  409. _normalize_path,
  410. )
  411. normalize_endings = re.compile('\r\n'), '\n'
  412. normalize_script = (
  413. re.compile('(\n?)- ([a-zA-Z_.-]+)-script.py\n- \\2.exe\n'),
  414. '\\1- \\2\n')
  415. if sys.version_info > (2, ):
  416. normalize___pycache__ = (
  417. re.compile('(\n?)d __pycache__\n'), '\\1')
  418. else:
  419. normalize___pycache__ = (
  420. re.compile('(\n?)- \S+\.pyc\n'), '\\1')
  421. normalize_egg_py = (
  422. re.compile('-py\d[.]\d(-\S+)?.egg'),
  423. '-pyN.N.egg',
  424. )
  425. normalize_exception_type_for_python_2_and_3 = (
  426. re.compile(r'^(\w+\.)*([A-Z][A-Za-z0-9]+Error: )'),
  427. '\2')
  428. not_found = (re.compile(r'Not found: [^\n]+/\w+/\r?\n'), '')
  429. ignore_not_upgrading = (
  430. re.compile(
  431. 'Not upgrading because not running a local buildout command.\n'
  432. ), '')