Add automated tests for commands

This commit is contained in:
Luck 2023-05-01 15:07:09 +01:00
parent 6d4ed224c8
commit bd08507150
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
11 changed files with 1204 additions and 25 deletions

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.command; package me.lucko.luckperms.common.command;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.lucko.luckperms.common.command.abstraction.Command; import me.lucko.luckperms.common.command.abstraction.Command;
@ -146,6 +147,11 @@ public class CommandManager {
return this.tabCompletions; return this.tabCompletions;
} }
@VisibleForTesting
public Map<String, Command<?>> getMainCommands() {
return this.mainCommands;
}
public CompletableFuture<Void> executeCommand(Sender sender, String label, List<String> args) { public CompletableFuture<Void> executeCommand(Sender sender, String label, List<String> args) {
UUID uniqueId = sender.getUniqueId(); UUID uniqueId = sender.getUniqueId();
if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !this.playerRateLimit.add(uniqueId)) { if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !this.playerRateLimit.add(uniqueId)) {

View File

@ -36,14 +36,15 @@ import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates; import me.lucko.luckperms.common.util.Predicates;
import net.kyori.adventure.text.Component;
import net.luckperms.api.node.Node; import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.InheritanceNode; import net.luckperms.api.node.types.InheritanceNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -69,7 +70,7 @@ public class HolderShowTracks<T extends PermissionHolder> extends ChildCommand<T
return; return;
} }
List<Map.Entry<Track, Component>> lines = new ArrayList<>(); List<Map.Entry<Track, InheritanceNode>> lines = new ArrayList<>();
if (target.getType() == HolderType.USER) { if (target.getType() == HolderType.USER) {
// if the holder is a user, we want to query parent groups for tracks // if the holder is a user, we want to query parent groups for tracks
@ -85,13 +86,7 @@ public class HolderShowTracks<T extends PermissionHolder> extends ChildCommand<T
.collect(Collectors.toList()); .collect(Collectors.toList());
for (Track track : tracks) { for (Track track : tracks) {
Component line = Component.text() lines.add(Maps.immutableEntry(track, node));
.append(Message.formatContextSetBracketed(node.getContexts(), Component.empty()))
.append(Component.newline())
.append(Message.formatTrackPath(track.getGroups(), groupName))
.build();
lines.add(Maps.immutableEntry(track, line));
} }
} }
} else { } else {
@ -99,10 +94,11 @@ public class HolderShowTracks<T extends PermissionHolder> extends ChildCommand<T
String groupName = ((Group) target).getName(); String groupName = ((Group) target).getName();
List<Track> tracks = plugin.getTrackManager().getAll().values().stream() List<Track> tracks = plugin.getTrackManager().getAll().values().stream()
.filter(t -> t.containsGroup(groupName)) .filter(t -> t.containsGroup(groupName))
.sorted(Comparator.comparing(Track::getName))
.collect(Collectors.toList()); .collect(Collectors.toList());
for (Track track : tracks) { for (Track track : tracks) {
lines.add(Maps.immutableEntry(track, Message.formatTrackPath(track.getGroups(), groupName))); lines.add(Maps.immutableEntry(track, Inheritance.builder(groupName).build()));
} }
} }
@ -112,8 +108,10 @@ public class HolderShowTracks<T extends PermissionHolder> extends ChildCommand<T
} }
Message.LIST_TRACKS.send(sender, target); Message.LIST_TRACKS.send(sender, target);
for (Map.Entry<Track, Component> line : lines) { for (Map.Entry<Track, InheritanceNode> line : lines) {
Message.LIST_TRACKS_ENTRY.send(sender, line.getKey().getName(), line.getValue()); Track track = line.getKey();
InheritanceNode node = line.getValue();
Message.LIST_TRACKS_ENTRY.send(sender, track.getName(), node.getContexts(), Message.formatTrackPath(track.getGroups(), node.getGroupName()));
} }
} }
} }

View File

@ -122,4 +122,8 @@ public class ExportCommand extends SingleCommand {
}); });
} }
public boolean isRunning() {
return this.running.get();
}
} }

View File

@ -142,4 +142,8 @@ public class ImportCommand extends SingleCommand {
}); });
} }
public boolean isRunning() {
return this.running.get();
}
} }

View File

