diff --git a/tools/gh_auth.py b/tools/gh_api.py similarity index 61% rename from tools/gh_auth.py rename to tools/gh_api.py index 60c1e6e..6b042ed 100644 --- a/tools/gh_auth.py +++ b/tools/gh_api.py @@ -7,7 +7,6 @@ except NameError: pass import requests -import keyring import getpass import json @@ -15,7 +14,14 @@ import json # password fake_username = 'ipython_tools' +token = None def get_auth_token(): + global token + + if token is not None: + return token + + import keyring token = keyring.get_password('github', fake_username) if token is not None: return token @@ -28,9 +34,11 @@ def get_auth_token(): auth_request = { "scopes": [ - "public_repo" + "public_repo", + "gist" ], - "note": "IPython tools" + "note": "IPython tools", + "note_url": "https://github.com/ipython/ipython/tree/master/tools", } response = requests.post('https://api.github.com/authorizations', auth=(user, pw), data=json.dumps(auth_request)) @@ -39,4 +47,34 @@ def get_auth_token(): keyring.set_password('github', fake_username, token) return token +def make_auth_header(): + return {'Authorization': 'token ' + get_auth_token()} + +def post_issue_comment(project, num, body): + url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num) + payload = json.dumps({'body': body}) + r = requests.post(url, data=payload, headers=make_auth_header()) + +def post_gist(content, description='', filename='file', auth=False): + """Post some text to a Gist, and return the URL.""" + post_data = json.dumps({ + "description": description, + "public": True, + "files": { + filename: { + "content": content + } + } + }).encode('utf-8') + + headers = make_auth_header() if auth else {} + response = requests.post("https://api.github.com/gists", data=post_data, headers=headers) + response.raise_for_status() + response_data = json.loads(response.text) + return response_data['html_url'] +def get_pull_request(project, num): + url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num) + response = requests.get(url) + response.raise_for_status() + return json.loads(response.text) diff --git a/tools/test_pr.py b/tools/test_pr.py index 3ce4766..de53fb1 100755 --- a/tools/test_pr.py +++ b/tools/test_pr.py @@ -10,14 +10,16 @@ from __future__ import print_function import errno from glob import glob +import io import json import os import re import requests import shutil from subprocess import call, check_call, check_output, PIPE, STDOUT, CalledProcessError +import sys -import gh_auth +import gh_api basedir = os.path.join(os.path.expanduser("~"), ".ipy_pr_tests") repodir = os.path.join(basedir, "ipython") @@ -66,10 +68,6 @@ def setup(): check_call(['git', 'pull', ipy_repository, 'master']) os.chdir(basedir) -def get_pull_request(num): - url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=gh_project, num=num) - return json.loads(requests.get(url).text) - missing_libs_re = re.compile(r"Tools and libraries NOT available at test time:\n" r"\s*(.*?)\n") def get_missing_libraries(log): @@ -108,22 +106,6 @@ def run_tests(venv): except CalledProcessError as e: return False, e.output.decode('utf-8') -def post_gist(content, description='IPython test log', filename="results.log"): - """Post some text to a Gist, and return the URL.""" - post_data = json.dumps({ - "description": description, - "public": True, - "files": { - filename: { - "content": content - } - } - }).encode('utf-8') - - response = requests.post("https://api.github.com/gists", data=post_data) - response_data = json.loads(response.text) - return response_data['html_url'] - def markdown_format(pr, results): def format_result(py, passed, gist_url, missing_libraries): s = "* %s: " % py @@ -149,18 +131,33 @@ def markdown_format(pr, results): def post_results_comment(pr, results, num): body = markdown_format(pr, results) - url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=gh_project, num=num) - payload = json.dumps({'body': body}) - auth_token = gh_auth.get_auth_token() - headers = {'Authorization': 'token ' + auth_token} - r = requests.post(url, data=payload, headers=headers) + gh_api.post_issue_comment(gh_project, num, body) +def print_results(pr, results): + print("\n") + if pr['mergeable']: + print("**Test results for commit %s merged into master**" % pr['head']['sha'][:7]) + else: + print("**Test results for commit %s (can't merge cleanly)**" % pr['head']['sha'][:7]) + print("Platform:", sys.platform) + for py, passed, gist_url, missing_libraries in results: + if passed: + print(py, ":", "OK") + else: + print(py, ":", "Failed") + print(" Test log:", gist_url) + if missing_libraries: + print(" Libraries not available:", missing_libraries) + print("Not available for testing:", ", ".join(unavailable_pythons)) -if __name__ == '__main__': - import sys - num = sys.argv[1] +def test_pr(num, post_results=True): + # Get Github authorisation first, so that the user is prompted straight away + # if their login is needed. + if post_results: + gh_api.get_auth_token() + setup() - pr = get_pull_request(num) + pr = gh_api.get_pull_request(gh_project, num) get_branch(repo=pr['head']['repo']['clone_url'], branch=pr['head']['ref'], owner=pr['head']['repo']['owner']['login'], @@ -174,23 +171,27 @@ if __name__ == '__main__': if passed: results.append((py, True, None, missing_libraries)) else: - gist_url = post_gist(log) - results.append((py, False, gist_url, missing_libraries)) + if post_results: + result_locn = gh_api.post_gist(log, description='IPython test log', + filename="results.log", auth=True) + else: + result_locn = os.path.join(venv, pr['head']['sha'][:7]+".log") + with io.open(result_locn, 'w', encoding='utf-8') as f: + f.write(log) + results.append((py, False, result_locn, missing_libraries)) - print("\n") - if pr['mergeable']: - print("**Test results for commit %s merged into master**" % pr['head']['sha'][:7]) - else: - print("**Test results for commit %s (can't merge cleanly)**" % pr['head']['sha'][:7]) - print("Platform:", sys.platform) - for py, passed, gist_url, missing_libraries in results: - if passed: - print(py, ":", "OK") - else: - print(py, ":", "Failed") - print(" Test log:", gist_url) - if missing_libraries: - print(" Libraries not available:", missing_libraries) - print("Not available for testing:", ", ".join(unavailable_pythons)) - post_results_comment(pr, results, num) - print("(Posted to Github)") + print_results(pr, results) + if post_results: + post_results_comment(pr, results, num) + print("(Posted to Github)") + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description="Test an IPython pull request") + parser.add_argument('-l', '--local', action='store_true', + help="Don't publish the results to Github") + parser.add_argument('number', type=int, help="The pull request number") + + args = parser.parse_args() + test_pr(args.number, post_results=(not args.local))