Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .github/scripts/post-result.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ else
heading="Tests did not pass ($RESULT)"
fi

summary=""
if [[ -n "${RESULTS_DIR:-}" && -d "${RESULTS_DIR:-}" ]]; then
summary="$(python3 .github/scripts/summarise-results.py "$RESULTS_DIR")"
fi

body="$(
cat << EOF
**$heading**

| | |
| --- | --- |
| Groups run | \`$TEST_GROUPS\` |
| Commit tested | \`$SHORT_SHA\` (\`$SHA\`) |
| Full log | [Actions run]($RUN_URL) |
- **Groups run:** \`$TEST_GROUPS\`
- **Commit tested:** \`$SHORT_SHA\`
- **Full log:** [Actions run]($RUN_URL)

$summary
EOF
)"

Expand Down
111 changes: 111 additions & 0 deletions .github/scripts/summarise-results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Turns the test result files into the markdown that goes in the reply comment.

Both pytest and vitest write their results as JUnit XML,
the workflow collects them, and this reads and prints a summary.

Usage:
python3 summarise-results.py <directory of xml files>
"""

import sys
import xml.etree.ElementTree as ElementTree
from pathlib import Path

MAX_FAILURES_LISTED = 20


def counts_for(root: ElementTree.Element) -> tuple[int, int, int]:
"""Total, failed and skipped, added up over every suite in one file."""
total = failed = skipped = 0

suites = root.iter("testsuite")

for suite in suites:
total += int(suite.get("tests", 0))
failed += int(suite.get("failures", 0)) + int(suite.get("errors", 0))
skipped += int(suite.get("skipped", 0))

return total, failed, skipped


def failures_in(root: ElementTree.Element, group: str) -> list[str]:
"""One markdown bullet per failing test, saying where it lives."""
bullets = []

for case in root.iter("testcase"):
problem = case.find("failure")
if problem is None:
problem = case.find("error")
if problem is None:
continue

where = case.get("file") or case.get("classname") or ""
line = case.get("line")
if where and line:
where = f"{where}:{line}"

name = case.get("name", "unknown test")
location = f" — `{where}`" if where else ""

message = (problem.get("message") or "").strip().splitlines()
summary = f" — {message[0]}" if message else ""

bullets.append(f"- **{group}** `{name}`{location}{summary}")

return bullets


def main() -> int:
directory = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
files = sorted(directory.rglob("junit-*.xml"))

if not files:
return 0

rows = []
failures: list[str] = []

for path in files:
group = path.stem.removeprefix("junit-")

try:
root = ElementTree.parse(path).getroot()
except ElementTree.ParseError:
# A half written file means the job died mid-run. Say so rather
# than losing the whole comment to an exception.
rows.append(f"| {group} | results unreadable |")
continue

total, failed, skipped = counts_for(root)
passed = total - failed - skipped

parts = [f"{passed}/{total} passed"]
if failed:
parts.append(f"{failed} failed")
if skipped:
parts.append(f"{skipped} skipped")

rows.append(f"| {group} | {', '.join(parts)} |")
failures.extend(failures_in(root, group))

print("| Group | Result |")
print("| --- | --- |")
for row in rows:
print(row)

if failures:
print()
print("**Failures**")
print()
for bullet in failures[:MAX_FAILURES_LISTED]:
print(bullet)

remaining = len(failures) - MAX_FAILURES_LISTED
if remaining > 0:
print(f"- ...and {remaining} more, see the full log")

return 0


if __name__ == "__main__":
sys.exit(main())
12 changes: 6 additions & 6 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ jobs:

steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true

Expand All @@ -50,10 +50,10 @@ jobs:

steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true

Expand All @@ -70,7 +70,7 @@ jobs:

steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Look for a web/ folder
id: detect
Expand All @@ -84,7 +84,7 @@ jobs:

- name: Set up Node.js
if: steps.detect.outputs.present == 'true'
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version: 22

Expand Down
45 changes: 29 additions & 16 deletions .github/workflows/pr-command.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: PR command
# /test python <pytest args> the quick tests
# /test slow <pytest args> the tests marked `slow`
# /test gpu <pytest args> the tests marked `gpu`, on the project's GPU machine
# /test web the 3D visualizer
# /test web the visualizer
# /test help explains the commands
#
# Changes to this file do not show on the PR that adds them
Expand All @@ -20,13 +20,8 @@ permissions:
issues: write
pull-requests: write

# There is deliberately no concurrency setting for the whole workflow. One here
# would put every comment on a pull request into a single queue, so `/test web`
# would sit waiting for `/test python` to finish. The cancelling is done per
# test group on the run-tests job below instead.

jobs:
# Decide whether to do anything at all, and if so, what.
# Decide whether to do anything at all
prepare:
name: prepare
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/test') }}
Expand All @@ -42,7 +37,7 @@ jobs:

steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Check the commenter is allowed to run commands
env:
Expand Down Expand Up @@ -71,7 +66,7 @@ jobs:
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: .github/scripts/find-commit.sh

# `/test help`: reply with the list of commands and run nothing.
# `/test help`: reply with the list of commands
help:
name: help
needs: prepare
Expand All @@ -80,16 +75,14 @@ jobs:

steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Post the list of commands
env:
GH_TOKEN: ${{ github.token }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: .github/scripts/post-help.sh

# pne copy of this job per group that was asked for, so
# `/test all` runs four of them at once.
run-tests:
name: ${{ matrix.group }}
needs: prepare
Expand All @@ -109,13 +102,13 @@ jobs:
steps:
# check out latest commit at time of comment
- name: Check out the pull request
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.sha }}

- name: Install uv
if: matrix.group != 'web'
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true

Expand All @@ -128,11 +121,12 @@ jobs:
env:
GROUP: ${{ matrix.group }}
PYTEST_ARGS: ${{ needs.prepare.outputs.pytest_args }}
JUNIT_XML: results/junit-${{ matrix.group }}.xml
run: scripts/test-check.sh "$GROUP" $PYTEST_ARGS

- name: Set up Node.js
if: matrix.group == 'web'
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version: 22

Expand All @@ -142,8 +136,19 @@ jobs:

- name: Run the web checks
if: matrix.group == 'web'
env:
JUNIT_XML: ${{ github.workspace }}/results/junit-web.xml
run: scripts/web-check.sh

- name: Keep the results for the report job
if: always()
uses: actions/upload-artifact@v7
with:
name: results-${{ matrix.group }}
path: results/
if-no-files-found: ignore
retention-days: 1

# return one comment with the results of all tests
report:
name: report
Expand All @@ -153,7 +158,14 @@ jobs:

steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Collect the results from every group
uses: actions/download-artifact@v8
with:
pattern: results-*
path: results
merge-multiple: true

- name: Post the result as a comment
env:
Expand All @@ -164,4 +176,5 @@ jobs:
SHA: ${{ needs.prepare.outputs.sha }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RESULTS_DIR: results
run: .github/scripts/post-result.sh
22 changes: 19 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ To run more specific tests, refer to the following commands.
| `scripts/test-check.sh slow` | The tests marked `slow`. |
| `scripts/test-check.sh gpu` | The tests marked `gpu`. |
| `scripts/test-check.sh all` | Every test. |
| `scripts/web-check.sh` | The visualizer, once `web/` exists. |
| `scripts/web-check.sh` | The visualizer in `web/`: lint, tests, and build. |


Testing in this project is mostly done with pytest (web test framework is undecided). We use pytest markers to categorize tests into three groups: `quick`, `slow`, and `gpu`.
Testing on the Python side is done with pytest, and on the web side with vitest. We use pytest markers to categorize tests into three groups: `quick`, `slow`, and `gpu`.
`slow` and `gpu` tests are explicitly marked using `@pytest.mark.<slow|gpu>`, a test is categorized as `quick` otherwise.
You can use `test-check.sh` to run tests locally. Refer to the table above on how to invoke specific groups.

Expand All @@ -49,9 +49,25 @@ uv run pytest
uv run pytest -k sampler
```