@ -1938,13 +1938,25 @@ public interface Message {
.append(text(':')) .append(text(':'))
); );
Args2<String, Component> LIST_TRACKS_ENTRY = (name, path) -> text() Args3<String, ContextSet, Component> LIST_TRACKS_ENTRY = (name, contextSet, path) -> join(newline(),
// "&a{}: {}" // "&3> &a{}: {}"
.color(GREEN) // "&7 ({}&7)"
.append(text(name)) text()
.append(text(": ")) .append(text('>', DARK_AQUA))
.append(path) .append(space())
.build(); .append(text().color(GREEN)
.append(text(name))
.append(text(": "))
.append(formatContextSetBracketed(contextSet, empty()))
.build()
),
text()
.color(GRAY)
.append(text(" "))
.append(OPEN_BRACKET)
.append(path)
.append(CLOSE_BRACKET)
);
Args1<PermissionHolder> LIST_TRACKS_EMPTY = holder -> prefixed(translatable() Args1<PermissionHolder> LIST_TRACKS_EMPTY = holder -> prefixed(translatable()
// "&b{}&a is not on any tracks." // "&b{}&a is not on any tracks."

View File

@ -29,18 +29,35 @@ import me.lucko.luckperms.standalone.app.LuckPermsApplication;
import me.lucko.luckperms.standalone.app.utils.AnsiUtils; import me.lucko.luckperms.standalone.app.utils.AnsiUtils;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
/** /**
* Dummy/singleton player class used by the standalone plugin. * Dummy/singleton player class used by the standalone plugin.
* *
* In various places (ContextManager, SenderFactory, ..) the platform "player" type is used * <p>In various places (ContextManager, SenderFactory, ..) the platform "player" type is used
* as a generic parameter. This class acts as this type for the standalone plugin. * as a generic parameter. This class acts as this type for the standalone plugin.</p>
*/ */
public class SingletonPlayer { public class SingletonPlayer {
/** Empty UUID used by the singleton player. */
private static final UUID UUID = new UUID(0, 0);
/** A message sink that prints the component to stdout */
private static final Consumer<Component> PRINT_TO_STDOUT = component -> LuckPermsApplication.LOGGER.info(AnsiUtils.format(component));
/** Singleton instance */
public static final SingletonPlayer INSTANCE = new SingletonPlayer(); public static final SingletonPlayer INSTANCE = new SingletonPlayer();
private static final UUID UUID = new UUID(0, 0); /** A set of message sinks that messages are delivered to */
private final Set<Consumer<Component>> messageSinks;
private SingletonPlayer() {
this.messageSinks = new CopyOnWriteArraySet<>();
this.messageSinks.add(PRINT_TO_STDOUT);
}
public String getName() { public String getName() {
return "StandaloneUser"; return "StandaloneUser";
@ -50,8 +67,18 @@ public class SingletonPlayer {
return UUID; return UUID;
} }
public void printStdout(Component component) { public void sendMessage(Component component) {
LuckPermsApplication.LOGGER.info(AnsiUtils.format(component)); for (Consumer<Component> sink : this.messageSinks) {
sink.accept(component);
}
}
public void addMessageSink(Consumer<Component> sink) {
this.messageSinks.add(sink);
}
public void removeMessageSink(Consumer<Component> sink) {
this.messageSinks.remove(sink);
} }
} }

View File

@ -26,6 +26,7 @@ dependencies {
testImplementation "org.testcontainers:junit-jupiter:1.17.6" testImplementation "org.testcontainers:junit-jupiter:1.17.6"
testImplementation 'org.mockito:mockito-core:4.11.0' testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0' testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0'
testImplementation 'org.awaitility:awaitility:4.2.0'
testImplementation 'com.zaxxer:HikariCP:4.0.3' testImplementation 'com.zaxxer:HikariCP:4.0.3'
testImplementation 'redis.clients:jedis:3.5.2' testImplementation 'redis.clients:jedis:3.5.2'

View File

@ -53,7 +53,7 @@ public class StandaloneSenderFactory extends SenderFactory<LPStandalonePlugin, S
@Override @Override
protected void sendMessage(SingletonPlayer sender, Component message) { protected void sendMessage(SingletonPlayer sender, Component message) {
Component rendered = TranslationManager.render(message, Locale.getDefault()); Component rendered = TranslationManager.render(message, Locale.getDefault());
sender.printStdout(rendered); sender.sendMessage(rendered);
} }
@Override @Override

View File

@ -0,0 +1,857 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.utils.CommandTester;
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import java.util.UUID;
public class CommandsIntegrationTest {
@Test
public void testGroupCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(executor)
.givenCommand("creategroup test")
.thenExpect("[LP] test was successfully created.")
.givenCommand("creategroup test2")
.thenExpect("[LP] test2 was successfully created.")
.givenCommand("deletegroup test2")
.thenExpect("[LP] test2 was successfully deleted.")
.givenCommand("listgroups")
.thenExpect("""
[LP] Showing group entries: (page 1 of 1 - 2 entries)
[LP] Groups: (name, weight, tracks)
[LP] - default - 0
[LP] - test - 0
"""
)
.givenCommand("group test info")
.thenExpect("""
[LP] > Group Info: test
[LP] - Display Name: test
[LP] - Weight: None
[LP] - Contextual Data: (mode: server)
[LP] Prefix: None
[LP] Suffix: None
[LP] Meta: None
"""
)
.givenCommand("group test meta set hello world")
.clearMessageBuffer()
.givenCommand("group test setweight 10")
.thenExpect("[LP] Set weight to 10 for group test.")
.givenCommand("group test setweight 100")
.thenExpect("[LP] Set weight to 100 for group test.")
.givenCommand("group test setdisplayname Test")
.thenExpect("[LP] Set display name to Test for group test in context global.")
.givenCommand("group test setdisplayname Dummy")
.thenExpect("[LP] Set display name to Dummy for group test in context global.")
.givenCommand("group Dummy info")
.thenExpect("""
[LP] > Group Info: test
[LP] - Display Name: Dummy
[LP] - Weight: 100
[LP] - Contextual Data: (mode: server)
[LP] Prefix: None
[LP] Suffix: None
[LP] Meta: (weight=100) (hello=world)
"""
)
.givenCommand("group test clone testclone")
.thenExpect("[LP] test (Dummy) was successfully cloned onto testclone (Dummy).")
.givenCommand("group testclone info")
.thenExpect("""
[LP] > Group Info: testclone
[LP] - Display Name: Dummy
[LP] - Weight: 100
[LP] - Contextual Data: (mode: server)
[LP] Prefix: None
[LP] Suffix: None
[LP] Meta: (weight=100) (hello=world)
"""
)
.givenCommand("group test rename test2")
.thenExpect("[LP] test (Dummy) was successfully renamed to test2 (Dummy).")
.givenCommand("group test2 info")
.thenExpect("""
[LP] > Group Info: test2
[LP] - Display Name: Dummy
[LP] - Weight: 100
[LP] - Contextual Data: (mode: server)
[LP] Prefix: None
[LP] Suffix: None
[LP] Meta: (weight=100) (hello=world)
"""
)
.givenCommand("listgroups")
.thenExpect("""
[LP] Showing group entries: (page 1 of 1 - 3 entries)
[LP] Groups: (name, weight, tracks)
[LP] - test2 (Dummy) - 100
[LP] - testclone (Dummy) - 100
[LP] - default - 0
"""
);
});
}
@Test
public void testGroupPermissionCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(executor)
.givenCommand("creategroup test")
.clearMessageBuffer()
.givenCommand("group test permission set test.node true")
.thenExpect("[LP] Set test.node to true for test in context global.")
.givenCommand("group test permission set test.node.other false server=test")
.thenExpect("[LP] Set test.node.other to false for test in context server=test.")
.givenCommand("group test permission set test.node.other false server=test world=test2")
.thenExpect("[LP] Set test.node.other to false for test in context server=test, world=test2.")
.givenCommand("group test permission settemp abc true 1h")
.thenExpect("[LP] Set abc to true for test for a duration of 1 hour in context global.")
.givenCommand("group test permission settemp abc true 2h replace")
.thenExpect("[LP] Set abc to true for test for a duration of 2 hours in context global.")
.givenCommand("group test permission unsettemp abc")
.thenExpect("[LP] Unset temporary permission abc for test in context global.")
.givenCommand("group test permission info")
.thenExpect("""
[LP] test's Permissions: (page 1 of 1 - 3 entries)
> test.node.other (server=test) (world=test2)
> test.node.other (server=test)
> test.node
"""
)
.givenCommand("group test permission unset test.node")
.thenExpect("[LP] Unset test.node for test in context global.")
.givenCommand("group test permission unset test.node.other")
.thenExpect("[LP] test does not have test.node.other set in context global.")
.givenCommand("group test permission unset test.node.other server=test")
.thenExpect("[LP] Unset test.node.other for test in context server=test.")
.givenCommand("group test permission check test.node.other")
.thenExpect("""
[LP] Permission information for test.node.other:
[LP] - test has test.node.other set to false in context server=test, world=test2.
[LP] - test does not inherit test.node.other.
[LP]
[LP] Permission check for test.node.other:
[LP] Result: undefined
[LP] Processor: None
[LP] Cause: None
[LP] Context: None
"""
)
.givenCommand("group test permission clear server=test world=test2")
.thenExpect("[LP] test's permissions were cleared in context server=test, world=test2. (1 node was removed.)")
.givenCommand("group test permission info")
.thenExpect("[LP] test does not have any permissions set.");
});
}
@Test
public void testGroupParentCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(executor)
.givenCommand("creategroup test")
.givenCommand("creategroup test2")
.givenCommand("creategroup test3")
.clearMessageBuffer()
.givenCommand("group test parent add default")
.thenExpect("[LP] test now inherits permissions from default in context global.")
.givenCommand("group test parent add test2 server=test")
.thenExpect("[LP] test now inherits permissions from test2 in context server=test.")
.givenCommand("group test parent add test3 server=test")
.thenExpect("[LP] test now inherits permissions from test3 in context server=test.")
.givenCommand("group test parent addtemp test2 1d server=hello")
.thenExpect("[LP] test now inherits permissions from test2 for a duration of 1 day in context server=hello.")
.givenCommand("group test parent removetemp test2 server=hello")
.thenExpect("[LP] test no longer temporarily inherits permissions from test2 in context server=hello.")
.givenCommand("group test parent info")
.thenExpect("""
[LP] test's Parents: (page 1 of 1 - 3 entries)
> test2 (server=test)
> test3 (server=test)
> default
"""
)
.givenCommand("group test parent set test2 server=test")
.thenExpect("[LP] test had their existing parent groups cleared, and now only inherits test2 in context server=test.")
.givenCommand("group test parent remove test2 server=test")
.thenExpect("[LP] test no longer inherits permissions from test2 in context server=test.")
.givenCommand("group test parent clear")
.thenExpect("[LP] test's parents were cleared in context global. (1 node was removed.)")
.givenCommand("group test parent info")
.thenExpect("[LP] test does not have any parents defined.");
});
}
@Test
public void testGroupMetaCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(executor)
.givenCommand("creategroup test")
.clearMessageBuffer()
.givenCommand("group test meta info")
.thenExpect("""
[LP] test has no prefixes.
[LP] test has no suffixes.
[LP] test has no meta.
"""
)
.givenCommand("group test meta set hello world")
.thenExpect("[LP] Set meta key 'hello' to 'world' for test in context global.")
.givenCommand("group test meta set hello world2 server=test")
.thenExpect("[LP] Set meta key 'hello' to 'world2' for test in context server=test.")
.givenCommand("group test meta addprefix 10 \"&ehello world\"")
.thenExpect("[LP] test had prefix 'hello world' set at a priority of 10 in context global.")
.givenCommand("group test meta addsuffix 100 \"&ehi\"")
.thenExpect("[LP] test had suffix 'hi' set at a priority of 100 in context global.")
.givenCommand("group test meta addsuffix 1 \"&6no\"")
.thenExpect("[LP] test had suffix 'no' set at a priority of 1 in context global.")
.givenCommand("group test meta settemp abc xyz 1d server=hello")
.thenExpect("[LP] Set meta key 'abc' to 'xyz' for test for a duration of 1 day in context server=hello.")
.givenCommand("group test meta addtempprefix 1000 abc 1d server=hello")
.thenExpect("[LP] test had prefix 'abc' set at a priority of 1000 for a duration of 1 day in context server=hello.")
.givenCommand("group test meta addtempsuffix 1000 xyz 3d server=hello")
.thenExpect("[LP] test had suffix 'xyz' set at a priority of 1000 for a duration of 3 days in context server=hello.")
.givenCommand("group test meta unsettemp abc server=hello")
.thenExpect("[LP] Unset temporary meta key 'abc' for test in context server=hello.")
.givenCommand("group test meta removetempprefix 1000 abc server=hello")
.thenExpect("[LP] test had temporary prefix 'abc' at priority 1000 removed in context server=hello.")
.givenCommand("group test meta removetempsuffix 1000 xyz server=hello")
.thenExpect("[LP] test had temporary suffix 'xyz' at priority 1000 removed in context server=hello.")
.givenCommand("group test meta info")
.thenExpect("""
[LP] test's Prefixes
[LP] -> 10 - 'hello world' (inherited from self)
[LP] test's Suffixes
[LP] -> 100 - 'hi' (inherited from self)
[LP] -> 1 - 'no' (inherited from self)
[LP] test's Meta
[LP] -> hello = 'world2' (inherited from self) (server=test)
[LP] -> hello = 'world' (inherited from self)
"""
)
.givenCommand("group test info")
.thenExpect("""
[LP] > Group Info: test
[LP] - Display Name: test
[LP] - Weight: None
[LP] - Contextual Data: (mode: server)
[LP] Prefix: "hello world"
[LP] Suffix: "hi"
[LP] Meta: (hello=world)
"""
)
.givenCommand("group test meta unset hello")
.thenExpect("[LP] Unset meta key 'hello' for test in context global.")
.givenCommand("group test meta unset hello server=test")
.thenExpect("[LP] Unset meta key 'hello' for test in context server=test.")
.givenCommand("group test meta removeprefix 10")
.thenExpect("[LP] test had all prefixes at priority 10 removed in context global.")
.givenCommand("group test meta removesuffix 100")
.thenExpect("[LP] test had all suffixes at priority 100 removed in context global.")
.givenCommand("group test meta removesuffix 1")
.thenExpect("[LP] test had all suffixes at priority 1 removed in context global.")
.givenCommand("group test meta info")
.thenExpect("""
[LP] test has no prefixes.
[LP] test has no suffixes.
[LP] test has no meta.
"""
);
});
}
@Test
public void testUserCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
plugin.getStorage().savePlayerData(UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"), "Notch").join();
new CommandTester(executor)
.givenCommand("user Luck info")
.thenExpect("""
[LP] > User Info: luck
[LP] - UUID: c1d60c50-70b5-4722-8057-87767557e50d
[LP] (type: official)
[LP] - Status: Offline
[LP] - Parent Groups:
[LP] > default
[LP] - Contextual Data: (mode: server)
[LP] Contexts: None
[LP] Prefix: None
[LP] Suffix: None
[LP] Primary Group: default
[LP] Meta: (primarygroup=default)
"""
)
.givenCommand("user c1d60c50-70b5-4722-8057-87767557e50d info")
.thenExpect("""
[LP] > User Info: luck
[LP] - UUID: c1d60c50-70b5-4722-8057-87767557e50d
[LP] (type: official)
[LP] - Status: Offline
[LP] - Parent Groups:
[LP] > default
[LP] - Contextual Data: (mode: server)
[LP] Contexts: None
[LP] Prefix: None
[LP] Suffix: None
[LP] Primary Group: default
[LP] Meta: (primarygroup=default)
"""
)
.givenCommand("creategroup admin")
.givenCommand("user Luck parent set admin")
.clearMessageBuffer()
.givenCommand("user Luck clone Notch")
.thenExpect("[LP] luck was successfully cloned onto notch.")
.givenCommand("user Notch parent info")
.thenExpect("""
[LP] notch's Parents: (page 1 of 1 - 1 entries)
> admin
"""
);
});
}
@Test
public void testUserPermissionCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(executor)
.givenCommand("user Luck permission set test.node true")
.thenExpect("[LP] Set test.node to true for luck in context global.")
.givenCommand("user Luck permission set test.node.other false server=test")
.thenExpect("[LP] Set test.node.other to false for luck in context server=test.")
.givenCommand("user Luck permission set test.node.other false server=test world=test2")
.thenExpect("[LP] Set test.node.other to false for luck in context server=test, world=test2.")
.givenCommand("user Luck permission settemp abc true 1h")
.thenExpect("[LP] Set abc to true for luck for a duration of 1 hour in context global.")
.givenCommand("user Luck permission settemp abc true 2h replace")
.thenExpect("[LP] Set abc to true for luck for a duration of 2 hours in context global.")
.givenCommand("user Luck permission unsettemp abc")
.thenExpect("[LP] Unset temporary permission abc for luck in context global.")
.givenCommand("user Luck permission info")
.thenExpect("""
[LP] luck's Permissions: (page 1 of 1 - 3 entries)
> test.node.other (server=test) (world=test2)
> test.node.other (server=test)
> test.node
"""
)
.givenCommand("user Luck permission unset test.node")
.thenExpect("[LP] Unset test.node for luck in context global.")
.givenCommand("user Luck permission unset test.node.other")
.thenExpect("[LP] luck does not have test.node.other set in context global.")
.givenCommand("user Luck permission unset test.node.other server=test")
.thenExpect("[LP] Unset test.node.other for luck in context server=test.")
.givenCommand("user Luck permission check test.node.other")
.thenExpect("""
[LP] Permission information for test.node.other:
[LP] - luck has test.node.other set to false in context server=test, world=test2.
[LP] - luck does not inherit test.node.other.
[LP]
[LP] Permission check for test.node.other:
[LP] Result: undefined
[LP] Processor: None
[LP] Cause: None
[LP] Context: None
"""
)
.givenCommand("user Luck permission clear server=test world=test2")
.thenExpect("[LP] luck's permissions were cleared in context server=test, world=test2. (1 node was removed.)")
.givenCommand("user Luck permission info")
.thenExpect("[LP] luck does not have any permissions set.");
});
}
@Test
public void testUserParentCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(executor)
.givenCommand("creategroup test2")
.givenCommand("creategroup test3")
.clearMessageBuffer()
.givenCommand("user Luck parent add default")
.thenExpect("[LP] luck already inherits from default in context global.")
.givenCommand("user Luck parent add test2 server=test")
.thenExpect("[LP] luck now inherits permissions from test2 in context server=test.")
.givenCommand("user Luck parent add test3 server=test")
.thenExpect("[LP] luck now inherits permissions from test3 in context server=test.")
.givenCommand("user Luck parent addtemp test2 1d server=hello")
.thenExpect("[LP] luck now inherits permissions from test2 for a duration of 1 day in context server=hello.")
.givenCommand("user Luck parent removetemp test2 server=hello")
.thenExpect("[LP] luck no longer temporarily inherits permissions from test2 in context server=hello.")
.givenCommand("user Luck parent info")
.thenExpect("""
[LP] luck's Parents: (page 1 of 1 - 3 entries)
> test2 (server=test)
> test3 (server=test)
> default
"""
)
.givenCommand("user Luck parent set test2 server=test")
.thenExpect("[LP] luck had their existing parent groups cleared, and now only inherits test2 in context server=test.")
.givenCommand("user Luck parent remove test2 server=test")
.thenExpect("[LP] luck no longer inherits permissions from test2 in context server=test.")
.givenCommand("user Luck parent clear")
.thenExpect("[LP] luck's parents were cleared in context global. (0 nodes were removed.)")
.givenCommand("user Luck parent info")
.thenExpect("""
[LP] luck's Parents: (page 1 of 1 - 1 entries)
> default
"""
);
});
}
@Test
public void testUserMetaCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(executor)
.givenCommand("user Luck meta info")
.thenExpect("""
[LP] luck has no prefixes.
[LP] luck has no suffixes.
[LP] luck has no meta.
"""
)
.givenCommand("user Luck meta set hello world")
.thenExpect("[LP] Set meta key 'hello' to 'world' for luck in context global.")
.givenCommand("user Luck meta set hello world2 server=test")
.thenExpect("[LP] Set meta key 'hello' to 'world2' for luck in context server=test.")
.givenCommand("user Luck meta addprefix 10 \"&ehello world\"")
.thenExpect("[LP] luck had prefix 'hello world' set at a priority of 10 in context global.")
.givenCommand("user Luck meta addsuffix 100 \"&ehi\"")
.thenExpect("[LP] luck had suffix 'hi' set at a priority of 100 in context global.")
.givenCommand("user Luck meta addsuffix 1 \"&6no\"")
.thenExpect("[LP] luck had suffix 'no' set at a priority of 1 in context global.")
.givenCommand("user Luck meta settemp abc xyz 1d server=hello")
.thenExpect("[LP] Set meta key 'abc' to 'xyz' for luck for a duration of 1 day in context server=hello.")
.givenCommand("user Luck meta addtempprefix 1000 abc 1d server=hello")
.thenExpect("[LP] luck had prefix 'abc' set at a priority of 1000 for a duration of 1 day in context server=hello.")
.givenCommand("user Luck meta addtempsuffix 1000 xyz 3d server=hello")
.thenExpect("[LP] luck had suffix 'xyz' set at a priority of 1000 for a duration of 3 days in context server=hello.")
.givenCommand("user Luck meta unsettemp abc server=hello")
.thenExpect("[LP] Unset temporary meta key 'abc' for luck in context server=hello.")
.givenCommand("user Luck meta removetempprefix 1000 abc server=hello")
.thenExpect("[LP] luck had temporary prefix 'abc' at priority 1000 removed in context server=hello.")
.givenCommand("user Luck meta removetempsuffix 1000 xyz server=hello")
.thenExpect("[LP] luck had temporary suffix 'xyz' at priority 1000 removed in context server=hello.")
.givenCommand("user Luck meta info")
.thenExpect("""
[LP] luck's Prefixes
[LP] -> 10 - 'hello world' (inherited from self)
[LP] luck's Suffixes
[LP] -> 100 - 'hi' (inherited from self)
[LP] -> 1 - 'no' (inherited from self)
[LP] luck's Meta
[LP] -> hello = 'world2' (inherited from self) (server=test)
[LP] -> hello = 'world' (inherited from self)
"""
)
.givenCommand("user Luck info")
.thenExpect("""
[LP] > User Info: luck
[LP] - UUID: c1d60c50-70b5-4722-8057-87767557e50d
[LP] (type: official)
[LP] - Status: Offline
[LP] - Parent Groups:
[LP] > default
[LP] - Contextual Data: (mode: server)
[LP] Contexts: None
[LP] Prefix: "hello world"
[LP] Suffix: "hi"
[LP] Primary Group: default
[LP] Meta: (hello=world) (primarygroup=default)
"""
)
.givenCommand("user Luck meta unset hello")
.thenExpect("[LP] Unset meta key 'hello' for luck in context global.")
.givenCommand("user Luck meta unset hello server=test")
.thenExpect("[LP] Unset meta key 'hello' for luck in context server=test.")
.givenCommand("user Luck meta removeprefix 10")
.thenExpect("[LP] luck had all prefixes at priority 10 removed in context global.")
.givenCommand("user Luck meta removesuffix 100")
.thenExpect("[LP] luck had all suffixes at priority 100 removed in context global.")
.givenCommand("user Luck meta removesuffix 1")
.thenExpect("[LP] luck had all suffixes at priority 1 removed in context global.")
.givenCommand("user Luck meta info")
.thenExpect("""
[LP] luck has no prefixes.
[LP] luck has no suffixes.
[LP] luck has no meta.
"""
);
});
}
@Test
public void testTrackCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(executor)
.givenCommand("createtrack test1")
.thenExpect("[LP] test1 was successfully created.")
.givenCommand("createtrack test2")
.thenExpect("[LP] test2 was successfully created.")
.givenCommand("listtracks")
.thenExpect("[LP] Tracks: test1, test2")
.givenCommand("deletetrack test2")
.thenExpect("[LP] test2 was successfully deleted.")
.givenCommand("creategroup aaa")
.givenCommand("creategroup bbb")
.givenCommand("creategroup ccc")
.clearMessageBuffer()
.givenCommand("track test1 append bbb")
.thenExpect("[LP] Group bbb was appended to track test1.")
.givenCommand("track test1 insert aaa 1")
.thenExpect("""
[LP] Group aaa was inserted into track test1 at position 1.
[LP] aaa ---> bbb
"""
)
.givenCommand("track test1 insert ccc 3")
.thenExpect("""
[LP] Group ccc was inserted into track test1 at position 3.
[LP] aaa ---> bbb ---> ccc
"""
)
.givenCommand("track test1 info")
.thenExpect("""
[LP] > Showing Track: test1
[LP] - Path: aaa ---> bbb ---> ccc
"""
)
.givenCommand("track test1 clone testclone")
.thenExpect("[LP] test1 was successfully cloned onto testclone.")
.givenCommand("track testclone info")
.thenExpect("""
[LP] > Showing Track: testclone
[LP] - Path: aaa ---> bbb ---> ccc
"""
)
.givenCommand("track test1 rename test2")
.thenExpect("[LP] test1 was successfully renamed to test2.")
.givenCommand("listtracks")
.thenExpect("[LP] Tracks: test2, testclone")
.givenCommand("track test2 info")
.thenExpect("""
[LP] > Showing Track: test2
[LP] - Path: aaa ---> bbb ---> ccc
"""
)
.givenCommand("group aaa showtracks")
.thenExpect("""
[LP] aaa's Tracks:
> test2:
(aaa ---> bbb ---> ccc)
> testclone:
(aaa ---> bbb ---> ccc)
"""
)
.givenCommand("track test2 remove bbb")
.thenExpect("""
[LP] Group bbb was removed from track test2.
[LP] aaa ---> ccc
"""
)
.givenCommand("track test2 clear")
.thenExpect("[LP] test2's groups track was cleared.");
});
}
@Test
public void testUserTrackCommands(@TempDir Path tempDir) {
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(executor)
.givenCommand("createtrack staff")
.givenCommand("createtrack premium")
.givenCommand("creategroup mod")
.givenCommand("creategroup admin")
.givenCommand("creategroup vip")
.givenCommand("creategroup vip+")
.givenCommand("creategroup mvp")
.givenCommand("creategroup mvp+")
.givenCommand("track staff append mod")
.givenCommand("track staff append admin")
.givenCommand("track premium append vip")
.givenCommand("track premium append vip+")
.givenCommand("track premium append mvp")
.givenCommand("track premium append mvp+")
.clearMessageBuffer()
.givenCommand("user Luck promote staff")
.thenExpect("[LP] luck isn't in any groups on staff, so they were added to the first group, mod in context global.")
.givenCommand("user Luck promote staff")
.thenExpect("""
[LP] Promoting luck along track staff from mod to admin in context global.
[LP] mod ---> admin
"""
)
.givenCommand("user Luck promote staff")
.thenExpect("[LP] The end of track staff was reached, unable to promote luck.")
.givenCommand("user Luck demote staff")
.thenExpect("""
[LP] Demoting luck along track staff from admin to mod in context global.
[LP] mod <--- admin
"""
)
.givenCommand("user Luck demote staff")
.thenExpect("[LP] The end of track staff was reached, so luck was removed from mod.")
.givenCommand("user Luck demote staff")
.thenExpect("[LP] luck isn't already in any groups on staff.")
.givenCommand("user Luck promote premium server=test1")
.thenExpect("[LP] luck isn't in any groups on premium, so they were added to the first group, vip in context server=test1.")
.givenCommand("user Luck promote premium server=test2")
.thenExpect("[LP] luck isn't in any groups on premium, so they were added to the first group, vip in context server=test2.")
.givenCommand("user Luck promote premium server=test1")
.thenExpect("""
[LP] Promoting luck along track premium from vip to vip+ in context server=test1.
[LP] vip ---> vip+ ---> mvp ---> mvp+
"""
)
.givenCommand("user Luck promote premium server=test2")
.thenExpect("""
[LP] Promoting luck along track premium from vip to vip+ in context server=test2.
[LP] vip ---> vip+ ---> mvp ---> mvp+
"""
)
.givenCommand("user Luck parent info")
.thenExpect("""
[LP] luck's Parents: (page 1 of 1 - 3 entries)
> vip+ (server=test2)
> vip+ (server=test1)
> default
"""
)
.givenCommand("user Luck showtracks")
.thenExpect("""
[LP] luck's Tracks:
> premium: (server=test2)
(vip ---> vip+ ---> mvp ---> mvp+)
> premium: (server=test1)
(vip ---> vip+ ---> mvp ---> mvp+)
"""
)
.givenCommand("user Luck demote premium server=test1")
.thenExpect("""
[LP] Demoting luck along track premium from vip+ to vip in context server=test1.
[LP] vip <--- vip+ <--- mvp <--- mvp+
"""
)
.givenCommand("user Luck demote premium server=test2")
.thenExpect("""
[LP] Demoting luck along track premium from vip+ to vip in context server=test2.
[LP] vip <--- vip+ <--- mvp <--- mvp+
"""
)
.givenCommand("user Luck demote premium server=test1")
.thenExpect("[LP] The end of track premium was reached, so luck was removed from vip.")
.givenCommand("user Luck demote premium server=test2")
.thenExpect("[LP] The end of track premium was reached, so luck was removed from vip.")
.givenCommand("user Luck parent info")
.thenExpect("""
[LP] luck's Parents: (page 1 of 1 - 1 entries)
> default
"""
);
});
}
}

