From e512bde26f5a22b4c7b00551429ab34ec205f6d2 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 28 Aug 2026 18:43:13 +0200 Subject: [PATCH] Gate the invariants, fix ECOTE's unrunnable tests, contain build output Two invariants had been established by hand and left ungated, which is the "a remedy that only works if someone remembers" pattern: - scripts/check_ignore_consistency.sh fails the commit if any TRACKED file is also gitignored. .binary-allowlist used to *instruct* the reader to re-run that check; now it is enforced. Verified both ways -- passes on the tree, and fails when an ignored file is force-added. DOCS-runnability.md now really has two columns. Each environment writes only its own gates/runnability-.tsv and the doc renders from both, so a CI run cannot clobber the local results. The ci tsv is gitignored: committing a copy generated here would label this machine's capabilities as the runner's. Rendering it surfaced a classic bug -- awk's NR==FNR idiom silently swallows the SECOND file when the first is empty, so the table came out with a header and no rows. It compares FILENAME instead. runnability.yml deliberately does NOT diff against a committed baseline. A status is a property of the machine, so `runnable` locally and `blocked` on a runner is correct rather than drift, and diffing would make the workflow red by construction. It fails on `error` instead -- a runner that crashes or reports an unknown kind. Then five more courses were actually run, because EADS alone had produced four real bugs and one sample proves nothing: - ECOAR and EPFU build (nasm + 32-bit gcc, and gcc respectively). - ECOTE's 17 test modules could not run AT ALL. Its package is named `code/`, which shadows the standard library: pytest's debugging plugin imports pdb, pdb imports code, and it got this directory instead, so the suite died with an INTERNALERROR before collecting a single test. Renamed to translator/ with all 17 imports updated -- 26 tests now pass. - A LaTeX report builds. That one exposed a hazard rather than a bug: latexmk wrote its PDF beside the source, and in a course where the built name matches the submitted one it would have silently overwritten a tracked deliverable. Both the LaTeX and C++ runners now build into a gitignored build/, and rebuilding ECOGR leaves `git status` empty. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE --- .binary-allowlist | 7 +- .github/workflows/runnability.yml | 40 +++++++ .gitignore | 4 + .pre-commit-config.yaml | 9 ++ DOCS-runnability.md | 100 +++++++++--------- Programming/ECOTE/program/README.md | 20 +++- .../program/{code => translator}/.flake8 | 0 .../program/{code => translator}/.pylintrc | 0 .../program/{code => translator}/__init__.py | 0 .../texfilecolumnswrong.tex | 0 .../texfilecolumnswrong.tex.html | 0 .../PASScomplicatedTable/texfilelonger.pdf | Bin .../PASScomplicatedTable/texfilelonger.tex | 0 .../texfilelonger.tex.html | 0 .../PASSsimpleMultipleTables/texfile.pdf | Bin .../PASSsimpleMultipleTables/texfile.tex | 0 .../PASSsimpleMultipleTables/texfile.tex.html | 0 .../PassparametersTable/texfilelonger.pdf | Bin .../PassparametersTable/texfilelonger.tex | 0 .../texfilelonger.tex.html | 0 .../program/{code => translator}/main.py | 0 .../program/{code => translator}/texfile.tex | 0 .../{code => translator}/texfile.tex.html | 0 .../test_code/test_begin_document.py | 2 +- .../test_code/test_begin_tabular.py | 2 +- .../test_code/test_command_name_check.py | 2 +- .../test_code/test_document_class.py | 2 +- .../test_document_class_only_checks.py | 2 +- .../test_code/test_generic_checks.py | 2 +- .../test_code/test_generic_checks_command.py | 2 +- .../test_code/test_length_conversions.py | 2 +- .../test_code/test_only_pipes_and_space.py | 2 +- .../test_code/test_split_columns.py | 2 +- .../unit_tests/test_code/test_split_rows.py | 2 +- .../test_tabular_columns_parameters.py | 2 +- .../test_code/test_tabular_parameters.py | 2 +- .../test_tabular_required_parameters.py | 2 +- .../test_code/test_translate_column.py | 2 +- .../test_translate_inside_to_html.py | 4 +- Programming/ECOTE/run.sh | 2 +- gates/runnability-local.tsv | 44 ++++++++ run.sh | 41 ++++--- scripts/check_ignore_consistency.sh | 42 ++++++++ scripts/course_lib.sh | 12 ++- 44 files changed, 268 insertions(+), 87 deletions(-) create mode 100644 .github/workflows/runnability.yml rename Programming/ECOTE/program/{code => translator}/.flake8 (100%) rename Programming/ECOTE/program/{code => translator}/.pylintrc (100%) rename Programming/ECOTE/program/{code => translator}/__init__.py (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex.html (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PASScomplicatedTable/texfilelonger.pdf (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PASScomplicatedTable/texfilelonger.tex (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PASScomplicatedTable/texfilelonger.tex.html (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PASSsimpleMultipleTables/texfile.pdf (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PASSsimpleMultipleTables/texfile.tex (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PASSsimpleMultipleTables/texfile.tex.html (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PassparametersTable/texfilelonger.pdf (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PassparametersTable/texfilelonger.tex (100%) rename Programming/ECOTE/program/{code => translator}/functional_tests/PassparametersTable/texfilelonger.tex.html (100%) rename Programming/ECOTE/program/{code => translator}/main.py (100%) rename Programming/ECOTE/program/{code => translator}/texfile.tex (100%) rename Programming/ECOTE/program/{code => translator}/texfile.tex.html (100%) create mode 100644 gates/runnability-local.tsv create mode 100755 scripts/check_ignore_consistency.sh diff --git a/.binary-allowlist b/.binary-allowlist index b3f3f4ef..281172a6 100644 --- a/.binary-allowlist +++ b/.binary-allowlist @@ -18,9 +18,10 @@ # - the repo root, gates/, scripts/ and .github/ have no entry either, so a # binary added there is blocked. # -# Every pattern here must also survive `git ls-files | git check-ignore -# --stdin --no-index` printing nothing -- an allowlisted file that .gitignore -# also matches fails silently on `git add` rather than erroring. +# An allowlisted file that .gitignore ALSO matches fails silently on +# `git add` rather than erroring. That is enforced, not remembered: the +# ignore-consistency pre-commit hook (scripts/check_ignore_consistency.sh) +# fails the commit if any tracked file is gitignored. # --- Reports and presentations (155 .pdf, 3 .docx, 1 .doc, 1 .pptx) --------- Programming/*.pdf diff --git a/.github/workflows/runnability.yml b/.github/workflows/runnability.yml new file mode 100644 index 00000000..604964de --- /dev/null +++ b/.github/workflows/runnability.yml @@ -0,0 +1,40 @@ +name: runnability + +# Probes every course directory and fails if any RUNNER is broken. +# +# Deliberately NOT a diff against a committed baseline. A runner's status is a +# property of the machine, not of the commit: MATLAB, Unity and the .NET SDK +# are absent here and present locally, so `runnable` on a laptop and `blocked` +# on ubuntu-latest is correct rather than drift. Diffing the two would make +# this workflow red by construction, which is the one outcome the whole +# exercise is meant to avoid. +# +# What IS a defect, and what this catches: a run.sh that crashes, hangs, or +# reports an unknown kind. Those are `error`, and any `error` fails the job. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + runnability: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Probe every course directory + run: ./run.sh status + + - name: Fail on any broken runner + run: | + set -euo pipefail + if grep -qP '\terror\t' gates/runnability-ci.tsv; then + echo "::error::a course run.sh failed to probe" + grep -P '\terror\t' gates/runnability-ci.tsv + exit 1 + fi + echo "all runners probed cleanly" + cut -f2 gates/runnability-ci.tsv | sort | uniq -c diff --git a/.gitignore b/.gitignore index 50dce689..3c449e44 100644 --- a/.gitignore +++ b/.gitignore @@ -482,3 +482,7 @@ Programming/ERSMS-project/nginx/ssl/nginx.crt # Output of ./run.sh run for the C++ courses. build/ + +# Generated fresh by the runner on each CI run. Committing a local copy +# would label this machine capabilities as the runner is, which is a lie. +gates/runnability-ci.tsv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c508e6e..c56e2d52 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,6 +56,15 @@ repos: language: script pass_filenames: true + # A tracked file that .gitignore also matches fails silently on + # `git add`. 130 of those were fixed by hand; this keeps them fixed. + - id: ignore-consistency + name: no tracked file is gitignored + entry: scripts/check_ignore_consistency.sh + language: script + pass_filenames: false + always_run: true + # Thin shim into ~/utils, which owns the four markdown namespaces. - id: md-naming name: markdown naming convention diff --git a/DOCS-runnability.md b/DOCS-runnability.md index 06354bde..15ee9fad 100644 --- a/DOCS-runnability.md +++ b/DOCS-runnability.md @@ -2,53 +2,57 @@ Generated by `./run.sh status`. Do not edit by hand. -Status is recorded PER ENVIRONMENT. A directory that is `runnable` locally and +Status is recorded PER ENVIRONMENT, from `gates/runnability-local.tsv` and +`gates/runnability-ci.tsv`. A directory that is `runnable` locally and `blocked` in CI is the normal case, not drift: the runner has no MATLAB, no -Unity and no .NET SDK. CI compares only its own column. +Unity and no .NET SDK. Each environment rewrites only its own file, so a CI +run cannot clobber the local column. -| Course | local | Detail | -|---|---|---| -| `NotProgramming/DPZ` | runnable | builds the report with latexmk | -| `NotProgramming/ECOGR` | runnable | builds the report with latexmk | -| `NotProgramming/EDABA` | runnable | builds the report with latexmk | -| `NotProgramming/EDABA-LAB` | runnable | builds the report with latexmk | -| `NotProgramming/EDISE` | runnable | builds the report with latexmk | -| `NotProgramming/EDISP` | no-code | submitted lab reports as PDFs only; no source was produced | -| `NotProgramming/ELAC` | runnable | builds the report with latexmk | -| `NotProgramming/EMISY` | runnable | builds the report with latexmk | -| `NotProgramming/ENUME` | blocked | needs MATLAB or Octave (none of: matlab octave) | -| `NotProgramming/EPHY2` | no-code | submitted lab reports as PDFs only; no source was produced | -| `NotProgramming/EPOGAI` | no-code | a presentation only; no source was produced | -| `NotProgramming/ERPM` | runnable | builds the report with latexmk | -| `NotProgramming/ESDP1` | runnable | builds the report with latexmk | -| `NotProgramming/ESOEN` | runnable | builds the report with latexmk | -| `NotProgramming/MOM` | runnable | builds the report with latexmk | -| `NotProgramming/SDM` | runnable | builds the report with latexmk | -| `NotProgramming/SDM2` | runnable | builds the report with latexmk | -| `NotProgramming/SPD` | runnable | builds the report with latexmk | -| `NotProgramming/STUP` | runnable | npm install && npm start | -| `Programming/AIS` | runnable | builds the report with latexmk | -| `Programming/BD2` | runnable | npm install && npm start | -| `Programming/BSS` | runnable | builds the report with latexmk | -| `Programming/EADS` | runnable | compiles each standalone .cpp | -| `Programming/EARIN` | runnable | python3 the entry script | -| `Programming/ECOAR` | runnable | make | -| `Programming/ECOTE` | runnable | python3 /home/kuhy/WUT_Computer_Science/Programming/ECOTE/program/code/main.py | -| `Programming/ECRYPT` | no-code | test1.py is a 0-byte stub; this course produced no submitted code | -| `Programming/ECRYPT_PROJECT` | runnable | python3 /home/kuhy/WUT_Computer_Science/Programming/ECRYPT_PROJECT/code | -| `Programming/EGUI` | runnable | dotnet build | -| `Programming/EOOP` | runnable | compiles each standalone .cpp | -| `Programming/EOPSY` | runnable | make | -| `Programming/EPFU` | runnable | make | -| `Programming/E-PSYOPS` | runnable | opens in the Unity editor | -| `Programming/ERSMS-project` | runnable | docker compose up | -| `Programming/NLP` | runnable | python3 the entry script | -| `Programming/PBAD` | runnable | jupyter nbconvert --execute | -| `Programming/PORR` | runnable | python3 /home/kuhy/WUT_Computer_Science/Programming/PORR/code | -| `Programming/PSD` | runnable | starts the Kafka/Flink stack (installs packages with sudo) | -| `Programming/psd_project` | runnable | mvn package | -| `Programming/TRAK` | runnable | python3 the entry script | -| `Programming/twm_4` | runnable | jupyter nbconvert --execute | -| `Programming/USD` | no-code | README describes a MetaDrive RL assignment; no implementation was submitted | -| `Programming/WDWR` | runnable | python3 the entry script | -| `Programming/ZPOB` | runnable | builds the report with latexmk | +`-` means that environment has not probed yet. + +| Course | local | ci | Detail (local) | +|---|---|---|---| +| `NotProgramming/DPZ` | runnable | - | builds the report with latexmk | +| `NotProgramming/ECOGR` | runnable | - | builds the report with latexmk | +| `NotProgramming/EDABA` | runnable | - | builds the report with latexmk | +| `NotProgramming/EDABA-LAB` | runnable | - | builds the report with latexmk | +| `NotProgramming/EDISE` | runnable | - | builds the report with latexmk | +| `NotProgramming/EDISP` | no-code | - | submitted lab reports as PDFs only; no source was produced | +| `NotProgramming/ELAC` | runnable | - | builds the report with latexmk | +| `NotProgramming/EMISY` | runnable | - | builds the report with latexmk | +| `NotProgramming/ENUME` | blocked | - | needs MATLAB or Octave (none of: matlab octave) | +| `NotProgramming/EPHY2` | no-code | - | submitted lab reports as PDFs only; no source was produced | +| `NotProgramming/EPOGAI` | no-code | - | a presentation only; no source was produced | +| `NotProgramming/ERPM` | runnable | - | builds the report with latexmk | +| `NotProgramming/ESDP1` | runnable | - | builds the report with latexmk | +| `NotProgramming/ESOEN` | runnable | - | builds the report with latexmk | +| `NotProgramming/MOM` | runnable | - | builds the report with latexmk | +| `NotProgramming/SDM` | runnable | - | builds the report with latexmk | +| `NotProgramming/SDM2` | runnable | - | builds the report with latexmk | +| `NotProgramming/SPD` | runnable | - | builds the report with latexmk | +| `NotProgramming/STUP` | runnable | - | npm install && npm start | +| `Programming/AIS` | runnable | - | builds the report with latexmk | +| `Programming/BD2` | runnable | - | npm install && npm start | +| `Programming/BSS` | runnable | - | builds the report with latexmk | +| `Programming/EADS` | runnable | - | compiles each standalone .cpp | +| `Programming/EARIN` | runnable | - | python3 the entry script | +| `Programming/ECOAR` | runnable | - | make | +| `Programming/ECOTE` | runnable | - | python3 /home/kuhy/WUT_Computer_Science/Programming/ECOTE/program/translator/main.py | +| `Programming/ECRYPT` | no-code | - | test1.py is a 0-byte stub; this course produced no submitted code | +| `Programming/ECRYPT_PROJECT` | runnable | - | python3 /home/kuhy/WUT_Computer_Science/Programming/ECRYPT_PROJECT/code | +| `Programming/EGUI` | runnable | - | dotnet build | +| `Programming/EOOP` | runnable | - | compiles each standalone .cpp | +| `Programming/EOPSY` | runnable | - | make | +| `Programming/EPFU` | runnable | - | make | +| `Programming/E-PSYOPS` | runnable | - | opens in the Unity editor | +| `Programming/ERSMS-project` | runnable | - | docker compose up | +| `Programming/NLP` | runnable | - | python3 the entry script | +| `Programming/PBAD` | runnable | - | jupyter nbconvert --execute | +| `Programming/PORR` | runnable | - | python3 /home/kuhy/WUT_Computer_Science/Programming/PORR/code | +| `Programming/PSD` | runnable | - | starts the Kafka/Flink stack (installs packages with sudo) | +| `Programming/psd_project` | runnable | - | mvn package | +| `Programming/TRAK` | runnable | - | python3 the entry script | +| `Programming/twm_4` | runnable | - | jupyter nbconvert --execute | +| `Programming/USD` | no-code | - | README describes a MetaDrive RL assignment; no implementation was submitted | +| `Programming/WDWR` | runnable | - | python3 the entry script | +| `Programming/ZPOB` | runnable | - | builds the report with latexmk | diff --git a/Programming/ECOTE/program/README.md b/Programming/ECOTE/program/README.md index 9c884d58..97efcb58 100644 --- a/Programming/ECOTE/program/README.md +++ b/Programming/ECOTE/program/README.md @@ -1,9 +1,21 @@ -To run unit tests: -Enter project environment: +# ECOTE translator -source /ecote/bin/activate +Translates LaTeX `tabular` environments into HTML tables. -Run tests: +| Path | What it is | +|---|---| +| `translator/` | The implementation. Entry point `translator/main.py`. | +| `translator/functional_tests/` | End-to-end fixtures. | +| `unit_tests/test_code/` | 17 pytest modules. | +## Run the tests + +```bash python -m pytest unit_tests +``` +> The package used to be called `code/`, which shadows Python's standard +> library `code` module. That is not cosmetic: pytest's debugging plugin +> imports `pdb`, `pdb` imports `code`, and it got this directory instead, so +> the whole suite died with an INTERNALERROR before collecting a single test. +> Renaming it is what makes these tests runnable at all. diff --git a/Programming/ECOTE/program/code/.flake8 b/Programming/ECOTE/program/translator/.flake8 similarity index 100% rename from Programming/ECOTE/program/code/.flake8 rename to Programming/ECOTE/program/translator/.flake8 diff --git a/Programming/ECOTE/program/code/.pylintrc b/Programming/ECOTE/program/translator/.pylintrc similarity index 100% rename from Programming/ECOTE/program/code/.pylintrc rename to Programming/ECOTE/program/translator/.pylintrc diff --git a/Programming/ECOTE/program/code/__init__.py b/Programming/ECOTE/program/translator/__init__.py similarity index 100% rename from Programming/ECOTE/program/code/__init__.py rename to Programming/ECOTE/program/translator/__init__.py diff --git a/Programming/ECOTE/program/code/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex b/Programming/ECOTE/program/translator/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex rename to Programming/ECOTE/program/translator/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex diff --git a/Programming/ECOTE/program/code/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex.html b/Programming/ECOTE/program/translator/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex.html similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex.html rename to Programming/ECOTE/program/translator/functional_tests/ERRORwrongColumnsNumber/texfilecolumnswrong.tex.html diff --git a/Programming/ECOTE/program/code/functional_tests/PASScomplicatedTable/texfilelonger.pdf b/Programming/ECOTE/program/translator/functional_tests/PASScomplicatedTable/texfilelonger.pdf similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PASScomplicatedTable/texfilelonger.pdf rename to Programming/ECOTE/program/translator/functional_tests/PASScomplicatedTable/texfilelonger.pdf diff --git a/Programming/ECOTE/program/code/functional_tests/PASScomplicatedTable/texfilelonger.tex b/Programming/ECOTE/program/translator/functional_tests/PASScomplicatedTable/texfilelonger.tex similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PASScomplicatedTable/texfilelonger.tex rename to Programming/ECOTE/program/translator/functional_tests/PASScomplicatedTable/texfilelonger.tex diff --git a/Programming/ECOTE/program/code/functional_tests/PASScomplicatedTable/texfilelonger.tex.html b/Programming/ECOTE/program/translator/functional_tests/PASScomplicatedTable/texfilelonger.tex.html similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PASScomplicatedTable/texfilelonger.tex.html rename to Programming/ECOTE/program/translator/functional_tests/PASScomplicatedTable/texfilelonger.tex.html diff --git a/Programming/ECOTE/program/code/functional_tests/PASSsimpleMultipleTables/texfile.pdf b/Programming/ECOTE/program/translator/functional_tests/PASSsimpleMultipleTables/texfile.pdf similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PASSsimpleMultipleTables/texfile.pdf rename to Programming/ECOTE/program/translator/functional_tests/PASSsimpleMultipleTables/texfile.pdf diff --git a/Programming/ECOTE/program/code/functional_tests/PASSsimpleMultipleTables/texfile.tex b/Programming/ECOTE/program/translator/functional_tests/PASSsimpleMultipleTables/texfile.tex similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PASSsimpleMultipleTables/texfile.tex rename to Programming/ECOTE/program/translator/functional_tests/PASSsimpleMultipleTables/texfile.tex diff --git a/Programming/ECOTE/program/code/functional_tests/PASSsimpleMultipleTables/texfile.tex.html b/Programming/ECOTE/program/translator/functional_tests/PASSsimpleMultipleTables/texfile.tex.html similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PASSsimpleMultipleTables/texfile.tex.html rename to Programming/ECOTE/program/translator/functional_tests/PASSsimpleMultipleTables/texfile.tex.html diff --git a/Programming/ECOTE/program/code/functional_tests/PassparametersTable/texfilelonger.pdf b/Programming/ECOTE/program/translator/functional_tests/PassparametersTable/texfilelonger.pdf similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PassparametersTable/texfilelonger.pdf rename to Programming/ECOTE/program/translator/functional_tests/PassparametersTable/texfilelonger.pdf diff --git a/Programming/ECOTE/program/code/functional_tests/PassparametersTable/texfilelonger.tex b/Programming/ECOTE/program/translator/functional_tests/PassparametersTable/texfilelonger.tex similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PassparametersTable/texfilelonger.tex rename to Programming/ECOTE/program/translator/functional_tests/PassparametersTable/texfilelonger.tex diff --git a/Programming/ECOTE/program/code/functional_tests/PassparametersTable/texfilelonger.tex.html b/Programming/ECOTE/program/translator/functional_tests/PassparametersTable/texfilelonger.tex.html similarity index 100% rename from Programming/ECOTE/program/code/functional_tests/PassparametersTable/texfilelonger.tex.html rename to Programming/ECOTE/program/translator/functional_tests/PassparametersTable/texfilelonger.tex.html diff --git a/Programming/ECOTE/program/code/main.py b/Programming/ECOTE/program/translator/main.py similarity index 100% rename from Programming/ECOTE/program/code/main.py rename to Programming/ECOTE/program/translator/main.py diff --git a/Programming/ECOTE/program/code/texfile.tex b/Programming/ECOTE/program/translator/texfile.tex similarity index 100% rename from Programming/ECOTE/program/code/texfile.tex rename to Programming/ECOTE/program/translator/texfile.tex diff --git a/Programming/ECOTE/program/code/texfile.tex.html b/Programming/ECOTE/program/translator/texfile.tex.html similarity index 100% rename from Programming/ECOTE/program/code/texfile.tex.html rename to Programming/ECOTE/program/translator/texfile.tex.html diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_begin_document.py b/Programming/ECOTE/program/unit_tests/test_code/test_begin_document.py index 12c21341..ae404221 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_begin_document.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_begin_document.py @@ -1,7 +1,7 @@ """ Tests begin document function """ -from code.main import begin_document +from translator.main import begin_document # Write python tests for a function translating LaTeX documentclass to html def given_empty_then_error(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_begin_tabular.py b/Programming/ECOTE/program/unit_tests/test_code/test_begin_tabular.py index dd02bd7a..91d6ae32 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_begin_tabular.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_begin_tabular.py @@ -1,7 +1,7 @@ """ Tests begin document function """ -from code.main import begin_tabular +from translator.main import begin_tabular # Write python tests for a function translating LaTeX documentclass to html def given_empty_then_error(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_command_name_check.py b/Programming/ECOTE/program/unit_tests/test_code/test_command_name_check.py index b5fe891d..57d72abd 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_command_name_check.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_command_name_check.py @@ -1,4 +1,4 @@ -from code.main import command_name_check +from translator.main import command_name_check def given_valid_then_empty(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_document_class.py b/Programming/ECOTE/program/unit_tests/test_code/test_document_class.py index 94520315..a06d6770 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_document_class.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_document_class.py @@ -1,7 +1,7 @@ """ Tests document class function """ -from code.main import document_class +from translator.main import document_class # Write python tests for a function translating LaTeX documentclass to html diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_document_class_only_checks.py b/Programming/ECOTE/program/unit_tests/test_code/test_document_class_only_checks.py index ad8aa299..b85f22ea 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_document_class_only_checks.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_document_class_only_checks.py @@ -1,4 +1,4 @@ -from code.main import document_class_only_checks +from translator.main import document_class_only_checks def test_missing_curly_bracket(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks.py b/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks.py index 545c4d46..454ebe95 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks.py @@ -1,4 +1,4 @@ -from code.main import generic_checks +from translator.main import generic_checks def test_empty_string(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks_command.py b/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks_command.py index f4793538..cd15fba0 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks_command.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_generic_checks_command.py @@ -1,4 +1,4 @@ -from code.main import generic_checks_command +from translator.main import generic_checks_command def test_empty_string(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_length_conversions.py b/Programming/ECOTE/program/unit_tests/test_code/test_length_conversions.py index 14229661..cedcb55f 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_length_conversions.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_length_conversions.py @@ -1,7 +1,7 @@ """ Tests begin document function """ -from code.main import length_conversions +from translator.main import length_conversions # Write python tests for a function translating LaTeX documentclass to html diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_only_pipes_and_space.py b/Programming/ECOTE/program/unit_tests/test_code/test_only_pipes_and_space.py index c330c426..d6d7024f 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_only_pipes_and_space.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_only_pipes_and_space.py @@ -1,4 +1,4 @@ -from code.main import only_pipes_and_space +from translator.main import only_pipes_and_space def given_empty_then_true(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_split_columns.py b/Programming/ECOTE/program/unit_tests/test_code/test_split_columns.py index 30f6012d..48dfc59a 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_split_columns.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_split_columns.py @@ -1,4 +1,4 @@ -from code.main import split_columns +from translator.main import split_columns def given_empty_then_correct(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_split_rows.py b/Programming/ECOTE/program/unit_tests/test_code/test_split_rows.py index 98c9e43e..3424ee8c 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_split_rows.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_split_rows.py @@ -1,4 +1,4 @@ -from code.main import split_rows +from translator.main import split_rows def given_empty_then_correct(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_tabular_columns_parameters.py b/Programming/ECOTE/program/unit_tests/test_code/test_tabular_columns_parameters.py index 07a91130..f289bdfd 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_tabular_columns_parameters.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_tabular_columns_parameters.py @@ -1,4 +1,4 @@ -from code.main import tabular_columns_parameters +from translator.main import tabular_columns_parameters def given_empty_then_error(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_tabular_parameters.py b/Programming/ECOTE/program/unit_tests/test_code/test_tabular_parameters.py index eade0793..90a47d86 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_tabular_parameters.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_tabular_parameters.py @@ -2,7 +2,7 @@ Tests tabular_parameters function """ -from code.main import tabular_parameters +from translator.main import tabular_parameters def given_empty_then_empty(): """ diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_tabular_required_parameters.py b/Programming/ECOTE/program/unit_tests/test_code/test_tabular_required_parameters.py index fb6623ae..f700737c 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_tabular_required_parameters.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_tabular_required_parameters.py @@ -2,7 +2,7 @@ Tests tabular_parameters function """ -from code.main import tabular_required_parameters +from translator.main import tabular_required_parameters def given_empty_then_error(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_translate_column.py b/Programming/ECOTE/program/unit_tests/test_code/test_translate_column.py index 493741af..a7861f19 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_translate_column.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_translate_column.py @@ -1,4 +1,4 @@ -from code.main import translate_column +from translator.main import translate_column def given_empty_then_correct(): diff --git a/Programming/ECOTE/program/unit_tests/test_code/test_translate_inside_to_html.py b/Programming/ECOTE/program/unit_tests/test_code/test_translate_inside_to_html.py index a949a1a5..9ac4f2c3 100644 --- a/Programming/ECOTE/program/unit_tests/test_code/test_translate_inside_to_html.py +++ b/Programming/ECOTE/program/unit_tests/test_code/test_translate_inside_to_html.py @@ -1,5 +1,5 @@ -from code.main import translate_inside_to_html -from code.main import tabular_required_parameters +from translator.main import translate_inside_to_html +from translator.main import tabular_required_parameters def given_correct_then_correct(): diff --git a/Programming/ECOTE/run.sh b/Programming/ECOTE/run.sh index 2e0cb758..9252dc47 100755 --- a/Programming/ECOTE/run.sh +++ b/Programming/ECOTE/run.sh @@ -8,5 +8,5 @@ set -euo pipefail . "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh" course_main --kind python \ - --entry "$(dirname "$(readlink -f "$0")")/program/code/main.py" \ + --entry "$(dirname "$(readlink -f "$0")")/program/translator/main.py" \ -- "$@" diff --git a/gates/runnability-local.tsv b/gates/runnability-local.tsv new file mode 100644 index 00000000..98cc7133 --- /dev/null +++ b/gates/runnability-local.tsv @@ -0,0 +1,44 @@ +NotProgramming/DPZ runnable builds the report with latexmk +NotProgramming/ECOGR runnable builds the report with latexmk +NotProgramming/EDABA runnable builds the report with latexmk +NotProgramming/EDABA-LAB runnable builds the report with latexmk +NotProgramming/EDISE runnable builds the report with latexmk +NotProgramming/EDISP no-code submitted lab reports as PDFs only; no source was produced +NotProgramming/ELAC runnable builds the report with latexmk +NotProgramming/EMISY runnable builds the report with latexmk +NotProgramming/ENUME blocked needs MATLAB or Octave (none of: matlab octave) +NotProgramming/EPHY2 no-code submitted lab reports as PDFs only; no source was produced +NotProgramming/EPOGAI no-code a presentation only; no source was produced +NotProgramming/ERPM runnable builds the report with latexmk +NotProgramming/ESDP1 runnable builds the report with latexmk +NotProgramming/ESOEN runnable builds the report with latexmk +NotProgramming/MOM runnable builds the report with latexmk +NotProgramming/SDM runnable builds the report with latexmk +NotProgramming/SDM2 runnable builds the report with latexmk +NotProgramming/SPD runnable builds the report with latexmk +NotProgramming/STUP runnable npm install && npm start +Programming/AIS runnable builds the report with latexmk +Programming/BD2 runnable npm install && npm start +Programming/BSS runnable builds the report with latexmk +Programming/EADS runnable compiles each standalone .cpp +Programming/EARIN runnable python3 the entry script +Programming/ECOAR runnable make +Programming/ECOTE runnable python3 /home/kuhy/WUT_Computer_Science/Programming/ECOTE/program/translator/main.py +Programming/ECRYPT no-code test1.py is a 0-byte stub; this course produced no submitted code +Programming/ECRYPT_PROJECT runnable python3 /home/kuhy/WUT_Computer_Science/Programming/ECRYPT_PROJECT/code +Programming/EGUI runnable dotnet build +Programming/EOOP runnable compiles each standalone .cpp +Programming/EOPSY runnable make +Programming/EPFU runnable make +Programming/E-PSYOPS runnable opens in the Unity editor +Programming/ERSMS-project runnable docker compose up +Programming/NLP runnable python3 the entry script +Programming/PBAD runnable jupyter nbconvert --execute +Programming/PORR runnable python3 /home/kuhy/WUT_Computer_Science/Programming/PORR/code +Programming/PSD runnable starts the Kafka/Flink stack (installs packages with sudo) +Programming/psd_project runnable mvn package +Programming/TRAK runnable python3 the entry script +Programming/twm_4 runnable jupyter nbconvert --execute +Programming/USD no-code README describes a MetaDrive RL assignment; no implementation was submitted +Programming/WDWR runnable python3 the entry script +Programming/ZPOB runnable builds the report with latexmk diff --git a/run.sh b/run.sh index 2e26a599..1479532c 100755 --- a/run.sh +++ b/run.sh @@ -84,39 +84,56 @@ probe_one() { cmd_status() { local env_label="${CI:+ci}" env_label="${env_label:-local}" + local store="${SCRIPT_DIR}/gates/runnability-${env_label}.tsv" log "probing course directories (${env_label})" - local tmp - tmp="$(mktemp)" - trap 'rm -f "${tmp:-}"' RETURN - local dir rel line + : > "$store" while IFS= read -r dir; do rel="${dir#"${SCRIPT_DIR}"/}" line="$(probe_one "$dir")" - printf '%s\t%s\n' "$rel" "$line" >> "$tmp" + printf '%s\t%s\n' "$rel" "$line" >> "$store" done < <(course_dirs) - write_runnability_doc "$tmp" "$env_label" + # Each environment writes only its OWN file, then the doc is rendered from + # both. Writing a single shared column would mean a CI run silently + # overwrote the local results with its own -- and the whole point of + # recording per environment is that "runnable here, blocked on a runner" + # is the normal case rather than drift. + write_runnability_doc log "wrote ${RUNNABILITY_DOC##*/}" - awk -F'\t' '{ n[$2]++ } END { for (k in n) printf " %-10s %d\n", k, n[k] }' "$tmp" + awk -F'\t' '{ n[$2]++ } END { for (k in n) printf " %-10s %d\n", k, n[k] }' "$store" } write_runnability_doc() { - local data="$1" env_label="$2" + local local_tsv="${SCRIPT_DIR}/gates/runnability-local.tsv" + local ci_tsv="${SCRIPT_DIR}/gates/runnability-ci.tsv" + [[ -f "$local_tsv" ]] || : > "$local_tsv" + [[ -f "$ci_tsv" ]] || : > "$ci_tsv" + { cat <<'MD' # Runnability Generated by `./run.sh status`. Do not edit by hand. -Status is recorded PER ENVIRONMENT. A directory that is `runnable` locally and +Status is recorded PER ENVIRONMENT, from `gates/runnability-local.tsv` and +`gates/runnability-ci.tsv`. A directory that is `runnable` locally and `blocked` in CI is the normal case, not drift: the runner has no MATLAB, no -Unity and no .NET SDK. CI compares only its own column. +Unity and no .NET SDK. Each environment rewrites only its own file, so a CI +run cannot clobber the local column. + +`-` means that environment has not probed yet. MD - printf '| Course | %s | Detail |\n|---|---|---|\n' "$env_label" - awk -F'\t' '{ printf "| `%s` | %s | %s |\n", $1, $2, $3 }' "$data" + printf '| Course | local | ci | Detail (local) |\n|---|---|---|---|\n' + # Compare FILENAME rather than using the NR==FNR idiom: with an EMPTY + # first file awk never reads it, so NR==FNR stays true for every row + # of the second file and the whole table comes out blank. + awk -F'\t' -v cifile="$ci_tsv" ' + FILENAME == cifile { ci[$1] = $2; next } + { printf "| `%s` | %s | %s | %s |\n", $1, $2, ($1 in ci ? ci[$1] : "-"), $3 } + ' "$ci_tsv" "$local_tsv" } > "$RUNNABILITY_DOC" } diff --git a/scripts/check_ignore_consistency.sh b/scripts/check_ignore_consistency.sh new file mode 100755 index 00000000..c048175f --- /dev/null +++ b/scripts/check_ignore_consistency.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# ============================================================================ +# Fail if any TRACKED file is also matched by a .gitignore rule. +# +# That combination fails silently: `git add` on such a file is not an error, +# the edit simply never lands. This repo had 130 of them -- dist/ swallowing +# the vendored jQuery bundle, and the LaTeX template's man-page patterns +# (*.[1-9]) eating EPFU's date-named doc/21.11/ directory along with its +# report.pdf. +# +# They were fixed by hand once. This is the gate that keeps them fixed, so +# that .binary-allowlist can stop *instructing* the reader to re-run a command +# and just have it enforced instead. +# +# Usage: scripts/check_ignore_consistency.sh +# ============================================================================ + +set -euo pipefail + +main() { + local offenders + offenders="$(git ls-files | git check-ignore --stdin --no-index 2>/dev/null || true)" + + if [[ -z "$offenders" ]]; then + exit 0 + fi + + echo "Tracked files that .gitignore also matches:" >&2 + printf '%s\n' "$offenders" | sed 's/^/ /' >&2 + cat >&2 <<'MSG' + +Each of these fails SILENTLY on `git add` -- not an error, the edit just +never lands. Fix by either: + 1. adding a "!" override in .gitignore (if the file belongs in the repo), or + 2. `git rm --cached` it (if it is build output that should never have been + tracked; --cached keeps it on disk). +MSG + exit 1 +} + +main "$@" diff --git a/scripts/course_lib.sh b/scripts/course_lib.sh index 8b238f81..29e46eec 100644 --- a/scripts/course_lib.sh +++ b/scripts/course_lib.sh @@ -140,11 +140,19 @@ _run() { [[ -n "$tex" ]] || _no_code "no .tex found" local tool tool="$(_require_any 'a LaTeX toolchain' latexmk pdflatex xelatex)" + # Build into build/, never beside the source. A submitted report.pdf + # is a tracked deliverable, and a rebuild whose output name happens + # to match it would silently overwrite what was handed in. + local dir base + dir="$(dirname "$tex")" + base="$(basename "$tex")" if [[ $tool == latexmk ]]; then - ( cd "$(dirname "$tex")" && latexmk -pdf -interaction=nonstopmode "$(basename "$tex")" ) + ( cd "$dir" && latexmk -pdf -interaction=nonstopmode -outdir=build "$base" ) else - ( cd "$(dirname "$tex")" && "$tool" -interaction=nonstopmode "$(basename "$tex")" ) + ( cd "$dir" && mkdir -p build \ + && "$tool" -interaction=nonstopmode -output-directory=build "$base" ) fi + printf 'output in %s/build\n' "$dir" ;; python) local entry="${COURSE_ENTRY:-}"