## The visualizer in `web/`

The visualizer is a [Vite](https://vite.dev/) project using React and TypeScript, tested with [vitest](https://vitest.dev/).

Packages are managed by pnpm and already installed by `scripts/install-dependencies.sh`. To start the dev server:

```bash
cd web && pnpm dev
```

Tests go in `web/tests/`.

**Future work:** Add three.js and make the visualizer 3d and interactive.

## Adding a package

To add a package, put it in `pyproject.toml`, run `uv sync`, and commit the change to `uv.lock`. Skipping the lockfile means the package works for you and breaks for everyone else.
For Python, put it in `pyproject.toml`, run `uv sync`, and commit the change to `uv.lock`.

For the frontend, run `pnpm add <package>` inside `web/` and commit the change to `web/pnpm-lock.yaml`.

## Opening a pull request

Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# terrain-diffusion

A reproduction of the InfiniteDiffusion paper: generating realistic landscape
terrain that continues forever in every direction, comes out the same every
time from the same seed, and uses a fixed amount of memory no matter how far
you explore.
A reproduction of the InfiniteDiffusion paper: generating realistic landscape terrain that continues forever in every direction, comes out the same every time from the same seed, and uses a fixed amount of memory no matter how far you explore.


## Getting set up
Expand All @@ -25,3 +22,8 @@ scripts/install-dependencies.sh
## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for how to run the checks, how tests are organised, and how changes get reviewed and merged.

## TODO

- Add end to end tests with [Playwright](https://playwright.dev/) once the visualizer draws something. The current tests run in jsdom, which has no canvas and no WebGL, thus cannot check what appears on screen
- Playwright simulates a real browser and can compare screenshots
9 changes: 8 additions & 1 deletion scripts/test-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,17 @@ if [[ -n "$markers" ]]; then
marker_args=(-m "$markers")
fi

# report failing tests in formatted table
report_args=()
if [[ -n "${JUNIT_XML:-}" ]]; then
mkdir -p "$(dirname "$JUNIT_XML")"
report_args=(--junitxml="$JUNIT_XML" -o junit_family=xunit1)
fi

echo "==> Running the '$group' tests (pytest)"

status=0
uv run pytest "${marker_args[@]}" "$@" || status=$?
uv run pytest "${marker_args[@]}" "${report_args[@]+"${report_args[@]}"}" "$@" || status=$?

if [[ $status -eq 5 ]]; then
if [[ "$group" == "python" ]]; then
Expand Down
Loading