Continue debug test-home naming

This commit is contained in:
2026-09-27 07:07:12 -07:00
parent 9be629d82e
commit f68d043d02
4 changed files with 233 additions and 9 deletions

View File

@@ -85,6 +85,7 @@ Run `./gradlew test` for the server-free JUnit suite. It has 26 tests covering:
- `MetropolisPlotGoCommandTest`: self and target teleportation, ambiguous memberships, absent membership, and unsafe-destination feedback. - `MetropolisPlotGoCommandTest`: self and target teleportation, ambiguous memberships, absent membership, and unsafe-destination feedback.
- `MetropolisPlotReserveCommandTest`: parsing six coordinates and rejecting malformed coordinates. - `MetropolisPlotReserveCommandTest`: parsing six coordinates and rejecting malformed coordinates.
- `MetropolisHomeAcquireTest`: acquisition below the limit and refusal at the limit. - `MetropolisHomeAcquireTest`: acquisition below the limit and refusal at the limit.
- `MetropolisDebugGenerateTestHomesCommandTest`: asynchronous tick-queued generation, one home per interval, no overlapping batches, and continuing test-player names across repeated runs.
The two opt-in tests in `src/integrationTest` require a running Paper server and RCON. They check that Metropolis appears in `/plugins` and that a coordinate reservation is persisted by WorldGuard. They do not currently verify restart persistence, join allocation, WorldEdit-selection reservations, permissions, teleports, generated blocks, protection behavior, or the other command workflows above. Run them with the environment variables documented in `README.md`; use a disposable server because the reservation test changes its WorldGuard data. The two opt-in tests in `src/integrationTest` require a running Paper server and RCON. They check that Metropolis appears in `/plugins` and that a coordinate reservation is persisted by WorldGuard. They do not currently verify restart persistence, join allocation, WorldEdit-selection reservations, permissions, teleports, generated blocks, protection behavior, or the other command workflows above. Run them with the environment variables documented in `README.md`; use a disposable server because the reservation test changes its WorldGuard data.

View File

@@ -89,7 +89,7 @@ Assign these permission nodes to groups or individual users in your permission p
| `/metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>` | `metropolis.plot.reserve` | Creates a named reservation from explicit bounds. A player can instead run `/metropolis-plot-reserve <name>` with a WorldEdit selection. | | `/metropolis-plot-reserve <name> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>` | `metropolis.plot.reserve` | Creates a named reservation from explicit bounds. A player can instead run `/metropolis-plot-reserve <name>` with a WorldEdit selection. |
| `/metropolis-plot-go [playerName]` | Reservation owner/member; `metropolis.plot.go` to target others or bypass membership | Teleports the sender or an online target to a reservation they own or belong to. If they have several, specify one with `/metropolis-plot-go <reservationName> [playerName]`. | | `/metropolis-plot-go [playerName]` | Reservation owner/member; `metropolis.plot.go` to target others or bypass membership | Teleports the sender or an online target to a reservation they own or belong to. If they have several, specify one with `/metropolis-plot-go <reservationName> [playerName]`. |
| `/metropolis-flag-reset` | `metropolis.flag.reset` | Reapplies Metropolis protection flags to the City and home regions. | | `/metropolis-flag-reset` | `metropolis.flag.reset` | Reapplies Metropolis protection flags to the City and home regions. |
| `/metropolis-debug-generatetesthomes <count>` | `metropolis.debug` | Generates test homes; intended for development servers. | | `/metropolis-debug-generatetesthomes <count>` | `metropolis.debug` | Queues test-home generation on the main thread, creating one home every two ticks; intended for development servers. |
## License ## License

View File

@@ -17,15 +17,22 @@ along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
package com.majinnaibu.bukkitplugins.metropolis.commands; package com.majinnaibu.bukkitplugins.metropolis.commands;
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin; import java.util.logging.Level;
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitTask;
public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor { public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor {
private static final long TICKS_BETWEEN_HOMES = 2L;
private MetropolisPlugin _plugin; private final MetropolisPlugin _plugin;
private BukkitTask _activeTask;
public MetropolisDebugGenerateTestHomesCommand(MetropolisPlugin plugin) { public MetropolisDebugGenerateTestHomesCommand(MetropolisPlugin plugin) {
_plugin = plugin; _plugin = plugin;
@@ -33,15 +40,102 @@ public class MetropolisDebugGenerateTestHomesCommand implements CommandExecutor
@Override @Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
try { if (args.length != 1) {
int numHomes = Integer.parseInt(args[0]); sender.sendMessage("Usage: /metropolis-debug-generatetesthomes <count>");
for (int i = 1; i <= numHomes; i++) { return false;
_plugin.generateHome(String.format("test%d", i));
} }
return true; int numHomes;
try {
numHomes = Integer.parseInt(args[0]);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
sender.sendMessage("Usage: /metropolis-debug-generatetesthomes <count>");
return false; return false;
} }
if (numHomes < 1) {
sender.sendMessage("Usage: /metropolis-debug-generatetesthomes <count>");
return false;
}
if (_activeTask != null && !_activeTask.isCancelled()) {
sender.sendMessage("[Metropolis] Test-home generation is already in progress.");
return false;
}
TestHomeBatch batch = new TestHomeBatch(sender, numHomes);
_activeTask =
_plugin.getServer().getScheduler().runTaskTimer(_plugin, batch, 1L, TICKS_BETWEEN_HOMES);
sender.sendMessage(
"[Metropolis] Queued "
+ numHomes
+ " test homes; generating one every "
+ TICKS_BETWEEN_HOMES
+ " ticks.");
return true;
}
private final class TestHomeBatch implements Runnable {
private final CommandSender sender;
private final int requestedHomes;
private int generatedHomes;
private int nextTestNumber = 1;
private boolean finished;
private TestHomeBatch(CommandSender sender, int requestedHomes) {
this.sender = sender;
this.requestedHomes = requestedHomes;
}
@Override
public void run() {
if (finished) {
return;
}
int testNumber = nextTestNumber++;
String testName = "Test" + testNumber;
try {
OfflinePlayer testPlayer = _plugin.getServer().getOfflinePlayer(testName);
if (_plugin.getNumPlots(testPlayer.getUniqueId()) > 0) {
return;
}
PlayerHome home = _plugin.generateHome(testPlayer, 1);
if (home == null) {
sender.sendMessage(
"[Metropolis] Could not generate "
+ testName
+ "; completed "
+ generatedHomes
+ " of "
+ requestedHomes
+ " test homes.");
finish();
return;
}
generatedHomes++;
if (generatedHomes == requestedHomes) {
sender.sendMessage("[Metropolis] Generated " + generatedHomes + " test homes.");
finish();
}
} catch (RuntimeException exception) {
_plugin
.getLogger()
.log(Level.SEVERE, "Failed to generate test home " + testName, exception);
sender.sendMessage(
"[Metropolis] Test-home generation failed after " + generatedHomes + " homes.");
finish();
}
}
private void finish() {
finished = true;
if (_activeTask != null) {
_activeTask.cancel();
_activeTask = null;
}
}
} }
} }

