Compare commits

..

62 Commits

Author SHA1 Message Date
0e412c2a42 fixed level loading and starting to setup proper gyms, zoos and playtest levels
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Test (push) Successful in 8m23s
Create tag and build when new code gets to main / Export (push) Successful in 10m40s
2026-02-03 16:31:37 +01:00
8a3faff7d0 small refacto of dash actions 2026-02-03 15:10:22 +01:00
3525f0e3eb added a parry button and animation that lets player chose their enemy hit behaviour
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 5m57s
Create tag and build when new code gets to main / Export (push) Successful in 7m43s
2026-01-30 13:19:28 +01:00
cc973b9f0d fixed spawners
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 6m4s
Create tag and build when new code gets to main / Export (push) Successful in 8m2s
2026-01-28 19:14:05 +01:00
8a552f7993 #minor CI FIXED
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Test (push) Successful in 6m0s
Create tag and build when new code gets to main / Export (push) Successful in 7m58s
2026-01-28 18:28:40 +01:00
93841bc85d manually uploading to itch
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Test (push) Successful in 5m58s
Create tag and build when new code gets to main / Export (push) Successful in 7m52s
2026-01-28 18:15:15 +01:00
9ba8847626 changing itch action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Failing after 5m21s
Create tag and build when new code gets to main / Export (push) Failing after 7m11s
2026-01-28 17:56:59 +01:00
51b7328310 changing itch action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Failing after 4s
Create tag and build when new code gets to main / Test (push) Has been cancelled
2026-01-28 17:54:06 +01:00
fdc352596d were getting there...
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 37s
Create tag and build when new code gets to main / Test (push) Successful in 5m43s
Create tag and build when new code gets to main / Export (push) Failing after 8m23s
2026-01-28 17:39:23 +01:00
89ba5cc985 trying the the fix on export again and trying to cache lfs objects again
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 40s
Create tag and build when new code gets to main / Test (push) Successful in 5m45s
Create tag and build when new code gets to main / Export (push) Failing after 7m56s
2026-01-28 17:25:28 +01:00
fdc79166a0 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 4m39s
Create tag and build when new code gets to main / Export (push) Failing after 5m16s
2026-01-28 16:22:38 +01:00
b84487336b testing ci 2026-01-28 16:20:44 +01:00
e4ab103c4d testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 1m15s
Create tag and build when new code gets to main / Test (push) Successful in 4m43s
2026-01-28 15:33:56 +01:00
3b6cf0252b testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 13s
Create tag and build when new code gets to main / BumpTag (push) Successful in 30s
Create tag and build when new code gets to main / Export (push) Failing after 45s
2026-01-28 15:29:31 +01:00
e908cd3085 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 14s
Create tag and build when new code gets to main / BumpTag (push) Failing after 24s
Create tag and build when new code gets to main / Export (push) Failing after 51s
2026-01-28 15:19:04 +01:00
6b23fdbd26 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 12s
Create tag and build when new code gets to main / BumpTag (push) Failing after 22s
Create tag and build when new code gets to main / Export (push) Failing after 1m29s
2026-01-28 15:13:11 +01:00
ea6258ff19 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 12s
Create tag and build when new code gets to main / BumpTag (push) Failing after 21s
Create tag and build when new code gets to main / Export (push) Failing after 53s
2026-01-28 15:05:12 +01:00
5e54f0f83b testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 12s
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 55s
2026-01-28 15:00:24 +01:00
f00439a430 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 18s
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 54s
2026-01-28 13:08:24 +01:00
02ec230b3f testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 13s
Create tag and build when new code gets to main / BumpTag (push) Successful in 25s
Create tag and build when new code gets to main / Export (push) Failing after 52s
2026-01-28 12:48:28 +01:00
867554b835 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 18s
Create tag and build when new code gets to main / BumpTag (push) Successful in 30s
Create tag and build when new code gets to main / Export (push) Failing after 47s
2026-01-28 12:45:13 +01:00
a4835eeb3c testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 7s
Create tag and build when new code gets to main / Test (push) Failing after 6m21s
2026-01-28 12:34:26 +01:00
55b877226e testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m31s
2026-01-28 10:49:08 +01:00
d7d33d0dac testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m28s
2026-01-28 10:47:15 +01:00
f92c6d282f testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m33s
2026-01-28 10:43:32 +01:00
2d41523668 testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m24s
2026-01-28 10:38:56 +01:00
34b04a365a testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-28 10:38:21 +01:00
d0ac644e14 testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 9m17s
2026-01-28 10:28:07 +01:00
510a3200d1 testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 9m32s
2026-01-28 10:17:05 +01:00
bef601941c testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m24s
2026-01-28 10:14:25 +01:00
b6605d6293 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 1m11s
Create tag and build when new code gets to main / Export (push) Failing after 1m29s
2026-01-28 10:10:51 +01:00
1b6742ea45 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 1m27s
2026-01-28 10:06:32 +01:00
405e487881 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 37s
Create tag and build when new code gets to main / Export (push) Failing after 2m18s
Create tag and build when new code gets to main / Test (push) Successful in 6m42s
2026-01-28 09:53:22 +01:00
af1f6da98d updating project files
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 31s
Create tag and build when new code gets to main / Test (push) Has been cancelled
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-28 09:50:10 +01:00
119850a7b4 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 3m29s
Create tag and build when new code gets to main / Test (push) Failing after 9m11s
2026-01-28 09:33:21 +01:00
b198aba09b updating state chart addon
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Export (push) Failing after 1m28s
2026-01-27 19:13:53 +01:00
37165d1562 fuck tests I guess
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 1m57s
2026-01-27 19:04:52 +01:00
5684561b66 fuck tests I guess 2026-01-27 19:04:34 +01:00
2678cac0e6 FUUUUCK FUUUUUUUUUUCK
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Export (push) Failing after 2m1s
2026-01-27 19:01:05 +01:00
230b409abe FUUUUCK FUUUUUUUUUUCK
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Failing after 15s
2026-01-27 18:56:12 +01:00
a6c80206c9 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 40s
2026-01-27 18:53:03 +01:00
1a73f23670 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 15s
2026-01-27 18:52:04 +01:00
38e62dcbb3 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 35s
Create tag and build when new code gets to main / Export (push) Failing after 1m8s
2026-01-27 18:48:37 +01:00
19bdc143c1 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 2m3s
Create tag and build when new code gets to main / Export (push) Failing after 1m37s
2026-01-27 18:44:03 +01:00
1709554e72 changing GDUnit dependency because 4.6 not yet supported
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 3m44s
2026-01-27 18:07:54 +01:00
5c2e9408c5 caching improvements
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Test (push) Failing after 2m6s
Create tag and build when new code gets to main / Export (push) Has been skipped
2026-01-27 17:59:10 +01:00
caeae26a09 fixed camera and sword animation issue and upgraded to Godot 4.6
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Failing after 2m10s
Create tag and build when new code gets to main / Export (push) Has been skipped
2026-01-27 17:47:19 +01:00
056a68b0ad small death animation and toolbox
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 35s
Create tag and build when new code gets to main / Test (push) Successful in 6m20s
Create tag and build when new code gets to main / Export (push) Successful in 7m20s
2026-01-27 16:42:31 +01:00
f1f0febf29 death and restart menu working 2026-01-27 15:11:43 +01:00
916a6e7153 some editor icons and a base level scene
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 33s
Create tag and build when new code gets to main / Test (push) Successful in 6m19s
Create tag and build when new code gets to main / Export (push) Successful in 7m39s
2026-01-27 10:35:35 +01:00
ddf1bd019b better healthbars and one for the player as well
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 42s
Create tag and build when new code gets to main / Test (push) Successful in 6m13s
Create tag and build when new code gets to main / Export (push) Successful in 7m5s
2026-01-26 18:09:29 +01:00
2fdc9c7ca8 instanciating explosion on slam
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Has been cancelled
Create tag and build when new code gets to main / Test (push) Has been cancelled
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-26 16:34:18 +01:00
d79ca44972 everything should work fine now
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Test (push) Successful in 8m7s
Create tag and build when new code gets to main / Export (push) Successful in 7m13s
2026-01-26 09:26:54 +01:00
148aea9bb4 trying to remove ad unit addon from build job because it breaks
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 6m59s
2026-01-26 09:18:28 +01:00
bdce8b969c reinstalling GDUnit from assetlib
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m41s
2026-01-26 09:05:55 +01:00
4095f818f6 gdunit addon post import by godot
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 7m17s
2026-01-26 08:59:34 +01:00
72bf3d4cc5 making sure the issue comes from GDUnit addon folder
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 7m6s
2026-01-26 08:51:14 +01:00
51907a1f01 trying to fix Export
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 6m53s
2026-01-26 08:41:48 +01:00
c5c4ceb032 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m47s
2026-01-26 08:30:31 +01:00
64957334de trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 35s
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-26 08:29:43 +01:00
7323b6e814 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 8m32s
2026-01-26 08:12:23 +01:00
24f9801093 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Export (push) Failing after 7m16s
2026-01-26 07:58:33 +01:00
2394 changed files with 46411 additions and 8249 deletions

View File

