From 3e7b8fdd31a9281ca9a76bf06aaabe902823ae02 Mon Sep 17 00:00:00 2001 From: georgiybza Date: Sat, 6 Jun 2026 21:57:20 -0400 Subject: [PATCH] Add demo and report --- EventHandler.js | 268 +++++++++++++++++++++++++++++++++++++++++++++++ index.html | 32 ++++++ pyodide-setup.js | 13 +++ report.md | 80 ++++++++++++++ 4 files changed, 393 insertions(+) create mode 100644 EventHandler.js create mode 100644 index.html create mode 100644 pyodide-setup.js create mode 100644 report.md diff --git a/EventHandler.js b/EventHandler.js new file mode 100644 index 0000000..9efee7e --- /dev/null +++ b/EventHandler.js @@ -0,0 +1,268 @@ +const loadAddTwoNumbersButton = document.getElementById("btn-add-two-numbers"); +const loadReverseStringButton = document.getElementById("btn-reverse-string"); +const clearPageButton = document.getElementById("btn-clear-page"); +const addTwoNumbersContent = document.getElementById("add-two-numbers"); +const reverseStringContent = document.getElementById("reverse-string"); + +function clearPage() { + addTwoNumbersContent.innerHTML = ""; + reverseStringContent.innerHTML = ""; +} + +// When Add Two Numbers Clicked => try submissions/add-two-numbers/correct.py, then wrong-answer.py, then broken.py +loadAddTwoNumbersButton.addEventListener("click",function() { + clearPage(); + fetch("questions/add-two-numbers/index.md") + .then(res => res.text()) + .then(text => { + + // Set HTML + const submitHTML = ``; + const revealSolutionHTML = ``; + const testSubmissionHTML = ``; + const brokenSubmissionHTML = ``; + const correctSubmissionHTML = ``; + const wrongSubmissionHTML = ``; + const inputBoxHTML = `
`; + const resultsHTML = `
`; + + addTwoNumbersContent.innerHTML = marked.parse(text) + inputBoxHTML + brokenSubmissionHTML + correctSubmissionHTML + wrongSubmissionHTML + testSubmissionHTML + submitHTML + resultsHTML; + + // Get the Buttons + const brokenButton = document.getElementById("btn-add-two-numbers-broken"); + const correctButton = document.getElementById("btn-add-two-numbers-correct"); + const wrongButton = document.getElementById("btn-add-two-numbers-wrong"); + const submitButton = document.getElementById("btn-submit"); + const testSubmissionButton = document.getElementById("btn-test-submission"); + + // Load starter + fetch("questions/add-two-numbers/starter.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }) + + brokenButton.addEventListener("click", async function() { + fetch("submissions/add-two-numbers/broken.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }); + }); + + correctButton.addEventListener("click", function() { + fetch("submissions/add-two-numbers/correct.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }); + }); + + wrongButton.addEventListener("click", function() { + fetch("submissions/add-two-numbers/wrong-answer.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }); + }); + + submitButton.addEventListener("click", async function() { + let pyodide = await pyodideReadyPromise; + + let inputBoxData = document.getElementById("output").value; + pyodide.FS.writeFile("solution.py", inputBoxData, { encoding: "utf8"}); + + fetch("questions/add-two-numbers/tests.py") + .then(res => res.text()) + .then(async testCode => { + const resultsDiv = document.getElementById("test-results"); + try { + pyodide.runPython(` + import sys, io + sys.stdout = io.StringIO() + if 'solution' in sys.modules: + del sys.modules['solution'] + `); + pyodide.runPython(testCode); + } catch(e) { + if (!e.message.includes("SystemExit")) { + resultsDiv.innerHTML = `

[ERROR, TRY AGAIN] ${e}

` + console.error(e); + } + } + const result = pyodide.runPython("sys.stdout.getvalue()"); + const parsed = JSON.parse(result); + + if (parsed.failed === 0) { + resultsDiv.innerHTML = `

Correct.

`; + } else { + resultsDiv.innerHTML = `

Incorrect, review submission and try again.

