When working on Pintos, I usually ran tests from the terminal.
For a single test, I had to remember the correct project directory and Make target. After the run finished, I had to find the generated result files and determine whether the test had actually passed.
This became repetitive, especially because Pintos is divided into several projects.
Threads User Programs Virtual Memory File System
Each project has its own build directory, and each test produces several output files.
At first, I thought I only needed to add a Run button to VS Code.
Once I started building it, however, I realized that running a test was only one part of the problem. The extension also needed to discover tests, group them, determine their status, show their artifacts, and connect them to the correct build directory.
That became Pintos Test Explorer.
A Pintos test is not a normal unit test
A normal test framework usually provides a command that returns a structured result.
Pintos works differently. Tests are defined through Makefiles and executed using Pintos’s existing build system.
An individual test is run through a target similar to:
make tests/threads/alarm-zero.result
This does not only execute one program.
Conceptually, the process is closer to:
Build the Pintos kernel → Build the test program → Prepare the disk image → Start Pintos through QEMU → Run the test inside the kernel → Save the kernel output → Run the host-side checker → Write the final result
The extension should not replace this process. It should use the existing Make targets and present the results in a more accessible way.
The three result files
A Pintos test can generate three important files.
*.output *.result *.errors
Each file has a different purpose.
| File | Meaning |
|---|---|
| output | Runtime output from the Pintos kernel |
| result | Final verdict from the host-side checker |
| errors | Build or test preparation errors |
One thing I initially had to clarify was that a pass() message from inside a Pintos test is not necessarily the final result.
That message is only part of the kernel output.
Test program calls pass() → Message appears in .output → Host-side checker analyzes the complete output → Final PASS or FAIL is written to .result
Therefore, the extension reads .result to display the final status.
.result contains PASS → PASS .result contains FAIL → FAIL Build stops before a normal result is produced → BUILD ERROR
This distinction became the basis of the sidebar status system.
Creating the Pintos sidebar
In package.json, I registered a dedicated Pintos container in the VS Code Activity Bar.
Activity Bar └─ Pintos └─ Pintos Tests
The extension contributes commands for:
Refreshing the test list Running one test Debugging one test Running checked tests Stopping a batch after the current test Opening test source files Opening result artifacts Resetting test results Managing custom tests
The sidebar is organized as a tree.
Pintos Tests ├─ Threads │ ├─ Alarm Clock │ │ ├─ alarm-single │ │ ├─ alarm-multiple │ │ └─ alarm-zero │ ├─ Priority │ └─ MLFQS ├─ User Programs ├─ Virtual Memory └─ File System
Each test row shows one of four states.
Not run PASS FAIL Build error
If result artifacts exist, the test can be expanded.
alarm-zero ├─ output ├─ result └─ errors
Selecting one of these entries opens the corresponding file directly in VS Code.
Building the tree
The sidebar is implemented through the PintosTreeProvider in extension.js.
The tree uses several node types.
ProjectNode GroupNode TestNode ArtifactNode
Their relationship is:
Project → Test group → Individual test → Generated artifacts
A project or group also summarizes its descendant tests.
Total PASS FAIL Build error Not run Checked
This makes it possible to see the overall state without opening every test.
Threads · 18/24 passed
If any test has failed, the project or group is shown in red. If at least one test has passed and no failure exists, it is shown in green.
Discovering the Pintos root
Another issue was that not every Pintos repository has the same layout.
The workspace may be the Pintos root itself:
workspace/ ├─ threads/ ├─ userprog/ ├─ vm/ └─ tests/
Or Pintos may be inside another repository:
workspace/ └─ pintos/ ├─ threads/ ├─ userprog/ └─ tests/
Some environments use src/ or pintos/src/, while educational repositories may wrap Pintos inside a larger Docker or lab project.
The extension searches several layouts.
current directory current directory/pintos current directory/src current directory/pintos/src
It also searches parent and child directories while ignoring folders such as .git, node_modules, and build.
A directory is considered a Pintos root only if important files exist.
utils/pintos threads/Make.vars userprog/Make.vars vm/Make.vars tests/Make.tests
This prevents the extension from activating in an unrelated folder that merely happens to contain a directory named tests.
Running a test
When the user selects Run, the extension first confirms that the selected project has a usable build directory.
If the build tree does not exist, it runs make in the project directory.
threads/ → make → threads/build/
It then removes old artifacts from the selected test.
This cleanup is important because a stale .result file from a previous execution could make a failed run look successful.
The actual test is launched through its existing Make target.
make -C threads/build \ --no-print-directory \ tests/threads/alarm-zero.result
The complete flow is:
Click Run → Resolve the project build directory → Prepare the build tree → Delete old output, result, and errors files → Run the test’s .result Make target → Pintos and QEMU execute the test → Read the new .result file → Refresh the sidebar
If Make exits before Pintos can write a normal result, the extension creates a BUILD_ERROR result and stores the recent build output in .errors.
This keeps a build failure separate from a normal test failure.
Running multiple tests
Checkboxes allow tests, groups, or entire projects to be selected.
Check individual tests → Run Checked Tests → Execute each test in sequence
Tests are run sequentially because each Pintos execution uses the same build environment and may start its own QEMU process.
The extension also supports stopping a batch.
Instead of killing the current Make or QEMU process immediately, the Stop button means:
Finish the current test → Skip the remaining selected tests → Print a partial summary
This avoids leaving the build directory or generated artifacts in an unclear state.
At the end, the extension prints a summary.
18 passed, 2 failed, 20 total
Conclusion
The actual structure became:
VS Code sidebar → Discover projects and tests → Resolve the correct build directory → Execute existing Pintos Make targets → Read output, result, and errors files → Display PASS, FAIL, or BUILD ERROR
The next problem was making the sidebar and terminal use the same test list and execution rules.
The second post covers the companion pt CLI, Makefile-based test discovery, GDB debugging, and custom test management.