@@ -10,50 +10,31 @@ on:
- "**"
env:
GODOT_VERSION: 4.6
GAME_NAME: MovementTests
ITCHIO_USERNAME: Minimata
ITCHIO_GAMEID: MovementTests
jobs:
Export:
runs-on: ubuntu-latest
runs-on: godot
steps:
- name: Install node, xvfb and curl
run: |
apt update && apt -y install curl nodejs xvfb
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Checkout
uses: actions/checkout@v6
with:
lfs: false
persist-credentials: true
- name: Checkout LFS
run: |
git lfs install --local
AUTH=$(git config http.${{ gitea.server_url }}/.extraheader)
AUTH_FILE=$(git config includeif.gitdir:/workspace/${{ gitea.repository }}/.git.path)
git config -f $AUTH_FILE --unset http.${{ gitea.server_url }}/.extraheader
git config -f $AUTH_FILE http.${{ gitea.server_url }}/${{ gitea.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
git lfs pull
- name: Launch Godot
- name: Run tests
uses: godot-gdunit-labs/gdUnit4-action@v1
with:
godot-version: '4.5.1'
godot-version: ${GODOT_VERSION}
godot-net: true
godot-force-mono: true
dotnet-version: 'net9.0'
version: 'v6.0.3'
paths: |
res://tests/
timeout: 1
publish-report: false
upload-report: false
- name: Test action
run: echo ${{ github.workspace }}
- name: Upload test report
uses: actions/upload-artifact@v3-node20
with:

View File

@@ -8,6 +8,7 @@ on:
- "**"
env:
GODOT_VERSION: 4.6
GAME_NAME: MovementTests
ITCHIO_USERNAME: Minimata
ITCHIO_GAMEID: MovementTests
@@ -21,6 +22,7 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4
with:
token: ${{ secrets.TOKEN }}
lfs: false
- name: Remove buggy pre-push hook
run: |
@@ -36,91 +38,79 @@ jobs:
INITIAL_VERSION: 0.1.0
DEFAULT_BUMP: patch
# Test:
# runs-on: ubuntu-latest
# steps:
# - name: Install node, xvfb and curl
# run: |
# apt update && apt -y install curl nodejs xvfb
#
# - name: Checkout
# uses: actions/checkout@v6
# with:
# lfs: false
# persist-credentials: true
#
# - name: Checkout LFS
# run: |
# git lfs install --local
# AUTH=$(git config http.${{ gitea.server_url }}/.extraheader)
# AUTH_FILE=$(git config includeif.gitdir:/workspace/${{ gitea.repository }}/.git.path)
# git config -f $AUTH_FILE --unset http.${{ gitea.server_url }}/.extraheader
# git config -f $AUTH_FILE http.${{ gitea.server_url }}/${{ gitea.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
# git lfs pull
#
# - name: Launch Godot
# uses: godot-gdunit-labs/gdUnit4-action@v1
# with:
# godot-version: '4.5.1'
# godot-net: true
# godot-force-mono: true
# dotnet-version: 'net9.0'
# version: 'v6.0.3'
# paths: |
# res://tests/
# timeout: 1
# publish-report: false
# upload-report: false
#
# - name: Upload test report
# uses: actions/upload-artifact@v3-node20
# with:
# name: Test Report
# path: ${{ github.workspace }}/reports/test-result.html
Test:
runs-on: godot
# env:
# RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
steps:
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Run tests
uses: godot-gdunit-labs/gdUnit4-action@v1
with:
godot-version: ${GODOT_VERSION}
godot-net: true
godot-force-mono: true
dotnet-version: 'net9.0'
paths: |
res://tests/
timeout: 1
publish-report: false
upload-report: false
- name: Upload test report
uses: actions/upload-artifact@v3-node20
with:
name: Test Report
path: ${{ github.workspace }}/reports/test-result.html
Export:
runs-on: ubuntu-latest
runs-on: godot
needs:
- BumpTag
# - Test # Wait for tests to finish
container:
image: barichello/godot-ci:mono-4.5
steps:
- name: Install node, curl and zip
run: |
apt update && apt -y install curl zip nodejs
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Checkout
uses: actions/checkout@v6
- name: Setup Godot
id: setup-godot
uses: https://git.game-dev.space/minimata/setup-godot.git@main
with:
lfs: false
persist-credentials: true
- name: Checkout LFS
godot-version: '4.6'
dotnet-version: 'net9.0'
- name: Remove GDUnit addon
run: |
git lfs install --local
AUTH=$(git config http.${{ gitea.server_url }}/.extraheader)
AUTH_FILE=$(git config includeif.gitdir:/workspace/${{ gitea.repository }}/.git.path)
git config -f $AUTH_FILE --unset http.${{ gitea.server_url }}/.extraheader
git config -f $AUTH_FILE http.${{ gitea.server_url }}/${{ gitea.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
git lfs pull
- name: Import resources and build solution
run: |
godot --headless --editor --build-solutions --quit --import --path $PWD
rm -rf ${{ gitea.workspace }}/addons/gdUnit4
- name: Build Windows
run: |
mkdir -v -p build/windows
godot --headless --verbose --build-solutions --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
${{ steps.setup-godot.outputs.godot_bin }} --headless --verbose --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
zip -r Windows.zip build/windows
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
gameData: Windows.zip
buildChannel: windows
- name: Setup Butler
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
mkdir ./tools 2>/dev/null || true
pushd tools
curl -sSLfo ./butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
unzip butler.zip
chmod +x ./butler
popd
./tools/butler -V
- name: Upload to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.BumpTag.outputs.tag_name }}"
./tools/butler push \
"Windows.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows ${versionArgument}

View File

@@ -8,6 +8,7 @@ on:
- "**"
env:
GODOT_VERSION: 4.6
GAME_NAME: MovementTests
ITCHIO_USERNAME: Minimata
ITCHIO_GAMEID: MovementTests
@@ -26,92 +27,90 @@ jobs:
separator: '/'
Release:
runs-on: ubuntu-latest
runs-on: godot
if: ${{ contains(gitea.ref_name, 'release/') }}
needs: ReleaseName
container:
image: barichello/godot-ci:mono-4.5
steps:
- name: Install node, curl and zip
run: |
apt update && apt -y install curl zip nodejs
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Checkout
uses: actions/checkout@v6
- name: Setup Godot
id: setup-godot
uses: https://git.game-dev.space/minimata/setup-godot.git@main
with:
lfs: false
persist-credentials: true
- name: Checkout LFS
godot-version: '4.6'
dotnet-version: 'net9.0'
- name: Setup Butler
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
git lfs install --local
AUTH=$(git config http.${{ gitea.server_url }}/.extraheader)
AUTH_FILE=$(git config includeif.gitdir:/workspace/${{ gitea.repository }}/.git.path)
git config -f $AUTH_FILE --unset http.${{ gitea.server_url }}/.extraheader
git config -f $AUTH_FILE http.${{ gitea.server_url }}/${{ gitea.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH"
git lfs pull
- name: Import resources and build solution
run: |
godot --headless --editor --build-solutions --quit --import --path $PWD
mkdir ./tools 2>/dev/null || true
pushd tools
curl -sSLfo ./butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
unzip butler.zip
chmod +x ./butler
popd
./tools/butler -V
- name: Build Windows
run: |
mkdir -v -p build/windows
godot --headless --verbose --build-solutions --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
zip -r Windows.zip build/windows
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.ReleaseName.outputs.release_name }}
gameData: Windows.zip
buildChannel: windows
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"Windows.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows ${versionArgument}
- name: Build Windows ARM
run: |
mkdir -v -p build/windowsArm
godot --headless --verbose --build-solutions --export-release "Windows ARM" build/windowsArm/${{ env.GAME_NAME }}.exe
zip -r WindowsArm.zip build/windowsArm
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.ReleaseName.outputs.release_name }}
gameData: WindowsArm.zip
buildChannel: windows-arm
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"WindowsArm.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows-arm ${versionArgument}
- name: Linux Build
run: |
mkdir -v -p build/linux
godot --headless --verbose --export-release "Linux/X11" build/linux/${{ env.GAME_NAME }}.x86_64
zip -r Linux.zip build/linux
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.ReleaseName.outputs.release_name }}
gameData: Linux.zip
buildChannel: linux
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"Linux.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:linux ${versionArgument}
- name: Mac Build
run: |
mkdir -v -p build/mac
godot --headless --verbose --export-release "macOS" build/mac/${{ env.GAME_NAME }}.zip
zip -r Mac.zip build/mac
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.ReleaseName.outputs.release_name }}
gameData: Mac.zip
buildChannel: mac
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"Mac.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:mac ${versionArgument}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Godot.NET.Sdk/4.5.0">
<Project Sdk="Godot.NET.Sdk/4.6.0">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
@@ -131,9 +131,9 @@
</ItemGroup>
<!-- gdUnit4 package dependencies -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3" />
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0" />
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
<PrivateAssets>none</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

142
Movement tests.csproj.old.1 Normal file
View File

@@ -0,0 +1,142 @@
<Project Sdk="Godot.NET.Sdk/4.5.0">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>Movementtests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Content Include=".runsettings" />
<Content Include="export_presets.cfg" />
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png" />
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import" />
<Content Include="menus\assets\git_logo\LICENSE.txt" />
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt" />
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png" />
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import" />
<Content Include="menus\assets\icon.png" />
<Content Include="menus\assets\icon.png.import" />
<Content Include="menus\ATTRIBUTION.md" />
<Content Include="menus\resources\themes\expedition.tres" />
<Content Include="menus\resources\themes\gravity.tres" />
<Content Include="menus\resources\themes\grow.tres" />
<Content Include="menus\resources\themes\lab.tres" />
<Content Include="menus\resources\themes\lore.tres" />
<Content Include="menus\resources\themes\steal_this_theme.tres" />
<Content Include="menus\scenes\credits\scrollable_credits.gd" />
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid" />
<Content Include="menus\scenes\credits\scrollable_credits.tscn" />
<Content Include="menus\scenes\credits\scrolling_credits.gd" />
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid" />
<Content Include="menus\scenes\credits\scrolling_credits.tscn" />
<Content Include="menus\scenes\end_credits\end_credits.gd" />
<Content Include="menus\scenes\end_credits\end_credits.gd.uid" />
<Content Include="menus\scenes\end_credits\end_credits.tscn" />
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd" />
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid" />
<Content Include="menus\scenes\game_scene\game_ui.tscn" />
<Content Include="menus\scenes\game_scene\input_display_label.gd" />
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid" />
<Content Include="menus\scenes\game_scene\levels\level.gd" />
<Content Include="menus\scenes\game_scene\levels\level.gd.uid" />
<Content Include="menus\scenes\game_scene\levels\level_1.tscn" />
<Content Include="menus\scenes\game_scene\levels\level_2.tscn" />
<Content Include="menus\scenes\game_scene\levels\level_3.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn" />
<Content Include="menus\scenes\game_scene\tutorial_manager.gd" />
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid" />
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn" />
<Content Include="menus\scenes\loading_screen\loading_screen.gd" />
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid" />
<Content Include="menus\scenes\loading_screen\loading_screen.tscn" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn" />
<Content Include="menus\scenes\menus\main_menu\main_menu.gd" />
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid" />
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn" />
<Content Include="menus\scenes\opening\opening.gd" />
<Content Include="menus\scenes\opening\opening.gd.uid" />
<Content Include="menus\scenes\opening\opening.tscn" />
<Content Include="menus\scenes\opening\opening_with_logo.tscn" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn" />
<Content Include="menus\scripts\game_state.gd" />
<Content Include="menus\scripts\game_state.gd.uid" />
<Content Include="menus\scripts\level_list_and_state_manager.gd" />
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid" />
<Content Include="menus\scripts\level_state.gd" />
<Content Include="menus\scripts\level_state.gd.uid" />
</ItemGroup>
<ItemGroup>
<Folder Include="addons\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RustyOptions" Version="0.10.1" />
</ItemGroup>
<!-- gdUnit4 package dependencies -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
<PrivateAssets>none</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1 +1 @@
uid://do2c2faoehm61
uid://i04mdqgjjfsa