`; + } + }); + }); + + + testSubmissionButton.addEventListener("click", async function() { + let pyodide = await pyodideReadyPromise; + let inputBoxData = document.getElementById("output").value; + pyodide.FS.writeFile("solution.py", inputBoxData, { encoding: "utf8"}); + + fetch("questions/add-two-numbers/tests.py") + .then(res => res.text()) + .then(testCode => { + const resultsDiv = document.getElementById("test-results"); + try { + pyodide.runPython(` + import sys, io + sys.stdout = io.StringIO() + if 'solution' in sys.modules: + del sys.modules['solution'] + `); + pyodide.runPython(testCode); + } catch(e) { + if (!e.message.includes("SystemExit")) { + resultsDiv.innerHTML = `

[ERROR, TRY AGAIN] ${e}

` + console.error(e); + } + } + const result = pyodide.runPython("sys.stdout.getvalue()"); + const parsed = JSON.parse(result); + if (parsed.failed === 0) { + resultsDiv.innerHTML = `All ${parsed.passed} tests passed.`; + } else { + resultsDiv.innerHTML = `${parsed.failed} test(s) failed, ${parsed.passed} passed.`; + } + }); + }); + }); +}); + +loadReverseStringButton.addEventListener("click",function() { + // Fetch .md file and display + clearPage(); + fetch("questions/reverse-string/index.md") + .then(res => res.text()) + .then(text => { + + // Set HTML + const submitHTML = ``; + const revealSolutionHTML = ``; + const testSubmissionHTML = ``; + const brokenSubmissionHTML = ``; + const correctSubmissionHTML = ``; + const wrongSubmissionHTML = ``; + const inputBoxHTML = `
`; + const resultsHTML = `
`; + reverseStringContent.innerHTML = marked.parse(text) + inputBoxHTML + brokenSubmissionHTML + correctSubmissionHTML + wrongSubmissionHTML + testSubmissionHTML + submitHTML + resultsHTML; + + // Get the Buttons + const brokenButton = document.getElementById("btn-reverse-string-broken"); + const correctButton = document.getElementById("btn-reverse-string-correct"); + const wrongButton = document.getElementById("btn-reverse-string-wrong"); + const submitButton = document.getElementById("btn-submit"); + const testSubmissionButton = document.getElementById("btn-test-submission"); + + fetch("questions/reverse-string/starter.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }) + + brokenButton.addEventListener("click", function() { + fetch("submissions/reverse-string/broken.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }); + }); + + correctButton.addEventListener("click", function() { + fetch("submissions/reverse-string/correct.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }); + }); + + wrongButton.addEventListener("click", function() { + fetch("submissions/reverse-string/wrong-answer.py") + .then(res => res.text()) + .then(text => { + document.getElementById("output").value = text; + }); + }); + + submitButton.addEventListener("click", async function() { + let pyodide = await pyodideReadyPromise; + + let inputBoxData = document.getElementById("output").value; + pyodide.FS.writeFile("solution.py", inputBoxData, { encoding: "utf8"}); + + fetch("questions/reverse-string/tests.py") + .then(res => res.text()) + .then(async testCode => { + const resultsDiv = document.getElementById("test-results"); + try { + pyodide.runPython(` + import sys, io + sys.stdout = io.StringIO() + if 'solution' in sys.modules: + del sys.modules['solution'] + `); + pyodide.runPython(testCode); + } catch(e) { + if (!e.message.includes("SystemExit")) { + resultsDiv.innerHTML = `

[ERROR, TRY AGAIN] ${e}

` + console.error(e); + } + } + const result = pyodide.runPython("sys.stdout.getvalue()"); + const parsed = JSON.parse(result); + + if (parsed.failed === 0) { + resultsDiv.innerHTML = `

Correct.

`; + } else { + resultsDiv.innerHTML = `

Incorrect, review submission and try again.

`; + } + }); + }); + testSubmissionButton.addEventListener("click", async function() { + let pyodide = await pyodideReadyPromise; + let inputBoxData = document.getElementById("output").value; + pyodide.FS.writeFile("solution.py", inputBoxData, { encoding: "utf8"}); + + fetch("questions/reverse-string/tests.py") + .then(res => res.text()) + .then(async testCode => { + const resultsDiv = document.getElementById("test-results"); + try { + pyodide.runPython(` + import sys, io + sys.stdout = io.StringIO() + if 'solution' in sys.modules: + del sys.modules['solution'] + `); + pyodide.runPython(testCode); + } catch(e) { + if (!e.message.includes("SystemExit")) { + resultsDiv.innerHTML = `

