├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package-lock.json ├── run ├── bukkit.yml ├── config │ ├── paper-global.yml │ └── paper-world-defaults.yml ├── eula.txt ├── gameteststructures │ ├── test.test.snbt │ └── test.test2.snbt ├── plugins │ └── MiniTestFramework │ │ └── test.js ├── server.properties └── test-results.xml ├── settings.gradle └── src ├── main └── java │ └── dev │ └── benndorf │ └── minitestframework │ ├── MiniTestCommand.java │ ├── MiniTestFramework.java │ ├── MiniTestFrameworkBootstrap.java │ ├── ci │ └── TestReporter.java │ ├── js │ └── GraalUtil.java │ └── ts │ ├── TSGenerator.java │ └── TSGeneratorRunner.java └── test └── java └── dev └── benndorf └── minitestframework └── TSGeneratorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .gradle 4 | build 5 | # this is stupid 6 | run/cache 7 | run/crash-reports 8 | run/logs 9 | run/plugins/bStats 10 | run/plugins/MiniTestFramework/minitestframework 11 | run/world 12 | run/world_nether 13 | run/world_the_end 14 | run/banned-ips.json 15 | run/banned-players.json 16 | run/commands.yml 17 | run/help.yml 18 | run/ops.json 19 | run/permissions.yml 20 | run/spigot.yml 21 | run/usercache.json 22 | run/version_history.json 23 | run/whitelist.json 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 MiniDigger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniTestFramework 2 | 3 | Integration Test Framework for Paper! 4 | 5 | ## Usage 6 | 7 | * Install plugin 8 | * Create Test Structure 9 | * `/test create [width]` 10 | * Build contraption 11 | * Save schematic 12 | * Create Test Script 13 | * create .js in plugin folder 14 | * register a new test case into the registry 15 | ```js 16 | import {registry, EntityType} from "./minitestframework/index.mjs"; 17 | registry.register("test", (helper) => { 18 | helper.pressButton(3, 3, 3); 19 | helper.succeedWhenEntityPresent(EntityType.MINECART, 1, 2, 3); 20 | }); 21 | ``` 22 | * use helper to do actions and assertions (basic auto complete should be provided by the auto generated definition files) 23 | * use `/test pos` ingame to find relation locations 24 | * Reload script changes with `/minitest reload` 25 | * Run test via command block ingame or `/test run*` commands 26 | 27 | Example: https://streamable.com/e/k6kngh 28 | 29 | ## Running in CI 30 | 31 | When the plugin detects that the CI env var is set to true, it will automatically run all tests, write a test-results.xml (in junit format) and stop the server. 32 | If exit code > 0, then X number of required tests failed. If exit code < 0, then X number of optional test failed. Exit code = 0 means happy day :) 33 | 34 | ## Contribution 35 | 36 | Best to hit me up on the paper discord: https://discord.gg/papermc 37 | `gradlew runServer` to test locally. 38 | 39 | ## Licence 40 | 41 | MIT 42 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import net.minecrell.pluginyml.bukkit.BukkitPluginDescription 2 | 3 | plugins { 4 | `java-library` 5 | id("io.papermc.paperweight.userdev") version "1.5.5" 6 | id("xyz.jpenilla.run-paper") version "2.1.0" 7 | id("net.minecrell.plugin-yml.paper") version "0.6.0" 8 | id("com.github.johnrengelman.shadow") version "8.1.1" 9 | } 10 | 11 | val javaVersion = 17 12 | 13 | dependencies { 14 | paperweight.paperDevBundle("1.20.1-R0.1-SNAPSHOT") 15 | 16 | val graalVersion = "23.0.1" 17 | implementation("org.graalvm.sdk:graal-sdk:$graalVersion") 18 | implementation("org.graalvm.js:js:$graalVersion") 19 | implementation("org.graalvm.truffle:truffle-api:$graalVersion") 20 | 21 | testImplementation("org.assertj:assertj-core:3.24.2") 22 | val junitVersion = "5.10.0" 23 | testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") 24 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") 25 | } 26 | 27 | repositories { 28 | mavenCentral() 29 | maven("https://repo.papermc.io/repository/maven-public/") 30 | maven("https://oss.sonatype.org/content/groups/public/") 31 | } 32 | 33 | tasks { 34 | // Run reobfJar on build 35 | build { 36 | dependsOn(reobfJar) 37 | } 38 | compileJava { 39 | options.encoding = Charsets.UTF_8.name() 40 | options.release.set(javaVersion) 41 | options.compilerArgs.add("-parameters") 42 | } 43 | processResources { 44 | filteringCharset = Charsets.UTF_8.name() 45 | } 46 | shadowJar { 47 | mergeServiceFiles() 48 | } 49 | test { 50 | useJUnitPlatform() 51 | } 52 | runServer { 53 | jvmArgs("-Dnet.kyori.adventure.text.warnWhenLegacyFormattingDetected=false") 54 | } 55 | runMojangMappedServer { 56 | jvmArgs("-Dnet.kyori.adventure.text.warnWhenLegacyFormattingDetected=false") 57 | } 58 | } 59 | 60 | paper { 61 | load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD 62 | main = "dev.benndorf.minitestframework.MiniTestFramework" 63 | bootstrapper = "dev.benndorf.minitestframework.MiniTestFrameworkBootstrap" 64 | apiVersion = "1.20" 65 | authors = listOf("MiniDigger") 66 | } 67 | 68 | java { 69 | toolchain.languageVersion.set(JavaLanguageVersion.of(javaVersion)) 70 | } 71 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | javaVersion=17 2 | version=1.0.0-SNAPSHOT 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniDigger/MiniTestFramework/dca2195e1187e02299e65a4c1e476d0bcb697ee7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command; 206 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 207 | # shell script including quotes and variable substitutions, so put them in 208 | # double quotes to make sure that they get re-expanded; and 209 | # * put everything else in single quotes, so that it's not re-expanded. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PaperGameTest", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "cjson": { 8 | "version": "0.5.0", 9 | "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.5.0.tgz", 10 | "integrity": "sha1-oPSGAeAWFk37LG2JHjgMlsramDk=", 11 | "requires": { 12 | "json-parse-helpfulerror": "^1.0.3" 13 | } 14 | }, 15 | "helper": { 16 | "version": "0.0.13", 17 | "resolved": "https://registry.npmjs.org/helper/-/helper-0.0.13.tgz", 18 | "integrity": "sha1-9K4I2iCoSTf6mfJLB9A8ieh0csA=", 19 | "requires": { 20 | "cjson": "*" 21 | } 22 | }, 23 | "jju": { 24 | "version": "1.4.0", 25 | "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", 26 | "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=" 27 | }, 28 | "json-parse-helpfulerror": { 29 | "version": "1.0.3", 30 | "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", 31 | "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", 32 | "requires": { 33 | "jju": "^1.1.0" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /run/bukkit.yml: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for Bukkit. 2 | # As you can see, there's actually not that much to configure without any plugins. 3 | # For a reference for any variable inside this file, check out the Bukkit Wiki at 4 | # https://www.spigotmc.org/go/bukkit-yml 5 | # 6 | # If you need help on this file, feel free to join us on irc or leave a message 7 | # on the forums asking for advice. 8 | # 9 | # IRC: #spigot @ irc.spi.gt 10 | # (If this means nothing to you, just go to https://www.spigotmc.org/go/irc ) 11 | # Forums: https://www.spigotmc.org/ 12 | # Bug tracker: https://www.spigotmc.org/go/bugs 13 | 14 | 15 | settings: 16 | use-map-color-cache: true 17 | allow-end: false 18 | warn-on-overload: true 19 | permissions-file: permissions.yml 20 | update-folder: update 21 | plugin-profiling: false 22 | connection-throttle: 4000 23 | query-plugins: true 24 | deprecated-verbose: default 25 | shutdown-message: Server closed 26 | minimum-api: none 27 | spawn-limits: 28 | axolotls: 5 29 | monsters: 70 30 | animals: 10 31 | water-animals: 5 32 | water-ambient: 20 33 | water-underground-creature: 5 34 | ambient: 15 35 | chunk-gc: 36 | period-in-ticks: 600 37 | ticks-per: 38 | axolotl-spawns: 1 39 | animal-spawns: 400 40 | monster-spawns: 1 41 | water-spawns: 1 42 | water-ambient-spawns: 1 43 | water-underground-creature-spawns: 1 44 | ambient-spawns: 1 45 | autosave: 6000 46 | aliases: now-in-commands.yml 47 | -------------------------------------------------------------------------------- /run/config/paper-global.yml: -------------------------------------------------------------------------------- 1 | # This is the global configuration file for Paper. 2 | # As you can see, there's a lot to configure. Some options may impact gameplay, so use 3 | # with caution, and make sure you know what each option does before configuring. 4 | # 5 | # If you need help with the configuration or have any questions related to Paper, 6 | # join us in our Discord or check the docs page. 7 | # 8 | # The world configuration options have been moved inside 9 | # their respective world folder. The files are named paper-world.yml 10 | # 11 | # Docs: https://docs.papermc.io/ 12 | # Discord: https://discord.gg/papermc 13 | # Website: https://papermc.io/ 14 | 15 | _version: 28 16 | block-updates: 17 | disable-noteblock-updates: false 18 | disable-tripwire-updates: false 19 | chunk-loading: 20 | autoconfig-send-distance: true 21 | enable-frustum-priority: false 22 | global-max-chunk-load-rate: -1.0 23 | global-max-chunk-send-rate: -1.0 24 | global-max-concurrent-loads: 500.0 25 | max-concurrent-sends: 2 26 | min-load-radius: 2 27 | player-max-concurrent-loads: 20.0 28 | target-player-chunk-send-rate: 100.0 29 | chunk-loading-advanced: 30 | auto-config-send-distance: true 31 | player-max-concurrent-chunk-generates: 0 32 | player-max-concurrent-chunk-loads: 0 33 | chunk-loading-basic: 34 | player-max-chunk-generate-rate: -1.0 35 | player-max-chunk-load-rate: 100.0 36 | player-max-chunk-send-rate: 75.0 37 | chunk-system: 38 | gen-parallelism: default 39 | io-threads: -1 40 | worker-threads: -1 41 | collisions: 42 | enable-player-collisions: true 43 | send-full-pos-for-hard-colliding-entities: true 44 | commands: 45 | fix-target-selector-tag-completion: true 46 | suggest-player-names-when-null-tab-completions: true 47 | time-command-affects-all-worlds: false 48 | console: 49 | enable-brigadier-completions: true 50 | enable-brigadier-highlighting: true 51 | has-all-permissions: false 52 | item-validation: 53 | book: 54 | author: 8192 55 | page: 16384 56 | title: 8192 57 | book-size: 58 | page-max: 2560 59 | total-multiplier: 0.98 60 | display-name: 8192 61 | lore-line: 8192 62 | resolve-selectors-in-books: false 63 | logging: 64 | deobfuscate-stacktraces: true 65 | log-player-ip-addresses: true 66 | messages: 67 | kick: 68 | authentication-servers-down: 69 | connection-throttle: Connection throttled! Please wait before reconnecting. 70 | flying-player: 71 | flying-vehicle: 72 | no-permission: I'm sorry, but you do not have permission to perform this command. 73 | Please contact the server administrators if you believe that this is in error. 74 | use-display-name-in-quit-message: false 75 | misc: 76 | chat-threads: 77 | chat-executor-core-size: -1 78 | chat-executor-max-size: -1 79 | fix-entity-position-desync: true 80 | lag-compensate-block-breaking: true 81 | load-permissions-yml-before-plugins: true 82 | max-joins-per-tick: 3 83 | region-file-cache-size: 256 84 | strict-advancement-dimension-check: false 85 | use-alternative-luck-formula: false 86 | use-dimension-type-for-custom-spawners: false 87 | packet-limiter: 88 | all-packets: 89 | action: KICK 90 | interval: 7.0 91 | max-packet-rate: 500.0 92 | kick-message: 93 | overrides: 94 | ServerboundPlaceRecipePacket: 95 | action: DROP 96 | interval: 4.0 97 | max-packet-rate: 5.0 98 | player-auto-save: 99 | max-per-tick: -1 100 | rate: -1 101 | proxies: 102 | bungee-cord: 103 | online-mode: true 104 | proxy-protocol: false 105 | velocity: 106 | enabled: false 107 | online-mode: false 108 | secret: '' 109 | scoreboards: 110 | save-empty-scoreboard-teams: false 111 | track-plugin-scoreboards: false 112 | spam-limiter: 113 | incoming-packet-threshold: 300 114 | recipe-spam-increment: 1 115 | recipe-spam-limit: 20 116 | tab-spam-increment: 1 117 | tab-spam-limit: 500 118 | timings: 119 | enabled: false 120 | hidden-config-entries: 121 | - database 122 | - settings.bungeecord-addresses 123 | - settings.velocity-support.secret 124 | - proxies.velocity.secret 125 | history-interval: 300 126 | history-length: 3600 127 | server-name: Unknown Server 128 | server-name-privacy: false 129 | url: https://timings.aikar.co/ 130 | verbose: true 131 | unsupported-settings: 132 | allow-grindstone-overstacking: false 133 | allow-headless-pistons: false 134 | allow-permanent-block-break-exploits: false 135 | allow-piston-duplication: false 136 | compression-format: ZLIB 137 | perform-username-validation: true 138 | watchdog: 139 | early-warning-delay: 10000 140 | early-warning-every: 5000 141 | -------------------------------------------------------------------------------- /run/config/paper-world-defaults.yml: -------------------------------------------------------------------------------- 1 | # This is the world defaults configuration file for Paper. 2 | # As you can see, there's a lot to configure. Some options may impact gameplay, so use 3 | # with caution, and make sure you know what each option does before configuring. 4 | # 5 | # If you need help with the configuration or have any questions related to Paper, 6 | # join us in our Discord or check the docs page. 7 | # 8 | # Configuration options here apply to all worlds, unless you specify overrides inside 9 | # the world-specific config file inside each world folder. 10 | # 11 | # Docs: https://docs.papermc.io/ 12 | # Discord: https://discord.gg/papermc 13 | # Website: https://papermc.io/ 14 | 15 | _version: 30 16 | anticheat: 17 | anti-xray: 18 | enabled: false 19 | engine-mode: 1 20 | hidden-blocks: 21 | - copper_ore 22 | - deepslate_copper_ore 23 | - gold_ore 24 | - deepslate_gold_ore 25 | - iron_ore 26 | - deepslate_iron_ore 27 | - coal_ore 28 | - deepslate_coal_ore 29 | - lapis_ore 30 | - deepslate_lapis_ore 31 | - mossy_cobblestone 32 | - obsidian 33 | - chest 34 | - diamond_ore 35 | - deepslate_diamond_ore 36 | - redstone_ore 37 | - deepslate_redstone_ore 38 | - clay 39 | - emerald_ore 40 | - deepslate_emerald_ore 41 | - ender_chest 42 | lava-obscures: false 43 | max-block-height: 64 44 | replacement-blocks: 45 | - stone 46 | - oak_planks 47 | - deepslate 48 | update-radius: 2 49 | use-permission: false 50 | obfuscation: 51 | items: 52 | hide-durability: false 53 | hide-itemmeta: false 54 | hide-itemmeta-with-visual-effects: false 55 | chunks: 56 | auto-save-interval: default 57 | delay-chunk-unloads-by: 10s 58 | entity-per-chunk-save-limit: 59 | arrow: -1 60 | ender_pearl: -1 61 | experience_orb: -1 62 | fireball: -1 63 | small_fireball: -1 64 | snowball: -1 65 | fixed-chunk-inhabited-time: -1 66 | flush-regions-on-save: false 67 | max-auto-save-chunks-per-tick: 24 68 | prevent-moving-into-unloaded-chunks: false 69 | collisions: 70 | allow-player-cramming-damage: false 71 | allow-vehicle-collisions: true 72 | fix-climbing-bypassing-cramming-rule: false 73 | max-entity-collisions: 8 74 | only-players-collide: false 75 | entities: 76 | armor-stands: 77 | do-collision-entity-lookups: true 78 | tick: true 79 | behavior: 80 | allow-spider-world-border-climbing: true 81 | baby-zombie-movement-modifier: 0.5 82 | disable-chest-cat-detection: false 83 | disable-creeper-lingering-effect: false 84 | disable-player-crits: false 85 | door-breaking-difficulty: 86 | husk: 87 | - HARD 88 | vindicator: 89 | - NORMAL 90 | - HARD 91 | zombie: 92 | - HARD 93 | zombie_villager: 94 | - HARD 95 | zombified_piglin: 96 | - HARD 97 | ender-dragons-death-always-places-dragon-egg: false 98 | experience-merge-max-value: -1 99 | mobs-can-always-pick-up-loot: 100 | skeletons: false 101 | zombies: false 102 | nerf-pigmen-from-nether-portals: false 103 | parrots-are-unaffected-by-player-movement: false 104 | phantoms-do-not-spawn-on-creative-players: true 105 | phantoms-only-attack-insomniacs: true 106 | phantoms-spawn-attempt-max-seconds: 119 107 | phantoms-spawn-attempt-min-seconds: 60 108 | piglins-guard-chests: true 109 | pillager-patrols: 110 | disable: false 111 | spawn-chance: 0.2 112 | spawn-delay: 113 | per-player: false 114 | ticks: 12000 115 | start: 116 | day: 5 117 | per-player: false 118 | player-insomnia-start-ticks: 72000 119 | should-remove-dragon: false 120 | spawner-nerfed-mobs-should-jump: false 121 | zombie-villager-infection-chance: -1.0 122 | zombies-target-turtle-eggs: true 123 | entities-target-with-follow-range: false 124 | markers: 125 | tick: true 126 | mob-effects: 127 | immune-to-wither-effect: 128 | wither: true 129 | wither-skeleton: true 130 | spiders-immune-to-poison-effect: true 131 | undead-immune-to-certain-effects: true 132 | spawning: 133 | all-chunks-are-slime-chunks: false 134 | alt-item-despawn-rate: 135 | enabled: false 136 | items: 137 | cobblestone: 300 138 | count-all-mobs-for-spawning: false 139 | creative-arrow-despawn-rate: default 140 | despawn-ranges: 141 | ambient: 142 | hard: 128 143 | soft: 32 144 | axolotls: 145 | hard: 128 146 | soft: 32 147 | creature: 148 | hard: 128 149 | soft: 32 150 | misc: 151 | hard: 128 152 | soft: 32 153 | monster: 154 | hard: 128 155 | soft: 32 156 | underground_water_creature: 157 | hard: 128 158 | soft: 32 159 | water_ambient: 160 | hard: 64 161 | soft: 32 162 | water_creature: 163 | hard: 128 164 | soft: 32 165 | disable-mob-spawner-spawn-egg-transformation: false 166 | duplicate-uuid: 167 | mode: SAFE_REGEN 168 | safe-regen-delete-range: 32 169 | filter-bad-tile-entity-nbt-from-falling-blocks: true 170 | filtered-entity-tag-nbt-paths: 171 | - Pos 172 | - Motion 173 | - SleepingX 174 | - SleepingY 175 | - SleepingZ 176 | iron-golems-can-spawn-in-air: false 177 | monster-spawn-max-light-level: -1 178 | non-player-arrow-despawn-rate: default 179 | per-player-mob-spawns: true 180 | scan-for-legacy-ender-dragon: true 181 | skeleton-horse-thunder-spawn-chance: 0.01 182 | slime-spawn-height: 183 | slime-chunk: 184 | maximum: 40.0 185 | surface-biome: 186 | maximum: 70.0 187 | minimum: 50.0 188 | spawn-limits: 189 | ambient: -1 190 | axolotls: -1 191 | creature: -1 192 | monster: -1 193 | underground_water_creature: -1 194 | water_ambient: -1 195 | water_creature: -1 196 | wandering-trader: 197 | spawn-chance-failure-increment: 25 198 | spawn-chance-max: 75 199 | spawn-chance-min: 25 200 | spawn-day-length: 24000 201 | spawn-minute-length: 1200 202 | wateranimal-spawn-height: 203 | maximum: default 204 | minimum: default 205 | environment: 206 | disable-explosion-knockback: false 207 | disable-ice-and-snow: false 208 | disable-teleportation-suffocation-check: false 209 | disable-thunder: false 210 | fire-tick-delay: 30 211 | frosted-ice: 212 | delay: 213 | max: 40 214 | min: 20 215 | enabled: true 216 | generate-flat-bedrock: false 217 | nether-ceiling-void-damage-height: disabled 218 | optimize-explosions: false 219 | portal-create-radius: 16 220 | portal-search-radius: 128 221 | portal-search-vanilla-dimension-scaling: true 222 | treasure-maps: 223 | enabled: true 224 | find-already-discovered: 225 | loot-tables: 'false' 226 | villager-trade: false 227 | water-over-lava-flow-speed: 5 228 | feature-seeds: 229 | generate-random-seeds-for-all: false 230 | fishing-time-range: 231 | maximum: 600 232 | minimum: 100 233 | fixes: 234 | disable-unloaded-chunk-enderpearl-exploit: true 235 | falling-block-height-nerf: disabled 236 | fix-curing-zombie-villager-discount-exploit: true 237 | fix-items-merging-through-walls: false 238 | fix-wither-targeting-bug: false 239 | prevent-tnt-from-moving-in-water: false 240 | remove-corrupt-tile-entities: false 241 | split-overstacked-loot: true 242 | tnt-entity-height-nerf: disabled 243 | hopper: 244 | cooldown-when-full: true 245 | disable-move-event: false 246 | ignore-occluding-blocks: false 247 | lootables: 248 | auto-replenish: false 249 | max-refills: -1 250 | refresh-max: 2d 251 | refresh-min: 12h 252 | reset-seed-on-fill: true 253 | restrict-player-reloot: true 254 | restrict-player-reloot-time: disabled 255 | maps: 256 | item-frame-cursor-limit: 128 257 | item-frame-cursor-update-interval: 10 258 | max-growth-height: 259 | bamboo: 260 | max: 16 261 | min: 11 262 | cactus: 3 263 | reeds: 3 264 | misc: 265 | disable-end-credits: false 266 | disable-relative-projectile-velocity: false 267 | disable-sprint-interruption-on-attack: false 268 | light-queue-size: 20 269 | max-leash-distance: 10.0 270 | redstone-implementation: VANILLA 271 | shield-blocking-delay: 5 272 | show-sign-click-command-failure-msgs-to-player: false 273 | update-pathfinding-on-block-update: true 274 | scoreboards: 275 | allow-non-player-entities-on-scoreboards: false 276 | use-vanilla-world-scoreboard-name-coloring: false 277 | spawn: 278 | allow-using-signs-inside-spawn-protection: false 279 | keep-spawn-loaded: false 280 | keep-spawn-loaded-range: 10 281 | tick-rates: 282 | behavior: 283 | villager: 284 | validatenearbypoi: -1 285 | container-update: 1 286 | grass-spread: 1 287 | mob-spawner: 1 288 | sensor: 289 | villager: 290 | secondarypoisensor: 40 291 | unsupported-settings: 292 | fix-invulnerable-end-crystal-exploit: true 293 | -------------------------------------------------------------------------------- /run/eula.txt: -------------------------------------------------------------------------------- 1 | #By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula). 2 | #You also agree that tacos are tasty, and the best food in the world. 3 | #Sat Nov 06 15:11:02 CET 2021 4 | eula=true 5 | -------------------------------------------------------------------------------- /run/gameteststructures/test.test.snbt: -------------------------------------------------------------------------------- 1 | { 2 | DataVersion: 2730, 3 | size: [5, 5, 5], 4 | data: [ 5 | {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, 6 | {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, 7 | {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, 8 | {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, 9 | {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, 10 | {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, 11 | {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, 12 | {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, 13 | {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, 14 | {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, 15 | {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, 16 | {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, 17 | {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, 18 | {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, 19 | {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, 20 | {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, 21 | {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, 22 | {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, 23 | {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, 24 | {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, 25 | {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, 26 | {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, 27 | {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, 28 | {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, 29 | {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, 30 | {pos: [0, 1, 0], state: "minecraft:air"}, 31 | {pos: [0, 1, 1], state: "minecraft:air"}, 32 | {pos: [0, 1, 2], state: "minecraft:air"}, 33 | {pos: [0, 1, 3], state: "minecraft:air"}, 34 | {pos: [0, 1, 4], state: "minecraft:air"}, 35 | {pos: [1, 1, 0], state: "minecraft:air"}, 36 | {pos: [1, 1, 1], state: "minecraft:rail{shape:south_east,waterlogged:false}"}, 37 | {pos: [1, 1, 2], state: "minecraft:air"}, 38 | {pos: [1, 1, 3], state: "minecraft:air"}, 39 | {pos: [1, 1, 4], state: "minecraft:air"}, 40 | {pos: [2, 1, 0], state: "minecraft:air"}, 41 | {pos: [2, 1, 1], state: "minecraft:rail{shape:east_west,waterlogged:false}"}, 42 | {pos: [2, 1, 2], state: "minecraft:air"}, 43 | {pos: [2, 1, 3], state: "minecraft:air"}, 44 | {pos: [2, 1, 4], state: "minecraft:air"}, 45 | {pos: [3, 1, 0], state: "minecraft:air"}, 46 | {pos: [3, 1, 1], state: "minecraft:rail{shape:south_west,waterlogged:false}"}, 47 | {pos: [3, 1, 2], state: "minecraft:powered_rail{powered:false,shape:north_south,waterlogged:false}"}, 48 | {pos: [3, 1, 3], state: "minecraft:polished_andesite"}, 49 | {pos: [3, 1, 4], state: "minecraft:air"}, 50 | {pos: [4, 1, 0], state: "minecraft:air"}, 51 | {pos: [4, 1, 1], state: "minecraft:air"}, 52 | {pos: [4, 1, 2], state: "minecraft:air"}, 53 | {pos: [4, 1, 3], state: "minecraft:air"}, 54 | {pos: [4, 1, 4], state: "minecraft:air"}, 55 | {pos: [0, 2, 0], state: "minecraft:air"}, 56 | {pos: [0, 2, 1], state: "minecraft:air"}, 57 | {pos: [0, 2, 2], state: "minecraft:air"}, 58 | {pos: [0, 2, 3], state: "minecraft:air"}, 59 | {pos: [0, 2, 4], state: "minecraft:air"}, 60 | {pos: [1, 2, 0], state: "minecraft:air"}, 61 | {pos: [1, 2, 1], state: "minecraft:air"}, 62 | {pos: [1, 2, 2], state: "minecraft:air"}, 63 | {pos: [1, 2, 3], state: "minecraft:air"}, 64 | {pos: [1, 2, 4], state: "minecraft:air"}, 65 | {pos: [2, 2, 0], state: "minecraft:air"}, 66 | {pos: [2, 2, 1], state: "minecraft:air"}, 67 | {pos: [2, 2, 2], state: "minecraft:air"}, 68 | {pos: [2, 2, 3], state: "minecraft:air"}, 69 | {pos: [2, 2, 4], state: "minecraft:air"}, 70 | {pos: [3, 2, 0], state: "minecraft:air"}, 71 | {pos: [3, 2, 1], state: "minecraft:air"}, 72 | {pos: [3, 2, 2], state: "minecraft:air"}, 73 | {pos: [3, 2, 3], state: "minecraft:stone_button{face:floor,facing:east,powered:false}"}, 74 | {pos: [3, 2, 4], state: "minecraft:air"}, 75 | {pos: [4, 2, 0], state: "minecraft:air"}, 76 | {pos: [4, 2, 1], state: "minecraft:air"}, 77 | {pos: [4, 2, 2], state: "minecraft:air"}, 78 | {pos: [4, 2, 3], state: "minecraft:air"}, 79 | {pos: [4, 2, 4], state: "minecraft:air"}, 80 | {pos: [0, 3, 0], state: "minecraft:air"}, 81 | {pos: [0, 3, 1], state: "minecraft:air"}, 82 | {pos: [0, 3, 2], state: "minecraft:air"}, 83 | {pos: [0, 3, 3], state: "minecraft:air"}, 84 | {pos: [0, 3, 4], state: "minecraft:air"}, 85 | {pos: [1, 3, 0], state: "minecraft:air"}, 86 | {pos: [1, 3, 1], state: "minecraft:air"}, 87 | {pos: [1, 3, 2], state: "minecraft:air"}, 88 | {pos: [1, 3, 3], state: "minecraft:air"}, 89 | {pos: [1, 3, 4], state: "minecraft:air"}, 90 | {pos: [2, 3, 0], state: "minecraft:air"}, 91 | {pos: [2, 3, 1], state: "minecraft:air"}, 92 | {pos: [2, 3, 2], state: "minecraft:air"}, 93 | {pos: [2, 3, 3], state: "minecraft:air"}, 94 | {pos: [2, 3, 4], state: "minecraft:air"}, 95 | {pos: [3, 3, 0], state: "minecraft:air"}, 96 | {pos: [3, 3, 1], state: "minecraft:air"}, 97 | {pos: [3, 3, 2], state: "minecraft:air"}, 98 | {pos: [3, 3, 3], state: "minecraft:air"}, 99 | {pos: [3, 3, 4], state: "minecraft:air"}, 100 | {pos: [4, 3, 0], state: "minecraft:air"}, 101 | {pos: [4, 3, 1], state: "minecraft:air"}, 102 | {pos: [4, 3, 2], state: "minecraft:air"}, 103 | {pos: [4, 3, 3], state: "minecraft:air"}, 104 | {pos: [4, 3, 4], state: "minecraft:air"}, 105 | {pos: [0, 4, 0], state: "minecraft:air"}, 106 | {pos: [0, 4, 1], state: "minecraft:air"}, 107 | {pos: [0, 4, 2], state: "minecraft:air"}, 108 | {pos: [0, 4, 3], state: "minecraft:air"}, 109 | {pos: [0, 4, 4], state: "minecraft:air"}, 110 | {pos: [1, 4, 0], state: "minecraft:air"}, 111 | {pos: [1, 4, 1], state: "minecraft:air"}, 112 | {pos: [1, 4, 2], state: "minecraft:air"}, 113 | {pos: [1, 4, 3], state: "minecraft:air"}, 114 | {pos: [1, 4, 4], state: "minecraft:air"}, 115 | {pos: [2, 4, 0], state: "minecraft:air"}, 116 | {pos: [2, 4, 1], state: "minecraft:air"}, 117 | {pos: [2, 4, 2], state: "minecraft:air"}, 118 | {pos: [2, 4, 3], state: "minecraft:air"}, 119 | {pos: [2, 4, 4], state: "minecraft:air"}, 120 | {pos: [3, 4, 0], state: "minecraft:air"}, 121 | {pos: [3, 4, 1], state: "minecraft:air"}, 122 | {pos: [3, 4, 2], state: "minecraft:air"}, 123 | {pos: [3, 4, 3], state: "minecraft:air"}, 124 | {pos: [3, 4, 4], state: "minecraft:air"}, 125 | {pos: [4, 4, 0], state: "minecraft:air"}, 126 | {pos: [4, 4, 1], state: "minecraft:air"}, 127 | {pos: [4, 4, 2], state: "minecraft:air"}, 128 | {pos: [4, 4, 3], state: "minecraft:air"}, 129 | {pos: [4, 4, 4], state: "minecraft:air"} 130 | ], 131 | entities: [ 132 | {blockPos: [3, 1, 2], pos: [3.5d, 1.0625d, 2.5d], nbt: {Air: 300s, Bukkit.updateLevel: 2, FallDistance: 0.0f, Fire: -1s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, Paper.Origin: [223.5d, 71.0625d, 117.5d], Paper.OriginWorld: [I; -1618278647, 1898990658, -1524964533, -541779802], Paper.SpawnReason: "DEFAULT", PortalCooldown: 0, Pos: [223.5d, 71.0625d, 117.5d], Rotation: [0.0f, 0.0f], Spigot.ticksLived: 992, UUID: [I; 393734910, -404008986, -1592521708, 218730232], WorldUUIDLeast: -6549672793041725274L, WorldUUIDMost: -6950453862781137854L, id: "minecraft:minecart"}} 133 | ], 134 | palette: [ 135 | "minecraft:polished_andesite", 136 | "minecraft:air", 137 | "minecraft:rail{shape:south_east,waterlogged:false}", 138 | "minecraft:rail{shape:east_west,waterlogged:false}", 139 | "minecraft:rail{shape:south_west,waterlogged:false}", 140 | "minecraft:powered_rail{powered:false,shape:north_south,waterlogged:false}", 141 | "minecraft:stone_button{face:floor,facing:east,powered:false}" 142 | ] 143 | } 144 | -------------------------------------------------------------------------------- /run/gameteststructures/test.test2.snbt: -------------------------------------------------------------------------------- 1 | { 2 | DataVersion: 2730, 3 | size: [5, 5, 5], 4 | data: [ 5 | {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, 6 | {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, 7 | {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, 8 | {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, 9 | {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, 10 | {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, 11 | {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, 12 | {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, 13 | {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, 14 | {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, 15 | {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, 16 | {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, 17 | {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, 18 | {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, 19 | {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, 20 | {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, 21 | {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, 22 | {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, 23 | {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, 24 | {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, 25 | {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, 26 | {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, 27 | {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, 28 | {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, 29 | {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, 30 | {pos: [0, 1, 0], state: "minecraft:air"}, 31 | {pos: [0, 1, 1], state: "minecraft:air"}, 32 | {pos: [0, 1, 2], state: "minecraft:air"}, 33 | {pos: [0, 1, 3], state: "minecraft:air"}, 34 | {pos: [0, 1, 4], state: "minecraft:air"}, 35 | {pos: [1, 1, 0], state: "minecraft:air"}, 36 | {pos: [1, 1, 1], state: "minecraft:air"}, 37 | {pos: [1, 1, 2], state: "minecraft:air"}, 38 | {pos: [1, 1, 3], state: "minecraft:air"}, 39 | {pos: [1, 1, 4], state: "minecraft:air"}, 40 | {pos: [2, 1, 0], state: "minecraft:air"}, 41 | {pos: [2, 1, 1], state: "minecraft:air"}, 42 | {pos: [2, 1, 2], state: "minecraft:air"}, 43 | {pos: [2, 1, 3], state: "minecraft:tnt{unstable:false}"}, 44 | {pos: [2, 1, 4], state: "minecraft:air"}, 45 | {pos: [3, 1, 0], state: "minecraft:air"}, 46 | {pos: [3, 1, 1], state: "minecraft:air"}, 47 | {pos: [3, 1, 2], state: "minecraft:air"}, 48 | {pos: [3, 1, 3], state: "minecraft:air"}, 49 | {pos: [3, 1, 4], state: "minecraft:air"}, 50 | {pos: [4, 1, 0], state: "minecraft:air"}, 51 | {pos: [4, 1, 1], state: "minecraft:air"}, 52 | {pos: [4, 1, 2], state: "minecraft:air"}, 53 | {pos: [4, 1, 3], state: "minecraft:air"}, 54 | {pos: [4, 1, 4], state: "minecraft:air"}, 55 | {pos: [0, 2, 0], state: "minecraft:air"}, 56 | {pos: [0, 2, 1], state: "minecraft:air"}, 57 | {pos: [0, 2, 2], state: "minecraft:air"}, 58 | {pos: [0, 2, 3], state: "minecraft:air"}, 59 | {pos: [0, 2, 4], state: "minecraft:air"}, 60 | {pos: [1, 2, 0], state: "minecraft:air"}, 61 | {pos: [1, 2, 1], state: "minecraft:air"}, 62 | {pos: [1, 2, 2], state: "minecraft:air"}, 63 | {pos: [1, 2, 3], state: "minecraft:air"}, 64 | {pos: [1, 2, 4], state: "minecraft:air"}, 65 | {pos: [2, 2, 0], state: "minecraft:air"}, 66 | {pos: [2, 2, 1], state: "minecraft:air"}, 67 | {pos: [2, 2, 2], state: "minecraft:air"}, 68 | {pos: [2, 2, 3], state: "minecraft:oak_button{face:floor,facing:south,powered:false}"}, 69 | {pos: [2, 2, 4], state: "minecraft:air"}, 70 | {pos: [3, 2, 0], state: "minecraft:air"}, 71 | {pos: [3, 2, 1], state: "minecraft:air"}, 72 | {pos: [3, 2, 2], state: "minecraft:air"}, 73 | {pos: [3, 2, 3], state: "minecraft:air"}, 74 | {pos: [3, 2, 4], state: "minecraft:air"}, 75 | {pos: [4, 2, 0], state: "minecraft:air"}, 76 | {pos: [4, 2, 1], state: "minecraft:air"}, 77 | {pos: [4, 2, 2], state: "minecraft:air"}, 78 | {pos: [4, 2, 3], state: "minecraft:air"}, 79 | {pos: [4, 2, 4], state: "minecraft:air"}, 80 | {pos: [0, 3, 0], state: "minecraft:air"}, 81 | {pos: [0, 3, 1], state: "minecraft:air"}, 82 | {pos: [0, 3, 2], state: "minecraft:air"}, 83 | {pos: [0, 3, 3], state: "minecraft:air"}, 84 | {pos: [0, 3, 4], state: "minecraft:air"}, 85 | {pos: [1, 3, 0], state: "minecraft:air"}, 86 | {pos: [1, 3, 1], state: "minecraft:air"}, 87 | {pos: [1, 3, 2], state: "minecraft:air"}, 88 | {pos: [1, 3, 3], state: "minecraft:air"}, 89 | {pos: [1, 3, 4], state: "minecraft:air"}, 90 | {pos: [2, 3, 0], state: "minecraft:air"}, 91 | {pos: [2, 3, 1], state: "minecraft:air"}, 92 | {pos: [2, 3, 2], state: "minecraft:air"}, 93 | {pos: [2, 3, 3], state: "minecraft:air"}, 94 | {pos: [2, 3, 4], state: "minecraft:air"}, 95 | {pos: [3, 3, 0], state: "minecraft:air"}, 96 | {pos: [3, 3, 1], state: "minecraft:air"}, 97 | {pos: [3, 3, 2], state: "minecraft:air"}, 98 | {pos: [3, 3, 3], state: "minecraft:air"}, 99 | {pos: [3, 3, 4], state: "minecraft:air"}, 100 | {pos: [4, 3, 0], state: "minecraft:air"}, 101 | {pos: [4, 3, 1], state: "minecraft:air"}, 102 | {pos: [4, 3, 2], state: "minecraft:air"}, 103 | {pos: [4, 3, 3], state: "minecraft:air"}, 104 | {pos: [4, 3, 4], state: "minecraft:air"}, 105 | {pos: [0, 4, 0], state: "minecraft:air"}, 106 | {pos: [0, 4, 1], state: "minecraft:air"}, 107 | {pos: [0, 4, 2], state: "minecraft:air"}, 108 | {pos: [0, 4, 3], state: "minecraft:air"}, 109 | {pos: [0, 4, 4], state: "minecraft:air"}, 110 | {pos: [1, 4, 0], state: "minecraft:air"}, 111 | {pos: [1, 4, 1], state: "minecraft:air"}, 112 | {pos: [1, 4, 2], state: "minecraft:air"}, 113 | {pos: [1, 4, 3], state: "minecraft:air"}, 114 | {pos: [1, 4, 4], state: "minecraft:air"}, 115 | {pos: [2, 4, 0], state: "minecraft:air"}, 116 | {pos: [2, 4, 1], state: "minecraft:air"}, 117 | {pos: [2, 4, 2], state: "minecraft:air"}, 118 | {pos: [2, 4, 3], state: "minecraft:air"}, 119 | {pos: [2, 4, 4], state: "minecraft:air"}, 120 | {pos: [3, 4, 0], state: "minecraft:air"}, 121 | {pos: [3, 4, 1], state: "minecraft:air"}, 122 | {pos: [3, 4, 2], state: "minecraft:air"}, 123 | {pos: [3, 4, 3], state: "minecraft:air"}, 124 | {pos: [3, 4, 4], state: "minecraft:air"}, 125 | {pos: [4, 4, 0], state: "minecraft:air"}, 126 | {pos: [4, 4, 1], state: "minecraft:air"}, 127 | {pos: [4, 4, 2], state: "minecraft:air"}, 128 | {pos: [4, 4, 3], state: "minecraft:air"}, 129 | {pos: [4, 4, 4], state: "minecraft:air"} 130 | ], 131 | entities: [ 132 | {blockPos: [3, 1, 1], pos: [3.7142589170416898d, 1.0d, 1.8290328739333859d], nbt: {AbsorptionAmount: 0.0f, Age: 0, AgeLocked: 0b, Air: 300s, ArmorDropChances: [0.085f, 0.085f, 0.085f, 0.085f], ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.25d, Name: "minecraft:generic.movement_speed"}, {Base: 10.0d, Name: "minecraft:generic.max_health"}], Brain: {memories: {}}, Bukkit.Aware: 1b, Bukkit.updateLevel: 2, CanPickUpLoot: 0b, DeathTime: 0s, FallDistance: 0.0f, FallFlying: 0b, Fire: 0s, ForcedAge: 0, HandDropChances: [0.085f, 0.085f], HandItems: [{}, {}], Health: 10.0f, HurtByTimestamp: 0, HurtTime: 0s, InLove: 0, Invulnerable: 0b, LeftHanded: 0b, Motion: [0.004305813212034916d, 0.0d, 0.0d], NoAI: 1b, OnGround: 0b, Paper.Origin: [125.71425891704169d, 74.0d, 292.8290328739334d], Paper.OriginWorld: [I; -1618278647, 1898990658, -1524964533, -541779802], Paper.SpawnReason: "NATURAL", PersistenceRequired: 1b, PortalCooldown: 0, Pos: [125.71425891704169d, 74.0d, 292.8290328739334d], Rotation: [0.0f, 0.0f], Saddle: 0b, Spigot.ticksLived: 198, UUID: [I; -1258148934, 226313184, -1438136554, 438299189], WorldUUIDLeast: -6549672793041725274L, WorldUUIDMost: -6950453862781137854L, id: "minecraft:pig"}} 133 | ], 134 | palette: [ 135 | "minecraft:polished_andesite", 136 | "minecraft:tnt{unstable:false}", 137 | "minecraft:air", 138 | "minecraft:oak_button{face:floor,facing:south,powered:false}" 139 | ] 140 | } 141 | -------------------------------------------------------------------------------- /run/plugins/MiniTestFramework/test.js: -------------------------------------------------------------------------------- 1 | import {registry, EntityType, Blocks} from "./minitestframework/index.mjs"; 2 | 3 | registry.register("test", (helper) => { 4 | helper.pressButton(3, 3, 3); 5 | helper.succeedWhenEntityPresent(EntityType.PIG, 1, 2, 3); 6 | }); 7 | 8 | registry.register("test2", (helper) => { 9 | helper.pressButton(2, 3, 3); 10 | helper.succeedWhenEntityNotPresent(EntityType.PIG, 3, 2, 1); 11 | }); 12 | 13 | registry.register("test3", (helper) => { 14 | helper.pressButton(1, 2, 3) 15 | helper.succeedWhenBlockPresent(Blocks.DIAMOND_BLOCK, 2, 2, 1); 16 | }); 17 | -------------------------------------------------------------------------------- /run/server.properties: -------------------------------------------------------------------------------- 1 | #Minecraft server properties 2 | #Sat Aug 19 16:51:53 CEST 2023 3 | enable-jmx-monitoring=false 4 | rcon.port=25575 5 | level-seed= 6 | enable-command-block=true 7 | gamemode=creative 8 | enable-query=false 9 | generator-settings={} 10 | enforce-secure-profile=true 11 | level-name=world 12 | motd=A Minecraft Server 13 | query.port=25565 14 | pvp=true 15 | generate-structures=true 16 | max-chained-neighbor-updates=1000000 17 | difficulty=easy 18 | network-compression-threshold=256 19 | max-tick-time=60000 20 | require-resource-pack=false 21 | max-players=42 22 | use-native-transport=true 23 | online-mode=true 24 | enable-status=true 25 | allow-flight=false 26 | initial-disabled-packs= 27 | broadcast-rcon-to-ops=true 28 | view-distance=10 29 | server-ip= 30 | resource-pack-prompt= 31 | allow-nether=false 32 | server-port=25565 33 | enable-rcon=false 34 | sync-chunk-writes=true 35 | op-permission-level=4 36 | prevent-proxy-connections=false 37 | hide-online-players=false 38 | resource-pack= 39 | entity-broadcast-range-percentage=100 40 | simulation-distance=10 41 | rcon.password= 42 | player-idle-timeout=0 43 | debug=false 44 | force-gamemode=false 45 | rate-limit=0 46 | hardcore=false 47 | white-list=false 48 | broadcast-console-to-ops=true 49 | spawn-npcs=true 50 | spawn-animals=true 51 | snooper-enabled=true 52 | function-permission-level=2 53 | initial-enabled-packs=vanilla 54 | level-type=minecraft\:normal 55 | text-filtering-config= 56 | spawn-monsters=true 57 | enforce-whitelist=false 58 | resource-pack-sha1= 59 | spawn-protection=16 60 | max-world-size=29999984 61 | -------------------------------------------------------------------------------- /run/test-results.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Paper' 5 | url = 'https://repo.papermc.io/repository/maven-public/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | rootProject.name = 'MiniTestFramework' 12 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/MiniTestCommand.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.commands.CommandSourceStack; 6 | import net.minecraft.commands.Commands; 7 | import net.minecraft.network.chat.Component; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.craftbukkit.v1_20_R1.command.VanillaCommandWrapper; 10 | 11 | import javax.xml.parsers.ParserConfigurationException; 12 | 13 | public class MiniTestCommand { 14 | 15 | public static void register(CommandDispatcher dispatcher, MiniTestFramework miniTestFramework) { 16 | dispatcher.register(Commands.literal("minitest").executes((c) -> { 17 | c.getSource().sendSystemMessage(Component.literal("Plugin is enabled!")); 18 | return 1; 19 | }).then(Commands.literal("reload").executes((c) -> { 20 | String msg = miniTestFramework.findTests(); 21 | c.getSource().sendSystemMessage(Component.literal("Reloaded! " + msg)); 22 | return 1; 23 | })).then(Commands.literal("ci").executes((c) -> { 24 | try { 25 | miniTestFramework.runAllTests(VanillaCommandWrapper.getListener(Bukkit.getConsoleSender()), false); 26 | c.getSource().sendSystemMessage(Component.literal("Ran all tests!")); 27 | return 1; 28 | } catch (ParserConfigurationException e) { 29 | c.getSource().sendSystemMessage(Component.literal("Error while running tests").withStyle(ChatFormatting.RED)); 30 | e.printStackTrace(); 31 | return 1; 32 | } 33 | })).then(Commands.literal("ts").executes((c) -> { 34 | miniTestFramework.initTS(); 35 | c.getSource().sendSystemMessage(Component.literal("Rewrote types!")); 36 | return 1; 37 | }))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/MiniTestFramework.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import dev.benndorf.minitestframework.ci.TestReporter; 5 | import dev.benndorf.minitestframework.js.GraalUtil; 6 | import dev.benndorf.minitestframework.ts.TSGenerator.TSType; 7 | import dev.benndorf.minitestframework.ts.TSGeneratorRunner; 8 | import net.minecraft.commands.CommandSourceStack; 9 | import net.minecraft.core.BlockPos; 10 | import net.minecraft.gametest.framework.*; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.world.level.block.Rotation; 13 | import net.minecraft.world.level.levelgen.Heightmap; 14 | import org.bukkit.Bukkit; 15 | import org.bukkit.craftbukkit.v1_20_R1.CraftServer; 16 | import org.bukkit.craftbukkit.v1_20_R1.command.VanillaCommandWrapper; 17 | import org.bukkit.plugin.java.JavaPlugin; 18 | import org.graalvm.polyglot.Engine; 19 | 20 | import javax.xml.parsers.ParserConfigurationException; 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.util.Collection; 26 | import java.util.function.Consumer; 27 | import java.util.stream.Collectors; 28 | import java.util.stream.Stream; 29 | 30 | import static dev.benndorf.minitestframework.ts.TSGenerator.*; 31 | 32 | public final class MiniTestFramework extends JavaPlugin { 33 | 34 | private Engine engine; 35 | private String currentFileName = ""; 36 | 37 | @TSHide 38 | @Override 39 | public void onLoad() { 40 | this.getDataFolder().mkdirs(); 41 | 42 | this.initJS(); 43 | this.initTS(); 44 | 45 | this.registerCommand(); 46 | } 47 | 48 | private void registerCommand() { 49 | final CommandDispatcher dispatcher = ((CraftServer) this.getServer()).getServer().vanillaCommandDispatcher.getDispatcher(); 50 | MiniTestCommand.register(dispatcher, this); 51 | } 52 | 53 | private void initJS() { 54 | this.engine = GraalUtil.createEngine(); 55 | this.getSLF4JLogger().info(this.findTests()); 56 | } 57 | 58 | @TSHide 59 | public void initTS() { 60 | try { 61 | TSGeneratorRunner.run(getDataFolder().toPath().resolve("minitestframework")); 62 | } catch (IOException e) { 63 | throw new RuntimeException(e); 64 | } 65 | } 66 | 67 | @TSHide 68 | @Override 69 | public void onEnable() { 70 | if ("true".equals(System.getenv("CI"))) { 71 | try { 72 | this.runAllTests(VanillaCommandWrapper.getListener(Bukkit.getConsoleSender()), true); 73 | } catch (final Exception ex) { 74 | this.getSLF4JLogger().error("Error while running tests", ex); 75 | } 76 | } 77 | } 78 | 79 | @TSHide 80 | public void runAllTests(final CommandSourceStack source, final boolean stopServer) throws ParserConfigurationException { 81 | GameTestRunner.clearMarkers(source.getLevel()); 82 | final Collection testFunctions = GameTestRegistry.getAllTestFunctions(); 83 | source.sendSuccess(() -> Component.literal("Running all " + testFunctions.size() + " tests..."), false); 84 | final BlockPos sourcePos = new BlockPos((int) source.getPosition().x(), (int) source.getPosition().y(), (int) source.getPosition().z()); 85 | final BlockPos startPos = new BlockPos(sourcePos.getX(), source.getLevel().getHeightmapPos(Heightmap.Types.WORLD_SURFACE, sourcePos).getY(), sourcePos.getZ() + 3); 86 | final Collection collection = GameTestRunner.runTests(testFunctions, startPos, StructureUtils.getRotationForRotationSteps(0), source.getLevel(), GameTestTicker.SINGLETON, 8); 87 | GlobalTestReporter.replaceWith(new TestReporter(new File( "test-results.xml"), new MultipleTestTracker(collection), stopServer)); 88 | } 89 | 90 | @TSHide 91 | public String findTests() { 92 | GameTestRegistry.getAllTestClassNames().clear(); 93 | GameTestRegistry.getAllTestFunctions().clear(); 94 | 95 | try (final Stream list = Files.list(this.getDataFolder().toPath())) { 96 | list 97 | .filter(p -> p.toString().endsWith(".js")) 98 | .forEach(file -> { 99 | try { 100 | this.currentFileName = file.getFileName().toString().replace(".js", ""); 101 | GraalUtil.execute(engine, this, file); 102 | } catch (final IOException ex) { 103 | this.getSLF4JLogger().error("Error while executing file {}", file.getFileName(), ex); 104 | } finally { 105 | this.currentFileName = null; 106 | } 107 | }); 108 | } catch (final IOException ex) { 109 | this.getSLF4JLogger().error("Error while scanning for files in {}", this.getDataFolder(), ex); 110 | } 111 | 112 | final String fileNames = String.join(", ", GameTestRegistry.getAllTestClassNames()); 113 | final String testNames = GameTestRegistry.getAllTestFunctions().stream().map(TestFunction::getTestName).collect(Collectors.joining(", ")); 114 | return String.format("Found tests %s in files %s", testNames, fileNames); 115 | } 116 | 117 | public void register(final String name, @TSType("(helper: GameTestHelper) => void") final Consumer method) { 118 | this.register("defaultBatch", name, Rotation.NONE, 100, 0, true, method); 119 | } 120 | 121 | public void register(final String batchId, final String name, @TSType("(helper: GameTestHelper) => void") final Consumer method) { 122 | this.register(batchId, name, Rotation.NONE, 100, 0, true, method); 123 | } 124 | 125 | public void register(final String batchId, final String name, final int tickLimit, final long duration, @TSType("(helper: GameTestHelper) => void") final Consumer method) { 126 | this.register(batchId, name, Rotation.NONE, tickLimit, duration, true, method); 127 | } 128 | 129 | public void register(final String batchId, final String name, final Rotation rotation, final int tickLimit, final long duration, final boolean required, @TSType("(helper: GameTestHelper) => void") final Consumer method) { 130 | this.register(batchId, name, rotation, tickLimit, duration, required, 1, 1, method); 131 | } 132 | 133 | public void register(final String batchId, String name, final Rotation rotation, final int tickLimit, final long duration, final boolean required, final int requiredSuccesses, final int maxAttempts, @TSType("(helper: GameTestHelper) => void") final Consumer method) { 134 | name = this.currentFileName + "." + name; 135 | final TestFunction testFunction = new TestFunction(batchId, name, name, rotation, tickLimit, duration, required, requiredSuccesses, maxAttempts, method); 136 | GameTestRegistry.getAllTestFunctions().add(testFunction); 137 | GameTestRegistry.getAllTestClassNames().add(this.currentFileName); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/MiniTestFrameworkBootstrap.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework; 2 | 3 | import io.papermc.paper.plugin.bootstrap.BootstrapContext; 4 | import io.papermc.paper.plugin.bootstrap.PluginBootstrap; 5 | import net.minecraft.SharedConstants; 6 | import net.minecraft.commands.synchronization.ArgumentTypeInfos; 7 | import net.minecraft.commands.synchronization.SingletonArgumentInfo; 8 | import net.minecraft.core.registries.BuiltInRegistries; 9 | import net.minecraft.gametest.framework.TestClassNameArgument; 10 | import net.minecraft.gametest.framework.TestFunctionArgument; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.lang.reflect.Method; 15 | 16 | @SuppressWarnings("UnstableApiUsage") 17 | public class MiniTestFrameworkBootstrap implements PluginBootstrap { 18 | 19 | @Override 20 | public void bootstrap(@NotNull BootstrapContext context) { 21 | // set to true so that the test command is registered and stuff 22 | SharedConstants.IS_RUNNING_IN_IDE = true; 23 | 24 | // bootstrap isn't early enough, argument types are still registered before this method is called, so we need to do it manually 25 | for (Method method : ArgumentTypeInfos.class.getDeclaredMethods()) { 26 | if (method.getName().equals("register")) { 27 | try { 28 | method.setAccessible(true); 29 | method.invoke(null, BuiltInRegistries.COMMAND_ARGUMENT_TYPE, "test_argument", TestFunctionArgument.class, SingletonArgumentInfo.contextFree(TestFunctionArgument::testFunctionArgument)); 30 | method.invoke(null, BuiltInRegistries.COMMAND_ARGUMENT_TYPE, "test_class", TestClassNameArgument.class, SingletonArgumentInfo.contextFree(TestClassNameArgument::testClassName)); 31 | } catch (IllegalAccessException | InvocationTargetException e) { 32 | throw new RuntimeException("Couldn't register gametest arguemnt types", e); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/ci/TestReporter.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework.ci; 2 | 3 | import com.google.common.base.Stopwatch; 4 | 5 | import net.minecraft.gametest.framework.GameTestInfo; 6 | import net.minecraft.gametest.framework.GlobalTestReporter; 7 | import net.minecraft.gametest.framework.MultipleTestTracker; 8 | 9 | import org.w3c.dom.Document; 10 | import org.w3c.dom.Element; 11 | 12 | import java.io.File; 13 | import java.time.Instant; 14 | import java.time.format.DateTimeFormatter; 15 | import java.util.Objects; 16 | import java.util.concurrent.TimeUnit; 17 | import javax.xml.parsers.DocumentBuilderFactory; 18 | import javax.xml.parsers.ParserConfigurationException; 19 | import javax.xml.transform.OutputKeys; 20 | import javax.xml.transform.Transformer; 21 | import javax.xml.transform.TransformerException; 22 | import javax.xml.transform.TransformerFactory; 23 | import javax.xml.transform.dom.DOMSource; 24 | import javax.xml.transform.stream.StreamResult; 25 | 26 | // kinda copied from vanilla I guess? 27 | public class TestReporter implements net.minecraft.gametest.framework.TestReporter { 28 | 29 | private final Document document; 30 | private final Element testSuite; 31 | private final Stopwatch stopwatch; 32 | private final File destination; 33 | private int failures; 34 | private int skips; 35 | private int successes; 36 | private final MultipleTestTracker tests; 37 | 38 | private final boolean stopServer; 39 | 40 | public TestReporter(final File dest, final MultipleTestTracker tests, final boolean stopServer) throws ParserConfigurationException { 41 | this.destination = dest; 42 | this.document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 43 | this.testSuite = this.document.createElement("testsuite"); 44 | this.tests = tests; 45 | this.stopServer = stopServer; 46 | 47 | final Element testSuites = this.document.createElement("testsuites"); 48 | testSuites.setAttribute("name", "MiniTestFramework Tests"); 49 | testSuites.appendChild(this.testSuite); 50 | this.document.appendChild(testSuites); 51 | 52 | this.testSuite.setAttribute("timestamp", DateTimeFormatter.ISO_INSTANT.format(Instant.now())); 53 | this.stopwatch = Stopwatch.createStarted(); 54 | } 55 | 56 | private Element createTestCase(final GameTestInfo testInfo, final String string) { 57 | final Element testCase = this.document.createElement("testcase"); 58 | testCase.setAttribute("name", string); 59 | testCase.setAttribute("classname", testInfo.getStructureName()); 60 | testCase.setAttribute("time", String.valueOf(testInfo.getRunTime() / 1000d)); 61 | this.testSuite.appendChild(testCase); 62 | return testCase; 63 | } 64 | 65 | @Override 66 | public void onTestFailed(final GameTestInfo testInfo) { 67 | final String name = testInfo.getTestName(); 68 | final String errorMsg = Objects.requireNonNull(testInfo.getError()).getMessage(); 69 | 70 | final Element failure; // "I'm a failure :(" 71 | if (testInfo.isRequired()) { 72 | this.failures++; 73 | failure = this.document.createElement("failure"); 74 | } else { 75 | this.skips++; 76 | failure = this.document.createElement("skipped"); 77 | } 78 | failure.setAttribute("message", errorMsg); 79 | 80 | final Element testCase = this.createTestCase(testInfo, name); 81 | testCase.appendChild(failure); 82 | this.checkDone(); 83 | } 84 | 85 | @Override 86 | public void onTestSuccess(final GameTestInfo testInfo) { 87 | this.successes++; 88 | final String testName = testInfo.getTestName(); 89 | this.createTestCase(testInfo, testName); 90 | this.checkDone(); 91 | } 92 | 93 | private void checkDone() { 94 | if (this.tests.isDone()) { 95 | GlobalTestReporter.finish(); 96 | System.out.println("GameTest done! " + this.tests.getTotalCount() + " tests were run"); 97 | int exitCode = 0; 98 | if (this.tests.hasFailedRequired()) { 99 | System.err.println(this.tests.getFailedRequiredCount() + " required tests failed :("); 100 | exitCode = this.tests.getFailedRequiredCount(); 101 | } else { 102 | System.out.println("All required tests passed :)"); 103 | } 104 | 105 | if (this.tests.hasFailedOptional()) { 106 | System.err.println(this.tests.getFailedOptionalCount() + " optional tests failed"); 107 | exitCode = exitCode > 0 ? exitCode : this.tests.getFailedOptionalCount() * -1; 108 | } 109 | if (stopServer) { 110 | Runtime.getRuntime().halt(exitCode); 111 | } 112 | } 113 | } 114 | 115 | @Override 116 | public void finish() { 117 | this.stopwatch.stop(); 118 | 119 | this.testSuite.setAttribute("tests", "" + (this.failures + this.skips + this.successes)); 120 | this.testSuite.setAttribute("name", "root"); 121 | this.testSuite.setAttribute("failures", "" + this.failures); 122 | this.testSuite.setAttribute("skipped", "" + this.skips); 123 | this.testSuite.setAttribute("time", String.valueOf(this.stopwatch.elapsed(TimeUnit.MILLISECONDS) / 1000d)); 124 | 125 | try { 126 | this.save(this.destination); 127 | } catch (final TransformerException exc) { 128 | throw new Error("Couldn't save test report", exc); 129 | } 130 | } 131 | 132 | public void save(final File file) throws TransformerException { 133 | final TransformerFactory factory = TransformerFactory.newInstance(); 134 | 135 | final Transformer transformer = factory.newTransformer(); 136 | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 137 | transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); 138 | 139 | final DOMSource source = new DOMSource(this.document); 140 | final StreamResult result = new StreamResult(file); 141 | transformer.transform(source, result); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/js/GraalUtil.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework.js; 2 | 3 | import dev.benndorf.minitestframework.MiniTestFramework; 4 | import net.minecraft.gametest.framework.GameTestHelper; 5 | import org.graalvm.polyglot.Context; 6 | import org.graalvm.polyglot.Engine; 7 | import org.graalvm.polyglot.Source; 8 | import org.graalvm.polyglot.Value; 9 | 10 | import java.io.IOException; 11 | import java.net.URL; 12 | import java.net.URLClassLoader; 13 | import java.nio.file.Path; 14 | 15 | public class GraalUtil { 16 | 17 | public static Engine createEngine() { 18 | 19 | final Loader loader = new Loader(MiniTestFramework.class.getClassLoader()); 20 | loader.addURL(locate(MiniTestFramework.class)); 21 | Thread.currentThread().setContextClassLoader(loader); 22 | 23 | return Engine.newBuilder().option("engine.WarnInterpreterOnly", "false").build(); 24 | } 25 | 26 | public static Context createContext(Engine engine, MiniTestFramework framework) { 27 | final Context context = Context.newBuilder("js") 28 | .engine(engine) 29 | .allowAllAccess(true) 30 | .allowExperimentalOptions(true) 31 | .option("js.nashorn-compat", "true") 32 | .option("js.commonjs-require", "true") 33 | .option("js.ecmascript-version", "2022") 34 | .option("js.commonjs-require-cwd", framework.getDataFolder().getAbsolutePath()) 35 | .build(); 36 | final Value bindings = context.getPolyglotBindings(); 37 | bindings.putMember("registry", framework); 38 | bindings.putMember("helper", GameTestHelper.class); 39 | return context; 40 | } 41 | 42 | private static URL locate(final Class clazz) { 43 | try { 44 | final URL resource = clazz.getProtectionDomain().getCodeSource().getLocation(); 45 | if (resource != null) return resource; 46 | } catch (final SecurityException | NullPointerException error) { 47 | // do nothing 48 | } 49 | final URL resource = clazz.getResource(clazz.getSimpleName() + ".class"); 50 | if (resource != null) { 51 | final String link = resource.toString(); 52 | final String suffix = clazz.getCanonicalName().replace('.', '/') + ".class"; 53 | if (link.endsWith(suffix)) { 54 | String path = link.substring(0, link.length() - suffix.length()); 55 | if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2); 56 | try { 57 | return new URL(path); 58 | } catch (final Exception error) { 59 | // do nothing 60 | } 61 | } 62 | } 63 | return null; 64 | } 65 | 66 | public static void execute(Engine engine, MiniTestFramework framework, final Path file) throws IOException { 67 | createContext(engine, framework).eval(Source.newBuilder("js", file.toFile()).mimeType("application/javascript+module").build()); 68 | } 69 | 70 | static class Loader extends URLClassLoader { 71 | 72 | public Loader(final ClassLoader parent) { 73 | super(new URL[0], parent); 74 | } 75 | 76 | @Override 77 | public void addURL(final URL location) { 78 | super.addURL(location); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/ts/TSGenerator.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework.ts; 2 | 3 | import java.lang.annotation.*; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Modifier; 7 | import java.lang.reflect.Parameter; 8 | import java.util.Arrays; 9 | import java.util.Comparator; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class TSGenerator { 14 | 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.PARAMETER) 17 | public @interface TSType { 18 | String value(); 19 | } 20 | 21 | @Retention(RetentionPolicy.RUNTIME) 22 | @Target(ElementType.METHOD) 23 | public @interface TSHide { 24 | 25 | } 26 | 27 | public static String generateClazz(final String alias, final Class clazz) { 28 | final StringBuilder sb = new StringBuilder(); 29 | 30 | if (alias != null) { 31 | sb.append("export const ").append(alias).append(": ").append(cleanName(clazz.getSimpleName())).append("\n"); 32 | } 33 | sb.append("export class ").append(cleanName(clazz.getSimpleName())).append(" {").append("\n"); 34 | 35 | Arrays.stream(clazz.getDeclaredFields()) 36 | .sorted(Comparator.comparing(Field::getName)) 37 | .filter(f -> Modifier.isPublic(f.getModifiers())) 38 | .forEach(f -> generateField(f, sb)); 39 | 40 | Arrays.stream(clazz.getDeclaredMethods()) 41 | .sorted(Comparator.comparing(Method::getName).thenComparing(Method::getParameterCount)) 42 | .filter(m -> Modifier.isPublic(m.getModifiers())) 43 | .forEach(m -> generateMethod(m, sb)); 44 | 45 | sb.append("}").append("\n"); 46 | return sb.toString(); 47 | } 48 | 49 | private static void generateField(Field field, StringBuilder sb) { 50 | sb.append(" "); 51 | if (Modifier.isStatic(field.getModifiers())) { 52 | sb.append("public static "); 53 | } 54 | final String type = type(field.getType(), null); 55 | sb.append(cleanName(field.getName())).append(": ").append(type == null ? "any" : type).append(";").append("\n"); 56 | } 57 | 58 | private static void generateMethod(Method method, StringBuilder sb) { 59 | if (method.getAnnotation(TSHide.class) != null) return; 60 | 61 | sb.append(" ").append(method.getName()).append("("); 62 | String prefix = ""; 63 | for (final Parameter parameter : method.getParameters()) { 64 | sb.append(prefix); 65 | prefix = ","; 66 | sb.append(cleanName(parameter.getName())); 67 | final String type = type(parameter.getType(), parameter.getAnnotation(TSType.class)); 68 | sb.append(type == null ? "" : ": " + type); 69 | } 70 | sb.append(")"); 71 | final String type = type(method.getReturnType(), null); 72 | sb.append(type == null ? "" : ": " + type); 73 | sb.append(";\n"); 74 | } 75 | 76 | public static final Map, String> mappings = new HashMap<>(); 77 | 78 | static { 79 | mappings.put(String.class, "string"); 80 | mappings.put(Long.class, "number"); 81 | mappings.put(long.class, "number"); 82 | mappings.put(Integer.class, "number"); 83 | mappings.put(int.class, "number"); 84 | mappings.put(Double.class, "number"); 85 | mappings.put(double.class, "number"); 86 | mappings.put(Float.class, "number"); 87 | mappings.put(float.class, "number"); 88 | mappings.put(Boolean.class, "boolean"); 89 | mappings.put(boolean.class, "boolean"); 90 | mappings.put(Void.class, "void"); 91 | mappings.put(void.class, "void"); 92 | } 93 | 94 | private static String type(final Class clazz, final TSType typeInfo) { 95 | if (typeInfo != null && typeInfo.value() != null) { 96 | return typeInfo.value(); 97 | } 98 | return mappings.getOrDefault(clazz, null); 99 | } 100 | 101 | private static String cleanName(final String name) { 102 | if (name.equals("function") || name.equals("object")) { 103 | return name + "P"; 104 | } 105 | return name; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/dev/benndorf/minitestframework/ts/TSGeneratorRunner.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework.ts; 2 | 3 | import dev.benndorf.minitestframework.MiniTestFramework; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.gametest.framework.GameTestHelper; 6 | import net.minecraft.world.entity.EntityType; 7 | import net.minecraft.world.item.Item; 8 | import net.minecraft.world.item.Items; 9 | import net.minecraft.world.level.block.Block; 10 | import net.minecraft.world.level.block.Blocks; 11 | 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.nio.file.StandardOpenOption; 16 | import java.util.List; 17 | 18 | public class TSGeneratorRunner { 19 | 20 | public static void run(final Path folder) throws IOException { 21 | final Path definitionFile = folder.resolve("index.d.ts"); 22 | 23 | List> simple = List.of(EntityType.class, Items.class, Item.class, Blocks.class, Block.class, BlockPos.class); 24 | for (Class aClass : simple) { 25 | TSGenerator.mappings.put(aClass, aClass.getSimpleName()); 26 | } 27 | 28 | StringBuilder sb = new StringBuilder(); 29 | for (Class aClass : simple) { 30 | sb.append(TSGenerator.generateClazz(null, aClass)); 31 | } 32 | sb.append(TSGenerator.generateClazz("helper", GameTestHelper.class)); 33 | sb.append(TSGenerator.generateClazz("registry", MiniTestFramework.class)); 34 | 35 | Files.writeString(definitionFile, sb, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 36 | 37 | final Path moduleFile = folder.resolve("index.mjs"); 38 | sb = new StringBuilder(); 39 | for (Class aClass : simple) { 40 | sb.append("export const ").append(aClass.getSimpleName()).append(" = Java.type('").append(aClass.getName()).append("');\n"); 41 | } 42 | sb.append("export const registry = Polyglot.import('registry');\n"); 43 | sb.append("export const helper = Polyglot.import('helper');\n"); 44 | 45 | Files.writeString(moduleFile, sb, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/dev/benndorf/minitestframework/TSGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package dev.benndorf.minitestframework; 2 | 3 | import dev.benndorf.minitestframework.ts.TSGeneratorRunner; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Path; 8 | 9 | class TSGeneratorTest { 10 | @Test 11 | public void test() throws IOException { 12 | TSGeneratorRunner.run(Path.of("run/plugins/MiniTestFramework/minitestframework")); 13 | } 14 | } 15 | --------------------------------------------------------------------------------