Browse Source

Added a bunch of stuff

Reports include stderr, for instance...
dhasenan 4 years ago
parent
commit
192599c3ab

+ 44
- 19
source/dubautotester/builder.d View File

@@ -7,31 +7,53 @@ import std.experimental.logger;
7 7
 import std.file;
8 8
 import std.path;
9 9
 
10
-bool checkout(PackageRevision pkg, string dir)
10
+ProcessResult checkout(PackageRevision pkg, string dir)
11 11
 {
12
+    ProcessResult pr;
12 13
     if (!dir.exists || !chainPath(dir, ".git").exists)
13 14
     {
14 15
         auto parent = dirName(dir);
15 16
         mkdirRecurse(parent);
16 17
         cd(parent);
17
-        if (clone(pkg.gitUrl, pkg.packageName).status != 0) return false;
18
+        pr = clone(pkg.gitUrl, pkg.packageName);
19
+        if (pr.status != 0) return pr;
20
+    }
21
+    else
22
+    {
23
+        cd(dir);
24
+        run(30.seconds, "git", "fetch", "--all");
18 25
     }
19 26
     cd(dir);
20
-    return reset("--hard", pkg.commitId).status == 0;
27
+    pr = reset("--hard", pkg.commitId);
28
+    if (pr.status != 0) return pr;
29
+    pr = updateSubmodules();
30
+    return pr;
21 31
 }
22 32
 
23 33
 Build build(Config config, PackageRevision revision)
