Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
afde0d8
initial test work --- trying it out, this will fail im sure
ShaneBeee Feb 11, 2026
59c17c6
test - add temp auth
ShaneBeee Feb 11, 2026
7638dd2
test - lets try this again
ShaneBeee Feb 11, 2026
b4aeb08
delete test yml
ShaneBeee Feb 11, 2026
5ec42b4
gradle.yml - remove github test
ShaneBeee Feb 11, 2026
92f1846
More work on tests
ShaneBeee Feb 11, 2026
df6162c
more test stuff
ShaneBeee Feb 11, 2026
83340a3
MORE test stuff
ShaneBeee Feb 11, 2026
fdfe258
MORE test stuff... again
ShaneBeee Feb 11, 2026
82e6af0
build.gradle.kts - didnt mean to commit my test parser
ShaneBeee Feb 11, 2026
e56a079
Skript - change when test start
ShaneBeee Feb 11, 2026
8775ca3
TestResults - use TreeMap to sort results
ShaneBeee Feb 11, 2026
063745f
add a few more basic tests
ShaneBeee Feb 11, 2026
150b814
add more tests
ShaneBeee Feb 11, 2026
c6fda73
More test changes
ShaneBeee Feb 11, 2026
9f751b1
Merge branch 'dev/patch' into dev/testing
ShaneBeee Feb 11, 2026
06b683d
ExprLocationDirection - fixed location offset
ShaneBeee Feb 11, 2026
7aa2b19
Merge branch 'dev/patch' into dev/testing
ShaneBeee Feb 12, 2026
092d401
TestRunner - run finish loading on addons to make sure functions regi…
ShaneBeee Feb 12, 2026
0620e9c
EffReturn.sk - functions fixed
ShaneBeee Feb 12, 2026
5907c3f
more tests
ShaneBeee Feb 12, 2026
703893d
TestRunner - more changes
ShaneBeee Feb 12, 2026
0518c30
Add test readme
ShaneBeee Feb 12, 2026
8cf4e30
Add test readme - wrong wordr
ShaneBeee Feb 12, 2026
d7e6da6
contributing.md - add link to test readme
ShaneBeee Feb 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ This way the team can discuss with you whether or not we want this in HySkript.
- Use descriptive commit messages
- Use descriptive PR titles
- Ensure you follow the code style of this project
- Do make sure you add tests and run the test to make sure it works.
- See the [Testing Guide](https://github.com/SkriptDev/HySkript/tree/master/src/test/README.md) for more information.

### Don't:
- Don't commit directly to `master`
Expand Down Expand Up @@ -60,3 +62,6 @@ This way the team can discuss with you whether or not we want this in HySkript.
- For expressions, please provide an example of using the getter as well as each changer you have applied.
- For all others, please provide at least one example per pattern.
- Please see other examples in HySkript for further inspiration.

### Tests:
See the [Testing Guide](https://github.com/SkriptDev/HySkript/tree/master/src/test/README.md) for more information.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle
name: Build and Upload Artifact

on:
push:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ bin/
### Mac OS ###
.DS_Store
**/.DS_Store

### Run Folder ###
run/
19 changes: 19 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ val hytaleVersion = "2026.02.06-aa1b071c2"
// https://maven.hytale.com/pre-release/com/hypixel/hytale/Server/maven-metadata.xml
// (Pre-releases shouldn't be used for production)

// Location of the Hytale Server Assets
// This is used in testing
// Change this to wherever you have it on your computer
val assetLocation = "/Users/ShaneBee/Desktop/Server/Hytale/Assets/Assets.zip"

repositories {
mavenCentral()
mavenLocal()
Expand All @@ -34,6 +39,12 @@ dependencies {
}
}

// This is used to enable Gson in the test environment via HytaleServer
val testRunnerClasspath by configurations.creating {
extendsFrom(configurations.compileOnly.get())
isCanBeResolved = true
}

tasks {
register("server", Copy::class) {
dependsOn("jar")
Expand All @@ -42,6 +53,14 @@ tasks {
destinationDir = file("/Users/ShaneBee/Desktop/Server/Hytale/Creative/mods/")
}
}
register<JavaExec>("testRunner") {
dependsOn("jar")
group = "application"
mainClass.set("com.github.skriptdev.skript.api.skript.testing.TestRunnerMain")
args(hytaleVersion, assetLocation)

classpath = sourceSets["main"].runtimeClasspath + testRunnerClasspath
}
processResources {
filesNotMatching("assets/**") {
expand("pluginVersion" to projectVersion, "hytaleVersion" to hytaleVersion)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.github.skriptdev.skript.api.skript.testing;

public class TestProperties {

public static final boolean ENABLED = Boolean.parseBoolean(System.getProperty("skript.test.enabled", "false"));
public static final String TEST_SCRIPTS_FOLDER = System.getProperty("skript.test.scripts", "mods/skript_HySkript/tests");

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.github.skriptdev.skript.api.skript.testing;

import com.github.skriptdev.skript.api.utils.Utils;
import com.github.skriptdev.skript.plugin.HySk;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class TestResults {

private boolean success = true;
private int failCount = 0;
private final Map<String, List<String>> successMap = new TreeMap<>();
private final Map<String, List<String>> failureMap = new TreeMap<>();

public boolean isSuccess() {
return this.success;
}

public int getFailCount() {
return this.failCount;
}

public Map<String, List<String>> getSuccessMap() {
return this.successMap;
}

public Map<String, List<String>> getFailureMap() {
return this.failureMap;
}

public void addSuccess(String test, String value) {
this.successMap.computeIfAbsent(test, _ -> new ArrayList<>()).add(value);
}

public void addFailure(String test, String value) {
this.success = false;
this.failCount++;
this.failureMap.computeIfAbsent(test, _ -> new ArrayList<>()).add(value);
}

public void process() {
this.failureMap.forEach((test, _) -> {
// We don't care about success if other tests failed in that test
this.successMap.remove(test);
});

}

public void clear() {
this.success = true;
this.successMap.clear();
this.failureMap.clear();
}

@SuppressWarnings({"CallToPrintStackTrace"})
public void printToJsonFile() {
Path resolve = HySk.getInstance().getDataDirectory().resolve("test-results.json");

try {
Files.createDirectories(resolve.getParent());
} catch (IOException e) {
throw new RuntimeException("Failed to create directories for " + resolve.toAbsolutePath(), e);
}

Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();

try (BufferedWriter writer = Files.newBufferedWriter(resolve, StandardCharsets.UTF_8)) {
gson.toJson(this, writer);
Utils.log("Test-Results successfully written to " + resolve.toAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.github.skriptdev.skript.api.skript.testing;

import com.github.skriptdev.skript.api.skript.testing.elements.EvtTest.TestContext;
import com.github.skriptdev.skript.api.utils.Utils;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.util.MessageUtil;
import fi.sulku.hytale.TinyMsg;
import io.github.syst3ms.skriptparser.lang.Statement;
import io.github.syst3ms.skriptparser.lang.Trigger;
import io.github.syst3ms.skriptparser.lang.TriggerMap;
import io.github.syst3ms.skriptparser.log.LogEntry;
import io.github.syst3ms.skriptparser.log.LogType;
import io.github.syst3ms.skriptparser.parsing.ScriptLoader;
import io.github.syst3ms.skriptparser.registration.SkriptAddon;
import io.github.syst3ms.skriptparser.variables.Variables;

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestRunner {

private static final ScheduledExecutorService SCHEDULED = HytaleServer.SCHEDULED_EXECUTOR;
private final TestResults testResults = new TestResults();
private final World world = Universe.get().getWorld("default");

@SuppressWarnings("DataFlowIssue")
public void start() {
Runnable runTestsRunnable = () -> {
Utils.log("Running tests in world 'default'...");
runTests();
};
Runnable loadTestsRunnable = () -> {
Utils.log("Testing has started!");
Utils.log("Loading test scripts...");
loadTests();
Utils.log("Finished loading test scripts!");

// Make sure the world isn't paused
if (this.world.isPaused()) this.world.setPaused(false);

// Run our tests in the world to make sure we have access to blocks/entities
this.world.execute(runTestsRunnable);
};

// Delay start to make sure the server has finished loading
SCHEDULED.schedule(loadTestsRunnable, 1, TimeUnit.SECONDS);
}

private void loadTests() {
Path path = Path.of(TestProperties.TEST_SCRIPTS_FOLDER);
loadScripts(path);
}

private void runTests() {
// Catch exceptions and treat them as failures
Statement.setExceptionHandler(e ->
this.testResults.addFailure("Exception",
e.getClass().getSimpleName() + ": " + e.getMessage()));

// Run all the test triggers
for (Trigger allTrigger : TriggerMap.getAllTriggers()) {
TestContext context = new TestContext(this.testResults, this.world);
Statement.runAll(allTrigger, context);
Variables.clearLocalVariables(context);
}

Runnable finishingRunnable = () -> {
// Process results
this.testResults.process();

// Print results
if (this.testResults.isSuccess()) {
Message message = TinyMsg.parse("<green>All tests passed!");
Utils.log(MessageUtil.toAnsiString(message).toAnsi());
} else {
Utils.error(this.testResults.getFailCount() + " tests failed!");
this.testResults.getFailureMap().forEach((test, failure) ->
Utils.error(" - [" + test + "]: " + failure));
}

Utils.log("Finished running tests!");

// Print results to file
this.testResults.printToJsonFile();
this.testResults.clear();

// Shutdown server
// Give it a bit of delay to really make sure we're finished
SCHEDULED.schedule(() -> HytaleServer.get().shutdownServer(), 1, TimeUnit.SECONDS);
};

// Delay finish to allow waits in tests
SCHEDULED.schedule(finishingRunnable, 1, TimeUnit.SECONDS);
}

private void loadScripts(Path directory) {
File scriptsDirectory = directory.toFile();
Utils.log("Loading test directory '" + scriptsDirectory.getAbsolutePath() + "'...");
List<String> scriptNames = loadScriptsInDirectory(scriptsDirectory);
SkriptAddon.getAddons().forEach(SkriptAddon::finishedLoading);

Utils.log("Loaded " + scriptNames.size() + " scripts!");
}

private List<String> loadScriptsInDirectory(File directory) {
if (directory == null || !directory.isDirectory()) return List.of();

List<String> loadedScripts = new ArrayList<>();

File[] files = directory.listFiles();
if (files == null) return loadedScripts;

Arrays.sort(files,
Comparator.comparing(File::isDirectory).reversed() // Directories first
.thenComparing(File::getName, String.CASE_INSENSITIVE_ORDER)); // Then sort by name alphabetically

for (File file : files) {
// Skip disabled files and hidden files
String fileName = file.getName();
if (fileName.startsWith("-") || fileName.startsWith(".")) continue;
if (file.isDirectory()) {
loadedScripts.addAll(loadScriptsInDirectory(file));
} else {
if (!fileName.endsWith(".sk")) continue;
Utils.log("Loading script '" + fileName + "'...");
List<LogEntry> logEntries = ScriptLoader.loadScript(file.toPath(), false);
for (LogEntry logEntry : logEntries) {
Utils.log(null, logEntry);
if (logEntry.getType() == LogType.ERROR) {
this.testResults.addFailure("Parsing Error:" + fileName, logEntry.getMessage());
}
}
loadedScripts.add(fileName.substring(0, fileName.length() - 3));
}
}
return loadedScripts;
}

}
Loading
Loading