View File

@ -0,0 +1,121 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.commands.misc.ExportCommand;
import me.lucko.luckperms.common.commands.misc.ImportCommand;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.node.types.Permission;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class ImportExportIntegrationTest {
@Test
public void testRoundTrip(@TempDir Path tempDirA, @TempDir Path tempDirB) throws IOException {
Path path = tempDirA.resolve("testfile.json.gz");
// run an export on environment A
TestPluginProvider.use(tempDirA, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
executor.execute("creategroup test").join();
executor.execute("group test permission set test.permission true").join();
executor.execute("createtrack test").join();
executor.execute("track test append default").join();
executor.execute("user Luck permission set hello").join();
executor.execute("export testfile").join();
ExportCommand exportCommand = (ExportCommand) plugin.getCommandManager().getMainCommands().get("export");
await().atMost(10, TimeUnit.SECONDS).until(() -> !exportCommand.isRunning());
});
// check the export contains the expected data
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(path))))) {
JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
assertEquals(2, obj.get("groups").getAsJsonObject().size());
assertEquals(1, obj.get("users").getAsJsonObject().size());
assertEquals(1, obj.get("tracks").getAsJsonObject().size());
}
// copy the export file from environment A to environment B
Files.copy(path, tempDirB.resolve("testfile.json.gz"));
// import the file on environment B
TestPluginProvider.use(tempDirB, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
assertNull(plugin.getGroupManager().getIfLoaded("test"));
executor.execute("import testfile").join();
ImportCommand importCommand = (ImportCommand) plugin.getCommandManager().getMainCommands().get("import");
await().atMost(10, TimeUnit.SECONDS).until(() -> !importCommand.isRunning());
// assert that the expected objects exist
Group testGroup = plugin.getGroupManager().getIfLoaded("test");
assertNotNull(testGroup);
assertEquals(ImmutableList.of(Permission.builder().permission("test.permission").build()), testGroup.normalData().asList());
Track testTrack = plugin.getTrackManager().getIfLoaded("test");
assertNotNull(testTrack);
assertEquals(ImmutableList.of("default"), testTrack.getGroups());
User testUser = plugin.getStorage().loadUser(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), null).join();
assertNotNull(testUser);
assertEquals("luck", testUser.getUsername().orElse(null));
assertEquals(ImmutableSet.of(
Permission.builder().permission("hello").build(),
Inheritance.builder().group("default").build()
), testUser.normalData().asSet());
});
}
}