View File

@@ -1 +1 @@
uid://bretpek2ehht4
uid://lcya8t25hn0j

View File

@@ -3,5 +3,5 @@
name="gdUnit4"
description="Unit Testing Framework for Godot Scripts"
author="Mike Schulze"
version="6.0.3"
version="6.1.0"
script="plugin.gd"

View File

@@ -1,23 +1,29 @@
@tool
extends EditorPlugin
# We need to define manually the slot id's, to be downwards compatible
const CONTEXT_SLOT_FILESYSTEM: int = 1 # EditorContextMenuPlugin.CONTEXT_SLOT_FILESYSTEM
const CONTEXT_SLOT_SCRIPT_EDITOR: int = 2 # EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR
var _gd_inspector: Control
var _gd_console: Control
var _gd_filesystem_context_menu: Variant
var _gd_scripteditor_context_menu: Variant
var _filesystem_context_menu: EditorContextMenuPlugin
var _editor_context_menu: EditorContextMenuPlugin
var _editor_code_context_menu: EditorContextMenuPlugin
func _enter_tree() -> void:
var inferred_declaration: int = ProjectSettings.get_setting("debug/gdscript/warnings/inferred_declaration")
var exclude_addons: bool = ProjectSettings.get_setting("debug/gdscript/warnings/exclude_addons")
if !exclude_addons and inferred_declaration != 0:
var is_gdunit_excluded_warnings: bool = false
if Engine.get_version_info().hex >= 0x40600:
var dirctrory_rules: Dictionary = ProjectSettings.get_setting("debug/gdscript/warnings/directory_rules")
if dirctrory_rules.has("res://addons/gdUnit4") and dirctrory_rules["res://addons/gdUnit4"] == 0:
is_gdunit_excluded_warnings = true
else:
is_gdunit_excluded_warnings = ProjectSettings.get_setting("debug/gdscript/warnings/exclude_addons")
if !is_gdunit_excluded_warnings and inferred_declaration != 0:
printerr("GdUnit4: 'inferred_declaration' is set to Warning/Error!")
printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded addons (debug/gdscript/warnings/exclude_addons)")
if Engine.get_version_info().hex >= 0x40600:
printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded the addon (debug/gdscript/warnings/directory_rules)")
else:
printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded addons (debug/gdscript/warnings/exclude_addons)")
printerr("Loading GdUnit4 Plugin failed.")
return
@@ -73,36 +79,21 @@ func check_running_in_test_env() -> bool:
func _add_context_menus() -> void:
if Engine.get_version_info().hex >= 0x40400:
# With Godot 4.4 we have to use the 'add_context_menu_plugin' to register editor context menus
_gd_filesystem_context_menu = _preload_gdx_script("res://addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandlerV44.gdx")
call_deferred("add_context_menu_plugin", CONTEXT_SLOT_FILESYSTEM, _gd_filesystem_context_menu)
# the CONTEXT_SLOT_SCRIPT_EDITOR is adding to the script panel instead of script editor see https://github.com/godotengine/godot/pull/100556
#_gd_scripteditor_context_menu = _preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandlerV44.gdx")
#call_deferred("add_context_menu_plugin", CONTEXT_SLOT_SCRIPT_EDITOR, _gd_scripteditor_context_menu)
# so we use the old hacky way to add the context menu
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd").new())
else:
# TODO Delete it if the minimum requirement for the plugin is set to Godot 4.4.
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd").new())
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd").new())
_filesystem_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd").new()
_editor_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd").new()
_editor_code_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd").new()
add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_FILESYSTEM, _filesystem_context_menu)
add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_menu)
add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _editor_code_context_menu)
func _remove_context_menus() -> void:
if is_instance_valid(_gd_filesystem_context_menu):
call_deferred("remove_context_menu_plugin", _gd_filesystem_context_menu)
if is_instance_valid(_gd_scripteditor_context_menu):
call_deferred("remove_context_menu_plugin", _gd_scripteditor_context_menu)
func _preload_gdx_script(script_path: String) -> Variant:
var script: GDScript = GDScript.new()
script.source_code = GdUnitFileAccess.resource_as_string(script_path)
script.take_over_path(script_path)
var err :Error = script.reload()
if err != OK:
push_error("Can't create context menu %s, error: %s" % [script_path, error_string(err)])
return script.new()
if is_instance_valid(_filesystem_context_menu):
remove_context_menu_plugin(_filesystem_context_menu)
if is_instance_valid(_editor_context_menu):
remove_context_menu_plugin(_editor_context_menu)
if is_instance_valid(_editor_code_context_menu):
remove_context_menu_plugin(_editor_code_context_menu)
func _on_resource_saved(resource: Resource) -> void:

View File

@@ -1 +1 @@
uid://bc4fimf6ynr5d
uid://8wxua8uw7x7k

View File

@@ -1 +1 @@
uid://buiskkw1yyuw3
uid://duj13ipuced2q

View File

@@ -1 +1 @@
uid://drfioswpw8u2u
uid://wd2ydmpylh1e

View File

@@ -1 +1 @@
uid://byeulsiqvaugq
uid://bwaeyokx1kgfd

View File

@@ -1 +1 @@
uid://bmy2nu4w22wia
uid://qqwrtj2mj3xi

View File

@@ -1 +1 @@
uid://c1jp2le4lldby
uid://fnv7o85xbiiq

View File

@@ -1 +1 @@
uid://bftfpffmfb1il
uid://bav21rax06rdf

View File

@@ -1 +1 @@
uid://dkap7kpfh2bhg
uid://b750alnjl31nv

View File

@@ -1 +1 @@
uid://8s1lymhdvlpu
uid://cfqx4148ov21q

View File

@@ -1 +1 @@
uid://x54vf4fue301
uid://hqhd4a063x3n

View File

@@ -1 +1 @@
uid://vt1hx0i6pg4h
uid://xsowfqnkhk7j

View File

@@ -1 +1 @@
uid://l487wamffax1
uid://crdh3ctgqxrqi

View File

@@ -1 +1 @@
uid://bvvptcdhi1g14
uid://bqguqoaa43uev

View File

@@ -1 +1 @@
uid://bwkv3a1hhdt88
uid://clve38xv30uem

View File

@@ -1 +1 @@
uid://ghuy35olsym1
uid://chqkkjclq101n

View File

@@ -1 +1 @@
uid://dmunl8xg53sym
uid://b7mk5mihqqr65

View File

@@ -1 +1 @@
uid://b4n45twg8y2ar
uid://cgee6csuvo1ye

View File

@@ -24,9 +24,8 @@ extends RefCounted
## Simulates that a key has been pressed.[br]
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
## [codeblock]
## func test_key_presssed():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
@@ -35,17 +34,43 @@ extends RefCounted
@abstract func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## Simulates that a key is pressed.[br]
## Simulates that a key is pressing.[br]
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0[br]See `test_key_shift_and_A_presssing` for example using key combinations
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
## [codeblock]
## # Do simulate key pressing A
## func test_key_A_presssing():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## await runner.simulate_key_press(KEY_A)
##
##
## # Do simulate keycombination pressing shift+A
## func test_key_shift_and_A_presssing():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## runner.simulate_key_press(KEY_SHIFT)
## runner.simulate_key_press(KEY_A)
## await _runner.await_input_processed()
## [/codeblock]
@abstract func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## Simulates that a key has been released.[br]
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
## [codeblock]
## # Do simulate releasing key A
## func test_key_A_releasing():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## await runner.simulate_key_release(KEY_A)
##
##
## # Do simulate keycombination pressing shift+A
## func test_key_shift_and_A_releasing(():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## runner.simulate_key_release(KEY_SHIFT)
## runner.simulate_key_release(KEY_A)
## await _runner.await_input_processed()
## [/codeblock]
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0[br]See `test_key_shift_and_A_releasing` for example using key combinations
@abstract func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner

View File

@@ -1 +1 @@
uid://dn20c5e8kb3q3
uid://cg867wakih43u

View File

