{"action":"created","comment":{"url":"https://api.github.com/repos/mono/mono/pulls/comments/48059180","id":48059180,"diff_hunk":"@@ -0,0 +1,364 @@\n+#!/usr/bin/python\n+\n+# Mimics GNU timeout, but does some fancy tracking based on custom features in mono nunit24.\n+\n+import argparse\n+import subprocess\n+import re\n+import signal\n+import time\n+import sys\n+import os.path\n+import copy\n+import tempfile\n+import calendar\n+import json\n+\n+### Constants\n+\n+# Here is how the communication with nunit works. It has to work with two constraints:\n+# - We don't invoke nunit directly. We invoke some Makefile which invokes some other Makefile\n+#   and at some point down the line someone calls nunit.\n+# - nunit has to be able to report back to us even if (especially if) it terminates improperly.\n+# To deal with all this, communication babysitter->nunit is done by environment variables,\n+# and communication nunit->babysitter is done by leaving behind files in known locations.\n+\n+# Filenames\n+\n+CURRENT_TEST_FILE = \"babysitter_report_current_test_file.txt\"\n+RAN_TEST_FILE     = \"babysitter_report_ran_test_file.txt\"\n+FAILED_TEST_FILE  = \"babysitter_report_failed_test_file.txt\"\n+LOGGING_FILE      = \"babysitter_report.json_lines\"\n+\n+# Environment keys\n+\n+CURRENT_TEST_KEY  = 'NUNIT_BABYSITTER_CURRENT_TEST_FILE' # Tell nunit where to leave files\n+RAN_TEST_KEY      = 'NUNIT_BABYSITTER_RAN_TEST_FILE'\n+FAILED_TEST_KEY   = 'NUNIT_BABYSITTER_FAILED_TEST_FILE'\n+RUN_KEY           = 'NUNIT_BABYSITTER_RUN_TEST' # Semicolon-separated list of test names\n+RUN_MODE_KEY      = 'NUNIT_BABYSITTER_RUN_MODE' # Equal to either RUN or AFTER\n+RETRY_KEY         = 'BABYSITTER_RETRY'          # Equal to an integer\n+CRASH_RESUME_KEY  = 'BABYSITTER_CRASH_RESUME'\n+VERBOSE_KEY       = 'BABYSITTER_VERBOSE'\n+LOGGING_DIR_KEY   = 'WORKSPACE'\n+\n+# JSON keys\n+\n+DATE_JSON       = 'date'          # POSIX timestamp of test run\n+INVOKE_JSON     = 'invocation'\n+COUNT_JSON      = 'passes'        # How many times was command executed?\n+LIMIT_JSON      = 'failure_max'\n+SUPPORT_JSON    = 'retry_support' # Was the test running with a babysitter-aware nunit?\n+FINAL_CODE_JSON = 'final_code'\n+TESTS_JSON      = 'tests'         # Holds dictionary of (test name)->(dict with TEST_ keys below)\n+TEST_FAILURES         = 'normal_failures'\n+TEST_CRASH_FAILURES   = 'crash_failures'\n+TEST_TIMEOUT_FAILURES = 'timeout_failures'\n+\n+### Interpret arguments\n+\n+scriptname = sys.argv[0]\n+\n+# This is very silly: This program needs to switch from argparse to manual parsing\n+# after the second positional argument, but argparse doesn't let you do this.\n+# I work around this with a custom subclass which can selectively swallow errors:\n+class Hesitate(Exception):\n+\tpass\n+class HesitantParser(argparse.ArgumentParser):\n+\tdef __init__(s, *args, **kwargs):\n+\t\ts.hesitating = True  # Errors will be swallowed until this is set false\n+\t\targparse.ArgumentParser.__init__(s, *args, **kwargs)\n+\tdef error(s, *args, **kwargs):\n+\t\tif s.hesitating:\n+\t\t\traise Hesitate() # Bail out before errors are printed.\n+\t\targparse.ArgumentParser.error(s, *args, **kwargs)\n+\n+# Define args\n+argparser = HesitantParser(description=\"\"\"\\\n+Run a test case with a timeout.\\n\n+Durations are floating point numbers followed by an optional unit:\\n\n+'s' for seconds (the default)\n+'m' for minutes\n+'h' for hours\n+'d' for days\\n\n+supported environment variables:\n+  %s: Directory path to save logs into\n+  %s: If set to a number, failed tests will be rerun this many times\n+  %s: If set, rerun even for tests which terminated early\n+  %s: If set, print extra logging during run\"\"\" %\n+\t\t(LOGGING_DIR_KEY, RETRY_KEY, CRASH_RESUME_KEY, VERBOSE_KEY),\n+\tformatter_class=argparse.RawTextHelpFormatter)\n+argparser.add_argument('-s', '--signal', dest='signal', metavar='signal', default='TERM',\n+\thelp=\"Send this signal to the command on timeout, instead of TERM.\")\n+argparser.add_argument('-k', '--kill-after-duration', dest='kill_after', metavar='duration',\n+\thelp=\"If process continues running after signal, send KILL after this duration.\")\n+argparser.add_argument('duration',\n+\thelp=\"Time to run before sending signal.\")\n+argparser.add_argument('command', nargs=\"+\", help=\"Command+args to run.\")\n+\n+# Repeatedly parse the given args until we find the shortest prefix for which parsing succeeds.\n+argc = len(sys.argv)\n+extra_args = []\n+for limit in range(1,argc+1):\n+\ttry:\n+\t\tif limit == argc: # On the final pass, parse for real\n+\t\t\targparser.hesitating = False\n+\t\targs = argparser.parse_args(sys.argv[1:limit])\n+\t\t# If we're still here, parse_args succeeded.\n+\t\t# The final parsed arg is the command; remaining argv items are its args.\n+\t\textra_args = sys.argv[limit:]\n+\t\tbreak\n+\texcept Hesitate: # Before the final pass, if parse_args fails, skip\n+\t\tpass\n+\n+argparser.hesitating = False # Just act like a normal argparser from here\n+\n+durationre = re.compile(r'(\\d+)([smhd]?)')\n+def parse_duration(duration): # Accept units\n+\tmatch = durationre.match(duration)\n+\tif not match:\n+\t\targparser.error(\"Could not understand duration %s\" % duration)\n+\ttime, units = match.group(1), match.group(2)\n+\ttime = int(time)\n+\tif units == 'm':\n+\t\ttime *= 60\n+\telif units == 'h':\n+\t\ttime *= 60*60\n+\telif units == 'd':\n+\t\ttime *= 60*60*24\n+\treturn time\n+\n+def parse_signal(sig):        # Accept names\n+\tif sig.isdigit():\n+\t\treturn int(sig)\n+\tfor k,v in signal.__dict__.iteritems():\n+\t\tif k == (\"SIG%s\" % sig):\n+\t\t\treturn v\n+\targparser.error(\"Could not understand signal name %s\" % sig)\n+\n+# Final interpretation of arguments\n+duration = parse_duration(args.duration)\n+kill_after = parse_duration(args.kill_after) if args.kill_after is not None else None\n+timeout_signal = parse_signal(args.signal)\n+command = args.command + extra_args\n+\n+# Process environment\n+global_env = copy.deepcopy( os.environ )\n+\n+verbose = VERBOSE_KEY in global_env\n+logging = LOGGING_DIR_KEY in global_env\n+logging_dir = global_env[LOGGING_DIR_KEY] if logging else None\n+logfile = os.path.join(logging_dir, LOGGING_FILE) if logging else None\n+crash_resuming = CRASH_RESUME_KEY in global_env\n+failmax = int(global_env[RETRY_KEY]) if RETRY_KEY in global_env else 0\n+babysitting = logging or failmax\n+if babysitting:\n+\tbabysitter_dir = tempfile.mkdtemp()\n+\tglobal_env[CURRENT_TEST_KEY] = os.path.join(babysitter_dir, CURRENT_TEST_FILE)\n+\tglobal_env[RAN_TEST_KEY]     = os.path.join(babysitter_dir, RAN_TEST_FILE)\n+\tglobal_env[FAILED_TEST_KEY]  = os.path.join(babysitter_dir, FAILED_TEST_FILE)\n+\n+### Utility functions\n+\n+def wait(proc, duration):\n+\t# FIXME: If we can guarantee Python 3.3, Popen objects have a wait(timeout) method","path":"scripts/babysitter","position":164,"original_position":164,"commit_id":"1dc0598badaed161efa92fd261de7e555d1a2b13","original_commit_id":"1dc0598badaed161efa92fd261de7e555d1a2b13","user":{"login":"xmcclure","id":15091917,"avatar_url":"https://avatars.githubusercontent.com/u/15091917?v=3","gravatar_id":"","url":"https://api.github.com/users/xmcclure","html_url":"https://github.com/xmcclure","followers_url":"https://api.github.com/users/xmcclure/followers","following_url":"https://api.github.com/users/xmcclure/following{/other_user}","gists_url":"https://api.github.com/users/xmcclure/gists{/gist_id}","starred_url":"https://api.github.com/users/xmcclure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xmcclure/subscriptions","organizations_url":"https://api.github.com/users/xmcclure/orgs","repos_url":"https://api.github.com/users/xmcclure/repos","events_url":"https://api.github.com/users/xmcclure/events{/privacy}","received_events_url":"https://api.github.com/users/xmcclure/received_events","type":"User","site_admin":false},"body":"I think I was thinking that if ANY of our test machines at some point get python 3.3, we could add a `version_info` check and get improved (more precise) behavior on those machines.","created_at":"2015-12-18T19:18:44Z","updated_at":"2015-12-18T19:18:44Z","html_url":"https://github.com/mono/mono/pull/2366#discussion_r48059180","pull_request_url":"https://api.github.com/repos/mono/mono/pulls/2366","_links":{"self":{"href":"https://api.github.com/repos/mono/mono/pulls/comments/48059180"},"html":{"href":"https://github.com/mono/mono/pull/2366#discussion_r48059180"},"pull_request":{"href":"https://api.github.com/repos/mono/mono/pulls/2366"}}},"pull_request":{"url":"https://api.github.com/repos/mono/mono/pulls/2366","id":54134527,"html_url":"https://github.com/mono/mono/pull/2366","diff_url":"https://github.com/mono/mono/pull/2366.diff","patch_url":"https://github.com/mono/mono/pull/2366.patch","issue_url":"https://api.github.com/repos/mono/mono/issues/2366","number":2366,"state":"open","locked":false,"title":"Adds babysitter script for PR tests","user":{"login":"xmcclure","id":15091917,"avatar_url":"https://avatars.githubusercontent.com/u/15091917?v=3","gravatar_id":"","url":"https://api.github.com/users/xmcclure","html_url":"https://github.com/xmcclure","followers_url":"https://api.github.com/users/xmcclure/followers","following_url":"https://api.github.com/users/xmcclure/following{/other_user}","gists_url":"https://api.github.com/users/xmcclure/gists{/gist_id}","starred_url":"https://api.github.com/users/xmcclure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xmcclure/subscriptions","organizations_url":"https://api.github.com/users/xmcclure/orgs","repos_url":"https://api.github.com/users/xmcclure/repos","events_url":"https://api.github.com/users/xmcclure/events{/privacy}","received_events_url":"https://api.github.com/users/xmcclure/received_events","type":"User","site_admin":false},"body":"I see two problems with our current automated PR tests:\r\n- Many false negatives— PR tests often fail due to recurring failures unrelated to the PR\r\n- Information about what failed is awkward to retrieve, scattered in multiple places\r\n\r\nThis commit adds a Python script, `scripts/babysitter`. Currently our Jenkins test suites are each run wrapped with the `timeout` command. The babysitter acts as a drop-in replacement for GNU timeout but adds the following features:\r\n- Logs machine readable output about each test suite (as line-delimited json)\r\n- If the test suite uses NUnit, can detect if a test case failed or crashed (terminated mono in mid-test), and retry unsuccessful tests (up to a limit).\r\n\r\nThe reasoning here is that tests which fail inconsistently are currently most likely due to one of our outstanding recurring failures rather than the change made in the PR. Therefore, if a failing test\r\nsucceeds on retry, the PR itself is probably valid (although the failure should be logged and looked at).\r\n\r\nIn addition to the script itself, changes to NUnit were required in order to support the retry feature and allow fine-grained logging for NUnit suites.\r\n\r\nMajor TODOs:\r\n- Add retry support for our non-NUnit-based test suites\r\n- Save the XML files NUnit produces (since reruns stomp XMLs from previous runs)\r\n- Add some kind of sensible feature for dealing with timeouts","created_at":"2015-12-18T18:31:37Z","updated_at":"2015-12-18T19:18:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a136b86f32982cb0991c06b45498a2a233c0efa8","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mono/mono/pulls/2366/commits","review_comments_url":"https://api.github.com/repos/mono/mono/pulls/2366/comments","review_comment_url":"https://api.github.com/repos/mono/mono/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mono/mono/issues/2366/comments","statuses_url":"https://api.github.com/repos/mono/mono/statuses/1dc0598badaed161efa92fd261de7e555d1a2b13","head":{"label":"xmcclure:scripts-babysitter","ref":"scripts-babysitter","sha":"1dc0598badaed161efa92fd261de7e555d1a2b13","user":{"login":"xmcclure","id":15091917,"avatar_url":"https://avatars.githubusercontent.com/u/15091917?v=3","gravatar_id":"","url":"https://api.github.com/users/xmcclure","html_url":"https://github.com/xmcclure","followers_url":"https://api.github.com/users/xmcclure/followers","following_url":"https://api.github.com/users/xmcclure/following{/other_user}","gists_url":"https://api.github.com/users/xmcclure/gists{/gist_id}","starred_url":"https://api.github.com/users/xmcclure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xmcclure/subscriptions","organizations_url":"https://api.github.com/users/xmcclure/orgs","repos_url":"https://api.github.com/users/xmcclure/repos","events_url":"https://api.github.com/users/xmcclure/events{/privacy}","received_events_url":"https://api.github.com/users/xmcclure/received_events","type":"User","site_admin":false},"repo":{"id":44119985,"name":"mono","full_name":"xmcclure/mono","owner":{"login":"xmcclure","id":15091917,"avatar_url":"https://avatars.githubusercontent.com/u/15091917?v=3","gravatar_id":"","url":"https://api.github.com/users/xmcclure","html_url":"https://github.com/xmcclure","followers_url":"https://api.github.com/users/xmcclure/followers","following_url":"https://api.github.com/users/xmcclure/following{/other_user}","gists_url":"https://api.github.com/users/xmcclure/gists{/gist_id}","starred_url":"https://api.github.com/users/xmcclure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xmcclure/subscriptions","organizations_url":"https://api.github.com/users/xmcclure/orgs","repos_url":"https://api.github.com/users/xmcclure/repos","events_url":"https://api.github.com/users/xmcclure/events{/privacy}","received_events_url":"https://api.github.com/users/xmcclure/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/xmcclure/mono","description":"Mono open source ECMA CLI, C# and .NET implementation.","fork":true,"url":"https://api.github.com/repos/xmcclure/mono","forks_url":"https://api.github.com/repos/xmcclure/mono/forks","keys_url":"https://api.github.com/repos/xmcclure/mono/keys{/key_id}","collaborators_url":"https://api.github.com/repos/xmcclure/mono/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/xmcclure/mono/teams","hooks_url":"https://api.github.com/repos/xmcclure/mono/hooks","issue_events_url":"https://api.github.com/repos/xmcclure/mono/issues/events{/number}","events_url":"https://api.github.com/repos/xmcclure/mono/events","assignees_url":"https://api.github.com/repos/xmcclure/mono/assignees{/user}","branches_url":"https://api.github.com/repos/xmcclure/mono/branches{/branch}","tags_url":"https://api.github.com/repos/xmcclure/mono/tags","blobs_url":"https://api.github.com/repos/xmcclure/mono/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/xmcclure/mono/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/xmcclure/mono/git/refs{/sha}","trees_url":"https://api.github.com/repos/xmcclure/mono/git/trees{/sha}","statuses_url":"https://api.github.com/repos/xmcclure/mono/statuses/{sha}","languages_url":"https://api.github.com/repos/xmcclure/mono/languages","stargazers_url":"https://api.github.com/repos/xmcclure/mono/stargazers","contributors_url":"https://api.github.com/repos/xmcclure/mono/contributors","subscribers_url":"https://api.github.com/repos/xmcclure/mono/subscribers","subscription_url":"https://api.github.com/repos/xmcclure/mono/subscription","commits_url":"https://api.github.com/repos/xmcclure/mono/commits{/sha}","git_commits_url":"https://api.github.com/repos/xmcclure/mono/git/commits{/sha}","comments_url":"https://api.github.com/repos/xmcclure/mono/comments{/number}","issue_comment_url":"https://api.github.com/repos/xmcclure/mono/issues/comments{/number}","contents_url":"https://api.github.com/repos/xmcclure/mono/contents/{+path}","compare_url":"https://api.github.com/repos/xmcclure/mono/compare/{base}...{head}","merges_url":"https://api.github.com/repos/xmcclure/mono/merges","archive_url":"https://api.github.com/repos/xmcclure/mono/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/xmcclure/mono/downloads","issues_url":"https://api.github.com/repos/xmcclure/mono/issues{/number}","pulls_url":"https://api.github.com/repos/xmcclure/mono/pulls{/number}","milestones_url":"https://api.github.com/repos/xmcclure/mono/milestones{/number}","notifications_url":"https://api.github.com/repos/xmcclure/mono/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/xmcclure/mono/labels{/name}","releases_url":"https://api.github.com/repos/xmcclure/mono/releases{/id}","created_at":"2015-10-12T16:32:27Z","updated_at":"2015-10-12T16:35:38Z","pushed_at":"2015-12-18T18:30:48Z","git_url":"git://github.com/xmcclure/mono.git","ssh_url":"git@github.com:xmcclure/mono.git","clone_url":"https://github.com/xmcclure/mono.git","svn_url":"https://github.com/xmcclure/mono","homepage":"http://www.mono-project.com/","size":453604,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"mono:master","ref":"master","sha":"e20cd1cf85333d8ac1e96a7d20585f2561101ffe","user":{"login":"mono","id":53395,"avatar_url":"https://avatars.githubusercontent.com/u/53395?v=3","gravatar_id":"","url":"https://api.github.com/users/mono","html_url":"https://github.com/mono","followers_url":"https://api.github.com/users/mono/followers","following_url":"https://api.github.com/users/mono/following{/other_user}","gists_url":"https://api.github.com/users/mono/gists{/gist_id}","starred_url":"https://api.github.com/users/mono/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mono/subscriptions","organizations_url":"https://api.github.com/users/mono/orgs","repos_url":"https://api.github.com/users/mono/repos","events_url":"https://api.github.com/users/mono/events{/privacy}","received_events_url":"https://api.github.com/users/mono/received_events","type":"Organization","site_admin":false},"repo":{"id":791611,"name":"mono","full_name":"mono/mono","owner":{"login":"mono","id":53395,"avatar_url":"https://avatars.githubusercontent.com/u/53395?v=3","gravatar_id":"","url":"https://api.github.com/users/mono","html_url":"https://github.com/mono","followers_url":"https://api.github.com/users/mono/followers","following_url":"https://api.github.com/users/mono/following{/other_user}","gists_url":"https://api.github.com/users/mono/gists{/gist_id}","starred_url":"https://api.github.com/users/mono/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mono/subscriptions","organizations_url":"https://api.github.com/users/mono/orgs","repos_url":"https://api.github.com/users/mono/repos","events_url":"https://api.github.com/users/mono/events{/privacy}","received_events_url":"https://api.github.com/users/mono/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mono/mono","description":"Mono open source ECMA CLI, C# and .NET implementation.","fork":false,"url":"https://api.github.com/repos/mono/mono","forks_url":"https://api.github.com/repos/mono/mono/forks","keys_url":"https://api.github.com/repos/mono/mono/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mono/mono/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mono/mono/teams","hooks_url":"https://api.github.com/repos/mono/mono/hooks","issue_events_url":"https://api.github.com/repos/mono/mono/issues/events{/number}","events_url":"https://api.github.com/repos/mono/mono/events","assignees_url":"https://api.github.com/repos/mono/mono/assignees{/user}","branches_url":"https://api.github.com/repos/mono/mono/branches{/branch}","tags_url":"https://api.github.com/repos/mono/mono/tags","blobs_url":"https://api.github.com/repos/mono/mono/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mono/mono/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mono/mono/git/refs{/sha}","trees_url":"https://api.github.com/repos/mono/mono/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mono/mono/statuses/{sha}","languages_url":"https://api.github.com/repos/mono/mono/languages","stargazers_url":"https://api.github.com/repos/mono/mono/stargazers","contributors_url":"https://api.github.com/repos/mono/mono/contributors","subscribers_url":"https://api.github.com/repos/mono/mono/subscribers","subscription_url":"https://api.github.com/repos/mono/mono/subscription","commits_url":"https://api.github.com/repos/mono/mono/commits{/sha}","git_commits_url":"https://api.github.com/repos/mono/mono/git/commits{/sha}","comments_url":"https://api.github.com/repos/mono/mono/comments{/number}","issue_comment_url":"https://api.github.com/repos/mono/mono/issues/comments{/number}","contents_url":"https://api.github.com/repos/mono/mono/contents/{+path}","compare_url":"https://api.github.com/repos/mono/mono/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mono/mono/merges","archive_url":"https://api.github.com/repos/mono/mono/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mono/mono/downloads","issues_url":"https://api.github.com/repos/mono/mono/issues{/number}","pulls_url":"https://api.github.com/repos/mono/mono/pulls{/number}","milestones_url":"https://api.github.com/repos/mono/mono/milestones{/number}","notifications_url":"https://api.github.com/repos/mono/mono/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mono/mono/labels{/name}","releases_url":"https://api.github.com/repos/mono/mono/releases{/id}","created_at":"2010-07-22T18:06:36Z","updated_at":"2015-12-18T17:23:17Z","pushed_at":"2015-12-18T18:31:37Z","git_url":"git://github.com/mono/mono.git","ssh_url":"git@github.com:mono/mono.git","clone_url":"https://github.com/mono/mono.git","svn_url":"https://github.com/mono/mono","homepage":"http://www.mono-project.com/","size":480773,"stargazers_count":4130,"watchers_count":4130,"language":"C#","has_issues":false,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":2035,"mirror_url":null,"open_issues_count":123,"forks":2035,"open_issues":123,"watchers":4130,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mono/mono/pulls/2366"},"html":{"href":"https://github.com/mono/mono/pull/2366"},"issue":{"href":"https://api.github.com/repos/mono/mono/issues/2366"},"comments":{"href":"https://api.github.com/repos/mono/mono/issues/2366/comments"},"review_comments":{"href":"https://api.github.com/repos/mono/mono/pulls/2366/comments"},"review_comment":{"href":"https://api.github.com/repos/mono/mono/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mono/mono/pulls/2366/commits"},"statuses":{"href":"https://api.github.com/repos/mono/mono/statuses/1dc0598badaed161efa92fd261de7e555d1a2b13"}}}}