24 34
 {
35
+    static import datefmt;
36
+    import std.datetime.systime : Clock;
37
+
25 38
     Build result;
26 39
     result.revisionId = revision.revisionId;
27 40
     result.packageName = revision.packageName;
28 41
     result.compilerRelease = config.compilerRelease;
29
-    static import datefmt;
30
-    import std.datetime.systime : Clock;
31 42
     result.time = datefmt.format(Clock.currTime, datefmt.ISO8601FORMAT);
32 43
 
44
+    if (config.isBlacklisted(revision.packageName))
45
+    {
46
+        result.skipped = true;
47
+        return result;
48
+    }
49
+
33 50
     auto dir = config.dataDir.buildPath(revision.packageName);
34
-    if (!checkout(revision, dir)) return result;
51
+    auto pr = checkout(revision, dir);
52
+    if (pr.status != 0)
53
+    {
54
+        result.stderr = cast(string)pr.stderr;
55
+        return result;
56
+    }
35 57
     result.canCheckOut = true;
36 58
 
37 59
     cd(dir);
@@ -56,6 +78,10 @@ Build build(Config config, PackageRevision revision)
56 78
     {
57 79
         result.canBuild = true;
58 80
     }
81
+    else
82
+    {
83
+        result.stderr = cast(string)buildResult.stderr;
84
+    }
59 85
     auto testResult = run(
60 86
             config.buildTimeout,
61 87
             config.dubLoc,
@@ -66,6 +92,10 @@ Build build(Config config, PackageRevision revision)
66 92
     {
67 93
         result.canTest = true;
68 94
     }
95
+    else
96
+    {
97
+        result.stderr ~= cast(string)testResult.stderr;
98
+    }
69 99
     if (buildResult.stderr.canFind("not sourceLibrary.".representation))
70 100
     {
71 101
         // It's a source library. Its build is effectively nothing. dub test is the best we have.
@@ -86,22 +116,12 @@ void buildAll(Config config, PackageRevision[] pkgs)
86 116
         if (config.isBlacklisted(pkg.packageName)) continue;
87 117
         auto res = build(config, pkg);
88 118
         config.record(res);
89
-        /*
90
-        config.outfile.writefln("%s,%s,%s,%s,%s,%s,%s",
91
-                config.compilerRelease,
92
-                res.revision.pkg.name,
93
-                res.revision.id,
94
-                res.canCheckOut,
95
-                res.canBuild,
96
-                res.canTest,
97
-                res.hasDeprecations);
98
-        config.outfile.flush;
99
-        */
100 119
     }
101 120
 }
102 121
 
103 122
 void dubClean(Config config)
104 123
 {
124
+    import core.time : seconds;
105 125
     infof("cleaning all dub packages to reduce disk usage");
106 126
     run(120.seconds, config.dubLoc, "clean", "--all-packages");
107 127
 }
@@ -112,15 +132,19 @@ void exerciseRelease(Config config, string compilerRelease, void delegate() afte
112 132
     config.compilerRelease = compilerRelease;
113 133
     try
114 134
     {
135
+        infof("downloading compiler %s", compilerRelease);
115 136
         downloadCompiler(config);
137
+        infof("finished");
116 138
     }
117 139
     catch (Exception e)
118 140
     {
119 141
         errorf("failed to download release %s: %s", compilerRelease, e);
120 142
         return;
121 143
     }
122
-    config.dubClean;
144
+    size_t built = 0;
123 145
     auto remaining = config.countRemainingPackages(compilerRelease);
146
+    infof("have %s packages to build", remaining);
147
+    if (remaining == 0) return;
124 148
     while (true)
125 149
     {
126 150
         auto pkg = config.nextPackage(config.compilerRelease);
@@ -139,6 +163,7 @@ void exerciseRelease(Config config, string compilerRelease, void delegate() afte
139 163
         {
140 164
             afterPackage();
141 165
         }
166
+        built++;
142 167
     }
143
-    config.dubClean;
168
+    if (built > 100) config.dubClean;
144 169
 }

+ 48
- 20
source/dubautotester/commandline.d View File

@@ -16,6 +16,8 @@ int runCommandLine(string[] args)
16 16
     string blacklistPath;
17 17
     bool verbose;
18 18
     bool report;
19
+    bool stalePackages = false;
20
+    uint iterations = uint.max;
19 21
 
20 22
     import std.getopt;
21 23
     auto opties = getopt(
@@ -28,7 +30,9 @@ int runCommandLine(string[] args)
28 30
             "c|version-count", "how many revisions per package to compile",
29 31
                 &config.revisionCount,
30 32
             "R|report", "generate reports and exit", &report,
31
-            "o|output", "output directory for reports", &config.reportsDir
33
+            "o|output", "output directory for reports", &config.reportsDir,
34
+            "i|iterations", "how many times to run through the cycle", &iterations,
35
+            "s|stale-packages", "whether to get fresh packages from dub", &stalePackages,
32 36
             );
33 37
 
34 38
     if (opties.helpWanted)
@@ -71,10 +75,6 @@ int runCommandLine(string[] args)
71 75
         return 0;
72 76
     }
73 77
 
74
-    auto pkgs = grabAll(config);
75
-    /*
76
-    config.importPackages(pkgs);
77
-    */
78 78
     auto compilerReleases = [
79 79
 		"2.082.0",
80 80
 		"2.081.0",
@@ -90,26 +90,54 @@ int runCommandLine(string[] args)
90 90
 		"2.071.0",
91 91
 		"2.070.0",
92 92
 		"2.069.0",
93
-		"2.068.0",
94
-		"2.067.0",
95 93
 		"2.081.2",
96 94
 		"2.081.1",
97 95
 		"2.080.1",
96
+		"2.078.1",
97
+		"2.073.2",
98
+		"2.073.1",
99
+		"2.070.1",
98 100
     ];
99
-    infof("have %s compiler releases and %s package revisions to test",
100
-            compilerReleases.length, pkgs.length);
101
-    long count = 0;
102
-    foreach (release; compilerReleases)
101
+
102
+    import std.datetime.systime : Clock;
103
+    import core.thread : Thread;
104
+    import core.time : dur;
105
+    // Grab it on the second check.
106
+    auto nextGrab = Clock.currTime + dur!"minutes"(10);
107
+    auto interval = dur!"hours"(1);
108
+    foreach (i; 0..iterations)
103 109
     {
104
-        exerciseRelease(config, release, {
105
-            count++;
106
-            if (count >= 100)
107
-            {
108
-                buildCompilerReports(config);
109
-                count = 0;
110
-            }
111
-        });
112
-        if (count > 0) buildCompilerReports(config);
110
+        infof("need to sleep until %s", nextGrab);
111
+        while (Clock.currTime < nextGrab)
112
+        {
113
+            Thread.sleep(dur!"seconds"(60));
114
+        }
115
+        nextGrab = Clock.currTime + interval;
116
+        if (!stalePackages)
117
+        {
118
+            infof("grabbing fresh packages");
119
+            auto pkgs = grabAllFresh(config);
120
+            config.importPackages(pkgs);
121
+            infof("have %s compiler releases and %s package revisions to test",
122
+                    compilerReleases.length, pkgs.length);
123
+        }
124
+        infof("going through the releases");
125
+        long count = 0;
126
+        foreach (release; compilerReleases)
127
+        {
128
+            exerciseRelease(config, release, {
129
+                count++;
130
+                if (count >= 100)
131
+                {
132
+                    buildCompilerReports(config);
133
+                    count = 0;
134
+                }
135
+            });
136
+            infof("finished release %s", release);
137
+            if (count > 0) buildCompilerReports(config);
138
+        }
139
+        // doesn't hurt to do this every run
140
+        buildCompilerReports(config);
113 141
     }
114 142
     return 0;
115 143
 }