@@ -27,20 +27,132 @@ extends GdUnitAssert
@abstract func append_failure_message(message: String) -> GdUnitSignalAssert
## Verifies that given signal is emitted until waiting time
@abstract func is_emitted(name: String, args := []) -> GdUnitSignalAssert
## Verifies that the specified signal is emitted with the expected arguments.[br]
##
## This assertion waits for a signal to be emitted from the object under test and
## validates that it was emitted with the correct arguments. The function supports
## both typed signals (Signal type) and string-based signal names for flexibility
## in different testing scenarios.[br]
## [br]
## [b]Parameters:[/b][br]
## [param signal_name]: The signal to monitor. Can be either:[br]
## • A [Signal] reference (recommended for type safety)[br]
## • A [String] with the signal name
## [param signal_args]: Optional expected signal arguments.[br]
## When provided, verifies the signal was emitted with exactly these values.[br]
## [br]
## [b]Returns:[/b][br]
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## signal signal_a(value: int)
## signal signal_b(name: String, count: int)
##
## # Wait for signal emission without checking arguments
## # Using Signal reference (type-safe)
## await assert_signal(instance).is_emitted(signal_a)
## # Using string name (dynamic)
## await assert_signal(instance).is_emitted("signal_a")
##
## # Wait for signal emission with specific argument
## await assert_signal(instance).is_emitted(signal_a, 10)
##
## # Wait for signal with multiple arguments
## await assert_signal(instance).is_emitted(signal_b, "test", 42)
##
## # Wait max 500ms for signal with argument 10
## await assert_signal(instance).wait_until(500).is_emitted(signal_a, 10)
## [/codeblock]
## [br]
## [b]Note:[/b] This is an async operation - use [code]await[/code] when calling.[br]
## The assertion fails if the signal is not emitted within the timeout period.
@abstract func is_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert
## Verifies that given signal is NOT emitted until waiting time
@abstract func is_not_emitted(name: String, args := []) -> GdUnitSignalAssert
## Verifies that the specified signal is NOT emitted with the expected arguments.[br]
##
## This assertion waits for a specified time period and validates that a signal
## was not emitted with the given arguments. Useful for ensuring certain conditions
## don't trigger unwanted signals or for verifying signal filtering logic.[br]
## [br]
## [b]Parameters:[/b][br]
## [param signal_name]: The signal to monitor. Can be either:[br]
## • A [Signal] reference (recommended for type safety)[br]
## • A [String] with the signal name
## [param signal_args]: Optional expected signal arguments.[br]
## When provided, verifies the signal was not emitted with these specific values.[br]
## If omitted, verifies the signal was not emitted at all.[br]
## [br]
## [b]Returns:[/b][br]
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## signal signal_a(value: int)
## signal signal_b(name: String, count: int)
##
## # Verify signal is not emitted at all (without checking arguments)
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a)
## await assert_signal(instance).wait_until(500).is_not_emitted("signal_a")
##
## # Verify signal is not emitted with specific argument
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a, 10)
##
## # Verify signal is not emitted with multiple arguments
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_b, "test", 42)
##
## # Can be emitted with different arguments (this passes)
## instance.emit_signal("signal_a", 20) # Emits with 20, not 10
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a, 10)
## [/codeblock]
## [br]
## [b]Note:[/b] This is an async operation - use [code]await[/code] when calling.[br]
## The assertion fails if the signal IS emitted with the specified arguments within the timeout period.
@abstract func is_not_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert
## Verifies the signal exists checked the emitter
@abstract func is_signal_exists(name: String) -> GdUnitSignalAssert
## Verifies that the specified signal exists on the emitter object.[br]
##
## This assertion checks if a signal is defined on the object under test,
## regardless of whether it has been emitted. Useful for validating that
## objects have the expected signals before testing their emission.[br]
## [br]
## [b]Parameters:[/b][br]
## [param signal_name]: The signal to check. Can be either:[br]
## • A [Signal] reference (recommended for type safety)[br]
## • A [String] with the signal name
## [br]
## [b]Returns:[/b][br]
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## signal my_signal(value: int)
## signal another_signal()
##
## # Verify signal exists using Signal reference
## assert_signal(instance).is_signal_exists(my_signal)
##
## # Verify signal exists using string name
## assert_signal(instance).is_signal_exists("my_signal")
##
## # Chain with other assertions
## assert_signal(instance) \
## .is_signal_exists(my_signal) \
## .is_emitted(my_signal, 42)
##
## [/codeblock]
## [br]
## [b]Note:[/b] This only checks signal definition, not emission.[br]
## The assertion fails if the signal is not defined on the object.
@abstract func is_signal_exists(signal_name: Variant) -> GdUnitSignalAssert
## Sets the assert signal timeout in ms, if the time over a failure is reported.[br]
## e.g.[br]
## Example:
## [codeblock]
## do wait until 5s the instance has emitted the signal `signal_a`[br]
## [code]assert_signal(instance).wait_until(5000).is_emitted("signal_a")[/code]
## assert_signal(instance).wait_until(5000).is_emitted("signal_a")
## [/codeblock]
@abstract func wait_until(timeout: int) -> GdUnitSignalAssert

View File

@@ -1 +1 @@
uid://572nse6u4l86
uid://ck4dbwhcpf144

View File

@@ -1 +1 @@
uid://ip241g801xri
uid://b2itt4pwvweqo

View File

@@ -14,14 +14,13 @@
class_name GdUnitTestSuite
extends Node
const NO_ARG :Variant = GdUnitConstants.NO_ARG
### internal runtime variables that must not be overwritten!!!
@warning_ignore("unused_private_class_variable")
var __is_skipped := false
@warning_ignore("unused_private_class_variable")
var __skip_reason :String = "Unknow."
var __active_test_case :String
var __skip_reason := "Unknow."
var __active_test_case: String
var __awaiter := __gdunit_awaiter()
@@ -29,7 +28,7 @@ var __awaiter := __gdunit_awaiter()
### in order to noticeably reduce the loading time of the test suite.
# We go this hard way to increase the loading performance to avoid reparsing all the used scripts
# for more detailed info -> https://github.com/godotengine/godot/issues/67400
func __lazy_load(script_path :String) -> GDScript:
func __lazy_load(script_path: String) -> GDScript:
return GdUnitAssertions.__lazy_load(script_path)
@@ -81,23 +80,23 @@ func after_test() -> void:
pass
func is_failure(_expected_failure :String = NO_ARG) -> bool:
func is_failure() -> bool:
return Engine.get_meta("GD_TEST_FAILURE") if Engine.has_meta("GD_TEST_FAILURE") else false
func set_active_test_case(test_case :String) -> void:
func set_active_test_case(test_case: String) -> void:
__active_test_case = test_case
# === Tools ====================================================================
# Mapps Godot error number to a readable error message. See at ERROR
# https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error
func error_as_string(error_number :int) -> String:
func error_as_string(error_number: int) -> String:
return error_string(error_number)
## A litle helper to auto freeing your created objects after test execution
func auto_free(obj :Variant) -> Variant:
func auto_free(obj: Variant) -> Variant:
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
assert(execution_context != null, "INTERNAL ERROR: The current execution_context is null! Please report this as bug.")
@@ -105,7 +104,7 @@ func auto_free(obj :Variant) -> Variant:
@warning_ignore("native_method_override")
func add_child(node :Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void:
func add_child(node: Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void:
super.add_child(node, force_readable_name, internal)
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
if execution_context != null:
@@ -123,7 +122,7 @@ func discard_error_interupted_by_timeout() -> void:
## Creates a new directory under the temporary directory *user://tmp*[br]
## Useful for storing data during test execution. [br]
## The directory is automatically deleted after test suite execution
func create_temp_dir(relative_path :String) -> String:
func create_temp_dir(relative_path: String) -> String:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().create_temp_dir(relative_path)
@@ -138,25 +137,25 @@ func clean_temp_dir() -> void:
## Creates a new file under the temporary directory *user://tmp* + <relative_path>[br]
## with given name <file_name> and given file <mode> (default = File.WRITE)[br]
## If success the returned File is automatically closed after the execution of the test suite
func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess:
func create_temp_file(relative_path: String, file_name: String, mode := FileAccess.WRITE) -> FileAccess:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().create_temp_file(relative_path, file_name, mode)
## Reads a resource by given path <resource_path> into a PackedStringArray.
func resource_as_array(resource_path :String) -> PackedStringArray:
func resource_as_array(resource_path: String) -> PackedStringArray:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().resource_as_array(resource_path)
## Reads a resource by given path <resource_path> and returned the content as String.
func resource_as_string(resource_path :String) -> String:
func resource_as_string(resource_path: String) -> String:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().resource_as_string(resource_path)
## Reads a resource by given path <resource_path> and return Variand translated by str_to_var
func resource_as_var(resource_path :String) -> Variant:
func resource_as_var(resource_path: String) -> Variant:
@warning_ignore("unsafe_method_access", "unsafe_cast")
return str_to_var(__gdunit_file_access().resource_as_string(resource_path) as String)
@@ -166,7 +165,7 @@ func resource_as_var(resource_path :String) -> Variant:
## signal_name: signal name[br]
## args: the expected signal arguments as an array[br]
## timeout: the timeout in ms, default is set to 2000ms
func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout :int = 2000) -> Variant:
func await_signal_on(source: Object, signal_name: String, args: Array = [], timeout: int = 2000) -> Variant:
@warning_ignore("unsafe_method_access")
return await __awaiter.await_signal_on(source, signal_name, args, timeout)
@@ -184,11 +183,50 @@ func await_idle_frame() -> void:
## await await_millis(myNode, 100).completed
## [/codeblock][br]
## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out
func await_millis(timeout :int) -> void:
func await_millis(timeout: int) -> void:
@warning_ignore("unsafe_method_access")
await __awaiter.await_millis(timeout)
## Collects detailed information about orphaned nodes for debugging purposes.[br]
##
## This function gathers comprehensive details about nodes that remain in memory
## after test execution (orphans). It provides debugging information to help
## identify the source of memory leaks in tests. Must be manually called in
## tests when orphan nodes are detected.[br]
## [br]
## [b]When to Use:[/b][br]
## - When GdUnit4 reports orphan nodes after test execution[br]
## - For debugging memory leaks in test scenarios[br]
## - To get detailed information about unreleased nodes[br]
## [br]
## [b]Usage Pattern:[/b][br]
## Add this call at the end of tests that are suspected to create orphans,
## or when the test runner reports orphan detection.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## func test_scene_management():
## # Test code that might create orphan nodes
## var scene = preload("res://TestScene.tscn").instantiate()
## add_child(scene)
##
## # Do test operations
## scene.some_method()
##
## # Clean up (but might miss some nodes)
## scene.queue_free()
##
## # Collect orphan details if any are detected
## collect_orphan_node_details()
## [/codeblock]
## [br]
## [b]Note:[/b] This is a debugging utility function that should be removed
## or commented out once orphan issues are resolved.
func collect_orphan_node_details() -> void:
GdUnitThreadManager.get_current_context().get_execution_context().orphan_monitor_collect()
## Creates a new scene runner to allow simulate interactions checked a scene.[br]
## The runner will manage the scene instance and release after the runner is released[br]
## example:[br]
@@ -200,7 +238,7 @@ func await_millis(timeout :int) -> void:
## # or simply creates a runner by using the scene resource path
## var runner := scene_runner("res://foo/my_scne.tscn")
## [/codeblock]
func scene_runner(scene :Variant, verbose := false) -> GdUnitSceneRunner:
func scene_runner(scene: Variant, verbose := false) -> GdUnitSceneRunner:
return auto_free(__lazy_load("res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd").new(scene, verbose))
@@ -216,13 +254,13 @@ const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB
## Creates a mock for given class name
func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Variant:
func mock(clazz: Variant, mock_mode := RETURN_DEFAULTS) -> Variant:
@warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode)
## Creates a spy checked given object instance
func spy(instance :Variant) -> Variant:
func spy(instance: Variant) -> Variant:
@warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance)
@@ -233,30 +271,30 @@ func spy(instance :Variant) -> Variant:
## # overrides the return value of myMock.is_selected() to false
## do_return(false).on(myMock).is_selected()
## [/codeblock]
func do_return(value :Variant) -> GdUnitMock:
func do_return(value: Variant) -> GdUnitMock:
return GdUnitMock.new(value)
## Verifies certain behavior happened at least once or exact number of times
func verify(obj :Variant, times := 1) -> Variant:
func verify(obj: Variant, times := 1) -> Variant:
@warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify(obj, times)
## Verifies no interactions is happen checked this mock or spy
func verify_no_interactions(obj :Variant) -> GdUnitAssert:
func verify_no_interactions(obj: Variant) -> GdUnitAssert:
@warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify_no_interactions(obj)
## Verifies the given mock or spy has any unverified interaction.
func verify_no_more_interactions(obj :Variant) -> GdUnitAssert:
func verify_no_more_interactions(obj: Variant) -> GdUnitAssert:
@warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify_no_more_interactions(obj)
## Resets the saved function call counters checked a mock or spy
func reset(obj :Variant) -> void:
func reset(obj: Variant) -> void:
@warning_ignore("unsafe_method_access")
__gdunit_object_interactions().reset(obj)
@@ -273,7 +311,7 @@ func reset(obj :Variant) -> void:
## # verify the signial is emitted
## await assert_signal(emitter).is_emitted('my_signal')
## [/codeblock]
func monitor_signals(source :Object, _auto_free := true) -> Object:
func monitor_signals(source: Object, _auto_free := true) -> Object:
@warning_ignore("unsafe_method_access")
__lazy_load("res://addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd")\
.get_current_context()\
@@ -502,29 +540,46 @@ func any_class(clazz :Object) -> GdUnitArgumentMatcher:
# === value extract utils ======================================================
## Builds an extractor by given function name and optional arguments
func extr(func_name :String, args := Array()) -> GdUnitValueExtractor:
func extr(func_name: String, args := Array()) -> GdUnitValueExtractor:
return __lazy_load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd").new(func_name, args)
## Constructs a tuple by given arguments
func tuple(arg0 :Variant,
arg1 :Variant=NO_ARG,
arg2 :Variant=NO_ARG,
arg3 :Variant=NO_ARG,
arg4 :Variant=NO_ARG,
arg5 :Variant=NO_ARG,
arg6 :Variant=NO_ARG,
arg7 :Variant=NO_ARG,
arg8 :Variant=NO_ARG,
arg9 :Variant=NO_ARG) -> GdUnitTuple:
return GdUnitTuple.new(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
## Creates a GdUnitTuple from the provided arguments for use in test assertions.
## [br]
## This is the primary helper function for creating tuples in GdUnit4 tests.
## It provides a convenient way to group multiple expected values when using
## [method extractv] assertions. The function enforces that tuples must contain
## at least two values, as single-value extractions don't require tuple grouping.
## [br]
## [b]Parameters:[/b] [br]
## - [code]...args[/code]: Variable number of arguments (minimum 2) to group into a tuple.
## Each argument represents a value to be compared in assertions.
## [br]
## [b]Returns:[/b] [br]
## A [GdUnitTuple] containing the provided values, or an empty tuple if fewer than
## 2 arguments are provided (with an error message).
## [br]
## [b]Error Handling:[/b] [br]
## [codeblock]
## # This will push an error and return empty tuple
## var invalid = tuple("single_value") # Error: requires at least 2 arguments
## [br]
## # Correct usage - minimum 2 arguments
## var valid = tuple("name", "value")
## var valid_multi = tuple(1, 2, 3, 4, 5) # Can have many values
## [/codeblock]
func tuple(...args: Array) -> GdUnitTuple:
if args.size() < 2:
push_error("Tuple requires at least two arguments.")
return GdUnitTuple.new()
return GdUnitTuple.new.callv(args)
# === Asserts ==================================================================
## The common assertion tool to verify values.
## It checks the given value by type to fit to the best assert
func assert_that(current :Variant) -> GdUnitAssert:
func assert_that(current: Variant) -> GdUnitAssert:
match typeof(current):
TYPE_BOOL:
return assert_bool(current)
@@ -549,22 +604,22 @@ func assert_that(current :Variant) -> GdUnitAssert:
## An assertion tool to verify boolean values.
func assert_bool(current :Variant) -> GdUnitBoolAssert:
func assert_bool(current: Variant) -> GdUnitBoolAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd").new(current)
## An assertion tool to verify String values.
func assert_str(current :Variant) -> GdUnitStringAssert:
func assert_str(current: Variant) -> GdUnitStringAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd").new(current)
## An assertion tool to verify integer values.
func assert_int(current :Variant) -> GdUnitIntAssert:
func assert_int(current: Variant) -> GdUnitIntAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd").new(current)
## An assertion tool to verify float values.
func assert_float(current :Variant) -> GdUnitFloatAssert:
func assert_float(current: Variant) -> GdUnitFloatAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current)
@@ -574,41 +629,41 @@ func assert_float(current :Variant) -> GdUnitFloatAssert:
## [codeblock]
## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001))
## [/codeblock]
func assert_vector(current :Variant, type_check := true) -> GdUnitVectorAssert:
func assert_vector(current: Variant, type_check := true) -> GdUnitVectorAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current, type_check)
## An assertion tool to verify arrays.
func assert_array(current :Variant, type_check := true) -> GdUnitArrayAssert:
func assert_array(current: Variant, type_check := true) -> GdUnitArrayAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current, type_check)
## An assertion tool to verify dictionaries.
func assert_dict(current :Variant) -> GdUnitDictionaryAssert:
func assert_dict(current: Variant) -> GdUnitDictionaryAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd").new(current)
## An assertion tool to verify FileAccess.
func assert_file(current :Variant) -> GdUnitFileAssert:
func assert_file(current: Variant) -> GdUnitFileAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current)
## An assertion tool to verify Objects.
func assert_object(current :Variant) -> GdUnitObjectAssert:
func assert_object(current: Variant) -> GdUnitObjectAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current)
func assert_result(current :Variant) -> GdUnitResultAssert:
func assert_result(current: Variant) -> GdUnitResultAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current)
## An assertion tool that waits until a certain time for an expected function return value
func assert_func(instance :Object, func_name :String, args := Array()) -> GdUnitFuncAssert:
func assert_func(instance: Object, func_name: String, args := Array()) -> GdUnitFuncAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd").new(instance, func_name, args)
## An assertion tool to verify for emitted signals until a certain time.
func assert_signal(instance :Object) -> GdUnitSignalAssert:
func assert_signal(instance: Object) -> GdUnitSignalAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd").new(instance)
@@ -619,7 +674,7 @@ func assert_signal(instance :Object) -> GdUnitSignalAssert:
## assert_failure(func(): assert_bool(true).is_not_equal(true)) \
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
## [/codeblock]
func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
func assert_failure(assertion: Callable) -> GdUnitFailureAssert:
@warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute(assertion)
@@ -631,7 +686,7 @@ func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
## await assert_failure_await(func(): assert_bool(true).is_not_equal(true)) \
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
## [/codeblock]
func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert:
func assert_failure_await(assertion: Callable) -> GdUnitFailureAssert:
@warning_ignore("unsafe_method_access")
return await __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute_and_await(assertion)
@@ -648,7 +703,7 @@ func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert:
## await assert_error(func (): push_error('test error') )\
## .is_push_error('test error')
## [/codeblock]
func assert_error(current :Callable) -> GdUnitGodotErrorAssert:
func assert_error(current: Callable) -> GdUnitGodotErrorAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current)

