├── .github
├── FUNDING.yml
└── workflows
│ └── lint.yml
├── .prettierignore
├── CNAME
├── LICENSE
├── favicon.png
├── index.html
├── repl
├── index.html
├── libjs.js
├── libjs.wasm
├── main.css
├── main.js
└── repl.js
├── test262
├── data
│ ├── per-file-bytecode-master.json
│ ├── per-file-bytecode-optimized-master.json
│ ├── per-file-master.json
│ └── results.json
├── fetch.js
├── index.html
├── main.css
├── main.js
├── per-file
│ ├── dir-icon.png
│ ├── index.html
│ ├── js-icon.png
│ ├── main.css
│ └── main.js
└── test-config.js
└── wasm
├── data
├── per-file-master.json
└── results.json
├── index.html
├── main.js
└── per-file
├── index.html
└── wasm-icon.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: linusg
2 | liberapay: linusg
3 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - name: Run prettier
11 | run: |
12 | npx prettier@2.8.8 --check .
13 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | test262/data/*
2 | wasm/data/*
3 | repl/libjs.js
4 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | libjs.dev
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Linus Groh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linusg/libjs-website/50fcff458444ee06b7de4540c643aa7226eb61ee/favicon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LibJS JavaScript engine
7 |
8 |
31 |
32 |
33 |
34 |
45 |
49 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/repl/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LibJS REPL
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 | LibJS REPL
20 |
21 |
press 'ctrl+enter' to run
22 |
press 'enter' to run
23 |
24 |
29 |
34 | <
35 |
36 |
Run!
37 |
40 |
41 |
42 |
43 |
Loading...
44 |
45 |
46 |
47 |
48 | Made by
49 | CxByte .
55 |
56 |
57 |
61 |
62 |
63 |
67 |
68 |
69 |
72 |
73 |
77 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/repl/libjs.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linusg/libjs-website/50fcff458444ee06b7de4540c643aa7226eb61ee/repl/libjs.wasm
--------------------------------------------------------------------------------
/repl/main.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --font-size: auto;
3 | }
4 |
5 | textarea {
6 | font-family: monospace;
7 | background: transparent;
8 | outline-style: dashed;
9 | outline-width: 2px;
10 | outline-color: var(--color-chart-border);
11 | outline-offset: 2px;
12 | border: none;
13 | color: var(--color-text);
14 | font-size: var(--font-size);
15 | }
16 |
17 | textarea[large] {
18 | height: 80vh;
19 | width: 100%;
20 | }
21 |
22 | #input {
23 | flex-grow: 1;
24 | padding: 4px;
25 | }
26 |
27 | #input-toggle {
28 | max-height: 30px;
29 | margin-top: 50%;
30 | background: var(--color-background);
31 | border: 2px solid var(--color-chart-border);
32 | color: var(--color-highlight);
33 | padding: 2px;
34 | }
35 |
36 | #input-toggle:hover {
37 | background: var(--color-highlight);
38 | color: var(--color-background);
39 | }
40 |
41 | #input-toggle.input-hidden {
42 | margin-right: 2px;
43 | }
44 |
45 | #input-toggle.input-shown {
46 | margin-right: 4px;
47 | margin-left: 2px;
48 | }
49 |
50 | #repl-window {
51 | display: flex;
52 | flex-direction: column;
53 | flex-grow: 1;
54 | }
55 |
56 | #repl-contents {
57 | outline-style: dashed;
58 | outline-width: 2px;
59 | outline-color: var(--color-chart-border);
60 | height: 80vh;
61 | padding: 4px;
62 | overflow: scroll;
63 | }
64 |
65 | #repl {
66 | display: flex;
67 | flex-direction: row;
68 | }
69 |
70 | main {
71 | margin: 40px 40px;
72 | }
73 |
74 | .repl-input-line {
75 | display: flex;
76 | flex-direction: row;
77 | }
78 |
79 | .repl-input-line > textarea {
80 | flex-grow: 1;
81 | margin-right: 8px;
82 | margin-top: 8px;
83 | margin-left: 8px;
84 | min-height: 1rem;
85 | }
86 |
87 | .hovered-related {
88 | background-color: var(--color-hover-highlight);
89 | }
90 |
91 | #loading-content {
92 | display: grid;
93 | }
94 |
--------------------------------------------------------------------------------
/repl/main.js:
--------------------------------------------------------------------------------
1 | const inputTemplate = document.getElementById("repl-input-template");
2 | const staticInputTemplate = document.getElementById(
3 | "repl-static-input-template"
4 | );
5 | const outputTemplate = document.getElementById("repl-output-template");
6 | const inputElement = document.getElementById("input");
7 | const inputTextArea = inputElement.querySelector("textarea");
8 | const outputElement = document.getElementById("repl-contents");
9 | const loadingContainer = document.getElementById("loading-content");
10 | const mainContainer = document.getElementById("main-content");
11 | const loadingText = document.getElementById("loading-text");
12 | const loadingProgress = document.getElementById("loading-progress");
13 | const headerDescriptionSpan = document.getElementById("header-description");
14 |
15 | (async function () {
16 | function updateLoading(name, { loaded, total, known }) {
17 | loadingText.innerText = `Loading ${name}...`;
18 | if (known) {
19 | loadingProgress.max = total;
20 | loadingProgress.value = loaded;
21 | } else {
22 | delete loadingProgress.max;
23 | delete loadingProgress.value;
24 | }
25 | }
26 |
27 | const repl = await createREPL({
28 | inputTemplate,
29 | staticInputTemplate,
30 | outputTemplate,
31 | inputElement,
32 | inputTextArea,
33 | outputElement,
34 | updateLoading,
35 | });
36 |
37 | const buildHash = Module.SERENITYOS_COMMIT;
38 | const shortenedBuildHash = buildHash.substring(0, 7);
39 | headerDescriptionSpan.innerHTML = ` (built from ${shortenedBuildHash} )`;
40 |
41 | loadingContainer.style.display = "none";
42 | mainContainer.style.display = "";
43 |
44 | repl.display("Ready!");
45 | inputTextArea.focus();
46 |
47 | const inputToggleButton = document.getElementById("input-toggle");
48 | const inputEditorTip = document.getElementById("input-editor-tip");
49 | const inputTip = document.getElementById("input-tip");
50 |
51 | inputToggleButton.addEventListener("click", () => {
52 | if (inputToggleButton.classList.contains("input-shown")) {
53 | inputToggleButton.classList.remove("input-shown");
54 | inputToggleButton.classList.add("input-hidden");
55 | inputToggleButton.textContent = ">";
56 | inputElement.style.display = "none";
57 | inputEditorTip.style.display = "none";
58 | inputTip.style.display = "";
59 | repl.allowDirectInput();
60 | } else {
61 | inputToggleButton.classList.remove("input-hidden");
62 | inputToggleButton.classList.add("input-shown");
63 | inputToggleButton.textContent = "<";
64 | inputElement.style.display = "";
65 | inputEditorTip.style.display = "";
66 | inputTip.style.display = "none";
67 | repl.prohibitDirectInput();
68 | inputTextArea.focus();
69 | }
70 | });
71 | })();
72 |
--------------------------------------------------------------------------------
/repl/repl.js:
--------------------------------------------------------------------------------
1 | if (typeof Module === "undefined")
2 | throw new Error("LibJS.js must be loaded before repl.js");
3 |
4 | function globalDisplayToUser(text) {
5 | globalDisplayToUser.repl.push(text);
6 | }
7 |
8 | async function createREPL(elements) {
9 | const repl = Object.create(null);
10 | elements.updateLoading("LibJS Runtime", { known: false });
11 |
12 | await new Promise((resolve) => addOnPostRun(resolve));
13 |
14 | elements.updateLoading("LibJS WebAssembly Module", { known: false });
15 | if (!runtimeInitialized) {
16 | initRuntime();
17 | }
18 |
19 | // The REPL only has access to a limited, virtual file system that does not contain time zone
20 | // information. Retrieve the current time zone from the running browser for LibJS to use.
21 | let timeZone;
22 |
23 | try {
24 | const dateTimeFormat = new Intl.DateTimeFormat();
25 | timeZone = Module.allocateUTF8(dateTimeFormat.resolvedOptions().timeZone);
26 | } catch {
27 | timeZone = Module.allocateUTF8("UTC");
28 | }
29 |
30 | if (Module._initialize_repl(timeZone) !== 0)
31 | throw new Error("Failed to initialize REPL");
32 |
33 | Module._free(timeZone);
34 |
35 | repl.private = {
36 | allowingDirectInput: false,
37 | activeInputs: [],
38 | inactiveInputs: [],
39 | outputs: [],
40 | prepareInput() {
41 | let node = elements.inputTemplate.content.children[0].cloneNode(true);
42 | return repl.private.attachInput(node, { directly: true });
43 | },
44 | prepareOutput() {
45 | let node = elements.outputTemplate.cloneNode(true).content.children[0];
46 | node = elements.outputElement.appendChild(node);
47 | node.addEventListener("mouseenter", () => {
48 | if (!node._input) return;
49 |
50 | node._input.classList.add("hovered-related");
51 | node._input._related.forEach((other) => {
52 | other.classList.add("hovered-related");
53 | });
54 | });
55 | node.addEventListener("mouseleave", () => {
56 | if (!node._input) return;
57 |
58 | node._input.classList.remove("hovered-related");
59 | node._input._related.forEach((other) => {
60 | other.classList.remove("hovered-related");
61 | });
62 | });
63 | return node;
64 | },
65 | attachInput(node, { directly }) {
66 | if (directly) {
67 | node = elements.outputElement.appendChild(node);
68 | node._isDirect = true;
69 | }
70 | node._related = [];
71 | const editor = node.querySelector("textarea");
72 | editor.addEventListener("keydown", (event) => {
73 | const requireCtrl = directly;
74 | if (event.keyCode == 13 && requireCtrl ^ event.ctrlKey) {
75 | event.preventDefault();
76 | repl.execute(node, editor.value);
77 | return false;
78 | }
79 | return true;
80 | });
81 | document
82 | .getElementById("run")
83 | .addEventListener("onclick", () => repl.execute(node, editor.value));
84 | node.addEventListener("mouseenter", () => {
85 | node._related.forEach((other) => {
86 | other.classList.add("hovered-related");
87 | });
88 | });
89 | node.addEventListener("mouseleave", () => {
90 | node._related.forEach((other) => {
91 | other.classList.remove("hovered-related");
92 | });
93 | });
94 | return node;
95 | },
96 | execute(text) {
97 | const encodedText = Module.allocateUTF8(text);
98 | let oldRepl = globalDisplayToUser.repl;
99 | try {
100 | globalDisplayToUser.repl = repl.private.outputs;
101 | Module._execute(encodedText);
102 | return repl.private.outputs;
103 | } finally {
104 | globalDisplayToUser.repl = oldRepl;
105 | repl.private.outputs = [];
106 | Module._free(encodedText);
107 | }
108 | },
109 | markRelated(node, input) {
110 | node._input = input;
111 | input._related.push(node);
112 | },
113 | };
114 |
115 | repl.private.attachInput(elements.inputElement, { directly: false });
116 |
117 | repl.display = (text, relatedInput = null) => {
118 | text.split("\n").forEach((line) => {
119 | const node = repl.private.prepareOutput();
120 | node.querySelector("pre").textContent = line;
121 | if (relatedInput !== null) {
122 | repl.private.markRelated(node, relatedInput);
123 | }
124 | });
125 | };
126 | repl.allowDirectInput = () => {
127 | repl.private.allowingDirectInput = true;
128 | repl.private.inactiveInputs.forEach((node) =>
129 | repl.private.attachInput(node, { directly: true })
130 | );
131 | repl.private.activeInputs = repl.private.inactiveInputs;
132 | repl.private.inactiveInputs = [];
133 | if (
134 | repl.private.allowingDirectInput &&
135 | repl.private.activeInputs.length == 0
136 | ) {
137 | repl.addInput();
138 | }
139 | };
140 | repl.prohibitDirectInput = () => {
141 | repl.private.allowingDirectInput = false;
142 | repl.private.activeInputs.forEach((node) => node.remove());
143 | repl.private.inactiveInputs = repl.private.inactiveInputs.concat(
144 | repl.private.activeInputs
145 | );
146 | repl.private.activeInputs = [];
147 | };
148 | repl.addInput = () => {
149 | const input = repl.private.prepareInput();
150 | repl.private.activeInputs.push(input);
151 | input.querySelector("textarea").focus();
152 | };
153 | repl.addStaticInput = (text) => {
154 | const input =
155 | elements.staticInputTemplate.cloneNode(true).content.children[0];
156 | input.querySelector("pre.content").textContent = text;
157 | input._related = [];
158 | return elements.outputElement.appendChild(input);
159 | };
160 | repl.execute = (input, text) => {
161 | repl.private.activeInputs = repl.private.activeInputs.filter(
162 | (i) => i !== input
163 | );
164 | let staticInput = repl.addStaticInput(text);
165 | let outputs = repl.private.execute(text).join("");
166 | if (outputs.endsWith("undefined\n"))
167 | outputs = outputs.substring(0, outputs.length - 10);
168 |
169 | repl.display(outputs, input);
170 |
171 | input._related.forEach((node) =>
172 | repl.private.markRelated(node, staticInput)
173 | );
174 | if (input._isDirect) {
175 | input.remove();
176 | }
177 |
178 | if (
179 | repl.private.allowingDirectInput &&
180 | repl.private.activeInputs.length == 0
181 | ) {
182 | repl.addInput();
183 | }
184 | };
185 |
186 | return repl;
187 | }
188 |
--------------------------------------------------------------------------------
/test262/fetch.js:
--------------------------------------------------------------------------------
1 | const fetchData = (url) => {
2 | const headers = new Headers();
3 | headers.append("pragma", "no-cache");
4 | headers.append("cache-control", "no-cache");
5 | return fetch(new Request(url), {
6 | method: "GET",
7 | headers,
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/test262/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LibJS test262 results
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 | LibJS test262 results
18 |
19 | Introduction
20 |
21 | These are the results of the
22 | SerenityOS
28 | JavaScript engine, LibJS, running the official ECMAScript conformance
29 | test suite
30 | test262
36 | as well as the
37 | test262 parser tests . All tests are re-run and the results updated automatically for
43 | every push to the master branch of the repository on GitHub. Dates and
44 | times are shown in your browser's timezone.
45 |
46 | Some milestones:
47 |
48 |
49 | Development of the engine started in March 2020 (see
50 | this YouTube video )
56 |
57 |
58 | Continuous running of test262 started in June 2021 (see
59 | this PR on GitHub ) — results before that time are not available
65 |
66 |
67 | In October 2021
68 | David
74 | reimplemented the test runner to use a streaming architecture (see
75 | this PR on GitHub ), resulting in a large performance improvement (the part that
81 | looks like a cliff).
82 |
83 |
84 |
85 | If you have any questions or want to help out with improving these
86 | test scores, feel free to get in touch on the
87 | SerenityOS Discord server
93 | (#js
).
94 |
95 |
96 |
97 | Per-file results
98 |
99 | Click here!
100 |
101 |
102 |
103 | Source code & Data
104 | Source code:
105 |
123 | Data (JSON):
124 |
161 |
162 |
163 | test262
164 | Loading...
165 |
166 |
167 |
168 |
169 |
170 | test262 (bytecode interpreter)
171 | Loading...
172 |
173 |
174 |
175 |
176 |
177 | test262 (bytecode interpreter with optimizations)
178 | Loading...
179 |
180 |
181 |
182 |
183 |
184 | test262 parser tests
185 | Loading...
186 |
187 |
188 |
189 |
190 |
191 | test262 performance
192 |
193 |
194 |
195 |
196 |
197 | test262 performance per test
198 |
199 |
200 |
201 |
202 |
203 | test262 performance (bytecode interpreter)
204 |
205 |
206 |
207 |
208 |
209 | test262 performance per test (bytecode interpreter)
210 |
211 |
212 |
213 |
214 |
215 | test262 performance (bytecode interpreter with optimizations)
216 |
217 |
218 |
219 |
220 |
221 |
222 | test262 performance per test (bytecode interpreter with optimizations)
223 |
224 |
225 |
228 |
229 |
230 |
231 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
253 |
257 |
258 |
259 |
--------------------------------------------------------------------------------
/test262/main.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --font-family: "Roboto", sans-serif;
3 | --font-family-heading: "Montserrat", sans-serif;
4 | --color-highlight: #1d60df;
5 | --color-hover-highlight: #1d60df40;
6 | --color-text: #312f2f;
7 | --color-background: #f6fcfc;
8 | --color-background-alt: #dfecec;
9 | --color-chart-border: rgba(0, 0, 0, 0.3);
10 | --color-chart-passed: #19db6e;
11 | --color-chart-failed: #f70c4e;
12 | --color-chart-skipped: #888888;
13 | --color-chart-metadata-error: #5c6bc0;
14 | --color-chart-harness-error: #26a69a;
15 | --color-chart-timeout-error: #26c6da;
16 | --color-chart-process-error: #ab47bc;
17 | --color-chart-runner-exception: #ff7043;
18 | --color-chart-todo-error: #ffca28;
19 | }
20 |
21 | @media (prefers-color-scheme: dark) {
22 | :root {
23 | --color-highlight: #03a9f4;
24 | --color-text: #dddddd;
25 | --color-background: #191922;
26 | --color-background-alt: #262635;
27 | --color-chart-border: rgba(255, 255, 255, 0.5);
28 | --color-chart-passed: #18b55d;
29 | --color-chart-failed: #c80f43;
30 | --color-chart-skipped: #aaaaaa;
31 | --color-chart-metadata-error: #3949ab;
32 | --color-chart-harness-error: #00897b;
33 | --color-chart-timeout-error: #00acc1;
34 | --color-chart-process-error: #8e24aa;
35 | --color-chart-runner-exception: #f4511e;
36 | --color-chart-todo-error: #ffb300;
37 | }
38 | }
39 |
40 | * {
41 | margin: 0;
42 | padding: 0;
43 | box-sizing: border-box;
44 | }
45 |
46 | body {
47 | background: var(--color-background);
48 | color: var(--color-text);
49 | font-family: var(--font-family);
50 | letter-spacing: 0.2px;
51 | line-height: 1.5;
52 | padding: 20px;
53 | }
54 |
55 | main,
56 | footer {
57 | max-width: 1200px;
58 | margin: 40px auto;
59 | }
60 |
61 | footer {
62 | text-align: center;
63 | }
64 |
65 | section {
66 | margin-top: 40px;
67 | }
68 |
69 | p {
70 | margin: 20px 0;
71 | }
72 |
73 | li {
74 | list-style-position: inside;
75 | }
76 |
77 | h1,
78 | h2 {
79 | font-family: var(--font-family-heading);
80 | font-weight: 700;
81 | }
82 |
83 | h1 {
84 | font-size: 40px;
85 | margin-bottom: 40px;
86 | text-align: center;
87 | }
88 |
89 | h2 {
90 | font-size: 30px;
91 | margin-bottom: 20px;
92 | }
93 |
94 | a,
95 | a:active,
96 | a:hover,
97 | a:visited {
98 | color: var(--color-highlight);
99 | }
100 |
101 | h2 a {
102 | text-decoration-thickness: 2px;
103 | }
104 |
105 | code {
106 | display: inline-block;
107 | padding: 0 2px;
108 | border-radius: 4px;
109 | font-size: 1rem;
110 | background: var(--color-background-alt);
111 | }
112 |
113 | .chart-wrapper {
114 | height: max(80vh, 400px);
115 | background: var(--color-background-alt);
116 | border-radius: 10px;
117 | padding: 20px;
118 | }
119 |
--------------------------------------------------------------------------------
/test262/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (() => {
4 | const { DateTime, Duration } = luxon;
5 |
6 | const backgroundColor = style.getPropertyValue("--color-background");
7 | const textColor = style.getPropertyValue("--color-text");
8 | const chartBorderColor = style.getPropertyValue("--color-chart-border");
9 | const fontFamily = style.getPropertyValue("font-family");
10 | const fontSize = parseInt(
11 | style.getPropertyValue("font-size").slice(0, -2),
12 | 10
13 | );
14 |
15 | Chart.defaults.borderColor = textColor;
16 | Chart.defaults.color = textColor;
17 | Chart.defaults.font.family = fontFamily;
18 | Chart.defaults.font.size = fontSize;
19 |
20 | // place tooltip's origin point under the cursor
21 | const tooltipPlugin = Chart.registry.getPlugin("tooltip");
22 | tooltipPlugin.positioners.underCursor = function (elements, eventPosition) {
23 | const pos = tooltipPlugin.positioners.average(elements);
24 |
25 | if (pos === false) {
26 | return false;
27 | }
28 |
29 | return {
30 | x: pos.x,
31 | y: eventPosition.y,
32 | };
33 | };
34 |
35 | class LineWithVerticalHoverLineController extends Chart.LineController {
36 | draw() {
37 | super.draw(arguments);
38 |
39 | if (!this.chart.tooltip._active.length) return;
40 |
41 | const { x } = this.chart.tooltip._active[0].element;
42 | const { top: topY, bottom: bottomY } = this.chart.chartArea;
43 | const ctx = this.chart.ctx;
44 |
45 | ctx.save();
46 | ctx.beginPath();
47 | ctx.moveTo(x, topY);
48 | ctx.lineTo(x, bottomY);
49 | ctx.lineWidth = 1;
50 | ctx.strokeStyle = chartBorderColor;
51 | ctx.stroke();
52 | ctx.restore();
53 | }
54 | }
55 |
56 | LineWithVerticalHoverLineController.id = "lineWithVerticalHoverLine";
57 | LineWithVerticalHoverLineController.defaults = Chart.LineController.defaults;
58 | Chart.register(LineWithVerticalHoverLineController);
59 |
60 | // This is when we started running the tests on Idan's self-hosted runner. Before that,
61 | // durations varied a lot across runs. See https://github.com/SerenityOS/serenity/pull/7718.
62 | const PERFORMANCE_CHART_START_DATE_TIME = DateTime.fromISO("2021-07-04");
63 |
64 | function prepareDataForCharts(data) {
65 | const charts = {
66 | ...Object.fromEntries(
67 | ["test262", "test262-bytecode", "test262-bytecode-optimized"].map(
68 | (name) => [
69 | name,
70 | {
71 | data: {
72 | [TestResult.PASSED]: [],
73 | [TestResult.FAILED]: [],
74 | [TestResult.SKIPPED]: [],
75 | [TestResult.METADATA_ERROR]: [],
76 | [TestResult.HARNESS_ERROR]: [],
77 | [TestResult.TIMEOUT_ERROR]: [],
78 | [TestResult.PROCESS_ERROR]: [],
79 | [TestResult.RUNNER_EXCEPTION]: [],
80 | [TestResult.TODO_ERROR]: [],
81 | [TestResult.DURATION]: [],
82 | },
83 | datasets: [],
84 | metadata: [],
85 | },
86 | ]
87 | )
88 | ),
89 | ["test262-parser-tests"]: {
90 | data: {
91 | [TestResult.PASSED]: [],
92 | [TestResult.FAILED]: [],
93 | },
94 | datasets: [],
95 | metadata: [],
96 | },
97 | ["test262-performance"]: {
98 | data: {
99 | [TestResult.DURATION]: [],
100 | },
101 | datasets: [],
102 | metadata: [],
103 | },
104 | ["test262-performance-per-test"]: {
105 | data: {
106 | [TestResult.DURATION]: [],
107 | },
108 | datasets: [],
109 | metadata: [],
110 | },
111 | ["test262-bytecode-performance"]: {
112 | data: {
113 | [TestResult.DURATION]: [],
114 | },
115 | datasets: [],
116 | metadata: [],
117 | },
118 | ["test262-bytecode-performance-per-test"]: {
119 | data: {
120 | [TestResult.DURATION]: [],
121 | },
122 | datasets: [],
123 | metadata: [],
124 | },
125 | ["test262-bytecode-optimized-performance"]: {
126 | data: {
127 | [TestResult.DURATION]: [],
128 | },
129 | datasets: [],
130 | metadata: [],
131 | },
132 | ["test262-bytecode-optimized-performance-per-test"]: {
133 | data: {
134 | [TestResult.DURATION]: [],
135 | },
136 | datasets: [],
137 | metadata: [],
138 | },
139 | };
140 |
141 | for (const entry of data) {
142 | for (const chart in charts) {
143 | const results = entry.tests[chart]?.results;
144 | if (!results) {
145 | continue;
146 | }
147 | charts[chart].metadata.push({
148 | commitTimestamp: entry.commit_timestamp,
149 | runTimestamp: entry.run_timestamp,
150 | duration: entry.tests[chart].duration,
151 | versions: entry.versions,
152 | total: results.total,
153 | });
154 | for (const testResult in charts[chart].data) {
155 | if (testResult === TestResult.DURATION) {
156 | continue;
157 | }
158 | charts[chart].data[testResult].push({
159 | x: entry.commit_timestamp * 1000,
160 | y: results[testResult] || 0,
161 | });
162 | }
163 | }
164 |
165 | const dt = DateTime.fromSeconds(entry.commit_timestamp);
166 | if (dt < PERFORMANCE_CHART_START_DATE_TIME) {
167 | continue;
168 | }
169 |
170 | // chart-test262-performance
171 | const performanceTests = entry.tests["test262"];
172 | const performanceChart = charts["test262-performance"];
173 | const performanceResults = performanceTests?.results;
174 | if (performanceResults) {
175 | performanceChart.metadata.push({
176 | commitTimestamp: entry.commit_timestamp,
177 | runTimestamp: entry.run_timestamp,
178 | duration: performanceTests.duration,
179 | versions: entry.versions,
180 | total: performanceResults.total,
181 | });
182 | performanceChart.data["duration"].push({
183 | x: entry.commit_timestamp * 1000,
184 | y: performanceTests.duration,
185 | });
186 | }
187 |
188 | // chart-test262-performance-per-test
189 | const performancePerTestTests = entry.tests["test262"];
190 | const performancePerTestChart = charts["test262-performance-per-test"];
191 | const performancePerTestResults = performancePerTestTests?.results;
192 | if (performancePerTestResults) {
193 | performancePerTestChart.metadata.push({
194 | commitTimestamp: entry.commit_timestamp,
195 | runTimestamp: entry.run_timestamp,
196 | duration:
197 | performancePerTestTests.duration / performancePerTestResults.total,
198 | versions: entry.versions,
199 | total: performancePerTestResults.total,
200 | });
201 | performancePerTestChart.data["duration"].push({
202 | x: entry.commit_timestamp * 1000,
203 | y: performancePerTestTests.duration / performancePerTestResults.total,
204 | });
205 | }
206 |
207 | // chart-test262-bytecode-performance
208 | const byteCodePerformanceTests = entry.tests["test262-bytecode"];
209 | const byteCodePerformanceChart = charts["test262-bytecode-performance"];
210 | const byteCodePerformanceResults = byteCodePerformanceTests?.results;
211 | if (byteCodePerformanceResults) {
212 | byteCodePerformanceChart.metadata.push({
213 | commitTimestamp: entry.commit_timestamp,
214 | runTimestamp: entry.run_timestamp,
215 | duration: byteCodePerformanceTests.duration,
216 | versions: entry.versions,
217 | total: byteCodePerformanceResults.total,
218 | });
219 | byteCodePerformanceChart.data["duration"].push({
220 | x: entry.commit_timestamp * 1000,
221 | y: byteCodePerformanceTests.duration,
222 | });
223 | }
224 |
225 | // chart-test262-bytecode-performance-per-test
226 | const byteCodePerformancePerTestTests = entry.tests["test262-bytecode"];
227 | const byteCodePerformancePerTestChart =
228 | charts["test262-bytecode-performance-per-test"];
229 | const byteCodePerformancePerTestResults =
230 | byteCodePerformancePerTestTests?.results;
231 | if (byteCodePerformancePerTestResults) {
232 | byteCodePerformancePerTestChart.metadata.push({
233 | commitTimestamp: entry.commit_timestamp,
234 | runTimestamp: entry.run_timestamp,
235 | duration:
236 | byteCodePerformancePerTestTests.duration /
237 | byteCodePerformancePerTestResults.total,
238 | versions: entry.versions,
239 | total: byteCodePerformancePerTestResults.total,
240 | });
241 | byteCodePerformancePerTestChart.data["duration"].push({
242 | x: entry.commit_timestamp * 1000,
243 | y:
244 | byteCodePerformancePerTestTests.duration /
245 | byteCodePerformancePerTestResults.total,
246 | });
247 | }
248 |
249 | // chart-test262-bytecode-optimized-performance
250 | const byteCodeOptimizedPerformanceTests =
251 | entry.tests["test262-bytecode-optimized"];
252 | const byteCodeOptimizedPerformanceChart =
253 | charts["test262-bytecode-optimized-performance"];
254 | const byteCodeOptimizedPerformanceResults =
255 | byteCodeOptimizedPerformanceTests?.results;
256 | if (byteCodeOptimizedPerformanceResults) {
257 | byteCodeOptimizedPerformanceChart.metadata.push({
258 | commitTimestamp: entry.commit_timestamp,
259 | runTimestamp: entry.run_timestamp,
260 | duration: byteCodeOptimizedPerformanceTests.duration,
261 | versions: entry.versions,
262 | total: byteCodeOptimizedPerformanceResults.total,
263 | });
264 | byteCodeOptimizedPerformanceChart.data["duration"].push({
265 | x: entry.commit_timestamp * 1000,
266 | y: byteCodeOptimizedPerformanceTests.duration,
267 | });
268 | }
269 |
270 | // chart-test262-bytecode-optimized-performance-per-test
271 | const byteCodeOptimizedPerformancePerTestTests =
272 | entry.tests["test262-bytecode-optimized"];
273 | const byteCodeOptimizedPerformancePerTestChart =
274 | charts["test262-bytecode-optimized-performance-per-test"];
275 | const byteCodeOptimizedPerformancePerTestResults =
276 | byteCodeOptimizedPerformancePerTestTests?.results;
277 | if (byteCodeOptimizedPerformancePerTestResults) {
278 | byteCodeOptimizedPerformancePerTestChart.metadata.push({
279 | commitTimestamp: entry.commit_timestamp,
280 | runTimestamp: entry.run_timestamp,
281 | duration:
282 | byteCodeOptimizedPerformancePerTestTests.duration /
283 | byteCodeOptimizedPerformancePerTestResults.total,
284 | versions: entry.versions,
285 | total: byteCodeOptimizedPerformancePerTestResults.total,
286 | });
287 | byteCodeOptimizedPerformancePerTestChart.data["duration"].push({
288 | x: entry.commit_timestamp * 1000,
289 | y:
290 | byteCodeOptimizedPerformancePerTestTests.duration /
291 | byteCodeOptimizedPerformancePerTestResults.total,
292 | });
293 | }
294 | }
295 |
296 | for (const chart in charts) {
297 | for (const testResult in charts[chart].data) {
298 | charts[chart].datasets.push({
299 | label: TestResultLabels[testResult],
300 | data: charts[chart].data[testResult],
301 | backgroundColor: TestResultColors[testResult],
302 | borderWidth: 2,
303 | borderColor: chartBorderColor,
304 | pointRadius: 0,
305 | pointHoverRadius: 0,
306 | fill: true,
307 | });
308 | }
309 | delete charts[chart].data;
310 | }
311 |
312 | return { charts };
313 | }
314 |
315 | function initializeChart(
316 | element,
317 | { datasets, metadata },
318 | { xAxisTitle = "Time", yAxisTitle = "Number of tests" } = {}
319 | ) {
320 | const ctx = element.getContext("2d");
321 |
322 | new Chart(ctx, {
323 | type: "lineWithVerticalHoverLine",
324 | data: {
325 | datasets,
326 | },
327 | options: {
328 | parsing: false,
329 | normalized: true,
330 | responsive: true,
331 | maintainAspectRatio: false,
332 | animation: false,
333 | plugins: {
334 | zoom: {
335 | zoom: {
336 | mode: "x",
337 | wheel: {
338 | enabled: true,
339 | },
340 | },
341 | pan: {
342 | enabled: true,
343 | mode: "x",
344 | },
345 | },
346 | hover: {
347 | mode: "index",
348 | intersect: false,
349 | },
350 | tooltip: {
351 | mode: "index",
352 | intersect: false,
353 | usePointStyle: true,
354 | boxWidth: 12,
355 | boxHeight: 12,
356 | padding: 20,
357 | position: "underCursor",
358 | titleColor: textColor,
359 | bodyColor: textColor,
360 | footerColor: textColor,
361 | footerFont: { weight: "normal" },
362 | footerMarginTop: 20,
363 | backgroundColor: backgroundColor,
364 | callbacks: {
365 | title: () => {
366 | return null;
367 | },
368 | beforeBody: (context) => {
369 | const { dataIndex } = context[0];
370 | const { total } = metadata[dataIndex];
371 | const formattedValue = total.toLocaleString("en-US");
372 | // Leading spaces to make up for missing color circle
373 | return ` Number of tests: ${formattedValue}`;
374 | },
375 | label: (context) => {
376 | // Space as padding between color circle and label
377 | const formattedValue = context.parsed.y.toLocaleString("en-US");
378 | if (
379 | context.dataset.label !==
380 | TestResultLabels[TestResult.DURATION]
381 | ) {
382 | const { total } = metadata[context.dataIndex];
383 | const percentOfTotal = (
384 | (context.parsed.y / total) *
385 | 100
386 | ).toFixed(2);
387 | return ` ${context.dataset.label}: ${formattedValue} (${percentOfTotal}%)`;
388 | } else {
389 | return ` ${context.dataset.label}: ${formattedValue}`;
390 | }
391 | },
392 |
393 | footer: (context) => {
394 | const { dataIndex } = context[0];
395 | const {
396 | commitTimestamp,
397 | duration: durationSeconds,
398 | versions,
399 | } = metadata[dataIndex];
400 | const dateTime = DateTime.fromSeconds(commitTimestamp);
401 | const duration = Duration.fromMillis(durationSeconds * 1000);
402 | const serenityVersion = versions.serenity.substring(0, 7);
403 | // prettier-ignore
404 | const libjsTest262Version = versions["libjs-test262"].substring(0, 7);
405 | const test262Version = versions.test262.substring(0, 7);
406 | // prettier-ignore
407 | const test262ParserTestsVersion = versions["test262-parser-tests"].substring(0, 7);
408 | return `\
409 | Committed on ${dateTime.toLocaleString(DateTime.DATETIME_SHORT)}, \
410 | run took ${duration.toISOTime()}
411 |
412 | Versions: serenity@${serenityVersion}, libjs-test262@${libjsTest262Version},
413 | test262@${test262Version}, test262-parser-tests@${test262ParserTestsVersion}`;
414 | },
415 | },
416 | },
417 | legend: {
418 | align: "end",
419 | labels: {
420 | usePointStyle: true,
421 | boxWidth: 10,
422 | // Only include passed, failed, TODO, and crashed in the legend
423 | filter: ({ text }) =>
424 | text === TestResultLabels[TestResult.PASSED] ||
425 | text === TestResultLabels[TestResult.FAILED] ||
426 | text === TestResultLabels[TestResult.TODO_ERROR] ||
427 | text === TestResultLabels[TestResult.PROCESS_ERROR],
428 | },
429 | },
430 | },
431 | scales: {
432 | x: {
433 | type: "time",
434 | title: {
435 | display: true,
436 | text: xAxisTitle,
437 | },
438 | grid: {
439 | borderColor: textColor,
440 | color: "transparent",
441 | borderWidth: 2,
442 | },
443 | },
444 | y: {
445 | stacked: true,
446 | beginAtZero: true,
447 | title: {
448 | display: true,
449 | text: yAxisTitle,
450 | },
451 | grid: {
452 | borderColor: textColor,
453 | color: chartBorderColor,
454 | borderWidth: 2,
455 | },
456 | },
457 | },
458 | },
459 | });
460 | }
461 |
462 | function initializeSummary(
463 | element,
464 | runTimestamp,
465 | commitHash,
466 | durationSeconds,
467 | results
468 | ) {
469 | const dateTime = DateTime.fromSeconds(runTimestamp);
470 | const duration = Duration.fromMillis(durationSeconds * 1000);
471 | const passed = results[TestResult.PASSED];
472 | const total = results.total;
473 | const percent = ((passed / total) * 100).toFixed(2);
474 | element.innerHTML = `
475 | The last test run was on
476 | ${dateTime.toLocaleString(DateTime.DATETIME_SHORT)}
477 | for commit
478 |
479 |
485 | ${commitHash.slice(0, 7)}
486 |
487 |
488 | and took ${duration.toISOTime()} .
489 | ${passed} of ${total} tests passed, i.e. ${percent}% .
490 | `;
491 | }
492 |
493 | function initialize(data) {
494 | const { charts } = prepareDataForCharts(data);
495 | initializeChart(document.getElementById("chart-test262"), charts.test262);
496 | initializeChart(
497 | document.getElementById("chart-test262-bytecode"),
498 | charts["test262-bytecode"]
499 | );
500 | initializeChart(
501 | document.getElementById("chart-test262-bytecode-optimized"),
502 | charts["test262-bytecode-optimized"]
503 | );
504 | initializeChart(
505 | document.getElementById("chart-test262-parser-tests"),
506 | charts["test262-parser-tests"]
507 | );
508 | initializeChart(
509 | document.getElementById("chart-test262-performance"),
510 | charts["test262-performance"],
511 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
512 | );
513 | initializeChart(
514 | document.getElementById("chart-test262-performance-per-test"),
515 | charts["test262-performance-per-test"],
516 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
517 | );
518 | initializeChart(
519 | document.getElementById("chart-test262-bytecode-performance"),
520 | charts["test262-bytecode-performance"],
521 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
522 | );
523 | initializeChart(
524 | document.getElementById("chart-test262-bytecode-performance-per-test"),
525 | charts["test262-bytecode-performance-per-test"],
526 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
527 | );
528 | initializeChart(
529 | document.getElementById("chart-test262-bytecode-optimized-performance"),
530 | charts["test262-bytecode-optimized-performance"],
531 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
532 | );
533 | initializeChart(
534 | document.getElementById(
535 | "chart-test262-bytecode-optimized-performance-per-test"
536 | ),
537 | charts["test262-bytecode-optimized-performance-per-test"],
538 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
539 | );
540 | const last = data.slice(-1)[0];
541 | initializeSummary(
542 | document.getElementById("summary-test262"),
543 | last.run_timestamp,
544 | last.versions.serenity,
545 | last.tests.test262.duration,
546 | last.tests.test262.results
547 | );
548 | initializeSummary(
549 | document.getElementById("summary-test262-bytecode"),
550 | last.run_timestamp,
551 | last.versions.serenity,
552 | last.tests["test262-bytecode"].duration,
553 | last.tests["test262-bytecode"].results
554 | );
555 | initializeSummary(
556 | document.getElementById("summary-test262-bytecode-optimized"),
557 | last.run_timestamp,
558 | last.versions.serenity,
559 | last.tests["test262-bytecode-optimized"].duration,
560 | last.tests["test262-bytecode-optimized"].results
561 | );
562 | initializeSummary(
563 | document.getElementById("summary-test262-parser-tests"),
564 | last.run_timestamp,
565 | last.versions.serenity,
566 | last.tests["test262-parser-tests"].duration,
567 | last.tests["test262-parser-tests"].results
568 | );
569 | }
570 |
571 | document.addEventListener("DOMContentLoaded", () => {
572 | fetchData("data/results.json")
573 | .then((response) => response.json())
574 | .then((data) => {
575 | data.sort((a, b) =>
576 | a.commit_timestamp === b.commit_timestamp
577 | ? 0
578 | : a.commit_timestamp < b.commit_timestamp
579 | ? -1
580 | : 1
581 | );
582 | return data;
583 | })
584 | .then((data) => initialize(data));
585 | });
586 | })();
587 |
--------------------------------------------------------------------------------
/test262/per-file/dir-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linusg/libjs-website/50fcff458444ee06b7de4540c643aa7226eb61ee/test262/per-file/dir-icon.png
--------------------------------------------------------------------------------
/test262/per-file/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LibJS test262 per-file results
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Search:
22 |
23 |
24 |
25 | Filter mode:
26 |
27 |
28 | Per-file results
29 |
30 |
Loading...
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
59 |
60 |
61 |
62 |
75 |
76 |
80 |
84 |
85 |
86 |
87 |
88 |
89 | (Open on GitHub )
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
108 |
109 |
110 | (Open on GitHub )
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/test262/per-file/js-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linusg/libjs-website/50fcff458444ee06b7de4540c643aa7226eb61ee/test262/per-file/js-icon.png
--------------------------------------------------------------------------------
/test262/per-file/main.css:
--------------------------------------------------------------------------------
1 | .hidden {
2 | display: none;
3 | }
4 |
5 | #legend {
6 | float: right;
7 | }
8 |
9 | #search {
10 | float: right;
11 | }
12 |
13 | #search-mode {
14 | float: right;
15 | margin-right: 5px;
16 | }
17 |
18 | .search-warning {
19 | align-content: center;
20 | text-align: center;
21 | border: solid 1px #4d4d0a;
22 | background: #f0db4f;
23 | }
24 |
25 | @media (prefers-color-scheme: dark) {
26 | .search-warning {
27 | border-color: #f0db4f;
28 | background-color: #4d4d0a;
29 | }
30 | }
31 |
32 | .legend-item {
33 | display: inline-flex;
34 | align-items: center;
35 | margin-left: 10px;
36 | }
37 |
38 | .legend-circle {
39 | width: 20px;
40 | height: 20px;
41 | display: inline-block;
42 | border-radius: 100%;
43 | margin-right: 5px;
44 | }
45 |
46 | .result-separator {
47 | height: 60px;
48 | }
49 |
50 | .tree-node-status-container {
51 | flex-grow: 2;
52 | padding-left: 20px;
53 | }
54 |
55 | .tree-node-status {
56 | display: flex;
57 | flex-flow: column;
58 | text-align: right;
59 | }
60 |
61 | .mode-summary-container {
62 | display: flex;
63 | flex-flow: row;
64 | justify-content: space-between;
65 | }
66 |
67 | .mode-bar-container {
68 | display: flex;
69 | flex-flow: row;
70 | width: 25vw;
71 | }
72 |
73 | .mode-result-text {
74 | flex-grow: 1;
75 | padding-right: 5px;
76 | }
77 |
78 | .tree-node-action {
79 | display: flex;
80 | align-items: center;
81 | }
82 |
83 | .tree-node-name {
84 | margin-left: 10px;
85 | }
86 |
87 | .node {
88 | display: flex;
89 | flex-flow: row;
90 | align-items: center;
91 | margin-bottom: 10px;
92 | }
93 |
--------------------------------------------------------------------------------
/test262/per-file/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const initialPathInTree = [window.config.initialPathInTree];
4 |
5 | let resultsNode;
6 | let legendNode;
7 | let searchInputNode;
8 | let summaryLabel;
9 | let summaryStatusLabel;
10 | let leafTreeNodeTemplate;
11 | let nonLeafTreeNodeTemplate;
12 | let pathInTree = new URL(location.href).searchParams
13 | .get("path")
14 | ?.split("/") ?? [...initialPathInTree];
15 | let tree;
16 |
17 | const resultsObject = Object.create(null);
18 | const legendResults = [
19 | TestResult.PASSED,
20 | TestResult.FAILED,
21 | TestResult.PROCESS_ERROR,
22 | TestResult.TODO_ERROR,
23 | ];
24 |
25 | const shownResultTypes = new Set(Object.values(TestResult));
26 | let searchQuery = "";
27 | let filtering = false;
28 |
29 | // Behavior: Initially show all
30 | // Click text -> disable that one
31 | // Click circle -> show only that one
32 |
33 | function initialize(data, modeName) {
34 | let mode;
35 | if (modeName === "") {
36 | mode = "AST";
37 | } else if (modeName === "bytecode") {
38 | mode = "Bytecode";
39 | } else if (modeName === "bytecode-optimized") {
40 | mode = "Optimized Bytecode";
41 | } else {
42 | mode = modeName;
43 | }
44 |
45 | // Do a pass and generate the tree.
46 | for (const testPath in data.results) {
47 | const segments = testPath.split("/");
48 | const fileName = segments.pop();
49 | let testObject = resultsObject;
50 | for (const pathSegment of segments) {
51 | if (!(pathSegment in testObject)) {
52 | testObject[pathSegment] = {
53 | children: Object.create(null),
54 | aggregatedResults: null,
55 | };
56 | }
57 |
58 | testObject = testObject[pathSegment].children;
59 | }
60 |
61 | if (!(fileName in testObject)) {
62 | testObject[fileName] = {
63 | children: null,
64 | results: Object.create(null),
65 | };
66 | }
67 |
68 | testObject[fileName].results[mode] = data.results[testPath].toLowerCase();
69 | }
70 | }
71 |
72 | function generateResults() {
73 | function constructTree(results) {
74 | if (results.children === null) {
75 | results.aggregatedResults = Object.fromEntries(
76 | Object.keys(results.results).map((name) => [
77 | name,
78 | { [results.results[name]]: 1 },
79 | ])
80 | );
81 |
82 | return;
83 | }
84 |
85 | for (const name in results.children) {
86 | constructTree(results.children[name]);
87 | }
88 |
89 | for (const name in results.children) {
90 | const childResults = results.children[name];
91 | results.aggregatedResults = Object.keys(
92 | childResults.aggregatedResults
93 | ).reduce((acc, mode) => {
94 | if (!(mode in acc)) acc[mode] = {};
95 | const modeAcc = acc[mode];
96 | const stats = childResults.aggregatedResults[mode];
97 | for (const name in stats) {
98 | if (name in modeAcc) modeAcc[name] += stats[name];
99 | else modeAcc[name] = stats[name];
100 | }
101 | return acc;
102 | }, results.aggregatedResults || {});
103 | }
104 | }
105 |
106 | // Now do another pass and aggregate the results.
107 | let results = {
108 | children: resultsObject,
109 | aggregatedResults: {},
110 | };
111 | constructTree(results);
112 | tree = results;
113 |
114 | resultsNode = document.getElementById("results");
115 | legendNode = document.getElementById("legend");
116 | searchInputNode = document.getElementById("search-input");
117 | summaryLabel = document.getElementById("summary");
118 | summaryStatusLabel = document.getElementById("summary-status");
119 | leafTreeNodeTemplate = document.getElementById("leaf-tree-node-template");
120 | nonLeafTreeNodeTemplate = document.getElementById(
121 | "nonleaf-tree-node-template"
122 | );
123 |
124 | // Now make a nice lazy-loaded collapsible tree in `resultsNode`.
125 | regenerateResults(resultsNode);
126 |
127 | summaryStatusLabel.classList.remove("hidden");
128 |
129 | legendNode.innerHTML = legendResults
130 | .map((result) => {
131 | const color = TestResultColors[result];
132 | const label = TestResultLabels[result];
133 | return `
134 |
135 |
136 | ${label}
137 |
138 | `;
139 | })
140 | .join(" ");
141 |
142 | function legendChanged() {
143 | legendNode.querySelectorAll(".legend-item").forEach((legendItem) => {
144 | if (shownResultTypes.has(legendItem.getAttribute("data-type")))
145 | legendItem.style.textDecoration = null;
146 | else legendItem.style.textDecoration = "line-through";
147 | });
148 | }
149 |
150 | legendNode.querySelectorAll(".legend-item").forEach((legendItem) => {
151 | legendItem.onclick = (event) => {
152 | const clickedCircle = event.target !== legendItem;
153 | const resultType = legendItem.getAttribute("data-type");
154 | if (clickedCircle) {
155 | if (shownResultTypes.size === 1 && shownResultTypes.has(resultType)) {
156 | Object.values(TestResult).forEach((v) => shownResultTypes.add(v));
157 | } else {
158 | shownResultTypes.clear();
159 | shownResultTypes.add(resultType);
160 | }
161 | } else {
162 | if (shownResultTypes.has(resultType)) {
163 | shownResultTypes.delete(resultType);
164 | } else {
165 | shownResultTypes.add(resultType);
166 | }
167 | }
168 |
169 | legendChanged();
170 | regenerateResults(resultsNode);
171 | };
172 | });
173 |
174 | searchInputNode.oninput = (event) => {
175 | searchQuery = event.target.value.toLowerCase();
176 | regenerateResults(resultsNode);
177 | };
178 |
179 | const filterModeCheckbox = document.getElementById("search-mode-checkbox");
180 | filterModeCheckbox.checked = filtering;
181 | filterModeCheckbox.oninput = (event) => {
182 | filtering = event.target.checked;
183 | regenerateResults(resultsNode);
184 | };
185 |
186 | // We hide the search input and filter mode checkbox until the rest is loaded
187 | document.getElementById("search").classList.remove("hidden");
188 | document.getElementById("search-mode").classList.remove("hidden");
189 | }
190 |
191 | window.onpopstate = (event) => {
192 | pathInTree = event.state?.pathInTree ?? [...initialPathInTree];
193 | regenerateResults(resultsNode);
194 | };
195 |
196 | function navigate() {
197 | if (!filtering) {
198 | searchInputNode.value = "";
199 | searchQuery = "";
200 | }
201 | history.pushState(
202 | { pathInTree },
203 | pathInTree[pathInTree.length - 1],
204 | generateQueryString(pathInTree)
205 | );
206 | regenerateResults(resultsNode);
207 | }
208 |
209 | function goToParentDirectory(count) {
210 | for (let i = 0; i < count; ++i) {
211 | pathInTree.pop();
212 | }
213 | navigate();
214 | }
215 |
216 | function generateQueryString(pathSegments) {
217 | return `?path=${pathSegments.join("/")}`;
218 | }
219 |
220 | function generateSummary(results) {
221 | summaryLabel.innerHTML = "/ ";
222 | for (let i = 0; i < pathInTree.length; ++i) {
223 | const pathSegment = pathInTree[i];
224 | const pathSegmentLink = document.createElement("a");
225 | pathSegmentLink.textContent = pathSegment;
226 | pathSegmentLink.href = generateQueryString(pathInTree.slice(0, i + 1));
227 | pathSegmentLink.onclick = (event) => {
228 | if (event.metaKey || event.ctrlKey) return;
229 | event.preventDefault();
230 | goToParentDirectory(pathInTree.length - i - 1);
231 | };
232 | summaryLabel.appendChild(pathSegmentLink);
233 | if (i < pathInTree.length - 1) {
234 | summaryLabel.insertAdjacentHTML("beforeend", " / ");
235 | }
236 | }
237 | summaryStatusLabel.innerHTML = generateStatus(results.aggregatedResults);
238 | }
239 |
240 | function generateChildNode(childName, child, filepath) {
241 | const template =
242 | child.children === null ? leafTreeNodeTemplate : nonLeafTreeNodeTemplate;
243 | const childNode = template.content.children[0].cloneNode(true);
244 | childNode.querySelector(".tree-node-name").textContent = childName;
245 | childNode.querySelector(".tree-node-name").title = filepath;
246 | childNode.querySelector(".tree-node-status").innerHTML = generateStatus(
247 | child.aggregatedResults
248 | );
249 | childNode.querySelector(".tree-node-github-url").href =
250 | window.config.generateGitHubURLFromTestPath(filepath);
251 | return childNode;
252 | }
253 |
254 | function makeChildNavigable(childNode, extraPathParts) {
255 | const actionNode = childNode.querySelector(".tree-node-action");
256 |
257 | actionNode.href = generateQueryString([...pathInTree, ...extraPathParts]);
258 | actionNode.onclick = function (event) {
259 | if (event.metaKey || event.ctrlKey) return;
260 | event.preventDefault();
261 | for (const part of extraPathParts) pathInTree.push(part);
262 | navigate();
263 | };
264 | }
265 |
266 | function sortResultsByTypeAndName([lhsName, lhsResult], [rhsName, rhsResult]) {
267 | if ((lhsResult.children === null) === (rhsResult.children === null))
268 | return lhsName.localeCompare(rhsName);
269 | return lhsResult.children === null ? 1 : -1;
270 | }
271 |
272 | // Setting this to false means filters check both AST and BC.
273 | let checkASTOnly = true;
274 |
275 | function entryHasFilteredResultType([, child]) {
276 | if (checkASTOnly && "AST" in child.aggregatedResults) {
277 | return Object.keys(child.aggregatedResults.AST).some((type) =>
278 | shownResultTypes.has(type)
279 | );
280 | }
281 |
282 | return Object.values(child.aggregatedResults).some((testType) =>
283 | Object.keys(testType).some((type) => shownResultTypes.has(type))
284 | );
285 | }
286 |
287 | function regenerateResults(targetNode) {
288 | const needle = searchQuery.length >= 3 ? searchQuery : "";
289 |
290 | for (const child of Array.prototype.slice.call(targetNode.children)) {
291 | child.remove();
292 | }
293 |
294 | const results = pathInTree.reduce((acc, x) => acc.children[x], tree);
295 | generateSummary(results);
296 |
297 | let nodes;
298 | if (!needle) {
299 | nodes = Object.entries(results.children)
300 | .filter(entryHasFilteredResultType)
301 | .sort(sortResultsByTypeAndName)
302 | .map(([childName, child]) => {
303 | const childNode = generateChildNode(
304 | childName,
305 | child,
306 | `${pathInTree.join("/")}/${childName}`
307 | );
308 |
309 | const isLeaf = child.children === null;
310 | if (!isLeaf) {
311 | makeChildNavigable(childNode, [childName]);
312 | }
313 | return childNode;
314 | });
315 | } else {
316 | function searchResults(result, allChildren = false, extraPath = "") {
317 | return Object.entries(result)
318 | .filter(entryHasFilteredResultType)
319 | .flatMap(([childName, child]) => {
320 | const isLeaf = child.children === null;
321 |
322 | let isSearchedFor = childName.toLowerCase().includes(needle);
323 | let relativePath = extraPath + childName;
324 | if (isLeaf) {
325 | if (isSearchedFor) return [[childName, child, relativePath]];
326 | else return [];
327 | }
328 |
329 | const childrenResults = searchResults(
330 | child.children,
331 | false,
332 | relativePath + "/"
333 | );
334 | if (isSearchedFor)
335 | childrenResults.push([childName, child, relativePath]);
336 |
337 | return childrenResults;
338 | })
339 | .sort(sortResultsByTypeAndName);
340 | }
341 |
342 | function filterResults(result) {
343 | function filterInternal(result, allChildren = false, extraPath = "") {
344 | return Object.entries(result)
345 | .filter(entryHasFilteredResultType)
346 | .map(([childName, child]) => {
347 | const isLeaf = child.children === null;
348 |
349 | let isSearchedFor = childName.toLowerCase().includes(needle);
350 | let relativePath = extraPath + childName;
351 |
352 | if (isLeaf) {
353 | if (isSearchedFor || allChildren)
354 | return [childName, child, relativePath, null];
355 | return [];
356 | }
357 | const childrenResults = filterInternal(
358 | child.children,
359 | isSearchedFor,
360 | relativePath + "/"
361 | );
362 | if (!isSearchedFor && childrenResults.length === 0 && !allChildren)
363 | return [];
364 |
365 | return [childName, child, relativePath, childrenResults];
366 | })
367 | .filter((i) => i.length > 0)
368 | .sort(sortResultsByTypeAndName);
369 | }
370 |
371 | let results = filterInternal(
372 | result,
373 | pathInTree.join("/").toLowerCase().includes(needle)
374 | );
375 |
376 | while (results.length === 1 && results[0][3] !== null)
377 | results = results[0][3];
378 |
379 | return results;
380 | }
381 |
382 | const maxResultsShown = 500;
383 | const foundResults = filtering
384 | ? filterResults(results.children)
385 | : searchResults(results.children);
386 | nodes = foundResults
387 | .filter((_, i) => i < maxResultsShown)
388 | .map(([childName, child, relativePath]) => {
389 | const childNode = generateChildNode(
390 | childName,
391 | child,
392 | `${pathInTree.join("/")}/${relativePath}`
393 | );
394 |
395 | if (child.children !== null) {
396 | const extraPathParts = [
397 | ...relativePath.split("/").filter((s) => s.length > 0),
398 | ];
399 | makeChildNavigable(childNode, extraPathParts, targetNode);
400 | }
401 |
402 | return childNode;
403 | });
404 |
405 | if (foundResults.length > maxResultsShown) {
406 | const extraNode = document.createElement("p");
407 | extraNode.innerText = `Only show the first ${maxResultsShown} of ${foundResults.length} results.`;
408 | extraNode.classList.add("search-warning");
409 | nodes.push(extraNode);
410 | }
411 | }
412 |
413 | nodes.forEach((node) => targetNode.appendChild(node));
414 | }
415 |
416 | function color(name) {
417 | return TestResultColors[name] || "black";
418 | }
419 |
420 | function resultAwareSort(names) {
421 | const resultOrder = [
422 | TestResult.PASSED,
423 | TestResult.FAILED,
424 | TestResult.SKIPPED,
425 | TestResult.PROCESS_ERROR,
426 | TestResult.TODO_ERROR,
427 | TestResult.METADATA_ERROR,
428 | TestResult.HARNESS_ERROR,
429 | TestResult.TIMEOUT_ERROR,
430 | TestResult.RUNNER_EXCEPTION,
431 | TestResult.DURATION,
432 | ];
433 |
434 | return names.sort((a, b) => {
435 | const aIndex = resultOrder.indexOf(a);
436 | const bIndex = resultOrder.indexOf(b);
437 | return aIndex - bIndex;
438 | });
439 | }
440 |
441 | function generateStatus(aggregatedResults) {
442 | const status = Object.keys(aggregatedResults)
443 | .sort()
444 | .reduce((acc, mode) => {
445 | const stats = aggregatedResults[mode];
446 | const total = Object.keys(stats).reduce(
447 | (acc, name) => acc + stats[name],
448 | 0
449 | );
450 | if (total === 0) return acc;
451 | acc.push(`
452 |
${mode}
453 |
454 | ${resultAwareSort(Object.keys(stats))
455 | .map((x) => {
456 | const percentTotal = ((100 * stats[x]) / total).toFixed(2);
457 | const toolTip = `${TestResultLabels[x]}: ${stats[x]} / ${total} (${percentTotal}%)`;
458 | const barColor = color(x);
459 | return `
`;
460 | })
461 | .join("")}
462 |
463 |
`);
464 | return acc;
465 | }, []);
466 | return status.join(" ");
467 | }
468 |
469 | document.addEventListener("DOMContentLoaded", () => {
470 | const promises = [];
471 | for (const [path, mode] of window.config.loadPathsAndModes) {
472 | promises.push(
473 | fetchData(`../data/${path}`)
474 | .then((response) => response.json())
475 | .then((data) => initialize(data, mode))
476 | );
477 | }
478 | Promise.all(promises).then(() => generateResults());
479 | });
480 |
--------------------------------------------------------------------------------
/test262/test-config.js:
--------------------------------------------------------------------------------
1 | const style = getComputedStyle(document.body);
2 |
3 | const TestResult = {
4 | PASSED: "passed",
5 | FAILED: "failed",
6 | SKIPPED: "skipped",
7 | METADATA_ERROR: "metadata_error",
8 | HARNESS_ERROR: "harness_error",
9 | TIMEOUT_ERROR: "timeout_error",
10 | PROCESS_ERROR: "process_error",
11 | RUNNER_EXCEPTION: "runner_exception",
12 | TODO_ERROR: "todo_error",
13 | DURATION: "duration",
14 | };
15 |
16 | const TestResultColors = {
17 | [TestResult.PASSED]: style.getPropertyValue("--color-chart-passed"),
18 | [TestResult.FAILED]: style.getPropertyValue("--color-chart-failed"),
19 | [TestResult.SKIPPED]: style.getPropertyValue("--color-chart-skipped"),
20 | [TestResult.METADATA_ERROR]: style.getPropertyValue(
21 | "--color-chart-metadata-error"
22 | ),
23 | [TestResult.HARNESS_ERROR]: style.getPropertyValue(
24 | "--color-chart-harness-error"
25 | ),
26 | [TestResult.TIMEOUT_ERROR]: style.getPropertyValue(
27 | "--color-chart-timeout-error"
28 | ),
29 | [TestResult.PROCESS_ERROR]: style.getPropertyValue(
30 | "--color-chart-process-error"
31 | ),
32 | [TestResult.RUNNER_EXCEPTION]: style.getPropertyValue(
33 | "--color-chart-runner-exception"
34 | ),
35 | [TestResult.TODO_ERROR]: style.getPropertyValue("--color-chart-todo-error"),
36 | };
37 |
38 | const TestResultLabels = {
39 | [TestResult.PASSED]: "Passed",
40 | [TestResult.FAILED]: "Failed",
41 | [TestResult.SKIPPED]: "Skipped",
42 | [TestResult.METADATA_ERROR]: "Metadata failed to parse",
43 | [TestResult.HARNESS_ERROR]: "Harness file failed to parse or run",
44 | [TestResult.TIMEOUT_ERROR]: "Timed out",
45 | [TestResult.PROCESS_ERROR]: "Crashed",
46 | [TestResult.RUNNER_EXCEPTION]: "Unhandled runner exception",
47 | [TestResult.TODO_ERROR]: "Not yet implemented",
48 | [TestResult.DURATION]: "Duration (seconds)",
49 | };
50 |
--------------------------------------------------------------------------------
/wasm/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LibWasm Spec Test results
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 | LibWasm Spec test results
18 |
19 | Introduction
20 |
21 | These are the results of the
22 | SerenityOS
28 | WebAssembly library, LibWasm, running the WebAssembly
29 | testsuite . All tests are re-run and the results updated automatically for
35 | every push to the master branch of the repository on GitHub. Dates and
36 | times are shown in your browser's timezone.
37 |
38 |
39 | If you have any questions or want to help out with improving these
40 | test scores, feel free to get in touch on the
41 | SerenityOS Discord server .
47 |
48 |
49 |
55 |
56 | Source code & Data
57 | Source code:
58 |
84 | Data (JSON):
85 |
103 |
104 |
105 | Results
106 | Loading...
107 |
108 |
109 |
110 |
111 |
112 | Performance
113 |
114 |
115 |
116 |
117 |
118 | Performance per test
119 |
120 |
121 |
122 |
123 |
124 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
153 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/wasm/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (() => {
4 | const { DateTime, Duration } = luxon;
5 |
6 | const backgroundColor = style.getPropertyValue("--color-background");
7 | const textColor = style.getPropertyValue("--color-text");
8 | const chartBorderColor = style.getPropertyValue("--color-chart-border");
9 | const fontFamily = style.getPropertyValue("font-family");
10 | const fontSize = parseInt(
11 | style.getPropertyValue("font-size").slice(0, -2),
12 | 10
13 | );
14 |
15 | Chart.defaults.borderColor = textColor;
16 | Chart.defaults.color = textColor;
17 | Chart.defaults.font.family = fontFamily;
18 | Chart.defaults.font.size = fontSize;
19 |
20 | // place tooltip's origin point under the cursor
21 | const tooltipPlugin = Chart.registry.getPlugin("tooltip");
22 | tooltipPlugin.positioners.underCursor = function (elements, eventPosition) {
23 | const pos = tooltipPlugin.positioners.average(elements);
24 |
25 | if (pos === false) {
26 | return false;
27 | }
28 |
29 | return {
30 | x: pos.x,
31 | y: eventPosition.y,
32 | };
33 | };
34 |
35 | class LineWithVerticalHoverLineController extends Chart.LineController {
36 | draw() {
37 | super.draw(arguments);
38 |
39 | if (!this.chart.tooltip._active.length) return;
40 |
41 | const { x } = this.chart.tooltip._active[0].element;
42 | const { top: topY, bottom: bottomY } = this.chart.chartArea;
43 | const ctx = this.chart.ctx;
44 |
45 | ctx.save();
46 | ctx.beginPath();
47 | ctx.moveTo(x, topY);
48 | ctx.lineTo(x, bottomY);
49 | ctx.lineWidth = 1;
50 | ctx.strokeStyle = chartBorderColor;
51 | ctx.stroke();
52 | ctx.restore();
53 | }
54 | }
55 |
56 | LineWithVerticalHoverLineController.id = "lineWithVerticalHoverLine";
57 | LineWithVerticalHoverLineController.defaults = Chart.LineController.defaults;
58 | Chart.register(LineWithVerticalHoverLineController);
59 |
60 | // This is when we started running the tests on Idan's self-hosted runner. Before that,
61 | // durations varied a lot across runs. See https://github.com/SerenityOS/serenity/pull/7718.
62 | const PERFORMANCE_CHART_START_DATE_TIME = DateTime.fromISO("2021-07-04");
63 |
64 | function prepareDataForCharts(data) {
65 | const charts = {
66 | [""]: {
67 | data: {
68 | [TestResult.PASSED]: [],
69 | [TestResult.FAILED]: [],
70 | [TestResult.SKIPPED]: [],
71 | [TestResult.METADATA_ERROR]: [],
72 | [TestResult.HARNESS_ERROR]: [],
73 | [TestResult.TIMEOUT_ERROR]: [],
74 | [TestResult.PROCESS_ERROR]: [],
75 | [TestResult.RUNNER_EXCEPTION]: [],
76 | [TestResult.TODO_ERROR]: [],
77 | [TestResult.DURATION]: [],
78 | },
79 | datasets: [],
80 | metadata: [],
81 | },
82 | ["performance"]: {
83 | data: {
84 | [TestResult.DURATION]: [],
85 | },
86 | datasets: [],
87 | metadata: [],
88 | },
89 | ["performance-per-test"]: {
90 | data: {
91 | [TestResult.DURATION]: [],
92 | },
93 | datasets: [],
94 | metadata: [],
95 | },
96 | };
97 |
98 | console.log(data);
99 | for (const entry of data) {
100 | const test = entry.tests["spectest"];
101 | const results = test.results;
102 | charts[""].metadata.push({
103 | commitTimestamp: entry.commit_timestamp,
104 | runTimestamp: entry.run_timestamp,
105 | duration: test.duration,
106 | versions: entry.versions,
107 | total: results.total,
108 | });
109 | for (const testResult in charts[""].data) {
110 | if (testResult === TestResult.DURATION) {
111 | continue;
112 | }
113 | charts[""].data[testResult].push({
114 | x: entry.commit_timestamp * 1000,
115 | y: results[testResult] || 0,
116 | });
117 | }
118 |
119 | const dt = DateTime.fromSeconds(entry.commit_timestamp);
120 | if (dt < PERFORMANCE_CHART_START_DATE_TIME) {
121 | continue;
122 | }
123 |
124 | // chart-performance
125 | const performanceTests = test;
126 | const performanceChart = charts["performance"];
127 | const performanceResults = performanceTests?.results;
128 | if (performanceResults) {
129 | performanceChart.metadata.push({
130 | commitTimestamp: entry.commit_timestamp,
131 | runTimestamp: entry.run_timestamp,
132 | duration: performanceTests.duration,
133 | versions: entry.versions,
134 | total: performanceResults.total,
135 | });
136 | performanceChart.data["duration"].push({
137 | x: entry.commit_timestamp * 1000,
138 | y: performanceTests.duration,
139 | });
140 | }
141 |
142 | // chart-performance-per-test
143 | const performancePerTestTests = test;
144 | const performancePerTestChart = charts["performance-per-test"];
145 | const performancePerTestResults = performancePerTestTests?.results;
146 | if (performancePerTestResults) {
147 | performancePerTestChart.metadata.push({
148 | commitTimestamp: entry.commit_timestamp,
149 | runTimestamp: entry.run_timestamp,
150 | duration:
151 | performancePerTestTests.duration / performancePerTestResults.total,
152 | versions: entry.versions,
153 | total: performancePerTestResults.total,
154 | });
155 | performancePerTestChart.data["duration"].push({
156 | x: entry.commit_timestamp * 1000,
157 | y: performancePerTestTests.duration / performancePerTestResults.total,
158 | });
159 | }
160 | }
161 |
162 | for (const chart in charts) {
163 | for (const testResult in charts[chart].data) {
164 | charts[chart].datasets.push({
165 | label: TestResultLabels[testResult],
166 | data: charts[chart].data[testResult],
167 | backgroundColor: TestResultColors[testResult],
168 | borderWidth: 2,
169 | borderColor: chartBorderColor,
170 | pointRadius: 0,
171 | pointHoverRadius: 0,
172 | fill: true,
173 | });
174 | }
175 | delete charts[chart].data;
176 | }
177 |
178 | return { charts };
179 | }
180 |
181 | function initializeChart(
182 | element,
183 | { datasets, metadata },
184 | { xAxisTitle = "Time", yAxisTitle = "Number of tests" } = {}
185 | ) {
186 | const ctx = element.getContext("2d");
187 |
188 | new Chart(ctx, {
189 | type: "lineWithVerticalHoverLine",
190 | data: {
191 | datasets,
192 | },
193 | options: {
194 | parsing: false,
195 | normalized: true,
196 | responsive: true,
197 | maintainAspectRatio: false,
198 | animation: false,
199 | plugins: {
200 | zoom: {
201 | zoom: {
202 | mode: "x",
203 | wheel: {
204 | enabled: true,
205 | },
206 | },
207 | pan: {
208 | enabled: true,
209 | mode: "x",
210 | },
211 | },
212 | hover: {
213 | mode: "index",
214 | intersect: false,
215 | },
216 | tooltip: {
217 | mode: "index",
218 | intersect: false,
219 | usePointStyle: true,
220 | boxWidth: 12,
221 | boxHeight: 12,
222 | padding: 20,
223 | position: "underCursor",
224 | titleColor: textColor,
225 | bodyColor: textColor,
226 | footerColor: textColor,
227 | footerFont: { weight: "normal" },
228 | footerMarginTop: 20,
229 | backgroundColor: backgroundColor,
230 | callbacks: {
231 | title: () => {
232 | return null;
233 | },
234 | beforeBody: (context) => {
235 | const { dataIndex } = context[0];
236 | const { total } = metadata[dataIndex];
237 | const formattedValue = total.toLocaleString("en-US");
238 | // Leading spaces to make up for missing color circle
239 | return ` Number of tests: ${formattedValue}`;
240 | },
241 | label: (context) => {
242 | // Space as padding between color circle and label
243 | const formattedValue = context.parsed.y.toLocaleString("en-US");
244 | if (
245 | context.dataset.label !==
246 | TestResultLabels[TestResult.DURATION]
247 | ) {
248 | const { total } = metadata[context.dataIndex];
249 | const percentOfTotal = (
250 | (context.parsed.y / total) *
251 | 100
252 | ).toFixed(2);
253 | return ` ${context.dataset.label}: ${formattedValue} (${percentOfTotal}%)`;
254 | } else {
255 | return ` ${context.dataset.label}: ${formattedValue}`;
256 | }
257 | },
258 |
259 | footer: (context) => {
260 | const { dataIndex } = context[0];
261 | const {
262 | commitTimestamp,
263 | duration: durationSeconds,
264 | versions,
265 | } = metadata[dataIndex];
266 | const dateTime = DateTime.fromSeconds(commitTimestamp);
267 | const duration = Duration.fromMillis(durationSeconds * 1000);
268 | const serenityVersion = versions.serenity.substring(0, 7);
269 | return `\
270 | Committed on ${dateTime.toLocaleString(DateTime.DATETIME_SHORT)}, \
271 | run took ${duration.toISOTime()}
272 |
273 | Versions: serenity@${serenityVersion}`;
274 | },
275 | },
276 | },
277 | legend: {
278 | align: "end",
279 | labels: {
280 | usePointStyle: true,
281 | boxWidth: 10,
282 | // Only include passed, failed, TODO, and crashed in the legend
283 | filter: ({ text }) =>
284 | text === TestResultLabels[TestResult.PASSED] ||
285 | text === TestResultLabels[TestResult.FAILED] ||
286 | text === TestResultLabels[TestResult.TODO_ERROR] ||
287 | text === TestResultLabels[TestResult.PROCESS_ERROR],
288 | },
289 | },
290 | },
291 | scales: {
292 | x: {
293 | type: "time",
294 | title: {
295 | display: true,
296 | text: xAxisTitle,
297 | },
298 | grid: {
299 | borderColor: textColor,
300 | color: "transparent",
301 | borderWidth: 2,
302 | },
303 | },
304 | y: {
305 | stacked: true,
306 | beginAtZero: true,
307 | title: {
308 | display: true,
309 | text: yAxisTitle,
310 | },
311 | grid: {
312 | borderColor: textColor,
313 | color: chartBorderColor,
314 | borderWidth: 2,
315 | },
316 | },
317 | },
318 | },
319 | });
320 | }
321 |
322 | function initializeSummary(
323 | element,
324 | runTimestamp,
325 | commitHash,
326 | durationSeconds,
327 | results
328 | ) {
329 | const dateTime = DateTime.fromSeconds(runTimestamp);
330 | const duration = Duration.fromMillis(durationSeconds * 1000);
331 | const passed = results[TestResult.PASSED];
332 | const total = results.total;
333 | const percent = ((passed / total) * 100).toFixed(2);
334 | element.innerHTML = `
335 | The last test run was on
336 | ${dateTime.toLocaleString(DateTime.DATETIME_SHORT)}
337 | for commit
338 |
339 |
345 | ${commitHash.slice(0, 7)}
346 |
347 |
348 | and took ${duration.toISOTime()} .
349 | ${passed} of ${total} tests passed, i.e. ${percent}% .
350 | `;
351 | }
352 |
353 | function initialize(data) {
354 | const { charts } = prepareDataForCharts(data);
355 | initializeChart(document.getElementById("chart"), charts[""]);
356 | initializeChart(
357 | document.getElementById("chart-performance"),
358 | charts["performance"],
359 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
360 | );
361 | initializeChart(
362 | document.getElementById("chart-performance-per-test"),
363 | charts["performance-per-test"],
364 | { yAxisTitle: TestResultLabels[TestResult.DURATION] }
365 | );
366 | const last = data.slice(-1)[0];
367 | if (last) {
368 | initializeSummary(
369 | document.getElementById("summary"),
370 | last.run_timestamp,
371 | last.versions.serenity,
372 | last.tests["spectest"].duration,
373 | last.tests["spectest"].results
374 | );
375 | }
376 | }
377 |
378 | document.addEventListener("DOMContentLoaded", () => {
379 | fetchData("data/results.json")
380 | .then((response) => response.json())
381 | .then((data) => {
382 | data.sort((a, b) =>
383 | a.commit_timestamp === b.commit_timestamp
384 | ? 0
385 | : a.commit_timestamp < b.commit_timestamp
386 | ? -1
387 | : 1
388 | );
389 | return data;
390 | })
391 | .then((data) => initialize(data));
392 | });
393 | })();
394 |
--------------------------------------------------------------------------------
/wasm/per-file/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LibWasm spec test per-file results
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Search:
22 |
23 |
24 |
25 | Filter mode:
26 |
27 |
28 | Per-file results
29 |
30 |
Loading...
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
59 |
60 |
61 |
62 |
73 |
74 |
78 |
82 |
83 |
84 |
85 |
90 |
91 | (Open on GitHub )
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
110 |
111 |
112 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/wasm/per-file/wasm-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linusg/libjs-website/50fcff458444ee06b7de4540c643aa7226eb61ee/wasm/per-file/wasm-icon.png
--------------------------------------------------------------------------------