+ 57
- 4
source/dubautotester/core.d View File

@@ -41,7 +41,8 @@ class Config
41 41
                  canCheckOut BOOLEAN NOT NULL,
42 42
                  canBuild BOOLEAN NOT NULL,
43 43
                  canTest BOOLEAN NOT NULL,
44
-                 hasDeprecations BOOLEAN NOT NULL
44
+                 hasDeprecations BOOLEAN NOT NULL,
45
+                 stderr TEXT NULL
45 46
                 )`);
46 47
         db.run(`CREATE TABLE IF NOT EXISTS revisions
47 48
                 (
@@ -52,11 +53,13 @@ class Config
52 53
                  lastBuilt TEXT NULL,
53 54
                  PRIMARY KEY (packageName, revisionId)
54 55
                 )`);
56
+        db.run(`CREATE INDEX IF NOT EXISTS idx_builds_pkg_compiler
57
+                ON builds (compilerRelease, packageName, revisionId)`);
55 58
         addRecord = db.prepare(`
56 59
                 INSERT INTO builds
57 60
                 (time, compilerRelease, packageName, revisionId, skipped, canCheckOut, canBuild,
58
-                 canTest, hasDeprecations)
59
-                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
61
+                 canTest, hasDeprecations, stderr)
62
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
60 63
                 `);
61 64
         updateBuildTime = db.prepare(`
62 65
                 UPDATE revisions
@@ -112,6 +115,9 @@ class Config
112 115
                 SELECT * FROM builds WHERE packageName = ?
113 116
                 ORDER BY revisionId DESC, compilerRelease DESC