View File

@@ -1 +1 @@
uid://cgbfa4cflb5nl
uid://bfroomur047su

View File

@@ -1,28 +1,86 @@
## A tuple implementation to hold two or many values
## A tuple implementation for GdUnit4 test assertions and value extraction.
## @tutorial(GdUnit4 Array Assertions): https://mikeschulze.github.io/gdUnit4/latest/testing/assert-array/#extractv
## @tutorial(GdUnit4 Testing Framework): https://mikeschulze.github.io/gdUnit4/
## [br]
## The GdUnitTuple class is a utility container designed specifically for the GdUnit4
## testing framework. It enables advanced assertion operations, particularly when
## extracting and comparing multiple values from complex test results.
## [br]
## [b]Primary Use Cases in Testing:[/b] [br]
## - Extracting multiple properties from test objects with [method extractv]## [br]
## - Grouping related assertion values for comparison## [br]
## - Returning multiple values from test helper methods## [br]
## - Organizing expected vs actual value pairs in assertions## [br]
## [br]
## [b]Example Usage in Tests:[/b]
## [codeblock]
## func test_player_stats_after_level_up():
## var player = Player.new()
## player.level_up()
##
## # Extract multiple properties using tuple
## assert_array([player]) \
## .extractv(extr("name"), extr("level"), extr("hp")) \
## .contains(tuple("Hero", 2, 150))
##
## func test_enemy_spawn_positions():
## var enemies: Array = spawn_enemies(3)
##
## # Verify multiple enemies have correct position data
## assert_array(enemies) \
## .extractv(extr("position.x"), extr("position.y")) \
## .contains_exactly([
## tuple(100, 200),
## tuple(150, 200),
## tuple(200, 200)
## ])
## [/codeblock]
## [br]
## [b]Integration with GdUnit4 Assertions:[/b] [br]
## Tuples work seamlessly with array assertion methods like: [br]
## - [code]contains()[/code] - Check if extracted values contain specific tuples [br]
## - [code]contains_exactly()[/code] - Verify exact tuple matches [br]
## - [code]is_equal()[/code] - Compare tuple equality [br]
## [br]
## [b]Note:[/b] This class is part of the GdUnit4 testing framework's internal
## utilities and is primarily intended for use within test assertions rather
## than production code.
class_name GdUnitTuple
extends RefCounted
const NO_ARG :Variant = GdUnitConstants.NO_ARG
var __values :Array = Array()
var _values: Array = []
func _init(arg0:Variant,
arg1 :Variant=NO_ARG,
arg2 :Variant=NO_ARG,
arg3 :Variant=NO_ARG,
arg4 :Variant=NO_ARG,
arg5 :Variant=NO_ARG,
arg6 :Variant=NO_ARG,
arg7 :Variant=NO_ARG,
arg8 :Variant=NO_ARG,
arg9 :Variant=NO_ARG) -> void:
__values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
## Initializes a new GdUnitTuple with test values.
## [br]
## Creates a tuple to hold multiple values extracted from test objects
## or expected values for assertions. Commonly used with the [code]tuple()[/code]
## helper function in GdUnit4 tests.
## [br]
## [b]Parameters:[/b]
## - [code]...args[/code]: Variable number of values to store.
func _init(...args: Array) -> void:
_values = args
## Returns the tuple's values as an array for assertion comparisons.
## [br]
## Provides access to the stored test values. Used internally by GdUnit4's
## assertion system when comparing tuples in test validations.
## [br]
## [b]Returns:[/b]
## An [Array] containing all values stored in the tuple.
func values() -> Array:
return __values
return _values
## Returns a string representation for test output and debugging.
## [br]
## Formats the tuple for display in test results, error messages, and debug logs.
## This method is automatically called by GdUnit4 when displaying assertion
## failures involving tuples.
## [br]
## [b]Returns:[/b]
## A [String] in the format "tuple([value1, value2, ...])"
func _to_string() -> String:
return "tuple(%s)" % str(__values)
return "tuple(%s)" % str(_values)

