From 0a0dd23a732084fe7698c4e3bc7273a6a2af3d1e Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 13:59:36 -0600 Subject: [PATCH 1/8] Stop file thread safely on interrupt --- .../java/com/bencodez/advancedcore/thread/FileThread.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/FileThread.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/FileThread.java index d235193e24..cd99a21afe 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/FileThread.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/FileThread.java @@ -115,12 +115,12 @@ public boolean hasPlayerFile(String uuid) { @Override public void run() { - while (!thread.isInterrupted()) { + while (!isInterrupted()) { try { sleep(50); } catch (InterruptedException e) { - e.printStackTrace(); - System.exit(0); + interrupt(); + break; } } } @@ -214,4 +214,4 @@ public void loadThread() { public void run(Runnable run) { getThread().run(run); } -} \ No newline at end of file +} From 55ec805108a291e8563eb9f0989f270c1247e87d Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 13:59:46 -0600 Subject: [PATCH 2/8] Stop legacy thread safely on interrupt --- .../main/java/com/bencodez/advancedcore/thread/Thread.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/Thread.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/Thread.java index c3fd4ac81d..87cd77392f 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/Thread.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/thread/Thread.java @@ -29,12 +29,12 @@ public String getName(java.util.UUID uuid) { @Override public void run() { - while (!thread.isInterrupted()) { + while (!isInterrupted()) { try { sleep(50); } catch (InterruptedException e) { - e.printStackTrace(); - System.exit(0); + interrupt(); + break; } } } From 92cac8ecef36d428e68b3a60108727e26545eb95 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 14:00:57 -0600 Subject: [PATCH 3/8] Honor inventory update interval --- .../com/bencodez/advancedcore/api/inventory/BInventory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java index fb1b56ad9b..fd7273e2af 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventory.java @@ -394,7 +394,7 @@ public void addUpdatingButton(AdvancedCorePlugin plugin, long delay, long interv if (futures == null) { futures = new ArrayList<>(); } - futures.add(plugin.getInventoryTimer().scheduleWithFixedDelay(runnable, delay, delay, TimeUnit.MILLISECONDS)); + futures.add(plugin.getInventoryTimer().scheduleWithFixedDelay(runnable, delay, interval, TimeUnit.MILLISECONDS)); } /** From 5eca2b0539864e581237cb7dcfd02517bb805c6b Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 14:01:53 -0600 Subject: [PATCH 4/8] Fix deterministic MiscUtils command and date bugs --- .../advancedcore/api/misc/MiscUtils.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/MiscUtils.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/MiscUtils.java index c5cc386363..f0a8276fa2 100644 --- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/MiscUtils.java +++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/misc/MiscUtils.java @@ -72,7 +72,7 @@ private MiscUtils() { */ public Date addSeconds(Date date, int seconds) { Calendar c = Calendar.getInstance(); - c.setTime(new Date()); + c.setTime(date); c.add(Calendar.SECOND, seconds); return c.getTime(); } @@ -165,6 +165,7 @@ public void executeConsoleCommands(final ArrayList cmds, final HashMap for (final String cmd : commands) { plugin.debug("Executing console command: " + cmd); runConsoleCommand(cmd, tick, stagger); + tick++; } } @@ -191,7 +193,7 @@ public void executeConsoleCommands(Player player, String command, HashMap 0) { plugin.getBukkitScheduler().runTaskLater(plugin, new Runnable() { @Override public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), commandToRun); } }, delay); @@ -472,12 +473,19 @@ public void run() { @Override public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), commandToRun); } }); } } + private String stripLeadingSlash(String command) { + if (command != null && command.startsWith("/")) { + return command.substring(1); + } + return command; + } + public void setBlockMeta(Block block, String str, Object value) { block.removeMetadata(str, plugin); block.setMetadata(str, new MetadataValue() { From 171f9787979b6deeb420aea049f4f9167624e2f7 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 14:02:30 -0600 Subject: [PATCH 5/8] Test MiscUtils correctness fixes --- .../tests/misc/MiscUtilsTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/misc/MiscUtilsTest.java diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/misc/MiscUtilsTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/misc/MiscUtilsTest.java new file mode 100644 index 0000000000..306995e729 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/misc/MiscUtilsTest.java @@ -0,0 +1,86 @@ +package com.bencodez.advancedcore.tests.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.ConsoleCommandSender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +import com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.tests.BaseTest; +import com.bencodez.simpleapi.scheduler.BukkitScheduler; + +public class MiscUtilsTest { + + private AdvancedCorePlugin plugin; + private BukkitScheduler scheduler; + private MiscUtils miscUtils; + + @BeforeEach + public void setUp() throws Exception { + plugin = BaseTest.getInstance().plugin; + scheduler = mock(BukkitScheduler.class); + when(plugin.getBukkitScheduler()).thenReturn(scheduler); + miscUtils = MiscUtils.getInstance(); + + Field pluginField = MiscUtils.class.getDeclaredField("plugin"); + pluginField.setAccessible(true); + pluginField.set(miscUtils, plugin); + } + + @Test + public void addSecondsUsesProvidedDate() { + Date base = new Date(1_000_000L); + + Date result = miscUtils.addSeconds(base, 30); + + assertEquals(1_030_000L, result.getTime()); + } + + @Test + public void staggeredCommandsIncrementDelayForEachCommand() { + ArrayList commands = new ArrayList<>(List.of("first", "second", "third")); + + miscUtils.executeConsoleCommands(commands, new HashMap<>(), true); + + verify(scheduler).runTask(eq(plugin), any(Runnable.class)); + verify(scheduler).runTaskLater(eq(plugin), any(Runnable.class), eq(1L)); + verify(scheduler).runTaskLater(eq(plugin), any(Runnable.class), eq(2L)); + } + + @Test + public void consoleCommandsStripLeadingSlashBeforeDispatch() { + Server server = mock(Server.class); + ConsoleCommandSender console = mock(ConsoleCommandSender.class); + ArgumentCaptor task = ArgumentCaptor.forClass(Runnable.class); + + try (MockedStatic bukkit = mockStatic(Bukkit.class)) { + bukkit.when(Bukkit::getServer).thenReturn(server); + bukkit.when(Bukkit::getConsoleSender).thenReturn(console); + bukkit.when(() -> Bukkit.getOfflinePlayer("Ben")).thenReturn(null); + + miscUtils.executeConsoleCommands("Ben", "/say hi", new HashMap<>()); + + verify(scheduler).executeOrScheduleSync(eq(plugin), task.capture()); + task.getValue().run(); + verify(server).dispatchCommand(console, "say hi"); + } + } +} From 54cda193e2e0b1036324c8117259cb4b7e5fec82 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 14:02:38 -0600 Subject: [PATCH 6/8] Test inventory update interval --- .../tests/inventory/BInventoryTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java new file mode 100644 index 0000000000..6a1af8d788 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java @@ -0,0 +1,37 @@ +package com.bencodez.advancedcore.tests.inventory; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import com.bencodez.advancedcore.AdvancedCorePlugin; +import com.bencodez.advancedcore.api.inventory.BInventory; + +public class BInventoryTest { + + @Test + public void updatingButtonUsesConfiguredIntervalAndCancelsFuture() { + AdvancedCorePlugin plugin = mock(AdvancedCorePlugin.class); + ScheduledExecutorService timer = mock(ScheduledExecutorService.class); + @SuppressWarnings("unchecked") + ScheduledFuture future = mock(ScheduledFuture.class); + Runnable runnable = mock(Runnable.class); + when(plugin.getInventoryTimer()).thenReturn(timer); + when(timer.scheduleWithFixedDelay(eq(runnable), eq(100L), eq(250L), eq(TimeUnit.MILLISECONDS))) + .thenReturn(future); + + BInventory inventory = new BInventory("Test"); + inventory.addUpdatingButton(plugin, 100L, 250L, runnable); + inventory.cancelTimer(); + + verify(timer).scheduleWithFixedDelay(runnable, 100L, 250L, TimeUnit.MILLISECONDS); + verify(future).cancel(true); + } +} From 6c8f36714302664b23fb288946be9cb4b5872cd6 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 14:02:47 -0600 Subject: [PATCH 7/8] Test safe thread interruption --- .../tests/thread/ThreadInterruptionTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/thread/ThreadInterruptionTest.java diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/thread/ThreadInterruptionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/thread/ThreadInterruptionTest.java new file mode 100644 index 0000000000..ae9f3d680f --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/thread/ThreadInterruptionTest.java @@ -0,0 +1,34 @@ +package com.bencodez.advancedcore.tests.thread; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Test; + +import com.bencodez.advancedcore.thread.FileThread; + +public class ThreadInterruptionTest { + + @Test + public void fileReadThreadStopsWhenInterrupted() throws InterruptedException { + FileThread.ReadThread readThread = FileThread.getInstance().new ReadThread(); + + readThread.start(); + readThread.interrupt(); + readThread.join(1_000L); + + assertFalse(readThread.isAlive()); + } + + @Test + @SuppressWarnings("deprecation") + public void legacyReadThreadStopsWhenInterrupted() throws InterruptedException { + com.bencodez.advancedcore.thread.Thread.ReadThread readThread = com.bencodez.advancedcore.thread.Thread + .getInstance().new ReadThread(); + + readThread.start(); + readThread.interrupt(); + readThread.join(1_000L); + + assertFalse(readThread.isAlive()); + } +} From cbb79e338f6b5bb49ada5855174b67a0a061a805 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 23 Aug 2026 14:03:53 -0600 Subject: [PATCH 8/8] Fix inventory test wildcard stub --- .../advancedcore/tests/inventory/BInventoryTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java index 6a1af8d788..2943b530c5 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/inventory/BInventoryTest.java @@ -1,6 +1,7 @@ package com.bencodez.advancedcore.tests.inventory; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -17,15 +18,15 @@ public class BInventoryTest { @Test + @SuppressWarnings("rawtypes") public void updatingButtonUsesConfiguredIntervalAndCancelsFuture() { AdvancedCorePlugin plugin = mock(AdvancedCorePlugin.class); ScheduledExecutorService timer = mock(ScheduledExecutorService.class); - @SuppressWarnings("unchecked") - ScheduledFuture future = mock(ScheduledFuture.class); + ScheduledFuture future = mock(ScheduledFuture.class); Runnable runnable = mock(Runnable.class); when(plugin.getInventoryTimer()).thenReturn(timer); - when(timer.scheduleWithFixedDelay(eq(runnable), eq(100L), eq(250L), eq(TimeUnit.MILLISECONDS))) - .thenReturn(future); + doReturn(future).when(timer).scheduleWithFixedDelay(eq(runnable), eq(100L), eq(250L), + eq(TimeUnit.MILLISECONDS)); BInventory inventory = new BInventory("Test"); inventory.addUpdatingButton(plugin, 100L, 250L, runnable);