Add unit tests for LuaCsHook Patch API
This commit is contained in:
524
Barotrauma/BarotraumaTest/LuaCs/HookPatchTests.cs
Normal file
524
Barotrauma/BarotraumaTest/LuaCs/HookPatchTests.cs
Normal file
@@ -0,0 +1,524 @@
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MoonSharp.Interpreter;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace TestProject.LuaCs
|
||||
{
|
||||
public class HookPatchTests
|
||||
{
|
||||
private readonly LuaCsSetup luaCs = new();
|
||||
|
||||
public HookPatchTests(ITestOutputHelper output)
|
||||
{
|
||||
Console.SetOut(new TestOutputTextWriterAdapter(output));
|
||||
Trace.Listeners.Add(new TestOutputTraceListenerAdapter(output));
|
||||
|
||||
UserData.RegisterType<TestValueType>();
|
||||
UserData.RegisterType<IBogusInterface>();
|
||||
UserData.RegisterType<InterfaceImplementingType>();
|
||||
UserData.RegisterType<PatchTarget1>();
|
||||
UserData.RegisterType<PatchTarget2>();
|
||||
UserData.RegisterType<PatchTarget3>();
|
||||
UserData.RegisterType<PatchTarget4>();
|
||||
UserData.RegisterType<PatchTarget5>();
|
||||
UserData.RegisterType<PatchTarget6>();
|
||||
|
||||
luaCs.Initialize();
|
||||
luaCs.Lua.Globals["TestValueType"] = UserData.CreateStatic<TestValueType>();
|
||||
luaCs.Lua.Globals["InterfaceImplementingType"] = UserData.CreateStatic<InterfaceImplementingType>();
|
||||
}
|
||||
|
||||
private DynValue AddPrefix<T>(string body, string testMethod = "Run", string? patchId = null)
|
||||
{
|
||||
var className = typeof(T).FullName;
|
||||
if (patchId != null)
|
||||
{
|
||||
return luaCs.Lua.DoString(@$"
|
||||
return Hook.Patch('{patchId}', '{className}', '{testMethod}', function(instance, ptable)
|
||||
{body}
|
||||
end, Hook.HookMethodType.Before)
|
||||
");
|
||||
}
|
||||
else
|
||||
{
|
||||
return luaCs.Lua.DoString(@$"
|
||||
return Hook.Patch('{className}', '{testMethod}', function(instance, ptable)
|
||||
{body}
|
||||
end, Hook.HookMethodType.Before)
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
private DynValue AddPostfix<T>(string body, string testMethod = "Run", string? patchId = null)
|
||||
{
|
||||
var className = typeof(T).FullName;
|
||||
if (patchId != null)
|
||||
{
|
||||
return luaCs.Lua.DoString(@$"
|
||||
return Hook.Patch('{patchId}', '{className}', '{testMethod}', function(instance, ptable)
|
||||
{body}
|
||||
end, Hook.HookMethodType.After)
|
||||
");
|
||||
}
|
||||
else
|
||||
{
|
||||
return luaCs.Lua.DoString(@$"
|
||||
return Hook.Patch('{className}', '{testMethod}', function(instance, ptable)
|
||||
{body}
|
||||
end, Hook.HookMethodType.After)
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
private DynValue RemovePrefix<T>(string patchName, string testMethod = "Run")
|
||||
{
|
||||
var className = typeof(T).FullName;
|
||||
return luaCs.Lua.DoString($@"
|
||||
return Hook.RemovePatch('{patchName}', '{className}', '{testMethod}', Hook.HookMethodType.Before)
|
||||
");
|
||||
}
|
||||
|
||||
private DynValue RemovePostfix<T>(string patchName, string testMethod = "Run")
|
||||
{
|
||||
var className = typeof(T).FullName;
|
||||
return luaCs.Lua.DoString($@"
|
||||
return Hook.RemovePatch('{patchName}', '{className}', '{testMethod}', Hook.HookMethodType.After)
|
||||
");
|
||||
}
|
||||
|
||||
public class PatchTarget1
|
||||
{
|
||||
public bool ran;
|
||||
|
||||
public void Run()
|
||||
{
|
||||
ran = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFullMethodReplacement()
|
||||
{
|
||||
var target = new PatchTarget1();
|
||||
AddPrefix<PatchTarget1>("ptable.PreventExecution = true");
|
||||
target.Run();
|
||||
Assert.False(target.ran);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOverrideExistingPatch()
|
||||
{
|
||||
var target = new PatchTarget1();
|
||||
AddPrefix<PatchTarget1>(@"
|
||||
ptable.PreventExecution = true
|
||||
originalPatchRan = true
|
||||
", patchId: "test");
|
||||
target.Run();
|
||||
Assert.False(target.ran);
|
||||
Assert.True(luaCs.Lua.Globals["originalPatchRan"] as bool?);
|
||||
|
||||
// Reset this global so we can test if the original patch ran
|
||||
// after replacing it.
|
||||
luaCs.Lua.Globals["originalPatchRan"] = false;
|
||||
|
||||
// Replace the existing prefix, but don't prevent execution this time
|
||||
AddPrefix<PatchTarget1>("replacementPatchRan = true", patchId: "test");
|
||||
target.Run();
|
||||
Assert.True(target.ran);
|
||||
|
||||
// Make sure the original patch didn't run
|
||||
Assert.False(luaCs.Lua.Globals["originalPatchRan"] as bool?);
|
||||
|
||||
// Test if the replacement patch ran
|
||||
Assert.True(luaCs.Lua.Globals["replacementPatchRan"] as bool?);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestRemovePrefix()
|
||||
{
|
||||
var target = new PatchTarget1();
|
||||
var patchId = AddPrefix<PatchTarget1>(@"
|
||||
ptable.PreventExecution = true
|
||||
patchRan = true
|
||||
");
|
||||
target.Run();
|
||||
Assert.False(target.ran);
|
||||
Assert.True(luaCs.Lua.Globals["patchRan"] as bool?);
|
||||
|
||||
luaCs.Lua.Globals["patchRan"] = false;
|
||||
|
||||
Assert.Equal(DataType.String, patchId.Type);
|
||||
RemovePrefix<PatchTarget1>(patchId.String);
|
||||
target.Run();
|
||||
Assert.True(target.ran);
|
||||
Assert.False(luaCs.Lua.Globals["patchRan"] as bool?);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestRemovePostfix()
|
||||
{
|
||||
var target = new PatchTarget1();
|
||||
var patchId = AddPostfix<PatchTarget1>(@"
|
||||
patchRan = true
|
||||
");
|
||||
target.Run();
|
||||
Assert.True(target.ran);
|
||||
Assert.True(luaCs.Lua.Globals["patchRan"] as bool?);
|
||||
|
||||
target.ran = false;
|
||||
luaCs.Lua.Globals["patchRan"] = false;
|
||||
|
||||
Assert.Equal(DataType.String, patchId.Type);
|
||||
RemovePostfix<PatchTarget1>(patchId.String);
|
||||
target.Run();
|
||||
Assert.True(target.ran);
|
||||
Assert.False(luaCs.Lua.Globals["patchRan"] as bool?);
|
||||
}
|
||||
|
||||
public struct TestValueType
|
||||
{
|
||||
public int foo;
|
||||
|
||||
public TestValueType(int foo)
|
||||
{
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
|
||||
public class PatchTarget2
|
||||
{
|
||||
public bool ran;
|
||||
|
||||
public object Run()
|
||||
{
|
||||
ran = true;
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IBogusInterface
|
||||
{
|
||||
int GetFoo();
|
||||
}
|
||||
|
||||
public class InterfaceImplementingType : IBogusInterface
|
||||
{
|
||||
private readonly int foo;
|
||||
|
||||
public InterfaceImplementingType(int foo)
|
||||
{
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public int GetFoo() => foo;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReturnBoxed()
|
||||
{
|
||||
var target = new PatchTarget2();
|
||||
AddPrefix<PatchTarget2>(@"
|
||||
ptable.PreventExecution = true
|
||||
return 123
|
||||
");
|
||||
var returnValue = target.Run();
|
||||
Assert.False(target.ran);
|
||||
Assert.Equal(123, (int)(double)returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReturnVoid()
|
||||
{
|
||||
var target = new PatchTarget2();
|
||||
// This should have no effect
|
||||
AddPrefix<PatchTarget2>("return");
|
||||
var returnValue = target.Run();
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(5, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReturnNil()
|
||||
{
|
||||
var target = new PatchTarget2();
|
||||
// This should modify the return value to "null"
|
||||
AddPostfix<PatchTarget2>("return nil");
|
||||
var returnValue = target.Run();
|
||||
Assert.True(target.ran);
|
||||
Assert.Null(returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReturnValueType()
|
||||
{
|
||||
var target = new PatchTarget2();
|
||||
AddPostfix<PatchTarget2>(@"
|
||||
return TestValueType.__new(100)
|
||||
");
|
||||
var returnValue = target.Run();
|
||||
Assert.True(target.ran);
|
||||
Assert.IsType<TestValueType>(returnValue);
|
||||
Assert.Equal(100, ((TestValueType)returnValue).foo);
|
||||
}
|
||||
|
||||
public class PatchTarget3
|
||||
{
|
||||
public bool ran;
|
||||
|
||||
public IBogusInterface Run()
|
||||
{
|
||||
ran = true;
|
||||
return new InterfaceImplementingType(5);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReturnInterfaceImplementingType()
|
||||
{
|
||||
var target = new PatchTarget3();
|
||||
AddPostfix<PatchTarget3>(@"
|
||||
return InterfaceImplementingType.__new(100);
|
||||
");
|
||||
var returnValue = target.Run()!;
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(100, returnValue.GetFoo());
|
||||
}
|
||||
|
||||
public class PatchTarget4
|
||||
{
|
||||
public bool ran;
|
||||
|
||||
public void Run(int a, out string outString, ref byte refByte, string b)
|
||||
{
|
||||
ran = true;
|
||||
outString = a + b + refByte;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestModifyParameters()
|
||||
{
|
||||
var target = new PatchTarget4();
|
||||
AddPrefix<PatchTarget4>(@"
|
||||
ptable['a'] = Int32(100)
|
||||
ptable['b'] = 'abc'
|
||||
ptable['refByte'] = Byte(4)
|
||||
");
|
||||
byte refByte = 123;
|
||||
target.Run(5, out var outString, ref refByte, "foo");
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal("100abc4", outString);
|
||||
}
|
||||
|
||||
public class PatchTarget5
|
||||
{
|
||||
public bool ran;
|
||||
|
||||
public string Run(Vector2 vec)
|
||||
{
|
||||
ran = true;
|
||||
return vec.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestParameterValueType()
|
||||
{
|
||||
var target = new PatchTarget5();
|
||||
AddPrefix<PatchTarget5>("patchRan = true");
|
||||
var returnValue = target.Run(new Vector2(1, 2));
|
||||
Assert.True(target.ran);
|
||||
Assert.True(luaCs.Lua.Globals["patchRan"] as bool?);
|
||||
Assert.Equal("{X:1 Y:2}", returnValue);
|
||||
}
|
||||
|
||||
public class PatchTarget6
|
||||
{
|
||||
public bool ran;
|
||||
|
||||
public sbyte RunSByte(sbyte v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public byte RunByte(byte v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public short RunInt16(short v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public ushort RunUInt16(ushort v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public int RunInt32(int v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint RunUInt32(uint v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public long RunInt64(long v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public ulong RunUInt64(ulong v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public float RunSingle(float v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
public double RunDouble(double v)
|
||||
{
|
||||
ran = true;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperSByte()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = SByte(-6)
|
||||
", testMethod: nameof(PatchTarget6.RunSByte));
|
||||
var returnValue = target.RunSByte(-5);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(-6, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperByte()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = Byte(6)
|
||||
", testMethod: nameof(PatchTarget6.RunByte));
|
||||
var returnValue = target.RunByte(5);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(6, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperInt16()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = Int16(-25000)
|
||||
", testMethod: nameof(PatchTarget6.RunInt16));
|
||||
var returnValue = target.RunInt16(30000);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(-25000, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperUInt16()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = UInt16(60000)
|
||||
", testMethod: nameof(PatchTarget6.RunUInt16));
|
||||
var returnValue = target.RunUInt16(50000);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(60000, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperInt32()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = Int32('7FFFFF00', 16)
|
||||
", testMethod: nameof(PatchTarget6.RunInt32));
|
||||
var returnValue = target.RunInt32(900000);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(0x7FFFFF00, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperUInt32()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = UInt32('AFFFFFFF', 16)
|
||||
", testMethod: nameof(PatchTarget6.RunUInt32));
|
||||
var returnValue = target.RunUInt32(300500);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(0xAFFFFFFF, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperInt64()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = Int64('7555555555555555', 16)
|
||||
", testMethod: nameof(PatchTarget6.RunInt64));
|
||||
var returnValue = target.RunInt64(0x7FFFFFFF00000000);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(0x7555555555555555, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperUInt64()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = UInt64('F555555555555555', 16)
|
||||
", testMethod: nameof(PatchTarget6.RunUInt64));
|
||||
var returnValue = target.RunUInt64(0xFFFFFFFF00000000);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(0xF555555555555555, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperSingle()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = Single(123.456)
|
||||
", testMethod: nameof(PatchTarget6.RunSingle));
|
||||
var returnValue = target.RunSingle(111.111f);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(123.456f, returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCastPrimitiveWrapperDouble()
|
||||
{
|
||||
var target = new PatchTarget6();
|
||||
AddPrefix<PatchTarget6>(@"
|
||||
ptable['v'] = Double(123.456)
|
||||
", testMethod: nameof(PatchTarget6.RunDouble));
|
||||
var returnValue = target.RunDouble(111.111d);
|
||||
Assert.True(target.ran);
|
||||
Assert.Equal(123.456d, returnValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace TestProject.LuaCs
|
||||
{
|
||||
internal class TestOutputTextWriterAdapter : TextWriter
|
||||
{
|
||||
private readonly ITestOutputHelper output;
|
||||
|
||||
public TestOutputTextWriterAdapter(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
public override void WriteLine(string? message) => output.WriteLine(message);
|
||||
|
||||
public override void WriteLine(string? format, params object?[] args) => output.WriteLine(format, args);
|
||||
|
||||
public override void Write(char value) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace TestProject.LuaCs
|
||||
{
|
||||
internal class TestOutputTraceListenerAdapter : TraceListener
|
||||
{
|
||||
private readonly ITestOutputHelper output;
|
||||
|
||||
public TestOutputTraceListenerAdapter(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public override void Write(string? message) => throw new NotImplementedException();
|
||||
|
||||
public override void WriteLine(string? message) => output.WriteLine(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user