View File

@ -0,0 +1,149 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.utils;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Utility for testing LuckPerms commands with BDD-like given/when/then assertions.
*/
public final class CommandTester implements Consumer<Component> {
private static final Logger LOGGER = LogManager.getLogger(CommandTester.class);
/** The LuckPerms command executor */
private final CommandExecutor executor;
/** A buffer of messages received by the test tool */
private final List<Component> messageBuffer = Collections.synchronizedList(new ArrayList<>());
public CommandTester(CommandExecutor executor) {
this.executor = executor;
}
/**
* Accept a message and add it to the buffer.
*
* @param component the message
*/
@Override
public void accept(Component component) {
this.messageBuffer.add(component);
}
/**
* Execute a command using the {@link CommandExecutor} and capture output to this test instance.
*
* @param command the command to run
* @return this
*/
public CommandTester givenCommand(String command) {
LOGGER.info("Executing test command: " + command);
SingletonPlayer.INSTANCE.addMessageSink(this);
this.executor.execute(command).join();
SingletonPlayer.INSTANCE.removeMessageSink(this);
return this;
}
/**
* Asserts that the current contents of the message buffer matches the given input string.
*
* @param expected the expected contents
* @return this
*/
public CommandTester thenExpect(String expected) {
String actual = this.renderBuffer().stream()
.map(String::trim)
.collect(Collectors.joining("\n"));
assertEquals(expected.trim(), actual.trim());
return this.clearMessageBuffer();
}
/**
* Clears the message buffer.
*
* @return this
*/
public CommandTester clearMessageBuffer() {
this.messageBuffer.clear();
return this;
}
/**
* Renders the contents of the message buffer.
*
* @return rendered copy of the buffer
*/
public List<String> renderBuffer() {
return this.messageBuffer.stream()
.map(component -> PlainTextComponentSerializer.plainText().serialize(component))
.collect(Collectors.toList());
}
/**
* Prints test case source code to stdout to test the given command.
*
* @param cmd the command
* @return this
*/
public CommandTester outputTest(String cmd) {
System.out.printf(".executeCommand(\"%s\")%n", cmd);
this.givenCommand(cmd);
List<String> render = this.renderBuffer();
if (render.size() == 1) {
System.out.printf(".expect(\"%s\")%n", render.get(0));
} else {
System.out.println(".expect(\"\"\"");
for (String s : render) {
System.out.println(" " + s);
}
System.out.println(" \"\"\"");
System.out.println(")");
}
System.out.println();
return this.clearMessageBuffer();
}
}