114 117
                 `);
118
+        countPackageMatches = db.prepare(`
119
+                SELECT COUNT(*) FROM revisions WHERE packageName = ? AND revisionId = ?
120
+                `);
115 121
 
116 122
         {
117 123
             import scriptlike;
@@ -143,13 +149,21 @@ class Config
143 149
     {
144 150
         db.run("begin");
145 151
         scope (exit) db.run("commit");
146
-        db.run("delete from revisions");
147 152
         foreach (rev; revisions)
148 153
         {
154
+            if (countMatches(rev.packageName, rev.revisionId) > 0) continue;
149 155
             addPackage.inject(rev);
150 156
         }
151 157
     }
152 158
 
159
+    uint countMatches(string packageName, string revisionId)
160
+    {
161
+        countPackageMatches.reset;
162
+        countPackageMatches.bind(1, packageName);
163
+        countPackageMatches.bind(2, revisionId);
164
+        return countPackageMatches.execute.oneValue!uint;
165
+    }
166
+
153 167
     PackageRevision nextPackage(string compilerRelease)
154 168
     {
155 169
         nextPackageSingle.reset;
@@ -237,6 +251,7 @@ private:
237 251
     Statement packageNames;
238 252
     Statement buildsByCompiler;
239 253
     Statement buildsByPackage;
254
+    Statement countPackageMatches;
240 255
 }
241 256
 
242 257
 struct PackageRevision
@@ -276,4 +291,42 @@ struct Build
276 291
 
277 292
     /// Whether there's any deprecated code in this.
278 293
     bool hasDeprecations;
294
+
295
+    /// The stderr of any failing processes. Only available on more recent builds.
296
+    string stderr;
297
+}
298
+
299
+unittest
300
+{
301
+    import std.file : remove;
302
+    import std.format : format;
303
+
304
+    auto cfg = new Config;
305
+    scope (exit) cfg.die;
306
+    cfg.outpath = "test.sqlite";
307
+    scope (exit) cfg.outpath.remove;
308
+    cfg.init;
309
+    Build testBuild =
310
+    {
311
+time: "2010-01-15T19:35:02.000000Z",
312
+      compilerRelease: "2.082.8",
313
+      packageName: "tester-test",
314
+      revisionId: "75.181.3",
315
+      skipped: false,
316
+      canCheckOut: true,
317
+      canTest: true,
318
+      canBuild: false,
319
+      hasDeprecations: true,
320
+      stderr: `Main package must have a binary target type, not sourceLibrary. Cannot build.
321
+The sun was shining on the sea, shining with all his might.`
322
+    };
323
+    cfg.record(testBuild);
324
+    auto byCompiler = cfg.findBuildsByCompiler(testBuild.compilerRelease);
325
+    auto byPackage = cfg.findBuildsByPackage(testBuild.packageName);
326
+    assert(byCompiler[0] == testBuild,
327
+            "byCompiler: expected %s, got %s".format(testBuild, byCompiler[0]));
328
+    assert(byPackage[0] == testBuild,
329
+            "byPackage: expected %s, got %s".format(testBuild, byPackage[0]));
330
+    assert(byCompiler[0].stderr != "");
331
+    assert(byPackage[0].stderr != "");
279 332
 }

+ 7
- 1
source/dubautotester/git.d View File

@@ -69,6 +69,7 @@ ProcessResult run(Duration maxTime, string[] args...)
69 69
         }
70 70
         if (end >= deadline)
71 71
         {
72
+            tracef("passed deadline, killing");
72 73
             kill(pid, 9);
73 74
             result.timedOut = true;
74 75
             result.status = int.max;
@@ -80,12 +81,18 @@ ProcessResult run(Duration maxTime, string[] args...)
80 81
     import std.file : read;
81 82
     result.stderr = cast(const(ubyte)[])read(stderrPath);
82 83
     result.runningTime = end - start;
84
+    tracef("finished with exit code %s in %s", result.status, result.runningTime);
83 85
     return result;
84 86
 }
85 87
 
86 88
 alias clone = cmd!"clone";
87 89
 alias reset = cmd!"reset";
88 90
 
91
+ProcessResult updateSubmodules()
92
+{
93
+    return run(30.seconds, "git", "submodule", "update", "--init", "--recursive");
94
+}
95
+
89 96
 private string procDir;
90 97
 
91 98
 string dataDir;
@@ -97,6 +104,5 @@ struct ProcessResult
97 104
     int status;
98 105
     Duration runningTime;
99 106
     bool timedOut;
100
-    //
101 107
     const(ubyte)[] stderr;
102 108
 }

+ 115
- 13
source/dubautotester/html.d View File

@@ -55,6 +55,8 @@ Document buildBasicDocument()
55 55
     auto style = head.addChild("style");
56 56
     style.setAttribute("type", "text/css");
57 57
     style.innerText = css;
58
+    head.addChild("script")
59
+        .setAttribute("src", "../autotester.js");
58 60
     doc.root.addChild("body");
59 61
     // Any chrome we want?
60 62
     // - navigation
@@ -104,6 +106,8 @@ void buildCompilerReports(Config config)
104 106
     StopWatch sw;
105 107
     sw.start;
106 108
 
109
+    std.file.write(config.reportsDir.buildPath("autotester.js"), js);
110
+
107 111
     tracef("building compiler reports!");
108 112
     auto dir = config.reportsDir.buildPath("compiler");
109 113
     dir.mkdirRecurse;
@@ -136,11 +140,19 @@ void buildCompilerReports(Config config)
136 140
         }
137 141
         tracef("have %s builds for release %s", builds.length, release);
138 142
         auto title = "DMD " ~ release ~ " report";
139
-        auto doc = buildStandardPage(builds, title, LinkTo.packages);
143
+        auto doc = buildStandardPage(builds, title, Type.packages);
140 144
         tracef("built standard page for %s summary document", release);
141 145
         auto filename = "dmd" ~ release ~ ".html";
142 146
         auto targetPath = dir.buildPath(filename);
143
-        std.file.write(targetPath, doc.toPrettyString);
147
+        import core.exception : UnicodeException;
148
+        try
149
+        {
150
+            std.file.write(targetPath, doc.toPrettyString);
151
+        }
152
+        catch (UnicodeException e)
153
+        {
154
+            errorf("failed to write release summary %s: %s", release, e);
155
+        }
144 156
         tracef("write report to %s", targetPath);
145 157
 
146 158
         // Update summary table
@@ -209,7 +221,7 @@ void writePackageSummary(string dir, string pkgName, Build[] builds)
209 221
 {
210 222
     import std.format : format;
211 223
     auto doc = buildStandardPage(builds, pkgName ~ " buildability across DMD versions",
212
-            LinkTo.compilers);
224
+            Type.compilers);
213 225
     auto badgeInfo = doc.mainBody.addChild("div");
214 226
     badgeInfo.addChild("div").addChild("img")
215 227
         .setAttribute("width", "100px")
@@ -222,8 +234,8 @@ void writePackageSummary(string dir, string pkgName, Build[] builds)
222 234
 </a>`.format(pkgName);
