├── CNAME ├── .github ├── FUNDING.yml └── workflows │ └── lint.yml ├── .prettierignore ├── favicon.png ├── repl ├── libjs.wasm ├── main.css ├── main.js ├── index.html └── repl.js ├── test262 ├── per-file │ ├── js-icon.png │ ├── dir-icon.png │ ├── main.css │ ├── index.html │ └── main.js ├── fetch.js ├── test-config.js ├── main.css ├── index.html └── main.js ├── wasm ├── per-file │ ├── wasm-icon.png │ └── index.html ├── index.html └── main.js ├── LICENSE └── index.html /CNAME: -------------------------------------------------------------------------------- 1 | libjs.dev 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: linusg 2 | liberapay: linusg 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test262/data/* 2 | wasm/data/* 3 | repl/libjs.js 4 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linusg/libjs-website/HEAD/favicon.png -------------------------------------------------------------------------------- /repl/libjs.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linusg/libjs-website/HEAD/repl/libjs.wasm -------------------------------------------------------------------------------- /test262/per-file/js-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linusg/libjs-website/HEAD/test262/per-file/js-icon.png -------------------------------------------------------------------------------- /wasm/per-file/wasm-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linusg/libjs-website/HEAD/wasm/per-file/wasm-icon.png -------------------------------------------------------------------------------- /test262/per-file/dir-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linusg/libjs-website/HEAD/test262/per-file/dir-icon.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |// Website for SerenityOS's JavaScript engine, LibJS.
35 | //
36 | // For details visit https://github.com/SerenityOS/serenity.
37 | //
38 | // Continuously updated test262 results are available at /test262.
39 | // Continuously updated Wasm results are available at /wasm.
40 | //
41 | // If you wish to play with LibJS in your browser, head over to /repl.
42 | //
43 | // If you're a SerenityOS contributor and want to do something
44 | // with this, speak to linusg.
45 |
49 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | >59 | 60 |
>65 | 66 |
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 |52 | Click here! 53 |
54 |Source code:
58 |Data (JSON):
85 |master branch
101 | Loading...
107 |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 |
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 |
99 | Click here! 100 |
101 |Source code:
105 |Data (JSON):
124 |master branch (AST
139 | Interpreter)
141 | master branch (Bytecode
148 | Interpreter)
150 | master branch (Bytecode
157 | Interpreter with Optimizations)
159 | Loading...
165 |Loading...
172 |Loading...
179 |Loading...
186 |
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 |
--------------------------------------------------------------------------------
/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(`
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 |
--------------------------------------------------------------------------------