Skip to content

Commit 5ae6571

Browse files
committed
Reinitialize repo without reference solution
0 parents  commit 5ae6571

17 files changed

Lines changed: 1580 additions & 0 deletions

.github/pull_request_template.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Submission Checklist
2+
3+
- [ ] I changed `solution.py`.
4+
- [ ] I did not edit the official tests to inflate my score.
5+
- [ ] I ran `python score.py`.
6+
- [ ] I added or updated my own tests under `tests/student/` if needed.
7+
8+
## Notes
9+
10+
Describe any tradeoffs, assumptions, or known gaps here.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Challenge Smoke Test
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
smoke-test:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Check out repository
13+
uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.11"
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
python -m pip install -r requirements.txt
24+
25+
- name: Smoke test starter import
26+
run: python -c "import solution"
27+
28+
- name: Run scoring harness against starter
29+
run: python score.py

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
__pycache__/
2+
.pytest_cache/
3+
.mypy_cache/
4+
.coverage
5+
.venv/
6+
venv/
7+
sharedringbuffercorrect.py

LICENSE

Lines changed: 339 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# UCI Rocket Project Liquids
2+
3+
Spring 2026 recruitment coding challenge: implement a shared ring buffer in Python.
4+
5+
## What Students Work On
6+
7+
Your task is to replace the intentionally bad starter in `solution.py` with a correct implementation of:
8+
9+
- `RingSpec`
10+
- `SharedRingBuffer`
11+
12+
The reference contract is defined by the official tests in `tests/official/`.
13+
14+
## Repo Layout
15+
16+
- `solution.py`: starter submission file. It is intentionally wrong.
17+
- `tests/official/`: official grading tests.
18+
- `tests/student/student_test_template.py`: example file you can copy to write your own tests.
19+
- `score.py`: simple local scoring script.
20+
21+
## Getting Started
22+
23+
1. Create a virtual environment.
24+
2. Install dependencies:
25+
26+
```bash
27+
python -m pip install -r requirements.txt
28+
```
29+
30+
3. Run the official score against your submission:
31+
32+
```bash
33+
python score.py
34+
```
35+
36+
## Student Workflow
37+
38+
- Edit `solution.py`.
39+
- Leave `tests/official/` alone when you want a real score.
40+
- Copy `tests/student/student_test_template.py` to something like `tests/student/test_my_solution.py`.
41+
- Add your own cases there as you build.
42+
43+
To run your own tests too:
44+
45+
```bash
46+
python score.py --include-student-tests
47+
```
48+
49+
## Challenge Contract
50+
51+
Your implementation should preserve the public API used by the tests, including:
52+
53+
- shared-memory backed storage via `multiprocessing.shared_memory`
54+
- per-reader positions and alive flags
55+
- wrap-around read and write memory views
56+
- simple byte-copy helpers
57+
- correct cleanup and unlink behavior
58+
59+
If you change signatures or remove attributes the official tests will fail.
60+
61+
## GitHub
62+
63+
This repo includes a basic GitHub Actions workflow that smoke-tests the starter and scoring harness on pushes and pull requests.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
numpy>=1.26,<3

score.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
from dataclasses import dataclass
5+
import io
6+
import os
7+
from pathlib import Path
8+
import sys
9+
import unittest
10+
11+
12+
ROOT = Path(__file__).resolve().parent
13+
14+
15+
@dataclass
16+
class SuiteSummary:
17+
label: str
18+
total: int
19+
passed: int
20+
failures: int
21+
errors: int
22+
skipped: int
23+
24+
@property
25+
def successful(self) -> bool:
26+
return self.failures == 0 and self.errors == 0
27+
28+
29+
def discover(relative_path: str) -> unittest.TestSuite:
30+
loader = unittest.TestLoader()
31+
return loader.discover(
32+
start_dir=str(ROOT / relative_path),
33+
pattern="test_*.py",
34+
top_level_dir=str(ROOT),
35+
)
36+
37+
38+
def summarize(label: str, result: unittest.TestResult) -> SuiteSummary:
39+
failures = len(result.failures) + len(getattr(result, "unexpectedSuccesses", []))
40+
errors = len(result.errors)
41+
skipped = len(getattr(result, "skipped", [])) + len(getattr(result, "expectedFailures", []))
42+
passed = max(0, result.testsRun - failures - errors - skipped)
43+
return SuiteSummary(
44+
label=label,
45+
total=result.testsRun,
46+
passed=passed,
47+
failures=failures,
48+
errors=errors,
49+
skipped=skipped,
50+
)
51+
52+
53+
def run_suite(label: str, relative_path: str, *, verbosity: int) -> SuiteSummary:
54+
print(f"\n== {label} ==")
55+
suite = discover(relative_path)
56+
stream = sys.stdout if verbosity > 1 else io.StringIO()
57+
runner = unittest.TextTestRunner(stream=stream, verbosity=verbosity)
58+
result = runner.run(suite)
59+
summary = summarize(label, result)
60+
print(
61+
f"{summary.label}: {summary.passed}/{summary.total} passed "
62+
f"({summary.failures} failures, {summary.errors} errors, {summary.skipped} skipped)"
63+
)
64+
if verbosity == 1 and not summary.successful:
65+
print("Run with --verbose to see full failure details.")
66+
return summary
67+
68+
69+
def main() -> int:
70+
parser = argparse.ArgumentParser(description="Run the shared ring buffer score harness.")
71+
parser.add_argument("--module", default="solution", help="Module name to score. Defaults to solution.")
72+
parser.add_argument(
73+
"--include-student-tests",
74+
action="store_true",
75+
help="Also run tests under tests/student.",
76+
)
77+
parser.add_argument(
78+
"--strict",
79+
action="store_true",
80+
help="Exit non-zero when any official test fails.",
81+
)
82+
parser.add_argument("--verbose", action="store_true", help="Use verbose unittest output.")
83+
args = parser.parse_args()
84+
85+
os.environ["RING_BUFFER_MODULE"] = args.module
86+
verbosity = 2 if args.verbose else 1
87+
88+
print(f"Scoring module: {args.module}")
89+
official = run_suite("Official Tests", "tests/official", verbosity=verbosity)
90+
91+
student = None
92+
if args.include_student_tests:
93+
student = run_suite("Student Tests", "tests/student", verbosity=verbosity)
94+
95+
if student is None:
96+
print(f"\nCurrent official score: {official.passed}/{official.total}")
97+
else:
98+
print(
99+
f"\nCurrent score: {official.passed}/{official.total} official, "
100+
f"{student.passed}/{student.total} student"
101+
)
102+
103+
if args.strict and not official.successful:
104+
return 1
105+
return 0
106+
107+
108+
if __name__ == "__main__":
109+
raise SystemExit(main())

