Skip to content
Closed
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
6 changes: 0 additions & 6 deletions .busted
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,4 @@ return {
ROOT = { "../spec" },
["exclude-tags"] = "builds",
},
generate = {
directory = "src",
lpath = "../runtime/lua/?.lua;../runtime/lua/?/init.lua",
helper = "HeadlessWrapper.lua",
ROOT = { "../spec/GenerateBuilds.lua" },
}
}
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.sh text eol=lf

###############################################################################
# Set default behavior for command prompt diff.
Expand Down
119 changes: 39 additions & 80 deletions .github/workflows/buildtest.yml
Original file line number Diff line number Diff line change
@@ -1,93 +1,52 @@
---
name: Run Tests
name: Compare saved builds
on:
pull_request:
branches:
- dev
branches: [dev, tests-branch]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: build-corpus-${{ github.ref }}
cancel-in-progress: true
jobs:
run_build_diff:
fixture_smoke:
name: Fixed fixture comparison
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout HEAD
uses: actions/checkout@v4
- name: Fetch Dev branch
id: get-dev-ref
run: |
git fetch --depth=1 origin dev
echo "devref=$(git rev-parse origin/dev)" >> $GITHUB_OUTPUT
- name: Download Dev branch cache
id: download-dev-ref-cache
uses: dawidd6/action-download-artifact@3ecf4024886f219d9290351234889bfb45d1b9da
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
name: cache-devref-${{ steps.get-dev-ref.outputs.devref }}
path: /tmp/cache/
if_no_artifact_found: warn
search_artifacts: true
# Dev ref cache contains the build list and build xmls. Use that one to keep tests reproducible
- name: Update static builds list from cache
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'true' }}
run: cat /tmp/cache/builds.txt > spec/builds.txt
- name: Download latest build list
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' }}
id: download-build-list
uses: dawidd6/action-download-artifact@3ecf4024886f219d9290351234889bfb45d1b9da
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
name: builds.txt
path: /tmp/latestbuildlist/
workflow: updatebuildlist.yml
if_no_artifact_found: warn
search_artifacts: true
- name: Update static builds list
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' && steps.download-build-list.outputs.found_artifact == 'true' }}
run: cat /tmp/latestbuildlist/builds.txt > spec/builds.txt
- name: Download latest build xmls
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' }}
uses: dawidd6/action-download-artifact@3ecf4024886f219d9290351234889bfb45d1b9da
python-version: '3.12'
- name: Compare every fixed fixture on base and candidate
env:
BASE_REF: ${{ github.event.pull_request.base.sha || github.sha }}
HEAD_REF: ${{ github.event.pull_request.head.sha || github.sha }}
run: python spec/RunBuildDiff.py --base "$BASE_REF" --head "$HEAD_REF" --fixtures-only --output "$RUNNER_TEMP/fixture-diff"
corpus_comparison:
name: Complete rotating corpus comparison
if: vars.TEST_BUILD_CORPUS_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
name: build-xmls
path: /tmp/cache/
if_no_artifact_found: warn
search_artifacts: true
- name: Calculate build xmls and differences between them
run: |
mkdir /tmp/cache || true # Make sure /tmp/cache exists. Ignore exit code
chmod -R 777 /tmp/cache && docker compose run -v '/tmp/cache/:/cache' -e 'CACHEDIR=/cache' busted-diff | tee /tmp/dockerlog
- name: Generate artefact
run: |
sed -n '/Runtime comparison for/,/Savefile Diff for/{/Savefile Diff for/!p;}' /tmp/dockerlog > /tmp/artefact
sed -n '/Savefile Diff for/, $p' /tmp/dockerlog >> /tmp/artefact
[ -s /tmp/artefact ] || rm /tmp/artefact
- name: Upload artefact
uses: actions/upload-artifact@v4
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
name: build-diff-output
path: /tmp/artefact
- name: Save used build list into cache
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' }}
run: cp spec/builds.txt /tmp/cache/
- name: Move xmls found in builds.txt to a new directory
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' && steps.download-build-list.outputs.found_artifact == 'true' }}
python-version: '3.12'
- name: Pin saved corpus once
run: |
mkdir new-build-xmls
while IFS= read -r line; do
FILENAME="/tmp/cache/${line//[^a-zA-Z0-9]/}.xml"
if [ -f "$FILENAME" ]; then
mv "$FILENAME" "./new-build-xmls/"
fi
done < "spec/builds.txt"
- name: Upload new build xmls
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' && steps.download-build-list.outputs.found_artifact == 'true' }}
uses: actions/upload-artifact@v4
with:
name: build-xmls
path: './new-build-xmls/*'
- name: Upload dev ref cache
if: ${{ steps.download-dev-ref-cache.outputs.found_artifact == 'false' }}
uses: actions/upload-artifact@v4
with:
name: cache-devref-${{ steps.get-dev-ref.outputs.devref }}
path: /tmp/cache/
git fetch origin refs/heads/build-test-corpus
CORPUS_SHA=$(git rev-parse FETCH_HEAD)
echo "Pinned corpus commit: $CORPUS_SHA"
git worktree add --detach "$RUNNER_TEMP/corpus" "$CORPUS_SHA"
- name: Calculate all saved inputs without provider requests
env:
BASE_REF: ${{ github.event.pull_request.base.sha || github.sha }}
HEAD_REF: ${{ github.event.pull_request.head.sha || github.sha }}
run: python spec/RunBuildDiff.py --base "$BASE_REF" --head "$HEAD_REF" --corpus "$RUNNER_TEMP/corpus" --output "$RUNNER_TEMP/corpus-diff"
16 changes: 15 additions & 1 deletion .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@ on:
push:
branches:
- dev
- tests-branch
pull_request:
branches:
- dev
- tests-branch
permissions:
contents: read
jobs:
run_unit_tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.12'
- name: Run corpus Python tests
run: |
python -m unittest discover -s tests -p 'test_update_build_corpus.py'
python -m unittest discover -s tests -p 'test_build_diff_contract.py'
- name: Run busted tests
run: docker compose run --no-TTY busted-tests
70 changes: 40 additions & 30 deletions .github/workflows/updatebuildlist.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
---
name: Update build list for tests
name: Refresh monthly build corpus
on:
schedule:
- cron: '20 4 * * *'
workflow_dispatch:
permissions:
contents: write
concurrency:
group: monthly-build-corpus-writer
cancel-in-progress: false
jobs:
update-builds-list:
runs-on: ubuntu-22.04
refresh:
if: vars.TEST_BUILD_CORPUS_ENABLED == 'true' && github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout HEAD
uses: actions/checkout@v4
- name: Install moreutils
run: sudo apt-get install -y moreutils
- name: Download latest build list
uses: dawidd6/action-download-artifact@3ecf4024886f219d9290351234889bfb45d1b9da
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
name: builds.txt
if_no_artifact_found: warn
path: /tmp/latestbuildlist/
- name: Update list
run: >
cat spec/builds.txt /tmp/latestbuildlist/builds.txt
<({ curl "https://pobarchives.com/api/builds?q=latest" & curl "https://pobarchives.com/api/builds?q=trending"; }
| jq -r '.builds[].build_info.build_link')
| tail -n 500
| sort -u
| sponge builds.txt
- name: Print new builds list
run: cat builds.txt
- name: Save new build list
uses: actions/upload-artifact@v4
with:
name: builds.txt
path: builds.txt
overwrite: true
retention-days: 3
python-version: '3.12'
- name: Load saved corpus or bootstrap an empty branch
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
REMOTE_REF=$(git ls-remote origin refs/heads/build-test-corpus)
if [ -n "$REMOTE_REF" ]; then
git fetch origin refs/heads/build-test-corpus
git worktree add --detach "$RUNNER_TEMP/corpus" FETCH_HEAD
else
git worktree add --detach "$RUNNER_TEMP/corpus" HEAD
git -C "$RUNNER_TEMP/corpus" switch --orphan build-test-corpus
fi
- name: Validate and construct next corpus
run: python spec/UpdateBuildCorpus.py --url https://api.pob.codes/test-builds --prior "$RUNNER_TEMP/corpus" --output "$RUNNER_TEMP/next-corpus"
- name: Commit manifest and retained bytes together
run: |
git -C "$RUNNER_TEMP/corpus" rm -r --ignore-unmatch codes manifest.json
cp -a "$RUNNER_TEMP/next-corpus/." "$RUNNER_TEMP/corpus/"
git -C "$RUNNER_TEMP/corpus" add manifest.json codes
if git -C "$RUNNER_TEMP/corpus" diff --cached --quiet; then
echo "Monthly batch already applied; no corpus change."
exit 0
fi
git -C "$RUNNER_TEMP/corpus" commit -m "Refresh monthly test-build corpus"
# Plain fast-forward push rejects a competing writer. The next daily run
# refetches/reapplies; never force-push or publish a partial manifest.
git -C "$RUNNER_TEMP/corpus" push origin HEAD:refs/heads/build-test-corpus
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ src/Export/ggpk/*.dll
src/Data/TimelessJewelData/*.bin

# Simplegraphic Debugging
runtime/imgui.ini
runtime/imgui.ini
__pycache__/
*.pyc
100 changes: 100 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,105 @@
# Contributing to Path of Building

## Monthly build-corpus CI

This branch separates monthly input collection from offline PoB comparisons.
PoB Codes serves up to 100 codes at `https://api.pob.codes/test-builds`. This
repository owns a FIFO of at most 500 unique encoded inputs on the dedicated
`build-test-corpus` branch. Fixed XML fixtures in `spec/TestBuilds` run alongside
the rotating corpus. There is no migration of `spec/builds.txt`, external
provider scraping, calculated-output cache, or new report format.

### Run locally

Install Python 3.12+ and Docker. Resolve base/head Git commits locally, then:

```sh
python -m unittest discover -s tests -v
python spec/RunBuildDiff.py --base <base-sha> --head <head-sha> --fixtures-only --output /tmp/pob-fixture-result
git fetch origin refs/heads/build-test-corpus
git worktree add --detach /tmp/pob-corpus FETCH_HEAD
python spec/RunBuildDiff.py --base <base-sha> --head <head-sha> --corpus /tmp/pob-corpus --output /tmp/pob-corpus-result
```

Each output directory must be new. `--strict` makes numerical differences fail;
without it, differences remain ordinary advisory `DiffOutput.lua` output.
Crashes, timeouts, invalid manifests, absent inputs, and missing calculated
stats always fail. `--extra-fixtures <directory>` adds local XML fixtures.
`--image <already-built-image>` supports offline reproduction; the runner pins
the inspected image ID for both sides. Otherwise it builds
`Dockerfile.test-builds`, with a digest-pinned base and version-pinned UTF-8 rock.
The old `busted-diff` Compose service is replaced by this host-side runner.

The runner archives each revision's `src` and `runtime`, supplies the same
headless compatibility helper and identical inputs, and uses two concurrent
containers at most. Each container has two CPUs, 2 GiB memory, no network, and
read-only runtime/input mounts. Batches contain at most 25 builds, with a
30-second per-input alarm and 300-second container deadline. Saved files must
match the complete expected input set and contain player stats; active minion
builds must also save minion stats. No live API access occurs during comparison.

### Input contract and FIFO

`spec/UpdateBuildCorpus.py` accepts schema version 1, opaque `batchId`, UTC
`period`, canonical millisecond UTC `generatedAt`, `patchVersion`,
`requestedCount: 100`, `count: 1..100`, and `builds: [{code, sha256}]`.
The SHA-256 covers the exact UTF-8 code string. The shared synthetic fixture is
`tests/fixtures/test-build-batch-v1.json`, mirrored by PoB Codes' shared-types
package. Limits are 150 KiB per code, 4 MiB inflated XML, and 16 MiB per batch.
The importer validates every code and rejects DTD/entities and malformed input
before constructing the next corpus. A short valid batch is accepted.

New hashes append in batch order; the oldest are evicted beyond 500. Repeated
batches are no-ops, a reused batch ID with different content fails, and an
older/same-period replacement is ignored. All-duplicate new monthly batches
still record their identity. `manifest.json` records ordered hashes, batch
identities, ETag, and the corpus digest; `codes/<sha256>.txt` retains the exact
encoded bytes. A Git commit publishes the manifest and codes atomically.

The daily updater polls conditionally, retries transport/429/5xx at most three
times with bounded waits, and respects Retry-After. Invalid responses preserve
the previous commit. A competing writer makes a normal fast-forward push fail;
the next daily run refetches and reapplies. Never force-push the corpus branch.
Manual reproduction, without publishing:

```sh
python spec/UpdateBuildCorpus.py --url https://api.pob.codes/test-builds --prior /tmp/pob-corpus --output /tmp/pob-next
```

### Activation and ownership

The workflow files can first be reviewed against `Paliak/PathOfBuilding`'s
`tests-branch`. GitHub schedules only execute registered default-branch
workflows: a merge solely to `tests-branch` is not scheduled activation. The
CI maintainer must carry the updater and helpers onto the default branch, allow
its scoped `contents: write` token to push `build-test-corpus`, and set repository
variable `TEST_BUILD_CORPUS_ENABLED=true`. The updater only runs from that default
branch and uses a serialized, non-cancelling writer group. No API secret is
needed. Never give PR comparison jobs write credentials.

Enable the PoB Codes provider first, then manually dispatch the updater from
the default branch and verify the first corpus commit. Until the variable is
enabled, only fixed-fixture smoke comparisons run. Enabling it before bootstrap
causes corpus comparison to fail explicitly rather than silently skip inputs.
PR jobs pin explicit base/head SHAs and a fetched corpus commit. If the provider
later becomes unavailable, existing corpus comparisons continue offline.

The original tests-branch runtime is from 2024 and cannot calculate the tested
modern 3.28 export. Integrating a supported modern PoB revision is a prerequisite
for enabling monthly corpus comparisons; this PR does not merge the entire dev
branch. Same-revision checks have passed with five fixed fixtures, one current
public build, and one synthetic minion build using the modern dev runtime.
Benchmark 100 distinct inputs and then the full 500-input corpus on the target
GitHub runner before making the rotating job required. Repeated copies of one
build are not representative capacity evidence. The CI maintainer owns runtime
support and corpus health; the provider maintainer owns patch selection and
monthly publication.

For local unit tests, `docker compose run --rm --no-TTY busted-tests` retains
the existing Busted suite. The new Python tests run in `unittest.yml` alongside
it. Standard public GitHub-hosted runners are the intended execution target;
confirm repository billing policy before enabling on a private fork.

# Table of contents
1. [Reporting bugs](#reporting-bugs)
2. [Requesting features](#requesting-features)
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile.test-builds
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM ghcr.io/paliak/busted-tests@sha256:0ce3f27d276dd6918d78ae11339e4135c445ea0e4fd31dbc88087ea25232ed90
RUN luarocks install luautf8 0.1.6-1
17 changes: 0 additions & 17 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,3 @@ services:
working_dir: /workdir
volumes:
- ./:/workdir:ro
busted-diff:
#build: .
image: ghcr.io/paliak/busted-tests:latest
environment: #Where in the container the folders are stored
WORKDIR: /workdir
HOME: /tmp
DEVREF: ${DEVREF:-origin/dev}
HEADREF: $HEADREF
container_name: busted-diff
tty: true
user: nobody:nobody
command: /bin/sh -c "dos2unix < /workdir/spec/BuildDiff.sh | /bin/sh"
security_opt:
- no-new-privileges:true
working_dir: /workdir
volumes:
- ./:/workdir:ro
Loading