From 8c08fb626f0a7152afd1b8496836fb7e8c4af37d Mon Sep 17 00:00:00 2001 From: trevon Date: Wed, 26 Aug 2026 01:12:23 +0000 Subject: [PATCH] push (solved) --- .forgejo/workflows/grade.yml | 34 +++ .gitignore | 4 + Makefile | 40 +++ README.md | 100 +++++++ compile_commands.json | 27 ++ .../test_hidden.cpython-314-pytest-9.1.1.pyc | Bin 0 -> 7914 bytes hidden/test_hidden.py | 44 +++ include/buf.h | 28 ++ include/list.h | 22 ++ include/record.h | 40 +++ include/store.h | 22 ++ src/buf.c | 101 +++++++ src/list.c | 113 ++++++++ src/main.c | 262 ++++++++++++++++++ src/record.c | 60 ++++ src/store.c | 94 +++++++ .../test_public.cpython-314-pytest-9.0.3.pyc | Bin 0 -> 5447 bytes .../test_public.cpython-314-pytest-9.1.1.pyc | Bin 0 -> 5396 bytes tests/public/test_public.py | 32 +++ tests/smoke.sh | 84 ++++++ tests/unit.c | 92 ++++++ 21 files changed, 1199 insertions(+) create mode 100644 .forgejo/workflows/grade.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 compile_commands.json create mode 100644 hidden/__pycache__/test_hidden.cpython-314-pytest-9.1.1.pyc create mode 100644 hidden/test_hidden.py create mode 100644 include/buf.h create mode 100644 include/list.h create mode 100644 include/record.h create mode 100644 include/store.h create mode 100644 src/buf.c create mode 100644 src/list.c create mode 100644 src/main.c create mode 100644 src/record.c create mode 100644 src/store.c create mode 100644 tests/public/__pycache__/test_public.cpython-314-pytest-9.0.3.pyc create mode 100644 tests/public/__pycache__/test_public.cpython-314-pytest-9.1.1.pyc create mode 100644 tests/public/test_public.py create mode 100644 tests/smoke.sh create mode 100644 tests/unit.c diff --git a/.forgejo/workflows/grade.yml b/.forgejo/workflows/grade.yml new file mode 100644 index 0000000..9e4d6c2 --- /dev/null +++ b/.forgejo/workflows/grade.yml @@ -0,0 +1,34 @@ +name: grade +on: [push] + +# Production wiring (gitea-runner on ghost, docker mode): +# - job container = gitea/runner-images (has docker CLI) + the host docker +# socket, so docker_grade.sh drives the host daemon exactly as designed +# - hidden tests mount from the runner host (/opt/grade-hidden) — never in +# this repo +# - GRADE_HMAC_SECRET mounts from /opt/grade/grade.env on the runner host +jobs: + grade: + runs-on: ubuntu-latest + container: + image: gitea/runner-images:ubuntu-22.04 + options: >- + -v /var/run/docker.sock:/var/run/docker.sock + -v /opt/grade/grade.env:/run/secrets/grade.env:ro + -v /opt/grade-hidden:/opt/grade-hidden:ro + steps: + - uses: actions/checkout@v4 + - name: Grade (blank fails, solved passes) + run: | + set -a; . /run/secrets/grade.env; set +a + docker run --rm --network=none \ + -v "$PWD":/work \ + -v /opt/grade-hidden/cs2060/record-manager:/hidden:ro \ + -e GRADE_HMAC_SECRET \ + grade-c:dev \ + python3 /opt/grade/grade_cli.py run \ + --student /work --hidden /hidden \ + --course CS2060 --assignment record-manager \ + --out /work/results.json + echo "--- signed result summary ---" + head -c 400 results.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5fd711e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +.cache/ +*.o +*.d diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f0bac10 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# shellcheck disable=SC2046,SC2035,SC2215,SC2285,SC2276,SC2068 +CC ?= cc +CFLAGS ?= -std=c17 -Wall -Wextra -Wpedantic -Werror -g +CPPFLAGS += -Iinclude -MMD -MP +SANFLAGS := -fsanitize=address,undefined -fno-omit-frame-pointer + +SRC := $(wildcard src/*.c) +OBJ := $(SRC:src/%.c=build/%.o) +DEP := $(OBJ:.o=.d) +BIN := build/recman +ASAN := build/recman_asan + +all: $(BIN) + +$(BIN): $(OBJ) + $(CC) $(CFLAGS) $^ -o $@ + +$(ASAN): $(SRC) + $(CC) $(CFLAGS) $(SANFLAGS) $(CPPFLAGS) $^ -o $@ + +build/%.o: src/%.c | build + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +build: + mkdir -p build + +asan: $(ASAN) + +test: $(BIN) $(ASAN) + ASAN_OPTIONS=detect_leaks=0 sh tests/smoke.sh $(BIN) + ASAN_OPTIONS=detect_leaks=0 sh tests/smoke.sh $(ASAN) + $(CC) $(CFLAGS) $(SANFLAGS) $(CPPFLAGS) -Itests tests/unit.c $(filter-out src/main.c,$(SRC)) -o build/unit_asan + ASAN_OPTIONS=detect_leaks=0 ./build/unit_asan + +clean: + rm -rf build + +-include $(DEP) + +.PHONY: all asan test clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7fd3a4 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Record Manager — Instructor Reference Solution (M1–M5) + +**PRIVATE — never ships to students.** This is the reference implementation for +the Record Manager cumulative project (`assignments/projects/`), milestone by +milestone, plus the graded-interface contract and grading notes. + +## Layout + +``` +include/record.h M1 record_t + record_new/free + status_t +include/buf.h M2 dynamic string buffer (+ readline) +include/list.h M3 opaque list_t + append/find/remove (+ len/get/sort helpers) +include/store.h M4 store_save / store_load (text file, '|' delimiter) +src/record.c M1 deep-copy record lifecycle +src/buf.c M2 growable buffer: append/appendc/cstr/reset/free/readline +src/list.c M3 singly linked list; remove frees node + record +src/store.c M4 one record per line; load appends + reports count +src/main.c M5 menu (add/list/search/delete/save/load/help/quit) +Makefile builds recman + recman_asan; `make test` runs unit + smoke +tests/unit.c direct tests of buf/list/store (links library sources) +tests/smoke.sh end-to-end CLI session +``` + +## Build + verify + +```bash +make # recman +make asan # recman_asan (ASan+UBSan) +make test # unit + smoke, both under ASan/UBSan +``` + +Reference must be `-std=c17 -Wall -Wextra -Wpedantic -Werror` clean and report +`0 bytes lost` under Valgrind. + +## Graded interface (what hidden tests touch) + +`record_new`, `record_free`, `list_new`, `list_free`, `list_append`, `list_find`, +`list_remove`, `store_save`, `store_load` — exact signatures in +`assignments/projects/README.md`. This reference adds three non-graded helpers +(`list_len`, `list_get`, `list_sort`) that the menu and store layer need; they +are extra, not substitutes. + +### Contract decisions (documented for graders) + +- **`list_remove`**: returns `0` on success, `STATUS_ERR_NOTFOUND` when no record + matches, `STATUS_ERR_ARG` on NULL args. Frees the matching record + node. +- **`store_load`**: returns the **number of records read** (≥ 0) on success; + negative `status_t` on error. "0 on success" from the interface table is + satisfied when the file is empty/blank. It **appends** into the existing list. +- **Delimiter `|`**: none of the three flavors' fields (email/phone, assignment/ + score, category/amount) can contain `|`; `name` may contain spaces — the + delimiter is safe across all flavors. +- **Empty fields**: saved as empty strings; loaded back as empty strings + (never NULL from `store_load`). + +## Milestone-by-milestone notes + +### M2 — dynamic string buffer (week 7) + +Hidden tests likely: appends beyond initial capacity, `buf_cstr` NUL-termination +after every append, reset-then-reuse, `buf_readline` on a long line (>256 chars), +EOF/empty-line behavior. Sticky-OOM flag keeps the buffer usable after a failed +realloc. **Common mistake:** forgetting the `+1` for the NUL in `buf_grow`, or +realloc-ing to a shrinking capacity. + +### M3 — linked list (week 11) + +Hidden tests likely: append to empty list, find across many nodes, remove head / +remove middle / remove missing, `list_free` of a 3-node list under Valgrind +(0 lost), NULL-arg safety. **Common mistakes:** freeing the record and then +walking `next` (order matters — save `next` first); `list_remove` freeing the +node but not the record (double-free later); storing `record_t *` by value +instead of pointer. + +### M4 — text file save/load (week 13) + +Hidden tests likely: save→load round-trip preserves order + all three fields; +load into a non-empty list appends; file with blank lines; missing file returns +error, not crash; fields containing spaces survive (the `|` delimiter). +**Common mistakes:** assuming `fgets` succeeds; writing with `fprintf` and never +checking `ferror`; not stripping `\n`; `strtok` mangles empty fields (use +`strchr` splitting instead — empty field1/field2 must survive). + +### M5 — refactor + search/sort + hardening (week 15) + +Hidden tests likely: multi-file build with one `make`; case-insensitive +substring `search` across **all three** fields; `sort` alphabetical by name; +ASan/UBSan-clean; Valgrind 0 lost. **Common mistakes:** search matching only +`name`; sort comparing `record_t*` pointers instead of `->name`; `-Werror` +violations from unused includes after the refactor. + +## Grading sanity checks + +1. `make` from a clean tree (no build/ artifacts committed). +2. `make asan && ./build/recman_asan` → add 3 records → delete middle → save → + quit → reload → `list` shows the surviving two in order. +3. `valgrind --leak-check=full ./build/recman` with the same session → `0 bytes + lost` (the menu leaks nothing). +4. Feed the `tests/` fixtures: long input line (buf), 10k-record list (list), + save/load with spaces and empties (store). diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..c43493e --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,27 @@ +[ + { + "directory": "/home/trevon/Documents/work/coursework/instructor/CS2060/record-manager", + "command": "cc -std=c17 -Wall -Wextra -Wpedantic -Werror -g -Iinclude -MMD -MP -c src/main.c -o build/main.o", + "file": "src/main.c" + }, + { + "directory": "/home/trevon/Documents/work/coursework/instructor/CS2060/record-manager", + "command": "cc -std=c17 -Wall -Wextra -Wpedantic -Werror -g -Iinclude -MMD -MP -c src/record.c -o build/record.o", + "file": "src/record.c" + }, + { + "directory": "/home/trevon/Documents/work/coursework/instructor/CS2060/record-manager", + "command": "cc -std=c17 -Wall -Wextra -Wpedantic -Werror -g -Iinclude -MMD -MP -c src/buf.c -o build/buf.o", + "file": "src/buf.c" + }, + { + "directory": "/home/trevon/Documents/work/coursework/instructor/CS2060/record-manager", + "command": "cc -std=c17 -Wall -Wextra -Wpedantic -Werror -g -Iinclude -MMD -MP -c src/list.c -o build/list.o", + "file": "src/list.c" + }, + { + "directory": "/home/trevon/Documents/work/coursework/instructor/CS2060/record-manager", + "command": "cc -std=c17 -Wall -Wextra -Wpedantic -Werror -g -Iinclude -MMD -MP -c src/store.c -o build/store.o", + "file": "src/store.c" + } +] diff --git a/hidden/__pycache__/test_hidden.cpython-314-pytest-9.1.1.pyc b/hidden/__pycache__/test_hidden.cpython-314-pytest-9.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a257e4296223f36a5e86f55cf015ecf3a23e3e02 GIT binary patch literal 7914 zcmeGhTWl29b!KPw;n}s>H4l?uU@*jZi|zHUADDoh@QjflGE*4h$}o&~27ANq&iKwO zhE0$xH6J7-fqvEfDpf1h`D&_ED$Pfms2};M+BIN6(|oock*byyTBNEUJ@?LIXSbUe zKB$nItDU*$oO{l>=iGDV-q|xdTADcu+J}FB@AaV&MP)JKwtxdX7zbdU5-5{8O$qd2 z+6<7M8DmR<&D2=X3~jbY!e*F^M9hc~XrlzCjmpQ(CLtuS0MRBV1luSxD$rZ0WLVxn zQ`A^aQPd)zE1D`gD`!>SR1K3qt0CT;QF%elYDnRa%0+ovMSRy(UM`;F3nk1V=kndW zq2$GmM;8^J~P+6uwi7i81G^LALLQZrgXO_@kNuN6}Vv}_4`XV~kN)k?@v$nYfwOU;ltr*J$Z8}8LFS}{qIi?B?xsY()xz?3n#j5fH< zQ;#T`-t;BDy*qdH&9~nq-~$-??<^$?GfJesei6olSiZImI4CR+{c4gyEZ_+9Sq$XO z2NY_;gfJ!qC8TL4Y6h4Xx)y+W>hoyp)jeSA6qJC;t-!fC1Fu+TXSeMZD*+2y2c$S$Mkm~`S8K11zB9V8 zbt!scQK>{v+*kv*Pv+oO5sm|~!k)0*1a_}sH%T~Ah+_gg4o zyU4-yT-!S zIw3&)az|-_6@osG8uJI-d4$l4yo}46V!C~6tNUkJ)5t0z4AH=eQrEhDIB=}@p$_Qh zaAy}HeyY$U22*s6J)y}Bt|p&P?Jlj_!+M2Wt!@unBDb5?1G{OoU!(-K8$W(d6QX`h z4~yaPP3~B=pY5ndyqaUvzpjg4zCWxQAOK?ReDo(C-Lnfo=3g3*d)YvI-9W035nT{8|CioZjr&y z^X%{3wVQ@hrsfH4yoV&k80;b6I{A@r4{2R-RidsZ!fIj7Q^*9))$e-qnEQ<<2br+u zsqP_bMXpgC{7;GN!~bN5h{6u>TVdTd*@1Vy@y5^c|9f_{eUlx}M0TuyCijl#^yU@M zqGsP&)Y41o0pa=C-#6L^Xa6m(BIi;6>_^@n4)0v;l(0dMi!F_0^n@4}6Ie=bJ>IHE z#i%O@Z^VCn%+rj4(`?7dKlo0w)hnK6)wi6G(Eb!ovljOxaQ%7A)6A2D)2v-+TG5YH z?mTtxDj{K`ut^Nny_c>Q1LIrp^HAHw)vlJ!l*o7%fBbms|5_`ofA?$g*Al)6Y+ljw zxI3r7Kf@8&vZ5ae*N?ggtPwhdt>T)x2(0~z2&{FrZ1(>L?5K?s#^4_V{DT~D-VRe# z&Hs*dK5HAl-EV9B95#H1dBIN=I>mK$zO+5T`*54r!wY(YM!mIFQe%S_Mf8H>>`_F2KHQZBDxm=FfbE&EO!K5~FRwD<6lg9l#Z z%5iQKSIm&fztE*$+?j-G0(7t90#X)AJLSzp0WS6c#*=zlahjLULcNS9^X;!uFpxC3 zH?SrEpdg2#j7tK*A@wDVUiCuGFb(cfSCYGg=Nu?YvZ8Q%6`4PxomcZvD&%BEe)$47 zI5^nTm+tEWRN-DhxP~~U>AH%zX^a5Jz`#IHUj{gE#g0pv1wDldZWYSBU~;pioXJ7$ zE0`-nIiiN%o=hq4#2v5X6dqRt!72m% zatoPGWs%nr)ISY=yHhaSo&=>GxUSg9*gPn(EiT9W7M5c*^9N9-9L5X0%MRKuivK<$cSR#Q~& z5WRg@Y?qiN8CeM_E9dhPQgxKo z3OW>otr!eT_z5wgK5B*X8gLladdZm4N_mB_O4E65O3q6~xu9B2k|E>ru#UCaby&%M z{0hdj6|#LNy^4_4vX?xWP{2NjkcO<)u84X;hvKzt!iW{JA0#Uz!y{2ttQMDPO4IV1 zo?LISq@?*gY>8GB+iOpl_9Fv+q~Att^M?G$Fv2wyKV{I07*K))L(*ugH)hY!=U1BX z7&GXFI>sS;9UZ&UHP~Pc)^B6BY3aTiY-bI&%a3JhI5Qaom#_G*Xm-VPBPnFtHEzkN zaZBDu3?zU8d~O(@LTwS3QsY;4&L8+wEPkzXVcSob>j!?^v^ZEvq;JJC^9S!m<96w| z!X8_gCAXz0f0sRG|8BF#D$xxd>H(JrJ@6)b%*F2bxIuDZ)?>N_BOU!8s=v(tJipwr z?~@asO@2DL+~LgPvDpAvaj!XX?UnaVFU%}Acg`QW7foCpd3&V7j$9tO@)C6KzjT)! zfnTK^-|K$Mz&7O3ZdS27Sl%AE4WOgL;{>_;kwDXq`wSIqe#lWx&F^NfbzV7l`PB8U z+mRjfd+$a03On}Gu^){=cYSOr+I^QDvwyePu?k)`j4u0NkKqnT?DeplRSXX}N?;iD zENY+7pG7{6EcaH)o*)1)_cLTjYlT(6Kl8mA=&sEyMK|4LRr_~~RWI+f!R0FG4c`HN z*~4yDF+4Ei4S<#k|DDRI(|1p)OQ+Oj-qG$c*Z|CaKSafc=f^&Ws2SIWuDo%%aDDi8 zQ}_J7F9U&K=M1Ctg< zYl#g^y3xoahgfTpGegxbpGm|BjFTA%ORey|NY`TKXS+VwwHz6E#K6K-)p$!(l_i73 zL-Lg;Nq_MR*wK