Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions org.mixedrealitytoolkit.core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Changed

* Updated code style in `HandsSubsystemDescriptor`, `MRTKSubsystemDescriptor`, `DictationSubsystemDescriptor`, and `XRSubsystemHelpers`. [PR #1109](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1109)
* `AssemblyExtensions.GetLoadableTypes` now throws `ArgumentNullException` when called on a null assembly. [PR #1122](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1122)

### Added

* Added edit mode tests for `AssemblyExtensions`, `SystemType`, and `SerializableDictionary`. [PR #1122](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1122)

### Fixed

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause

using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace MixedReality.Toolkit.Core.Tests.EditMode
{
/// <summary>
/// Unit tests for AssemblyExtensions.
/// These run outside of PlayMode and do not require Unity engine initialization.
/// </summary>
public class AssemblyExtensionsTests
{
[Test]
public void GetLoadableTypes_ReturnsTypes_ForValidAssembly()
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();

IEnumerable<Type> types = currentAssembly.GetLoadableTypes();

Assert.IsNotNull(types, "GetLoadableTypes should never return null.");
Assert.Greater(types.Count(), 0, "GetLoadableTypes should return the types within the executing assembly.");
Assert.IsTrue(types.Contains(typeof(AssemblyExtensionsTests)), "GetLoadableTypes failed to return known loadable types.");
}

[Test]
public void GetLoadableTypes_HandlesNullAssembly_Safely()
{
Assembly nullAssembly = null;
Assert.Throws<ArgumentNullException>(() => nullAssembly.GetLoadableTypes());
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause

using NUnit.Framework;
using System.Collections.Generic;
using UnityEngine;

namespace MixedReality.Toolkit.Core.Tests.EditMode
{
/// <summary>
/// Unit tests for SerializableDictionary.
/// These run outside of PlayMode and do not require Unity engine initialization.
/// </summary>
public class SerializableDictionaryTests
{
[Test]
public void Serialization_RestoresDictionary_FromInternalEntries()
{
var dict = new SerializableDictionary<string, int>();
dict.Add("Key1", 100);
dict.Add("Key2", 200);

ISerializationCallbackReceiver receiver = dict;

// Simulate Unity preparing to serialize the object (populates the internal 'entries' list)
receiver.OnBeforeSerialize();

// Clear the base dictionary to simulate starting fresh after deserialization
((Dictionary<string, int>)dict).Clear();
Assert.AreEqual(0, dict.Count, "Base dictionary should be empty before deserialization.");

// Simulate Unity finishing deserialization (repopulates the dictionary from 'entries')
receiver.OnAfterDeserialize();

Assert.AreEqual(2, dict.Count, "Dictionary should have restored 2 items.");
Assert.AreEqual(100, dict["Key1"]);
Assert.AreEqual(200, dict["Key2"]);
}

[Test]
public void EditorOverride_Clear_RemovesAllSerializedEntries()
{
var dict = new SerializableDictionary<string, int>();
dict.Add("Key1", 100);

ISerializationCallbackReceiver receiver = dict;
receiver.OnBeforeSerialize(); // Populate internal list

// Call the custom overridden Clear()
dict.Clear();

// Attempt to deserialize (which would normally restore Key1 if the internal list wasn't cleared)
receiver.OnAfterDeserialize();

Assert.AreEqual(0, dict.Count, "Dictionary should remain empty because the internal serialized entries were cleared.");
}

[Test]
public void EditorOverride_Remove_RemovesSpecificSerializedEntry()
{
var dict = new SerializableDictionary<string, int>();
dict.Add("A", 1);
dict.Add("B", 2);
dict.Add("C", 3);

ISerializationCallbackReceiver receiver = dict;
receiver.OnBeforeSerialize();

// Use the overridden remove method which should also remove from the internal list
bool removedB = dict.Remove("B");
bool removedC = dict.Remove("C", out int valC);

// Clear the dictionary and restore from the serialized list to verify they are gone
((Dictionary<string, int>)dict).Clear();
receiver.OnAfterDeserialize();

Assert.IsTrue(removedB, "Remove(key) should return true for existing key.");
Assert.IsTrue(removedC, "Remove(key, out val) should return true for existing key.");
Assert.AreEqual(3, valC, "Remove(key, out val) should output the correct value.");

Assert.AreEqual(1, dict.Count, "Only 1 item should remain after deserialization.");
Assert.IsTrue(dict.ContainsKey("A"), "Key 'A' should have been preserved.");
Assert.IsFalse(dict.ContainsKey("B"), "Key 'B' should have been permanently removed.");
Assert.IsFalse(dict.ContainsKey("C"), "Key 'C' should have been permanently removed.");
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

116 changes: 116 additions & 0 deletions org.mixedrealitytoolkit.core/Tests/Editor/SystemTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause

using NUnit.Framework;
using System;
using UnityEngine;
using UnityEngine.TestTools;

namespace MixedReality.Toolkit.Core.Tests.EditMode
{
/// <summary>
/// Unit tests for SystemType.
/// These run outside of PlayMode and do not require Unity engine initialization.
/// </summary>
public class SystemTypeTests
{
[Test]
public void GetReference_FromType_ReturnsValidString()
{
string reference = SystemType.GetReference(typeof(Vector3));

Assert.IsFalse(string.IsNullOrEmpty(reference));
Assert.IsTrue(reference.Contains("UnityEngine.Vector3"));
Assert.IsTrue(reference.Contains("UnityEngine.CoreModule"));
}

[Test]
public void GetReference_NullOrEmpty_ReturnsEmptyString()
{
Assert.AreEqual(string.Empty, SystemType.GetReference((Type)null));
Assert.AreEqual(string.Empty, SystemType.GetReference((string)null));
Assert.AreEqual(string.Empty, SystemType.GetReference(string.Empty));
}

[Test]
public void Constructors_SetPropertiesCorrectly()
{
Type targetType = typeof(int);

// Initialize from Type
SystemType fromType = new SystemType(targetType);
Assert.AreEqual(targetType, fromType.Type);
Assert.AreEqual(SystemType.GetReference(targetType), (string)fromType);

// Initialize from AssemblyQualifiedName
SystemType fromString = new SystemType(targetType.AssemblyQualifiedName);
Assert.AreEqual(targetType, fromString.Type);
}

[Test]
public void Constructor_AbstractType_SetsTypeToNull()
{
// SystemType is intentionally designed to nullify abstract types when initialized via string
SystemType fromString = new SystemType(typeof(Array).AssemblyQualifiedName);
Assert.IsNull(fromString.Type, "SystemType should not allow abstract types when initialized from an assembly string.");
}

[Test]
public void InvalidTypeAssignment_LogsError_ButSetsType()
{
SystemType sysType = new SystemType(typeof(int));

// Enums violate the ValidConstraint. SystemType logs an error but still completes the assignment.
LogAssert.Expect(LogType.Error, $"'{typeof(DayOfWeek).FullName}' is not a valid class or struct type.");

sysType.Type = typeof(DayOfWeek);

Assert.AreEqual(typeof(DayOfWeek), sysType.Type);
}

[Test]
public void ImplicitConversions_WorkCorrectly()
{
Type originalType = typeof(string);

// Type -> SystemType
SystemType sysType = originalType;
Assert.IsNotNull(sysType);

// SystemType -> Type
Type convertedType = sysType;
Assert.AreEqual(originalType, convertedType);

// SystemType -> string
string reference = sysType;
Assert.AreEqual(SystemType.GetReference(originalType), reference);
}

[Test]
public void Equality_MatchesSameTypes()
{
SystemType type1 = new SystemType(typeof(float));
SystemType type2 = new SystemType(typeof(float));
SystemType type3 = new SystemType(typeof(double));

Assert.IsTrue(type1.Equals(type2));
Assert.IsFalse(type1.Equals(type3));
Assert.IsFalse(type1.Equals(null));

// HashCodes should match for identical references
Assert.AreEqual(type1.GetHashCode(), type2.GetHashCode());
}

[Test]
public void Serialization_RestoresType()
{
var sysType = new SystemType(typeof(int));
ISerializationCallbackReceiver receiver = sysType;

// SystemType uses OnAfterDeserialize to re-establish the `type` mapping from the string `reference`
receiver.OnAfterDeserialize();

Assert.AreEqual(typeof(int), sysType.Type);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ namespace MixedReality.Toolkit
public static class AssemblyExtensions
{
/// <summary>
/// Assembly.GetTypes() can throw in some cases. This extension will catch that exception and return only the types which were successfully loaded from the assembly.
/// Assembly.GetTypes() can throw in some cases. This extension will catch that exception and return only the types which were successfully loaded from the assembly.
/// </summary>
public static IEnumerable<Type> GetLoadableTypes(this Assembly @this)
{
if (@this == null)
{
throw new ArgumentNullException(nameof(@this), "Assembly cannot be null.");
}

try
{
return @this.GetTypes();
Expand Down