Switch back to mocking

The ByteBuddy solution complains about adding/removing fields
But making a delegating mock maker is easy enough
This commit is contained in:
Norbi Peti 2020-10-07 20:08:37 +02:00
parent 3f2fa286fb
commit 7cebb74835
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 103 additions and 97 deletions

View file

@ -35,6 +35,7 @@ import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.mockito.internal.util.MockUtil;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.awt.*; import java.awt.*;
@ -101,17 +102,15 @@ public class DiscordPlugin extends ButtonPlugin {
return getIConfig().getData("inviteLink", ""); return getIConfig().getData("inviteLink", "");
} }
/*@Override @Override
public void onLoad() { //Needed by ServerWatcher public void onLoad() { //Needed by ServerWatcher
var thread = Thread.currentThread(); var thread = Thread.currentThread();
getLogger().info("Setting context class loader for " + thread);
var cl = thread.getContextClassLoader(); var cl = thread.getContextClassLoader();
thread.setContextClassLoader(getClassLoader()); thread.setContextClassLoader(getClassLoader());
MockUtil.isMock(null); //Load MockUtil to load Mockito plugins MockUtil.isMock(null); //Load MockUtil to load Mockito plugins
//new ByteBuddy().rebase(Player.class)
getLogger().info("Restoring context class loader");
thread.setContextClassLoader(cl); thread.setContextClassLoader(cl);
}*/ getLogger().info("Load complete");
}
@Override @Override
public void pluginEnable() { public void pluginEnable() {

View file

@ -1,10 +1,12 @@
package buttondevteam.discordplugin.broadcaster; package buttondevteam.discordplugin.broadcaster;
import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.playerfaker.DelegatingMockMaker;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -34,6 +36,7 @@ public class PlayerListWatcher {
module.logWarn("Player list already mocked!"); module.logWarn("Player list already mocked!");
return false; return false;
} }
DelegatingMockMaker.getInstance().setMockMaker(new SubclassByteBuddyMockMaker());
val icbcl = Class.forName(nms + ".IChatBaseComponent"); val icbcl = Class.forName(nms + ".IChatBaseComponent");
Method sendMessageTemp; Method sendMessageTemp;
try { try {

View file

@ -0,0 +1,20 @@
package buttondevteam.discordplugin.playerfaker;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Delegate;
import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker;
import org.mockito.plugins.MockMaker;
public class DelegatingMockMaker implements MockMaker {
@Getter
@Setter
@Delegate
private MockMaker mockMaker = new SubclassByteBuddyMockMaker();
@Getter
private static DelegatingMockMaker instance;
public DelegatingMockMaker() {
instance = this;
}
}

View file

@ -1,36 +1,24 @@
package buttondevteam.discordplugin.playerfaker; package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.lib.TBMCCoreAPI;
import com.destroystokyo.paper.profile.CraftPlayerProfile; import com.destroystokyo.paper.profile.CraftPlayerProfile;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding; import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.objenesis.ObjenesisStd; import org.mockito.Mockito;
import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable;
import static net.bytebuddy.matcher.ElementMatchers.*;
public class ServerWatcher { public class ServerWatcher {
private List<Player> playerList; private List<Player> playerList;
private final List<Player> fakePlayers = new ArrayList<>(); private final List<Player> fakePlayers = new ArrayList<>();
private Server origServer; private Server origServer;
//private ByteBuddy byteBuddy;
//private AsmVisitorWrapper mockTransformer;
@IgnoreForBinding @IgnoreForBinding
public void enableDisable(boolean enable) throws Exception { public void enableDisable(boolean enable) throws Exception {
@ -38,30 +26,62 @@ public class ServerWatcher {
serverField.setAccessible(true); serverField.setAccessible(true);
if (enable) { if (enable) {
var serverClass = Bukkit.getServer().getClass(); var serverClass = Bukkit.getServer().getClass();
//var mockMaker = new InlineByteBuddyMockMaker();
//System.setProperty("net.bytebuddy.experimental", "true");
/*try {
var resources = cl.getResources("mockito-extensions/" + MockMaker.class.getName());
System.out.println("Found resources: " + resources);
Iterables.toIterable(resources).forEach(resource -> System.out.println("Resource: " + resource));
} catch (IOException e) {
throw new IllegalStateException("Failed to load " + MockMaker.class, e);
}*/
ByteBuddyAgent.install();
var originalServer = serverField.get(null); var originalServer = serverField.get(null);
var impl = MethodDelegation.to(this); //var impl = MethodDelegation.to(this);
var names = Arrays.stream(ServerWatcher.class.getMethods()).map(Method::getName).toArray(String[]::new); //var names = Arrays.stream(ServerWatcher.class.getMethods()).map(Method::getName).toArray(String[]::new);
var utype = new ByteBuddy() //InlineBytecodeGenerator /*if (byteBuddy == null) {
byteBuddy = new ByteBuddy()
.with(TypeValidation.DISABLED) .with(TypeValidation.DISABLED)
.with(Implementation.Context.Disabled.Factory.INSTANCE) .with(Implementation.Context.Disabled.Factory.INSTANCE)
.with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE) .with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE)
.ignore(isSynthetic().and(not(isConstructor())).or(isDefaultFinalizer())) .ignore(isSynthetic().and(not(isConstructor())).or(isDefaultFinalizer()));
.redefine(serverClass)
.method(target -> !target.isStatic()).intercept(MethodCall.invokeSelf().on(originalServer).withAllArguments()) mockTransformer=new InlineByteBuddyMockMaker().createMock()
.method(target -> Arrays.stream(names).anyMatch(target.getActualName()::equalsIgnoreCase)).intercept(impl).make(); }*/
var ltype = utype.load(serverClass.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); DelegatingMockMaker.getInstance().setMockMaker(new InlineByteBuddyMockMaker());
var type = ltype.getLoaded(); var settings = Mockito.withSettings().stubOnly()
var mock = new ObjenesisStd().newInstance(type); .defaultAnswer(invocation -> {
var method = invocation.getMethod();
int pc = method.getParameterCount();
Player player = null;
switch (method.getName()) {
case "getPlayer":
if (pc == 1 && method.getParameterTypes()[0] == UUID.class)
player = MCChatUtils.LoggedInPlayers.get(invocation.<UUID>getArgument(0));
break;
case "getPlayerExact":
if (pc == 1) {
final String argument = invocation.getArgument(0);
player = MCChatUtils.LoggedInPlayers.values().stream()
.filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null);
}
break;
case "getOnlinePlayers":
if (playerList == null) {
@SuppressWarnings("unchecked") var list = (List<Player>) method.invoke(origServer, invocation.getArguments());
playerList = new AppendListView<>(list, fakePlayers);
}
return playerList;
case "createProfile": //Paper's method, casts the player to a CraftPlayer
if (pc == 2) {
UUID uuid = invocation.getArgument(0);
String name = invocation.getArgument(1);
player = uuid != null ? MCChatUtils.LoggedInPlayers.get(uuid) : null;
if (player == null && name != null)
player = MCChatUtils.LoggedInPlayers.values().stream()
.filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
if (player != null)
return new CraftPlayerProfile(player.getUniqueId(), player.getName());
}
break;
}
if (player != null)
return player;
return method.invoke(origServer, invocation.getArguments());
});
//var mock = mockMaker.createMock(settings, MockHandlerFactory.createMockHandler(settings));
//thread.setContextClassLoader(cl);
var mock = Mockito.mock(serverClass, settings);
for (var field : serverClass.getFields()) //Copy public fields, private fields aren't accessible directly anyways for (var field : serverClass.getFields()) //Copy public fields, private fields aren't accessible directly anyways
if (!Modifier.isFinal(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) if (!Modifier.isFinal(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()))
field.set(mock, field.get(originalServer)); field.set(mock, field.get(originalServer));
@ -71,61 +91,25 @@ public class ServerWatcher {
serverField.set(null, origServer); serverField.set(null, origServer);
} }
public Player getPlayer(UUID uuid, @SuperCall Callable<Player> original) { /*@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (classBeingRedefined == null)
return null;
try { try {
var player = MCChatUtils.LoggedInPlayers.get(uuid); return byteBuddy
if (player == null) return original.call(); .redefine(
return player; classBeingRedefined,
} catch (Exception e) { ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)
TBMCCoreAPI.SendException("Failed to handle getPlayer!", e); )
//.visit(new InlineBytecodeGenerator.ParameterWritingVisitorWrapper(classBeingRedefined))
.visit(mockTransformer)
.make()
.getBytes();
} catch (Throwable throwable) {
TBMCCoreAPI.SendException("Failed to transform class!", throwable);
return null; return null;
} }
} }*/
public Player getPlayerExact(String name, @SuperCall Callable<Player> original) {
try {
var player = MCChatUtils.LoggedInPlayers.values().stream()
.filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
if (player == null) return original.call();
return player;
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to handle getPlayerExact!", e);
return null;
}
}
@RuntimeType
//public List<Player> getOnlinePlayers(@SuperCall Callable<List<Player>> original) {
public List<Player> getOnlinePlayers() {
try {
if (playerList == null) {
//var list = original.call();
var list = new ArrayList<Player>();
playerList = new AppendListView<>(list, fakePlayers);
}
return playerList;
} catch (
Exception e) {
TBMCCoreAPI.SendException("Failed to handle getOnlinePlayers!", e);
return null;
}
}
@RuntimeType
public Object createProfile(UUID uuid, String name) { //Paper's method, casts the player to a CraftPlayer
try {
var player = uuid != null ? MCChatUtils.LoggedInPlayers.get(uuid) : null;
if (player == null && name != null)
player = MCChatUtils.LoggedInPlayers.values().stream()
.filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
if (player != null)
return new CraftPlayerProfile(player.getUniqueId(), player.getName());
return null;
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to handle createProfile!", e);
return null;
}
}
@RequiredArgsConstructor @RequiredArgsConstructor
public static class AppendListView<T> extends AbstractSequentialList<T> { public static class AppendListView<T> extends AbstractSequentialList<T> {

View file

@ -1 +1 @@
org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker buttondevteam.discordplugin.playerfaker.DelegatingMockMaker