[pypy-svn] buildbot default: merge heads
arigo
commits-noreply at bitbucket.org
Fri Apr 29 11:52:40 CEST 2011
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r496:c5111efe6dd9
Date: 2011-04-29 11:52 +0200
http://bitbucket.org/pypy/buildbot/changeset/c5111efe6dd9/
Log: merge heads
diff --git a/bitbucket_hook/hook.py b/bitbucket_hook/hook.py
--- a/bitbucket_hook/hook.py
+++ b/bitbucket_hook/hook.py
@@ -1,307 +1,53 @@
import os.path
import py
-import smtplib
-import socket
import subprocess
import sys
-from subprocess import Popen, PIPE
+import time
-LOCAL_REPOS = py.path.local(__file__).dirpath('repos')
-REMOTE_BASE = 'http://bitbucket.org'
+from .main import app
+from . import scm
+#
+from . import stdoutlog
+from . import irc
+from . import mail
-if socket.gethostname() == 'viper':
- # for debugging, antocuni's settings
- SMTP_SERVER = "out.alice.it"
- SMTP_PORT = 25
- ADDRESS = 'anto.cuni at gmail.com'
- #
- CHANNEL = '#test'
- BOT = '/tmp/commit-bot/message'
-else:
- # real settings, (they works on codespeak at least)
- SMTP_SERVER = 'localhost'
- SMTP_PORT = 25
- ADDRESS = 'pypy-svn at codespeak.net'
- #
- CHANNEL = '#pypy'
- BOT = '/svn/hooks/commit-bot/message'
-hgexe = str(py.path.local.sysfind('hg'))
+HANDLERS = [
+ stdoutlog.handle_commit,
+ irc.handle_commit,
+ mail.handle_commit
+ ]
-TEMPLATE = u"""\
-Author: {author}
-Branch: {branches}
-Changeset: r{rev}:{node|short}
-Date: {date|isodate}
-%(url)s
+def check_for_local_repo(local_repo, remote_repo, owner):
+ if local_repo.check(dir=True):
+ return True
+ if owner == app.config['DEFAULT_USER']:
+ print >> sys.stderr, 'Automatic initial clone of %s' % remote_repo
+ scm.hg('clone', str(remote_repo), str(local_repo))
+ return True
+ return False
-Log:\t{desc|fill68|tabindent}
+def get_commits(payload, seen_nodes=set()):
+ import operator
+ commits = sorted(payload['commits'],
+ key=operator.itemgetter('revision'))
+ for commit in commits:
+ node = commit['raw_node']
+ if node in seen_nodes:
+ continue
+ seen_nodes.add(node)
+ yield commit
-"""
-def getpaths(files, listfiles=False):
-
- # Handle empty input
- if not files:
- return '', ''
- files = [f['file'] for f in files]
- if not any(files):
- return '', ''
-
- dirname = os.path.dirname
- basename = os.path.basename
-
- common_prefix = [dirname(f) for f in files]
-
- # Single file, show its full path
- if len(files) == 1:
- common_prefix = files[0]
- listfiles = False
-
- else:
- common_prefix = [path.split(os.sep) for path in common_prefix]
- common_prefix = os.sep.join(os.path.commonprefix(common_prefix))
- if common_prefix and not common_prefix.endswith('/'):
- common_prefix += '/'
-
- if listfiles:
- # XXX Maybe should return file paths relative to prefix? Or TMI?
- filenames = [basename(f) for f in files if f and basename(f)]
- filenames = ' M(%s)' % ', '.join(filenames)
- else:
- filenames = ''
- return common_prefix, filenames
-
-
-class BitbucketHookHandler(object):
- Popen, PIPE = Popen, PIPE
- def _hgexe(self, argv):
- proc = self.Popen([hgexe] + list(argv), stdout=self.PIPE,
- stderr=self.PIPE)
- stdout, stderr = proc.communicate()
- ret = proc.wait()
- return stdout, stderr, ret
-
- def hg(self, *argv):
- argv = map(str, argv)
- stdout, stderr, ret = self._hgexe(argv)
- if ret != 0:
- print >> sys.stderr, 'error: hg', ' '.join(argv)
- print >> sys.stderr, stderr
- raise Exception('error when executing hg')
- return unicode(stdout, encoding='utf-8', errors='replace')
-
- SMTP = smtplib.SMTP
- def send(self, from_, to, subject, body, test=False):
- from email.mime.text import MIMEText
- # Is this a valid workaround for unicode errors?
- body = body.encode('ascii', 'xmlcharrefreplace')
- msg = MIMEText(body, _charset='utf-8')
- msg['From'] = from_
- msg['To'] = to
- msg['Subject'] = subject
- if test:
- print '#' * 20
- print "Email contents:\n"
- print from_
- print to
- print msg.get_payload(decode=True)
- else:
- smtp = self.SMTP(SMTP_SERVER, SMTP_PORT)
- smtp.sendmail(from_, [to], msg.as_string())
-
- call_subprocess = staticmethod(subprocess.call)
-
- def send_irc_message(self, message, test=False):
- if test:
- print message + '\n'
- else:
- return self.call_subprocess([BOT, CHANNEL, message])
-
- def handle(self, payload, test=False):
- path = payload['repository']['absolute_url']
- self.payload = payload
- self.local_repo = LOCAL_REPOS.join(path)
- self.remote_repo = REMOTE_BASE + path
- if not self.local_repo.check(dir=True):
- print >> sys.stderr, 'Ignoring unknown repo', path
- return
- self.hg('pull', '-R', self.local_repo)
- self.handle_irc_message(test)
- self.handle_diff_email(test)
-
- USE_COLOR_CODES = True
- LISTFILES = False
- def handle_irc_message(self, test=False):
- import operator
- commits = sorted(self.payload['commits'],
- key=operator.itemgetter('revision'))
- if test:
- print "#" * 20
- print "IRC messages:"
-
- for commit in commits:
- author = commit['author']
- branch = commit['branch']
- node = commit['node']
-
- files = commit.get('files', [])
- common_prefix, filenames = getpaths(files, self.LISTFILES)
- pathlen = len(common_prefix) + len(filenames) + 2
- common_prefix = '/' + common_prefix
-
- if self.USE_COLOR_CODES:
- author = '\x0312%s\x0F' % author # in blue
- branch = '\x02%s\x0F' % branch # in bold
- node = '\x0311%s\x0F' % node # in azure
- common_prefix = '\x0315%s\x0F' % common_prefix # in gray
-
- message = commit['message'].replace('\n', ' ')
- fields = (author, branch, node, common_prefix, filenames)
- part1 = '%s %s %s %s%s: ' % fields
- totallen = 160 + pathlen
- if len(message) + len(part1) <= totallen:
- irc_msg = part1 + message
- else:
- maxlen = totallen - (len(part1) + 3)
- irc_msg = part1 + message[:maxlen] + '...'
- self.send_irc_message(irc_msg, test)
-
- def handle_diff_email(self, test=False):
- import operator
- commits = sorted(self.payload['commits'],
- key=operator.itemgetter('revision'))
- for commit in commits:
- self.send_diff_for_commit(commit, test)
-
- def send_diff_for_commit(self, commit, test=False):
- hgid = commit['raw_node']
- sender = commit['author'] + ' <commits-noreply at bitbucket.org>'
- lines = commit['message'].splitlines()
- line0 = lines and lines[0] or ''
- reponame = self.payload['repository']['name']
- # TODO: maybe include the modified paths in the subject line?
- url = self.remote_repo + 'changeset/' + commit['node'] + '/'
- template = TEMPLATE % {'url': url}
- subject = '%s %s: %s' % (reponame, commit['branch'], line0)
- body = self.hg('-R', self.local_repo, 'log', '-r', hgid,
- '--template', template)
- diff = self.get_diff(hgid, commit['files'])
- body = body+diff
- self.send(sender, ADDRESS, subject, body, test)
-
- def get_diff(self, hgid, files):
- import re
- binary = re.compile('^GIT binary patch$', re.MULTILINE)
- files = [item['file'] for item in files]
- lines = []
- for filename in files:
- out = self.hg('-R', self.local_repo, 'diff', '--git', '-c', hgid,
- self.local_repo.join(filename))
- match = binary.search(out)
- if match:
- # it's a binary patch, omit the content
- out = out[:match.end()]
- out += u'\n[cut]'
- lines.append(out)
- return u'\n'.join(lines)
-
-
-if __name__ == '__main__':
- import hook as hookfile
- repopath = os.path.dirname(os.path.dirname(hookfile.__file__))
- print 'Repository path:', repopath
- test_payload = {u'repository': {u'absolute_url': '',
- u'name': u'test',
- u'owner': u'antocuni',
- u'slug': u'test',
- u'website': u''},
- u'user': u'antocuni'}
-
- commits = [{u'author': u'arigo',
- u'branch': u'default',
- u'files': [],
- u'message': u'Merge heads.',
- u'node': u'00ae063c6b8c',
- u'parents': [u'278760e9c560', u'29f1ff96548d'],
- u'raw_author': u'Armin Rigo <arigo at tunes.org>',
- u'raw_node': u'00ae063c6b8c13d873d92afc5485671f6a944077',
- u'revision': 403,
- u'size': 0,
- u'timestamp': u'2011-01-09 13:07:24'},
-
- {u'author': u'antocuni',
- u'branch': u'default',
- u'files': [{u'file': u'bitbucket_hook/hook.py', u'type': u'modified'}],
- u'message': u"don't send newlines to irc",
- u'node': u'e17583fbfa5c',
- u'parents': [u'69e9eac01cf6'],
- u'raw_author': u'Antonio Cuni <anto.cuni at gmail.com>',
- u'raw_node': u'e17583fbfa5c5636b5375a5fc81f3d388ce1b76e',
- u'revision': 399,
- u'size': 19,
- u'timestamp': u'2011-01-07 17:42:13'},
-
- {u'author': u'antocuni',
- u'branch': u'default',
- u'files': [{u'file': u'.hgignore', u'type': u'added'}],
- u'message': u'ignore irrelevant files',
- u'node': u'5cbd6e289c04',
- u'parents': [u'3a7c89443fc8'],
- u'raw_author': u'Antonio Cuni <anto.cuni at gmail.com>',
- u'raw_node': u'5cbd6e289c043c4dd9b6f55b5ec1c8d05711c6ad',
- u'revision': 362,
- u'size': 658,
- u'timestamp': u'2010-11-04 16:34:31'},
-
- {u'author': u'antocuni',
- u'branch': u'default',
- u'files': [{u'file': u'bitbucket_hook/hook.py', u'type': u'modified'},
- {u'file': u'bitbucket_hook/__init__.py', u'type': u'added'},
- {u'file': u'bitbucket_hook/test/__init__.py',
- u'type': u'added'},
- {u'file': u'bitbucket_hook/test/test_hook.py',
- u'type': u'added'}],
- u'message': u'partially refactor the hook to be more testable, and write a test for the fix in 12cc0caf054d',
- u'node': u'9c7bc068df88',
- u'parents': [u'12cc0caf054d'],
- u'raw_author': u'Antonio Cuni <anto.cuni at gmail.com>',
- u'raw_node': u'9c7bc068df8850f4102c610d2bee3cdef67b30e6',
- u'revision': 391,
- u'size': 753,
- u'timestamp': u'2010-12-19 14:45:44'}]
-
-
- test_payload[u'commits'] = commits
-
-## # To regenerate:
-## try:
-## from json import loads # 2.6
-## except ImportError:
-## from simplejson import loads
-##
-## from urllib2 import urlopen
-## url = ("https://api.bitbucket.org/1.0/repositories/pypy/buildbot/"
-## "changesets/%s/")
-##
-## # Representative changesets
-## mergeheads = u'00ae063c6b8c'
-## singlefilesub = u'e17583fbfa5c'
-## root = u'5cbd6e289c04'
-## multiadd = u'9c7bc068df88'
-## test_nodes = mergeheads, singlefilesub, root, multiadd
-##
-## commits = []
-## for commit in test_nodes:
-## req = urlopen(url % commit)
-## payload = req.read()
-## req.close()
-## commits.append(loads(payload))
-##
-## test_payload['commits'] = commits
-
- LOCAL_REPOS = py.path.local(repopath)
-
- hook = BitbucketHookHandler()
- hook.USE_COLOR_CODES = False
- hook.handle(test_payload, test=True)
+def handle(payload, test=False):
+ path = payload['repository']['absolute_url']
+ owner = payload['repository']['owner']
+ local_repo = app.config['LOCAL_REPOS'].join(path)
+ remote_repo = app.config['REMOTE_BASE'] + path
+ if not check_for_local_repo(local_repo, remote_repo, owner):
+ print >> sys.stderr, 'Ignoring unknown repo', path
+ return
+ scm.hg('pull', '-R', local_repo)
+ for commit in get_commits(payload):
+ for handler in HANDLERS:
+ handler(payload, commit, test)
diff --git a/bitbucket_hook/test/test_hook.py b/bitbucket_hook/test/test_hook.py
--- a/bitbucket_hook/test/test_hook.py
+++ b/bitbucket_hook/test/test_hook.py
@@ -1,144 +1,31 @@
# -*- encoding: utf-8 -*-
+import py
+import pytest
+from bitbucket_hook import hook, scm, mail, irc
-from bitbucket_hook.hook import BitbucketHookHandler, getpaths
+#XXX
+hook.app.config['USE_COLOR_CODES'] = False
-class BaseHandler(BitbucketHookHandler):
-
- def __init__(self):
- self.mails = []
-
- def send(self, from_, to, subject, body, test=False):
- self.mails.append((from_, to, subject, body))
-
-
-def test_non_ascii_encoding_guess_utf8():
- class MyHandler(BaseHandler):
- def _hgexe(self, argv):
- return u'späm'.encode('utf-8'), '', 0
- #
- handler = MyHandler()
- stdout = handler.hg('foobar')
- assert type(stdout) is unicode
- assert stdout == u'späm'
-
-def test_non_ascii_encoding_invalid_utf8():
- class MyHandler(BaseHandler):
- def _hgexe(self, argv):
- return '\xe4aa', '', 0 # invalid utf-8 string
- #
- handler = MyHandler()
- stdout = handler.hg('foobar')
- assert type(stdout) is unicode
- assert stdout == u'\ufffdaa'
def test_sort_commits():
- class MyHandler(BaseHandler):
- def __init__(self):
- self.sent_commits = []
- def send_diff_for_commit(self, commit, test=False):
- self.sent_commits.append(commit['node'])
#
- handler = MyHandler()
- handler.payload = {
- 'commits': [{'revision': 43, 'node': 'second'},
- {'revision': 42, 'node': 'first'}]
- }
- handler.handle_diff_email()
- assert handler.sent_commits == ['first', 'second']
+ seen_nodes = set()
+ payload = {
+ 'commits': [
+ {'revision': 43, 'node': 'second', 'raw_node': 'first'},
+ {'revision': 42, 'node': 'first', 'raw_node': 'second'},
+ ],
+ }
+ commits = hook.get_commits(payload, seen_nodes)
+ commits = [x['node'] for x in commits]
-def test_getpaths():
- d = dict
+ assert commits == ['first', 'second']
- barefile = [d(file='file')]
- distinct = [d(file='path1/file1'), d(file='path2/file2'),
- d(file='path3/file')]
- shared = [d(file='path/file1'), d(file='path/file2'),
- d(file='path/file')]
- deepfile = [d(file='a/long/path/to/deepfile.py')]
- slashesfile = [d(file='/slashesfile/')]
- slashleft = [d(file='/slashleft')]
- slashright = [d(file='slashright/')]
+LONG_MESSAGE = u'This is a test with a long message: ' + 'x' * 1000
+LONG_CUT = LONG_MESSAGE[:160 - 29]
- nocommon = distinct + [d(file='path4/file')]
- nocommonplusroot = distinct + barefile
-
- common = [d(file='some/path/to/file'), d(file='some/path/to/deeper/file'),
- d(file='some/path/to/anotherfile'), d(file='some/path/to/afile')]
- commonplusroot = shared + barefile
-
- empty = d(file='')
- nocommonplusempty = distinct + [empty]
- commonplusempty = shared + [empty]
- nocommonplusslash = distinct + [d(file='path4/dir/')]
- commonplusslash = shared + [d(file='path/dir/')]
-
- pypydoubleslash = [d(file='pypy/jit/metainterp/opt/u.py'),
- d(file='pypy/jit/metainterp/test/test_c.py'),
- d(file='pypy/jit/metainterp/test/test_o.py')]
-
- pypyempty = [d(file='pypy/rlib/rdtoa.py'),
- d(file='pypy/rlib/test/test_rdtoa.py')]
-
- nothing = ('', '')
-
- # (input, expected output) for listfiles=False
- files_expected = [([], nothing),
- ([empty], nothing),
- ([empty, empty], nothing),
- (barefile, ('file', '')),
- (deepfile, ('a/long/path/to/deepfile.py', '')),
- (slashesfile, ('/slashesfile/', '')),
- (slashleft, ('/slashleft', '')),
- (slashright, ('slashright/', '')),
- (nocommon, nothing),
- (nocommonplusroot, nothing),
- (nocommonplusempty, nothing),
- (common, ('some/path/to/', '')),
- (commonplusroot, nothing),
- (commonplusempty, nothing),
- (nocommonplusslash, nothing),
- (commonplusslash, ('path/', '')),
- (pypydoubleslash, ('pypy/jit/metainterp/', '')),
- (pypyempty, ('pypy/rlib/', '')),
- ]
-
- for f, wanted in files_expected:
- assert getpaths(f) == wanted
-
- # (input, expected output) for listfiles=True
- files_expected = [([], nothing),
- ([empty], nothing),
- ([empty, empty], nothing),
- (barefile, ('file', '')),
- (deepfile, ('a/long/path/to/deepfile.py', '')),
- (slashesfile, ('/slashesfile/', '')),
- (slashleft, ('/slashleft', '')),
- (slashright, ('slashright/', '')),
- (nocommon, ('', ' M(file1, file2, file, file)')),
- (nocommonplusroot, ('', ' M(file1, file2, file, file)')),
- (nocommonplusempty, ('',' M(file1, file2, file)')),
- (common, ('some/path/to/',
- ' M(file, file, anotherfile, afile)')),
- (commonplusroot, ('', ' M(file1, file2, file, file)')),
- (commonplusempty, ('',' M(file1, file2, file)')),
- (nocommonplusslash, ('',' M(file1, file2, file)')),
- (commonplusslash, ('path/',' M(file1, file2, file)')),
- (pypydoubleslash, ('pypy/jit/metainterp/',
- ' M(u.py, test_c.py, test_o.py)')),
- (pypyempty, ('pypy/rlib/',
- ' M(rdtoa.py, test_rdtoa.py)')),
- ]
-
- for f, wanted in files_expected:
- assert getpaths(f, listfiles=True) == wanted
-
-
-
-LONG_MESSAGE = u'This is a test with a long message: ' + 'x'*1000
-LONG_CUT = LONG_MESSAGE[:160-29]
-
def irc_cases(payload=None):
if payload is None:
@@ -154,12 +41,12 @@
d(file='my/file3')]
single_file_deep = [d(file='path/to/single')]
- cases = [(no_file, ''), # No diff
- (single_file,'single'), # Single file
+ cases = [(no_file, ''), # No diff
+ (single_file, 'single'), # Single file
(multiple_files, ''), # No common prefix
- (multiple_files_subdir, 'path/'), # Common prefix
- (multiple_files_subdir_root, ''), # No common subdir, file in root
- (single_file_deep,'path/to/single') # Single file in deep path
+ (multiple_files_subdir, 'path/'), # Common prefix
+ (multiple_files_subdir_root, ''), # No common subdir file in root
+ (single_file_deep, 'path/to/single'), # Single file in deep path
]
author = u'antocuni'
@@ -171,7 +58,7 @@
for i, (case, snippet) in enumerate(cases):
rev = 44 + i
- node = chr(97+i) + 'xxyyy'
+ node = chr(97 + i) + 'xxyyy'
raw_node = node * 2
expected.append(expected_template % (node, snippet, LONG_CUT))
commits.append(d(revision=rev, files=case, author=author,
@@ -181,51 +68,64 @@
return payload, expected
-def test_irc_message():
- class MyHandler(BaseHandler):
- USE_COLOR_CODES = False
- def __init__(self):
- self.messages = []
- def send_irc_message(self, message, test=False):
- self.messages.append(message)
+def test_irc_message(monkeypatch, messages):
+ payload = {
+ 'repository': {
+ 'owner': 'pypy',
+ 'name': 'pypy',
+ },
+ 'commits': [
+ {
+ 'revision': 42,
+ 'branch': u'default',
+ 'author': u'antocuni',
+ 'message': u'this is a test',
+ 'node': 'abcdef',
+ 'raw_node': 'abcdef',
+ },
+ {
+ 'revision': 43,
+ 'author': u'antocuni',
+ 'branch': u'mybranch',
+ 'message': LONG_MESSAGE,
+ 'node': 'xxxyyy',
+ 'raw_node': 'xxxyyy',
+ },
+ ]
+ }
- handler = MyHandler()
- handler.payload = {
- 'commits': [{'revision': 42,
- 'branch': u'default',
- 'author': u'antocuni',
- 'message': u'this is a test',
- 'node': 'abcdef'
- },
- {'revision': 43,
- 'author': u'antocuni',
- 'branch': u'mybranch',
- 'message': LONG_MESSAGE,
- 'node': 'xxxyyy'
- }
- ]}
+ payload, expected = irc_cases(payload)
+ commits = payload['commits']
+ irc.handle_commit(payload, commits[0])
+ irc.handle_commit(payload, commits[1])
- handler.payload, expected = irc_cases(handler.payload)
- handler.handle_irc_message()
-
- msg1, msg2 = handler.messages[:2]
+ msg1, msg2 = messages[:2]
assert msg1 == 'antocuni default abcdef /: this is a test'
x = 'antocuni mybranch xxxyyy /: %s...' % LONG_CUT
assert msg2 == x
- for got, wanted in zip(handler.messages[2:], expected):
+ for got, wanted in zip(messages[2:], expected):
assert got == wanted
-def noop(*args, **kwargs): pass
+
+def noop(*args, **kwargs):
+ pass
+
+
class mock:
__init__ = noop
- def communicate(*args, **kwargs): return '1', 2
- def wait(*args, **kwargs): return 0
+
+ def communicate(*args, **kwargs):
+ return '1', 2
+
+ def wait(*args, **kwargs):
+ return 0
+
sendmail = noop
-def test_handle():
- handler = BitbucketHookHandler()
+
+def test_handle(monkeypatch):
commits, _ = irc_cases()
test_payload = {u'repository': {u'absolute_url': '',
u'name': u'test',
@@ -235,14 +135,57 @@
u'user': u'antocuni',
'commits': commits['commits']}
- handler.call_subprocess = noop
- handler.Popen = mock
- handler.SMTP = mock
+ monkeypatch.setattr(scm, 'Popen', mock)
+ monkeypatch.setattr(irc.subprocess, 'call', noop)
+ monkeypatch.setattr(mail, 'SMTP', mock)
- handler.handle(test_payload)
- handler.handle(test_payload, test=True)
+ hook.handle(test_payload)
+ hook.handle(test_payload, test=True)
- handler.LISTFILES = True
- handler.handle(test_payload)
- handler.handle(test_payload, test=True)
+ hook.app.config['LISTFILES'] = True
+ hook.handle(test_payload)
+ hook.handle(test_payload, test=True)
+
+def test_handle_unknown(monkeypatch):
+ hgcalls = []
+ def hgraise(*args):
+ hgcalls.append(args)
+
+ monkeypatch.setattr(scm, 'hg', hgraise)
+ hook.handle({
+ u'repository': {
+ u'absolute_url': '/foobar/myrepo',
+ u'owner': 'foobar',
+ },
+ })
+ assert hgcalls == []
+
+ hook.handle({
+ u'repository': {
+ u'absolute_url': '/pypy/myrepo',
+ u'owner': 'pypy'
+ },
+ u'commits': [],
+ })
+ assert hgcalls[0][:2] == ('clone', 'http://bitbucket.org/pypy/myrepo',)
+ local_repo = hgcalls[0][-1]
+ assert hgcalls[1] == ('pull', '-R', local_repo)
+
+
+def test_ignore_duplicate_commits(monkeypatch, mails, messages):
+ commits, _ = irc_cases()
+ payload = {u'repository': {u'absolute_url': '',
+ u'name': u'test',
+ u'owner': u'antocuni',
+ u'slug': u'test',
+ u'website': u''},
+ u'user': u'antocuni',
+ 'commits': commits['commits']}
+ seen_nodes = set()
+ commits_listed = list(hook.get_commits(payload, seen_nodes))
+ commits_again = list(hook.get_commits(payload, seen_nodes))
+ num_commits = len(commits['commits'])
+ assert len(commits_listed) == num_commits
+ assert not commits_again
+
diff --git a/bitbucket_hook/test/test_main.py b/bitbucket_hook/test/test_main.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/test/test_main.py
@@ -0,0 +1,32 @@
+from bitbucket_hook.main import app
+from bitbucket_hook import hook
+
+def test_get():
+ client = app.test_client()
+ response = client.get('/')
+
+
+def test_post(monkeypatch):
+ client = app.test_client()
+ def handle(payload, test):
+ assert payload=={}
+ assert test==app.config['TESTING']
+ monkeypatch.setattr(hook, 'handle', handle)
+
+ app.config['TESTING'] = True
+ response = client.post('/', data={'payload':"{}"})
+
+ app.config['TESTING'] = False
+ response = client.post('/', data={'payload':"{}"})
+
+ assert response.status_code == 200
+
+def test_post_error(monkeypatch):
+ client = app.test_client()
+ def handle(payload, test):
+ raise Exception('omg')
+ monkeypatch.setattr(hook, 'handle', handle)
+ response = client.post('/', data={'payload':"{}"})
+ assert response.status_code == 500
+
+
diff --git a/bitbucket_hook/test/conftest.py b/bitbucket_hook/test/conftest.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/test/conftest.py
@@ -0,0 +1,27 @@
+#XXX imports in conftest globals dont sow in coverage reports
+
+
+def pytest_funcarg__mails(request):
+ return []
+
+
+def pytest_funcarg__messages(request):
+ return []
+
+
+def pytest_funcarg__monkeypatch(request):
+ from bitbucket_hook import irc, mail
+ mp = request.getfuncargvalue('monkeypatch')
+ mails = request.getfuncargvalue('mails')
+
+ def send(from_, to, subject, body, test=False, mails=mails):
+ mails.append((from_, to, subject, body))
+ mp.setattr(mail, 'send', send)
+
+ messages = request.getfuncargvalue('messages')
+
+ def send_irc_message(message, test=False):
+ messages.append(message)
+ mp.setattr(irc, 'send_message', send_irc_message)
+
+ return mp
diff --git a/bitbucket_hook/test_hook_testcall.py b/bitbucket_hook/test_hook_testcall.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/test_hook_testcall.py
@@ -0,0 +1,109 @@
+import os
+import py
+
+
+def test_handlecall():
+ from bitbucket_hook.hook import handle
+ from bitbucket_hook.main import app
+ repopath = os.path.dirname(os.path.dirname(__file__))
+ print 'Repository path:', repopath
+ test_payload = {u'repository': {u'absolute_url': '',
+ u'name': u'test',
+ u'owner': u'antocuni',
+ u'slug': u'test',
+ u'website': u''},
+ u'user': u'antocuni'}
+
+ commits = [{u'author': u'arigo',
+ u'branch': u'default',
+ u'files': [],
+ u'message': u'Merge heads.',
+ u'node': u'00ae063c6b8c',
+ u'parents': [u'278760e9c560', u'29f1ff96548d'],
+ u'raw_author': u'Armin Rigo <arigo at tunes.org>',
+ u'raw_node': u'00ae063c6b8c13d873d92afc5485671f6a944077',
+ u'revision': 403,
+ u'size': 0,
+ u'timestamp': u'2011-01-09 13:07:24'},
+
+ {u'author': u'antocuni',
+ u'branch': u'default',
+ u'files': [{u'file': u'bitbucket_hook/hook.py',
+ u'type': u'modified'}],
+ u'message': u"don't send newlines to irc",
+ u'node': u'e17583fbfa5c',
+ u'parents': [u'69e9eac01cf6'],
+ u'raw_author': u'Antonio Cuni <anto.cuni at gmail.com>',
+ u'raw_node': u'e17583fbfa5c5636b5375a5fc81f3d388ce1b76e',
+ u'revision': 399,
+ u'size': 19,
+ u'timestamp': u'2011-01-07 17:42:13'},
+
+ {u'author': u'antocuni',
+ u'branch': u'default',
+ u'files': [{u'file': u'.hgignore', u'type': u'added'}],
+ u'message': u'ignore irrelevant files',
+ u'node': u'5cbd6e289c04',
+ u'parents': [u'3a7c89443fc8'],
+ u'raw_author': u'Antonio Cuni <anto.cuni at gmail.com>',
+ u'raw_node': u'5cbd6e289c043c4dd9b6f55b5ec1c8d05711c6ad',
+ u'revision': 362,
+ u'size': 658,
+ u'timestamp': u'2010-11-04 16:34:31'},
+
+ {u'author': u'antocuni',
+ u'branch': u'default',
+ u'files': [{u'file': u'bitbucket_hook/hook.py',
+ u'type': u'modified'},
+ {u'file': u'bitbucket_hook/__init__.py',
+ u'type': u'added'},
+ {u'file': u'bitbucket_hook/test/__init__.py',
+ u'type': u'added'},
+ {u'file': u'bitbucket_hook/test/test_hook.py',
+ u'type': u'added'}],
+ u'message': u'partially refactor the hook to be more testable,'
+ u' and write a test for the fix in 12cc0caf054d',
+ u'node': u'9c7bc068df88',
+ u'parents': [u'12cc0caf054d'],
+ u'raw_author': u'Antonio Cuni <anto.cuni at gmail.com>',
+ u'raw_node': u'9c7bc068df8850f4102c610d2bee3cdef67b30e6',
+ u'revision': 391,
+ u'size': 753,
+ u'timestamp': u'2010-12-19 14:45:44'}]
+
+ test_payload[u'commits'] = commits
+
+## # To regenerate:
+## try:
+## from json import loads # 2.6
+## except ImportError:
+## from simplejson import loads
+##
+## from urllib2 import urlopen
+## url = ("https://api.bitbucket.org/1.0/repositories/pypy/buildbot/"
+## "changesets/%s/")
+##
+## # Representative changesets
+## mergeheads = u'00ae063c6b8c'
+## singlefilesub = u'e17583fbfa5c'
+## root = u'5cbd6e289c04'
+## multiadd = u'9c7bc068df88'
+## test_nodes = mergeheads, singlefilesub, root, multiadd
+##
+## commits = []
+## for commit in test_nodes:
+## req = urlopen(url % commit)
+## payload = req.read()
+## req.close()
+## commits.append(loads(payload))
+##
+## test_payload['commits'] = commits
+
+ app.config['LOCAL_REPOS'] = py.path.local(repopath)
+ app.config['USE_COLOR_CODES'] = False
+
+ handle(test_payload, test=True)
+
+
+if __name__ == '__main__':
+ test_handlecall()
diff --git a/bitbucket_hook/test/test_scm.py b/bitbucket_hook/test/test_scm.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/test/test_scm.py
@@ -0,0 +1,34 @@
+# -*- encoding: utf-8 -*-
+import py
+import pytest
+
+from bitbucket_hook import scm
+
+
+def test_non_ascii_encoding_guess_utf8(monkeypatch):
+
+ def _hgexe(argv):
+ return u'späm'.encode('utf-8'), '', 0
+ monkeypatch.setattr(scm, '_hgexe', _hgexe)
+ stdout = scm.hg('foobar')
+ assert type(stdout) is unicode
+ assert stdout == u'späm'
+
+
+def test_non_ascii_encoding_invalid_utf8(monkeypatch):
+
+ def _hgexe(argv):
+ return '\xe4aa', '', 0 # invalid utf-8 string
+ monkeypatch.setattr(scm, '_hgexe', _hgexe)
+ stdout = scm.hg('foobar')
+ assert type(stdout) is unicode
+ assert stdout == u'\ufffdaa'
+
+
+ at pytest.mark.skip_if("not py.path.local.sysfind('hg')",
+ reason='hg binary missing')
+def test_hg():
+ scm.hg('help')
+ with pytest.raises(Exception):
+ print scm.hg
+ scm.hg('uhmwrong')
diff --git a/bitbucket_hook/run.py b/bitbucket_hook/run.py
new file mode 100755
--- /dev/null
+++ b/bitbucket_hook/run.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python
+
+"""
+To start the server in production mode, run this command::
+
+ ./run.py deploy
+"""
+
+import py
+import sys
+import argparse
+main = py.path.local(__file__).dirpath().join('main.py').pyimport()
+
+
+if __name__ == '__main__':
+ HOST_NAME = 'codespeak.net'
+ PORT_NUMBER = 9237
+ main.app.run(
+ host = HOST_NAME if 'deploy' in sys.argv else 'localhost',
+ debug = 'debug' in sys.argv,
+ port=PORT_NUMBER)
+
diff --git a/bitbucket_hook/irc.py b/bitbucket_hook/irc.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/irc.py
@@ -0,0 +1,108 @@
+'''
+utilities for interacting with the irc bot (via cli)
+'''
+
+import os
+import subprocess
+
+def getpaths(files, listfiles=False):
+
+ # Handle empty input
+ if not files:
+ return '', ''
+ files = [f['file'] for f in files]
+ if not any(files):
+ return '', ''
+
+ dirname = os.path.dirname
+ basename = os.path.basename
+
+ common_prefix = [dirname(f) for f in files]
+
+ # Single file, show its full path
+ if len(files) == 1:
+ common_prefix = files[0]
+ listfiles = False
+
+ else:
+ common_prefix = [path.split(os.sep) for path in common_prefix]
+ common_prefix = os.sep.join(os.path.commonprefix(common_prefix))
+ if common_prefix and not common_prefix.endswith('/'):
+ common_prefix += '/'
+
+ if listfiles:
+ # XXX Maybe should return file paths relative to prefix? Or TMI?
+ filenames = [basename(f) for f in files if f and basename(f)]
+ filenames = ' M(%s)' % ', '.join(filenames)
+ else:
+ filenames = ''
+ return common_prefix, filenames
+
+
+def send_message(message, test=False):
+ if test:
+ print message + '\n'
+ else:
+ from .main import app
+ return subprocess.call([
+ app.config['BOT'],
+ app.config['CHANNEL'],
+ message,
+ ])
+
+def get_short_id(owner, repo, branch):
+ """
+ Custom rules to get a short string that identifies a repo/branch in a
+ useful way, for IRC messages. Look at test_irc.test_get_short_id for what
+ we expect.
+ """
+ from .main import app
+ repo_parts = []
+ if owner != app.config['DEFAULT_USER']:
+ repo_parts.append('%s' % owner)
+ if repo_parts or repo != app.config['DEFAULT_REPO']:
+ repo_parts.append(repo)
+ repo_id = '/'.join(repo_parts)
+ #
+ if repo_id == '':
+ return branch
+ elif branch == 'default':
+ return repo_id
+ elif repo_id == branch:
+ return repo_id # e.g., pypy/extradoc has a branch extradoc, just return 'extradoc'
+ else:
+ return '%s[%s]' % (repo_id, branch)
+ return branch
+
+
+def handle_commit(payload, commit, test=False):
+ from .main import app
+
+ repo_owner = payload['repository']['owner']
+ repo_name = payload['repository']['name']
+ author = commit['author']
+ branch = commit['branch']
+ node = commit['node']
+ short_id = get_short_id(repo_owner, repo_name, branch)
+
+ files = commit.get('files', [])
+ common_prefix, filenames = getpaths(files, app.config['LISTFILES'])
+ pathlen = len(common_prefix) + len(filenames) + 2
+ common_prefix = '/' + common_prefix
+
+ if app.config['USE_COLOR_CODES']:
+ author = '\x0312%s\x0F' % author # in blue
+ short_id = '\x02%s\x0F' % short_id # in bold
+ node = '\x0311%s\x0F' % node # in azure
+ common_prefix = '\x0315%s\x0F' % common_prefix # in gray
+
+ message = commit['message'].replace('\n', ' ')
+ fields = (author, short_id, node, common_prefix, filenames)
+ part1 = '%s %s %s %s%s: ' % fields
+ totallen = 160 + pathlen
+ if len(message) + len(part1) <= totallen:
+ irc_msg = part1 + message
+ else:
+ maxlen = totallen - (len(part1) + 3)
+ irc_msg = part1 + message[:maxlen] + '...'
+ send_message(irc_msg, test)
diff --git a/bitbucket_hook/stdoutlog.py b/bitbucket_hook/stdoutlog.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/stdoutlog.py
@@ -0,0 +1,21 @@
+import time
+
+RED = 31
+GREEN = 32
+YELLOW = 33
+BLUE = 34
+MAGENTA = 35
+CYAN = 36
+GRAY = 37
+
+def color(s, fg=1, bg=1):
+ template = '\033[%02d;%02dm%s\033[0m'
+ return template % (bg, fg, s)
+
+def handle_commit(payload, commit, test=False):
+ author = commit['author']
+ node = commit['node']
+ timestamp = commit.get('timestamp')
+ curtime = time.strftime('[%Y-%m-%d %H:%M]')
+ log = '%s %s %s %s' % (curtime, node, timestamp, author)
+ print color(log, fg=GREEN)
diff --git a/bitbucket_hook/mail.py b/bitbucket_hook/mail.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/mail.py
@@ -0,0 +1,57 @@
+from . import scm
+from smtplib import SMTP
+
+
+TEMPLATE = u"""\
+Author: {author}
+Branch: {branches}
+Changeset: r{rev}:{node|short}
+Date: {date|isodate}
+%(url)s
+
+Log:\t{desc|fill68|tabindent}
+
+"""
+
+def handle_commit(payload, commit, test=False):
+ from .main import app
+
+ path = payload['repository']['absolute_url']
+ local_repo = app.config['LOCAL_REPOS'].join(path)
+ remote_repo = app.config['REMOTE_BASE'] + path
+
+ hgid = commit['raw_node']
+ sender = commit['author'] + ' <commits-noreply at bitbucket.org>'
+ lines = commit['message'].splitlines()
+ line0 = lines and lines[0] or ''
+ reponame = payload['repository']['name']
+ # TODO: maybe include the modified paths in the subject line?
+ url = remote_repo + 'changeset/' + commit['node'] + '/'
+ template = TEMPLATE % {'url': url}
+ subject = '%s %s: %s' % (reponame, commit['branch'], line0)
+ body = scm.hg('-R', local_repo, 'log', '-r', hgid,
+ '--template', template)
+ diff = scm.get_diff(local_repo, hgid, commit['files'])
+ body = body + diff
+ send(sender, app.config['ADDRESS'], subject, body, test)
+
+
+def send(from_, to, subject, body, test=False):
+ from .main import app
+ from email.mime.text import MIMEText
+ # Is this a valid workaround for unicode errors?
+ body = body.encode('ascii', 'xmlcharrefreplace')
+ msg = MIMEText(body, _charset='utf-8')
+ msg['From'] = from_
+ msg['To'] = to
+ msg['Subject'] = subject
+ if test:
+ print '#' * 20
+ print "Email contents:\n"
+ print from_
+ print to
+ print msg.get_payload(decode=True)
+ else:
+ smtp = SMTP(app.config['SMTP_SERVER'], app.config['SMTP_PORT'])
+ smtp.sendmail(from_, [to], msg.as_string())
+
diff --git a/bot2/pypybuildbot/builds.py b/bot2/pypybuildbot/builds.py
--- a/bot2/pypybuildbot/builds.py
+++ b/bot2/pypybuildbot/builds.py
@@ -202,7 +202,9 @@
logfiles={'pytestLog': 'cpython.log'}))
if pypyjit:
- # upload nightly build, if we're running jit tests
+ # kill this step when the transition to test_pypy_c_new has been
+ # completed
+ # "old" test_pypy_c
self.addStep(PytestCmd(
description="pypyjit tests",
command=["python", "pypy/test_all.py",
@@ -210,6 +212,15 @@
"--resultlog=pypyjit.log",
"pypy/module/pypyjit/test"],
logfiles={'pytestLog': 'pypyjit.log'}))
+ #
+ # "new" test_pypy_c
+ self.addStep(PytestCmd(
+ description="pypyjit tests",
+ command=["pypy/translator/goal/pypy-c", "pypy/test_all.py",
+ "--resultlog=pypyjit_new.log",
+ "pypy/module/pypyjit/test_pypy_c"],
+ logfiles={'pytestLog': 'pypyjit_new.log'}))
+
if pypyjit:
kind = 'jit'
else:
diff --git a/bitbucket_hook/test/test_irc.py b/bitbucket_hook/test/test_irc.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/test/test_irc.py
@@ -0,0 +1,103 @@
+from bitbucket_hook import irc
+import subprocess
+
+def fl(*paths):
+ return [{'file': x} for x in paths]
+
+
+def pytest_generate_tests(metafunc):
+
+ barefile = fl('file')
+ distinct = fl('path1/file1', 'path2/file2', 'path3/file')
+ shared = fl('path/file1', 'path/file2', 'path/file')
+
+ deepfile = fl('a/long/path/to/deepfile.py')
+ slashesfile = fl('/slashesfile/')
+ slashleft = fl('/slashleft')
+ slashright = fl('slashright/')
+
+ nocommon = distinct + fl('path4/file')
+ nocommonplusroot = distinct + barefile
+
+ common = fl('some/path/to/file', 'some/path/to/deeper/file',
+ 'some/path/to/anotherfile', 'some/path/to/afile')
+ commonplusroot = shared + barefile
+
+ empty = fl('')
+ nocommonplusempty = distinct + empty
+ commonplusempty = shared + empty
+ nocommonplusslash = distinct + fl('path4/dir/')
+ commonplusslash = shared + fl('path/dir/')
+
+ pypydoubleslash = fl('pypy/jit/metainterp/opt/u.py',
+ 'pypy/jit/metainterp/test/test_c.py',
+ 'pypy/jit/metainterp/test/test_o.py')
+
+ pypyempty = fl('pypy/rlib/rdtoa.py', 'pypy/rlib/test/test_rdtoa.py')
+
+ nothing = ('', '')
+
+ expectations = [
+ ('null', [], nothing),
+ ('empty', empty, nothing),
+ ('empty*2', empty * 2, nothing),
+ ('bare', barefile, ('file', '')),
+ ('deep', deepfile, ('a/long/path/to/deepfile.py', '')),
+ ('slashes', slashesfile, ('/slashesfile/', '')),
+ ('slashleft', slashleft, ('/slashleft', '')),
+ ('slashright', slashright, ('slashright/', '')),
+ ('nocommon', nocommon, ('', ' M(file1, file2, file, file)')),
+ ('nocommon+root', nocommonplusroot,
+ ('', ' M(file1, file2, file, file)')),
+ ('nocommon+empty', nocommonplusempty, ('', ' M(file1, file2, file)')),
+ ('common', common, ('some/path/to/',
+ ' M(file, file, anotherfile, afile)')),
+ ('common+root', commonplusroot, ('', ' M(file1, file2, file, file)')),
+ ('common+empty', commonplusempty, ('', ' M(file1, file2, file)')),
+ ('nocommon+slash', nocommonplusslash, ('', ' M(file1, file2, file)')),
+ ('common+slash', commonplusslash, ('path/', ' M(file1, file2, file)')),
+ ('pypydoubledash', pypydoubleslash, ('pypy/jit/metainterp/',
+ ' M(u.py, test_c.py, test_o.py)')),
+ ('pypyempty', pypyempty, ('pypy/rlib/',
+ ' M(rdtoa.py, test_rdtoa.py)')),
+ ]
+
+ if metafunc.function.__name__ == 'test_getpaths':
+ for name, files, (common, listfiles) in expectations:
+ metafunc.addcall(id='list/' + name, funcargs={
+ 'files': files,
+ 'expected_common': common,
+ 'expected_listfiles': listfiles,
+ })
+ metafunc.addcall(id='nolist/' + name, funcargs={
+ 'files': files,
+ 'expected_common': common,
+ 'expected_listfiles': listfiles,
+ })
+
+
+def test_getpaths(files, expected_common, expected_listfiles):
+ common, files = irc.getpaths(files, listfiles=bool(expected_listfiles))
+ assert common == expected_common
+ assert files == expected_listfiles
+
+def test_send_message(monkeypatch):
+ monkeypatch.undo() # hack to get at the functions
+
+ # gets called in normal mode
+ monkeypatch.setattr(subprocess, 'call', lambda *k, **kw: None)
+ irc.send_message('test')
+
+ # doesnt get called in test mode
+ monkeypatch.setattr(subprocess, 'call', lambda: None)
+ irc.send_message('test', test=True)
+
+def test_get_short_id():
+ assert irc.get_short_id('pypy', 'pypy', 'default') == 'default'
+ assert irc.get_short_id('pypy', 'pypy', 'mybranch') == 'mybranch'
+ assert irc.get_short_id('pypy', 'buildbot', 'default') == 'buildbot'
+ assert irc.get_short_id('pypy', 'buildbot', 'mybranch') == 'buildbot[mybranch]'
+ assert irc.get_short_id('pypy', 'extradoc', 'extradoc') == 'extradoc'
+ #
+ assert irc.get_short_id('anto', 'pypy', 'default') == 'anto/pypy'
+ assert irc.get_short_id('anto', 'pypy', 'mybranch') == 'anto/pypy[mybranch]'
diff --git a/bitbucket_hook/scm.py b/bitbucket_hook/scm.py
new file mode 100644
--- /dev/null
+++ b/bitbucket_hook/scm.py
@@ -0,0 +1,36 @@
+import sys
+from subprocess import Popen, PIPE
+
+
+def _hgexe(argv):
+ proc = Popen(['hg'] + list(argv), stdout=PIPE, stderr=PIPE)
+ stdout, stderr = proc.communicate()
+ ret = proc.wait()
+ return stdout, stderr, ret
+
+
+def hg(*argv):
+ argv = map(str, argv)
+ stdout, stderr, ret = _hgexe(argv)
+ if ret != 0:
+ print >> sys.stderr, 'error: hg', ' '.join(argv)
+ print >> sys.stderr, stderr
+ raise Exception('error when executing hg')
+ return unicode(stdout, encoding='utf-8', errors='replace')
+
+
+def get_diff(local_repo, hgid, files):
+ import re
+ binary = re.compile('^GIT binary patch$', re.MULTILINE)
+ files = [item['file'] for item in files]
+ lines = []
+ for filename in files:
+ out = hg('-R', local_repo, 'diff', '--git', '-c', hgid,
+ local_repo.join(filename))
+ match = binary.search(out)
+ if match:
+ # it's a binary patch, omit the content
+ out = out[:match.end()]
+ out += u'\n[cut]'
+ lines.append(out)
+ return u'\n'.join(lines)
diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -2,6 +2,9 @@
*.pyc
*~
+# test coverage files
+.coverage
+
# master/slaveinfo.py contains the passwords, so it should never be tracked
master/slaveinfo.py
diff --git a/bot2/pypybuildbot/summary.py b/bot2/pypybuildbot/summary.py
--- a/bot2/pypybuildbot/summary.py
+++ b/bot2/pypybuildbot/summary.py
@@ -67,12 +67,18 @@
def populate_one(self, name, shortrepr, longrepr=None):
if shortrepr == '!':
namekey = [name, '']
- else:
- namekey = name.split(':', 1)
+ else:
+ # pytest2 and pytest1 use different separators/test id
+ # syntax support both here for now
+ if '.py::' in name:
+ namekey = name.split('::', 1)
+ else:
+ namekey = name.split(':', 1)
if namekey[0].endswith('.py'):
namekey[0] = namekey[0][:-3].replace('/', '.')
if len(namekey) == 1:
namekey.append('')
+ namekey[1] = namekey[1].replace("::", ".")
namekey = tuple(namekey)
self._outcomes[namekey] = shortrepr
@@ -106,7 +112,7 @@
kind = None
def add_one():
if kind is not None:
- self.populate_one(name, kind, ''.join(longrepr))
+ self.populate_one(name, kind, ''.join(longrepr))
for line in log.readlines():
first = line[0]
if first == ' ':
@@ -570,7 +576,7 @@
mod, testname = self.get_namekey(request)
if mod is None:
return "no such test"
- return "%s %s" % (mod, testname)
+ return "%s %s" % (mod, testname)
def body(self, request):
t0 = time.time()
@@ -660,7 +666,7 @@
request.site.buildbot_service.head_elements = old_head_elements
def getTitle(self, request):
- status = self.getStatus(request)
+ status = self.getStatus(request)
return "%s: summaries of last %d revisions" % (status.getProjectName(),
N)
diff --git a/bitbucket_hook/main.py b/bitbucket_hook/main.py
--- a/bitbucket_hook/main.py
+++ b/bitbucket_hook/main.py
@@ -7,57 +7,81 @@
codespeak.
"""
-import time
-import BaseHTTPServer
import json
-import cgi
import traceback
import pprint
import sys
+import flask
+import py
-from hook import BitbucketHookHandler
-HOST_NAME = 'codespeak.net'
-PORT_NUMBER = 9237
+app = flask.Flask(__name__)
-class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
- def do_GET(self):
- """Respond to a GET request."""
- self.send_response(200)
- self.send_header("Content-type", "text/html")
- self.end_headers()
- self.wfile.write("""
- <html>
- <p>This is the pypy bitbucket hook. Use the following form only for testing</p>
- <form method=post>
- payload: <input name=payload> <br>
- submit: <input type=submit>
- </form>
- </html>
- """)
- def do_POST(self):
- length = int(self.headers['Content-Length'])
- query_string = self.rfile.read(length)
- data = dict(cgi.parse_qsl(query_string))
- payload = json.loads(data['payload'])
- handler = BitbucketHookHandler()
- try:
- handler.handle(payload)
- except:
- traceback.print_exc()
- print >> sys.stderr, 'payload:'
- pprint.pprint(payload, sys.stderr)
- print >> sys.stderr
+ at app.route('/', methods=['GET'])
+def test_form():
+ """Respond to a GET request."""
+ return """
+ <html>
+ <p>
+ This is the pypy bitbucket hook.
+ Use the following form only for testing
+ </p>
+ <form method=post>
+ payload: <input name=payload> <br>
+ submit: <input type=submit>
+ </form>
+ </html>
+ """
-if __name__ == '__main__':
- server_class = BaseHTTPServer.HTTPServer
- httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
- print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER)
+
+ at app.route('/', methods=['POST'])
+def handle_payload():
+ payload = json.loads(flask.request.form['payload'])
try:
- httpd.serve_forever()
- except KeyboardInterrupt:
- pass
- httpd.server_close()
- print time.asctime(), "Server Stops - %s:%s" % (HOST_NAME, PORT_NUMBER)
+ from . import hook
+ hook.handle(payload, test=app.testing)
+ except:
+ traceback.print_exc()
+ print >> sys.stderr, 'payload:'
+ pprint.pprint(payload, sys.stderr)
+ print >> sys.stderr
+ raise
+ return 'ok'
+
+
+class DefaultConfig(object):
+ LOCAL_REPOS = py.path.local(__file__).dirpath('repos')
+ REMOTE_BASE = 'http://bitbucket.org'
+ USE_COLOR_CODES = True
+ LISTFILES = False
+ #
+ DEFAULT_USER = 'pypy'
+ DEFAULT_REPO = 'pypy'
+
+
+class CodeSpeakConfig(DefaultConfig):
+ SMTP_SERVER = 'localhost'
+ SMTP_PORT = 25
+ ADDRESS = 'pypy-svn at codespeak.net'
+ #
+ CHANNEL = '#pypy'
+ BOT = '/svn/hooks/commit-bot/message'
+
+
+class ViperConfig(DefaultConfig):
+ SMTP_SERVER = "out.alice.it"
+ SMTP_PORT = 25
+ ADDRESS = 'anto.cuni at gmail.com'
+ #
+ CHANNEL = '#test'
+ BOT = '/tmp/commit-bot/message'
+
+
+if py.std.socket.gethostname() == 'viper':
+ # for debugging, antocuni's settings
+ app.config.from_object(ViperConfig)
+else:
+ # real settings, (they works on codespeak at least)
+ app.config.from_object(CodeSpeakConfig)
More information about the Pypy-commit
mailing list