| 
 | 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