|
| 1 | +module dlangbot.ci; |
| 2 | + |
| 3 | +import dlangbot.github; |
| 4 | + |
| 5 | +import vibe.core.log; |
| 6 | +import vibe.http.client : HTTPClientRequest, requestHTTP; |
| 7 | + |
| 8 | +import std.conv : to; |
| 9 | +import std.format : format; |
| 10 | +import std.regex : matchFirst, regex; |
| 11 | +import std.exception; |
| 12 | +import std.variant : Nullable; |
| 13 | + |
| 14 | +// list of used APIs (overwritten by the test suite) |
| 15 | +string dTestAPI = "http://dtest.dlang.io"; |
| 16 | +string circleCiAPI = "https://circleci.com/api/v1.1"; |
| 17 | +string projectTesterAPI = "https://ci.dawg.eu"; |
| 18 | + |
| 19 | +// only since 2.073 |
| 20 | +auto nullable(T)(T t) { return Nullable!T(t); } |
| 21 | + |
| 22 | +/** |
| 23 | +There's no way to get the PR number from a GitHub status event (or other API |
| 24 | +endpoints). |
| 25 | +Hence we have to check the sender for this information. |
| 26 | +*/ |
| 27 | +Nullable!uint getPRForStatus(string repoSlug, string url, string context) |
| 28 | +{ |
| 29 | + Nullable!uint prNumber; |
| 30 | + |
| 31 | + try { |
| 32 | + logDebug("getPRNumber (repo: %s, ci: %s)", repoSlug, context); |
| 33 | + switch (context) { |
| 34 | + case "auto-tester": |
| 35 | + prNumber = checkAutoTester(url); |
| 36 | + break; |
| 37 | + case "ci/circleci": |
| 38 | + prNumber = checkCircleCi(url); |
| 39 | + break; |
| 40 | + case "continuous-integration/travis-ci/pr": |
| 41 | + prNumber = checkTravisCi(url); |
| 42 | + break; |
| 43 | + case "CyberShadow/DAutoTest": |
| 44 | + prNumber = checkDTest(url); |
| 45 | + break; |
| 46 | + case "Project Tester": |
| 47 | + prNumber = checkProjectTester(url); |
| 48 | + break; |
| 49 | + // CodeCov provides no way atm |
| 50 | + default: |
| 51 | + } |
| 52 | + } catch (Exception e) { |
| 53 | + logDebug("PR number for: %s (by CI: %s) couldn't be detected", repoSlug, context); |
| 54 | + logDebug("Exception", e); |
| 55 | + } |
| 56 | + |
| 57 | + return prNumber; |
| 58 | +} |
| 59 | + |
| 60 | +class PRDetectionException : Exception |
| 61 | +{ |
| 62 | + this() |
| 63 | + { |
| 64 | + super("Failure to detected PR number"); |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +Nullable!uint checkCircleCi(string url) |
| 69 | +{ |
| 70 | + import std.algorithm.iteration : splitter; |
| 71 | + import std.array : array; |
| 72 | + import std.range : back, retro; |
| 73 | + |
| 74 | + // https://circleci.com/gh/dlang/dmd/2827?utm_campaign=v... |
| 75 | + static circleCiRe = regex(`circleci.com/gh/(.*)/([0-9]+)`); |
| 76 | + Nullable!uint pr; |
| 77 | + |
| 78 | + auto m = url.matchFirst(circleCiRe); |
| 79 | + enforce(!m.empty); |
| 80 | + |
| 81 | + string repoSlug = m[1]; |
| 82 | + ulong buildNumber = m[2].to!ulong; |
| 83 | + |
| 84 | + auto resp = requestHTTP("%s/project/github/%s/%d" |
| 85 | + .format(circleCiAPI, repoSlug, buildNumber)).readJson; |
| 86 | + if (auto prs = resp["pull_requests"][]) |
| 87 | + { |
| 88 | + pr = prs[0]["url"].get!string |
| 89 | + .splitter("/") |
| 90 | + .array // TODO: splitter is not bidirectional |
| 91 | + // https://issues.dlang.org/show_bug.cgi?id=17047 |
| 92 | + .back.to!uint; |
| 93 | + } |
| 94 | + // branch in upstream |
| 95 | + return pr; |
| 96 | +} |
| 97 | + |
| 98 | + |
| 99 | +Nullable!uint checkTravisCi(string url) |
| 100 | +{ |
| 101 | + import dlangbot.travis : getPRNumber; |
| 102 | + |
| 103 | + // https://travis-ci.org/dlang/dmd/builds/203056613 |
| 104 | + static travisCiRe = regex(`travis-ci.org/(.*)/builds/([0-9]+)`); |
| 105 | + Nullable!uint pr; |
| 106 | + |
| 107 | + auto m = url.matchFirst(travisCiRe); |
| 108 | + enforce(!m.empty); |
| 109 | + |
| 110 | + string repoSlug = m[1]; |
| 111 | + ulong buildNumber = m[2].to!ulong; |
| 112 | + |
| 113 | + return getPRNumber(repoSlug, buildNumber); |
| 114 | +} |
| 115 | + |
| 116 | +// tests PRs only |
| 117 | +auto checkAutoTester(string url) |
| 118 | +{ |
| 119 | + // https://auto-tester.puremagic.com/pull-history.ghtml?projectid=1&repoid=1&pullid=6552 |
| 120 | + static autoTesterRe = regex(`pullid=([0-9]+)`); |
| 121 | + |
| 122 | + auto m = url.matchFirst(autoTesterRe); |
| 123 | + enforce(!m.empty); |
| 124 | + return m[1].to!uint.nullable; |
| 125 | +} |
| 126 | + |
| 127 | +// tests PRs only |
| 128 | +auto checkDTest(string url) |
| 129 | +{ |
| 130 | + import vibe.stream.operations : readAllUTF8; |
| 131 | + |
| 132 | + // http://dtest.dlang.io/results/f3f364ddcf96e98d1a6566b04b130c3f8b37a25f/378ec2f7616ec7ca4554c5381b45561473b0c218/ |
| 133 | + static dTestRe = regex(`results/([0-9a-f]+)/([0-9a-f]+)`); |
| 134 | + static dTestReText = regex(`<tr>.*Pull request.*<a href=".*\/pull\/([0-9]+)"`); |
| 135 | + |
| 136 | + // to enable testing: don't use link directly |
| 137 | + auto shas = url.matchFirst(dTestRe); |
| 138 | + enforce(!shas.empty); |
| 139 | + string headSha = shas[1]; // = PR |
| 140 | + string baseSha= shas[2]; // e.g upstream/master |
| 141 | + |
| 142 | + auto m = requestHTTP("%s/results/%s/%s/".format(dTestAPI, headSha, baseSha)) |
| 143 | + .bodyReader |
| 144 | + .readAllUTF8 |
| 145 | + .matchFirst(dTestReText); |
| 146 | + |
| 147 | + enforce(!m.empty); |
| 148 | + return m[1].to!uint.nullable; |
| 149 | +} |
| 150 | + |
| 151 | +// tests PRs only ? |
| 152 | +Nullable!uint checkProjectTester(string url) |
| 153 | +{ |
| 154 | + import vibe.stream.operations : readAllUTF8; |
| 155 | + import vibe.inet.url : URL; |
| 156 | + |
| 157 | + // 1: repoSlug, 2: pr |
| 158 | + static projectTesterReText = `href="https:\/\/github[.]com\/(.*)\/pull\/([0-9]+)`; |
| 159 | + |
| 160 | + auto uri = URL(url); |
| 161 | + |
| 162 | + auto m = requestHTTP("%s%s".format(projectTesterAPI, uri.path)) |
| 163 | + .bodyReader |
| 164 | + .readAllUTF8 |
| 165 | + .matchFirst(projectTesterReText); |
| 166 | + |
| 167 | + enforce(!m.empty, "Project tester detection failed"); |
| 168 | + return m[2].to!uint.nullable; |
| 169 | +} |
0 commit comments