View File

@@ -1 +1 @@
uid://mjqw2uww51fk
uid://ckj5nn4gln5bw

View File

@@ -1 +1 @@
uid://2dylh01qtb66
uid://cyagv2phlu24d

View File

@@ -1 +1 @@
uid://bcx6bgypklb3e
uid://hrk46nhl8icp

View File

@@ -1 +1 @@
uid://r43u2usutiss
uid://bymqibokrfyuk

View File

@@ -1 +1 @@
uid://coauynw7rnsij
uid://bxkxeyj2jbth2

View File

@@ -40,22 +40,40 @@ static func input_event_as_text(event :InputEvent) -> String:
var text := ""
if event is InputEventKey:
var key_event := event as InputEventKey
text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [
event.as_text(), key_event.pressed, key_event.keycode, key_event.physical_keycode]
text += """
InputEventKey : keycode=%s (%s) pressed: %s
physical_keycode: %s
location: %s
echo: %s""" % [
key_event.keycode,
event.as_text_keycode(),
key_event.pressed,
key_event.physical_keycode,
key_event.location,
key_event.echo]
else:
text += event.as_text()
if event is InputEventMouse:
var mouse_event := event as InputEventMouse
text += ", global_position %s" % mouse_event.global_position
text += """
global_position: %s""" % mouse_event.global_position
if event is InputEventWithModifiers:
var mouse_event := event as InputEventWithModifiers
text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [
text += """
--------
mods: %s
shift: %s
alt: %s
control: %s
meta: %s
command: %s""" % [
mouse_event.get_modifiers_mask(),
mouse_event.shift_pressed,
mouse_event.alt_pressed,
mouse_event.ctrl_pressed,
mouse_event.meta_pressed,
mouse_event.command_or_control_autoremap]
return text
return text.dedent()
static func _colored_string_div(characters: String) -> String:
@@ -119,6 +137,10 @@ static func _nerror(number :Variant) -> String:
return "[color=%s]%s[/color]" % [ERROR_COLOR, str(number)]
static func _colored(value: Variant, color: Color) -> String:
return "[color=%s]%s[/color]" % [color.to_html(), value]
static func _colored_value(value :Variant) -> String:
match typeof(value):
TYPE_STRING, TYPE_STRING_NAME:
@@ -161,19 +183,53 @@ static func _index_report_as_table(index_reports :Array) -> String:
return table.replace("$cells", cells)
static func orphan_detected_on_suite_setup(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test suite setup stage! [b]Check before() and after()![/b]" % [
_warning("WARNING:"), count]
static func orphan_warning(orphans_count: int) -> String:
return """
%s: Found %s possible orphan nodes.
Add %s to the end of the test to collect details.""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans_count),
_colored_value("collect_orphan_node_details()")
]
static func orphan_detected_on_suite_setup(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
return """
%s Detected %s orphan nodes!
[b]Verify your test suite setup.[/b]
%s""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans.size()),
_build_orphan_node_stacktrace(orphans)]
static func orphan_detected_on_test_setup(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test setup! [b]Check before_test() and after_test()![/b]" % [
_warning("WARNING:"), count]
static func orphan_detected_on_test_setup(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
return """
%s Detected %s orphan nodes on test setup!
[b]Check before_test() and after_test()![/b]
%s""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans.size()),
_build_orphan_node_stacktrace(orphans)
]
static func orphan_detected_on_test(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test execution!" % [
_warning("WARNING:"), count]
static func orphan_detected_on_test(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
return """
%s Detected %s orphan nodes!
%s""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans.size()),
_build_orphan_node_stacktrace(orphans)
]
static func _build_orphan_node_stacktrace(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
var stack_trace := "\n"
for orphan in orphans:
stack_trace += orphan.as_trace(orphan, true) + "\n"
return stack_trace.indent(" ")
static func fuzzer_interuped(iterations: int, error: String) -> String:
@@ -188,6 +244,10 @@ static func test_timeout(timeout :int) -> String:
return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))]
static func test_session_terminated() -> String:
return "%s" % _error("Test Session Terminated")
# gdlint:disable = mixed-tabs-and-spaces
static func test_suite_skipped(hint :String, skip_count :int) -> String:
return """

View File

@@ -1 +1 @@
uid://vl7cfc01g5wl
uid://c178wdncle4i5

View File

@@ -1 +1 @@
uid://brxvavm3ml0om
uid://5fthlxduiurg

View File

@@ -75,7 +75,7 @@ func _toPackedStringArray(value: Variant) -> PackedStringArray:
return PackedStringArray([str(value)])
func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array[Array]:
func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array:
var current_value := _toPackedStringArray(current)
var expected_value := _toPackedStringArray(expected)
var index_report := Array()
@@ -374,24 +374,12 @@ func extractv(...extractors: Array) -> GdUnitArrayAssert:
_current_value_provider = DefaultValueProvider.new(null)
else:
for element: Variant in current:
var ev: Array[Variant] = [
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG
]
var ev: Array[Variant] = []
for index: int in extractors.size():
var extractor: GdUnitValueExtractor = extractors[index]
ev[index] = extractor.extract_value(element)
ev.append(extractor.extract_value(element))
if extractors.size() > 1:
extracted_elements.append(GdUnitTuple.new(ev[0], ev[1], ev[2], ev[3], ev[4], ev[5], ev[6], ev[7], ev[8], ev[9]))
extracted_elements.append(GdUnitTuple.new.callv(ev))
else:
extracted_elements.append(ev[0])
_current_value_provider = DefaultValueProvider.new(extracted_elements)

View File

@@ -1 +1 @@
uid://bx7cehfdh2x4w
uid://381c6r7r5uhp

View File

@@ -1 +1 @@
uid://cq38mcld2thyl
uid://8c05oepulju5

View File

@@ -1 +1 @@
uid://61d7pdgldg0r
uid://crrhhudfmy3ay

View File

@@ -1 +1 @@
uid://cxndss6mdq7de
uid://c310wp8sog1ti

View File

@@ -1 +1 @@
uid://dqrp7csbeyvon
uid://diqntuiv0y7pg

View File

@@ -1 +1 @@
uid://cbrj7dsr235i0
uid://b8htynygoyy0f

View File

@@ -1 +1 @@
uid://2s6h0titid8y
uid://dg0uusgasnr64

View File

@@ -1 +1 @@
uid://dvce6xeybbh1i
uid://fyb32t5gwdxk

View File

@@ -1 +1 @@
uid://c2jdw0vv5nldq
uid://bd15pn72awek1

View File

@@ -4,6 +4,7 @@ var _current_failure_message := ""
var _custom_failure_message := ""
var _additional_failure_message := ""
var _callable: Callable
var _logger := GodotGdErrorMonitor.new()
func _init(callable: Callable) -> void:
@@ -14,17 +15,10 @@ func _init(callable: Callable) -> void:
func _execute() -> Array[ErrorLogEntry]:
# execute the given code and monitor for runtime errors
if _callable == null or not _callable.is_valid():
@warning_ignore("return_value_discarded")
_report_error("Invalid Callable '%s'" % _callable)
else:
await _callable.call()
return await _error_monitor().scan(true)
func _error_monitor() -> GodotGdErrorMonitor:
return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor
_logger.start()
await _callable.call()
_logger.stop()
return _logger.log_entries()
func failure_message() -> String:
@@ -46,8 +40,6 @@ func _report_error(error_message: String, failure_line_number: int = -1) -> GdUn
func _has_log_entry(log_entries: Array[ErrorLogEntry], type: ErrorLogEntry.TYPE, error: Variant) -> bool:
for entry in log_entries:
if entry._type == type and GdObjects.equals(entry._message, error):
# Erase the log entry we already handled it by this assertion, otherwise it will report at twice
_error_monitor().erase_log_entry(entry)
return true
return false
@@ -55,12 +47,11 @@ func _has_log_entry(log_entries: Array[ErrorLogEntry], type: ErrorLogEntry.TYPE,
func _to_list(log_entries: Array[ErrorLogEntry]) -> String:
if log_entries.is_empty():
return "no errors"
if log_entries.size() == 1:
return log_entries[0]._message
var value := ""
var values := []
for entry in log_entries:
value += "'%s'\n" % entry._message
return value
values.append(entry)
return "\n".join(values)
func is_null() -> GdUnitGodotErrorAssert:
@@ -90,6 +81,9 @@ func append_failure_message(message: String) -> GdUnitGodotErrorAssert:
func is_success() -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var log_entries := await _execute()
if log_entries.is_empty():
return _report_success()
@@ -100,6 +94,9 @@ func is_success() -> GdUnitGodotErrorAssert:
func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error)
if result.is_error():
return _report_error(result.error_message())
@@ -108,12 +105,15 @@ func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
return _report_success()
return _report_error("""
Expecting: a runtime error is triggered.
message: '%s'
found: %s
expected: '%s'
current: '%s'
""".dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)])
func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_warning)
if result.is_error():
return _report_error(result.error_message())
@@ -122,12 +122,15 @@ func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert:
return _report_success()
return _report_error("""
Expecting: push_warning() is called.
message: '%s'
found: %s
expected: '%s'
current: '%s'
""".dedent().trim_prefix("\n") % [expected_warning, _to_list(log_entries)])
func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error)
if result.is_error():
return _report_error(result.error_message())
@@ -136,6 +139,14 @@ func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
return _report_success()
return _report_error("""
Expecting: push_error() is called.
message: '%s'
found: %s
expected: '%s'
current: '%s'
""".dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)])
func _validate_callable() -> bool:
if _callable == null or not _callable.is_valid():
@warning_ignore("return_value_discarded")
_report_error("Invalid Callable '%s'" % _callable)
return false
return true

View File

@@ -1 +1 @@
uid://cyi6ooahncq7q
uid://d3st26kcndm8l

View File

@@ -1 +1 @@
uid://j4mpmwm2hw61
uid://bcuh7lnvrg7mb

View File

@@ -1 +1 @@
uid://bm6qm58a0dacq
uid://ba515a8xk0ubo

View File

@@ -1 +1 @@
uid://b0dlq6jyjcvps
uid://dwl5ooagjg2nc

View File

@@ -91,7 +91,11 @@ func is_not_equal(_expected: Variant) -> GdUnitSignalAssert:
# Verifies the signal exists checked the emitter
func is_signal_exists(signal_name :String) -> GdUnitSignalAssert:
func is_signal_exists(signal_name: Variant) -> GdUnitSignalAssert:
if not (signal_name is String or signal_name is Signal):
return report_error("Invalid signal_name: expected String or Signal, but is '%s'" % type_string(typeof(signal_name)))
signal_name = (signal_name as Signal).get_name() if signal_name is Signal else signal_name
if not _emitter.has_signal(signal_name):
@warning_ignore("return_value_discarded")
report_error("The signal '%s' not exists checked object '%s'." % [signal_name, _emitter.get_class()])
@@ -99,20 +103,36 @@ func is_signal_exists(signal_name :String) -> GdUnitSignalAssert:
# Verifies that given signal is emitted until waiting time
func is_emitted(name :String, args := []) -> GdUnitSignalAssert:
func is_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert:
_line_number = GdUnitAssertions.get_line_number()
return await _wail_until_signal(name, args, false)
return await _wail_until_signal(
signal_name,
_wrap_arguments.callv(signal_args),
false)
# Verifies that given signal is NOT emitted until waiting time
func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert:
func is_not_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert:
_line_number = GdUnitAssertions.get_line_number()
return await _wail_until_signal(name, args, true)
return await _wail_until_signal(
signal_name,
_wrap_arguments.callv(signal_args),
true)
func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_emitted: bool) -> GdUnitSignalAssert:
func _wrap_arguments(...args: Array) -> Array:
# Check using old syntax
if not args.is_empty() and args[0] is Array:
return args[0]
return args
func _wail_until_signal(signal_value: Variant, expected_args: Array, expect_not_emitted: bool) -> GdUnitSignalAssert:
if _emitter == null:
return report_error("Can't wait for signal checked a NULL object.")
if not (signal_value is String or signal_value is Signal):
return report_error("Invalid signal_name: expected String or Signal, but is '%s'" % type_string(typeof(signal_value)))
var signal_name := (signal_value as Signal).get_name() if signal_value is Signal else signal_value
# first verify the signal is defined
if not _emitter.has_signal(signal_name):
return report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()])

View File

@@ -1 +1 @@
uid://dlh37yc086vr5
uid://c3ytdhnam8ba7

View File

@@ -1 +1 @@
uid://dxqvilchqqeta
uid://cw5h1gecccd37

View File

@@ -1 +1 @@
uid://r4avfcakvscw
uid://pyvt1oi43853

View File

@@ -1 +1 @@
uid://8y15b6ts3kss
uid://beueufp3wgjw8

View File

@@ -1 +1 @@
uid://d4hd3vc50jltg
uid://34eyejb3x15x

View File

@@ -1 +1 @@
uid://w4mr1j0k0l
uid://crpirtbbekydy

View File

@@ -1 +1 @@
uid://ccm3ivfiaf3i7
uid://dbbsj4qqi8w3m

View File

@@ -1 +1 @@
uid://ccnb2ah35atho
uid://cpokylwutgxw6

View File

@@ -1 +1 @@
uid://0p8udx4tdwol
uid://swuykw4qh2xx

View File

@@ -1 +1 @@
uid://bk60ywsj4ekp7
uid://etq75dot1c03

View File

@@ -1 +1 @@
uid://b5sli0lem5xca
uid://cx8bp6vsxyrts

View File

@@ -1 +1 @@
uid://b7ldhc4ryfh1v
uid://dk4tv55wf5mlm

View File

@@ -1 +1 @@
uid://bbaqjhpbxce3u
uid://ds3n71e8n4wce

View File

@@ -196,7 +196,7 @@ static func resource_as_string(resource_path :String) -> String:
if file == null:
push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())])
return ""
return file.get_as_text(true)
return file.get_as_text()
static func make_qualified_path(path :String) -> String:

View File

@@ -1 +1 @@
uid://dflqb5germp5n
uid://ddr26lg6f055n

View File

@@ -1 +1 @@
uid://cqndh0nuu8ltx
uid://c0ymnlx3jooyl

View File

@@ -1 +1 @@
uid://cnvq3nb61ei76
uid://b6k1ufvw6w2s5

View File

@@ -18,7 +18,10 @@ var _config := {
TESTS : Array([], TYPE_OBJECT, "RefCounted", GdUnitTestCase),
# the port of running test server for this session
SERVER_PORT : -1
SERVER_PORT : -1,
# Exit on first failure
EXIT_FAIL_FAST : false
}
@@ -40,6 +43,15 @@ func server_port() -> int:
return _config.get(SERVER_PORT, -1)
func do_fail_fast(is_fail_fast: bool) -> GdUnitRunnerConfig:
_config[EXIT_FAIL_FAST] = is_fail_fast
return self
func is_fail_fast() -> bool:
return _config.get(EXIT_FAIL_FAST, false)
func add_test_cases(tests: Array[GdUnitTestCase]) -> GdUnitRunnerConfig:
test_cases().append_array(tests)
return self
@@ -57,7 +69,8 @@ func save_config(path: String = CONFIG_FILE) -> GdUnitResult:
var to_save := {
VERSION : CONFIG_VERSION,
SERVER_PORT : _config.get(SERVER_PORT),
EXIT_FAIL_FAST : is_fail_fast(),
SERVER_PORT : server_port(),
TESTS : Array()
}

View File

@@ -1 +1 @@
uid://ltvpkh3ayklf
uid://dvrxtyni1604f

View File

@@ -72,12 +72,11 @@ func _init(p_scene: Variant, p_verbose: bool, p_hide_push_errors := false) -> vo
return
_scene_tree().root.add_child(_current_scene)
Engine.set_meta("GdUnitSceneRunner", self)
# do finally reset all open input events when the scene is removed
@warning_ignore("return_value_discarded")
_scene_tree().root.child_exiting_tree.connect(func f(child :Node) -> void:
_scene_tree().root.child_exiting_tree.connect(func f(child: Node) -> void:
if child == _current_scene:
# we need to disable the processing to avoid input flush buffer errors
_current_scene.process_mode = Node.PROCESS_MODE_DISABLED
_reset_input_to_default()
)
_simulate_start_time = LocalTime.now()
@@ -103,6 +102,7 @@ func _notification(what: int) -> void:
_current_scene.free()
_is_disposed = true
_current_scene = null
Engine.remove_meta("GdUnitSceneRunner")
func _scene_tree() -> SceneTree:
@@ -145,6 +145,7 @@ func simulate_action_release(action: String, event_index := -1) -> GdUnitSceneRu
@warning_ignore("return_value_discarded")
func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
_push_warning_deprecated_arguments(shift_pressed, ctrl_pressed)
simulate_key_press(key_code, shift_pressed, ctrl_pressed)
await _scene_tree().process_frame
simulate_key_release(key_code, shift_pressed, ctrl_pressed)
@@ -152,30 +153,33 @@ func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed :=
func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
_push_warning_deprecated_arguments(shift_pressed, ctrl_pressed)
__print_current_focus()
var event := InputEventKey.new()
event.pressed = true
event.keycode = key_code as Key
event.physical_keycode = key_code as Key
event.unicode = key_code
event.alt_pressed = key_code == KEY_ALT
event.shift_pressed = shift_pressed or key_code == KEY_SHIFT
event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL
event.set_alt_pressed(key_code == KEY_ALT)
event.set_shift_pressed(shift_pressed)
event.set_ctrl_pressed(ctrl_pressed)
event.get_modifiers_mask()
_apply_input_modifiers(event)
_key_on_press.append(key_code)
return _handle_input_event(event)
func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
_push_warning_deprecated_arguments(shift_pressed, ctrl_pressed)
__print_current_focus()
var event := InputEventKey.new()
event.pressed = false
event.keycode = key_code as Key
event.physical_keycode = key_code as Key
event.unicode = key_code
event.alt_pressed = key_code == KEY_ALT
event.shift_pressed = shift_pressed or key_code == KEY_SHIFT
event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL
event.set_alt_pressed(key_code == KEY_ALT)
event.set_shift_pressed(shift_pressed)
event.set_ctrl_pressed(ctrl_pressed)
_apply_input_modifiers(event)
_key_on_press.erase(key_code)
return _handle_input_event(event)
@@ -485,6 +489,8 @@ func find_child(name: String, recursive: bool = true, owned: bool = false) -> No
func _scene_name() -> String:
if scene() == null:
return "unknown"
var scene_script :GDScript = scene().get_script()
var scene_name :String = scene().get_name()
if not scene_script:
@@ -515,6 +521,13 @@ func _apply_input_modifiers(event: InputEvent) -> void:
_event.ctrl_pressed = _event.ctrl_pressed or last_input_event.ctrl_pressed
# this line results into reset the control_pressed state!!!
#event.command_or_control_autoremap = event.command_or_control_autoremap or _last_input_event.command_or_control_autoremap
if _last_input_event is InputEventKey and event is InputEventWithModifiers:
var last_input_event := _last_input_event as InputEventKey
var _event := event as InputEventWithModifiers
_event.shift_pressed = _event.shift_pressed or last_input_event.keycode == KEY_SHIFT
_event.alt_pressed = _event.alt_pressed or last_input_event.keycode == KEY_ALT
_event.ctrl_pressed = _event.ctrl_pressed or last_input_event.keycode == KEY_CTRL
_event.meta_pressed = _event.meta_pressed or last_input_event.keycode == KEY_META
# copy over current active mouse mask and combine with curren mask
@@ -620,3 +633,10 @@ func scene() -> Node:
if not _is_disposed:
push_error("The current scene instance is not valid anymore! check your test is valid. e.g. check for missing awaits.")
return null
func _push_warning_deprecated_arguments(shift_pressed: bool, ctrl_pressed: bool) -> void:
if shift_pressed:
push_warning("Deprecated! Don't use 'shift_pressed' it will be removed in v7.0, checkout the documentaion how to use key combinations.")
if ctrl_pressed:
push_warning("Deprecated! Don't use 'ctrl_pressed' it will be removed in v7.0, checkout the documentaion how to use key combinations.")

View File

@@ -1 +1 @@
uid://7a566a4kfreu
uid://bdhmqovuioydb

View File

@@ -21,6 +21,7 @@ const TEST_SUITE_NAMING_CONVENTION = GROUP_TEST + "/test_suite_naming_convention
const TEST_DISCOVER_ENABLED = GROUP_TEST + "/test_discovery"
const TEST_FLAKY_CHECK = GROUP_TEST + "/flaky_check_enable"
const TEST_FLAKY_MAX_RETRIES = GROUP_TEST + "/flaky_max_retries"
const TEST_RERUN_UNTIL_FAILURE_RETRIES = GROUP_TEST + "/rerun_until_failure_retries"
# Report Setiings
@@ -62,6 +63,7 @@ const SHORTCUT_INSPECTOR_RERUN_TEST = GROUP_SHORTCUT_INSPECTOR + "/rerun_test"
const SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG = GROUP_SHORTCUT_INSPECTOR + "/rerun_test_debug"
const SHORTCUT_INSPECTOR_RUN_TEST_OVERALL = GROUP_SHORTCUT_INSPECTOR + "/run_test_overall"
const SHORTCUT_INSPECTOR_RUN_TEST_STOP = GROUP_SHORTCUT_INSPECTOR + "/run_test_stop"
const SHORTCUT_INSPECTOR_RERUN_TEST_UNTIL_FAILURE = GROUP_SHORTCUT_INSPECTOR + "/rerun_test_until_failure"
const GROUP_SHORTCUT_EDITOR = SHORTCUT_SETTINGS + "/editor"
const SHORTCUT_EDITOR_RUN_TEST = GROUP_SHORTCUT_EDITOR + "/run_test"
@@ -112,6 +114,7 @@ static func setup() -> void:
create_property_if_need(TEST_DISCOVER_ENABLED, false, "Automatically detect new tests in test lookup folders at runtime")
create_property_if_need(TEST_FLAKY_CHECK, false, "Rerun tests on failure and mark them as FLAKY")
create_property_if_need(TEST_FLAKY_MAX_RETRIES, 3, "Sets the number of retries for rerunning a flaky test")
create_property_if_need(TEST_RERUN_UNTIL_FAILURE_RETRIES, 10, "The number of reruns until the test fails.")
# report settings
create_property_if_need(REPORT_PUSH_ERRORS, false, "Report push_error() as failure")
create_property_if_need(REPORT_SCRIPT_ERRORS, true, "Report script errors as failure")
@@ -148,6 +151,7 @@ static func create_shortcut_properties_if_need() -> void:
# inspector
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS), "Rerun the most recently executed tests")
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG), "Rerun the most recently executed tests (Debug mode)")
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_UNTIL_FAILURE, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_UNTIL_FAILURE), "Rerun tests until failure occurs")
create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_OVERALL, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL), "Runs all tests (Debug mode)")
create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stop the current test execution")
# script editor
@@ -155,8 +159,8 @@ static func create_shortcut_properties_if_need() -> void:
create_property_if_need(SHORTCUT_EDITOR_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG), "Run the currently selected test (Debug mode).")
create_property_if_need(SHORTCUT_EDITOR_CREATE_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.CREATE_TEST), "Create a new test case for the currently selected function")
# filesystem
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Run all test suites in the selected folder or file")
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Run all test suites in the selected folder or file (Debug)")
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTSUITE), "Run all test suites in the selected folder or file")
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTSUITE_DEBUG), "Run all test suites in the selected folder or file (Debug)")
static func create_property_if_need(name :String, default :Variant, help :="", value_set := PackedStringArray()) -> void:
@@ -306,6 +310,10 @@ static func get_flaky_max_retries() -> int:
return get_setting(TEST_FLAKY_MAX_RETRIES, 3)
static func get_rerun_max_retries() -> int:
return get_setting(TEST_RERUN_UNTIL_FAILURE_RETRIES, 10)
static func set_test_discover_enabled(enable :bool) -> void:
var property := get_property(TEST_DISCOVER_ENABLED)
property.set_value(enable)

View File

@@ -1 +1 @@
uid://coby4unvmd3eh
uid://djrx6fy3w3bb

View File

@@ -1 +1 @@
uid://ckx5jnr3ip6vp
uid://l1nsecnjoon6

View File

@@ -107,7 +107,7 @@ func is_signal_collecting(emitter: Object, signal_name: String) -> bool:
return _collected_signals.has(emitter) and (_collected_signals[emitter] as Dictionary).has(signal_name)
func match(emitter :Object, signal_name :String, args :Array) -> bool:
func match(emitter: Object, signal_name: String, args: Array) -> bool:
#prints("match", signal_name, _collected_signals[emitter][signal_name]);
if _collected_signals.is_empty() or not _collected_signals.has(emitter):
return false

View File

@@ -1 +1 @@
uid://cm0rbs8vhdhd1
uid://bnvdsssykfaeh

View File

@@ -33,9 +33,9 @@ signal gdunit_client_connected(client_id: int)
signal gdunit_client_disconnected(client_id: int)
## Emitted when a client terminates unexpectedly.
## Emitted when a the user stops (terminates) the current test session
@warning_ignore("unused_signal")
signal gdunit_client_terminated()
signal gdunit_test_session_terminate()
## Emitted when a test execution event occurs.[br]

View File

@@ -1 +1 @@
uid://kj16fg0hf6kn
uid://7fkqtqq0ib25

View File

@@ -1 +1 @@
uid://4sujouo3vf6d
uid://djyr7is32ffbs

View File

@@ -1 +1 @@
uid://ierjyaem56m3
uid://cg0fqsmpf8fdh

View File

@@ -1 +1 @@
uid://dthfh16tl5wqc
uid://cqe6i2xbwgneb

View File

@@ -70,8 +70,14 @@ func scan_directory(resource_path: String) -> Array[Script]:
func _scan_test_suites_scripts(dir: DirAccess, collected_suites: Array[Script]) -> Array[Script]:
# Skip excluded directories
if dir.file_exists(".gdignore"):
prints("Exclude directory %s, containing .gdignore file" % dir.get_current_dir())
return []
if exclude_scan_directories.has(dir.get_current_dir()):
return collected_suites
var err := dir.list_dir_begin()
if err != OK:
push_error("Error on scanning directory %s" % dir.get_current_dir(), error_string(err))

View File

@@ -1 +1 @@
uid://bju0nt1bgsc2s
uid://bymtxj63ek2kd

View File

@@ -1 +1 @@
uid://d05qgv6uu477i
uid://4tbcywx0qg1d

View File

@@ -1 +1 @@
uid://dehxycxsj68ev
uid://d0d4s6tkgoh3b

View File

@@ -1 +1 @@
uid://dmta1h7ndfnko
uid://dd7g37aslbmm1

View File

@@ -9,7 +9,8 @@ var _attribute: TestCaseAttribute
var _current_iteration: int = -1
var _expect_to_interupt := false
var _timer: Timer
var _interupted: bool = false
var _interupted := false
var _terminated := false
var _failed := false
var _parameter_set_resolver: GdUnitTestParameterSetResolver
var _is_disposed := false
@@ -123,7 +124,7 @@ func do_interrupt() -> void:
# We need to dispose manually the function state here
GdObjects.dispose_function_state(_func_state)
if not is_expect_interupted():
var execution_context:= GdUnitThreadManager.get_current_context().get_execution_context()
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
if is_fuzzed():
execution_context.add_report(GdUnitReport.new()\
.create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout")))
@@ -133,6 +134,16 @@ func do_interrupt() -> void:
completed.emit()
func do_terminate() -> void:
_terminated = true
# We need to dispose manually the function state here
GdObjects.dispose_function_state(_func_state)
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
execution_context.add_report(GdUnitReport.new()\
.create(GdUnitReport.TERMINATED, line_number(), GdAssertMessages.test_session_terminated()))
completed.emit()
func _set_failure_handler() -> void:
if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received):
@warning_ignore("return_value_discarded")
@@ -172,6 +183,10 @@ func is_expect_interupted() -> bool:
return _expect_to_interupt
func is_terminated() -> bool:
return _terminated
func is_parameterized() -> bool:
return _parameter_set_resolver.is_parameterized()
@@ -192,11 +207,6 @@ func test_name() -> String:
return _test_case.test_name
@warning_ignore("native_method_override")
func get_name() -> StringName:
return _test_case.test_name
func line_number() -> int:
return _test_case.line_number

View File

@@ -1 +1 @@
uid://cb2lkpvh0liiv
uid://dvhr6i0bdk05n

View File

@@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://csgvrbao53xmv"
uid="uid://c0lvcprd6501t"
path="res://.godot/imported/touch-button.png-2fff40c8520d8e97a57db1b2b043f641.ctex"
metadata={
"vram_texture": false

View File

@@ -1 +1 @@
uid://d2bres53mgxnw
uid://b3ilfkx1js423

View File

@@ -0,0 +1,64 @@
@abstract class_name GdUnitBaseCommand
extends Node
var id: String
var icon: Texture2D
var shortcut: Shortcut = null
var shortcut_type: GdUnitShortcut.ShortCut
func _init(p_id: String, p_shortcut: GdUnitShortcut.ShortCut = GdUnitShortcut.ShortCut.NONE) -> void:
id = p_id
shortcut_type = p_shortcut
_set_shortcut()
func _shortcut_input(event: InputEvent) -> void:
if is_running():
return
if shortcut and shortcut.matches_event(event):
execute()
get_viewport().set_input_as_handled()
func update_shortcut() -> void:
_set_shortcut()
func _set_shortcut() -> void:
if shortcut_type == GdUnitShortcut.ShortCut.NONE:
return
var property_name := GdUnitShortcut.as_property(shortcut_type)
var property := GdUnitSettings.get_property(property_name)
var keys := GdUnitShortcut.default_keys(shortcut_type)
if property != null:
keys = property.value()
var inputEvent := _create_shortcut_input_even(keys)
shortcut = Shortcut.new()
shortcut.set_events([inputEvent])
func _create_shortcut_input_even(key_codes: PackedInt32Array) -> InputEventKey:
var inputEvent := InputEventKey.new()
inputEvent.pressed = true
for key_code in key_codes:
match key_code:
KEY_ALT:
inputEvent.alt_pressed = true
KEY_SHIFT:
inputEvent.shift_pressed = true
KEY_CTRL:
inputEvent.ctrl_pressed = true
_:
inputEvent.keycode = key_code as Key
inputEvent.physical_keycode = key_code as Key
return inputEvent
@abstract func is_running() -> bool
@abstract func execute(...parameters: Array) -> void

View File

@@ -0,0 +1 @@
uid://bxuturao0ahb5

Some files were not shown because too many files have changed in this diff Show More