├── .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 |
// 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 | -------------------------------------------------------------------------------- /repl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LibJS REPL 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 |
19 |

LibJS REPL

20 | 42 |
43 | Loading... 44 | 45 |
46 |
47 | 56 | 62 | 68 | 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 | 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 | 24 | 28 |

Per-file results

29 |
30 | Loading... 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 59 | 60 | 61 | 62 | 75 | 76 | 80 | 84 | 85 | 100 | 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 |
50 |

Per-file results

51 |

52 | Click here! 53 |

54 |
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 | 24 | 28 |

Per-file results

29 |
30 | Loading... 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 59 | 60 | 61 | 62 | 73 | 74 | 78 | 82 | 83 | 102 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /wasm/per-file/wasm-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linusg/libjs-website/50fcff458444ee06b7de4540c643aa7226eb61ee/wasm/per-file/wasm-icon.png --------------------------------------------------------------------------------