View File

@@ -0,0 +1,129 @@
/*
This file is part of Metropolis.
Metropolis is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Metropolis is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Metropolis. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
*/
package com.majinnaibu.bukkitplugins.metropolis.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.majinnaibu.bukkitplugins.metropolis.MetropolisPlugin;
import com.majinnaibu.bukkitplugins.metropolis.PlayerHome;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.junit.jupiter.api.Test;
class MetropolisDebugGenerateTestHomesCommandTest {
@Test
void repeatedRunsContinueWithTheNextUnusedTestName() {
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
Server server = mock(Server.class);
BukkitScheduler scheduler = mock(BukkitScheduler.class);
CommandSender sender = mock(CommandSender.class);
Set<UUID> existingHomeOwners = new HashSet<>();
List<String> generatedNames = new ArrayList<>();
List<Runnable> scheduledBatches = new ArrayList<>();
when(plugin.getServer()).thenReturn(server);
when(server.getScheduler()).thenReturn(scheduler);
when(scheduler.runTaskTimer(eq(plugin), any(Runnable.class), eq(1L), eq(2L)))
.thenAnswer(
invocation -> {
scheduledBatches.add(invocation.getArgument(1));
return mock(BukkitTask.class);
});
when(server.getOfflinePlayer(anyString()))
.thenAnswer(
invocation -> {
String name = invocation.getArgument(0);
OfflinePlayer player = mock(OfflinePlayer.class);
UUID playerId = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
when(player.getName()).thenReturn(name);
when(player.getUniqueId()).thenReturn(playerId);
return player;
});
when(plugin.getNumPlots(any(UUID.class)))
.thenAnswer(invocation -> existingHomeOwners.contains(invocation.getArgument(0)) ? 1 : 0);
when(plugin.generateHome(any(OfflinePlayer.class), eq(1)))
.thenAnswer(
invocation -> {
OfflinePlayer player = invocation.getArgument(0);
generatedNames.add(player.getName());
existingHomeOwners.add(player.getUniqueId());
return mock(PlayerHome.class);
});
MetropolisDebugGenerateTestHomesCommand executor =
new MetropolisDebugGenerateTestHomesCommand(plugin);
assertTrue(executor.onCommand(sender, mock(Command.class), "testhomes", new String[] {"10"}));
assertTrue(generatedNames.isEmpty());
assertEquals(1, scheduledBatches.size());
assertFalse(executor.onCommand(sender, mock(Command.class), "testhomes", new String[] {"10"}));
scheduledBatches.getFirst().run();
assertEquals(List.of("Test1"), generatedNames);
for (int tick = 1; tick < 10; tick++) {
scheduledBatches.getFirst().run();
}
assertEquals(10, generatedNames.size());
assertTrue(executor.onCommand(sender, mock(Command.class), "testhomes", new String[] {"10"}));
assertEquals(2, scheduledBatches.size());
for (int tick = 0; tick < 20; tick++) {
scheduledBatches.get(1).run();
}
List<String> expectedNames = new ArrayList<>();
for (int testNumber = 1; testNumber <= 20; testNumber++) {
expectedNames.add("Test" + testNumber);
}
assertEquals(expectedNames, generatedNames);
}
@Test
void rejectsNonPositiveCountsWithoutGeneratingHomes() {
MetropolisPlugin plugin = mock(MetropolisPlugin.class);
MetropolisDebugGenerateTestHomesCommand executor =
new MetropolisDebugGenerateTestHomesCommand(plugin);
assertFalse(
executor.onCommand(
mock(CommandSender.class), mock(Command.class), "testhomes", new String[] {"0"}));
verify(plugin, never()).generateHome(any(OfflinePlayer.class), eq(1));
}
}