#!/usr/bin/env node // Reads every results/dawn-node//*.json (one per CTS query, written by // run-shard.sh with --print-json) and tallies per-case status, overall and // broken down by top-level CTS area (the first path segment after // "webgpu:", e.g. "api", "shader", "idl", "web_platform"). Only walks // results/dawn-node/, so it reflects whatever fraction of the sharded run // has finished when it's run -- rerun it any time for a progress snapshot, // not just once the full run is done. import { readdirSync, readFileSync, statSync } from 'node:fs'; import { join } from 'node:path'; const SELF_DIR = new URL('.', import.meta.url).pathname; const ROOT = join(SELF_DIR, 'results', 'dawn-node'); function areaOf(testName) { // "webgpu:api,validation,...:..." -> "api,validation"; take the first two // comma-separated segments after the suite name, since "api" and "shader" // alone are too coarse (validation vs operation, validation vs execution). const afterSuite = testName.split(':')[1] ?? ''; const segments = afterSuite.split(','); return segments.slice(0, 2).join(',') || '(unknown)'; } const overall = new Map(); const byArea = new Map(); function bump(map, key, status) { if (!map.has(key)) map.set(key, new Map()); const counts = map.get(key); counts.set(status, (counts.get(status) ?? 0) + 1); } // dawn.node sometimes writes shader-compiler warnings ("warning:\nCompilation // log for...") straight to stdout ahead of --print-json's own output, and at // least one test (print_environment) logs its own JSON blob before the // real results JSON. Either contaminates a naive whole-file JSON.parse. The // real results object is always the last top-level `{"version": ...}` blob // in the file (that's what --print-json writes last), so find that instead // of assuming the file is clean JSON on its own. function parseResultsJSON(text) { const marker = '{\n "version"'; const lastStart = text.lastIndexOf(marker); if (lastStart === -1) return undefined; try { return JSON.parse(text.slice(lastStart)); } catch { return undefined; } } let filesRead = 0; let filesUnreadable = []; let shardDirs = []; try { shardDirs = readdirSync(ROOT).filter(name => /^\d+$/.test(name)); } catch (e) { console.error(`no results yet under ${ROOT}: ${e.message}`); process.exit(1); } for (const shard of shardDirs) { const shardDir = join(ROOT, shard); let entries; try { entries = readdirSync(shardDir); } catch { continue; } for (const entry of entries) { if (!entry.endsWith('.json')) continue; const path = join(shardDir, entry); if (statSync(path).size === 0) continue; const parsed = parseResultsJSON(readFileSync(path, 'utf8')); if (parsed === undefined) { // A query that crashed before printing any results JSON (a loader // error, typically) rather than one whose output was just // contaminated by stray stdout text. run-shard.sh's shard.log has the // detail for these. filesUnreadable.push(path); continue; } filesRead++; for (const [testName, result] of parsed.results ?? []) { const status = result.status ?? '(no status)'; bump(overall, '(all)', status); bump(byArea, areaOf(testName), status); } } } function printTable(map) { const statuses = new Set(); for (const counts of map.values()) { for (const s of counts.keys()) statuses.add(s); } const statusList = [...statuses].sort(); for (const [key, counts] of [...map.entries()].sort()) { const total = [...counts.values()].reduce((a, b) => a + b, 0); const parts = statusList .filter(s => counts.has(s)) .map(s => `${s}=${counts.get(s)}`); console.log(`${key}: total=${total} ${parts.join(' ')}`); } } console.log(`query files read: ${filesRead} of ${shardDirs.length} shards`); if (filesUnreadable.length) { console.log(`query files with no readable results (crashed before printing JSON): ${filesUnreadable.length}`); for (const p of filesUnreadable) console.log(` ${p}`); } console.log(''); console.log('overall:'); printTable(overall); console.log(''); console.log('by area:'); printTable(byArea);