Skip to content

Commit

Permalink
Fix tests running while world has not loaded (#7379)
Browse files Browse the repository at this point in the history
* init commit

* use chunks

* oops

* use non-spawn-chunk chunk

---------

Co-authored-by: Moderocky <admin@moderocky.com>
  • Loading branch information
Efnilite and Moderocky authored Jan 9, 2025
1 parent 42b6198 commit 1f64752
Showing 1 changed file with 141 additions and 142 deletions.
283 changes: 141 additions & 142 deletions src/main/java/ch/njol/skript/Skript.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.registrations.EventValues;
import ch.njol.skript.registrations.Feature;
import ch.njol.skript.test.runner.EffObjectives;
import ch.njol.skript.test.runner.SkriptAsyncJUnitTest;
import ch.njol.skript.test.runner.SkriptJUnitTest;
import ch.njol.skript.test.runner.SkriptTestEvent;
import ch.njol.skript.test.runner.TestMode;
import ch.njol.skript.test.runner.TestTracker;
import ch.njol.skript.test.runner.*;
import ch.njol.skript.timings.SkriptTimings;
import ch.njol.skript.update.ReleaseManifest;
import ch.njol.skript.update.ReleaseStatus;
Expand Down Expand Up @@ -67,7 +62,6 @@
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.skriptlang.skript.bukkit.SkriptMetrics;
import org.skriptlang.skript.bukkit.tags.TagModule;
import org.skriptlang.skript.bukkit.breeding.BreedingModule;
import org.skriptlang.skript.bukkit.displays.DisplayModule;
import org.skriptlang.skript.bukkit.fishing.FishingModule;
Expand All @@ -77,16 +71,17 @@
import org.skriptlang.skript.bukkit.loottables.LootTableModule;
import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys;
import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
import org.skriptlang.skript.bukkit.tags.TagModule;
import org.skriptlang.skript.lang.comparator.Comparator;
import org.skriptlang.skript.lang.comparator.Comparators;
import org.skriptlang.skript.lang.converter.Converter;
import org.skriptlang.skript.lang.converter.Converters;
import org.skriptlang.skript.lang.entry.EntryValidator;
import org.skriptlang.skript.log.runtime.RuntimeErrorManager;
import org.skriptlang.skript.lang.experiment.ExperimentRegistry;
import org.skriptlang.skript.lang.script.Script;
import org.skriptlang.skript.lang.structure.Structure;
import org.skriptlang.skript.lang.structure.StructureInfo;
import org.skriptlang.skript.log.runtime.RuntimeErrorManager;
import org.skriptlang.skript.registration.SyntaxInfo;
import org.skriptlang.skript.registration.SyntaxOrigin;
import org.skriptlang.skript.registration.SyntaxRegistry;
Expand All @@ -105,7 +100,6 @@
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
Expand Down Expand Up @@ -675,122 +669,7 @@ protected void afterErrors() {
debug("Early init done");

if (TestMode.ENABLED) {
// Ignore late init (scripts, etc.) in test mode
Bukkit.getScheduler().runTaskLater(Skript.this, () -> {
info("Skript testing environment enabled, starting...");

// Delay is in Minecraft ticks.
AtomicLong shutdownDelay = new AtomicLong(0);
List<Class<?>> asyncTests = new ArrayList<>();
CompletableFuture<Void> onAsyncComplete = CompletableFuture.completedFuture(null);

if (TestMode.GEN_DOCS) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "skript gen-docs");
} else if (TestMode.DEV_MODE) { // Developer controlled environment.
info("Test development mode enabled. Test scripts are at " + TestMode.TEST_DIR);
return;
} else {
info("Loading all tests from " + TestMode.TEST_DIR);

// Treat parse errors as fatal testing failure
CountingLogHandler errorCounter = new CountingLogHandler(Level.SEVERE);
try {
errorCounter.start();
File testDir = TestMode.TEST_DIR.toFile();
assert testDir != null;
ScriptLoader.loadScripts(testDir, errorCounter);
} finally {
errorCounter.stop();
}

Bukkit.getPluginManager().callEvent(new SkriptTestEvent());
if (errorCounter.getCount() > 0) {
TestTracker.testStarted("parse scripts");
TestTracker.testFailed(errorCounter.getCount() + " error(s) found");
}
if (errored) { // Check for exceptions thrown while script was executing
TestTracker.testStarted("run scripts");
TestTracker.testFailed("exception was thrown during execution");
}
if (TestMode.JUNIT) {
AtomicLong milliseconds = new AtomicLong(0),
tests = new AtomicLong(0), fails = new AtomicLong(0),
ignored = new AtomicLong(0), size = new AtomicLong(0);

info("Running sync JUnit tests...");
try {
List<Class<?>> classes = Lists.newArrayList(Utils.getClasses(Skript.getInstance(), "org.skriptlang.skript.test", "tests"));
// Don't attempt to run inner/anonymous classes as tests
classes.removeIf(Class::isAnonymousClass);
classes.removeIf(Class::isLocalClass);
// Test that requires package access. This is only present when compiling with src/test.
classes.add(Class.forName("ch.njol.skript.variables.FlatFileStorageTest"));
size.set(classes.size());
for (Class<?> clazz : classes) {
if (SkriptAsyncJUnitTest.class.isAssignableFrom(clazz)) {
asyncTests.add(clazz); // do these later, all together
continue;
}

runTest(clazz, shutdownDelay, tests, milliseconds, ignored, fails);
}
} catch (IOException e) {
Skript.exception(e, "Failed to execute JUnit runtime tests.");
} catch (ClassNotFoundException e) {
// Should be the Skript test jar gradle task.
assert false : "Class 'ch.njol.skript.variables.FlatFileStorageTest' was not found.";
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException | SecurityException e) {
Skript.exception(e, "Failed to initalize test JUnit classes.");
}
if (ignored.get() > 0)
Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!");

onAsyncComplete = CompletableFuture.runAsync(() -> {
info("Running async JUnit tests...");
try {
for (Class<?> clazz : asyncTests) {
runTest(clazz, shutdownDelay, tests, milliseconds, ignored, fails);
}
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException | SecurityException e) {
Skript.exception(e, "Failed to initalize test JUnit classes.");
}
if (ignored.get() > 0)
Skript.warning("There were " + ignored + " ignored test cases! " +
"This can mean they are not properly setup in order in that class!");

info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails +
" failures in " + milliseconds + " milliseconds.");
});
}
}

onAsyncComplete.thenRun(() -> {
double display = shutdownDelay.get() / 20.0;
info("Testing done, shutting down the server in " + display + " second" + (display == 1 ? "" : "s") + "...");

// Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests
Bukkit.getScheduler().runTaskLater(Skript.this, () -> {
info("Shutting down server.");
if (TestMode.JUNIT && !EffObjectives.isJUnitComplete())
EffObjectives.fail();

info("Collecting results to " + TestMode.RESULTS_FILE);
String results = new GsonBuilder()
.setPrettyPrinting() // Easier to read lines
.disableHtmlEscaping() // Fixes issue with "'" character in test strings going unicode
.create().toJson(TestTracker.collectResults());
try {
Files.write(TestMode.RESULTS_FILE, results.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Skript.exception(e, "Failed to write test results.");
}

Bukkit.getServer().shutdown();
}, shutdownDelay.get());
});
}, 50); // 50 ticks is necessary on 1.19.4/1.20.4 to allow the server time to properly load/start ticking, for some reason
Bukkit.getWorlds().get(0).getChunkAtAsync(100, 100).thenRun(() -> runTests());
}