solution.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import TypeAlias
5+
6+
7+
RingView: TypeAlias = tuple[memoryview, memoryview | None, int, bool]
8+
9+
__all__ = ["RingSpec", "SharedRingBuffer"]
10+
11+
12+
def _todo(method: str) -> NotImplementedError:
13+
return NotImplementedError(
14+
f"{method} is still a TODO. Right now this file is a ring buffer in the same way "
15+
f"a cardboard tube is a liquid engine."
16+
)
17+
18+
19+
@dataclass(frozen=True)
20+
class RingSpec:
21+
name: str
22+
size: int
23+
num_readers: int
24+
reader: int = -1
25+
cache_align: bool = False
26+
cache_size: int = 64
27+
28+
def __post_init__(self) -> None:
29+
if self.size <= 0:
30+
raise ValueError("size must be > 0")
31+
if self.num_readers < 1:
32+
raise ValueError("num_readers must be >= 1")
33+
if self.cache_align:
34+
if self.cache_size <= 0:
35+
raise ValueError("cache_size must be > 0 when cache_align is True")
36+
if self.cache_size & (self.cache_size - 1):
37+
raise ValueError("cache_size must be a power of two when cache_align is True")
38+
39+
def to_kwargs(self, *, create: bool, reader: int) -> dict:
40+
return {
41+
"name": self.name,
42+
"create": create,
43+
"size": self.size,
44+
"num_readers": self.num_readers,
45+
"reader": reader,
46+
"cache_align": self.cache_align,
47+
"cache_size": self.cache_size,
48+
}
49+
50+
51+
class SharedRingBuffer:
52+
_NO_READER = -1
53+
54+
def __init__(
55+
self,
56+
name: str,
57+
create: bool,
58+
size: int,
59+
num_readers: int,
60+
reader: int,
61+
cache_align: bool = False,
62+
cache_size: int = 64,
63+
):
64+
raise _todo("__init__")
65+
66+
def close(self) -> None:
67+
raise _todo("close")
68+
69+
def unlink(self) -> None:
70+
raise _todo("unlink")
71+
72+
def __enter__(self) -> "SharedRingBuffer":
73+
raise _todo("__enter__")
74+
75+
def __exit__(self, *_):
76+
raise _todo("__exit__")
77+
78+
def calculate_pressure(self) -> int:
79+
raise _todo("calculate_pressure")
80+
81+
def int_to_pos(self, value: int) -> int:
82+
raise _todo("int_to_pos")
83+
84+
def update_reader_pos(self, new_reader_pos: int) -> None:
85+
raise _todo("update_reader_pos")
86+
87+
def set_reader_active(self, active: bool) -> None:
88+
raise _todo("set_reader_active")
89+
90+
def is_reader_active(self) -> bool:
91+
raise _todo("is_reader_active")
92+
93+
def update_write_pos(self, new_writer_pos: int) -> None:
94+
raise _todo("update_write_pos")
95+
96+
def inc_writer_pos(self, inc_amount: int) -> None:
97+
raise _todo("inc_writer_pos")
98+
99+
def inc_reader_pos(self, inc_amount: int) -> None:
100+
raise _todo("inc_reader_pos")
101+
102+
def get_write_pos(self):
103+
raise _todo("get_write_pos")
104+
105+
def compute_max_amount_writable(self, force_rescan: bool = False) -> int:
106+
raise _todo("compute_max_amount_writable")
107+
108+
def jump_to_writer(self) -> None:
109+
raise _todo("jump_to_writer")
110+
111+
def expose_writer_mem_view(self, size: int) -> RingView:
112+
raise _todo("expose_writer_mem_view")
113+
114+
def expose_reader_mem_view(self, size: int) -> RingView:
115+
raise _todo("expose_reader_mem_view")
116+
117+
def simple_write(self, writer_mem_view: RingView, src: object) -> None:
118+
raise _todo("simple_write")
119+
120+
def simple_read(self, reader_mem_view: RingView, dst: object) -> None:
121+
raise _todo("simple_read")
122+
123+
def write_array(self, arr) -> int:
124+
raise _todo("write_array")
125+
126+
def read_array(self, nbytes: int, dtype):
127+
raise _todo("read_array")

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Test package for the shared ring buffer challenge.

tests/official/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Official scoring tests.

0 commit comments

Comments
 (0)