mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
- Added Sandbox.Test.Unit project (contains independent tests that can run in parallel) - Modify some slow/stress tests (e.g. instead of doing a million iterations settle for 10k). Tests run almost twice as fast now.
436 lines
9.5 KiB
C#
436 lines
9.5 KiB
C#
using System;
|
|
using static Sandbox.IByteParsable;
|
|
|
|
namespace SystemTest;
|
|
|
|
public class MyDataClass : IByteParsable<MyDataClass>
|
|
{
|
|
public string DataContents;
|
|
|
|
public static object ReadObject( ref ByteStream stream, ByteParseOptions o ) => Read( ref stream, o );
|
|
public static void WriteObject( ref ByteStream stream, object value, ByteParseOptions o ) => Write( ref stream, value as MyDataClass, o );
|
|
|
|
public static MyDataClass Read( ref ByteStream stream, ByteParseOptions o )
|
|
{
|
|
if ( stream.Read<byte>() == 0 )
|
|
return default;
|
|
|
|
var value = new MyDataClass();
|
|
value.DataContents = stream.Read<string>();
|
|
|
|
return value;
|
|
}
|
|
|
|
public static void Write( ref ByteStream stream, MyDataClass value, ByteParseOptions o )
|
|
{
|
|
if ( value is null )
|
|
{
|
|
stream.Write( (byte)0 );
|
|
return;
|
|
}
|
|
|
|
stream.Write( (byte)1 );
|
|
stream.Write( value.DataContents );
|
|
}
|
|
}
|
|
|
|
public class MyDataClassChild : MyDataClass
|
|
{
|
|
|
|
}
|
|
|
|
public struct MyPlainOldDataClass : IByteParsable<MyPlainOldDataClass>
|
|
{
|
|
public Vector3 Position;
|
|
|
|
public static object ReadObject( ref ByteStream stream, ByteParseOptions o ) => Read( ref stream, o );
|
|
public static void WriteObject( ref ByteStream stream, object value, ByteParseOptions o ) => Write( ref stream, (MyPlainOldDataClass)value, o );
|
|
|
|
public static MyPlainOldDataClass Read( ref ByteStream stream, ByteParseOptions o )
|
|
{
|
|
var value = new MyPlainOldDataClass();
|
|
value.Position = stream.Read<Vector3>();
|
|
|
|
return value;
|
|
}
|
|
|
|
public static void Write( ref ByteStream stream, MyPlainOldDataClass value, ByteParseOptions o )
|
|
{
|
|
stream.Write( value.Position );
|
|
}
|
|
}
|
|
|
|
[TestClass]
|
|
public class ByteStreamTest
|
|
{
|
|
[TestMethod]
|
|
public void WriterBasic()
|
|
{
|
|
var s = ByteStream.Create( 512 );
|
|
|
|
s.Write( (int)32 );
|
|
Assert.AreEqual( 4, s.Position );
|
|
s.Write( (uint)32 );
|
|
Assert.AreEqual( 8, s.Position );
|
|
s.Write( (short)32 );
|
|
Assert.AreEqual( 10, s.Position );
|
|
s.Write( (ushort)32 );
|
|
Assert.AreEqual( 12, s.Position );
|
|
s.Write( (long)32 );
|
|
Assert.AreEqual( 20, s.Position );
|
|
s.Write( (ulong)32 );
|
|
Assert.AreEqual( 28, s.Position );
|
|
s.Write( Vector2.One );
|
|
Assert.AreEqual( 36, s.Position );
|
|
s.Write( Vector3.One );
|
|
Assert.AreEqual( 48, s.Position );
|
|
s.Write( Vector4.One );
|
|
Assert.AreEqual( 64, s.Position );
|
|
s.Write( Transform.Zero );
|
|
s.Write( "hello there this is a big string" );
|
|
|
|
Console.WriteLine( $"Written {s.Position}b" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadWriteInt()
|
|
{
|
|
var s = ByteStream.Create( 512 );
|
|
|
|
s.Write( 32 );
|
|
s.Write( 64 );
|
|
s.Write( 512 );
|
|
|
|
var data = s.ToArray();
|
|
|
|
s.Dispose();
|
|
|
|
s = ByteStream.CreateReader( data );
|
|
Assert.AreEqual( 32, s.Read<int>() );
|
|
Assert.AreEqual( 64, s.Read<int>() );
|
|
Assert.AreEqual( 512, s.Read<int>() );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadWriteFloat()
|
|
{
|
|
var s = ByteStream.Create( 512 );
|
|
|
|
s.Write( 32.0f );
|
|
s.Write( 64.0f );
|
|
s.Write( 512.0f );
|
|
|
|
var data = s.ToArray();
|
|
|
|
s.Dispose();
|
|
|
|
s = ByteStream.CreateReader( data );
|
|
Assert.AreEqual( 32.0f, s.Read<float>() );
|
|
Assert.AreEqual( 64.0f, s.Read<float>() );
|
|
Assert.AreEqual( 512.0f, s.Read<float>() );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadWriteVector3()
|
|
{
|
|
var s = ByteStream.Create( 512 );
|
|
|
|
s.Write( new Vector3( 4, 5, 1 ) );
|
|
s.Write( new Vector3( 44, 1235, 5121 ) );
|
|
s.Write( new Vector3( 0, 0, 0 ) );
|
|
|
|
var data = s.ToArray();
|
|
|
|
s.Dispose();
|
|
|
|
s = ByteStream.CreateReader( data );
|
|
Assert.AreEqual( new Vector3( 4, 5, 1 ), s.Read<Vector3>() );
|
|
Assert.AreEqual( new Vector3( 44, 1235, 5121 ), s.Read<Vector3>() );
|
|
Assert.AreEqual( new Vector3( 0, 0, 0 ), s.Read<Vector3>() );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadWriteByteParsable()
|
|
{
|
|
var s = ByteStream.Create( 512 );
|
|
|
|
var a = new MyDataClass();
|
|
a.DataContents = "Hello There Happy Birthday Superman";
|
|
|
|
s.Write( a );
|
|
|
|
var data = s.ToArray();
|
|
|
|
Assert.AreNotEqual( 0, data.Length );
|
|
|
|
s.Dispose();
|
|
|
|
s = ByteStream.CreateReader( data );
|
|
|
|
var b = s.Read<MyDataClass>();
|
|
|
|
Assert.AreEqual( b.DataContents, a.DataContents );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadWriteByteParsableStruct()
|
|
{
|
|
var s = ByteStream.Create( 512 );
|
|
|
|
var a = new MyPlainOldDataClass();
|
|
a.Position = Vector3.Up * 1000.0f;
|
|
|
|
s.Write( a );
|
|
|
|
var data = s.ToArray();
|
|
|
|
Assert.AreNotEqual( 0, data.Length );
|
|
|
|
s.Dispose();
|
|
|
|
s = ByteStream.CreateReader( data );
|
|
|
|
var b = s.Read<MyPlainOldDataClass>();
|
|
|
|
Assert.AreEqual( b.Position, a.Position );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Growing()
|
|
{
|
|
var s = ByteStream.Create( 2 );
|
|
|
|
s.Write( 32 );
|
|
s.Write( 64 );
|
|
s.Write( 512 );
|
|
|
|
var data = s.ToArray();
|
|
|
|
s.Dispose();
|
|
|
|
s = ByteStream.CreateReader( data );
|
|
Assert.AreEqual( 32, s.Read<int>() );
|
|
Assert.AreEqual( 64, s.Read<int>() );
|
|
Assert.AreEqual( 512, s.Read<int>() );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void WriteStreamToStream()
|
|
{
|
|
using var s = ByteStream.Create( 2 );
|
|
|
|
s.Write( 64 );
|
|
s.Write( "Hello" );
|
|
s.Write( 512 );
|
|
|
|
using var t = ByteStream.Create( 2 );
|
|
t.Write( 32 );
|
|
t.Write( s );
|
|
t.Write( 109 );
|
|
|
|
Assert.AreEqual( s.Length + 4 + 4, t.Length );
|
|
|
|
var data = t.ToArray();
|
|
|
|
var r = ByteStream.CreateReader( data );
|
|
Assert.AreEqual( 32, r.Read<int>() );
|
|
Assert.AreEqual( 64, r.Read<int>() );
|
|
Assert.AreEqual( "Hello", r.Read<string>() );
|
|
Assert.AreEqual( 512, r.Read<int>() );
|
|
Assert.AreEqual( 109, r.Read<int>() );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void No_Negative_Buffers()
|
|
{
|
|
Assert.ThrowsException<ArgumentOutOfRangeException>( () => ByteStream.Create( -1 ) );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadBuffer()
|
|
{
|
|
var data = Enumerable.Range( 0, 256 ).Select( x => (byte)x ).ToArray();
|
|
var r = ByteStream.CreateReader( data );
|
|
|
|
var buffer = new byte[256];
|
|
|
|
r.Read<int>();
|
|
|
|
Assert.AreEqual( 128, r.Read( buffer, 128, 128 ) );
|
|
|
|
Assert.AreEqual( 4, buffer[128] );
|
|
Assert.AreEqual( 5, buffer[129] );
|
|
Assert.AreEqual( 127 + 4, buffer[128 + 127] );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadBufferUntilStreamEnd()
|
|
{
|
|
var data = Enumerable.Range( 0, 16 ).Select( x => (byte)x ).ToArray();
|
|
var r = ByteStream.CreateReader( data );
|
|
|
|
var buffer = new byte[256];
|
|
|
|
r.Read<int>();
|
|
|
|
Assert.AreEqual( 12, r.Read( buffer, 128, 128 ) );
|
|
|
|
Assert.AreEqual( 4, buffer[128] );
|
|
Assert.AreEqual( 5, buffer[129] );
|
|
Assert.AreEqual( 15, buffer[128 + 11] );
|
|
|
|
Assert.AreEqual( 0, r.Read( buffer, 0, 256 ) );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ReadBufferOutOfRange()
|
|
{
|
|
var buffer = new byte[256];
|
|
|
|
Assert.ThrowsException<ArgumentOutOfRangeException>( () =>
|
|
{
|
|
var r = ByteStream.CreateReader( new byte[128] );
|
|
return r.Read( buffer, -1, 16 );
|
|
} );
|
|
|
|
Assert.ThrowsException<ArgumentOutOfRangeException>( () =>
|
|
{
|
|
var r = ByteStream.CreateReader( new byte[128] );
|
|
return r.Read( buffer, 384, 16 );
|
|
} );
|
|
|
|
Assert.ThrowsException<ArgumentOutOfRangeException>( () =>
|
|
{
|
|
var r = ByteStream.CreateReader( new byte[128] );
|
|
return r.Read( buffer, 128, 256 );
|
|
} );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void LeakExploit()
|
|
{
|
|
const int chunkSize = 0x50000000 - 128;
|
|
byte[] chunk1 = new byte[chunkSize];
|
|
|
|
const int kLeakSize = 8;
|
|
|
|
Assert.ThrowsException<System.OverflowException>( () =>
|
|
{
|
|
ByteStream leakStream = ByteStream.Create( 128 );
|
|
leakStream.Write( chunk1, (int)(0x80000000 - kLeakSize), kLeakSize );
|
|
leakStream.Position = 0;
|
|
ulong arrPtr = leakStream.Read<ulong>();
|
|
|
|
} );
|
|
}
|
|
|
|
/// <summary>
|
|
/// We were getting rare access violations when decompressing a <see cref="ByteStream"/>.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void DecompressStressTest()
|
|
{
|
|
var payloadBytes = CreateRandomBytes( 0x1eb78437, 1024 );
|
|
|
|
using var payload = new ByteStream( payloadBytes );
|
|
using var compressed = payload.Compress();
|
|
|
|
var compressedBytes = compressed.ToArray();
|
|
|
|
for ( var i = 0; i < 10; ++i )
|
|
{
|
|
using var temp = new ByteStream( compressedBytes );
|
|
|
|
GC.Collect();
|
|
|
|
using var decompressed = temp.Decompress();
|
|
|
|
Assert.AreEqual( payloadBytes.Length, decompressed.Length );
|
|
|
|
foreach ( var b in payloadBytes )
|
|
{
|
|
Assert.AreEqual( b, decompressed.Read<byte>() );
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void WriteOffset()
|
|
{
|
|
using var stream = new ByteStream( 10 );
|
|
|
|
var data = new byte[100];
|
|
|
|
data[50] = 123;
|
|
data[59] = 231;
|
|
|
|
stream.Write( data, 50, 10 );
|
|
|
|
var written = stream.ToArray();
|
|
|
|
Assert.AreEqual( 123, written[0] );
|
|
Assert.AreEqual( 231, written[9] );
|
|
}
|
|
|
|
private static byte[] CreateRandomBytes( int seed, int length )
|
|
{
|
|
var random = new Random( seed );
|
|
var bytes = new byte[length];
|
|
|
|
random.NextBytes( bytes );
|
|
|
|
return bytes;
|
|
}
|
|
|
|
// This isn't : unmanaged
|
|
public readonly record struct TypeWrapper( Type Value );
|
|
|
|
interface IConstrainetBreaker<TFrom>
|
|
{
|
|
void Write( TFrom value );
|
|
void Read();
|
|
}
|
|
|
|
public class ConstraintBreaker<TFrom> : IConstrainetBreaker<TFrom> where TFrom : unmanaged
|
|
{
|
|
public void Write( TFrom value )
|
|
{
|
|
var byteStream = ByteStream.Create( IntPtr.Size );
|
|
byteStream.Write( value );
|
|
}
|
|
|
|
public void Read()
|
|
{
|
|
var byteStream = ByteStream.Create( IntPtr.Size );
|
|
var x = byteStream.Read<TFrom>();
|
|
}
|
|
}
|
|
|
|
|
|
[TestMethod]
|
|
public void CannotWriteNonPod()
|
|
{
|
|
var breakerType = typeof( ConstraintBreaker<> ).MakeGenericType( [typeof( TypeWrapper )] );
|
|
var instance = (IConstrainetBreaker<TypeWrapper>)Activator.CreateInstance( breakerType );
|
|
|
|
Assert.ThrowsException<InvalidOperationException>(
|
|
() =>
|
|
{
|
|
instance.Write( new TypeWrapper( typeof( int ) ) );
|
|
} );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CannotReadNonPod()
|
|
{
|
|
var breakerType = typeof( ConstraintBreaker<> ).MakeGenericType( [typeof( TypeWrapper )] );
|
|
var instance = (IConstrainetBreaker<TypeWrapper>)Activator.CreateInstance( breakerType );
|
|
|
|
Assert.ThrowsException<InvalidOperationException>(
|
|
() =>
|
|
{
|
|
instance.Read();
|
|
} );
|
|
}
|
|
}
|