Skip to content

Commit e8caf27

Browse files
authored
feat: with specs dashboard
2 parents 9ef9297 + 42ad1c6 commit e8caf27

File tree

15 files changed

+501
-89
lines changed

15 files changed

+501
-89
lines changed

munge_aggregates.js

Lines changed: 162 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,51 @@ if (!dbFile || !hugoOutput) {
1818
process.exit(1);
1919
}
2020

21+
/**
22+
* @param {string} u a spec URL like "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header"
23+
* @returns the spec's parent, or "null" if it's a top-level spec
24+
*/
25+
const computeParent = (u) => {
26+
const url = new URL(u);
27+
const segments = url.pathname.split('/').filter(Boolean);
28+
29+
// if there's a hash, consider it as a segment
30+
if (url.hash) segments.push(url.hash.substring(1));
31+
32+
if (segments.length <= 1) {
33+
return "null";
34+
}
35+
36+
const parent = segments.slice(0, -1).join('/');
37+
return `${url.protocol}//${url.host}/${parent}`
38+
};
39+
40+
/**
41+
* @param {string} u a spec URL like "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header"
42+
* @returns the spec's name, or the hash if it's a top-level spec and whether it was found in a hash
43+
*/
44+
const computeName = (u) => {
45+
const url = new URL(u);
46+
47+
if (url.hash) {
48+
return {
49+
isHashed: true,
50+
name: url.hash.substring(1),
51+
};
52+
}
53+
54+
const segments = url.pathname.split('/').filter(Boolean);
55+
56+
if (segments.length === 0) {
57+
throw new Error(`Invalid spec URL: ${u}`);
58+
}
59+
60+
return {
61+
isHashed: false,
62+
name: segments[segments.length - 1],
63+
};
64+
};
65+
2166
const main = async () => {
2267
let db = new sqlite3.Database(dbFile, (err) => {
2368
if (err) {
@@ -58,6 +103,8 @@ const main = async () => {
58103
`;
59104
const testsRows = await all(testsQuery);
60105
const groups = {};
106+
const flatTestGroups = {}; // used for specs generation.
107+
61108
for (const row of testsRows) {
62109
const { versions, full_name, name, parent_test_full_name } = row;
63110
const slug = slugify(full_name);
@@ -66,10 +113,115 @@ const main = async () => {
66113
groups[parent_test_full_name] = {};
67114
}
68115

69-
groups[parent_test_full_name][full_name] = { versions: versions?.split(',') || [], name, full_name, slug };
116+
const g = { versions: versions?.split(',') || [], name, full_name, slug };
117+
118+
groups[parent_test_full_name][full_name] = g;
119+
flatTestGroups[full_name] = g;
70120
}
71121
outputJSON("data/testgroups.json", groups);
72122

123+
// Query to fetch all test specs
124+
const specsQuery = `
125+
SELECT
126+
spec_url as full_name,
127+
GROUP_CONCAT(DISTINCT test_run_version) AS versions
128+
FROM TestSpecs
129+
GROUP BY full_name
130+
ORDER BY full_name
131+
`;
132+
const specsRows = await all(specsQuery);
133+
const specs = {};
134+
const flatSpecs = {};
135+
136+
for (const row of specsRows) {
137+
const { versions, full_name } = row;
138+
let current = full_name;
139+
140+
while (current !== "null") {
141+
const slug = slugify(current);
142+
const parent = computeParent(current);
143+
const { name, isHashed } = computeName(current)
144+
145+
if (!specs[parent]) {
146+
specs[parent] = {};
147+
}
148+
149+
flatSpecs[current] = true
150+
151+
specs[parent][current] = {
152+
versions: versions?.split(',') || [],
153+
spec_full_name: current,
154+
slug,
155+
name,
156+
isHashed,
157+
};
158+
159+
current = parent;
160+
}
161+
}
162+
outputJSON("data/specs.json", specs);
163+
164+
const descendTheSpecsTree = (current, path) => {
165+
Object.entries(specs[current] || {})
166+
.forEach(([key, spec]) => {
167+
const addSpecs = (current) => {
168+
let hashes = [...(current.specs || []), spec.name];
169+
hashes = [...new Set(hashes)]; // deduplicate
170+
return { ...current, hashes }
171+
};
172+
173+
// To reproduce the structure of URLs and hashes, we update existing specs pages
174+
if (spec.isHashed) {
175+
const p = path.join("/");
176+
outputFrontmatter(
177+
`content/specs/${p}/_index.md`,
178+
addSpecs
179+
);
180+
// We assume there are no recursion / children for hashes
181+
return
182+
}
183+
184+
const newPath = [...path, spec.name];
185+
const p = newPath.join("/");
186+
187+
outputFrontmatter(`content/specs/${p}/_index.md`, {
188+
...spec,
189+
title: spec.name,
190+
});
191+
192+
descendTheSpecsTree(key, newPath);
193+
})
194+
}
195+
196+
descendTheSpecsTree("null", [])
197+
198+
// Aggregate test results per specs
199+
const specsTestGroups = {};
200+
201+
for (const fullName of Object.keys(flatSpecs)) {
202+
// list all the test names for a given spec.
203+
// we prefix search the database for spec_urls starting with the spec name
204+
const specsQuery = `
205+
SELECT
206+
test_full_name
207+
FROM TestSpecs
208+
WHERE spec_url LIKE ?
209+
ORDER BY test_full_name
210+
`;
211+
const tests = await all(specsQuery, [fullName + '%']);
212+
213+
const s = tests.map(x => x.test_full_name)
214+
.reduce((acc, name) => {
215+
return {
216+
...acc,
217+
[name]: flatTestGroups[name]
218+
}
219+
}, {});
220+
specsTestGroups[fullName] = s;
221+
}
222+
223+
outputJSON("data/specsgroups.json", specsTestGroups);
224+
73225
// Query to fetch all stdouts
74226
const logsQuery = `
75227
SELECT
@@ -269,6 +421,7 @@ const slugify = (str) => {
269421
.replace(/_+/g, '_') // remove consecutive underscores
270422
.replace(/[\/]/g, "__")
271423
.replace(/[^a-z0-9 -]/g, '-') // remove non-alphanumeric characters
424+
.replace(/-+/g, '-') // remove consecutive dashes
272425
}
273426

274427
const outputJSON = (p, data) => {
@@ -283,7 +436,7 @@ const outputJSON = (p, data) => {
283436
fs.writeFileSync(fullPath, json);
284437
}
285438

286-
const outputFrontmatter = (p, data) => {
439+
const outputFrontmatter = (p, dataOrUpdate) => {
287440
const fullPath = `${hugoOutput}/${p}`;
288441

289442
// TODO: implement update frontmatter
@@ -303,7 +456,12 @@ const outputFrontmatter = (p, data) => {
303456
content.content = existing.content;
304457
content.data = existing.data;
305458
}
306-
content.data = { ...content.data, ...data };
459+
460+
if (typeof dataOrUpdate === "function") {
461+
content.data = dataOrUpdate(content.data);
462+
} else {
463+
content.data = { ...content.data, ...dataOrUpdate };
464+
}
307465

308466
const md = matter.stringify(content.content, content.data);
309467
fs.writeFileSync(fullPath, md);
@@ -322,4 +480,4 @@ main()
322480
.catch((e) => {
323481
console.error(e);
324482
process.exit(1);
325-
})
483+
})

munge_sql.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,28 @@ const main = async () => {
100100
);
101101
`)
102102

103+
// Create the SPECS
104+
await run(`
105+
CREATE TABLE IF NOT EXISTS TestSpecs (
106+
test_run_implementation_id TEXT,
107+
test_run_version TEXT,
108+
test_full_name TEXT,
109+
110+
spec_url TEXT,
111+
112+
PRIMARY KEY (test_run_implementation_id, test_run_version, test_full_name, spec_url),
113+
114+
-- test run
115+
FOREIGN KEY (test_run_implementation_id, test_run_version)
116+
REFERENCES TestRun (implementation_id, version),
117+
118+
-- test result
119+
FOREIGN KEY (test_run_implementation_id, test_run_version, test_full_name)
120+
REFERENCES TestResult (test_run_implementation_id, test_run_version, full_name)
121+
);
122+
`);
123+
124+
103125
for (const file of files) {
104126
const fileName = file.split("/").slice(-1)[0].split(".")[0];
105127
const implemId = fileName;
@@ -146,6 +168,16 @@ const main = async () => {
146168
VALUES (?, ?, ?, ?)
147169
`, [implemId, version, fullName, test.output]);
148170

171+
const specsArray = test.meta?.specs || [];
172+
for (const specUrl of specsArray) {
173+
// add `https://` if the specs don't have it
174+
const cleanSpecUrl = specUrl.startsWith("http") ? specUrl : `https://${specUrl}`;
175+
176+
await run(`
177+
INSERT INTO TestSpecs (test_run_implementation_id, test_run_version, test_full_name, spec_url)
178+
VALUES (?, ?, ?, ?)
179+
`, [implemId, version, fullName, cleanSpecUrl]);
180+
}
149181
}
150182
}
151183

tests/path_gateway_dag_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestGatewayJsonCbor(t *testing.T) {
4242
},
4343
{
4444
Name: "GET UnixFS file with JSON bytes is returned with application/json Content-Type - with headers",
45-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header",
45+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header",
4646
Hint: `
4747
## Quick regression check for JSON stored on UnixFS:
4848
## it has nothing to do with DAG-JSON and JSON codecs,
@@ -479,7 +479,7 @@ func TestNativeDag(t *testing.T) {
479479
Response: Expect().
480480
Headers(
481481
Header("Content-Type").Hint("expected Content-Type").Equals("application/vnd.ipld.dag-{{format}}", row.Format),
482-
Header("Content-Length").Spec("specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header").Hint("includes Content-Length").Equals("{{length}}", len(dagTraversal.RawData())),
482+
Header("Content-Length").Spec("https://specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header").Hint("includes Content-Length").Equals("{{length}}", len(dagTraversal.RawData())),
483483
Header("Content-Disposition").Hint("includes Content-Disposition").Contains(`{{disposition}}; filename="{{cid}}.{{format}}"`, row.Disposition, dagTraversalCID, row.Format),
484484
Header("X-Content-Type-Options").Hint("includes nosniff hint").Contains("nosniff"),
485485
),
@@ -553,7 +553,7 @@ func TestNativeDag(t *testing.T) {
553553
},
554554
{
555555
Name: Fmt("HEAD {{name}} with only-if-cached for missing block returns HTTP 412 Precondition Failed", row.Name),
556-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#only-if-cached",
556+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#only-if-cached",
557557
Request: Request().
558558
Path("/ipfs/{{cid}}", missingCID).
559559
Header("Cache-Control", "only-if-cached").

tests/path_gateway_tar_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func TestTar(t *testing.T) {
7676
},
7777
{
7878
Name: "GET TAR with explicit ?filename= succeeds with modified Content-Disposition header",
79-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header",
79+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header",
8080
Request: Request().
8181
Path("/ipfs/{{cid}}", dirCID).
8282
Query("filename", "testтест.tar").

tests/path_gateway_unixfs_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func TestGatewayCache(t *testing.T) {
202202
// ==========
203203
{
204204
Name: "GET for /ipfs/ file with matching Etag in If-None-Match returns 304 Not Modified",
205-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
205+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
206206
Request: Request().
207207
Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()).
208208
Headers(
@@ -213,7 +213,7 @@ func TestGatewayCache(t *testing.T) {
213213
},
214214
{
215215
Name: "GET for /ipfs/ dir with index.html file with matching Etag in If-None-Match returns 304 Not Modified",
216-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
216+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
217217
Request: Request().
218218
Path("/ipfs/{{cid}}/root2/root3/root4/", fixture.MustGetCid()).
219219
Headers(
@@ -224,7 +224,7 @@ func TestGatewayCache(t *testing.T) {
224224
},
225225
{
226226
Name: "GET for /ipfs/ file with matching third Etag in If-None-Match returns 304 Not Modified",
227-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
227+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
228228
Request: Request().
229229
Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()).
230230
Headers(
@@ -235,7 +235,7 @@ func TestGatewayCache(t *testing.T) {
235235
},
236236
{
237237
Name: "GET for /ipfs/ file with matching weak Etag in If-None-Match returns 304 Not Modified",
238-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
238+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
239239
Request: Request().
240240
Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()).
241241
Headers(
@@ -246,7 +246,7 @@ func TestGatewayCache(t *testing.T) {
246246
},
247247
{
248248
Name: "GET for /ipfs/ file with wildcard Etag in If-None-Match returns 304 Not Modified",
249-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
249+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
250250
Request: Request().
251251
Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()).
252252
Headers(
@@ -257,7 +257,7 @@ func TestGatewayCache(t *testing.T) {
257257
},
258258
{
259259
Name: "GET for /ipfs/ dir listing with matching weak Etag in If-None-Match returns 304 Not Modified",
260-
Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
260+
Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header",
261261
Request: Request().
262262
Path("/ipfs/{{cid}}/root2/root3/", fixture.MustGetCid()).
263263
Headers(

tests/redirects_file_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
)
1616

1717
func TestRedirectsFileSupport(t *testing.T) {
18-
tooling.LogSpecs(t, "specs.ipfs.tech/http-gateways/web-redirects-file/")
18+
tooling.LogSpecs(t, "https://specs.ipfs.tech/http-gateways/web-redirects-file/")
1919
fixture := car.MustOpenUnixfsCar("redirects_file/redirects.car")
2020
redirectDir := fixture.MustGetNode("examples")
2121
redirectDirCID := redirectDir.Base32Cid()
@@ -166,8 +166,8 @@ func TestRedirectsFileSupport(t *testing.T) {
166166
Contains("could not parse _redirects:"),
167167
Contains(`forced redirects (or "shadowing") are not supported`),
168168
),
169-
).Spec("specs.ipfs.tech/http-gateways/web-redirects-file/#no-forced-redirects"),
170-
Spec: "specs.ipfs.tech/http-gateways/web-redirects-file/#error-handling",
169+
).Spec("https://specs.ipfs.tech/http-gateways/web-redirects-file/#no-forced-redirects"),
170+
Spec: "https://specs.ipfs.tech/http-gateways/web-redirects-file/#error-handling",
171171
},
172172
{
173173
Name: "invalid file: request for $TOO_LARGE_REDIRECTS_DIR_HOSTNAME/not-found returns error about too large redirects file",
@@ -182,7 +182,7 @@ func TestRedirectsFileSupport(t *testing.T) {
182182
Contains("redirects file size cannot exceed"),
183183
),
184184
),
185-
Spec: "specs.ipfs.tech/http-gateways/web-redirects-file/#max-file-size",
185+
Spec: "https://specs.ipfs.tech/http-gateways/web-redirects-file/#max-file-size",
186186
},
187187
}...)
188188

tests/trustless_gateway_car_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ func TestTrustlessCarDagScopeAll(t *testing.T) {
408408

409409
func TestTrustlessCarEntityBytes(t *testing.T) {
410410
tooling.LogTestGroup(t, GroupBlockCar)
411-
tooling.LogSpecs(t, "specs.ipfs.tech/http-gateways/trustless-gateway/#entity-bytes-request-query-parameter")
411+
tooling.LogSpecs(t, "https://specs.ipfs.tech/http-gateways/trustless-gateway/#entity-bytes-request-query-parameter")
412412

413413
singleLayerHamtMultiBlockFilesFixture := car.MustOpenUnixfsCar("trustless_gateway_car/single-layer-hamt-with-multi-block-files.car")
414414
subdirWithMixedBlockFiles := car.MustOpenUnixfsCar("trustless_gateway_car/subdir-with-mixed-block-files.car")

tests/trustless_gateway_raw_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
func TestTrustlessRaw(t *testing.T) {
1616
tooling.LogTestGroup(t, GroupBlockCar)
17-
tooling.LogSpecs(t, "specs.ipfs.tech/http-gateways/trustless-gateway/#block-responses-application-vnd-ipld-raw")
17+
tooling.LogSpecs(t, "https://specs.ipfs.tech/http-gateways/trustless-gateway/#block-responses-application-vnd-ipld-raw")
1818

1919
fixture := car.MustOpenUnixfsCar("gateway-raw-block.car")
2020

0 commit comments

Comments
 (0)