Skip to content
Closed

Test #31

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2585e10
Drop support for versions before 26.1
Moggla Sep 20, 2026
411a90b
Add help dialog
Moggla Sep 20, 2026
fd0cca4
Add settings dialog
Moggla Sep 20, 2026
644699b
Remove text based settings menu
Moggla Sep 20, 2026
4107a31
Remove deprecated overlay formats key
Moggla Sep 20, 2026
816d428
Add toggle button and settings tooltip to help dialog
Moggla Sep 20, 2026
9d60c24
Rebuild the settings as a trigger based button menu
Moggla Sep 20, 2026
9de4a18
Update README screenshots and world options steps
Moggla Sep 20, 2026
490035f
Mention F3+F6 looking_at_block_state in player-placed trees tooltip
Moggla Sep 20, 2026
1cf5882
Add PackTest CI workflows and first test
Moggla Sep 20, 2026
b7c467b
Update README
Moggla Sep 21, 2026
be63cc7
Build tests on real worldgen trees with shared helpers
Moggla Sep 21, 2026
e4f275b
Isolate tests with an arena template and per-test dummies
Moggla Sep 21, 2026
bf3292c
Wait for the datapack to finish instead of a fixed delay
Moggla Sep 21, 2026
cdcba58
Add chop tests for 15 vanilla tree types
Moggla Sep 21, 2026
f4f528e
Test mangroves with a finder for the lowest log and warnings for know…
Moggla Sep 21, 2026
78f95d5
Add tests for nether fungi and huge mushrooms
Moggla Sep 21, 2026
0a85c4a
Add negative tests: structures the datapack must not chop
Moggla Sep 21, 2026
c33531a
Restore the persistent check for leaves
Moggla Sep 21, 2026
6f45ebb
Make the "not chopped" checks able to fail
Moggla Sep 21, 2026
29f8b83
Add settings tests, one test environment per setting
Moggla Sep 21, 2026
60f4196
Run the fungus tests in their own environment, test chop_fungi off
Moggla Sep 21, 2026
e958176
Test slow chop, its interval and its blocks per chop
Moggla Sep 21, 2026
00e7fd1
Test destroy_roots and start_chop_on_root on a hand built mangrove
Moggla Sep 21, 2026
ad7d05c
Test plant_sapling, drop_loot and stop_chopping, spread the tests ove…
Moggla Sep 21, 2026
af5d280
Test the settings menu: flips, guards, admins, sliders, player toggle
Moggla Sep 21, 2026
9fd0354
Test that uninstall removes every scoreboard and trigger of the datapack
Moggla Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Binary file modified .github/resources/help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .github/resources/settings1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .github/resources/settings2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions .github/scripts/gen-uninstall-check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Generates the checks of the uninstall test from the code of the datapack, so nobody has to keep a list.

Usage: gen-uninstall-check.py <repo root> <folder of the test datapack in the world, the one that contains data/>

