mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-01 08:18:20 -04:00
* Move all unit tests to Engine/Tests * Fix Margin.EdgeSubtract * Fix Capsule.Contains * EnvironmentVariables.Remove if it's null * Fixed ray trace never returning startedsolid * Fix HistoryList.Navigate on empty list * Fix GameObject.WorldPosition accepting NaNs * Fix flex: initial expanding to the wrong grow/shrink * Translation.TryConvert shouldn't throw on invalid enum strings * Skip sound file tests on machines with no audio device
437 lines
12 KiB
C#
437 lines
12 KiB
C#
using Sandbox.Internal;
|
|
using System.Collections.Generic;
|
|
|
|
namespace SceneTests.GameObjects;
|
|
|
|
[TestClass]
|
|
[DoNotParallelize]
|
|
public class CloneTest : SceneTest
|
|
{
|
|
TypeLibrary TypeLibrary;
|
|
|
|
private TypeLibrary _oldTypeLibrary;
|
|
|
|
[TestInitialize]
|
|
public void TestInitialize()
|
|
{
|
|
// Replace TypeLibrary / NodeLibrary with mocked ones, store the originals
|
|
|
|
_oldTypeLibrary = Game.TypeLibrary;
|
|
|
|
TypeLibrary = new Sandbox.Internal.TypeLibrary();
|
|
TypeLibrary.AddAssembly( typeof( ModelRenderer ).Assembly, false );
|
|
TypeLibrary.AddAssembly( typeof( ComponentWithPrefabSceneReference ).Assembly, false );
|
|
JsonUpgrader.UpdateUpgraders( TypeLibrary );
|
|
|
|
Game.TypeLibrary = TypeLibrary;
|
|
}
|
|
|
|
[TestCleanup]
|
|
public void Cleanup()
|
|
{
|
|
// Make sure our mocked TypeLibrary doesn't leak out, restore old ones
|
|
Game.TypeLibrary = _oldTypeLibrary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When cloning a GameObject that has child GameObjects that are being destroyed, they shouldn't be serialized, or cloned.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void DestroyBCloneASameFrame()
|
|
{
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var a = scene.CreateObject();
|
|
var b = scene.CreateObject();
|
|
b.SetParent( a );
|
|
|
|
// Make sure we have one child on a
|
|
Assert.AreEqual( 1, a.Children.Count );
|
|
|
|
// Mark b as destroyed, won't actually be destroyed until next game tick
|
|
b.Destroy();
|
|
|
|
// Clone a, we shouldn't have any children because we are destroying b
|
|
var c = a.Clone();
|
|
|
|
Assert.AreEqual( 0, c.Children.Count );
|
|
}
|
|
|
|
/// <summary>
|
|
/// When cloning something, you're meant to be able to start it as disabled.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void CloneAsDisabled()
|
|
{
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var a = scene.CreateObject();
|
|
|
|
var b = a.Clone( new CloneConfig()
|
|
{
|
|
StartEnabled = false
|
|
} );
|
|
|
|
Assert.IsFalse( b.Enabled );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ComponentNotClonedFlag()
|
|
{
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var a = scene.CreateObject();
|
|
var comp = a.AddComponent<ComponentWithOnStartOnAwake>();
|
|
comp.Flags |= ComponentFlags.NotCloned;
|
|
|
|
var clone = a.Clone();
|
|
|
|
Assert.IsNull( clone.GetComponent<ComponentWithOnStartOnAwake>() );
|
|
}
|
|
|
|
/// <summary>
|
|
/// When cloning something, you're meant to be able to start it as disabled.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void CloneAsDisabled_Overloads()
|
|
{
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var a = scene.CreateObject();
|
|
|
|
{
|
|
var b = a.Clone( Transform.Zero, null, false );
|
|
Assert.IsFalse( b.Enabled );
|
|
}
|
|
|
|
{
|
|
var b = a.Clone( Transform.Zero, a, false );
|
|
Assert.IsFalse( b.Enabled );
|
|
}
|
|
}
|
|
[TestMethod]
|
|
public void CloneAsDisabled_DoesNotCallOnAwakeAndOnStart()
|
|
{
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var go = scene.CreateObject();
|
|
go.AddComponent<ComponentWithOnStartOnAwake>();
|
|
|
|
var clonedGo = go.Clone( new CloneConfig()
|
|
{
|
|
StartEnabled = false
|
|
} );
|
|
|
|
Assert.IsFalse( clonedGo.Enabled );
|
|
|
|
var clonedComp = clonedGo.GetComponent<ComponentWithOnStartOnAwake>( true );
|
|
Assert.IsNotNull( clonedComp );
|
|
|
|
// need to tick to make sure all callbacks have a change to trigger
|
|
scene.GameTick();
|
|
|
|
Assert.IsFalse( clonedComp.WasAwakeCalled );
|
|
Assert.IsFalse( clonedComp.WasStartCalled );
|
|
}
|
|
[TestMethod]
|
|
public void CloneAsEnabled_DoesCallOnAwakeAndOnStart()
|
|
{
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var go = scene.CreateObject();
|
|
go.AddComponent<ComponentWithOnStartOnAwake>();
|
|
|
|
var clonedGo = go.Clone( new CloneConfig()
|
|
{
|
|
StartEnabled = true
|
|
} );
|
|
|
|
Assert.IsTrue( clonedGo.Enabled );
|
|
|
|
var clonedComp = clonedGo.GetComponent<ComponentWithOnStartOnAwake>( true );
|
|
Assert.IsNotNull( clonedComp );
|
|
|
|
// need to tick to make sure all callbacks have a change to trigger
|
|
scene.GameTick();
|
|
|
|
Assert.IsTrue( clonedComp.WasAwakeCalled );
|
|
Assert.IsTrue( clonedComp.WasStartCalled );
|
|
}
|
|
|
|
/// <summary>
|
|
/// When cloning a prefab, that has a PrefabScene property that references the prefab itself, we should be able to clone it.
|
|
/// The cloned PrefabScene property should still point to the PrefabScene.
|
|
/// </summary>
|
|
[TestMethod]
|
|
[DataRow( false )]
|
|
[DataRow( true )]
|
|
public void CloneSelfReferencingPrefabSceneProperty( bool breakBeforeClone )
|
|
{
|
|
// Load the nested prefab into the resource register so the prefab can find it.
|
|
var pfile = new PrefabFile();
|
|
pfile.RegisterWeakResourceId( "prefabwithselfreference.prefab" );
|
|
pfile.LoadFromJson( PrefabWithSelfReference );
|
|
Game.Resources.Register( pfile );
|
|
|
|
try
|
|
{
|
|
var prefab = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
GameObject gameObjectToClone = prefab;
|
|
// We also want to test cloning broken-from-prefab objects
|
|
if ( breakBeforeClone )
|
|
{
|
|
// This clone is part of the setup and not part of the actual test
|
|
gameObjectToClone = prefab.Clone();
|
|
gameObjectToClone.BreakFromPrefab();
|
|
}
|
|
|
|
var prefabInstance = gameObjectToClone.Clone();
|
|
Assert.IsNotNull( prefabInstance.GetComponent<ComponentWithPrefabSceneReference>() );
|
|
Assert.IsNotNull( prefab.GetComponent<ComponentWithPrefabSceneReference>().PrefabRef );
|
|
Assert.AreEqual( prefab.GetComponent<ComponentWithPrefabSceneReference>().PrefabRef, prefabInstance.GetComponent<ComponentWithPrefabSceneReference>().PrefabRef );
|
|
}
|
|
finally
|
|
{
|
|
Game.Resources.Unregister( pfile );
|
|
}
|
|
}
|
|
|
|
static readonly string PrefabWithSelfReference = """
|
|
{
|
|
"RootObject": {
|
|
"__guid": "1390a3b8-bbc3-4c73-9e18-2edc5b9c2587",
|
|
"Flags": 0,
|
|
"Name": "prefabwithselfreference",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "ComponentWithPrefabSceneReference",
|
|
"__guid": "f1982f17-e2d2-4ee0-b24f-07558f309b78",
|
|
"PrefabRef": {
|
|
"_type": "gameobject",
|
|
"prefab": "prefabwithselfreference.prefab"
|
|
}
|
|
}
|
|
],
|
|
"Children": [],
|
|
"__variables": [],
|
|
"__properties": {
|
|
"FixedUpdateFrequency": 50,
|
|
"MaxFixedUpdates": 5,
|
|
"NetworkFrequency": 30,
|
|
"NetworkInterpolation": true,
|
|
"PhysicsSubSteps": 1,
|
|
"ThreadedAnimation": true,
|
|
"TimeScale": 1,
|
|
"UseFixedUpdate": true,
|
|
"Metadata": {},
|
|
"NavMesh": {
|
|
"Enabled": false,
|
|
"IncludeStaticBodies": true,
|
|
"IncludeKeyframedBodies": true,
|
|
"EditorAutoUpdate": true,
|
|
"AgentHeight": 64,
|
|
"AgentRadius": 16,
|
|
"AgentStepSize": 18,
|
|
"AgentMaxSlope": 40,
|
|
"ExcludedBodies": "",
|
|
"IncludedBodies": ""
|
|
}
|
|
}
|
|
},
|
|
"ShowInMenu": false,
|
|
"MenuPath": null,
|
|
"MenuIcon": null,
|
|
"DontBreakAsTemplate": false,
|
|
"ResourceVersion": 1,
|
|
"__version": 1
|
|
}
|
|
""";
|
|
|
|
public class ComponentWithPrefabSceneReference : Component
|
|
{
|
|
[Sandbox.Property]
|
|
public PrefabScene PrefabRef { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// When cloning a prefab, that has a GameObject property that references the root of the prefab and therefore the prefab itself, we should be able to clone it.
|
|
/// To match old cloning behvaiour, the cloned property should still reference the cloned root GameObject and no longer the PrefabScene.
|
|
/// </summary>
|
|
[TestMethod]
|
|
[DataRow( false )]
|
|
[DataRow( true )]
|
|
public void ClonePrefabRootReferencingGameObjectProperty( bool breakBeforeClone )
|
|
{
|
|
// Load the nested prefab into the resource register so the prefab can find it.
|
|
var pfile = new PrefabFile();
|
|
pfile.RegisterWeakResourceId( "prefabwithRootreference.prefab" );
|
|
pfile.LoadFromJson( PrefabWithRootReference );
|
|
Game.Resources.Register( pfile );
|
|
|
|
try
|
|
{
|
|
var prefab = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
GameObject gameObjectToClone = prefab;
|
|
// We also want to test cloning broken-from-prefab objects
|
|
if ( breakBeforeClone )
|
|
{
|
|
// This clone is part of the setup and not part of the actual test
|
|
gameObjectToClone = prefab.Clone();
|
|
gameObjectToClone.BreakFromPrefab();
|
|
}
|
|
|
|
var prefabInstance = gameObjectToClone.Clone();
|
|
Assert.IsNotNull( prefabInstance.GetComponent<ComponentWithPrefabRootReference>() );
|
|
Assert.IsNotNull( prefab.GetComponent<ComponentWithPrefabRootReference>().PrefabRootRef );
|
|
|
|
Assert.AreNotEqual( prefab.GetComponent<ComponentWithPrefabRootReference>().PrefabRootRef, prefabInstance.GetComponent<ComponentWithPrefabRootReference>().PrefabRootRef );
|
|
Assert.IsInstanceOfType( prefabInstance.GetComponent<ComponentWithPrefabRootReference>().PrefabRootRef, typeof( GameObject ) );
|
|
}
|
|
finally
|
|
{
|
|
Game.Resources.Unregister( pfile );
|
|
}
|
|
}
|
|
|
|
public class ComponentWithPrefabRootReference : Component
|
|
{
|
|
[Sandbox.Property]
|
|
public GameObject PrefabRootRef { get; set; }
|
|
}
|
|
|
|
|
|
static readonly string PrefabWithRootReference = """
|
|
{
|
|
"RootObject": {
|
|
"__guid": "1390a3b8-bbc3-4c73-9e18-2edc5b9c2587",
|
|
"Flags": 0,
|
|
"Name": "prefabwithRootreference",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "ComponentWithPrefabRootReference",
|
|
"__guid": "f1982f17-e2d2-4ee0-b24f-07558f309b78",
|
|
"PrefabRootRef": {
|
|
"_type": "gameobject",
|
|
"prefab": "prefabwithRootreference.prefab"
|
|
}
|
|
}
|
|
],
|
|
"Children": [],
|
|
"__variables": [],
|
|
"__properties": {
|
|
"FixedUpdateFrequency": 50,
|
|
"MaxFixedUpdates": 5,
|
|
"NetworkFrequency": 30,
|
|
"NetworkInterpolation": true,
|
|
"PhysicsSubSteps": 1,
|
|
"ThreadedAnimation": true,
|
|
"TimeScale": 1,
|
|
"UseFixedUpdate": true,
|
|
"Metadata": {},
|
|
"NavMesh": {
|
|
"Enabled": false,
|
|
"IncludeStaticBodies": true,
|
|
"IncludeKeyframedBodies": true,
|
|
"EditorAutoUpdate": true,
|
|
"AgentHeight": 64,
|
|
"AgentRadius": 16,
|
|
"AgentStepSize": 18,
|
|
"AgentMaxSlope": 40,
|
|
"ExcludedBodies": "",
|
|
"IncludedBodies": ""
|
|
}
|
|
}
|
|
},
|
|
"ShowInMenu": false,
|
|
"MenuPath": null,
|
|
"MenuIcon": null,
|
|
"DontBreakAsTemplate": false,
|
|
"ResourceVersion": 1,
|
|
"__version": 1
|
|
}
|
|
""";
|
|
|
|
/// <summary>
|
|
/// We would like to clone some types by copy.
|
|
/// But we can only do that if they don't have any reference types in them.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void ReflectionQueryCache_IsTypeClonableByCopy()
|
|
{
|
|
Assert.IsTrue( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( int ) ) );
|
|
Assert.IsTrue( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( Vector3 ) ) );
|
|
|
|
Assert.IsTrue( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( ValueTypeWithValueTypeProperty ) ) );
|
|
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( List<> ) ) );
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( GameObject ) ) );
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( Component ) ) );
|
|
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( EmptyReferenceType ) ) );
|
|
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( ReferenceTypeWithReferenceTypeProperty ) ) );
|
|
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( ValueTypeWithReferenceTypeProperty ) ) );
|
|
|
|
Assert.IsFalse( ReflectionQueryCache.IsTypeCloneableByCopy( typeof( ValueTypeWithCollectionProperty ) ) );
|
|
}
|
|
}
|
|
|
|
public class EmptyReferenceType
|
|
{
|
|
}
|
|
|
|
public class ReferenceTypeWithReferenceTypeProperty
|
|
{
|
|
public EmptyReferenceType Reference;
|
|
}
|
|
|
|
public struct ValueTypeWithReferenceTypeProperty
|
|
{
|
|
public EmptyReferenceType Reference;
|
|
}
|
|
|
|
public struct ValueTypeWithValueTypeProperty
|
|
{
|
|
public Vector3 Value;
|
|
}
|
|
|
|
public struct ValueTypeWithCollectionProperty
|
|
{
|
|
public List<int> Collection;
|
|
}
|
|
|
|
public class ComponentWithOnStartOnAwake : Component
|
|
{
|
|
public bool WasAwakeCalled = false;
|
|
public bool WasStartCalled = false;
|
|
|
|
protected override void OnAwake()
|
|
{
|
|
base.OnAwake();
|
|
WasAwakeCalled = true;
|
|
}
|
|
|
|
protected override void OnStart()
|
|
{
|
|
base.OnStart();
|
|
WasStartCalled = true;
|
|
}
|
|
}
|