Skript.metrics = new Metrics(Skript.getInstance(), 722); // 722 is our bStats plugin ID
Expand Down Expand Up @@ -855,29 +734,33 @@ protected void afterErrors() {
}

// Send a warning to console when the plugin is reloaded
Bukkit.getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onServerReload(ServerLoadEvent event) {
if ((event.getType() != ServerLoadEvent.LoadType.RELOAD))
return;
Bukkit.getPluginManager().registerEvents(new ServerReloadListener(), this);

for (OfflinePlayer player : Bukkit.getOperators()) {
if (player.isOnline()) {
player.getPlayer().sendMessage(ChatColor.YELLOW + getWarningMessage());
player.getPlayer().sendMessage(ChatColor.YELLOW + getRestartMessage());
}
}
// Tell Timings that we are here!
SkriptTimings.setSkript(this);
}

Skript.warning(getWarningMessage());
Skript.warning(getRestartMessage());
private static class ServerReloadListener implements Listener {

@EventHandler
public void onServerReload(ServerLoadEvent event) {
if ((event.getType() != ServerLoadEvent.LoadType.RELOAD))
return;

for (OfflinePlayer player : Bukkit.getOperators()) {
if (player.isOnline()) {
player.getPlayer().sendMessage(ChatColor.YELLOW + getWarningMessage());
player.getPlayer().sendMessage(ChatColor.YELLOW + getRestartMessage());
}
}
}, this);

// Tell Timings that we are here!
SkriptTimings.setSkript(this);
Skript.warning(getWarningMessage());
Skript.warning(getRestartMessage());
}
}

private class JoinUpdateNotificationListener implements Listener {

@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (!event.getPlayer().hasPermission("skript.admin"))
Expand Down Expand Up @@ -905,7 +788,123 @@ public void run() {
}
};
}
}
}