[ERROR, TRY AGAIN] ${e}

` + console.error(e); + } + } + const result = pyodide.runPython("sys.stdout.getvalue()"); + const parsed = JSON.parse(result); + if (parsed.failed === 0) { + resultsDiv.innerHTML = `All ${parsed.passed} tests passed.`; + } else { + resultsDiv.innerHTML = `${parsed.failed} test(s) failed, ${parsed.passed} passed.`; + } + }); + }); + }); +}); + +// When Clear Page Clicked => try clear page +clearPageButton.addEventListener("click",function() { + addTwoNumbersContent.innerHTML = ""; + reverseStringContent.innerHTML = ""; +}); \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..d1403ea --- /dev/null +++ b/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + +

+ Choose the Problem: + + + + +

+ + +
+
+
+ + + + + + + + + diff --git a/pyodide-setup.js b/pyodide-setup.js new file mode 100644 index 0000000..f137e87 --- /dev/null +++ b/pyodide-setup.js @@ -0,0 +1,13 @@ +/* + * + * + * + * +*/ +async function main() { + console.time("Pyodide Loading Time"); + let pyodide = await loadPyodide(); + console.timeEnd("Pyodide Loading Time"); + return pyodide; +}; +let pyodideReadyPromise = main(); \ No newline at end of file diff --git a/report.md b/report.md new file mode 100644 index 0000000..e9a198f --- /dev/null +++ b/report.md @@ -0,0 +1,80 @@ +# Pyodide Findings +This report contains my findings while using Pyodide to display content onto WebAssembly. This report is not solidified and could be subject to change. + +## Test & Submission Workflow +The test and submission workflow follows the current routine in summary: +1. Create the test file +2. Student submits code +3. Results are shown + +The test file runs against the submission inside Pyodide by writing to the solution.py file (i.e., the answer file) and tests.py imports solution.py to run its tests. To receive whether the answer was correct or incorrect, we fetch the tests.py file. Since the user has the ability to run broken code, we surround the code execution in a try-catch state, where catch catches the expected broken code. Moving on, we parse the result from test.py to count the amount of test passes/fails to determine whether the answer they submitted was correct, or whether the test was successful depending on what button the user pressed. + +## Startup Time +Cold Startup Time: ~3500ms on average. + +Warm Subsequent Execution Time(s): ~800ms on average. + +## Missing stdlib Modules + +In version 0.29.4, only the Python stdlib is available after importing Pyodide by default. + +The following libraries are included: +- math +- random +- collections +- dataclasses +- typing +- sys +- os +- io + +The following libraries have limited functionality: +- hashlib +- decimal +- pydoc +- webbrowser +- zoneinfo +- urllib3 +- requests + +The following modules are unavailable: +- curses +- dbm +- ensurepip +- fcntl +- grp +- idlelib +- lib2to3 +- msvcrt +- pwd +- resource +- syslog +- termios +- tkinter +- turtle.py +- turtledemo +- venv +- winreg +- winsound + +The following modules are included, but do not work: + +- multiprocessing +- threading +- sockets +- pty +- tty + +Source: https://pyodide.org/en/stable/usage/wasm-constraints.html + +## Multi-File Questions +N/A + +## Timeout & Infinite Loops +Testing out a counting infinite while loop resulted in the website slowing down severely and essentially crashing. The only way to counteract this is through a Web Worker, where Pyodide runs on a seperate thread. In NodeJS you can use worker_threads which is similar to Web Workers, as well as vm.runInNewContext() with a timeout option. In sum, it is doable in both NodeJS and through a browser Web Worker, but NodeJS offers slightly more control. + +## Output Capture +Output is captured by using the Python sys and io modules. The output is stored by sys.stdout = io.StringIO() when the test is executed, and then is retrieved and stored by sys.stdout.getvalue(), and then for formatting is parsed through JSON. + +## Browser Deployment Setup +N/A \ No newline at end of file