1
0

__init__.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2015-2018 - Copyright Red Hat Inc
  4. Authors:
  5. Pierre-Yves Chibon <pingou@pingoured.fr>
  6. """
  7. __requires__ = ['SQLAlchemy >= 0.7']
  8. import pkg_resources
  9. import json
  10. import logging
  11. import os
  12. import re
  13. import resource
  14. import shutil
  15. import subprocess
  16. import sys
  17. import tempfile
  18. import time
  19. import unittest
  20. logging.basicConfig(stream=sys.stderr)
  21. # Always enable performance counting for tests
  22. os.environ['PAGURE_PERFREPO'] = 'true'
  23. from contextlib import contextmanager
  24. from datetime import date
  25. from datetime import datetime
  26. from datetime import timedelta
  27. from functools import wraps
  28. from urlparse import urlparse, parse_qs
  29. import mock
  30. import pygit2
  31. import redis
  32. from bs4 import BeautifulSoup
  33. from celery.app.task import EagerResult
  34. from sqlalchemy import create_engine
  35. from sqlalchemy.orm import sessionmaker
  36. from sqlalchemy.orm import scoped_session
  37. sys.path.insert(0, os.path.join(os.path.dirname(
  38. os.path.abspath(__file__)), '..'))
  39. import pagure
  40. import pagure.api
  41. import pagure.flask_app
  42. import pagure.lib
  43. import pagure.lib.model
  44. import pagure.perfrepo as perfrepo
  45. from pagure.config import config as pagure_config, reload_config
  46. from pagure.lib.repo import PagureRepo
  47. HERE = os.path.join(os.path.dirname(os.path.abspath(__file__)))
  48. LOG = logging.getLogger(__name__)
  49. LOG.setLevel(logging.INFO)
  50. PAGLOG = logging.getLogger('pagure')
  51. PAGLOG.setLevel(logging.CRITICAL)
  52. PAGLOG.handlers = []
  53. CONFIG_TEMPLATE = """
  54. GIT_FOLDER = '%(path)s/repos'
  55. ENABLE_DOCS = %(enable_docs)s
  56. ENABLE_TICKETS = %(enable_tickets)s
  57. REMOTE_GIT_FOLDER = '%(path)s/remotes'
  58. DB_URL = '%(dburl)s'
  59. ALLOW_PROJECT_DOWAIT = True
  60. DEBUG = True
  61. PAGURE_CI_SERVICES = ['jenkins']
  62. EMAIL_SEND = False
  63. TESTING = True
  64. GIT_FOLDER = '%(path)s/repos'
  65. REQUESTS_FOLDER = '%(path)s/repos/requests'
  66. TICKETS_FOLDER = %(tickets_folder)r
  67. DOCS_FOLDER = %(docs_folder)r
  68. ATTACHMENTS_FOLDER = '%(path)s/attachments'
  69. BROKER_URL = 'redis+socket://%(global_path)s/broker'
  70. CELERY_CONFIG = {
  71. "task_always_eager": True,
  72. }
  73. """
  74. # The Celery docs warn against using task_always_eager:
  75. # http://docs.celeryproject.org/en/latest/userguide/testing.html
  76. # but that warning is only valid when testing the async nature of the task, not
  77. # what the task actually does.
  78. LOG.info('BUILD_ID: %s', os.environ.get('BUILD_ID'))
  79. WAIT_REGEX = re.compile("""var _url = '(\/wait\/[a-z0-9-]+\??.*)'""")
  80. def get_wait_target(html):
  81. """ This parses the window.location out of the HTML for the wait page. """
  82. found = WAIT_REGEX.findall(html)
  83. if len(found) == 0:
  84. raise Exception("Not able to get wait target in %s" % html)
  85. return found[-1]
  86. def get_post_target(html):
  87. """ This parses the wait page form to get the POST url. """
  88. soup = BeautifulSoup(html, 'html.parser')
  89. form = soup.find(id='waitform')
  90. if not form:
  91. raise Exception("Not able to get the POST url in %s" % html)
  92. return form.get('action')
  93. def get_post_args(html):
  94. """ This parses the wait page for the hidden arguments of the form. """
  95. soup = BeautifulSoup(html, 'html.parser')
  96. output = {}
  97. inputs = soup.find_all('input')
  98. if not inputs:
  99. raise Exception("Not able to get the POST arguments in %s" % html)
  100. for inp in inputs:
  101. if inp.get('type') == 'hidden':
  102. output[inp.get('name')] = inp.get('value')
  103. return output
  104. def create_maybe_waiter(method, getter):
  105. def maybe_waiter(*args, **kwargs):
  106. """ A wrapper for self.app.get()/.post() that will resolve wait's """
  107. result = method(*args, **kwargs)
  108. # Handle the POST wait case
  109. form_url = None
  110. form_args = None
  111. if 'id="waitform"' in result.data:
  112. form_url = get_post_target(result.data)
  113. form_args = get_post_args(result.data)
  114. form_args['csrf_token'] = result.data.split(
  115. 'name="csrf_token" type="hidden" value="')[1].split('">')[0]
  116. count = 0
  117. while 'We are waiting for your task to finish.' in result.data:
  118. # Resolve wait page
  119. target_url = get_wait_target(result.data)
  120. if count > 10:
  121. time.sleep(0.5)
  122. else:
  123. time.sleep(0.1)
  124. result = getter(target_url, follow_redirects=True)
  125. if count > 50:
  126. raise Exception('Had to wait too long')
  127. else:
  128. if form_url and form_args:
  129. return method(form_url, data=form_args, follow_redirects=True)
  130. return result
  131. return maybe_waiter
  132. @contextmanager
  133. def user_set(APP, user):
  134. """ Set the provided user as fas_user in the provided application."""
  135. # Hack used to remove the before_request function set by
  136. # flask.ext.fas_openid.FAS which otherwise kills our effort to set a
  137. # flask.g.fas_user.
  138. from flask import appcontext_pushed, g
  139. keep = []
  140. for meth in APP.before_request_funcs[None]:
  141. if 'flask_fas_openid.FAS' in str(meth):
  142. continue
  143. keep.append(meth)
  144. APP.before_request_funcs[None] = keep
  145. def handler(sender, **kwargs):
  146. g.fas_user = user
  147. g.fas_session_id = b'123'
  148. g.authenticated = True
  149. with appcontext_pushed.connected_to(handler, APP):
  150. yield
  151. tests_state = {
  152. "path": tempfile.mkdtemp(prefix='pagure-tests-'),
  153. "broker": None,
  154. "broker_client": None,
  155. "results": {},
  156. }
  157. def _populate_db(session):
  158. # Create a couple of users
  159. item = pagure.lib.model.User(
  160. user='pingou',
  161. fullname='PY C',
  162. password='foo',
  163. default_email='bar@pingou.com',
  164. )
  165. session.add(item)
  166. item = pagure.lib.model.UserEmail(
  167. user_id=1,
  168. email='bar@pingou.com')
  169. session.add(item)
  170. item = pagure.lib.model.UserEmail(
  171. user_id=1,
  172. email='foo@pingou.com')
  173. session.add(item)
  174. item = pagure.lib.model.User(
  175. user='foo',
  176. fullname='foo bar',
  177. password='foo',
  178. default_email='foo@bar.com',
  179. )
  180. session.add(item)
  181. item = pagure.lib.model.UserEmail(
  182. user_id=2,
  183. email='foo@bar.com')
  184. session.add(item)
  185. session.commit()
  186. def store_eager_results(*args, **kwargs):
  187. """A wrapper for EagerResult that stores the instance."""
  188. result = EagerResult(*args, **kwargs)
  189. tests_state["results"][result.id] = result
  190. return result
  191. def setUp():
  192. # In order to save time during local test execution, we create sqlite DB
  193. # file only once and then we populate it and empty it for every test case
  194. # (as opposed to creating DB file for every test case).
  195. session = pagure.lib.model.create_tables(
  196. 'sqlite:///%s/db.sqlite' % tests_state["path"],
  197. acls=pagure_config.get('ACLS', {}),
  198. )
  199. tests_state["db_session"] = session
  200. # Create a broker
  201. broker_url = os.path.join(tests_state["path"], 'broker')
  202. tests_state["broker"] = broker = subprocess.Popen(
  203. ['/usr/bin/redis-server', '--unixsocket', broker_url, '--port',
  204. '0', '--loglevel', 'warning', '--logfile', '/dev/null'],
  205. stdout=None, stderr=None)
  206. broker.poll()
  207. if broker.returncode is not None:
  208. raise Exception('Broker failed to start')
  209. tests_state["broker_client"] = redis.Redis(unix_socket_path=broker_url)
  210. # Store the EagerResults to be able to retrieve them later
  211. tests_state["eg_patcher"] = mock.patch('celery.app.task.EagerResult')
  212. eg_mock = tests_state["eg_patcher"].start()
  213. eg_mock.side_effect = store_eager_results
  214. def tearDown():
  215. tests_state["db_session"].close()
  216. tests_state["eg_patcher"].stop()
  217. broker = tests_state["broker"]
  218. broker.kill()
  219. broker.wait()
  220. shutil.rmtree(tests_state["path"])
  221. class SimplePagureTest(unittest.TestCase):
  222. """
  223. Simple Test class that does not set a broker/worker
  224. """
  225. populate_db = True
  226. config_values = {}
  227. @mock.patch('pagure.lib.notify.fedmsg_publish', mock.MagicMock())
  228. def __init__(self, method_name='runTest'):
  229. """ Constructor. """
  230. unittest.TestCase.__init__(self, method_name)
  231. self.session = None
  232. self.path = None
  233. self.gitrepo = None
  234. self.gitrepos = None
  235. def perfMaxWalks(self, max_walks, max_steps):
  236. """ Check that we have not performed too many walks/steps. """
  237. num_walks = 0
  238. num_steps = 0
  239. for reqstat in perfrepo.REQUESTS:
  240. for walk in reqstat['walks'].values():
  241. num_walks += 1
  242. num_steps += walk['steps']
  243. self.assertLessEqual(num_walks, max_walks,
  244. '%s git repo walks performed, at most %s allowed'
  245. % (num_walks, max_walks))
  246. self.assertLessEqual(num_steps, max_steps,
  247. '%s git repo steps performed, at most %s allowed'
  248. % (num_steps, max_steps))
  249. def perfReset(self):
  250. """ Reset perfrepo stats. """
  251. perfrepo.reset_stats()
  252. perfrepo.REQUESTS = []
  253. def setUp(self):
  254. self.perfReset()
  255. if not self.path:
  256. self.path = tempfile.mkdtemp(prefix='pagure-tests-path-')
  257. LOG.debug('Testdir: %s', self.path)
  258. for folder in ['repos', 'forks', 'releases', 'remotes', 'attachments']:
  259. os.mkdir(os.path.join(self.path, folder))
  260. if hasattr(pagure, 'REDIS') and pagure.REDIS:
  261. pagure.REDIS.connection_pool.disconnect()
  262. pagure.REDIS = None
  263. if hasattr(pagure.lib, 'REDIS') and pagure.lib.REDIS:
  264. pagure.lib.REDIS.connection_pool.disconnect()
  265. pagure.lib.REDIS = None
  266. # Database
  267. self._prepare_db()
  268. # Write a config file
  269. config_values = {
  270. 'path': self.path, 'dburl': self.dbpath,
  271. 'enable_docs': True,
  272. 'docs_folder': '%s/repos/docs' % self.path,
  273. 'enable_tickets': True,
  274. 'tickets_folder': '%s/repos/tickets' % self.path,
  275. 'global_path': tests_state["path"],
  276. }
  277. config_values.update(self.config_values)
  278. config_path = os.path.join(self.path, 'config')
  279. if not os.path.exists(config_path):
  280. with open(config_path, 'w') as f:
  281. f.write(CONFIG_TEMPLATE % config_values)
  282. os.environ["PAGURE_CONFIG"] = config_path
  283. pagure_config.update(reload_config())
  284. reload(pagure.lib.tasks)
  285. reload(pagure.lib.tasks_services)
  286. self._app = pagure.flask_app.create_app({'DB_URL': self.dbpath})
  287. # Remove the log handlers for the tests
  288. self._app.logger.handlers = []
  289. self.app = self._app.test_client()
  290. self.gr_patcher = mock.patch('pagure.lib.tasks.get_result')
  291. gr_mock = self.gr_patcher.start()
  292. gr_mock.side_effect = lambda tid: tests_state["results"][tid]
  293. def tearDown(self):
  294. self.gr_patcher.stop()
  295. self.session.rollback()
  296. self._clear_database()
  297. # Remove testdir
  298. try:
  299. shutil.rmtree(self.path)
  300. except:
  301. # Sometimes there is a race condition that makes deleting the folder
  302. # fail during the first attempt. So just try a second time if that's
  303. # the case.
  304. shutil.rmtree(self.path)
  305. self.path = None
  306. del self.app
  307. del self._app
  308. def shortDescription(self):
  309. doc = self.__str__() + ": " + self._testMethodDoc
  310. return doc or None
  311. def _prepare_db(self):
  312. self.dbpath = 'sqlite:///%s' % os.path.join(
  313. tests_state["path"], 'db.sqlite')
  314. self.session = tests_state["db_session"]
  315. pagure.lib.model.create_default_status(
  316. self.session, acls=pagure_config.get('ACLS', {}))
  317. if self.populate_db:
  318. _populate_db(self.session)
  319. def _clear_database(self):
  320. tables = reversed(pagure.lib.model.BASE.metadata.sorted_tables)
  321. if self.dbpath.startswith('postgresql'):
  322. self.session.execute("TRUNCATE %s CASCADE" % ", ".join(
  323. [t.name for t in tables]))
  324. elif self.dbpath.startswith('sqlite'):
  325. for table in tables:
  326. self.session.execute("DELETE FROM %s" % table.name)
  327. elif self.dbpath.startswith('mysql'):
  328. self.session.execute("SET FOREIGN_KEY_CHECKS = 0")
  329. for table in tables:
  330. self.session.execute("TRUNCATE %s" % table.name)
  331. self.session.execute("SET FOREIGN_KEY_CHECKS = 1")
  332. self.session.commit()
  333. def get_csrf(self, url='/new', output=None):
  334. """Retrieve a CSRF token from given URL."""
  335. if output is None:
  336. output = self.app.get(url)
  337. self.assertEqual(output.status_code, 200)
  338. return output.data.split(
  339. 'name="csrf_token" type="hidden" value="')[1].split('">')[0]
  340. def assertURLEqual(self, url_1, url_2):
  341. url_parsed_1 = list(urlparse(url_1))
  342. url_parsed_1[4] = parse_qs(url_parsed_1[4])
  343. url_parsed_2 = list(urlparse(url_2))
  344. url_parsed_2[4] = parse_qs(url_parsed_2[4])
  345. return self.assertListEqual(url_parsed_1, url_parsed_2)
  346. def assertJSONEqual(self, json_1, json_2):
  347. return self.assertEqual(json.loads(json_1), json.loads(json_2))
  348. class Modeltests(SimplePagureTest):
  349. """ Model tests. """
  350. def setUp(self): # pylint: disable=invalid-name
  351. """ Set up the environnment, ran before every tests. """
  352. # Clean up test performance info
  353. super(Modeltests, self).setUp()
  354. self.app.get = create_maybe_waiter(self.app.get, self.app.get)
  355. self.app.post = create_maybe_waiter(self.app.post, self.app.get)
  356. def tearDown(self): # pylint: disable=invalid-name
  357. """ Remove the test.db database if there is one. """
  358. tests_state["broker_client"].flushall()
  359. super(Modeltests, self).tearDown()
  360. class FakeGroup(object): # pylint: disable=too-few-public-methods
  361. """ Fake object used to make the FakeUser object closer to the
  362. expectations.
  363. """
  364. def __init__(self, name):
  365. """ Constructor.
  366. :arg name: the name given to the name attribute of this object.
  367. """
  368. self.name = name
  369. self.group_type = 'cla'
  370. class FakeUser(object): # pylint: disable=too-few-public-methods
  371. """ Fake user used to test the fedocallib library. """
  372. def __init__(self, groups=None, username='username', cla_done=True, id=1):
  373. """ Constructor.
  374. :arg groups: list of the groups in which this fake user is
  375. supposed to be.
  376. """
  377. if isinstance(groups, basestring):
  378. groups = [groups]
  379. self.id = id
  380. self.groups = groups or []
  381. self.user = username
  382. self.username = username
  383. self.name = username
  384. self.email = 'foo@bar.com'
  385. self.approved_memberships = [
  386. FakeGroup('packager'),
  387. FakeGroup('design-team')
  388. ]
  389. self.dic = {}
  390. self.dic['timezone'] = 'Europe/Paris'
  391. self.login_time = datetime.utcnow()
  392. self.cla_done = cla_done
  393. def __getitem__(self, key):
  394. return self.dic[key]
  395. def create_projects(session, is_fork=False, user_id=1, hook_token_suffix=''):
  396. """ Create some projects in the database. """
  397. item = pagure.lib.model.Project(
  398. user_id=user_id, # pingou
  399. name='test',
  400. is_fork=is_fork,
  401. parent_id=1 if is_fork else None,
  402. description='test project #1',
  403. hook_token='aaabbbccc' + hook_token_suffix,
  404. )
  405. item.close_status = ['Invalid', 'Insufficient data', 'Fixed', 'Duplicate']
  406. session.add(item)
  407. item = pagure.lib.model.Project(
  408. user_id=user_id, # pingou
  409. name='test2',
  410. is_fork=is_fork,
  411. parent_id=2 if is_fork else None,
  412. description='test project #2',
  413. hook_token='aaabbbddd' + hook_token_suffix,
  414. )
  415. item.close_status = ['Invalid', 'Insufficient data', 'Fixed', 'Duplicate']
  416. session.add(item)
  417. item = pagure.lib.model.Project(
  418. user_id=user_id, # pingou
  419. name='test3',
  420. is_fork=is_fork,
  421. parent_id=3 if is_fork else None,
  422. description='namespaced test project',
  423. hook_token='aaabbbeee' + hook_token_suffix,
  424. namespace='somenamespace',
  425. )
  426. item.close_status = ['Invalid', 'Insufficient data', 'Fixed', 'Duplicate']
  427. session.add(item)
  428. session.commit()
  429. def create_projects_git(folder, bare=False):
  430. """ Create some projects in the database. """
  431. repos = []
  432. for project in ['test.git', 'test2.git',
  433. os.path.join('somenamespace', 'test3.git')]:
  434. repo_path = os.path.join(folder, project)
  435. repos.append(repo_path)
  436. if not os.path.exists(repo_path):
  437. os.makedirs(repo_path)
  438. pygit2.init_repository(repo_path, bare=bare)
  439. return repos
  440. def create_tokens(session, user_id=1, project_id=1):
  441. """ Create some tokens for the project in the database. """
  442. item = pagure.lib.model.Token(
  443. id='aaabbbcccddd',
  444. user_id=user_id,
  445. project_id=project_id,
  446. expiration=datetime.utcnow() + timedelta(days=30)
  447. )
  448. session.add(item)
  449. item = pagure.lib.model.Token(
  450. id='foo_token',
  451. user_id=user_id,
  452. project_id=project_id,
  453. expiration=datetime.utcnow() + timedelta(days=30)
  454. )
  455. session.add(item)
  456. item = pagure.lib.model.Token(
  457. id='expired_token',
  458. user_id=user_id,
  459. project_id=project_id,
  460. expiration=datetime.utcnow() - timedelta(days=1)
  461. )
  462. session.add(item)
  463. session.commit()
  464. def create_tokens_acl(session, token_id='aaabbbcccddd', acl_name=None):
  465. """ Create some ACLs for the token. If acl_name is not set, the token will
  466. have all the ACLs enabled.
  467. """
  468. if acl_name is None:
  469. for aclid in range(len(pagure_config['ACLS'])):
  470. token_acl = pagure.lib.model.TokenAcl(
  471. token_id=token_id,
  472. acl_id=aclid + 1,
  473. )
  474. session.add(token_acl)
  475. else:
  476. acl = session.query(pagure.lib.model.ACL).filter_by(
  477. name=acl_name).one()
  478. token_acl = pagure.lib.model.TokenAcl(
  479. token_id=token_id,
  480. acl_id=acl.id,
  481. )
  482. session.add(token_acl)
  483. session.commit()
  484. def add_content_git_repo(folder, branch='master'):
  485. """ Create some content for the specified git repo. """
  486. if not os.path.exists(folder):
  487. os.makedirs(folder)
  488. brepo = pygit2.init_repository(folder, bare=True)
  489. newfolder = tempfile.mkdtemp(prefix='pagure-tests')
  490. repo = pygit2.clone_repository(folder, newfolder)
  491. # Create a file in that git repo
  492. with open(os.path.join(newfolder, 'sources'), 'w') as stream:
  493. stream.write('foo\n bar')
  494. repo.index.add('sources')
  495. repo.index.write()
  496. parents = []
  497. commit = None
  498. try:
  499. commit = repo.revparse_single(
  500. 'HEAD' if branch == 'master' else branch)
  501. except KeyError:
  502. pass
  503. if commit:
  504. parents = [commit.oid.hex]
  505. # Commits the files added
  506. tree = repo.index.write_tree()
  507. author = pygit2.Signature(
  508. 'Alice Author', 'alice@authors.tld')
  509. committer = pygit2.Signature(
  510. 'Cecil Committer', 'cecil@committers.tld')
  511. repo.create_commit(
  512. 'refs/heads/%s' % branch, # the name of the reference to update
  513. author,
  514. committer,
  515. 'Add sources file for testing',
  516. # binary string representing the tree object ID
  517. tree,
  518. # list of binary strings representing parents of the new commit
  519. parents,
  520. )
  521. parents = []
  522. commit = None
  523. try:
  524. commit = repo.revparse_single(
  525. 'HEAD' if branch == 'master' else branch)
  526. except KeyError:
  527. pass
  528. if commit:
  529. parents = [commit.oid.hex]
  530. subfolder = os.path.join('folder1', 'folder2')
  531. if not os.path.exists(os.path.join(newfolder, subfolder)):
  532. os.makedirs(os.path.join(newfolder, subfolder))
  533. # Create a file in that git repo
  534. with open(os.path.join(newfolder, subfolder, 'file'), 'w') as stream:
  535. stream.write('foo\n bar\nbaz')
  536. repo.index.add(os.path.join(subfolder, 'file'))
  537. with open(os.path.join(newfolder, subfolder, 'fileŠ'), 'w') as stream:
  538. stream.write('foo\n bar\nbaz')
  539. repo.index.add(os.path.join(subfolder, 'fileŠ'))
  540. repo.index.write()
  541. # Commits the files added
  542. tree = repo.index.write_tree()
  543. author = pygit2.Signature(
  544. 'Alice Author', 'alice@authors.tld')
  545. committer = pygit2.Signature(
  546. 'Cecil Committer', 'cecil@committers.tld')
  547. repo.create_commit(
  548. 'refs/heads/%s' % branch, # the name of the reference to update
  549. author,
  550. committer,
  551. 'Add some directory and a file for more testing',
  552. # binary string representing the tree object ID
  553. tree,
  554. # list of binary strings representing parents of the new commit
  555. parents
  556. )
  557. # Push to origin
  558. ori_remote = repo.remotes[0]
  559. master_ref = repo.lookup_reference(
  560. 'HEAD' if branch == 'master' else 'refs/heads/%s' % branch).resolve()
  561. refname = '%s:%s' % (master_ref.name, master_ref.name)
  562. PagureRepo.push(ori_remote, refname)
  563. shutil.rmtree(newfolder)
  564. def add_readme_git_repo(folder, readme_name='README.rst'):
  565. """ Create a README file for the specified git repo. """
  566. if not os.path.exists(folder):
  567. os.makedirs(folder)
  568. brepo = pygit2.init_repository(folder, bare=True)
  569. newfolder = tempfile.mkdtemp(prefix='pagure-tests')
  570. repo = pygit2.clone_repository(folder, newfolder)
  571. if readme_name == 'README.rst':
  572. content = """Pagure
  573. ======
  574. :Author: Pierre-Yves Chibon <pingou@pingoured.fr>
  575. Pagure is a light-weight git-centered forge based on pygit2.
  576. Currently, Pagure offers a web-interface for git repositories, a ticket
  577. system and possibilities to create new projects, fork existing ones and
  578. create/merge pull-requests across or within projects.
  579. Homepage: https://github.com/pypingou/pagure
  580. Dev instance: http://209.132.184.222/ (/!\\ May change unexpectedly, it's a dev instance ;-))
  581. """
  582. else:
  583. content = """Pagure
  584. ======
  585. This is a placeholder """ + readme_name + """
  586. that should never get displayed on the website if there is a README.rst in the repo.
  587. """
  588. parents = []
  589. commit = None
  590. try:
  591. commit = repo.revparse_single('HEAD')
  592. except KeyError:
  593. pass
  594. if commit:
  595. parents = [commit.oid.hex]
  596. # Create a file in that git repo
  597. with open(os.path.join(newfolder, readme_name), 'w') as stream:
  598. stream.write(content)
  599. repo.index.add(readme_name)
  600. repo.index.write()
  601. # Commits the files added
  602. tree = repo.index.write_tree()
  603. author = pygit2.Signature(
  604. 'Alice Author', 'alice@authors.tld')
  605. committer = pygit2.Signature(
  606. 'Cecil Committer', 'cecil@committers.tld')
  607. repo.create_commit(
  608. 'refs/heads/master', # the name of the reference to update
  609. author,
  610. committer,
  611. 'Add a README file',
  612. # binary string representing the tree object ID
  613. tree,
  614. # list of binary strings representing parents of the new commit
  615. parents
  616. )
  617. # Push to origin
  618. ori_remote = repo.remotes[0]
  619. master_ref = repo.lookup_reference('HEAD').resolve()
  620. refname = '%s:%s' % (master_ref.name, master_ref.name)
  621. PagureRepo.push(ori_remote, refname)
  622. shutil.rmtree(newfolder)
  623. def add_commit_git_repo(folder, ncommits=10, filename='sources',
  624. branch='master'):
  625. """ Create some more commits for the specified git repo. """
  626. if not os.path.exists(folder):
  627. os.makedirs(folder)
  628. pygit2.init_repository(folder, bare=True)
  629. newfolder = tempfile.mkdtemp(prefix='pagure-tests')
  630. repo = pygit2.clone_repository(folder, newfolder)
  631. for index in range(ncommits):
  632. # Create a file in that git repo
  633. with open(os.path.join(newfolder, filename), 'a') as stream:
  634. stream.write('Row %s\n' % index)
  635. repo.index.add(filename)
  636. repo.index.write()
  637. parents = []
  638. commit = None
  639. try:
  640. commit = repo.revparse_single('HEAD')
  641. except KeyError:
  642. pass
  643. if commit:
  644. parents = [commit.oid.hex]
  645. # Commits the files added
  646. tree = repo.index.write_tree()
  647. author = pygit2.Signature(
  648. 'Alice Author', 'alice@authors.tld')
  649. committer = pygit2.Signature(
  650. 'Cecil Committer', 'cecil@committers.tld')
  651. repo.create_commit(
  652. 'refs/heads/master',
  653. author,
  654. committer,
  655. 'Add row %s to %s file' % (index, filename),
  656. # binary string representing the tree object ID
  657. tree,
  658. # list of binary strings representing parents of the new commit
  659. parents,
  660. )
  661. # Push to origin
  662. ori_remote = repo.remotes[0]
  663. PagureRepo.push(ori_remote, 'HEAD:refs/heads/%s' % branch)
  664. shutil.rmtree(newfolder)
  665. def add_content_to_git(folder, filename='sources', content='foo'):
  666. """ Create some more commits for the specified git repo. """
  667. if not os.path.exists(folder):
  668. os.makedirs(folder)
  669. brepo = pygit2.init_repository(folder, bare=True)
  670. newfolder = tempfile.mkdtemp(prefix='pagure-tests')
  671. repo = pygit2.clone_repository(folder, newfolder)
  672. # Create a file in that git repo
  673. with open(os.path.join(newfolder, filename), 'a') as stream:
  674. stream.write('%s\n' % content)
  675. repo.index.add(filename)
  676. repo.index.write()
  677. parents = []
  678. commit = None
  679. try:
  680. commit = repo.revparse_single('HEAD')
  681. except KeyError:
  682. pass
  683. if commit:
  684. parents = [commit.oid.hex]
  685. # Commits the files added
  686. tree = repo.index.write_tree()
  687. author = pygit2.Signature(
  688. 'Alice Author', 'alice@authors.tld')
  689. committer = pygit2.Signature(
  690. 'Cecil Committer', 'cecil@committers.tld')
  691. repo.create_commit(
  692. 'refs/heads/master', # the name of the reference to update
  693. author,
  694. committer,
  695. 'Add content to file %s' % (filename),
  696. # binary string representing the tree object ID
  697. tree,
  698. # list of binary strings representing parents of the new commit
  699. parents,
  700. )
  701. # Push to origin
  702. ori_remote = repo.remotes[0]
  703. master_ref = repo.lookup_reference('HEAD').resolve()
  704. refname = '%s:%s' % (master_ref.name, master_ref.name)
  705. PagureRepo.push(ori_remote, refname)
  706. shutil.rmtree(newfolder)
  707. def add_binary_git_repo(folder, filename):
  708. """ Create a fake image file for the specified git repo. """
  709. if not os.path.exists(folder):
  710. os.makedirs(folder)
  711. brepo = pygit2.init_repository(folder, bare=True)
  712. newfolder = tempfile.mkdtemp(prefix='pagure-tests')
  713. repo = pygit2.clone_repository(folder, newfolder)
  714. content = b"""\x00\x00\x01\x00\x01\x00\x18\x18\x00\x00\x01\x00 \x00\x88
  715. \t\x00\x00\x16\x00\x00\x00(\x00\x00\x00\x18\x00x00\x00\x01\x00 \x00\x00\x00
  716. \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
  717. 00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa7lM\x01\xa6kM\t\xa6kM\x01
  718. \xa4fF\x04\xa2dE\x95\xa2cD8\xa1a
  719. """
  720. parents = []
  721. commit = None
  722. try:
  723. commit = repo.revparse_single('HEAD')
  724. except KeyError:
  725. pass
  726. if commit:
  727. parents = [commit.oid.hex]
  728. # Create a file in that git repo
  729. with open(os.path.join(newfolder, filename), 'wb') as stream:
  730. stream.write(content)
  731. repo.index.add(filename)
  732. repo.index.write()
  733. # Commits the files added
  734. tree = repo.index.write_tree()
  735. author = pygit2.Signature(
  736. 'Alice Author', 'alice@authors.tld')
  737. committer = pygit2.Signature(
  738. 'Cecil Committer', 'cecil@committers.tld')
  739. repo.create_commit(
  740. 'refs/heads/master', # the name of the reference to update
  741. author,
  742. committer,
  743. 'Add a fake image file',
  744. # binary string representing the tree object ID
  745. tree,
  746. # list of binary strings representing parents of the new commit
  747. parents
  748. )
  749. # Push to origin
  750. ori_remote = repo.remotes[0]
  751. master_ref = repo.lookup_reference('HEAD').resolve()
  752. refname = '%s:%s' % (master_ref.name, master_ref.name)
  753. PagureRepo.push(ori_remote, refname)
  754. shutil.rmtree(newfolder)
  755. @contextmanager
  756. def capture_output(merge_stderr=True):
  757. import sys
  758. from cStringIO import StringIO
  759. oldout, olderr = sys.stdout, sys.stderr
  760. try:
  761. out = StringIO()
  762. err = StringIO()
  763. if merge_stderr:
  764. sys.stdout = sys.stderr = out
  765. yield out
  766. else:
  767. sys.stdout, sys.stderr = out, err
  768. yield out, err
  769. finally:
  770. sys.stdout, sys.stderr = oldout, olderr
  771. if __name__ == '__main__':
  772. SUITE = unittest.TestLoader().loadTestsFromTestCase(Modeltests)
  773. unittest.TextTestRunner(verbosity=2).run(SUITE)