223 235
     badgeInfo.appendText("Adding it to a README.md? Try this:");
224 236
     badgeInfo.addChild("div").addChild("pre")
225
-        .innerText = `[![DMD build canary](http://ikeran.org/packages/%1$s.svg)](http://ikeran.org/packages/%1$s.html)`
226
-            .format(pkgName);
237
+        .innerText = (`[![DMD build canary](http://ikeran.org/packages/%1$s.svg)]` ~
238
+                `(http://ikeran.org/packages/%1$s.html)`).format(pkgName);
227 239
 
228 240
     std.file.write(dir.chainPath(pkgName ~ ".html"), doc.toPrettyString);
229 241
 }
@@ -271,7 +283,7 @@ enum basicSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40">
271 283
   <line x1="52" y1="20" x2="82" y2="20" stroke="#000000" />
272 284
 </svg>`;
273 285
 
274
-enum LinkTo
286
+enum Type
275 287
 {
276 288
     nothing = 0,
277 289
     packages = 1,
@@ -279,7 +291,7 @@ enum LinkTo
279 291
     both = 3,
280 292
 }
281 293
 
282
-Document buildStandardPage(Build[] builds, string title, LinkTo linkTo)
294
+Document buildStandardPage(Build[] builds, string title, Type linkTo)
283 295
 {
284 296
     auto doc = buildBasicDocument;
285 297
     doc.title = title;
@@ -298,7 +310,7 @@ Document buildStandardPage(Build[] builds, string title, LinkTo linkTo)
298 310
     return doc;
299 311
 }
300 312
 
301
-void buildPackageReportStuff(Element context, Build[] builds, LinkTo linkTo)
313
+void buildPackageReportStuff(Element context, Build[] builds, Type linkTo)
302 314
 {
303 315
     tracef("sorting builds");
304 316
     builds.sort!(buildLess);
@@ -317,12 +329,12 @@ void buildPackageReportStuff(Element context, Build[] builds, LinkTo linkTo)
317 329
         }
318 330
     }
319 331
     tracef("added table header");
320
-    foreach (build; builds)
332
+    foreach (i, build; builds)
321 333
     {
322 334
         auto tr = table.addChild("tr");
323 335
         auto pkgName = tr.addChild("td")
324 336
             .addClass("packageName");
325
-        if (linkTo & LinkTo.packages)
337
+        if (linkTo & Type.packages)
326 338
         {
327 339
             pkgName.addChild("a")
328 340
                 .setAttribute("href", "../package/" ~ build.packageName ~ ".html")
@@ -337,7 +349,7 @@ void buildPackageReportStuff(Element context, Build[] builds, LinkTo linkTo)
337 349
             .innerText = build.revisionId;
338 350
         auto compiler = tr.addChild("td")
339 351
             .addClass("compilerRelease");
340
-        if (linkTo & LinkTo.compilers)
352
+        if (linkTo & Type.compilers)
341 353
         {
342 354
             compiler.addChild("a")
343 355
                 .setAttribute("href", "../compiler/dmd" ~ build.compilerRelease ~ ".html")
@@ -351,10 +363,15 @@ void buildPackageReportStuff(Element context, Build[] builds, LinkTo linkTo)
351 363
             .addClass("canCheckOut")
352 364
             .addClass(build.canCheckOut ? "success" : "failure")
353 365
             .innerText = build.canCheckOut ? "checks out" : "git failure";
366
+        build.stderr = build.stderr.utfSafe;
367
+        bool isSourceLib = build.stderr.canFind(
368
+                "Main package must have a binary target type, not sourceLibrary. Cannot build.");
369
+        string buildClass = (isSourceLib || build.canBuild) ? "success" : "failure";
370
+        string buildMessage = build.canBuild ? "builds" : (isSourceLib ? "sourcelib" : "failure");
354 371
         tr.addChild("td")
355 372
             .addClass("canBuild")
356
-            .addClass(build.canBuild ? "success" : "failure")
357
-            .innerText = build.canBuild ? "builds" : "build errors";
373
+            .addClass(buildClass)
374
+            .innerText = buildMessage;
358 375
         tr.addChild("td")
359 376
             .addClass("canTest")
360 377
             .addClass(build.canTest ? "success" : "failure")
@@ -363,6 +380,91 @@ void buildPackageReportStuff(Element context, Build[] builds, LinkTo linkTo)
363 380
             .addClass("hasDeprecations")
364 381
             .addClass(build.hasDeprecations ? "failure" : "success")
365 382
             .innerText = build.hasDeprecations ? "deprecated" : "good";
383
+        if (linkTo & Type.packages)
384
+        {
385
+            if (build.stderr.length > 0)
386
+            {
387
+                import std.conv : to;
388
+                auto id = "build" ~ i.to!string;
389
+                tr.addChild("td")
390
+                    .addChild("a")
391
+                    .setAttribute("name", id)
392
+                    .addClass("showOutputLink")
393
+                    .setAttribute("id", id)
394
+                    .setAttribute("onclick", "showBuildOutput(" ~ id ~ ")")
395
+                    .setAttribute("data-stderr", build.stderr)
396
+                    .setAttribute("href", "#" ~ id)
397
+                    .innerText = "Build log";
398
+            }
399
+        }
366 400
     }
367 401
     tracef("added builds");
368 402
 }
403
+
404
+unittest
405
+{
406
+    Build testBuild =
407
+    {
408
+time: "2010-01-15T19:35:02.000000Z",
409
+      compilerRelease: "2.082.8",
410
+      packageName: "tester-test",
411
+      revisionId: "75.181.3",
412
+      skipped: false,
413
+      canCheckOut: true,
414
+      canTest: true,
415
+      canBuild: false,
416
+      hasDeprecations: true,
417
+      stderr: `Main package must have a binary target type, not sourceLibrary. Cannot build.
418
+The sun was shining on the sea, shining with all his might.`
419
+    };
420
+    auto doc = buildBasicDocument;
421
+    auto elem = doc.mainBody.addChild("div");
422
+    buildPackageReportStuff(elem, [testBuild], Type.packages);
423
+    auto text = doc.toString;
424
+    assert(text.canFind(testBuild.stderr));
425
+}
426
+
427
+string utfSafe(string s)
428
+{
429
+    import std.array : Appender;
430
+    import std.utf;
431
+    Appender!dstring ap;
432
+    size_t i;
433
+    while (i < s.length)
434
+    {
435
+        ap ~= decode!(Yes.useReplacementDchar)(s, i);
436
+    }
437
+    return ap.data.to!string;
438
+}
439
+
440
+unittest
441
+{
442
+    auto s = utfSafe("hello\xFF\xFAworld");
443
+    assert(s == "hello\uFFFDworld");
444
+}
445
+
446
+enum js = `
447
+function showBuildOutput(id) {
448
+    var elem = document.getElementById(id);
449
+    var popup = document.getElementById("popup");
450
+    if (popup == null) {
451
+        popup = document.addChild("div");
452
+        popup.id = "popup";
453
+        popup.style.position = "absolute";
454
+        popup.style.top = "4em";
455
+        popup.style.left = "4em";
456
+        popup.style.right = "4em";
457
+        popup.style.bottom = "4em";
458
+        popup.style.border = "4px solid red;"
459
+        popup.style.padding = "2em"
460
+
461
+        popup.addChild("pre").id = "stderr";
462
+        var button = popup.addChild("button");
463
+        button.innerText = "Close";
464
+        button.addEventListener("click", function() { popup.style.display = "none"; });
465
+    }
466
+    popup.style.display = "block";
467
+    var stderrBlock = document.getElementById("stderr");
468
+    stderrBlock.innerText = elem.dataset.stderr;
469
+}
470
+`;

+ 1
- 0
source/dubautotester/packagefinder.d View File

@@ -98,6 +98,7 @@ PackageRevision[] grabAllFresh(Config config)
98 98
     auto names = packageNames;
99 99
     auto packageJsons = names
100 100
         .map!getPackageJson
101
+        .array
101 102
         .filter!(x => x.type == JSON_TYPE.OBJECT)
102 103
         .array;
103 104
     infof("downloading packages fresh");