Writes <folder>/data/timber_test/function/generated/uninstall_objectives.mcfunction:
- one check per scoreboard objective (triggers are objectives) that any function of the datapack creates with
"scoreboard objectives add": after timber:uninstall the objective must not exist any more. A new objective that is added
to init and forgotten in uninstall makes the test fail, with its name.
- a TIMBER-WARNING line for everything that can't be looked at from a function, but that uninstall should clean up too:
scheduled functions (schedule function) and storages, compared with the schedule clear / data remove storage of uninstall.
"""
import os
import re
import sys

repo, dest = sys.argv[1], sys.argv[2]

# 121/ holds the overlay of the newest pack format, both are scanned
folders = [os.path.join(repo, "data", "timber", "function"), os.path.join(repo, "121", "data", "timber", "function")]
uninstall_text = ""
objectives, schedules, storages = set(), set(), set()
for folder in folders:
for root, _, files in os.walk(folder):
for name in files:
if not name.endswith(".mcfunction"):
continue
path = os.path.join(root, name)
text = open(path, encoding="utf-8", errors="replace").read()
if os.path.relpath(path, folder).replace(os.sep, "/") == "uninstall.mcfunction":
uninstall_text += text + "\n"
continue
objectives.update(re.findall(r"scoreboard objectives add (\S+)", text))
schedules.update(re.findall(r"schedule function (timber:[\w/]+)", text))
storages.update(re.findall(r"storage (timber:\w+)", text))

if not objectives:
sys.exit("gen-uninstall-check: no 'scoreboard objectives add' found in " + str(folders) + ", is the path right?")

lines = [
"#> GENERATED by .github/scripts/gen-uninstall-check.py from the code of the datapack, do not edit.",
"#> Checks that no scoreboard or trigger of the datapack exists after timber:uninstall.",
"scoreboard objectives add timber_test dummy",
"scoreboard players set #left timber_test 0",
"data modify storage timber_test:uninstall left set value []",
]
for name in sorted(objectives):
# adding 0 to a holder works when the objective exists and fails when it doesn't.
# The names go into a list, a fail in the loop would end this function at the first one.
lines += [
f"execute store success score #exists timber_test run scoreboard players add #uninstall_probe {name} 0",
f'execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall left append value "{name}"',
f"execute if score #exists timber_test matches 1 run say TIMBER-ERROR uninstall: the scoreboard {name} still exists",
f"execute if score #exists timber_test matches 1 run scoreboard players add #left timber_test 1",
f"execute if score #exists timber_test matches 1 run scoreboard players reset #uninstall_probe {name}",
]

cleared_schedules = set(re.findall(r"schedule clear (timber:[\w/]+)", uninstall_text))
for target in sorted(schedules - cleared_schedules):
lines.append(f"say TIMBER-WARNING uninstall: {target} is scheduled somewhere but uninstall doesn't clear it (schedule clear)")
removed_storages = set(re.findall(r"data remove storage (timber:\w+)", uninstall_text))
for target in sorted(storages - removed_storages):
lines.append(f"say TIMBER-WARNING uninstall: storage {target} is used but uninstall doesn't remove it (data remove storage)")

lines.append('execute unless score #left timber_test matches 0 run fail [{"text":"still exist after uninstall: "},{"nbt":"left","storage":"timber_test:uninstall"}]')
# (no assert after the fail: it would replace the message with "Expected #left ...", without the names)

out = os.path.join(dest, "data", "timber_test", "function", "generated")
os.makedirs(out, exist_ok=True)
with open(os.path.join(out, "uninstall_objectives.mcfunction"), "w", newline="\n") as f:
f.write("\n".join(lines) + "\n")
print(f"gen-uninstall-check: {len(objectives)} objectives, {len(schedules - cleared_schedules)} schedules and "
f"{len(storages - removed_storages)} storages that uninstall leaves")
37 changes: 37 additions & 0 deletions .github/scripts/setup-test-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Sets up a Fabric server with Fabric API and PackTest for the given Minecraft version in the current directory.
# Exit code 3 means the tooling is not available for that version (yet), any other non-zero code is a real error.
set -euo pipefail

MC="$1"
UA="Moggla/Timber-Datapack CI"

# prints the download url of the newest release of a Modrinth project for $MC, or nothing if there is none
modrinth_url() {
curl -fsSL -A "$UA" -G "https://api.modrinth.com/v2/project/$1/version" \
--data-urlencode "game_versions=[\"$MC\"]" \
--data-urlencode 'loaders=["fabric"]' \
| jq -r '[.[] | select(.version_type == "release")][0].files[0].url // empty'
}

FABRIC_API=$(modrinth_url fabric-api)
PACKTEST=$(modrinth_url packtest)
LOADER=$(curl -fsSL https://meta.fabricmc.net/v2/versions/loader | jq -r '[.[] | select(.stable)][0].version')
INSTALLER=$(curl -fsSL https://meta.fabricmc.net/v2/versions/installer | jq -r '[.[] | select(.stable)][0].version')
# the meta api answers unknown versions with an error or an empty list
SERVER=$(curl -sSL "https://meta.fabricmc.net/v2/versions/loader/$MC" | jq -r 'if type == "array" and length > 0 then "yes" else empty end' 2>/dev/null || true)

MISSING=""
[ -n "$SERVER" ] || MISSING="$MISSING Fabric server,"
[ -n "$FABRIC_API" ] || MISSING="$MISSING Fabric API,"
[ -n "$PACKTEST" ] || MISSING="$MISSING PackTest,"
if [ -n "$MISSING" ]; then
echo "Not available for Minecraft $MC yet:${MISSING%,}" >&2
exit 3
fi

mkdir -p mods
curl -fsSL -o server.jar "https://meta.fabricmc.net/v2/versions/loader/$MC/$LOADER/$INSTALLER/server/jar"
curl -fsSL -o mods/fabric-api.jar "$FABRIC_API"
curl -fsSL -o mods/packtest.jar "$PACKTEST"
echo "eula=true" > eula.txt
109 changes: 109 additions & 0 deletions .github/workflows/latest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Latest Minecraft

# Checks whether the datapack works on a Minecraft release newer than the ones in the test matrix
# and opens an issue with the result, so that nobody has to look at the runs.
# Not a required check: Fabric, Fabric API and PackTest usually lag a few days behind a new release.
on:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
latest:
name: Latest Minecraft release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Find latest release
id: version
run: |
MANIFEST=$(curl -fsSL https://piston-meta.mojang.com/mc/game/version_manifest_v2.json)
MC=$(jq -r '.latest.release' <<< "$MANIFEST")
URL=$(jq -r --arg id "$MC" '.versions[] | select(.id == $id) | .url' <<< "$MANIFEST")
JAVA=$(curl -fsSL "$URL" | jq -r '.javaVersion.majorVersion')

# highest version that the test matrix already covers
SUPPORTED=$(sed -n 's/.*minecraft: "\([^"]*\)".*/\1/p' .github/workflows/test.yml | sort -V | tail -n 1)

echo "mc=$MC" >> "$GITHUB_OUTPUT"
echo "java=$JAVA" >> "$GITHUB_OUTPUT"
if [ "$MC" != "$SUPPORTED" ] && [ "$(printf '%s\n%s\n' "$SUPPORTED" "$MC" | sort -V | tail -n 1)" = "$MC" ]; then
echo "newer=true" >> "$GITHUB_OUTPUT"
echo "Latest Minecraft release $MC is newer than the supported $SUPPORTED (Java $JAVA)"
else
echo "newer=false" >> "$GITHUB_OUTPUT"
echo "Latest Minecraft release $MC is already covered by the test matrix (highest: $SUPPORTED)." | tee -a "$GITHUB_STEP_SUMMARY"
fi

- uses: actions/setup-java@v4
if: steps.version.outputs.newer == 'true'
with:
distribution: temurin
java-version: ${{ steps.version.outputs.java }}

- name: Set up Fabric server, Fabric API and PackTest
id: setup
if: steps.version.outputs.newer == 'true'
run: |
set +e
bash .github/scripts/setup-test-server.sh "${{ steps.version.outputs.mc }}"
CODE=$?
if [ $CODE -eq 3 ]; then
echo "ready=false" >> "$GITHUB_OUTPUT"
echo "Minecraft ${{ steps.version.outputs.mc }} can't be tested yet, the test tooling isn't available for it. Trying again next time." | tee -a "$GITHUB_STEP_SUMMARY"
exit 0
fi
[ $CODE -eq 0 ] || exit $CODE
echo "ready=true" >> "$GITHUB_OUTPUT"

- name: Install datapacks
if: steps.setup.outputs.ready == 'true'
run: |
mkdir -p world/datapacks/timber world/datapacks/timber_test
cp -r data pack.mcmeta 121 world/datapacks/timber/
cp -r tests/. world/datapacks/timber_test/

- name: Run tests
id: tests
if: steps.setup.outputs.ready == 'true'
continue-on-error: true
run: java -Xmx2G -Dpacktest.auto -Dpacktest.auto.annotations -jar server.jar nogui

- name: Open an issue with the result
if: steps.setup.outputs.ready == 'true'
env:
GH_TOKEN: ${{ github.token }}
MC: ${{ steps.version.outputs.mc }}
OUTCOME: ${{ steps.tests.outcome }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [ "$OUTCOME" = "success" ]; then
TITLE="Minecraft $MC works with the datapack"
BODY="The tests pass on Minecraft $MC ([run]($RUN_URL)).

To support it:
- bump \`max_format\` in \`pack.mcmeta\`
- add \`$MC\` to the matrix in \`.github/workflows/test.yml\`
- release the update"
else
TITLE="Minecraft $MC breaks the datapack"
BODY="The tests fail on Minecraft $MC ([run]($RUN_URL)). See the annotations of the run for the failed tests."
fi

# closed issues count too, so a handled version doesn't get reported again
EXISTING=$(gh issue list --state all --search "\"$TITLE\" in:title" --json title --jq "[.[] | select(.title == \"$TITLE\")] | length")
if [ "$EXISTING" -gt 0 ]; then
echo "Issue \"$TITLE\" already exists." | tee -a "$GITHUB_STEP_SUMMARY"
else
gh issue create --title "$TITLE" --body "$BODY" --label "CI/CD"
echo "Opened issue \"$TITLE\"." | tee -a "$GITHUB_STEP_SUMMARY"
fi

- name: Fail if the tests failed
if: steps.tests.outcome == 'failure'
run: exit 1
63 changes: 63 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Test

on:
pull_request:
push:
branches: [main]

jobs:
test:
name: Minecraft ${{ matrix.minecraft }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- minecraft: "26.1.2"
java: "25"
- minecraft: "26.3"
java: "25"
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}

- name: Set up Fabric server, Fabric API and PackTest
run: bash .github/scripts/setup-test-server.sh "${{ matrix.minecraft }}"

- name: Install datapacks
run: |
mkdir -p world/datapacks/timber world/datapacks/timber_test
cp -r data pack.mcmeta 121 world/datapacks/timber/
cp -r tests/. world/datapacks/timber_test/

# the uninstall test checks the scoreboards the code of the datapack creates, so a forgotten one can't slip through
- name: Generate the uninstall check
run: python3 .github/scripts/gen-uninstall-check.py . world/datapacks/timber_test

- name: Run tests
run: |
set -o pipefail
java -Xmx2G -Dpacktest.auto -Dpacktest.auto.annotations -jar server.jar nogui | tee server-output.log

# tests can log a line with TIMBER-WARNING and still pass, show those as warnings so they don't go unnoticed
- name: Report test warnings
if: always()
run: |
grep -h 'TIMBER-WARNING' server-output.log | sed 's/.*TIMBER-WARNING //' | while read -r line; do
echo "::warning title=Test warning (Minecraft ${{ matrix.minecraft }})::$line"
echo "- Minecraft ${{ matrix.minecraft }}: $line" >> "$GITHUB_STEP_SUMMARY"
done || true

# single check to require in the branch ruleset, so changing the matrix doesn't need a ruleset update
test-result:
name: Test result
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- name: Fail if any test job failed or was cancelled
run: test "${{ needs.test.result }}" = "success"
27 changes: 26 additions & 1 deletion 121/data/timber/predicate/block/leaf.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,35 @@
"type": "minecraft:location_check",
"predicate": {
"block": {
"blocks": "#minecraft:leaves"
"blocks": "#minecraft:leaves",
"state": {
"persistent": "false"
}
}
}
},
{
"type": "minecraft:all_of",
"terms": [
{
"type": "minecraft:location_check",
"predicate": {
"block": {
"blocks": "#minecraft:leaves"
}
}
},
{
"type": "minecraft:entity_scores",
"entity": "this",
"scores": {
"timber_prsistent": {
"min": 1
}
}
}
]
},
{
"type": "minecraft:location_check",
"predicate": {
Expand Down
52 changes: 0 additions & 52 deletions 18-26/data/minecraft/loot_tables/blocks/mushroom_stem.json

This file was deleted.

17 changes: 0 additions & 17 deletions 18-26/data/timber/functions/autoplant/plant.mcfunction

This file was deleted.

Loading
Loading