private void runTests() {
info("Skript testing environment enabled, starting...");

// Delay is in Minecraft ticks.
AtomicLong shutdownDelay = new AtomicLong(0);
List<Class<?>> asyncTests = new ArrayList<>();
CompletableFuture<Void> onAsyncComplete = CompletableFuture.completedFuture(null);

if (TestMode.GEN_DOCS) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "skript gen-docs");
} else if (TestMode.DEV_MODE) { // Developer controlled environment.
info("Test development mode enabled. Test scripts are at " + TestMode.TEST_DIR);
return;
} else {
info("Loading all tests from " + TestMode.TEST_DIR);

// Treat parse errors as fatal testing failure
CountingLogHandler errorCounter = new CountingLogHandler(Level.SEVERE);
try {
errorCounter.start();
File testDir = TestMode.TEST_DIR.toFile();
assert testDir != null;
ScriptLoader.loadScripts(testDir, errorCounter);
} finally {
errorCounter.stop();
}

Bukkit.getPluginManager().callEvent(new SkriptTestEvent());
if (errorCounter.getCount() > 0) {
TestTracker.testStarted("parse scripts");
TestTracker.testFailed(errorCounter.getCount() + " error(s) found");
}
if (errored) { // Check for exceptions thrown while script was executing
TestTracker.testStarted("run scripts");
TestTracker.testFailed("exception was thrown during execution");
}
if (TestMode.JUNIT) {
AtomicLong milliseconds = new AtomicLong(0),
tests = new AtomicLong(0), fails = new AtomicLong(0),
ignored = new AtomicLong(0), size = new AtomicLong(0);

info("Running sync JUnit tests...");
try {
List<Class<?>> classes = Lists.newArrayList(Utils.getClasses(Skript.getInstance(), "org.skriptlang.skript.test", "tests"));
// Don't attempt to run inner/anonymous classes as tests
classes.removeIf(Class::isAnonymousClass);
classes.removeIf(Class::isLocalClass);
// Test that requires package access. This is only present when compiling with src/test.
classes.add(Class.forName("ch.njol.skript.variables.FlatFileStorageTest"));
size.set(classes.size());
for (Class<?> clazz : classes) {
if (SkriptAsyncJUnitTest.class.isAssignableFrom(clazz)) {
asyncTests.add(clazz); // do these later, all together
continue;
}

runTest(clazz, shutdownDelay, tests, milliseconds, ignored, fails);
}
} catch (IOException e) {
Skript.exception(e, "Failed to execute JUnit runtime tests.");
} catch (ClassNotFoundException e) {
// Should be the Skript test jar gradle task.
assert false : "Class 'ch.njol.skript.variables.FlatFileStorageTest' was not found.";
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException | SecurityException e) {
Skript.exception(e, "Failed to initalize test JUnit classes.");
}
if (ignored.get() > 0)
Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!");

onAsyncComplete = CompletableFuture.runAsync(() -> {
info("Running async JUnit tests...");
try {
for (Class<?> clazz : asyncTests) {
runTest(clazz, shutdownDelay, tests, milliseconds, ignored, fails);
}
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException | SecurityException e) {
Skript.exception(e, "Failed to initalize test JUnit classes.");
}
if (ignored.get() > 0)
Skript.warning("There were " + ignored + " ignored test cases! " +
"This can mean they are not properly setup in order in that class!");

info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails +
" failures in " + milliseconds + " milliseconds.");
});
}
}

onAsyncComplete.thenRun(() -> {
double display = shutdownDelay.get() / 20.0;
info("Testing done, shutting down the server in " + display + " second" + (display == 1 ? "" : "s") + "...");

// Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests
Bukkit.getScheduler().runTaskLater(Skript.this, () -> {
info("Shutting down server.");
if (TestMode.JUNIT && !EffObjectives.isJUnitComplete())
EffObjectives.fail();

info("Collecting results to " + TestMode.RESULTS_FILE);
String results = new GsonBuilder()
.setPrettyPrinting() // Easier to read lines
.disableHtmlEscaping() // Fixes issue with "'" character in test strings going unicode
.create().toJson(TestTracker.collectResults());
try {
Files.write(TestMode.RESULTS_FILE, results.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Skript.exception(e, "Failed to write test results.");
}

Bukkit.getServer().shutdown();
}, shutdownDelay.get());
});
}

private void runTest(Class<?> clazz, AtomicLong shutdownDelay, AtomicLong tests,
AtomicLong milliseconds, AtomicLong ignored, AtomicLong fails)
Expand Down

0 comments on commit 1f64752

Please sign in to comment.