上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 54a2f503330754db18fa779653556e95
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using UnityEditor.Build.Reporting;
using UnityEditor.VersionControl;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Features.Mock;
using Assert = UnityEngine.Assertions.Assert;
using UnityEngine.XR.OpenXR.Tests;
using static UnityEditor.XR.OpenXR.Tests.OpenXREditorTestHelpers;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class BootConfigEditorTests : OpenXRLoaderSetup
{
[Test]
public void TestCanCreateBootConfigAndroid()
{
TestBuildTarget(BuildTarget.Android);
}
[Test]
public void TestCanCreateBootConfigWindows()
{
TestBuildTarget(BuildTarget.StandaloneWindows);
TestBuildTarget(BuildTarget.StandaloneWindows64);
}
private void TestBuildTarget(BuildTarget buildTarget)
{
var bootConfig = new BootConfig(buildTarget);
bootConfig.ReadBootConfig();
// Check to see that we do not have the following key in the boot config
Assert.IsFalse(bootConfig.TryGetValue("xr-sample-bootconfig-key01", out string value));
Assert.AreEqual(value, null);
// Check to see that we can store a key and retrieve it.
bootConfig.SetValueForKey("xr-sample-bootconfig-key02", "primary value");
Assert.IsTrue(bootConfig.TryGetValue("xr-sample-bootconfig-key02", out string key02value));
Assert.AreEqual(key02value, "primary value");
Assert.IsTrue(bootConfig.CheckValuePairExists("xr-sample-bootconfig-key02", "primary value"));
// check to see that we can write the keys to the boot config and ensure that we can
// retrieve the stored values
bootConfig.WriteBootConfig();
var cloneBootConfig = new BootConfig(buildTarget);
cloneBootConfig.ReadBootConfig();
Assert.IsTrue(cloneBootConfig.TryGetValue("xr-sample-bootconfig-key02", out key02value));
Assert.AreEqual(key02value, "primary value");
Assert.IsTrue(cloneBootConfig.CheckValuePairExists("xr-sample-bootconfig-key02", "primary value"));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1c3182e62fd44cea6ebdbb5b36feec5
timeCreated: 1677640135

View File

@@ -0,0 +1,57 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using UnityEditor.Build.Reporting;
using UnityEditor.VersionControl;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Features.Mock;
using Assert = UnityEngine.Assertions.Assert;
using UnityEngine.XR.OpenXR.Tests;
using static UnityEditor.XR.OpenXR.Tests.OpenXREditorTestHelpers;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class FeatureModifyingTests : OpenXRLoaderSetup
{
// Override AfterTest to prevent OpenXRSettings.Instance.features from getting reset.
// This test suite destroys and restores OpenXRSettings.Instance.features manually.
public override void AfterTest()
{
}
[Test]
public void DuplicateSettingAssetTest()
{
// Local OpenXR filepath that contains the test OpenXR Package Settings.asset
string openXRFolder = Path.GetFullPath("Packages/com.unity.xr.openxr");
string settingsFilePath = OpenXRPackageSettings.OpenXRPackageSettingsAssetPath();
string metaFilePath = settingsFilePath + ".meta";
string testAssetName = "OpenXR Package Settings With Duplicates.testasset";
string testAssetPath = Path.Combine(openXRFolder, "Tests", "Editor", testAssetName);
string testMetaAssetPath = testAssetPath + ".meta";
// Copy in the test files (the files with duplicate settings)
File.Delete(settingsFilePath);
File.Delete(metaFilePath);
File.Copy(testAssetPath, settingsFilePath);
File.Copy(testMetaAssetPath, metaFilePath);
// Verify that we detect duplicates in the test file.
Assert.IsFalse(OpenXRProjectValidation.AssetHasNoDuplicates(), "The duplicate settings on the bad asset should be detected.");
// Regenerate the asset (as if the user clicks on the Fix button in the validation window)
OpenXRProjectValidation.RegenerateXRPackageSettingsAsset();
// Verify that there are no duplicates in the settings file now.
Assert.IsTrue(OpenXRProjectValidation.AssetHasNoDuplicates(), "After regenerating the asset, the duplicate settings should be removed.");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2755597eb30ac124c861b2694debd290
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,408 @@
using System;
using System.Linq;
using NUnit.Framework;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Features;
using Assert = UnityEngine.Assertions.Assert;
using static UnityEditor.XR.OpenXR.Features.OpenXRFeatureSetManager;
using static UnityEditor.XR.OpenXR.Tests.OpenXREditorTestHelpers;
using UnityEngine.XR.OpenXR.Tests;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class FeatureSetTests : OpenXRLoaderSetup
{
const string k_KnownFeatureSetName = "Known Test";
const string k_TestFeatureSetName = "Test Feature Set";
const string k_TestFeatureSetNameHandAndEye = "Test Feature Set Hand and Eye Tracking";
const string k_TestFeatureSetNameHand = "Test Feature Set Hand Tracking";
const string k_TestFeatureSetDescription = "Test feature set";
const string k_TestFeatureSetId = "com.unity.xr.test.featureset";
const string k_TestFeatureSetIdTwo = "com.unity.xr.test.featureset2";
const string k_TestFeatureSetIdThree = "com.unity.xr.test.featureset3";
const string k_TestFeatureSetIdFour = "com.unity.xr.test.featureset4";
[OpenXRFeatureSet(
FeatureIds = new string[]
{
MicrosoftHandInteraction.featureId
},
UiName = k_TestFeatureSetName,
Description = k_TestFeatureSetDescription,
FeatureSetId = k_TestFeatureSetId,
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.Standalone },
RequiredFeatureIds = new string[]
{
MicrosoftHandInteraction.featureId
}
)]
[OpenXRFeatureSet(
FeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
},
UiName = k_TestFeatureSetNameHandAndEye,
Description = k_TestFeatureSetDescription,
FeatureSetId = k_TestFeatureSetIdTwo,
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.WSA },
RequiredFeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
}
)]
[OpenXRFeatureSet(
FeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
},
UiName = k_TestFeatureSetNameHand,
Description = k_TestFeatureSetDescription,
FeatureSetId = k_TestFeatureSetIdThree,
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.WSA },
RequiredFeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
}
)]
[OpenXRFeatureSet(
FeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
},
UiName = k_TestFeatureSetName,
Description = k_TestFeatureSetDescription,
FeatureSetId = k_TestFeatureSetId,
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.Android },
RequiredFeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
}
)]
[OpenXRFeatureSet(
FeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
HTCViveControllerProfile.featureId,
OculusTouchControllerProfile.featureId,
},
UiName = k_TestFeatureSetName,
Description = k_TestFeatureSetDescription,
FeatureSetId = k_TestFeatureSetIdFour,
SupportedBuildTargets = new BuildTargetGroup[] { BuildTargetGroup.Standalone },
RequiredFeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
},
DefaultFeatureIds = new string[]
{
HTCViveControllerProfile.featureId,
}
)]
sealed class TestFeatureSet { }
public override void BeforeTest()
{
base.BeforeTest();
OpenXRFeature.canSetFeatureDisabled = null;
InitializeFeatureSets(true);
}
/// <summary>
/// Initialize the feature sets by disabling all features sets and all features
/// </summary>
/// <param name="addTestFeatures">True to include test features</param>
private void InitializeFeatureSets(bool addTestFeatures)
{
// Initialize first with test feature sets so we can make sure all feature sets are disabled
OpenXRFeatureSetManager.InitializeFeatureSets(true);
foreach (var buildTargetGroup in GetBuildTargetGroups())
{
// Disable all feature sets for this build target
foreach (var featureSetInfo in FeatureSetInfosForBuildTarget(buildTargetGroup))
{
featureSetInfo.isEnabled = false;
featureSetInfo.wasEnabled = false;
OpenXREditorSettings.Instance.SetFeatureSetSelected(buildTargetGroup, featureSetInfo.featureSetId, false);
}
// Disable all features for this build target
var extInfo = FeatureHelpersInternal.GetAllFeatureInfo(buildTargetGroup);
foreach (var ext in extInfo.Features)
{
ext.Feature.enabled = false;
}
}
// If requested with no feature sets then reinitialize
if (!addTestFeatures)
OpenXRFeatureSetManager.InitializeFeatureSets(false);
foreach (var buildTargetGroup in GetBuildTargetGroups())
{
// No feature sets should be enabled for any build target
Assert.IsFalse(FeatureSetInfosForBuildTarget(buildTargetGroup).Any(f => f.isEnabled));
// No features should be enabled
AssertAllFeatures(buildTargetGroup, FeatureDisabled);
}
}
public override void AfterTest()
{
base.AfterTest();
OpenXRFeature.canSetFeatureDisabled = OpenXRFeatureSetManager.CanFeatureBeDisabled;
}
[Test]
public void NoFeatureSetsReturnsEmptyList()
{
var featureSets = FeatureSetsForBuildTarget(BuildTargetGroup.iOS);
Assert.AreEqual(0, featureSets.Count);
}
[Test]
public void FoundExpectedFeatureSets()
{
InitializeFeatureSets(false);
string[] expectedFeatureSets = new string[]
{
KnownFeatureSetsContent.s_MicrosoftHoloLensFeatureSetId
};
var featureSets = FeatureSetsForBuildTarget(BuildTargetGroup.WSA);
Assert.IsNotNull(featureSets);
Assert.AreEqual(expectedFeatureSets.Length, featureSets.Count);
foreach (var featureSet in featureSets)
{
if (Array.IndexOf(expectedFeatureSets, featureSet.featureSetId) == -1)
Assert.IsTrue(false, $"Found unexpected feature set id {featureSet.featureSetId}!");
}
}
[Test]
public void UnknownFeatureSetRerturnNull()
{
// For this test we do not want the test features enabled so rerun the initilization with
InitializeFeatureSets(false);
var foundFeatureSet = GetFeatureSetWithId(BuildTargetGroup.iOS, k_TestFeatureSetId);
Assert.IsNull(foundFeatureSet);
foundFeatureSet = GetFeatureSetWithId(BuildTargetGroup.Standalone, "BAD FEATURE SET ID");
Assert.IsNull(foundFeatureSet);
}
[Test]
public void OverrideKnownTestFeatureSet()
{
var foundFeatureSet = GetFeatureSetWithId(BuildTargetGroup.Standalone, k_TestFeatureSetId);
Assert.IsNotNull(foundFeatureSet);
Assert.AreEqual(0, String.Compare(foundFeatureSet.name, k_TestFeatureSetName, true));
}
[Test]
public void NonoverrideKnownTestFeatureSet()
{
var foundFeatureSet = GetFeatureSetWithId(BuildTargetGroup.WSA, k_TestFeatureSetId);
Assert.IsNotNull(foundFeatureSet);
Assert.AreEqual(0, String.Compare(foundFeatureSet.name, k_KnownFeatureSetName, true));
}
[Test]
public void EnableFeatureSetEnablesFeatures()
{
EnableFeatureSet(BuildTargetGroup.Standalone, k_TestFeatureSetId, enabled: true);
AssertOnlyFeatures(BuildTargetGroup.Standalone, new string[] { MicrosoftHandInteraction.featureId }, FeatureEnabled);
}
[Test]
public void DisableFeatureSetDisabledFeatures()
{
// Enable the feature set and make sure only its features are enabled
EnableFeatureSet(BuildTargetGroup.Standalone, k_TestFeatureSetId, true);
AssertOnlyFeatures(BuildTargetGroup.Standalone, new string[] { MicrosoftHandInteraction.featureId }, FeatureEnabled);
// Disable the feature set an make sure its features are disabled
EnableFeatureSet(BuildTargetGroup.Standalone, k_TestFeatureSetId, false);
AssertAllFeatures(BuildTargetGroup.Standalone, FeatureDisabled);
}
[Test]
public void DisableSharedFeaturesLeaveSharedFeaturesEnabled()
{
// Ensable all WSA feature sets and make sure only the WSA feature set features are enabled
EnableFeatureSets(BuildTargetGroup.WSA, enabled: true);
AssertOnlyFeatures(BuildTargetGroup.WSA, new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
}, FeatureEnabled);
// Disable the feature seth with both features set as required
EnableFeatureSet(BuildTargetGroup.WSA, k_TestFeatureSetIdTwo, enabled: false);
AssertOnlyFeatures(BuildTargetGroup.WSA, new string[]
{
MicrosoftHandInteraction.featureId,
}, FeatureEnabled);
// Disable all WSA feature sets and make sure all features are disabled
EnableFeatureSets(BuildTargetGroup.WSA, enabled: false);
AssertAllFeatures(BuildTargetGroup.WSA, FeatureDisabled);
}
[Test]
public void DisableSharedFeaturesLeaveOthersFeaturesEnabled()
{
string[] allFeatureIds = new string[]
{
MicrosoftHandInteraction.featureId,
EyeGazeInteraction.featureId,
MicrosoftMotionControllerProfile.featureId,
};
string[] otherFeatureIds = new string[]
{
MicrosoftMotionControllerProfile.featureId,
};
EnableFeatureInfos(BuildTargetGroup.WSA, otherFeatureIds, true);
// Enable the second feature set and ensure that only features in the `all` list are enabled
var featureSetToEnable = GetFeatureSetInfoWithId(BuildTargetGroup.WSA, k_TestFeatureSetIdTwo);
EnableFeatureSet(BuildTargetGroup.WSA, featureSetToEnable.featureSetId, true);
AssertOnlyFeatures(BuildTargetGroup.WSA, allFeatureIds, FeatureEnabled);
// Disable the second feature set and ensure only features in the `others` list are enabled
var featureSetToDisable = GetFeatureSetInfoWithId(BuildTargetGroup.WSA, k_TestFeatureSetIdTwo);
Assert.IsNotNull(featureSetToDisable);
EnableFeatureSet(BuildTargetGroup.WSA, featureSetToDisable.featureSetId, enabled: false);
AssertOnlyFeatures(BuildTargetGroup.WSA, otherFeatureIds, FeatureEnabled);
}
[Test]
public void EnablingFeatureSetEnabledDefaultFeatures()
{
var foundFeatureSet = GetFeatureSetInfoWithId(BuildTargetGroup.Standalone, k_TestFeatureSetIdFour);
Assert.IsNotNull(foundFeatureSet);
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, true);
// Ensure that only the non-optional features are enabled
AssertOnlyFeatures(BuildTargetGroup.Standalone, foundFeatureSet.featureIds, (f) => f.Feature.enabled == !FeatureIsOptional(foundFeatureSet, f));
}
[Test]
public void EnablingFeatureSetLeavesOptionFeaturesEnabled()
{
// Enable the feature set
var foundFeatureSet = GetFeatureSetInfoWithId(BuildTargetGroup.Standalone, k_TestFeatureSetIdFour);
Assert.IsNotNull(foundFeatureSet);
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, true);
// Ensure the Optional features are all disabled
AssertAllFeatures(BuildTargetGroup.Standalone, (f) => !FeatureIsOptional(foundFeatureSet, f) || !f.Feature.enabled);
// Disable the feature set and ensure the optional features are disabled
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, false);
AssertAllFeatures(BuildTargetGroup.Standalone, (f) => !FeatureIsOptional(foundFeatureSet, f) || !f.Feature.enabled);
// Enable the optional features and the feature set and ensure the optional features are still enabled
EnableFeatureInfos(BuildTargetGroup.Standalone, true, (f) => FeatureIsOptional(foundFeatureSet, f));
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, enabled: true);
AssertAllFeatures(BuildTargetGroup.Standalone, (f) => !FeatureIsOptional(foundFeatureSet, f) || f.Feature.enabled);
// Enable the feature set again and make sure the optional features are still enabled
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, enabled: true);
AssertAllFeatures(BuildTargetGroup.Standalone, (f) => !FeatureIsOptional(foundFeatureSet, f) || f.Feature.enabled);
}
[Test]
public void DisablingFeatureSetLeavesDefaultFeaturesEnabled()
{
var foundFeatureSet = GetFeatureSetInfoWithId(BuildTargetGroup.Standalone, k_TestFeatureSetIdFour);
Assert.IsNotNull(foundFeatureSet);
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, true);
// Ensure that the only enabled features are the non optional features
AssertAllFeatures(BuildTargetGroup.Standalone, foundFeatureSet.featureIds, (f) => f.Feature.enabled == !FeatureIsOptional(foundFeatureSet, f));
// Disabling the feature set should disable the required components but not the default ones
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, enabled: false);
AssertAllFeatures(BuildTargetGroup.Standalone, foundFeatureSet.requiredFeatureIds, FeatureDisabled);
AssertAllFeatures(BuildTargetGroup.Standalone, foundFeatureSet.defaultFeatureIds, FeatureEnabled);
}
[Test]
public void DisablingFeatureSetLeavesDisabledDefaultFeaturesDisabled()
{
var buildTargetGroup = BuildTargetGroup.Standalone;
var foundFeatureSet = GetFeatureSetInfoWithId(buildTargetGroup, k_TestFeatureSetIdFour);
Assert.IsNotNull(foundFeatureSet);
EnableFeatureSet(buildTargetGroup, foundFeatureSet.featureSetId, enabled: true);
// Ensure that only the non optional features are enabled
AssertOnlyFeatures(buildTargetGroup, foundFeatureSet.featureIds, (f) => f.Feature.enabled == !FeatureIsOptional(foundFeatureSet, f));
// Disable all features in the default feature list
EnableFeatureInfos(buildTargetGroup, foundFeatureSet.defaultFeatureIds, enable: false);
AssertAllFeatures(buildTargetGroup, foundFeatureSet.defaultFeatureIds, FeatureDisabled);
// Ensure that all features in the required list are enabled and that all features in the default features list are disabled
AssertAllFeatures(buildTargetGroup, foundFeatureSet.requiredFeatureIds, FeatureEnabled);
}
[Test]
public void CanNotChangeEnabledStateOfRequiredFeature()
{
OpenXRFeatureSetManager.activeBuildTarget = BuildTargetGroup.Standalone;
var foundFeatureSet = GetFeatureSetInfoWithId(BuildTargetGroup.Standalone, k_TestFeatureSetIdFour);
Assert.IsNotNull(foundFeatureSet);
var featureInfos = GetFeatureInfos(BuildTargetGroup.Standalone, foundFeatureSet.requiredFeatureIds);
foreach (var featureInfo in featureInfos)
{
AssertFeatureEnabled(featureInfo, false);
featureInfo.Feature.enabled = true;
AssertFeatureEnabled(featureInfo, true);
featureInfo.Feature.enabled = false;
AssertFeatureEnabled(featureInfo, false);
}
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, enabled: true);
OpenXRFeature.canSetFeatureDisabled = OpenXRFeatureSetManager.CanFeatureBeDisabled;
foreach (var featureInfo in featureInfos)
{
AssertFeatureEnabled(featureInfo, true);
featureInfo.Feature.enabled = false;
AssertFeatureEnabled(featureInfo, true);
}
EnableFeatureSet(BuildTargetGroup.Standalone, foundFeatureSet.featureSetId, enabled: false);
foreach (var featureInfo in featureInfos)
{
AssertFeatureEnabled(featureInfo, false);
featureInfo.Feature.enabled = true;
AssertFeatureEnabled(featureInfo, true);
featureInfo.Feature.enabled = false;
AssertFeatureEnabled(featureInfo, false);
}
OpenXRFeatureSetManager.activeBuildTarget = BuildTargetGroup.Unknown;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 328562bda27130c4a853afe94025c8a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,122 @@
using System;
using System.Linq;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor.Build.Reporting;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Features.Mock;
using Assert = UnityEngine.Assertions.Assert;
using UnityEngine.XR.OpenXR.Tests;
using static UnityEditor.XR.OpenXR.Tests.OpenXREditorTestHelpers;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class FeatureTests : OpenXRLoaderSetup
{
[Test]
public void EnableFeatures()
{
var featureInfos = GetFeatureInfos(BuildTargetGroup.Standalone);
featureInfos.SingleOrDefault(ext => ext.Attribute.UiName == "Mock Runtime").Feature.enabled = true;
Assert.IsTrue(MockRuntime.Instance.enabled);
featureInfos.SingleOrDefault(ext => ext.Attribute.UiName == "Mock Runtime").Feature.enabled = false;
Assert.IsFalse(MockRuntime.Instance.enabled);
}
[Test]
public void CheckDefaultValues()
{
var featureInfos = GetFeatureInfos(BuildTargetGroup.Standalone);
var mockExtInfo = featureInfos.SingleOrDefault(ext => ext.Attribute.UiName == "Mock Runtime");
Assert.AreEqual(mockExtInfo.Attribute.UiName, mockExtInfo.Feature.nameUi);
Assert.AreEqual(mockExtInfo.Attribute.Version, mockExtInfo.Feature.version);
Assert.AreEqual(mockExtInfo.Attribute.OpenxrExtensionStrings, mockExtInfo.Feature.openxrExtensionStrings);
}
[Test]
public void ValidationError()
{
bool errorFixed = false;
// Set up a validation check ...
MockRuntime.Instance.TestCallback = (s, o) =>
{
if (s == "GetValidationChecks")
{
var validationChecks = o as List<OpenXRFeature.ValidationRule>;
validationChecks?.Add(new OpenXRFeature.ValidationRule
{
message = "Mock Validation Fail",
checkPredicate = () => errorFixed,
fixIt = () => errorFixed = true,
error = true
});
}
return true;
};
// Try to build the player ...
var report = zBuildHookTests.BuildMockPlayer();
// It will fail because of the above validation issue ...
Assert.AreEqual(BuildResult.Failed, report.summary.result);
// There's one validation issue ...
var validationIssues = new List<OpenXRFeature.ValidationRule>();
OpenXRProjectValidation.GetCurrentValidationIssues(validationIssues, BuildTargetGroup.Standalone);
Assert.AreEqual(1, validationIssues.Count);
// Fix it ...
Assert.IsFalse(errorFixed);
validationIssues[0].fixIt.Invoke();
Assert.IsTrue(errorFixed);
// Now there's zero validation issues ...
OpenXRProjectValidation.GetCurrentValidationIssues(validationIssues, BuildTargetGroup.Standalone);
Assert.AreEqual(0, validationIssues.Count);
// Close the validation window ...
OpenXRProjectValidationRulesSetup.CloseWindow();
}
[Test]
public void GetFeatureByFeatureId()
{
var feature = FeatureHelpers.GetFeatureWithIdForActiveBuildTarget(MockRuntime.featureId);
Assert.IsNotNull(feature);
}
[Test]
public void GetFeatureByUnknownFeatureIdReturnsNull()
{
var feature = FeatureHelpers.GetFeatureWithIdForActiveBuildTarget("some.unknown.feature.id");
Assert.IsNull(feature);
feature = FeatureHelpers.GetFeatureWithIdForActiveBuildTarget("");
Assert.IsNull(feature);
feature = FeatureHelpers.GetFeatureWithIdForActiveBuildTarget(null);
Assert.IsNull(feature);
}
[Test]
public void GetFeaturesWithIdsReturnsFeatures()
{
var featureIds = new string[] { MockRuntime.featureId, EyeGazeInteraction.featureId };
var features = FeatureHelpers.GetFeaturesWithIdsForActiveBuildTarget(featureIds);
Assert.IsNotNull(features);
Assert.IsTrue(features.Length == 2);
var expectedTypes = new Type[] { typeof(MockRuntime), typeof(EyeGazeInteraction) };
foreach (var feature in features)
{
Assert.IsTrue(Array.IndexOf(expectedTypes, feature.GetType()) > -1);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a788d71a20869d4db1291a18e647426
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2bcf50ca7a335ea40b37e3bcf05eea52
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
using NUnit.Framework;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.XR.OpenXR.Tests;
using Assert = UnityEngine.Assertions.Assert;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class OpenXRCallbackTests : OpenXRLoaderSetup
{
[Test]
public void InstanceCreated()
{
bool instanceCreated = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
{
instanceCreated = true;
Assert.AreEqual(10, (ulong)param);
}
return true;
};
AddExtension(MockRuntime.XR_UNITY_mock_test);
base.InitializeAndStart();
Assert.IsTrue(instanceCreated);
}
[Test]
public void SessionCreated()
{
bool sessionCreated = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnSessionCreate))
{
sessionCreated = true;
Assert.AreEqual(3, (ulong)param);
}
return true;
};
base.InitializeAndStart();
Assert.IsTrue(sessionCreated);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8dc9dc15a0724a46ba08d4aecabca976
timeCreated: 1586986782

View File

@@ -0,0 +1,62 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Tests;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.TestTools;
using UnityEngine.Scripting;
using Assert = UnityEngine.Assertions.Assert;
namespace UnityEditor.XR.OpenXR.Tests
{
#if false // Tests are temporarily disabled due to an issue with [BeforeTest] being called twice during a domain reload
/// <summary>
/// Tests domain reloads within OpenXR. Note that his class was named with two z's in front
/// because the domain reloads during this test were causing other tests to fail that ran after it.
/// </summary>
internal class zzOpenXRDomainReloadTests : OpenXRLoaderSetup
{
[UnityTest]
public IEnumerator AfterInitialize()
{
MockRuntimeFeature.Instance.TestCallback = (methodName, param) => true;
Assert.IsNull(OpenXRLoaderBase.Instance);
base.InitializeAndStart();
// Perform a domain reload and wait for it to complete
EditorUtility.RequestScriptReload();
yield return new WaitForDomainReload();
Assert.IsNotNull(OpenXRLoaderBase.Instance);
base.StopAndShutdown();
Assert.IsNull(OpenXRLoaderBase.Instance);
yield return null;
}
[UnityTest]
public IEnumerator BeforeInitialize()
{
MockRuntimeFeature.Instance.TestCallback = (methodName, param) => true;
// Perform a domain reload and wait for it to complete
EditorUtility.RequestScriptReload();
yield return new WaitForDomainReload();
base.InitializeAndStart();
base.StopAndShutdown();
yield return null;
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 742a28a82ea69ef4fba68040fee09aac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,230 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.XR.OpenXR.Features;
using static UnityEditor.XR.OpenXR.Features.OpenXRFeatureSetManager;
namespace UnityEditor.XR.OpenXR.Tests
{
internal static class OpenXREditorTestHelpers
{
public delegate bool FeatureInfoPredicate(FeatureHelpersInternal.FeatureInfo featureInfo);
public static bool FeatureEnabled(FeatureHelpersInternal.FeatureInfo featureInfo) => featureInfo.Feature.enabled;
public static bool FeatureDisabled(FeatureHelpersInternal.FeatureInfo featureInfo) => !featureInfo.Feature.enabled;
private static Dictionary<BuildTargetGroup, FeatureHelpersInternal.FeatureInfo[]> s_FeatureInfos;
private static BuildTargetGroup[] s_BuildTargetGroups =
((BuildTargetGroup[])Enum.GetValues(typeof(BuildTargetGroup))).Distinct().ToArray();
/// <summary>
/// Return the distinct list of build target groups to test
/// </summary>
public static BuildTargetGroup[] GetBuildTargetGroups() => s_BuildTargetGroups;
/// <summary>
/// Clear the FeatureInfo cache
/// </summary>
public static void ClearFeatureInfos()
{
s_FeatureInfos = null;
}
/// <summary>
/// Helper function to retrieve the cached FeatureInfos for a given build target group
/// </summary>
/// <param name="buildTargetGroup">Build target group</param>
/// <returns>Array of FeatureInfos for the build target group</returns>
public static FeatureHelpersInternal.FeatureInfo[] GetFeatureInfos(BuildTargetGroup buildTargetGroup)
{
if (null == s_FeatureInfos)
s_FeatureInfos = new Dictionary<BuildTargetGroup, FeatureHelpersInternal.FeatureInfo[]>();
if (!s_FeatureInfos.TryGetValue(buildTargetGroup, out var featureInfos))
{
featureInfos = FeatureHelpersInternal.GetAllFeatureInfo(buildTargetGroup).Features.ToArray();
s_FeatureInfos[buildTargetGroup] = featureInfos;
}
return featureInfos;
}
/// <summary>
/// Helper function to retrieve a subset of FeatureInfos in <paramref name="buildTargetGroup"/>.
/// </summary>
/// <param name="buildTargetGroup">Build target group</param>
/// <param name="featureIds">Specific feature identifiers to retrieve</param>
/// <returns>Array of FeatureInfos matching request</returns>
public static FeatureHelpersInternal.FeatureInfo[] GetFeatureInfos(BuildTargetGroup buildTargetGroup, string[] featureIds)
{
return GetFeatureInfos(buildTargetGroup).Where(f => featureIds.Contains(f.Attribute.FeatureId)).ToArray();
}
/// <summary>
/// Enable or Disable a feature set
/// </summary>
/// <param name="buildTargetGroup">Build target group containing feature set</param>
/// <param name="featureSetId">Identifier of feature</param>
/// <param name="enabled">True to enable, false to disable</param>
/// <param name="changed">True if the FeatureSet enabled state should be considered changed</param>
/// <param name="commit">True if the FeatureSet enabled state should be automatically commmitted with `SetFeaturesFromEnabledFeatureSets`</param>
public static void EnableFeatureSet(BuildTargetGroup buildTargetGroup, string featureSetId, bool enabled = true, bool changed = true, bool commit = true)
{
var featureSetInfo = GetFeatureSetInfoWithId(buildTargetGroup, featureSetId);
Assert.IsNotNull(featureSetInfo, $"FeatureSetInfo '{featureSetId}' not found ");
featureSetInfo.isEnabled = enabled;
featureSetInfo.wasEnabled = changed ? !enabled : enabled;
if (commit)
SetFeaturesFromEnabledFeatureSets(buildTargetGroup);
}
/// <summary>
/// Enable/Disable all feature sets for <paramref name="buildTargetGroup"/>
/// </summary>
/// <param name="buildTargetGroup">Build Target Group</param>
/// <param name="enabled">Enabled state</param>
/// <param name="changed">True if the feature set should be marked as changed, false if not</param>
public static void EnableFeatureSets(BuildTargetGroup buildTargetGroup, bool enabled, bool changed = true, bool commit = true)
{
foreach (var featureSetInfo in FeatureSetInfosForBuildTarget(buildTargetGroup))
{
featureSetInfo.isEnabled = enabled;
featureSetInfo.wasEnabled = changed ? !enabled : enabled;
}
if (commit)
SetFeaturesFromEnabledFeatureSets(buildTargetGroup);
}
public static void EnableFeatureInfos(BuildTargetGroup buildTargetGroup, string[] featureIds, bool enable, FeatureInfoPredicate predicate = null)
{
foreach (var featureInfo in GetFeatureInfos(buildTargetGroup, featureIds))
{
if (null == predicate || predicate(featureInfo))
featureInfo.Feature.enabled = enable;
}
}
public static void EnableFeatureInfos(BuildTargetGroup buildTargetGroup, bool enable, FeatureInfoPredicate predicate = null)
{
foreach (var featureInfo in GetFeatureInfos(buildTargetGroup))
{
if (null == predicate || predicate(featureInfo))
featureInfo.Feature.enabled = enable;
}
}
/// <summary>
/// Builds a comma separated list of features that pass the <paramref name="check"/> within <paramref name="featureInfos"/>
/// </summary>
/// <param name="featureInfos">Feature infos</param>
/// <param name="check">Delegate used to check status</param>
/// <param name="exclude">Optional list of feature identifiers exclude</param>
/// <returns>Comma separated list of features</returns>
public static string FeaturesToString(FeatureHelpersInternal.FeatureInfo[] featureInfos, FeatureInfoPredicate check, string[] exclude = null)
{
return string.Join(",", featureInfos
.Where(f => (exclude == null || !exclude.Contains(f.Attribute.FeatureId)) && check(f))
.Select(f => f.Attribute.FeatureId));
}
/// <summary>
/// Builds a comma separated list of features from <paramref name="featureIds"/> in <paramref name="buildTargetGroup"/> that pass the <paramref name="check"/>
/// </summary>
/// <param name="buildTargetGroup">Build Target Group</param>
/// <param name="featureIds">Feature ids to include</param>
/// <param name="check">Delegate used to check status</param>
/// <returns>Comma separated list of features</returns>
public static string FeaturesToString(BuildTargetGroup buildTargetGroup, string[] featureIds, FeatureInfoPredicate check)
{
return FeaturesToString(GetFeatureInfos(buildTargetGroup).Where(f => featureIds.Contains(f.Attribute.FeatureId)).ToArray(), check);
}
/// <summary>
/// Builds a comma separated list of features identifier that pass the <paramref name="check"/> within <paramref name="buildTargetGroup"/>
/// </summary>
/// <param name="buildTargetGroup">Build Target Group</param>
/// <param name="check">Delegate used to check status</param>
/// <param name="exclude">Optional list of feature identifiers to exclude from the list</param>
/// <returns>Comma separated list of features</returns>
public static string FeaturesToString(BuildTargetGroup buildTargetGroup, FeatureInfoPredicate check, string[] exclude = null)
{
return FeaturesToString(GetFeatureInfos(buildTargetGroup), check, exclude: exclude);
}
/// <summary>
/// Checks if all features for the <paramref name="buildTargetGroup"/> pass the <paramref name="check"/>.
/// </summary>
/// <param name="buildTargetGroup">Build Target Group</param>
/// <param name="check">Delegate used to check status</param>
/// <returns>True if the check passes, false if not</returns>
public static bool CheckAllFeatures(BuildTargetGroup buildTargetGroup, FeatureInfoPredicate check)
{
return GetFeatureInfos(buildTargetGroup).All(f => check(f));
}
/// <summary>
/// Check that all features in <paramref name="featureIds"/> in <paramref name="buildTargetGroup"/> match the pass the <paramref name="check"/>.
/// </summary>
/// <param name="buildTargetGroup">Build target group</param>
/// <param name="featureIds">Features to check</param>
/// <param name="check">Delegate used to check status</param>
/// <returns>True if the check passes, false if not</returns>
public static bool CheckAllFeatures(BuildTargetGroup buildTargetGroup, string[] featureIds, FeatureInfoPredicate check)
{
return GetFeatureInfos(buildTargetGroup).All(f => !featureIds.Contains(f.Attribute.FeatureId) || check(f));
}
/// <summary>
/// Check that only features in <paramref name="featureIds"/> for <paramref name="buildTargetGroup"/> pass the <paramref name="check"/>.
/// </summary>
/// <param name="buildTargetGroup">Build target group</param>
/// <param name="featureIds">Features to check</param>
/// <param name="check">Delegate used to check a FeatureInfo</param>
/// <returns>True if the check passes, false if not</returns>
public static bool CheckOnlyFeatures(BuildTargetGroup buildTargetGroup, string[] featureIds, FeatureInfoPredicate check)
{
return GetFeatureInfos(buildTargetGroup).All(f => featureIds.Contains(f.Attribute.FeatureId) == check(f));
}
private static string AssertFeaturesMessage(string features) =>
$"The following features failed the check: {features}";
public static void AssertOnlyFeatures(BuildTargetGroup buildTargetGroup, string[] featureIds, FeatureInfoPredicate check)
{
Assert.IsTrue(
CheckOnlyFeatures(buildTargetGroup, featureIds, check),
AssertFeaturesMessage(FeaturesToString(buildTargetGroup, featureIds, (f) => !check(f))));
}
public static void AssertAllFeatures(BuildTargetGroup buildTargetGroup, string[] featureIds, FeatureInfoPredicate check)
{
Assert.IsTrue(
CheckAllFeatures(buildTargetGroup, featureIds, check),
AssertFeaturesMessage(FeaturesToString(buildTargetGroup, featureIds, (f) => !check(f))));
}
public static void AssertAllFeatures(BuildTargetGroup buildTargetGroup, FeatureInfoPredicate check)
{
Assert.IsTrue(
CheckAllFeatures(buildTargetGroup, check),
AssertFeaturesMessage(FeaturesToString(buildTargetGroup, (f) => !check(f))));
}
public static void AssertFeatureEnabled(FeatureHelpersInternal.FeatureInfo featureInfo, bool enabled = true)
{
Assert.IsTrue(featureInfo.Feature.enabled == enabled, $"{featureInfo.Attribute.FeatureId} should be {(enabled ? "enabled" : "disabled")}");
}
public static bool FeatureIsOptional(FeatureSetInfo featureSet, FeatureHelpersInternal.FeatureInfo feature)
{
return Array.IndexOf(featureSet.featureIds, feature.Attribute.FeatureId) > -1 &&
(featureSet.requiredFeatureIds == null || Array.IndexOf(featureSet.requiredFeatureIds, feature.Attribute.FeatureId) == -1) &&
(featureSet.defaultFeatureIds == null || Array.IndexOf(featureSet.defaultFeatureIds, feature.Attribute.FeatureId) == -1);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f87eee2b71c3bf24aa0e63794ac22ae5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using System;
using NUnit.Framework;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class OpenXREditorTests
{
[Test]
public void DocumentationVersion()
{
var version = PackageManager.PackageInfo.FindForAssembly(typeof(OpenXREditorTests).Assembly)?.version;
var majorminor = "@" + OpenXRFeatureAttribute.k_PackageVersionRegex.Match(version).Groups[1].Value + "/";
UnityEngine.Debug.Log(typeof(KHRSimpleControllerProfile).GetCustomAttribute<OpenXRFeatureAttribute>().InternalDocumentationLink);
Assert.IsTrue(typeof(KHRSimpleControllerProfile).GetCustomAttribute<OpenXRFeatureAttribute>().InternalDocumentationLink.Contains(majorminor));
}
[Test]
public void PluginVersion()
{
var version = PackageManager.PackageInfo.FindForAssembly(typeof(OpenXREditorTests).Assembly)?.version;
System.Text.RegularExpressions.Regex ReleaseVersion = new System.Text.RegularExpressions.Regex(@"^(\d+\.\d+\.\d+)$");
//check for release build provider version number matches package version
if (ReleaseVersion.IsMatch(version))
{
var tag = OpenXRRuntime.pluginVersion;
Assert.AreEqual(0, String.Compare(version, tag), "Tag in github must match the package version number.");
return;
}
//check for non-release build package version number supposed to be x.x.x-pre.x(pre-release) or x.x.x-exp.x(experimental)
System.Text.RegularExpressions.Regex PreviewVersion = new System.Text.RegularExpressions.Regex(@"^(\d+\.\d+\.\d+\-\w+\.\d+)$");
Assert.IsTrue(PreviewVersion.IsMatch(version), "Wrong package version format! Non-release branch should follow x.x.x-pre.x(pre-release) or x.x.x-exp.x(experimental)");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c99482b3559f1f47babd8d8fec8a820
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections;
using System.Linq;
using NUnit.Framework;
using UnityEngine.InputSystem;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Tests;
using Assert = UnityEngine.Assertions.Assert;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class OpenXRInputEditorTests : OpenXRInputTestsBase
{
/// <summary>
/// Tests whether or not the device layout for an interaction feature is registered/unregistered
/// when the feature is enabled/disabled
/// </summary>
#if !INPUT_SYSTEM_BINDING_VALIDATOR
[Test]
public void DeviceLayoutRegistration([ValueSource(nameof(s_InteractionFeatureLayouts))] (Type featureType, Type layoutType, string layoutNameOverride) interactionFeature)
{
var layoutName = interactionFeature.layoutNameOverride ?? interactionFeature.layoutType.Name;
// Make sure the layout is not registered as it would give the test a false positive
InputSystem.RemoveLayout(layoutName);
Assert.IsFalse(IsLayoutRegistered(layoutName), "Layout is still registered, test will give a false positive");
// Enabling the feature should register the layout
EnableFeature(interactionFeature.featureType);
Assert.IsTrue(IsLayoutRegistered(layoutName), "Layout was not registered by enabling the feature");
// When an interaction feature is disabled its layout should be disable as well
EnableFeature(interactionFeature.featureType, false);
Assert.IsFalse(IsLayoutRegistered(layoutName), "Layout was not unregistered by the interaction feature");
}
/// <summary>
/// Tests that interaction features enabled in multiple build targets properly registers and unregisters
/// the device layout depending on whether the feature is enabled in at least one build target.
/// </summary>
[Test]
public void InteractionFeatureLayoutRegistration()
{
var packageSettings = OpenXRSettings.GetPackageSettings();
Assert.IsNotNull(packageSettings);
// Ignore the test if there is not more than 1 build target.
var features = packageSettings.GetFeatures<OculusTouchControllerProfile>().Select(f => f.feature).ToArray();
if (features.Length < 2)
return;
// Disable all of the oculus interaction features
foreach (var feature in features)
{
feature.enabled = false;
}
// Make sure the oculus device layout is not registered
NUnit.Framework.Assert.Throws(typeof(ArgumentException), () => UnityEngine.InputSystem.InputSystem.LoadLayout<OculusTouchControllerProfile.OculusTouchController>());
// Enable one of the features and make sure the layout is registered
features[0].enabled = true;
NUnit.Framework.Assert.DoesNotThrow(() => UnityEngine.InputSystem.InputSystem.LoadLayout<OculusTouchControllerProfile.OculusTouchController>());
NUnit.Framework.Assert.DoesNotThrow(() => UnityEngine.InputSystem.InputSystem.LoadLayout<OculusTouchControllerProfile.OculusTouchController>());
// Enable a second feature and make sure the layout is still enabled
features[1].enabled = true;
NUnit.Framework.Assert.DoesNotThrow(() => UnityEngine.InputSystem.InputSystem.LoadLayout<OculusTouchControllerProfile.OculusTouchController>());
// Disable the first feature and make sure the layout is still enabled
features[0].enabled = false;
NUnit.Framework.Assert.DoesNotThrow(() => UnityEngine.InputSystem.InputSystem.LoadLayout<OculusTouchControllerProfile.OculusTouchController>());
// Disable the second feature and make sure the layout is no longer
features[1].enabled = false;
NUnit.Framework.Assert.Throws(typeof(ArgumentException), () => UnityEngine.InputSystem.InputSystem.LoadLayout<OculusTouchControllerProfile.OculusTouchController>());
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ee1a4eb1841c4a40a7e4a76be579ca3e
timeCreated: 1622132012

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Tests;
using Assert = UnityEngine.Assertions.Assert;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class OpenXRRuntimeSelectorTests : OpenXRInputTestsBase
{
[Test]
public void NoAvailableRuntimesTest()
{
List<OpenXRRuntimeSelector.RuntimeDetector> detectorList = OpenXRRuntimeSelector.GenerateRuntimeDetectorList();
Assert.IsTrue(detectorList.Count > 0);
Assert.IsTrue(detectorList[0] is OpenXRRuntimeSelector.SystemDefault, "First choice should always be SystemDefault");
Assert.IsTrue(detectorList[detectorList.Count - 1] is OpenXRRuntimeSelector.OtherRuntime, "Last choice should always be Other");
}
[Test]
public void BuiltInRuntimesAsAvailableRuntimesTest()
{
// Simulate what happens if the AvailableRuntimes registry key is empty.
// WindowsMR, SteamVR, and Oculus should all be added to the list.
Dictionary<string, int> runtimePathToValue = new Dictionary<string, int>();
List<OpenXRRuntimeSelector.RuntimeDetector> detectorList = OpenXRRuntimeSelector.GenerateRuntimeDetectorList(runtimePathToValue);
Assert.IsTrue(detectorList.Count > 0);
Assert.IsTrue(detectorList[0] is OpenXRRuntimeSelector.SystemDefault);
Assert.IsTrue(detectorList[detectorList.Count - 1] is OpenXRRuntimeSelector.OtherRuntime);
bool foundWindowsMRDetector = false;
bool foundSteamVRDetector = false;
bool foundOculusDetector = false;
foreach (var detector in detectorList)
{
foundWindowsMRDetector |= detector is OpenXRRuntimeSelector.WindowsMRDetector;
foundSteamVRDetector |= detector is OpenXRRuntimeSelector.SteamVRDetector;
foundOculusDetector |= detector is OpenXRRuntimeSelector.OculusDetector;
}
Assert.IsTrue(foundWindowsMRDetector);
Assert.IsTrue(foundSteamVRDetector);
Assert.IsTrue(foundOculusDetector);
}
[Test]
public void DiscoveredAvailableRuntimesTest()
{
OpenXRRuntimeSelector.WindowsMRDetector windowsMRDetector = new OpenXRRuntimeSelector.WindowsMRDetector();
OpenXRRuntimeSelector.SteamVRDetector steamVRDetector = new OpenXRRuntimeSelector.SteamVRDetector();
OpenXRRuntimeSelector.OculusDetector oculusDetector = new OpenXRRuntimeSelector.OculusDetector();
string enabledRuntime = "enabledRuntime";
string disabledRuntime = "disabledRuntime";
Dictionary<string, int> runtimePathToValue = new Dictionary<string, int>()
{
{ windowsMRDetector.jsonPath, 0},
{ steamVRDetector.jsonPath, 1},
{ enabledRuntime, 0 },
{ disabledRuntime, 1 }
};
List<OpenXRRuntimeSelector.RuntimeDetector> detectorList = OpenXRRuntimeSelector.GenerateRuntimeDetectorList(runtimePathToValue);
Assert.IsTrue(detectorList.Count > 0);
Assert.IsTrue(detectorList[0] is OpenXRRuntimeSelector.SystemDefault);
Assert.IsTrue(detectorList[detectorList.Count - 1] is OpenXRRuntimeSelector.OtherRuntime);
HashSet<string> detectedJsons = new HashSet<string>();
foreach (var detector in detectorList)
{
detectedJsons.Add(detector.jsonPath);
}
Assert.IsTrue(detectedJsons.Contains(windowsMRDetector.jsonPath));
Assert.IsFalse(detectedJsons.Contains(steamVRDetector.jsonPath));
Assert.IsTrue(detectedJsons.Contains(oculusDetector.jsonPath));
Assert.IsTrue(detectedJsons.Contains(enabledRuntime));
Assert.IsFalse(detectedJsons.Contains(disabledRuntime));
}
[Test]
public void TestSelectOtherRuntime()
{
// Get the OtherRuntime choice (verify that it's the last detector)
var runtimeDetector = OpenXRRuntimeSelector.RuntimeDetectors[OpenXRRuntimeSelector.RuntimeDetectors.Count - 1];
OpenXRRuntimeSelector.OtherRuntime otherRuntime = runtimeDetector as OpenXRRuntimeSelector.OtherRuntime;
Assert.IsTrue(otherRuntime != null);
// Save data that this test will modify to restore state after the test finishes.
string previousOtherJsonPath = otherRuntime.jsonPath;
string previousActiveRuntime = Environment.GetEnvironmentVariable(OpenXRRuntimeSelector.RuntimeDetector.k_RuntimeEnvKey);
string previousSelectedRuntime = Environment.GetEnvironmentVariable(OpenXRRuntimeSelector.k_SelectedRuntimeEnvKey);
try
{
// Set the other runtime value and set this as the selected runtime.
string jsonPath = "testJsonPath";
otherRuntime.SetOtherRuntimeJsonPath(jsonPath);
OpenXRRuntimeSelector.SetSelectedRuntime(jsonPath);
// Exit Edit Mode. Active runtime and Selected runtime should have the json path.
OpenXRRuntimeSelector.ActivateOrDeactivateRuntime(PlayModeStateChange.ExitingEditMode);
string activeRuntime = Environment.GetEnvironmentVariable(OpenXRRuntimeSelector.RuntimeDetector.k_RuntimeEnvKey);
string selectedRuntime = Environment.GetEnvironmentVariable(OpenXRRuntimeSelector.k_SelectedRuntimeEnvKey);
Assert.AreEqual(activeRuntime, jsonPath);
Assert.AreEqual(selectedRuntime, jsonPath);
// Simulate a refresh of the runtime detectors when entering play mode.
// The OtherRuntime should still have the jsonPath from before.
OpenXRRuntimeSelector.RefreshRuntimeDetectorList();
runtimeDetector = OpenXRRuntimeSelector.RuntimeDetectors[OpenXRRuntimeSelector.RuntimeDetectors.Count - 1];
otherRuntime = runtimeDetector as OpenXRRuntimeSelector.OtherRuntime;
Assert.AreEqual(otherRuntime.jsonPath, jsonPath);
// Enter Edit Mode. Active runtime should be cleared, selected runtime should still have the json path.
OpenXRRuntimeSelector.ActivateOrDeactivateRuntime(PlayModeStateChange.EnteredEditMode);
activeRuntime = Environment.GetEnvironmentVariable(OpenXRRuntimeSelector.RuntimeDetector.k_RuntimeEnvKey);
selectedRuntime = Environment.GetEnvironmentVariable(OpenXRRuntimeSelector.k_SelectedRuntimeEnvKey);
Assert.AreNotEqual(activeRuntime, jsonPath);
Assert.AreEqual(selectedRuntime, jsonPath);
// Refresh again. OtherRuntime and selected runtime should still have the json path.
OpenXRRuntimeSelector.RefreshRuntimeDetectorList();
runtimeDetector = OpenXRRuntimeSelector.RuntimeDetectors[OpenXRRuntimeSelector.RuntimeDetectors.Count - 1];
otherRuntime = runtimeDetector as OpenXRRuntimeSelector.OtherRuntime;
Assert.AreEqual(otherRuntime.jsonPath, jsonPath);
}
finally
{
otherRuntime.SetOtherRuntimeJsonPath(previousOtherJsonPath);
Environment.SetEnvironmentVariable(OpenXRRuntimeSelector.RuntimeDetector.k_RuntimeEnvKey, previousActiveRuntime);
Environment.SetEnvironmentVariable(OpenXRRuntimeSelector.k_SelectedRuntimeEnvKey, previousSelectedRuntime);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14582cceaceaa6949852ce064e85a84a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using System;
using NUnit.Framework;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR;
using Unity.XR.CoreUtils.Editor;
namespace UnityEditor.XR.OpenXR.Tests
{
internal class OpenXRValidationTests
{
internal class FakeFeature : OpenXRFeature
{
}
/// <summary>
/// Test that IsRuleEnabled will be true at the correct time for a BuildValidationRule.
/// </summary>
[Test]
public void IsRuleEnabledTest()
{
// Create a validation rule that is enabled when FakeFeature is active
OpenXRFeature.ValidationRule testRule = new OpenXRFeature.ValidationRule(ScriptableObject.CreateInstance<FakeFeature>())
{
message = "Fake feature message.",
checkPredicate = () => true,
fixIt = () => { },
error = false,
errorEnteringPlaymode = false
};
// Create the build validation rule for Standalone (arbitrarily picked)
BuildValidationRule buildValidationRule = OpenXRProjectValidationRulesSetup.ConvertRuleToBuildValidationRule(testRule, BuildTargetGroup.Standalone);
// Since the feature isn't in the active Standalone settings, the rule should not be enabled.
Assert.IsFalse(buildValidationRule.IsRuleEnabled());
// Temporarily add an enabled FakeFeature to the Standalone settings, and then restore the settings when the test is done.
// The build validation rule should be enabled when we add the feature to the Standalone settings.
OpenXRSettings standaloneSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Standalone);
OpenXRFeature firstStandaloneSetting = standaloneSettings.features[0];
try
{
FakeFeature fakeFeature = ScriptableObject.CreateInstance<FakeFeature>();
fakeFeature.enabled = true;
standaloneSettings.features[0] = fakeFeature;
Assert.IsTrue(buildValidationRule.IsRuleEnabled());
}
finally
{
standaloneSettings.features[0] = firstStandaloneSetting;
}
// Create another build validation rule for something else other than Standalone.
// The build validation rule should not be enabled when we add the feature to the Standalone group.
buildValidationRule = OpenXRProjectValidationRulesSetup.ConvertRuleToBuildValidationRule(testRule, BuildTargetGroup.WSA);
standaloneSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Standalone);
firstStandaloneSetting = standaloneSettings.features[0];
try
{
FakeFeature fakeFeature = ScriptableObject.CreateInstance<FakeFeature>();
fakeFeature.enabled = true;
standaloneSettings.features[0] = fakeFeature;
Assert.IsFalse(buildValidationRule.IsRuleEnabled());
}
finally
{
standaloneSettings.features[0] = firstStandaloneSetting;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8329db5b1413bf24f849af3e63602cd9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
{
"name": "Unity.XR.OpenXR.Tests.Editor",
"rootNamespace": "",
"references": [
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:0acc523941302664db1f4e527237feb3",
"GUID:4847341ff46394e83bb78fbd0652937e",
"GUID:96aa6ba065960476598f8f643e7252b6",
"GUID:e40ba710768534012815d3193fa296cb",
"GUID:2023fa18bf9504e98b11fe2174802802",
"GUID:6150739e4dc7bff4d833306fd9d5a4f0",
"GUID:75469ad4d38634e559750d17036d5f7c",
"GUID:4ddd23ea56a3a40f0aa0036d1624a53e"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [
{
"name": "com.unity.inputsystem",
"expression": "1.6.3",
"define": "INPUT_SYSTEM_BINDING_VALIDATOR"
}
],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 666653aa92bac494b9db60410f5b51f0
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,158 @@
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.XR.OpenXR.Tests;
using Assert = UnityEngine.Assertions.Assert;
[assembly: UnityPlatform(RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor)]
namespace UnityEditor.XR.OpenXR.Tests
{
internal class XRLoaderLifecycleTests : OpenXRLoaderSetup
{
[Test]
public void FullLifecycleOrder()
{
bool subsystemCreate = false;
bool subsystemStart = false;
bool hookInstanceProc = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
switch (methodName)
{
case nameof(OpenXRFeature.HookGetInstanceProcAddr):
Assert.IsFalse(subsystemCreate);
Assert.IsFalse(subsystemStart);
hookInstanceProc = true;
break;
case nameof(OpenXRFeature.OnSubsystemCreate):
Assert.IsTrue(hookInstanceProc);
Assert.IsFalse(subsystemStart);
subsystemCreate = true;
break;
case nameof(OpenXRFeature.OnSubsystemStart):
Assert.IsTrue(hookInstanceProc);
Assert.IsTrue(subsystemCreate);
subsystemStart = true;
break;
}
return true;
};
base.InitializeAndStart();
ProcessOpenXRMessageLoop();
Assert.IsTrue(hookInstanceProc);
Assert.IsTrue(subsystemCreate);
Assert.IsTrue(subsystemStart);
bool subsystemStop = false;
bool subsystemDestroy = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
switch (methodName)
{
case nameof(OpenXRFeature.OnSubsystemStop):
Assert.IsFalse(subsystemDestroy);
subsystemStop = true;
break;
case nameof(OpenXRFeature.OnSubsystemDestroy):
Assert.IsTrue(subsystemStop);
subsystemDestroy = true;
break;
}
return true;
};
base.StopAndShutdown();
Assert.IsTrue(subsystemStop);
Assert.IsTrue(subsystemDestroy);
}
[Test]
public void InstanceCreate() => TestInstanceCreate(true, false);
[Test]
public void InstanceCreateFail() => TestInstanceCreate(false, false);
[Test]
public void InstanceCreateRequired() => TestInstanceCreate(true, true);
[Test]
public void InstanceCreateFailRequired() => TestInstanceCreate(false, true);
public void TestInstanceCreate(bool result, bool required)
{
Loader.DisableValidationChecksOnEnteringPlaymode = true;
bool instanceCreated = false;
bool hookInstanceProcAddr = false;
bool other = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
switch (methodName)
{
case nameof(OpenXRFeature.OnInstanceCreate):
instanceCreated = true;
return result;
case nameof(OpenXRFeature.HookGetInstanceProcAddr):
hookInstanceProcAddr = true;
break;
default:
other = true;
break;
}
return true;
};
MockRuntime.Instance.required = required;
base.InitializeAndStart();
Assert.IsTrue(instanceCreated);
Assert.IsTrue(hookInstanceProcAddr);
if (required && !result)
Assert.IsNull(OpenXRLoaderBase.Instance);
else
Assert.IsNotNull(OpenXRLoaderBase.Instance);
// A feature that fails that is not required should be disabled
if (!result && !required)
Assert.IsFalse(MockRuntime.Instance.enabled);
base.StopAndShutdown();
if (result)
Assert.IsTrue(other);
else
// A feature that fails initialize should have no further callbacks
Assert.IsFalse(other);
}
[Test]
public void DeinitWithoutInit()
{
bool callback = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
callback = true;
return true;
};
base.StopAndShutdown();
Assert.IsFalse(callback);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd9093c836e64270bb8a8c79d98e0055
timeCreated: 1586981136

View File

@@ -0,0 +1,45 @@
using System.IO;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEditor.SceneManagement;
[assembly: UnityPlatform(RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor)]
namespace UnityEditor.XR.OpenXR.Tests
{
internal class aAssetBundleTests
{
[Test]
public void BuildAssetBundle()
{
#if UNITY_EDITOR_WIN
var target = BuildTarget.StandaloneWindows64;
#elif UNITY_EDITOR_OSX
var target = BuildTarget.StandaloneOSX;
#else
var target = BuildTarget.NoTarget;
#endif
// Test is only valid if we have a valid build target and that build target is available
if (target == BuildTarget.NoTarget || !BuildPipeline.IsBuildTargetSupported(BuildPipeline.GetBuildTargetGroup(target), target))
return;
// Create an asset and add it to an asset bundle
var scene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects);
EditorSceneManager.SaveScene(scene, "Assets/abtest.unity");
AssetDatabase.Refresh();
var importer = AssetImporter.GetAtPath("Assets/abtest.unity");
importer.assetBundleName = "mocktest";
if (!Directory.Exists("mocktest/ab"))
Directory.CreateDirectory("mocktest/ab");
// Build the asset bundle
BuildPipeline.BuildAssetBundles("mocktest/ab", BuildAssetBundleOptions.ForceRebuildAssetBundle, target);
// Cleanup
AssetDatabase.DeleteAsset("Assets/abtest.unity");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3bfe33a5f8e944b0be093f643d913262
timeCreated: 1625861611

View File

@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR.Tests;
using Assert = UnityEngine.Assertions.Assert;
namespace UnityEditor.XR.OpenXR.Tests
{
// If you change this file, be sure to run the "no players" tests on yamato.
// APV jobs don't include "players" such as standalone, so `BuildMockPlayer()`
// will fail during APV. The "no players" job on yamato will catch this.
internal class zBuildHookTests : OpenXRLoaderSetup
{
internal static BuildReport BuildMockPlayer()
{
BuildPlayerOptions opts = new BuildPlayerOptions();
#if UNITY_EDITOR_WIN
opts.target = BuildTarget.StandaloneWindows64;
#elif UNITY_EDITOR_OSX
opts.target = BuildTarget.StandaloneOSX;
#endif
if (File.Exists("Assets/main.unity"))
opts.scenes = new string[] { "Assets/main.unity" };
opts.targetGroup = BuildTargetGroup.Standalone;
opts.locationPathName = "mocktest/mocktest.exe";
UnityEngine.TestTools.LogAssert.ignoreFailingMessages = true;
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Standalone, opts.target);
var report = BuildPipeline.BuildPlayer(opts);
UnityEngine.TestTools.LogAssert.ignoreFailingMessages = false;
return report;
}
[Test]
public void PrePostCallbacksAreReceived()
{
bool preprocessCalled = false;
bool postprocessCalled = false;
BuildCallbacks.TestCallback = (methodName, param) =>
{
if (methodName == "OnPreprocessBuildExt")
{
preprocessCalled = true;
}
if (methodName == "OnPostprocessBuildExt")
{
postprocessCalled = true;
}
return true;
};
var result = BuildMockPlayer();
if (Environment.GetEnvironmentVariable("UNITY_OPENXR_YAMATO") == "1")
Assert.IsTrue(result.summary.result == BuildResult.Succeeded);
else if (result.summary.result != BuildResult.Succeeded)
return;
Assert.IsTrue(preprocessCalled);
Assert.IsTrue(postprocessCalled);
}
[Test]
public void NoBuildCallbacksFeatureDisabled()
{
bool preprocessCalled = false;
bool postprocessCalled = false;
BuildCallbacks.TestCallback = (methodName, param) =>
{
if (methodName == "OnPreprocessBuildExt")
{
preprocessCalled = true;
}
if (methodName == "OnPostprocessBuildExt")
{
postprocessCalled = true;
}
return true;
};
// Disable mock runtime, no callbacks should occur during build
EnableFeature<MockRuntime>(false);
BuildMockPlayer();
Assert.IsFalse(preprocessCalled);
Assert.IsFalse(postprocessCalled);
}
[Test]
public void NoBuildCallbacksOpenXRDisabled()
{
bool preprocessCalled = false;
bool postprocessCalled = false;
BuildCallbacks.TestCallback = (methodName, param) =>
{
if (methodName == "OnPreprocessBuildExt")
{
preprocessCalled = true;
}
if (methodName == "OnPostprocessBuildExt")
{
postprocessCalled = true;
}
return true;
};
// Remove OpenXR Loader, no callbacks should occur during build
var loaders = XRGeneralSettings.Instance.Manager.activeLoaders;
XRGeneralSettings.Instance.Manager.TrySetLoaders(new List<XRLoader>());
BuildMockPlayer();
XRGeneralSettings.Instance.Manager.TrySetLoaders(new List<XRLoader>(loaders));
Assert.IsFalse(preprocessCalled);
Assert.IsFalse(postprocessCalled);
}
[Test]
public void VerifyBootConfigWrite()
{
bool preprocessCalled = false;
bool postprocessCalled = false;
BootConfigTests.TestCallback = (methodName, param) =>
{
if (methodName == "OnPreprocessBuildExt")
{
preprocessCalled = true;
}
if (methodName == "OnPostprocessBuildExt")
{
postprocessCalled = true;
}
return true;
};
var result = BuildMockPlayer();
BuildMockPlayer();
Assert.IsFalse(preprocessCalled);
Assert.IsFalse(postprocessCalled);
BootConfigTests.EnsureCleanupFromLastRun();
}
private bool HasOpenXRLibraries(BuildReport report)
{
var path = Path.GetDirectoryName(report.summary.outputPath);
var dir = new DirectoryInfo(path);
var ext = "dll";
if (Application.platform == RuntimePlatform.OSXEditor)
ext = "dylib";
var dlls = dir.EnumerateFiles($"*.{ext}", SearchOption.AllDirectories).Select(s => s.Name).ToList();
return dlls.Contains($"openxr_loader.{ext}") || dlls.Contains($"UnityOpenXR.{ext}");
}
[Test]
public void VerifyBuildOutputLibraries()
{
var resultWithOpenXR = BuildMockPlayer();
// Disable this test if we're not running our openxr yamato infrastructure
if (resultWithOpenXR.summary.result != BuildResult.Succeeded && Environment.GetEnvironmentVariable("UNITY_OPENXR_YAMATO") != "1")
return;
Assert.IsTrue(HasOpenXRLibraries(resultWithOpenXR));
// Remove OpenXR Loader
XRGeneralSettings.Instance.Manager.TrySetLoaders(new List<XRLoader>());
var resultWithoutOpenXR = BuildMockPlayer();
Assert.IsFalse(HasOpenXRLibraries(resultWithoutOpenXR));
}
[Test]
public void VerifyBuildWithoutAnalytics()
{
var packageName = "com.unity.analytics";
UnityEditor.PackageManager.Client.Remove(packageName);
var result = BuildMockPlayer();
if (Environment.GetEnvironmentVariable("UNITY_OPENXR_YAMATO") == "1")
Assert.IsTrue(result.summary.result == BuildResult.Succeeded);
else if (result.summary.result != BuildResult.Succeeded)
return;
}
internal class BuildCallbacks : OpenXRFeatureBuildHooks
{
[NonSerialized] internal static Func<string, object, bool> TestCallback = (methodName, param) => true;
public override int callbackOrder => 1;
public override Type featureType => typeof(MockRuntime);
protected override void OnPreprocessBuildExt(BuildReport report)
{
TestCallback(MethodBase.GetCurrentMethod().Name, report);
}
protected override void OnPostGenerateGradleAndroidProjectExt(string path)
{
TestCallback(MethodBase.GetCurrentMethod().Name, path);
}
protected override void OnPostprocessBuildExt(BuildReport report)
{
TestCallback(MethodBase.GetCurrentMethod().Name, report);
}
protected override void OnProcessBootConfigExt(BuildReport report, BootConfigBuilder builder)
{
TestCallback(MethodBase.GetCurrentMethod().Name, report);
}
}
internal class BootConfigTests : OpenXRFeatureBuildHooks
{
[NonSerialized] internal static Func<string, object, bool> TestCallback = (methodName, param) => true;
public override int callbackOrder => 1;
public override Type featureType => typeof(MockRuntime);
// For this test, we want to ensure that the last run actually cleans up any settings that we've
// stored into the boot settings of the EditorUserBuildSettings. We need the last run BuildReport
// in order to check the EditorUserBuildSettings.
private static BuildReport s_lastRunBuildReport;
protected override void OnPreprocessBuildExt(BuildReport report)
{
}
protected override void OnPostGenerateGradleAndroidProjectExt(string path)
{
}
protected override void OnPostprocessBuildExt(BuildReport report)
{
// check to see if we've got the boot config written into the UserSettings
var bootConfig = new BootConfig(report);
bootConfig.ReadBootConfig();
Assert.IsTrue(bootConfig.TryGetValue("key-01", out var key01Value));
Assert.AreEqual(key01Value, "primary test value");
Assert.IsTrue(bootConfig.TryGetValue("key-02", out var key02Value));
Assert.AreEqual(key02Value, "secondary test value");
Assert.IsTrue(bootConfig.TryGetValue("key-03", out var key03Value));
Assert.AreEqual(key03Value, "1");
Assert.IsTrue(bootConfig.TryGetValue("key-04", out var key04Value));
Assert.AreEqual(key04Value, "0");
s_lastRunBuildReport = report;
}
protected override void OnProcessBootConfigExt(BuildReport report, BootConfigBuilder builder)
{
// Now we set some boot config values and check to make sure they're there.
builder.SetBootConfigValue("key-01", "primary test value");
builder.SetBootConfigValue("key-02", "secondary test value");
builder.SetBootConfigBoolean("key-03", true);
builder.SetBootConfigBoolean("key-04", false);
Assert.IsTrue(builder.TryGetBootConfigValue("key-01", out var result01));
Assert.AreEqual(result01, "primary test value");
Assert.IsTrue(builder.TryGetBootConfigValue("key-02", out var result02));
Assert.AreEqual(result02, "secondary test value");
Assert.IsTrue(builder.TryGetBootConfigBoolean("key-03", out var result03));
Assert.IsTrue(result03);
Assert.IsTrue(builder.TryGetBootConfigBoolean("key-04", out var result04));
Assert.IsFalse(result04);
builder.SetBootConfigValue("key-05", "remove-me");
Assert.IsTrue(builder.TryRemoveBootConfigEntry("key-05"));
Assert.IsFalse(builder.TryGetBootConfigValue("key-05", out var result05));
Assert.IsFalse(builder.TryGetBootConfigBoolean("key-999", out var result06));
Assert.IsFalse(result06);
}
public static void EnsureCleanupFromLastRun()
{
// make sure that the UserSettings doesn't hold any additional configs we've previously written
if (s_lastRunBuildReport == null)
return;
var bootConfig = new BootConfig(s_lastRunBuildReport);
bootConfig.ReadBootConfig();
Assert.IsFalse(bootConfig.TryGetValue("key-01", out var key01Value));
Assert.IsFalse(bootConfig.TryGetValue("key-02", out var key02Value));
Assert.IsFalse(bootConfig.TryGetValue("key-03", out var key03Value));
Assert.IsFalse(bootConfig.TryGetValue("key-04", out var key04Value));
Assert.IsFalse(bootConfig.TryGetValue("key-05", out var key05Value));
s_lastRunBuildReport = null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a0dc36717c904db4ab68f42baa559da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,372 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
class zBuildSamplesYamatoOnly
{
struct SampleBuildTargetSetup
{
public BuildTarget buildTarget;
public BuildTargetGroup targetGroup;
public Action<string, string> setupPlayerSettings;
public string outputPostfix;
public Regex sampleRegex;
}
static void WriteAndroidInstallerScripts(string outputFile, string identifier)
{
var dir = Path.GetDirectoryName(outputFile);
if (dir == null) return;
Directory.CreateDirectory(dir);
var scripts = new string[] { "install.command", "install.bat" };
foreach (var script in scripts)
{
var scriptPath = Path.Combine(dir, script);
var scriptContents = $"adb uninstall {identifier}\n" +
$"adb install \"{Path.GetFileName(outputFile)}\"\n\n";
File.AppendAllText(scriptPath, scriptContents);
}
}
static void EnableQuestFeature()
{
foreach (var feature in OpenXRSettings.ActiveBuildTargetInstance.features)
{
if (String.Compare(feature.featureIdInternal, "com.unity.openxr.feature.metaquest", true) == 0)
{
Console.WriteLine($"Enable: {feature.nameUi}");
feature.enabled = true;
return;
}
}
Assert.IsTrue(false, "Could not enable meta quest extension - if you're not on build machine you must copy dir MetaQuest to your project.");
}
static void EnableMSFTObserverFeature()
{
foreach (var feature in OpenXRSettings.ActiveBuildTargetInstance.features)
{
if (String.Compare(feature.featureIdInternal, "com.unity.openxr.feature.example.msftobserver", true) == 0)
{
Console.WriteLine($"Enable: {feature.nameUi}");
feature.enabled = true;
return;
}
}
}
static void EnableFeature<TFeatureType>() where TFeatureType : OpenXRFeature
{
foreach (var feature in OpenXRSettings.ActiveBuildTargetInstance.features)
{
if (feature is TFeatureType)
{
Console.WriteLine($"Enable: {feature.nameUi}");
feature.enabled = true;
break;
}
}
}
static void EnableSampleFeatures()
{
foreach (var feature in OpenXRSettings.ActiveBuildTargetInstance.features)
{
if (feature.GetType().Namespace == null)
{
throw new Exception("All code in the OpenXR Package must be in a namespace.");
}
if (feature.GetType().Namespace.StartsWith("UnityEngine.XR.OpenXR.Samples"))
{
Console.WriteLine($"Enable: {feature.nameUi}");
feature.enabled = true;
}
}
}
static void EnableStandaloneProfiles()
{
EnableFeature<MicrosoftHandInteraction>();
EnableFeature<MicrosoftMotionControllerProfile>();
EnableFeature<HTCViveControllerProfile>();
EnableFeature<ValveIndexControllerProfile>();
EnableFeature<OculusTouchControllerProfile>();
EnableFeature<MetaQuestTouchProControllerProfile>();
EnableFeature<HandInteractionProfile>();
EnableFeature<PalmPoseInteraction>();
EnableFeature<DPadInteraction>();
EnableFeature<HandCommonPosesInteraction>();
}
static void EnableWSAProfiles()
{
EnableFeature<MicrosoftHandInteraction>();
EnableFeature<EyeGazeInteraction>();
EnableFeature<MicrosoftMotionControllerProfile>();
EnableFeature<HandInteractionProfile>();
EnableFeature<PalmPoseInteraction>();
EnableFeature<HandCommonPosesInteraction>();
}
static void EnableAndroidProfiles()
{
EnableFeature<OculusTouchControllerProfile>();
EnableFeature<MetaQuestTouchProControllerProfile>();
}
static SampleBuildTargetSetup[] buildTargetSetup =
{
#if UNITY_EDITOR_WIN
new SampleBuildTargetSetup
{
buildTarget = BuildTarget.StandaloneWindows64,
targetGroup = BuildTargetGroup.Standalone,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
EnableStandaloneProfiles();
PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new[] { GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Vulkan });
OpenXRSettings.ActiveBuildTargetInstance.depthSubmissionMode = OpenXRSettings.DepthSubmissionMode.Depth24Bit;
},
outputPostfix = "dx11",
},
new SampleBuildTargetSetup
{
sampleRegex = new Regex(".*Render.*"), // Only build dx12 variant for Render Samples
buildTarget = BuildTarget.StandaloneWindows64,
targetGroup = BuildTargetGroup.Standalone,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new[] { GraphicsDeviceType.Direct3D12, GraphicsDeviceType.Direct3D11 });
QualitySettings.SetQualityLevel(5);
QualitySettings.antiAliasing = 4;
},
outputPostfix = "dx12",
},
new SampleBuildTargetSetup
{
sampleRegex = new Regex(".*Render.*"), // Only build vulkan variant for Render Samples
buildTarget = BuildTarget.StandaloneWindows64,
targetGroup = BuildTargetGroup.Standalone,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11 });
OpenXRSettings.ActiveBuildTargetInstance.depthSubmissionMode = OpenXRSettings.DepthSubmissionMode.Depth24Bit;
},
outputPostfix = "vk",
},
new SampleBuildTargetSetup
{
buildTarget = BuildTarget.WSAPlayer,
targetGroup = BuildTargetGroup.WSA,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
EnableMSFTObserverFeature();
EnableFeature<EyeGazeInteraction>();
EnableFeature<MicrosoftHandInteraction>();
EnableWSAProfiles();
PlayerSettings.SetGraphicsAPIs(BuildTarget.WSAPlayer, new[] { GraphicsDeviceType.Direct3D11 });
PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.GazeInput, true);
#if UNITY_2021_3_OR_NEWER
PlayerSettings.WSA.packageName = PlayerSettings.GetApplicationIdentifier(NamedBuildTarget.WindowsStoreApps);
#else
PlayerSettings.WSA.packageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.WSA);
#endif
OpenXRSettings.ActiveBuildTargetInstance.renderMode = OpenXRSettings.RenderMode.SinglePassInstanced;
OpenXRSettings.ActiveBuildTargetInstance.depthSubmissionMode = OpenXRSettings.DepthSubmissionMode.Depth16Bit;
},
outputPostfix = "dx11",
},
new SampleBuildTargetSetup
{
sampleRegex = new Regex(".*Render.*"), // Only build dx12 variant for Render Samples
buildTarget = BuildTarget.WSAPlayer,
targetGroup = BuildTargetGroup.WSA,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
EnableMSFTObserverFeature();
EnableFeature<EyeGazeInteraction>();
EnableFeature<MicrosoftHandInteraction>();
PlayerSettings.SetGraphicsAPIs(BuildTarget.WSAPlayer, new[] { GraphicsDeviceType.Direct3D12 });
QualitySettings.SetQualityLevel(5);
QualitySettings.antiAliasing = 4;
#if UNITY_2021_3_OR_NEWER
PlayerSettings.WSA.packageName = PlayerSettings.GetApplicationIdentifier(NamedBuildTarget.WindowsStoreApps);
#else
PlayerSettings.WSA.packageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.WSA);
#endif
PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.GazeInput, true);
},
outputPostfix = "dx12",
},
#endif
new SampleBuildTargetSetup
{
sampleRegex = new Regex(".*Render.*|.*MetaSample.*"), // Only build vulkan variant for Render Samples
buildTarget = BuildTarget.Android,
targetGroup = BuildTargetGroup.Android,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
EnableQuestFeature();
EnableAndroidProfiles();
PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.OpenGLES3 });
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel25;
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
#if UNITY_2021_3_OR_NEWER
PlayerSettings.SetScriptingBackend(NamedBuildTarget.Android, ScriptingImplementation.IL2CPP);
#else
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
#endif
WriteAndroidInstallerScripts(outputFile, identifier);
OpenXRSettings.ActiveBuildTargetInstance.depthSubmissionMode = OpenXRSettings.DepthSubmissionMode.Depth16Bit;
},
outputPostfix = "arm64_vk",
},
new SampleBuildTargetSetup
{
sampleRegex = new Regex("^(?!.*MetaSample).*$"), // Don't build the Meta Sample for GLES
buildTarget = BuildTarget.Android,
targetGroup = BuildTargetGroup.Android,
setupPlayerSettings = (outputFile, identifier) =>
{
EnableSampleFeatures();
EnableQuestFeature();
EnableAndroidProfiles();
PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.Android, false);
PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new[] { GraphicsDeviceType.OpenGLES3, GraphicsDeviceType.Vulkan });
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel25;
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
#if UNITY_2021_3_OR_NEWER
PlayerSettings.SetScriptingBackend(NamedBuildTarget.Android, ScriptingImplementation.IL2CPP);
#else
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
#endif
WriteAndroidInstallerScripts(outputFile, identifier);
OpenXRSettings.ActiveBuildTargetInstance.depthSubmissionMode = OpenXRSettings.DepthSubmissionMode.Depth16Bit;
},
outputPostfix = "arm64_gles3",
},
};
static string GetBuildFileExt(BuildTarget target)
{
switch (target)
{
case BuildTarget.Android:
return ".apk";
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
return ".exe";
default:
return "";
}
}
static string GetResultDir()
{
bool next = false;
foreach (var arg in System.Environment.GetCommandLineArgs())
{
if (next)
return arg;
if (arg == "-resultDir")
next = true;
}
return "OpenXR Samples";
}
static void BuildSamples()
{
string resultDir = GetResultDir();
Console.WriteLine("Result Dir: " + resultDir);
var sampleName = "Unknown Sample";
var projSamplesDir = new DirectoryInfo("Assets/Sample");
if (projSamplesDir.Exists)
{
// Use the directory name in the samples directory, if it exists
sampleName = projSamplesDir.GetDirectories()[0].Name;
}
else
{
// Otherwise use the current folder as the project name
projSamplesDir = new DirectoryInfo("Assets");
sampleName = new DirectoryInfo(".").Name;
}
PlayerSettings.colorSpace = ColorSpace.Linear;
FeatureHelpers.RefreshFeatures(EditorUserBuildSettings.selectedBuildTargetGroup);
foreach (var setup in buildTargetSetup)
{
if (setup.sampleRegex != null && !setup.sampleRegex.Match(sampleName).Success)
continue;
if (EditorUserBuildSettings.activeBuildTarget != setup.buildTarget)
continue;
string outputDir = Path.Combine(resultDir, setup.buildTarget.ToString());
string identifier = "com.openxr." + sampleName + "." + setup.outputPostfix;
#if UNITY_2021_3_OR_NEWER
PlayerSettings.SetApplicationIdentifier(NamedBuildTarget.FromBuildTargetGroup(setup.targetGroup), identifier);
#else
PlayerSettings.SetApplicationIdentifier(setup.targetGroup, identifier);
#endif
PlayerSettings.productName = "OpenXR " + sampleName + " " + setup.outputPostfix;
Console.WriteLine("=========== Setting up player settings (changing graphics apis)");
string outputFile = Path.Combine(outputDir,
PlayerSettings.productName + GetBuildFileExt(setup.buildTarget));
setup.setupPlayerSettings(outputFile, identifier);
// Get the list of scenes set in build settings in the project
var scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray();
// If there aren't any, just build all of the scenes found in the sample
if (scenes.Length == 0)
{
scenes = Directory.GetFiles(projSamplesDir.FullName, "*.unity", SearchOption.AllDirectories);
}
BuildPlayerOptions buildOptions = new BuildPlayerOptions
{
scenes = scenes,
target = setup.buildTarget,
targetGroup = setup.targetGroup,
locationPathName = outputFile,
};
Console.WriteLine($"=========== Building {sampleName} {setup.buildTarget}_{setup.outputPostfix}");
var report = BuildPipeline.BuildPlayer(buildOptions);
Console.WriteLine($"=========== Build Result {sampleName} {setup.buildTarget}_{setup.outputPostfix} {report.summary.result}");
if (report.summary.result == BuildResult.Failed)
{
EditorApplication.Exit(1);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b539520d5a342436b8ee4abb353fd33b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 95a45e2eff66745b1ba7ca6684e8a4b0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,222 @@
using System;
using NUnit.Framework;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class DiagnosticReportTests
{
const string k_SectionOneTitle = "Section One";
const string k_SectionTwoTitle = "Section Two";
[SetUp]
public void SetUp()
{
DiagnosticReport.StartReport();
}
[Test]
public void GettingSectionReturnsValidHandle()
{
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
Assert.AreNotEqual(DiagnosticReport.k_NullSection, sectionOneHandle);
}
[Test]
public void SameSectionTitleGivesSameSectionHandle()
{
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
var sectionOneHandleTwo = DiagnosticReport.GetSection(k_SectionOneTitle);
Assert.AreEqual(sectionOneHandle, sectionOneHandleTwo);
}
[Test]
public void DifferentSectionTitlesGiveDifferentSectionHandles()
{
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
var sectionTwoHandle = DiagnosticReport.GetSection(k_SectionTwoTitle);
Assert.AreNotEqual(sectionOneHandle, sectionTwoHandle);
}
[Test]
public void CheckSimpleReportGenerationIsCorrect()
{
const string k_ExpectedOutput = "==== Section One ====\n\n==== Last 20 Events ====\n";
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
var report = DiagnosticReport.GenerateReport();
Assert.IsFalse(String.IsNullOrEmpty(report));
Assert.AreEqual(k_ExpectedOutput, report);
}
[Test]
public void CheckGenerateReportWithEntries()
{
const string k_ExpectedOutput = @"==== Section One ====
Entry Header: Entry Body
Entry Header 2: Entry Body 2
Entry Header 3: Entry Body 3
==== Last 20 Events ====
";
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Entry Header", "Entry Body");
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Entry Header 2", "Entry Body 2");
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Entry Header 3", "Entry Body 3");
var report = DiagnosticReport.GenerateReport();
Assert.AreEqual(k_ExpectedOutput, report);
}
[Test]
public void CheckGenerateReportWithMultipleSectionsAndEntries()
{
const string k_ExpectedOutput = @"==== Section One ====
Entry Header: Entry Body
Entry Header 2: Entry Body 2
Entry Header 3: Entry Body 3
==== Section Two ====
Entry Header 4: Entry Body 4
Entry Header 5: Entry Body 5
Entry Header 6: Entry Body 6
==== Last 20 Events ====
";
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Entry Header", "Entry Body");
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Entry Header 2", "Entry Body 2");
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Entry Header 3", "Entry Body 3");
var sectionTwoHandle = DiagnosticReport.GetSection(k_SectionTwoTitle);
DiagnosticReport.AddSectionEntry(sectionTwoHandle, "Entry Header 4", "Entry Body 4");
DiagnosticReport.AddSectionEntry(sectionTwoHandle, "Entry Header 5", "Entry Body 5");
DiagnosticReport.AddSectionEntry(sectionTwoHandle, "Entry Header 6", "Entry Body 6");
var report = DiagnosticReport.GenerateReport();
Assert.AreEqual(k_ExpectedOutput, report);
}
[Test]
public void CheckGeneratedEventsAreReported()
{
const string k_ExpectedOutput = @"==== Last 20 Events ====
Event One: Event Body One
";
DiagnosticReport.AddEventEntry("Event One", "Event Body One");
var report = DiagnosticReport.GenerateReport();
Assert.AreEqual(k_ExpectedOutput, report);
}
[Test]
public void CheckGeneratedEventsOverTwentyAreReported()
{
const string k_ExpectedOutput = @"==== Last 20 Events ====
Event 11: Event Body 11
Event 12: Event Body 12
Event 13: Event Body 13
Event 14: Event Body 14
Event 15: Event Body 15
Event 16: Event Body 16
Event 17: Event Body 17
Event 18: Event Body 18
Event 19: Event Body 19
Event 20: Event Body 20
Event 21: Event Body 21
Event 22: Event Body 22
Event 23: Event Body 23
Event 24: Event Body 24
Event 25: Event Body 25
Event 26: Event Body 26
Event 27: Event Body 27
Event 28: Event Body 28
Event 29: Event Body 29
Event 30: Event Body 30
";
for (int i = 0; i <= 30; i++)
{
DiagnosticReport.AddEventEntry($"Event {i}", $"Event Body {i}");
}
var report = DiagnosticReport.GenerateReport();
Assert.AreEqual(k_ExpectedOutput, report);
}
[Test]
public void CheckFullReport()
{
const string k_ExpectedOutput = @"==== Section One ====
Section One Entry One: Simple
==== Section Two ====
Section Two Entry One: Simple
Section Two Entry Two: (2)
FOO=BAR
BAZ=100
==== Last 20 Events ====
Event 11: Event Body 11
Event 12: Event Body 12
Event 13: Event Body 13
Event 14: Event Body 14
Event 15: Event Body 15
Event 16: Event Body 16
Event 17: Event Body 17
Event 18: Event Body 18
Event 19: Event Body 19
Event 20: Event Body 20
Event 21: Event Body 21
Event 22: Event Body 22
Event 23: Event Body 23
Event 24: Event Body 24
Event 25: Event Body 25
Event 26: Event Body 26
Event 27: Event Body 27
Event 28: Event Body 28
Event 29: Event Body 29
Event 30: Event Body 30
";
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
DiagnosticReport.AddSectionEntry(sectionOneHandle, "Section One Entry One", "Simple");
for (int i = 0; i <= 30; i++)
{
DiagnosticReport.AddEventEntry($"Event {i}", $"Event Body {i}");
}
var sectionTwoHandle = DiagnosticReport.GetSection(k_SectionTwoTitle);
DiagnosticReport.AddSectionEntry(sectionTwoHandle, "Section Two Entry One", "Simple");
DiagnosticReport.AddSectionBreak(sectionTwoHandle);
DiagnosticReport.AddSectionEntry(sectionTwoHandle, "Section Two Entry Two", @"(2)
FOO=BAR
BAZ=100
");
var report = DiagnosticReport.GenerateReport();
Debug.Log(report);
Assert.AreEqual(k_ExpectedOutput, report);
}
[Test]
public void SectionReportsStayInCreatedOrder()
{
var sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
var sectionTwoHandle = DiagnosticReport.GetSection(k_SectionTwoTitle);
var reportOne = DiagnosticReport.GenerateReport();
DiagnosticReport.StartReport();
sectionTwoHandle = DiagnosticReport.GetSection(k_SectionTwoTitle);
sectionOneHandle = DiagnosticReport.GetSection(k_SectionOneTitle);
var reportTwo = DiagnosticReport.GenerateReport();
Assert.AreNotEqual(reportOne, reportTwo);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6bf97c878af61544d8e27f6a50a1d9ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,192 @@
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine.TestTools;
using UnityEngine.XR.Management;
namespace UnityEngine.XR.TestTooling
{
internal abstract class LoaderTestSetup<L, S> : ManagementTestSetup, IPrebuildSetup, IPostBuildCleanup
where L : XRLoader
where S : ScriptableObject
{
protected abstract string settingsKey { get; }
protected L loader = null;
protected S settings = null;
public bool IsRunning<T>()
where T : class, ISubsystem
{
return XRGeneralSettings.Instance?.Manager?.activeLoader?.GetLoadedSubsystem<T>()?.running ?? false;
}
#if UNITY_EDITOR
T GetOrCreateAsset<T>(string path) where T : UnityEngine.ScriptableObject
{
T asset = default(T);
if (!File.Exists(path))
{
asset = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
}
else
{
asset = AssetDatabase.LoadAssetAtPath<T>(path);
}
return asset as T;
}
#endif
protected void DestroyLoaderAndSettings()
{
#if UNITY_EDITOR
var path = GetAssetPathForComponents(s_TempSettingsPath);
var settingsPath = Path.Combine(path, $"Test_{typeof(S).Name}.asset");
AssetDatabase.DeleteAsset(settingsPath);
var loaderPath = Path.Combine(path, $"Test_{typeof(L).Name}.asset");
AssetDatabase.DeleteAsset(loaderPath);
#endif
}
protected void SetupLoaderAndSettings()
{
#if UNITY_EDITOR
// Setup Loader
var path = GetAssetPathForComponents(s_TempSettingsPath);
var loaderPath = Path.Combine(path, $"Test_{typeof(L).Name}.asset");
loader = GetOrCreateAsset<L>(loaderPath);
if (xrGeneralSettings == null)
{
xrGeneralSettings = XRGeneralSettings.Instance;
testManager = xrGeneralSettings?.Manager ?? null;
}
#pragma warning disable CS0618
xrGeneralSettings?.Manager.loaders.Clear();
xrGeneralSettings?.Manager.loaders.Add(loader);
#pragma warning restore CS0618
// Setup Settings
var settingsPath = Path.Combine(path, $"Test_{typeof(S).Name}.asset");
settings = GetOrCreateAsset<S>(settingsPath);
EditorBuildSettings.AddConfigObject(settingsKey, settings, true);
#endif
}
public override void SetupTest()
{
#if UNITY_EDITOR
base.SetupTest();
SetupLoaderAndSettings();
#endif
}
protected void RemoveLoaderAndSettings()
{
#if UNITY_EDITOR
StopAndShutdown();
if (loader != null)
{
#pragma warning disable CS0618
xrGeneralSettings.Manager.loaders.Remove(loader);
#pragma warning restore CS0618
}
EditorBuildSettings.RemoveConfigObject(settingsKey);
loader = null;
#endif
}
public override void TearDownTest()
{
#if UNITY_EDITOR
RemoveLoaderAndSettings();
base.TearDownTest();
#endif
}
protected void Initialize()
{
var manager = XRGeneralSettings.Instance?.Manager;
manager?.InitializeLoaderSync();
}
protected void Start()
{
var manager = XRGeneralSettings.Instance?.Manager;
if ((manager?.activeLoader ?? null) != null)
{
manager.StartSubsystems();
}
}
protected void InitializeAndStart()
{
Initialize();
Start();
}
protected void Stop()
{
var manager = XRGeneralSettings.Instance?.Manager;
if (manager != null && manager.activeLoader != null)
manager?.StopSubsystems();
else if (loader != null)
loader.Stop();
}
protected void Shutdown()
{
var manager = XRGeneralSettings.Instance?.Manager;
if (manager != null && manager.activeLoader != null)
manager?.DeinitializeLoader();
else if (loader != null)
loader.Deinitialize();
}
protected void StopAndShutdown()
{
Stop();
Shutdown();
}
protected void RestartProvider()
{
StopAndShutdown();
InitializeAndStart();
}
// IPrebuildSetup - Build time setup
public virtual void Setup()
{
#if UNITY_EDITOR
if (XRGeneralSettings.Instance != null)
XRGeneralSettings.Instance.InitManagerOnStart = false;
#endif
}
// IPostBuildCleanup - Build time cleanup
public virtual void Cleanup()
{
#if UNITY_EDITOR
if (XRGeneralSettings.Instance != null)
XRGeneralSettings.Instance.InitManagerOnStart = true;
#endif
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c3fab20e4f2e451dbbc1a5856ce147be
timeCreated: 1586815402

View File

@@ -0,0 +1,130 @@
using System;
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.Management;
#endif
using UnityEngine.XR.Management;
namespace UnityEngine.XR.TestTooling
{
// Mostly borrowed from XRManagement - this should probably live in that package.
internal abstract class ManagementTestSetup
{
protected static readonly string[] s_TestGeneralSettings = { "Temp", "Test" };
protected static readonly string[] s_TempSettingsPath = { "Temp", "Test", "Settings" };
/// <summary>
/// When true, AssetDatabase.AddObjectToAsset will not be called to add XRManagerSettings to XRGeneralSettings.
/// </summary>
protected virtual bool TestManagerUpgradePath => false;
protected string testPathToGeneralSettings;
protected string testPathToSettings;
#pragma warning disable 0414 // CS0414: The field 'ManagementTestSetup.currentSettings' is assigned but its value is never used
private UnityEngine.Object currentSettings = null;
#pragma warning restore 0414
protected XRManagerSettings testManager = null;
protected XRGeneralSettings xrGeneralSettings = null;
#if UNITY_EDITOR
protected XRGeneralSettingsPerBuildTarget buildTargetSettings = null;
#endif
public virtual void SetupTest()
{
#if UNITY_EDITOR
var windowTypes = TypeCache.GetTypesDerivedFrom(typeof(EditorWindow));
foreach (var wt in windowTypes)
{
if (wt.Name.Contains("ProjectSettingsWindow"))
{
var projectSettingsWindow = EditorWindow.GetWindow(wt);
if (projectSettingsWindow != null)
{
projectSettingsWindow.Close();
}
}
}
testPathToGeneralSettings = GetAssetPathForComponents(s_TestGeneralSettings);
testPathToGeneralSettings = Path.Combine(testPathToGeneralSettings, "Test_XRGeneralSettingsPerBuildTarget.asset");
if (File.Exists(testPathToGeneralSettings))
{
AssetDatabase.DeleteAsset(testPathToGeneralSettings);
}
buildTargetSettings = ScriptableObject.CreateInstance<XRGeneralSettingsPerBuildTarget>();
AssetDatabase.CreateAsset(buildTargetSettings, testPathToGeneralSettings);
testPathToSettings = GetAssetPathForComponents(s_TempSettingsPath);
testPathToSettings = Path.Combine(testPathToSettings, "Test_XRGeneralSettings.asset");
if (File.Exists(testPathToSettings))
{
AssetDatabase.DeleteAsset(testPathToSettings);
}
testManager = ScriptableObject.CreateInstance<XRManagerSettings>();
xrGeneralSettings = ScriptableObject.CreateInstance<XRGeneralSettings>() as XRGeneralSettings;
XRGeneralSettings.Instance = xrGeneralSettings;
xrGeneralSettings.Manager = testManager;
buildTargetSettings.SetSettingsForBuildTarget(BuildTargetGroup.Standalone, xrGeneralSettings);
buildTargetSettings.SetSettingsForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), xrGeneralSettings);
AssetDatabase.CreateAsset(xrGeneralSettings, testPathToSettings);
AssetDatabase.AddObjectToAsset(testManager, xrGeneralSettings);
AssetDatabase.SaveAssets();
EditorBuildSettings.AddConfigObject(XRGeneralSettings.k_SettingsKey, buildTargetSettings, true);
#endif
}
public virtual void TearDownTest()
{
#if UNITY_EDITOR
EditorBuildSettings.RemoveConfigObject(XRGeneralSettings.k_SettingsKey);
buildTargetSettings = null;
testManager = null;
xrGeneralSettings = null;
AssetDatabase.DeleteAsset(Path.Combine("Assets", "Temp"));
#endif
}
#if UNITY_EDITOR
public static string GetAssetPathForComponents(string[] pathComponents, string root = "Assets")
{
if (pathComponents.Length <= 0)
return null;
string path = root;
foreach (var pc in pathComponents)
{
string subFolder = Path.Combine(path, pc);
bool shouldCreate = true;
foreach (var f in AssetDatabase.GetSubFolders(path))
{
if (String.Compare(Path.GetFullPath(f), Path.GetFullPath(subFolder), true) == 0)
{
shouldCreate = false;
break;
}
}
if (shouldCreate)
AssetDatabase.CreateFolder(path, pc);
path = subFolder;
}
return path;
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 205f1850c21694e34b2b06a191e3b27b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,188 @@
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using ProviderXRStats = UnityEngine.XR.Provider.XRStats;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class MetaPerformanceMetricsTests : OpenXRLoaderSetup
{
private static readonly string kAppCPUFrametimeStr = "/perfmetrics_meta/app/cpu_frametime";
private static readonly string kAppGPUFrametimeStr = "/perfmetrics_meta/app/gpu_frametime";
private static readonly string kAppMotionToPhotonLatencyStr =
"/perfmetrics_meta/app/motion_to_photon_latency";
private static readonly string kCompositorCPUFrametimeStr =
"/perfmetrics_meta/compositor/cpu_frametime";
private static readonly string kCompositorGPUFrametimeStr =
"/perfmetrics_meta/compositor/gpu_frametime";
private static readonly string kCompositorDroppedFrameCountStr =
"/perfmetrics_meta/compositor/dropped_frame_count";
private static readonly string kDeviceCPUUtilizationAvgStr =
"/perfmetrics_meta/device/cpu_utilization_average";
private static readonly string kDeviceCPUUtilizationWorstStr =
"/perfmetrics_meta/device/cpu_utilization_worst";
private static readonly string kDeviceGPUUtilizationStr =
"/perfmetrics_meta/device/gpu_utilization";
private readonly List<string> xrPathStrings = new List<string>
{
kAppCPUFrametimeStr,
kAppGPUFrametimeStr,
kAppGPUFrametimeStr, // because we have 2 consumers
kAppMotionToPhotonLatencyStr,
kCompositorCPUFrametimeStr,
kCompositorGPUFrametimeStr,
kCompositorGPUFrametimeStr, // because we have 2 consumers
kCompositorDroppedFrameCountStr,
kCompositorDroppedFrameCountStr, // because we have 2 consumers
kDeviceCPUUtilizationAvgStr,
kDeviceCPUUtilizationWorstStr,
kDeviceGPUUtilizationStr
};
private static readonly string kUnityStatsAppCpuTime = "perfmetrics.appcputime";
private static readonly string kUnityStatsAppGpuTime = "perfmetrics.appgputime";
private static readonly string kUnityStatsAppGpuTimeFunc = "GPUAppLastFrameTime";
private static readonly string kUnityStatsCompositorCpuTime =
"perfmetrics.compositorcputime";
private static readonly string kUnityStatsCompositorGpuTime =
"perfmetrics.compositorgputime";
private static readonly string kUnityStatsCompositorGpuTimeFunc =
"GPUCompositorLastFrameTime";
private static readonly string kUnityStatsGpuUtil = "perfmetrics.gpuutil";
private static readonly string kUnityStatsCpuUtilAvg = "perfmetrics.cpuutilavg";
private static readonly string kUnityStatsCpuUtilWorst = "perfmetrics.cpuutilworst";
private static readonly string kUnityStatsDroppedFrameCount = "appstats.compositordroppedframes";
private static readonly string kUnityStatsDroppedFrameCountFunc = "droppedFrameCount";
private static readonly string kUnityStatsMotionToPhotonFunc = "motionToPhoton";
private readonly List<string> unityStatStrings = new List<string>
{
kUnityStatsAppCpuTime,
kUnityStatsAppGpuTime,
kUnityStatsAppGpuTimeFunc,
kUnityStatsCompositorCpuTime,
kUnityStatsCompositorGpuTime,
kUnityStatsCompositorGpuTimeFunc,
kUnityStatsGpuUtil,
kUnityStatsCpuUtilAvg,
kUnityStatsCpuUtilWorst,
kUnityStatsDroppedFrameCount,
kUnityStatsDroppedFrameCountFunc,
kUnityStatsMotionToPhotonFunc
};
private readonly Dictionary<string, XrPerformanceMetricsCounterUnitMETA> unitMap =
new Dictionary<string, XrPerformanceMetricsCounterUnitMETA>()
{
{
kAppCPUFrametimeStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META
},
{
kAppGPUFrametimeStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META
},
{
kAppMotionToPhotonLatencyStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META
},
{
kCompositorCPUFrametimeStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META
},
{
kCompositorGPUFrametimeStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META
},
{
kCompositorDroppedFrameCountStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_GENERIC_META
},
{
kDeviceCPUUtilizationAvgStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_PERCENTAGE_META
},
{
kDeviceCPUUtilizationWorstStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_PERCENTAGE_META
},
{
kDeviceGPUUtilizationStr,
XrPerformanceMetricsCounterUnitMETA.XR_PERFORMANCE_METRICS_COUNTER_UNIT_PERCENTAGE_META
}
};
private enum XrPerformanceMetricsCounterUnitMETA
{
XR_PERFORMANCE_METRICS_COUNTER_UNIT_GENERIC_META = 0,
XR_PERFORMANCE_METRICS_COUNTER_UNIT_PERCENTAGE_META = 1,
XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META = 2,
XR_PERFORMANCE_METRICS_COUNTER_UNIT_BYTES_META = 3,
XR_PERFORMANCE_METRICS_COUNTER_UNIT_HERTZ_META = 4,
XR_PERFORMANCE_METRICS_COUNTER_UNIT_MAX_ENUM_META = 0x7FFFFFFF
}
[UnityTest]
public IEnumerator TestAllMetrics()
{
base.InitializeAndStart();
yield return new WaitForXrFrame(2);
List<XRDisplaySubsystem> displays = new List<XRDisplaySubsystem>();
SubsystemManager.GetSubsystems(displays);
Assert.That(displays.Count, Is.EqualTo(1));
// insert a -1 dummy value to ensure we're ready when we start querying
for (int i = -1; i < 5; i++)
{
foreach (string xrPathString in xrPathStrings)
{
MockRuntime.MetaPerformanceMetrics_SeedCounterOnce_Float(
xrPathString,
(float)i,
(uint)unitMap[xrPathString]
);
}
}
// wait for results to come available.
bool didSync = false;
for (int i = 0; i < 60; i++)
{
if (ProviderXRStats.TryGetStat(displays[0], kUnityStatsAppCpuTime, out var sync))
{
if (sync < 0)
{
didSync = true;
break;
}
if (sync > 0)
Assert.Fail("did not receive -1.f sync stat");
}
yield return new WaitForXrFrame();
}
Assert.True(didSync, "did not receive -1.f sync stat");
for (int i = 0; i < 5; i++)
{
yield return new WaitForXrFrame();
foreach (string unityStatString in unityStatStrings)
{
Assert.True(
ProviderXRStats.TryGetStat(displays[0], unityStatString, out var stat),
"did not get stat for " + unityStatString
);
Assert.That(stat, Is.EqualTo((float)i).Within(0.001));
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30581af4aa5c63b4493cfae0e77f36d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.XR;
using UnityEngine.Scripting;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Input;
#if USE_INPUT_SYSTEM_POSE_CONTROL
using PoseControl = UnityEngine.InputSystem.XR.PoseControl;
#else
using PoseControl = UnityEngine.XR.OpenXR.Input.PoseControl;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Mock Interaction feature used in tests to test interaction features.
/// </summary>
#if UNITY_EDITOR
[UnityEditor.XR.OpenXR.Features.OpenXRFeature(UiName = "Mock Interaction Feature",
BuildTargetGroups = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.Android },
Company = "Unity",
Desc = "Mock interaction feature used for testing interactions",
DocumentationLink = Constants.k_DocumentationManualURL + "features/khrsimplecontrollerprofile.html",
OpenxrExtensionStrings = "",
Version = "0.0.1",
Category = UnityEditor.XR.OpenXR.Features.FeatureCategory.Interaction,
Hidden = true,
Priority = int.MinValue,
FeatureId = featureId)]
#endif
internal class MockInteractionFeature : OpenXRInteractionFeature
{
public const string featureId = "com.unity.openxr.feature.input.mockinteraction";
private const string kDeviceLocalizedName = "Mock Controller OpenXR";
/// <summary>
/// Mock controller used in testing of interaction features
/// </summary>
[Preserve, InputControlLayout(displayName = kDeviceLocalizedName, commonUsages = new[] { "LeftHand", "RightHand" })]
public class MockController : XRControllerWithRumble
{
[Preserve, InputControl]
public AxisControl trigger { get; private set; }
[Preserve, InputControl]
public ButtonControl triggerPressed { get; private set; }
[Preserve, InputControl]
public Vector2Control thumbstick { get; private set; }
[Preserve, InputControl(offset = 0)]
public PoseControl devicePose { get; private set; }
[Preserve, InputControl(offset = 2)]
public new ButtonControl isTracked { get; private set; }
[Preserve, InputControl(offset = 4)]
public new IntegerControl trackingState { get; private set; }
[Preserve, InputControl]
public new Vector3Control devicePosition { get; private set; }
[Preserve, InputControl]
public new QuaternionControl deviceRotation { get; private set; }
[Preserve, InputControl]
public HapticControl haptic { get; private set; }
protected override void FinishSetup()
{
base.FinishSetup();
trigger = GetChildControl<AxisControl>("trigger");
triggerPressed = GetChildControl<ButtonControl>("triggerPressed");
thumbstick = GetChildControl<Vector2Control>("thumbstick");
devicePose = GetChildControl<PoseControl>("devicePose");
isTracked = GetChildControl<ButtonControl>("isTracked");
trackingState = GetChildControl<IntegerControl>("trackingState");
devicePosition = GetChildControl<Vector3Control>("devicePosition");
deviceRotation = GetChildControl<QuaternionControl>("deviceRotation");
haptic = GetChildControl<HapticControl>("haptic");
}
}
/// <summary>
/// Get/Set the action map config that should be used to register actions for this profile. If no
/// config is specified the default action map config will be used.
/// </summary>
public ActionMapConfig actionMapConfig { get; set; }
/// <summary>
/// Registers the <see cref="MockController"/> layout with the Input System.
/// </summary>
protected override void RegisterDeviceLayout()
{
InputSystem.InputSystem.RegisterLayout(typeof(MockController),
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
/// Removes the <see cref="MockController"/> layout from the Input System.
/// </summary>
protected override void UnregisterDeviceLayout()
{
InputSystem.InputSystem.RemoveLayout(nameof(MockController));
}
protected override string GetDeviceLayoutName()
{
return nameof(MockController);
}
/// <summary>
/// Create a default action map config for the controller
/// </summary>
/// <returns></returns>
public ActionMapConfig CreateDefaultActionMapConfig() =>
new ActionMapConfig()
{
name = "mockcontroller",
localizedName = kDeviceLocalizedName,
manufacturer = "Unity",
serialNumber = "",
desiredInteractionProfile = "/interaction_profiles/unity/mock_controller",
deviceInfos = new List<DeviceConfig>
{
new DeviceConfig
{
userPath = UserPaths.leftHand,
characteristics = InputDeviceCharacteristics.Left | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Controller
},
new DeviceConfig
{
userPath = UserPaths.rightHand,
characteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Controller
}
},
actions = new List<ActionConfig>
{
new ActionConfig
{
name = nameof(MockController.triggerPressed),
localizedName = "Trigger Pressed",
type = ActionType.Binary,
bindings = new List<ActionBinding>
{
new ActionBinding
{
interactionPath = "/input/trigger/click"
}
}
},
new ActionConfig
{
name = nameof(MockController.trigger),
localizedName = "Trigger",
type = ActionType.Axis1D,
bindings = new List<ActionBinding>
{
new ActionBinding
{
interactionPath = "/input/trigger/value"
}
}
},
new ActionConfig
{
name = nameof(MockController.thumbstick),
localizedName = "Thumbstick",
type = ActionType.Axis2D,
bindings = new List<ActionBinding>
{
new ActionBinding
{
interactionPath = "/input/thumbstick/value"
}
}
},
new ActionConfig
{
name = nameof(MockController.devicePose),
localizedName = "Grip",
type = ActionType.Pose,
bindings = new List<ActionBinding>
{
new ActionBinding
{
interactionPath = "/input/grip/pose"
}
}
},
new ActionConfig
{
name = nameof(MockController.haptic),
localizedName = "Haptic Output",
type = ActionType.Vibrate,
bindings = new List<ActionBinding>
{
new ActionBinding
{
interactionPath = "/output/haptic"
}
}
}
}
};
protected override void RegisterActionMapsWithRuntime()
{
AddActionMap(actionMapConfig ?? CreateDefaultActionMapConfig());
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 19db6adf5be3433989f34a1788172b2c
timeCreated: 1621029060

View File

@@ -0,0 +1,438 @@
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
using UnityEngine.XR.OpenXR.NativeTypes;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Defines tests that validate the MockRuntime itself.
/// </summary>
internal class MockRuntimeTests : OpenXRLoaderSetup
{
[UnityTest]
public IEnumerator TransitionToState()
{
InitializeAndStart();
yield return new WaitForXrFrame();
Assert.AreEqual(MockRuntime.sessionState, XrSessionState.Focused, "MockRuntime must be in focused state for this test to work correctly");
Assert.IsTrue(MockRuntime.TransitionToState(XrSessionState.Visible, false), "Failed to transition to visible state");
Assert.AreEqual(MockRuntime.sessionState, XrSessionState.Visible);
}
[UnityTest]
public IEnumerator TransitionToStateForced()
{
InitializeAndStart();
yield return new WaitForXrFrame();
Assert.IsFalse(MockRuntime.TransitionToState(XrSessionState.Synchronized, false), "Synchronized state must be an invalid transition for this test to be valid");
Assert.IsTrue(MockRuntime.TransitionToState(XrSessionState.Synchronized, true), "Force state transition should not return false");
yield return new WaitForXrFrame();
Assert.IsTrue(MockRuntime.sessionState == XrSessionState.Synchronized);
}
[UnityTest]
public IEnumerator CreateSessionFailure()
{
bool sawCreateSession = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnSessionCreate))
{
sawCreateSession = true;
}
return true;
};
MockRuntime.SetFunctionCallback("xrCreateSession", (name) => XrResult.RuntimeFailure);
base.InitializeAndStart();
yield return null;
Assert.IsFalse(sawCreateSession);
}
static XrResult[] beginSessionSuccessResults = new XrResult[]
{
XrResult.Success,
XrResult.LossPending
};
[UnityTest]
public IEnumerator BeginSessionSuccessWithValues([ValueSource("beginSessionSuccessResults")]
XrResult successResult)
{
var states = new List<XrSessionState>();
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnSessionStateChange))
{
var newState = (XrSessionState)((MockRuntime.XrSessionStateChangedParams)param).NewState;
states.Add(newState);
}
return true;
};
MockRuntime.SetFunctionCallback("xrBeginSession", (name) => successResult);
base.InitializeAndStart();
yield return null;
Assert.IsTrue(base.IsRunning<XRDisplaySubsystem>());
switch (successResult)
{
case XrResult.Success:
Assert.IsTrue(states.Contains(XrSessionState.Ready));
Assert.IsTrue(states.Contains(XrSessionState.Synchronized));
Assert.IsTrue(states.Contains(XrSessionState.Visible));
Assert.IsTrue(states.Contains(XrSessionState.Focused));
break;
case XrResult.LossPending:
Assert.IsTrue(states.Contains(XrSessionState.Ready));
Assert.IsFalse(states.Contains(XrSessionState.Synchronized));
Assert.IsFalse(states.Contains(XrSessionState.Visible));
Assert.IsFalse(states.Contains(XrSessionState.Focused));
break;
}
}
[UnityTest]
public IEnumerator BeginSessionFailure()
{
var states = new List<XrSessionState>();
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnSessionStateChange))
{
var newState = (XrSessionState)((MockRuntime.XrSessionStateChangedParams)param).NewState;
states.Add(newState);
}
return true;
};
MockRuntime.SetFunctionCallback("xrBeginSession", (name) => XrResult.RuntimeFailure);
InitializeAndStart();
yield return null;
Assert.IsTrue(base.IsRunning<XRDisplaySubsystem>());
Assert.IsTrue(states.Contains(XrSessionState.Ready));
Assert.IsFalse(states.Contains(XrSessionState.Synchronized));
Assert.IsFalse(states.Contains(XrSessionState.Visible));
Assert.IsFalse(states.Contains(XrSessionState.Focused));
}
#if false
// TEST WAS UNSTABLE, DISABLING FOR RELEASE ONLY
[UnityTest]
public IEnumerator TestRequestExitShutsdownSubsystems()
{
bool sawSessionDestroy = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnSessionDestroy))
{
sawSessionDestroy = true;
}
return true;
};
AddExtension(MockRuntime.XR_UNITY_mock_test);
base.InitializeAndStart();
yield return null;
Assert.IsTrue(base.IsRunning<XRDisplaySubsystem>());
var wait = new WaitForLoaderShutdown(Loader);
MockDriver.RequestExitSession(MockRuntime.Instance.XrSession);
yield return wait;
Assert.IsTrue(sawSessionDestroy);
}
#endif
[UnityTest]
public IEnumerator RequestExitSession()
{
InitializeAndStart();
// Wait for a single XrFrame to make sure OpenXR is up and running
yield return new WaitForXrFrame();
// Request the session exit which should shutdown OpenXR
MockRuntime.RequestExitSession();
// Wait for OpenXR to shutdown
yield return new WaitForLoaderShutdown();
}
[UnityTest]
public IEnumerator CauseInstanceLoss()
{
bool instanceLost = false;
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnInstanceLossPending))
{
instanceLost = true;
}
return true;
};
InitializeAndStart();
yield return null;
MockRuntime.CauseInstanceLoss();
yield return new WaitForLoaderShutdown();
Assert.IsTrue(instanceLost);
}
[UnityTest]
public IEnumerator DisplayTransparent()
{
AddExtension(MockRuntime.XR_UNITY_mock_test);
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
{
MockRuntime.ChooseEnvironmentBlendMode(XrEnvironmentBlendMode.Additive);
}
return true;
};
base.InitializeAndStart();
yield return null;
var displays = new List<XRDisplaySubsystem>();
SubsystemManager.GetSubsystems(displays);
Assert.AreEqual(false, displays[0].displayOpaque);
}
[UnityTest]
public IEnumerator DisplayOpaque()
{
base.InitializeAndStart();
yield return null;
var displays = new List<XRDisplaySubsystem>();
SubsystemManager.GetSubsystems(displays);
Assert.AreEqual(true, displays[0].displayOpaque);
}
[UnityTest]
public IEnumerator MultipleEnvironmentBlendModes()
{
//Mock available environmentBlendModes are Opaque and Additive in mock_runtime.cpp EnumerateEnvironmentBlendModes.
AddExtension(MockRuntime.XR_UNITY_mock_test);
base.InitializeAndStart();
yield return null;
//check default mode is Opaque.
Assert.AreEqual(XrEnvironmentBlendMode.Opaque, MockRuntime.GetXrEnvironmentBlendMode());
//Switch to another supported mode - Additive.
MockRuntime.ChooseEnvironmentBlendMode(XrEnvironmentBlendMode.Additive);
yield return new WaitForXrFrame(2);
Assert.AreEqual(XrEnvironmentBlendMode.Additive, MockRuntime.GetXrEnvironmentBlendMode());
//Set to unsupported mode - Alpha_blend
MockRuntime.ChooseEnvironmentBlendMode(XrEnvironmentBlendMode.AlphaBlend);
yield return new WaitForXrFrame(2);
Assert.AreNotEqual(XrEnvironmentBlendMode.AlphaBlend, MockRuntime.GetXrEnvironmentBlendMode());
}
[UnityTest]
public IEnumerator BoundaryPoints()
{
AddExtension(MockRuntime.XR_UNITY_mock_test);
base.InitializeAndStart();
yield return null;
Assert.IsTrue(base.IsRunning<XRInputSubsystem>(), "Input subsystem failed to properly start!");
MockRuntime.SetReferenceSpaceBounds(XrReferenceSpaceType.Stage, new Vector2 { x = 1.0f, y = 3.0f });
yield return null;
var input = Loader.GetLoadedSubsystem<XRInputSubsystem>();
Assert.That(() => input, Is.Not.Null);
input.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor);
yield return null;
var points = new List<Vector3>();
Assert.IsTrue(input.TryGetBoundaryPoints(points), "Failed to get boundary points!");
Assert.That(() => points.Count, Is.EqualTo(4), "Incorrect number of boundary points received!");
var comparer = new Vector3EqualityComparer(10e-6f);
Assert.That(points[0], Is.EqualTo(new Vector3 { x = -0.5f, y = 0.0f, z = -1.5f }).Using(comparer));
Assert.That(points[1], Is.EqualTo(new Vector3 { x = -0.5f, y = 0.0f, z = 1.5f }).Using(comparer));
Assert.That(points[2], Is.EqualTo(new Vector3 { x = 0.5f, y = 0.0f, z = 1.5f }).Using(comparer));
Assert.That(points[3], Is.EqualTo(new Vector3 { x = 0.5f, y = 0.0f, z = -1.5f }).Using(comparer));
}
[UnityTest]
public IEnumerator NoBoundaryPoints()
{
AddExtension(MockRuntime.XR_UNITY_mock_test);
base.InitializeAndStart();
yield return null;
Assert.IsTrue(base.IsRunning<XRInputSubsystem>(), "Input subsystem failed to properly start!");
var input = Loader.GetLoadedSubsystem<XRInputSubsystem>();
Assert.That(() => input, Is.Not.Null);
input.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor);
yield return null;
var points = new List<Vector3>();
input.TryGetBoundaryPoints(points);
Assert.That(() => points.Count, Is.EqualTo(0), "Incorrect number of boundary points received!");
}
[UnityTest]
public IEnumerator BoundryPointsForTrackingOrigin()
{
AddExtension(MockRuntime.XR_UNITY_mock_test);
base.InitializeAndStart();
yield return null;
Assert.IsTrue(base.IsRunning<XRInputSubsystem>(), "Input subsystem failed to properly start!");
MockRuntime.SetReferenceSpaceBounds(XrReferenceSpaceType.Stage, new Vector2 { x = 1.0f, y = 3.0f });
yield return null;
var input = Loader.GetLoadedSubsystem<XRInputSubsystem>();
Assert.That(() => input, Is.Not.Null);
input.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor);
yield return null;
var points = new List<Vector3>();
Assert.IsTrue(input.TryGetBoundaryPoints(points), "Failed to get boundary points!");
Assert.That(() => points.Count, Is.EqualTo(4), "Incorrect number of boundary points received!");
input.TrySetTrackingOriginMode(TrackingOriginModeFlags.Device);
yield return null;
points.Clear();
input.TryGetBoundaryPoints(points);
Assert.That(() => points.Count, Is.EqualTo(0), "Incorrect number of boundary points received!");
}
[UnityTest]
public IEnumerator BeforeFunctionCallbackSuccess()
{
var createInstanceCalled = false;
MockRuntime.SetFunctionCallback("xrCreateInstance", (name) =>
{
createInstanceCalled = true;
return XrResult.Success;
});
InitializeAndStart();
yield return new WaitForXrFrame(1);
Assert.IsTrue(createInstanceCalled, "xrCreateInstance callback was not called");
}
[Test]
public void BeforeFunctionCallbackError()
{
var createInstanceCalled = false;
MockRuntime.SetFunctionCallback("xrCreateInstance", (name) =>
{
createInstanceCalled = true;
return XrResult.RuntimeFailure;
});
LogAssert.ignoreFailingMessages = true;
InitializeAndStart();
LogAssert.ignoreFailingMessages = false;
Assert.IsTrue(OpenXRLoaderBase.Instance == null, "OpenXR instance should have failed to initialize");
Assert.IsTrue(createInstanceCalled, "xrCreateInstance callback was not called");
}
[UnityTest]
public IEnumerator AfterFunctionCallback()
{
var createInstanceCalled = false;
var createInstanceSuccess = false;
MockRuntime.SetFunctionCallback("xrCreateInstance", (name, result) =>
{
createInstanceCalled = true;
createInstanceSuccess = result == XrResult.Success;
});
InitializeAndStart();
yield return new WaitForXrFrame(1);
Assert.IsTrue(createInstanceCalled, "xrCreateInstance callback was not called");
Assert.IsTrue(createInstanceSuccess, "xrCreateInstance result was not XR_SUCCESS");
}
[UnityTest]
public IEnumerator CallbacksClearedOnLoaderShutdown()
{
MockRuntime.SetFunctionCallback("xrBeginSession", (func) => XrResult.Success);
InitializeAndStart();
yield return new WaitForXrFrame(1);
StopAndShutdown();
Assert.IsTrue(OpenXRLoader.Instance == null, "OpenXR should not be running");
Assert.IsNull(MockRuntime.GetBeforeFunctionCallback("xrBeginSession"), "Callback should have been cleared when loader shut down");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3b727f98ed496a3428842314282a4638
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,70 @@
using NUnit.Framework;
using UnityEngine.XR.OpenXR.Input;
using System.Text;
using UnityEngine.TestTools;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class NativeTests : OpenXRLoaderSetup
{
public override void BeforeTest()
{
OpenXRLoaderBase.Internal_UnloadOpenXRLibrary();
base.BeforeTest();
}
[Test]
public void OpenXRLoader_LoadOpenXRLibrary_NullLoaderPath()
{
Assert.IsFalse(OpenXRLoaderBase.Internal_LoadOpenXRLibrary(null));
}
[Test]
public void OpenXRLoader_LoadOpenXRLibrary_InvalidLoaderPath()
{
Assert.IsFalse(OpenXRLoaderBase.Internal_LoadOpenXRLibrary(OpenXRLoaderBase.StringToWCHAR_T("abababab")));
}
[Test]
public void OpenXRLoader_InitializeSession_BeforeLoadingLibrary()
{
Assert.IsFalse(OpenXRLoaderBase.Internal_InitializeSession());
}
[Test]
public void OpenXRLoader_CreateSessionIfNeeded_BeforeLoadingLibrary()
{
Assert.IsFalse(OpenXRLoaderBase.Internal_CreateSessionIfNeeded());
}
[Test]
public void OpenXRLoader_RequestEnableExtensionString_BeforeLoadingLibrary()
{
Assert.IsFalse(OpenXRLoaderBase.Internal_RequestEnableExtensionString(null));
}
[Test]
public void OpenXRLoader_RequestEnableExtensionString_Null()
{
Assert.IsFalse(OpenXRLoaderBase.Internal_RequestEnableExtensionString("some_extension"));
}
[Test]
public void OpenXRInput_TryGetInputSourceName_BeforeInitializing()
{
Assert.IsFalse(OpenXRInput.Internal_TryGetInputSourceName(0, 0, 0, 0, out var name));
}
[Test]
public void OpenXRInput_SuggestBindings_BeforeInitializing()
{
Assert.IsFalse(OpenXRInput.Internal_SuggestBindings("", null, 0));
}
[Test]
public void OpenXRInput_AttachActionSets_BeforeInitializing()
{
Assert.IsFalse(OpenXRInput.Internal_AttachActionSets());
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54113394d100b63429b665f2fa220a13
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine.XR.Management;
using UnityEngine.TestTools;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class NoRuntimeTests : OpenXRLoaderSetup
{
private XRManagerSettings manager => XRGeneralSettings.Instance?.Manager ?? null;
private XRLoader activeLoader => manager?.activeLoader ?? null;
public override void BeforeTest()
{
base.BeforeTest();
Environment.SetEnvironmentVariable("XR_RUNTIME_JSON", "asdf.json");
EnableMockRuntime(false);
Loader.DisableValidationChecksOnEnteringPlaymode = true;
}
public override void AfterTest()
{
if (Loader != null)
Loader.DisableValidationChecksOnEnteringPlaymode = false;
Environment.SetEnvironmentVariable("XR_RUNTIME_JSON", "");
base.AfterTest();
}
[UnityTest]
[Category("Loader Tests")]
[UnityPlatform(include = new[] { RuntimePlatform.WindowsEditor })] // we can't run these tests on player because only the mock loader is included - this needs the khronos loader
public IEnumerator NoInitNoCrash()
{
base.InitializeAndStart();
yield return null;
Assert.IsNull(activeLoader);
}
[UnityTest]
[Category("Loader Tests")]
[UnityPlatform(include = new[] { RuntimePlatform.WindowsEditor })]
public IEnumerator LoadRuntimeAfterNoRuntime()
{
base.InitializeAndStart();
yield return null;
Assert.IsNull(activeLoader);
#if !OPENXR_USE_KHRONOS_LOADER
Environment.SetEnvironmentVariable("XR_RUNTIME_JSON", "");
EnableMockRuntime();
base.InitializeAndStart();
yield return null;
Assert.IsNotNull(activeLoader);
Assert.AreEqual(OpenXRRuntime.name, "Unity Mock Runtime");
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6040e3f8adec12540a375e422f163de9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine.TestTools;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Mock;
namespace UnityEngine.XR.OpenXR.Tests
{
class OpenXRFeatureTests : OpenXRLoaderSetup
{
private class FakeFeature : OpenXRFeature
{
}
[Test]
public void HighPriority()
{
MockRuntime.Instance.priority = Int32.MaxValue;
base.InitializeAndStart();
Assert.IsTrue(OpenXRSettings.Instance.features[0] == MockRuntime.Instance);
}
[Test]
public void LowPriority()
{
MockRuntime.Instance.priority = Int32.MinValue;
base.InitializeAndStart();
Assert.IsTrue(OpenXRSettings.Instance.features[OpenXRSettings.Instance.features.Length - 1] == MockRuntime.Instance);
}
[Test]
public void ChangeEnabledAtRuntime()
{
base.InitializeAndStart();
MockRuntime.Instance.enabled = false;
LogAssert.Expect(LogType.Error, "OpenXRFeature.enabled cannot be changed while OpenXR is running");
}
[Test]
public void FeatureFailedInitialization()
{
bool enableStatus = true;
//Force OnInstanceCreate returning false so that failedInitialization is true.
MockRuntime.Instance.TestCallback = (methodName, param) =>
{
if (methodName == nameof(OpenXRFeature.OnInstanceCreate))
return false;
return true;
};
base.InitializeAndStart();
enableStatus = MockRuntime.Instance.enabled;
MockRuntime.Instance.enabled = enableStatus;
Assert.IsTrue(MockRuntime.Instance.enabled == enableStatus);
}
[Test]
public void GetFeatureCount()
{
Assert.IsTrue(OpenXRSettings.Instance.featureCount == OpenXRSettings.Instance.GetFeatures(typeof(OpenXRFeature)).Length);
}
[Test]
public void GetFeatureByTypeBadType()
{
Assert.IsNull(OpenXRSettings.Instance.GetFeature(typeof(OpenXRLoader)));
}
[Test]
public void GetFeatureByTypeNotFound()
{
Assert.IsNull(OpenXRSettings.Instance.GetFeature<FakeFeature>());
}
[Test]
public void GetFeatureByType()
{
var feature = OpenXRSettings.Instance.GetFeature<MockRuntime>();
Assert.IsNotNull(feature);
Assert.IsTrue(feature is MockRuntime);
Assert.IsNotNull(OpenXRSettings.Instance.GetFeature(typeof(MockRuntime)) as MockRuntime);
}
[Test]
public void GetFeaturesByTypeArray()
{
var features = OpenXRSettings.Instance.GetFeatures<MockRuntime>();
Assert.IsNotNull(features);
Assert.IsTrue(features.Length == 1);
Assert.IsTrue(features[0] is MockRuntime);
features = OpenXRSettings.Instance.GetFeatures(typeof(MockRuntime));
Assert.IsNotNull(features);
Assert.IsTrue(features.Length == 1);
Assert.IsTrue(features[0] is MockRuntime);
}
[Test]
public void GetFeaturesByGenericList()
{
var features = new List<MockRuntime>();
Assert.IsTrue(OpenXRSettings.Instance.GetFeatures(features) == 1);
Assert.IsNotNull(features[0]);
}
[Test]
public void GetFeaturesByTypeList()
{
var features = new List<OpenXRFeature>();
Assert.IsTrue(OpenXRSettings.Instance.GetFeatures(typeof(MockRuntime), features) == 1);
Assert.IsNotNull(features[0]);
Assert.IsTrue(features[0] is MockRuntime);
}
[Test]
public void GetFeaturesArray()
{
var features = OpenXRSettings.Instance.GetFeatures();
Assert.IsNotNull(features);
Assert.IsTrue(features.Length == OpenXRSettings.Instance.featureCount);
}
[Test]
public void GetFeaturesList()
{
var features = new List<OpenXRFeature>();
Assert.IsTrue(OpenXRSettings.Instance.GetFeatures(features) == OpenXRSettings.Instance.featureCount);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9050a5edea0d4cf19f270d6c4ebac796
timeCreated: 1604700422

View File

@@ -0,0 +1,737 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.XR;
using UnityEngine.TestTools;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
using UnityEngine.XR.OpenXR.Features.ConformanceAutomation;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.XR.OpenXR.Input;
using UnityEngine.XR.OpenXR.NativeTypes;
#if USE_INPUT_SYSTEM_POSE_CONTROL
using PoseStruct = UnityEngine.InputSystem.XR.PoseState;
#else
using PoseStruct = UnityEngine.XR.OpenXR.Input.Pose;
#endif
namespace UnityEngine.XR.OpenXR.Tests
{
internal class OpenXRInputTestsBase : OpenXRLoaderSetup
{
private static readonly List<XRNodeState> s_NodeStates = new List<XRNodeState>();
protected static bool IsNodeTracked(XRNode node)
{
s_NodeStates.Clear();
InputTracking.GetNodeStates(s_NodeStates);
return s_NodeStates.Where(s => s.nodeType == node).Select(s => s.tracked).FirstOrDefault();
}
/// <summary>
/// List of all known interaction features and their associated devices for testing
/// </summary>
protected static readonly (Type featureType, Type layoutType, string layoutNameOverride)[] s_InteractionFeatureLayouts = {
(typeof(OculusTouchControllerProfile), typeof(OculusTouchControllerProfile.OculusTouchController), null),
(typeof(EyeGazeInteraction), typeof(EyeGazeInteraction.EyeGazeDevice), "EyeGaze"),
(typeof(MicrosoftHandInteraction), typeof(MicrosoftHandInteraction.HoloLensHand), null),
(typeof(KHRSimpleControllerProfile), typeof(KHRSimpleControllerProfile.KHRSimpleController), null),
(typeof(HandInteractionProfile), typeof(HandInteractionProfile.HandInteraction), null),
(typeof(MetaQuestTouchProControllerProfile), typeof(MetaQuestTouchProControllerProfile.QuestProTouchController), null),
(typeof(MetaQuestTouchPlusControllerProfile), typeof(MetaQuestTouchPlusControllerProfile.QuestTouchPlusController), null),
#if !UNITY_ANDROID
(typeof(HTCViveControllerProfile), typeof(HTCViveControllerProfile.ViveController), null),
(typeof(HPReverbG2ControllerProfile), typeof(HPReverbG2ControllerProfile.ReverbG2Controller), null),
(typeof(MicrosoftMotionControllerProfile), typeof(MicrosoftMotionControllerProfile.WMRSpatialController), null),
(typeof(ValveIndexControllerProfile), typeof(ValveIndexControllerProfile.ValveIndexController), null)
#endif
};
/// <summary>
/// List of interaction features that should not be tested.
/// </summary>
protected static readonly Type[] s_IgnoreInteractionFeatures = {
typeof(MockInteractionFeature),
typeof(HandCommonPosesInteraction),
typeof(DPadInteraction),
typeof(PalmPoseInteraction)
};
/// <summary>
/// Return true if the given layout is registered.
/// </summary>
/// <param name="layoutName">Name of the layout</param>
/// <returns>True if the layout is registered with the input system</returns>
protected static bool IsLayoutRegistered(string layoutName)
{
// Force an input system update first to make sure all registrations are committed.
InputSystem.InputSystem.Update();
try
{
return InputSystem.InputSystem.LoadLayout(layoutName) != null;
}
catch (Exception)
{
return false;
}
}
}
internal class OpenXRInputTests : OpenXRInputTestsBase
{
protected override void QueryBuildFeatures(List<Type> featureTypes)
{
base.QueryBuildFeatures(featureTypes);
featureTypes.AddRange(s_InteractionFeatureLayouts.Select(i => i.featureType));
featureTypes.Add(typeof(MockInteractionFeature));
featureTypes.Add(typeof(ConformanceAutomationFeature));
}
/// <summary>
/// Tests whether or not the device layout for an interaction feature is registered at runtime
/// </summary>
[UnityTest]
public IEnumerator DeviceLayoutRegistration([ValueSource(nameof(s_InteractionFeatureLayouts))] (Type featureType, Type layoutType, string layoutNameOverride) interactionFeature)
{
var layoutName = interactionFeature.layoutNameOverride ?? interactionFeature.layoutType.Name;
// Make sure the layout is not registered as it would give the test a false positive
InputSystem.InputSystem.RemoveLayout(layoutName);
Assert.IsFalse(IsLayoutRegistered(layoutName), "Layout is still registered, test will give a false positive");
// Starting OpenXR should register all layouts from interaction features. Make sure that the
// layout is registered after starting.
EnableFeature(interactionFeature.featureType);
InitializeAndStart();
yield return new WaitForXrFrame(2);
Assert.IsTrue(IsLayoutRegistered(layoutName), "Layout was not registered during Initialization");
}
/// <summary>
/// Validate that data flows through the given OpenXR interaction path to the give action.
/// </summary>
/// <param name="inputAction">Input action that should receive the data</param>
/// <param name="userPath">OpenXR User Path</param>
/// <param name="interactionPath">OpenXR interaction path</param>
/// <param name="value">Value to verify</param>
/// <returns></returns>
private static IEnumerator ValidateInputAction(InputAction inputAction, string userPath, string interactionPath, bool value)
{
ConformanceAutomationFeature.ConformanceAutomationSetBool(userPath, interactionPath, value);
yield return new WaitForXrFrame(2);
var actualValue = inputAction.ReadValue<float>() > 0.0f;
Assert.IsTrue(actualValue == value, $"Expected '{value}' but received '{actualValue}' from '{inputAction}' bound to '{interactionPath}'");
}
/// <summary>
/// Validate that data flows through the given OpenXR interaction path to the give action.
/// </summary>
/// <param name="inputAction">Input action that should receive the data</param>
/// <param name="userPath">OpenXR User Path</param>
/// <param name="interactionPath">OpenXR interaction path</param>
/// <param name="value">Value to verify</param>
/// <returns></returns>
private static IEnumerator ValidateInputAction(InputAction inputAction, string userPath, string interactionPath, float value)
{
ConformanceAutomationFeature.ConformanceAutomationSetFloat(userPath, interactionPath, value);
yield return new WaitForXrFrame(2);
var actualValue = inputAction.ReadValue<float>();
Assert.IsTrue(actualValue >= value - float.Epsilon && actualValue <= value + float.Epsilon, $"Expected '{value}' but received '{actualValue}' from '{inputAction}' bound to '{interactionPath}'");
}
/// <summary>
/// Validate that data flows through the given OpenXR interaction path to the give action.
/// </summary>
/// <param name="inputAction">Input action that should receive the data</param>
/// <param name="userPath">OpenXR User Path</param>
/// <param name="interactionPath">OpenXR interaction path</param>
/// <param name="value">Value to verify</param>
/// <returns></returns>
private static IEnumerator ValidateInputAction(InputAction inputAction, string userPath, string interactionPath, Vector2 value)
{
ConformanceAutomationFeature.ConformanceAutomationSetVec2(userPath, interactionPath, value);
yield return new WaitForXrFrame(2);
var actualValue = inputAction.ReadValue<Vector2>();
Assert.IsTrue(
actualValue.x >= value.x - float.Epsilon && actualValue.x <= value.x + float.Epsilon &&
actualValue.y >= value.y - float.Epsilon && actualValue.y <= value.y + float.Epsilon,
$"Expected '{value}' but received '{actualValue}' from '{inputAction}' bound to '{interactionPath}'"
);
}
/// <summary>
/// Validate that data flows through the given OpenXR interaction path to the give action.
/// </summary>
/// <param name="inputAction">Input action that should receive the data</param>
/// <param name="userPath">OpenXR User Path</param>
/// <param name="interactionPath">OpenXR interaction path</param>
/// <param name="expected">Value to verify</param>
/// <returns></returns>
private static IEnumerator ValidateInputAction(InputAction inputAction, string userPath, string interactionPath, PoseStruct expected)
{
ConformanceAutomationFeature.ConformanceAutomationSetPose(userPath, interactionPath, expected.position, expected.rotation);
ConformanceAutomationFeature.ConformanceAutomationSetVelocity(
userPath,
interactionPath,
((expected.trackingState & InputTrackingState.Velocity) == InputTrackingState.Velocity),
expected.velocity,
((expected.trackingState & InputTrackingState.AngularVelocity) == InputTrackingState.AngularVelocity),
expected.angularVelocity);
ConformanceAutomationFeature.ConformanceAutomationSetActive(null, userPath, expected.isTracked);
yield return new WaitForXrFrame(2);
switch (inputAction.expectedControlType)
{
case "Vector3":
{
var received = inputAction.ReadValue<Vector3>();
Assert.IsTrue(received == expected.position, $"Action '{inputAction.bindings[0].path}' bound to '{interactionPath}' expected '{expected.position} but received '{received}'");
break;
}
case "Quaternion":
{
var received = inputAction.ReadValue<Quaternion>();
Assert.IsTrue(received == expected.rotation, $"Action '{inputAction.bindings[0].path}' bound to '{interactionPath}' expected '{expected.rotation}' but received '{received}'");
break;
}
case "Button":
{
var received = inputAction.ReadValue<float>() > 0.0f;
Assert.IsTrue(received == expected.isTracked, $"Action '{inputAction.bindings[0].path}' bound to '{interactionPath}' expected '{expected.isTracked}' but received '{received}'");
break;
}
case "Integer":
{
var received = inputAction.ReadValue<int>();
Assert.IsTrue(received == (int)expected.trackingState, $"Action '{inputAction.bindings[0].path}' bound to '{interactionPath}' expected '{expected.trackingState}' but received '{(InputTrackingState)received}'");
break;
}
case "Pose":
{
var received = inputAction.ReadValue<PoseStruct>();
Assert.IsTrue(received.isTracked == expected.isTracked, $"Action '{inputAction.bindings[0].path}/isTracked' bound to '{interactionPath}' expected '{expected.isTracked}' but received '{received.isTracked}'");
Assert.IsTrue(received.trackingState == expected.trackingState, $"Action '{inputAction.bindings[0].path}/trackingState' bound to '{interactionPath}' expected '{expected.trackingState}' but received '{received.trackingState}'");
if (received.isTracked)
{
Assert.IsTrue(received.position == expected.position, $"Action '{inputAction.bindings[0].path}/position' bound to '{interactionPath}' expected '{expected.position}' but received '{received.position}'");
Assert.IsTrue(received.rotation == expected.rotation, $"Action '{inputAction.bindings[0].path}/rotation' bound to '{interactionPath}' expected '{expected.rotation}' but received '{received.rotation}'");
if ((received.trackingState & InputTrackingState.Velocity) == InputTrackingState.Velocity)
Assert.IsTrue(received.velocity == expected.velocity, $"Action '{inputAction.bindings[0].path}/position' bound to '{interactionPath}' expected '{expected.velocity}' but received '{received.velocity}'");
if ((received.trackingState & InputTrackingState.AngularVelocity) == InputTrackingState.AngularVelocity)
Assert.IsTrue(received.angularVelocity == expected.angularVelocity, $"Action '{inputAction.bindings[0].path}/position' bound to '{interactionPath}' expected '{expected.angularVelocity}' but received '{received.angularVelocity}'");
}
break;
}
}
yield return null;
}
/// <summary>
/// Validate that the haptic associated with an input action fires
/// </summary>
/// <param name="inputAction">Input action</param>
/// <param name="amplitude">Amplitude for haptic</param>
/// <param name="duration">Duration for haptic</param>
/// <param name="inputDevice">Device to filter with</param>
private static IEnumerator ValidateHaptic(InputAction inputAction, float amplitude, float duration, InputSystem.InputDevice inputDevice = null)
{
var hapticImpulseCount = 0;
var hapticStopCount = 0;
void OnHapticOutput(MockRuntime.ScriptEvent evt, ulong param)
{
hapticImpulseCount += evt == MockRuntime.ScriptEvent.HapticImpulse ? 1 : 0;
hapticStopCount += evt == MockRuntime.ScriptEvent.HapticStop ? 1 : 0;
}
MockRuntime.onScriptEvent += OnHapticOutput;
if (null == inputAction)
{
Assert.IsNotNull(inputDevice);
if (inputDevice is XRControllerWithRumble rumble)
rumble.SendImpulse(amplitude, duration);
}
else
OpenXRInput.SendHapticImpulse(inputAction, amplitude, duration, inputDevice);
// Give some time for the haptic event to make its way to our callback
yield return new WaitForXrFrame(2);
if (null != inputAction)
OpenXRInput.StopHaptics(inputAction, inputDevice);
yield return new WaitForXrFrame(2);
MockRuntime.onScriptEvent -= OnHapticOutput;
Assert.IsTrue(hapticImpulseCount == 1, null == inputAction ?
$"Haptic impulse failed for XRControllerWithRumble '{inputDevice.name}" :
$"Haptic impulse failed for action '{inputAction}'");
Assert.IsTrue(inputAction == null || hapticStopCount == 1, $"Haptic stop failed for action '{inputAction}'");
}
/// <summary>
/// Validate that data flows from OpenXR to the InputSystem through the given OpenXR interaction path to
/// the given input ControlItem
/// </summary>
/// <param name="localizedActionMapName">Device layout name to validate</param>
/// <param name="control">Control within the device layout to validate</param>
/// <param name="userPath">OpenXR User path to bind to</param>
/// <param name="interactionPath">OpenXR interaction path to bind to</param>
/// <param name="controlLayoutOverride">Optional override for the control layout</param>
/// <param name="usageOverride">Optional usage override for the binding</param>
/// <returns></returns>
private static IEnumerator ValidateLayoutControl(InputControlLayout layout, InputControlLayout.ControlItem control, string userPath, string interactionPath, string controlLayoutOverride = null, string usageOverride = null)
{
// Convert the user path to a usage to limit the bound action
var usage = userPath switch
{
"/user/hand/left" => "{LeftHand}",
"/user/hand/right" => "{RightHand}",
_ => ""
};
// Create an action bound to the control
var action = new InputAction(
null,
InputActionType.Value,
$"<{layout.name}>{usage}/{(usageOverride != null ? $"{{{usageOverride}}}" : control.name)}",
null,
null,
control.layout);
action.Enable();
// Make sure the input system updates and wait a frame to ensure the action is properly bound before testing with it
InputSystem.InputSystem.Update();
yield return new WaitForXrFrame(1);
// Use the usage to find the device for the action
var inputDevice = !string.IsNullOrEmpty(usage) ?
InputSystem.InputSystem.GetDevice<InputSystem.InputDevice>(usage.Substring(1, usage.Length - 2)) :
null;
// Check input TryGetInputSourceName
Assert.IsTrue(
OpenXRInput.TryGetInputSourceName(action, 0, out var actionName, OpenXRInput.InputSourceNameFlags.All, inputDevice),
$"Failed to retrieve input source for action '{action}'.");
Assert.IsNotEmpty(actionName, $"Input source name for action '{action}' should not be empty");
switch (controlLayoutOverride ?? control.layout)
{
case "Button":
{
yield return ValidateInputAction(action, userPath, interactionPath, true);
yield return ValidateInputAction(action, userPath, interactionPath, false);
break;
}
case "Axis":
{
yield return ValidateInputAction(action, userPath, interactionPath, 1.0f);
yield return ValidateInputAction(action, userPath, interactionPath, 0.0f);
// TODO: Disabled this because the Microsoft Motion Controller and the HTC Vive controller specify Axis1D controls that are not actually 1DAxis controls
//yield return ValidateInputAction(action, userPath, interactionPath, 0.5f);
break;
}
case "Stick":
case "Vector2":
{
yield return ValidateInputAction(action, userPath, interactionPath, Vector2.one);
yield return ValidateInputAction(action, userPath, interactionPath, Vector2.zero);
yield return ValidateInputAction(action, userPath, interactionPath, new Vector2(1.0f, 0.0f));
yield return ValidateInputAction(action, userPath, interactionPath, new Vector2(0.0f, 1.0f));
break;
}
case "Pose":
{
yield return ValidateInputAction(action, userPath, interactionPath, new PoseStruct
{
position = Vector3.one,
rotation = Quaternion.identity,
isTracked = true,
trackingState = InputTrackingState.Position | InputTrackingState.Rotation
});
yield return ValidateInputAction(action, userPath, interactionPath, new PoseStruct
{
position = Vector3.zero,
rotation = Quaternion.identity,
isTracked = false,
trackingState = InputTrackingState.None
});
yield return ValidateInputAction(action, userPath, interactionPath, new PoseStruct
{
position = Vector3.zero,
rotation = Quaternion.Euler(90, 0, 0),
isTracked = true,
trackingState = InputTrackingState.Position | InputTrackingState.Rotation
});
// Velocity only
yield return ValidateInputAction(action, userPath, interactionPath, new PoseStruct
{
position = Vector3.zero,
rotation = Quaternion.identity,
isTracked = true,
trackingState = InputTrackingState.Position | InputTrackingState.Rotation | InputTrackingState.Velocity,
velocity = new Vector3(1, 2, 3)
});
// AngularVelocity only
yield return ValidateInputAction(action, userPath, interactionPath, new PoseStruct
{
position = Vector3.zero,
rotation = Quaternion.Euler(90, 0, 0),
isTracked = true,
trackingState = InputTrackingState.Position | InputTrackingState.Rotation | InputTrackingState.AngularVelocity,
angularVelocity = new Vector3(1, 2, 3)
});
// Velocity and AngularVelocity
yield return ValidateInputAction(action, userPath, interactionPath, new PoseStruct
{
position = Vector3.zero,
rotation = Quaternion.Euler(90, 0, 0),
isTracked = true,
trackingState = InputTrackingState.Position | InputTrackingState.Rotation | InputTrackingState.Velocity | InputTrackingState.AngularVelocity,
velocity = new Vector3(1, 2, 3),
angularVelocity = new Vector3(3, 2, 1)
});
break;
}
case "Haptic":
{
// Validate haptics through the action
yield return ValidateHaptic(action, 1.0f, 1.0f, inputDevice);
// Validate haptics through a rumble controller
if (inputDevice is XRControllerWithRumble)
yield return ValidateHaptic(null, 1.0f, 1.0f, inputDevice);
break;
}
default:
Assert.Fail($"Unknown control type `{control.layout}`");
break;
}
}
/// <summary>
/// Tests all controls of all interaction features to ensure data flows through properly.
/// </summary>
[UnityTest]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // These tests time out on 2022+ on the Mac Editor CI machines
public IEnumerator ValidateControls([ValueSource(nameof(s_InteractionFeatureLayouts))] (Type featureType, Type layoutType, string layoutNameOverride) interactionFeature)
{
// Enable the needed features
EnableMockRuntime();
EnableFeature<ConformanceAutomationFeature>();
var feature = EnableFeature(interactionFeature.featureType) as OpenXRInteractionFeature;
// Make sure all the devices are registered with the input system
InputSystem.InputSystem.Update();
var actionMaps = new List<OpenXRInteractionFeature.ActionMapConfig>();
feature.CreateActionMaps(actionMaps);
base.InitializeAndStart();
yield return new WaitForXrFrame(2);
var layoutName = interactionFeature.layoutNameOverride ?? interactionFeature.layoutType.Name;
var layout = InputSystem.InputSystem.LoadLayout(layoutName);
Assert.IsNotNull(layout, $"Missing layout '{layoutName}'");
// Get list of all known user paths supported by this action map
var userPaths = actionMaps.SelectMany(m => m.deviceInfos.Select(d => d.userPath)).Distinct().ToList();
var actionMapCoverage = new HashSet<OpenXRInteractionFeature.ActionConfig>();
foreach (var control in layout.controls)
{
// Find the ActionConfig that matches the given control name
var actionConfigs = actionMaps.SelectMany(m => m.actions).Where(a => a.name == control.name).ToArray();
// Control should not be specified in more than one action map config. If there is a future reason for this then this
// test will need to be extended to accomodate that.
Assert.IsTrue(actionConfigs.Length < 2, $"Control '{control.name}' with type '{control.layout}' is specified in more than one ActionConfig");
var actionConfig = actionConfigs.Length == 1 ? actionConfigs[0] : null;
// Controls with offsets that are not-zero should not be in the action config as they are "virtual" controls.
if (control.offset != uint.MaxValue && control.offset != 0)
{
// Any controls with offsets should not be in the ActionConfig
Assert.IsNull(actionConfig, $"Control '{control.name}' with type '{control.layout}' has offset and should not be included in the ActionMapConfig");
foreach (var userPath in userPaths)
{
switch (control.name)
{
case "isTracked":
case "trackingState":
case "devicePosition":
case "deviceRotation":
yield return ValidateLayoutControl(layout, control, userPath, $"{userPath}/input/grip/pose", "Pose");
break;
case "pointerPosition":
case "pointerRotation":
yield return ValidateLayoutControl(layout, control, userPath, $"{userPath}/input/aim/pose", "Pose");
break;
default:
break;
}
}
continue;
}
// Control must be in the action map config if it does not have a non-zero offset
Assert.IsNotNull(actionConfig, $"Control '{control.name}' with type '{control.layout}' is missing from the ActionMapConfig");
Assert.IsTrue(
actionConfig.usages.Count == control.usages.Count &&
actionConfig.usages.Intersect(control.usages.Select(u => u.ToString())).Count() == actionConfig.usages.Count,
$"ActionConfig usage list for control `{control.name}` does not match ControlItem usage list");
actionMapCoverage.Add(actionConfig);
foreach (var binding in actionConfig.bindings)
foreach (var userPath in (binding.userPaths ?? userPaths))
{
yield return ValidateLayoutControl(layout, control, userPath, $"{userPath}{binding.interactionPath}");
// Ensure the usages all map correctly to the data as well
foreach (var usage in actionConfig.usages)
{
yield return ValidateLayoutControl(layout, control, userPath, $"{userPath}{binding.interactionPath}", null, usage);
}
}
}
// Make sure that there are no action maps that reference controls that were not paired up
foreach (var actionConfig in actionMaps.SelectMany(m => m.actions))
{
Assert.IsTrue(actionMapCoverage.Contains(actionConfig), $"Action config '{actionConfig.name}' does not have a matching control in the parent layout");
}
}
private static readonly Regex k_ErrorNoDevices = new Regex("ActionMapConfig contains no `deviceInfos`.*");
private static readonly Regex k_ErrorInvalidDeviceName = new Regex(@".*Invalid device name.*");
private static readonly Regex k_ErrorInvalidInteractionProfile = new Regex(@".*Invalid interaction profile.*");
private static readonly Regex k_ErrorInvalidUserPath = new Regex(@".*Invalid user path.*");
private static readonly Regex k_ErrorInvalidUsage = new Regex(@".*Invalid Usage.*");
private static readonly Regex k_ErrorInvalidActionSetName = new Regex(@".*Invalid ActionSet name.*");
private static readonly Regex k_ErrorInvalidActionType = new Regex(@".*Invalid action type \'\d*' for action '.*'");
private static readonly (Action<OpenXRInteractionFeature.ActionMapConfig> filter, Regex expectLog, Regex expectReport)[] s_ActionMapTests =
{
// One or more device infos must be specified
((c) => c.deviceInfos = null, k_ErrorNoDevices, null),
((c) => c.deviceInfos = new List<OpenXRInteractionFeature.DeviceConfig>(), k_ErrorNoDevices, null),
// Desired interaction profile must be specified and be a valid path
((c) => c.desiredInteractionProfile = "", k_ErrorInvalidInteractionProfile, k_ErrorInvalidInteractionProfile),
((c) => c.desiredInteractionProfile = "bad", k_ErrorInvalidInteractionProfile, k_ErrorInvalidInteractionProfile),
((c) => c.desiredInteractionProfile = new String('a', 500), k_ErrorInvalidInteractionProfile, k_ErrorInvalidInteractionProfile),
// Device user path must be specified and be a valid path
((c) => c.deviceInfos[0].userPath = null, k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
((c) => c.deviceInfos[0].userPath = "", k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
((c) => c.deviceInfos[0].userPath = "bad", k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
((c) => c.deviceInfos[0].userPath = "/user/" + new String('a', 500), k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
// Name must be valid
((c) => c.name = null, k_ErrorInvalidActionSetName, k_ErrorInvalidActionSetName),
((c) => c.name = "", k_ErrorInvalidActionSetName, k_ErrorInvalidActionSetName),
((c) => c.name = new String('a', 500), k_ErrorInvalidActionSetName, k_ErrorInvalidActionSetName),
// Localized name
((c) => c.localizedName = null, k_ErrorInvalidDeviceName, k_ErrorInvalidDeviceName),
((c) => c.localizedName = "", k_ErrorInvalidDeviceName, k_ErrorInvalidDeviceName),
((c) => c.localizedName = new String('a', 500), k_ErrorInvalidDeviceName, k_ErrorInvalidDeviceName),
// Manufacturer or serial number should be allowed to be null or empty
((c) => c.manufacturer = "", null, null),
((c) => c.manufacturer = null, null, null),
((c) => c.serialNumber = "", null, null),
((c) => c.serialNumber = null, null, null),
// Invalid action type
((c) => c.actions[0].type = (OpenXRInteractionFeature.ActionType)100, k_ErrorInvalidActionType, k_ErrorInvalidActionType),
// Action Usages
((c) => c.actions[0].usages = new List<string> {""}, k_ErrorInvalidUsage, k_ErrorInvalidUsage),
((c) => c.actions[0].usages = new List<string> {null}, k_ErrorInvalidUsage, k_ErrorInvalidUsage),
((c) => c.actions[0].usages = new List<string> {new string('a', 500)}, k_ErrorInvalidUsage, k_ErrorInvalidUsage),
// Invalid user path on binding
((c) => c.actions[0].bindings[0].userPaths = new List<string> {"bad", "bad"}, k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
((c) => c.actions[0].bindings[0].userPaths = new List<string> {null, null}, k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
((c) => c.actions[0].bindings[0].userPaths = new List<string> {"/" + new string('a', 500)}, k_ErrorInvalidUserPath, k_ErrorInvalidUserPath),
// Invalid interaction profile on bindings
((c) => c.actions[0].bindings[0].interactionProfileName = null, null, null),
((c) => c.actions[0].bindings[0].interactionProfileName = "", k_ErrorInvalidInteractionProfile, k_ErrorInvalidInteractionProfile),
((c) => c.actions[0].bindings[0].interactionProfileName = "/" + new string('a', 500), k_ErrorInvalidInteractionProfile, k_ErrorInvalidInteractionProfile),
((c) => c.actions[0].bindings[0].interactionProfileName = "bad", k_ErrorInvalidInteractionProfile, k_ErrorInvalidInteractionProfile),
};
[UnityTest]
public IEnumerator ValidateActionMapConfig([ValueSource(nameof(s_ActionMapTests))] (Action<OpenXRInteractionFeature.ActionMapConfig> filter, Regex expectLog, Regex expectReport) test)
{
var feature = EnableFeature<MockInteractionFeature>();
// Set an action map config for the feature that has a bad interaction profile
var actionMapConfig = feature.CreateDefaultActionMapConfig();
test.filter(actionMapConfig);
feature.actionMapConfig = actionMapConfig;
InitializeAndStart();
yield return new WaitForXrFrame(2);
if (test.expectLog != null)
LogAssert.Expect(LogType.Error, test.expectLog);
if (test.expectReport != null)
Assert.IsTrue(DoesDiagnosticReportContain(test.expectReport), "Missing report entry");
}
/// <summary>
/// Defines a list of OpenXR API methods to test failure with
/// </summary>
private static readonly (string function, XrResult result, Regex expectLog)[] s_RuntimeFailureTests =
{
("xrSuggestInteractionProfileBindings", XrResult.FeatureUnsupported, new Regex(@".*Failed to suggest bindings for interaction profile.*XR_ERROR_FEATURE_UNSUPPORTED.*")),
("xrCreateActionSet", XrResult.FeatureUnsupported, new Regex(@".*Failed to create ActionSet.*XR_ERROR_FEATURE_UNSUPPORTED.*")),
("xrCreateAction", XrResult.FeatureUnsupported, new Regex(@".*Failed to create Action.*XR_ERROR_FEATURE_UNSUPPORTED.*")),
("xrAttachSessionActionSets", XrResult.FeatureUnsupported, new Regex(@".*Failed to attach ActionSets.*XR_ERROR_FEATURE_UNSUPPORTED.*")),
};
/// <summary>
/// Test a failure in suggested bindings.
/// </summary>
[UnityTest]
public IEnumerator RuntimeMethodFailure([ValueSource(nameof(s_RuntimeFailureTests))] (string function, XrResult result, Regex expectLog) test)
{
MockRuntime.SetFunctionCallback(test.function, (name) => test.result);
EnableFeature<OculusTouchControllerProfile>();
InitializeAndStart();
yield return new WaitForXrFrame(1);
StopAndShutdown();
LogAssert.Expect(LogType.Error, test.expectLog);
Assert.IsTrue(DoesDiagnosticReportContain(test.expectLog));
}
/// <summary>
/// Ensures that the `interactionFeatureLayouts` list is not missing any entries
/// </summary>
[Test]
public void AllInteractionFeaturesCovered()
{
// Array of all known interaction features
var knownInteractionFeatures = OpenXRSettings.Instance.GetFeatures<OpenXRInteractionFeature>()
.Select(f => f.GetType())
.Where(f => !s_IgnoreInteractionFeatures.Contains(f))
.ToArray();
// Array of interaction features being tested
var testedFeatures = s_InteractionFeatureLayouts.Select(l => l.featureType).ToArray();
// Make sure the two arrays are equal
Assert.IsTrue(knownInteractionFeatures.Length == testedFeatures.Length && knownInteractionFeatures.Intersect(testedFeatures).Count() == knownInteractionFeatures.Length,
"One or more interaction features has not been added to the testable interaction feature list.");
}
/// <summary>
/// Ensures that EyeGaze isTracked, position, rotation features map correctly to action handles.
/// (Since the EyeGaze features use pose instead of devicePose)
/// </summary>
[UnityTest]
public IEnumerator EyeGazeFeatureTest()
{
EnableFeature<EyeGazeInteraction>();
InitializeAndStart();
yield return new WaitForXrFrame(1);
InputAction inputAction = new InputAction(null, InputActionType.Value, "<XRInputV1::EyeTrackingOpenXR>/pose/isTracked");
InputControl control = inputAction.controls[0];
var isTrackedHandle = OpenXRInput.GetActionHandle(new InputAction(null, InputActionType.Value, "<XRInputV1::EyeTrackingOpenXR>/pose/isTracked"));
Assert.IsTrue(isTrackedHandle != 0);
var positionHandle = OpenXRInput.GetActionHandle(new InputAction(null, InputActionType.Value, "<XRInputV1::EyeTrackingOpenXR>/pose/position"));
Assert.IsTrue(positionHandle != 0);
var rotationHandle = OpenXRInput.GetActionHandle(new InputAction(null, InputActionType.Value, "<XRInputV1::EyeTrackingOpenXR>/pose/rotation"));
Assert.IsTrue(rotationHandle != 0);
}
[UnityTest]
public IEnumerator InputTrackingAquiredAndLost()
{
EnableFeature<OculusTouchControllerProfile>();
var tracked = false;
InputTracking.trackingAcquired += (ns) =>
{
if (ns.nodeType == XRNode.LeftHand)
tracked = ns.tracked;
};
InputTracking.trackingLost += (ns) =>
{
if (ns.nodeType == XRNode.LeftHand)
tracked = ns.tracked;
};
// Node should be untracked before we start
Assert.IsFalse(IsNodeTracked(XRNode.LeftHand));
// Node should be tracked after we start
InitializeAndStart();
yield return new WaitForXrFrame(1);
Assert.IsTrue(tracked, "There's no tracking after initialization and start");
// Clear the space location flags for the node which should switch to the untracked state
var gripAction = OpenXRInput.GetActionHandle(new InputAction(null, InputActionType.Value, "<XRInputV1::Oculus::OculusTouchControllerOpenXR>{LeftHand}/devicePose"));
var aimAction = OpenXRInput.GetActionHandle(new InputAction(null, InputActionType.Value, "<XRInputV1::Oculus::OculusTouchControllerOpenXR>{LeftHand}/pointer"));
MockRuntime.SetSpace(gripAction, Vector3.zero, Quaternion.identity, XrSpaceLocationFlags.None);
MockRuntime.SetSpace(aimAction, Vector3.zero, Quaternion.identity, XrSpaceLocationFlags.None);
yield return new WaitForXrFrame(2);
Assert.IsFalse(tracked, "Tracking is kept after clearing space location flags");
// Reset the space location flags to make sure it goes back to tracked state
var trackedFlags = XrSpaceLocationFlags.PositionValid | XrSpaceLocationFlags.OrientationValid | XrSpaceLocationFlags.PositionTracked | XrSpaceLocationFlags.OrientationTracked;
MockRuntime.SetSpace(gripAction, Vector3.zero, Quaternion.identity, trackedFlags);
MockRuntime.SetSpace(aimAction, Vector3.zero, Quaternion.identity, trackedFlags);
yield return new WaitForXrFrame(2);
Assert.IsTrue(tracked, "There's no tracking after resetting space location flags");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32a1e7d2570a89b4caac944990252b9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,286 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEditor;
#if UNITY_EDITOR
using UnityEditor.XR.OpenXR.Features;
#endif
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.TestTooling;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.XR.OpenXR.NativeTypes;
using Assert = UnityEngine.Assertions.Assert;
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests.Editor")]
[assembly: UnityPlatform(RuntimePlatform.WindowsPlayer, RuntimePlatform.WindowsEditor, RuntimePlatform.Android, RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer)]
namespace UnityEngine.XR.OpenXR.Tests
{
internal class OpenXRLoaderSetup : LoaderTestSetup<OpenXRLoader, OpenXRSettings>
{
protected override string settingsKey => "OpenXRTestSettings";
private OpenXRFeature[] savedFeatures = null;
/// <summary>
/// Save the previous value of OpenXRRestarter.DisableApplicationQuit.
/// We want to set it to true when running the test, and then restore the value after the test.
/// </summary>
private bool oldDisableApplicationQuit = false;
/// <summary>
/// Helper method to return a feature of the given type
/// </summary>
/// <param name="featureType">Feature type</param>
/// <returns>Reference to the requested feature or null if not found</returns>
protected OpenXRFeature GetFeature(Type featureType) =>
OpenXRSettings.ActiveBuildTargetInstance.GetFeature(featureType);
/// <summary>
/// Helper method to return a feature of the given type
/// </summary>
/// <typeparam name="T">Feature Type</typeparam>
/// <returns>Reference to the requested feature or null if not found</returns>
protected T GetFeature<T>() where T : OpenXRFeature => GetFeature(typeof(T)) as T;
/// <summary>
/// Enables a required feature of a given type.
/// </summary>
/// <param name="featureType">Type of feature to enable</param>
/// <returns>Feature that was enabled or null</returns>
protected OpenXRFeature EnableFeature(Type featureType, bool enable = true)
{
var feature = GetFeature(featureType);
Assert.IsNotNull(feature);
feature.enabled = enable;
return feature;
}
/// <summary>
/// Enables a required feature of a given type.
/// </summary>
/// <typeparam name="T">Type of feature to enable</typeparam>
/// <returns>Feature that was enabled or null</returns>
protected T EnableFeature<T>(bool enable = true) where T : OpenXRFeature => EnableFeature(typeof(T), enable) as T;
protected bool EnableMockRuntime(bool enable = true)
{
var feature = MockRuntime.Instance;
if (null == feature)
return false;
if (feature.enabled == enable)
return true;
feature.enabled = enable;
feature.openxrExtensionStrings = MockRuntime.XR_UNITY_null_gfx + " " + MockRuntime.XR_UNITY_android_present;
feature.priority = 0;
feature.required = false;
feature.ignoreValidationErrors = true;
return true;
}
protected void AddExtension(string extensionName)
{
MockRuntime.Instance.openxrExtensionStrings += $" {extensionName}";
}
private void DisableAllFeatures()
{
foreach (var ext in OpenXRSettings.ActiveBuildTargetInstance.features)
{
ext.enabled = false;
}
}
#pragma warning disable CS0618
public OpenXRLoader Loader => XRGeneralSettings.Instance?.Manager?.loaders[0] as OpenXRLoader;
#pragma warning restore CS0618
public override void SetupTest()
{
base.SetupTest();
#if UNITY_EDITOR
UnityEditor.XR.OpenXR.Features.FeatureHelpers.RefreshFeatures(BuildTargetGroup.Standalone);
UnityEditor.XR.OpenXR.Features.FeatureHelpers.RefreshFeatures(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget));
#endif
// Enable all build features
var featureTypes = new List<Type>();
QueryBuildFeatures(featureTypes);
featureTypes.Add(typeof(MockRuntime));
foreach (var feature in featureTypes.Select(featureType => OpenXRSettings.ActiveBuildTargetInstance.GetFeature(featureType)).Where(feature => null != feature))
{
feature.enabled = true;
}
}
/// <summary>
/// Override to return a list of feature types that should be enabled in the build
/// </summary>
protected virtual void QueryBuildFeatures(List<Type> featureTypes)
{
}
// NOTE: If you override this function, do NOT add the SetUp test attribute.
// If you do the overriden function and this function will be called separately
// and will most likely invalidate your test or even crash Unity.
[SetUp]
public virtual void BeforeTest()
{
// Make sure we are not running
if (OpenXRLoaderBase.Instance != null)
StopAndShutdown();
// Disable quitting the application functionality for all tests (since we don't want to exit play mode during the test)
oldDisableApplicationQuit = OpenXRRestarter.DisableApplicationQuit;
OpenXRRestarter.DisableApplicationQuit = true;
// Cache off the features before we start
savedFeatures = (OpenXRFeature[])OpenXRSettings.ActiveBuildTargetInstance.features.Clone();
// Disable all features to make sure the feature list is clean before tests start.
DisableAllFeatures();
// Enable the mock runtime and reset it back to default state
Assert.IsTrue(EnableMockRuntime());
MockRuntime.ResetDefaults();
OpenXRRuntime.ClearEvents();
OpenXRRestarter.Instance.ResetCallbacks();
#pragma warning disable CS0618
loader = XRGeneralSettings.Instance?.Manager?.loaders[0] as OpenXRLoader;
#pragma warning restore CS0618
#if UNITY_EDITOR && OPENXR_USE_KHRONOS_LOADER
var features = FeatureHelpersInternal.GetAllFeatureInfo(BuildTargetGroup.Standalone);
foreach (var f in features.Features)
{
if (f.Feature.nameUi == "Mock Runtime")
{
var path = Path.GetFullPath(f.PluginPath + "/unity-mock-runtime.json");
Environment.SetEnvironmentVariable("XR_RUNTIME_JSON", path);
}
}
#endif
}
[UnityTearDown]
public IEnumerator TearDown()
{
AfterTest();
yield return null;
}
// NOTE: If you override this function, do NOT add the SetUp test attribute.
// If you do the overriden function and this function will be called separately
// and will most likely invalidate your test or even crash Unity.
public virtual void AfterTest()
{
#pragma warning disable CS0618
var curLoader = XRGeneralSettings.Instance?.Manager?.activeLoader;
// Restore the original loader if it got removed during the test
if (curLoader == null)
XRGeneralSettings.Instance?.Manager?.TryAddLoader(loader);
#pragma warning restore CS0618
OpenXRRestarter.Instance.ResetCallbacks();
StopAndShutdown();
EnableMockRuntime(false);
MockRuntime.Instance.TestCallback = (methodName, param) => true;
MockRuntime.KeepFunctionCallbacks = false;
MockRuntime.ClearFunctionCallbacks();
// Replace the features with the saved features
OpenXRSettings.ActiveBuildTargetInstance.features = savedFeatures;
// Wait for all shutdowns and restart requests to finish
// and then restore the value of OpenXRRestarter.DisableApplicationQuit
WaitForRestarterToFinish();
}
private IEnumerable WaitForRestarterToFinish()
{
// It is possible that a test may have done something to initiate the OpenXRRestarter. To ensure
// that the restarter does not impact other tests we must make sure it finishes before continuing.
yield return new WaitForRestarter();
// Restore the value of DisableApplicationQuit.
OpenXRRestarter.DisableApplicationQuit = oldDisableApplicationQuit;
}
public override void Setup()
{
SetupTest();
EnableMockRuntime();
base.Setup();
}
public override void Cleanup()
{
base.Cleanup();
TearDownTest();
EnableMockRuntime(false);
}
static Dictionary<XrSessionState, HashSet<XrSessionState>> s_AllowedStateTransitions = new Dictionary<XrSessionState, HashSet<XrSessionState>>()
{
{XrSessionState.Unknown, new HashSet<XrSessionState>() {XrSessionState.Unknown}},
{XrSessionState.Idle, new HashSet<XrSessionState>() {XrSessionState.Unknown, XrSessionState.Unknown, XrSessionState.Exiting, XrSessionState.LossPending, XrSessionState.Stopping}},
{XrSessionState.Ready, new HashSet<XrSessionState>() {XrSessionState.Idle}},
{XrSessionState.Synchronized, new HashSet<XrSessionState>() {XrSessionState.Ready, XrSessionState.Visible}},
{XrSessionState.Visible, new HashSet<XrSessionState>() {XrSessionState.Synchronized, XrSessionState.Focused}},
{XrSessionState.Focused, new HashSet<XrSessionState>() {XrSessionState.Visible}},
{XrSessionState.Stopping, new HashSet<XrSessionState>() {XrSessionState.Synchronized}},
{XrSessionState.LossPending, new HashSet<XrSessionState>() {XrSessionState.Unknown, XrSessionState.Idle, XrSessionState.Ready, XrSessionState.Synchronized, XrSessionState.Visible, XrSessionState.Focused, XrSessionState.Stopping, XrSessionState.Exiting, XrSessionState.LossPending}},
{XrSessionState.Exiting, new HashSet<XrSessionState>() {XrSessionState.Idle}},
};
public void CheckValidStateTransition(XrSessionState oldState, XrSessionState newState)
{
bool hasNewState = s_AllowedStateTransitions.ContainsKey(newState);
bool canTransitionTo = s_AllowedStateTransitions[newState].Contains(oldState);
Debug.LogWarning($"Attempting to transition from {oldState} to {newState}");
if (!hasNewState)
Debug.LogError($"Has {newState} : {hasNewState}");
if (!canTransitionTo)
Debug.LogError($"Can transition from {oldState} to {newState} : {canTransitionTo}");
NUnit.Framework.Assert.IsTrue(hasNewState);
NUnit.Framework.Assert.IsTrue(canTransitionTo);
}
/// <summary>
/// Return true if the diagnostic report contains text that matches the given regex
/// </summary>
/// <param name="match">Regex to match</param>
/// <returns>True if the report matches the regex</returns>
protected bool DoesDiagnosticReportContain(Regex match) =>
match.IsMatch(DiagnosticReport.GenerateReport());
/// <summary>
/// Return true if the diagnostic report contains the given text
/// </summary>
/// <param name="match">String to search for</param>
/// <returns>True if the report contains the given text</returns>
protected bool DoesDiagnosticReportContain(string match) =>
DiagnosticReport.GenerateReport().Contains(match);
protected void ProcessOpenXRMessageLoop() => loader.ProcessOpenXRMessageLoop();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9cd6d4e6af6509848ad46f9249bc5a03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,184 @@
#if UNITY_EDITOR && ENABLE_TEST_SUPPORT
#define TEST_SUPPORT
#endif
#if TEST_SUPPORT
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NUnit.Framework;
using UnityEngine.TestTools;
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests.Editor")]
namespace UnityEngine.XR.OpenXR.Tests
{
internal class OpenXRLoaderTests : OpenXRLoaderSetup
{
public override void BeforeTest()
{
RemoveLoaderAndSettings();
DestroyLoaderAndSettings();
SetupLoaderAndSettings();
base.BeforeTest();
}
public override void AfterTest()
{
base.AfterTest();
RemoveLoaderAndSettings();
DestroyLoaderAndSettings();
SetupLoaderAndSettings();
}
#pragma warning disable CS0649
public struct ExpectedLogMessage
{
public LogType logType;
public string matchingRegex;
}
#pragma warning restore CS0649
public struct StateTransition
{
public OpenXRLoader.LoaderState targetState;
public bool expectedInitializeReturn;
public bool expectedStartReturn;
public bool expectedStopReturn;
public bool expectedDeinitializeReturn;
public ExpectedLogMessage[] expectedLogMessages;
}
public static List<StateTransition> stateTransitions = new List<StateTransition>()
{
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.InitializeAttempted,
expectedInitializeReturn = false,
expectedStartReturn = false,
expectedStopReturn = false,
expectedDeinitializeReturn = true,
expectedLogMessages = new ExpectedLogMessage[] { },
},
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.Initialized,
expectedInitializeReturn = true,
expectedStartReturn = true,
expectedStopReturn = true,
expectedDeinitializeReturn = true,
expectedLogMessages = new ExpectedLogMessage[] { },
},
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.StartAttempted,
expectedInitializeReturn = true,
expectedStartReturn = false,
expectedStopReturn = true,
expectedDeinitializeReturn = true,
expectedLogMessages = new ExpectedLogMessage[] { },
},
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.Started,
expectedInitializeReturn = true,
expectedStartReturn = true,
expectedStopReturn = true,
expectedDeinitializeReturn = true,
expectedLogMessages = new ExpectedLogMessage[] { },
},
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.StopAttempted,
expectedInitializeReturn = true,
expectedStartReturn = true,
expectedStopReturn = false,
expectedDeinitializeReturn = false,
expectedLogMessages = new ExpectedLogMessage[] { },
},
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.Stopped,
expectedInitializeReturn = true,
expectedStartReturn = true,
expectedStopReturn = true,
expectedDeinitializeReturn = true,
expectedLogMessages = new ExpectedLogMessage[] { },
},
new StateTransition()
{
targetState = OpenXRLoader.LoaderState.DeinitializeAttempted,
expectedInitializeReturn = true,
expectedStartReturn = true,
expectedStopReturn = true,
expectedDeinitializeReturn = false,
expectedLogMessages = new ExpectedLogMessage[] { },
},
};
[UnityTest]
[Category("Loader Tests")]
public IEnumerator StateTransitionValidation([ValueSource("stateTransitions")] StateTransition stateTransition)
{
Assert.IsNotNull(Loader);
if (Loader != null)
Loader.targetLoaderState = stateTransition.targetState;
bool ret = Loader.Initialize();
yield return null;
Assert.AreEqual(stateTransition.expectedInitializeReturn, ret);
ret = Loader.Start();
yield return null;
Assert.AreEqual(stateTransition.expectedStartReturn, ret);
ret = Loader.Stop();
yield return null;
Assert.AreEqual(stateTransition.expectedStopReturn, ret);
ret = Loader.Deinitialize();
yield return null;
Assert.AreEqual(stateTransition.expectedDeinitializeReturn, ret);
foreach (var expectedLog in stateTransition.expectedLogMessages)
{
LogAssert.Expect(expectedLog.logType, expectedLog.matchingRegex);
}
Loader.targetLoaderState = OpenXRLoaderBase.LoaderState.Uninitialized;
}
[UnityTest]
[Category("Loader Tests")]
public IEnumerator CanStopAndStartMultipleTimes()
{
Assert.IsNotNull(Loader);
bool ret = Loader.Initialize();
Assert.IsTrue(ret);
yield return null;
Assert.AreEqual(OpenXRLoader.LoaderState.Initialized, Loader.currentLoaderState);
for (int i = 0; i < 5; i++)
{
ret = Loader.Start();
Assert.IsTrue(ret);
yield return null;
Assert.AreEqual(OpenXRLoader.LoaderState.Started, Loader.currentLoaderState);
ret = Loader.Stop();
Assert.IsTrue(ret);
yield return null;
Assert.AreEqual(OpenXRLoader.LoaderState.Stopped, Loader.currentLoaderState);
}
ret = Loader.Deinitialize();
Assert.IsTrue(ret);
yield return null;
Assert.AreEqual(OpenXRLoader.LoaderState.Uninitialized, Loader.currentLoaderState);
}
}
}
#endif // TEST_SUPPORT

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc089751c573dfb4d8d426e22a9fe5ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
using NUnit.Framework;
using System.Collections;
using UnityEngine.TestTools;
using UnityEngine.XR.OpenXR.Features.Extensions.PerformanceSettings;
using UnityEngine.XR.OpenXR.Features.Mock;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class OpenXRPerformanceSettingsTest : OpenXRLoaderSetup
{
[UnityTest]
public IEnumerator PerformanceLevelHintIsSet()
{
base.EnableMockRuntime();
base.EnableFeature<XrPerformanceSettingsFeature>();
base.InitializeAndStart();
yield return new WaitForXrFrame(2);
const PerformanceDomain performanceDomain = PerformanceDomain.Cpu;
const PerformanceLevelHint expectedPerformanceLevel = PerformanceLevelHint.SustainedLow;
// Set performance level hint
bool callSuccess = XrPerformanceSettingsFeature.SetPerformanceLevelHint(performanceDomain, expectedPerformanceLevel);
yield return new WaitForXrFrame();
// Get performance level hint in MockRuntime
PerformanceLevelHint performanceHintInMock = MockRuntime.PerformanceSettings_GetPerformanceLevelHint(performanceDomain);
base.StopAndShutdown();
Assert.IsTrue(callSuccess, "Setting performance level hint failed.");
Assert.AreEqual(expectedPerformanceLevel, performanceHintInMock, "Performance level hint wasn't set correctly.");
}
[UnityTest]
public IEnumerator ReceiveEventNotification_NormalToWarning()
{
base.EnableMockRuntime();
base.EnableFeature<XrPerformanceSettingsFeature>();
base.InitializeAndStart();
yield return new WaitForXrFrame(2);
PerformanceChangeNotification expectedNotification = new()
{
domain = PerformanceDomain.Cpu,
subDomain = PerformanceSubDomain.Thermal,
fromLevel = PerformanceNotificationLevel.Normal,
toLevel = PerformanceNotificationLevel.Warning
};
bool receivedEvent = false;
bool notificationMatches = false;
// Subscribe to performance level change event
XrPerformanceSettingsFeature.OnXrPerformanceChangeNotification += (notification) =>
{
receivedEvent = true;
notificationMatches = expectedNotification.Equals(notification);
};
// Trigger a performance level change event
MockRuntime.PerformanceSettings_CauseNotification(PerformanceDomain.Cpu, PerformanceSubDomain.Thermal, PerformanceNotificationLevel.Warning);
yield return new WaitForXrFrame(2);
base.StopAndShutdown();
// Verify that the event was received
Assert.IsTrue(receivedEvent, "Performance change notification event wasn't received.");
Assert.IsTrue(notificationMatches, "Performance change notification doesn't match expected notification.");
}
[UnityTest]
public IEnumerator ExtensionNotInitialized()
{
base.EnableFeature<XrPerformanceSettingsFeature>(false);
base.InitializeAndStart();
yield return new WaitForXrFrame(2);
try
{
Assert.IsFalse(XrPerformanceSettingsFeature.SetPerformanceLevelHint(PerformanceDomain.Cpu, PerformanceLevelHint.SustainedLow), "Setting performance level hint should fail when the extension is not initialized.");
}
finally
{
StopAndShutdown();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6948c0ff319b582489e91d86f4c615b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6dbd87dbe22dfb9449d36960109d3522
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.TestTools;
using UnityEngine.XR.OpenXR.Features;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class OpenXRStatisticsTests : OpenXRLoaderSetup
{
[UnityTest]
public IEnumerator RegisterAndSetTestStatistic_UsingStatFlagStatOptionNone()
{
var feature = ScriptableObject.CreateInstance<SingleStatTestFeature>();
feature.ClearStatOnUpdate = false;
base.InitializeAndStart();
yield return null;
feature.CreateStat();
yield return null;
feature.SetStatValue(1.0f);
yield return null;
var stat1Success =
Provider.XRStats.TryGetStat(GetFirstDisplaySubsystem(), SingleStatTestFeature.StatName, out float value1);
base.StopAndShutdown();
Object.DestroyImmediate(feature);
Assert.IsTrue(stat1Success);
Assert.AreEqual(1.0f, value1);
}
[UnityTest]
public IEnumerator RegisterAndSetTestStatistic_UsingStatFlagClearOnUpdate()
{
var feature = ScriptableObject.CreateInstance<SingleStatTestFeature>();
feature.ClearStatOnUpdate = true;
base.InitializeAndStart();
yield return null;
feature.CreateStat();
yield return null;
feature.SetStatValue(1.0f);
yield return null;
var beforeUpdateSuccess =
Provider.XRStats.TryGetStat(GetFirstDisplaySubsystem(), SingleStatTestFeature.StatName, out float beforeUpdateValue);
yield return null;
var afterUpdateSuccess =
Provider.XRStats.TryGetStat(GetFirstDisplaySubsystem(), SingleStatTestFeature.StatName, out float afterUpdateValue);
base.StopAndShutdown();
Object.DestroyImmediate(feature);
Assert.IsTrue(beforeUpdateSuccess);
Assert.AreEqual(1.0f, beforeUpdateValue);
Assert.IsTrue(afterUpdateSuccess);
Assert.AreEqual(0.0f, afterUpdateValue);
}
private static IntegratedSubsystem GetFirstDisplaySubsystem()
{
List<XRDisplaySubsystem> displays = new();
SubsystemManager.GetSubsystems(displays);
if (displays.Count == 0)
{
Debug.Log("No display subsystem found.");
return null;
}
return displays[0];
}
}
internal class SingleStatTestFeature : OpenXRFeature
{
public const string StatName = "TestStat";
[NonSerialized]
private ulong m_statId;
public bool ClearStatOnUpdate { get; set; } = false;
public void CreateStat()
{
StatFlags flags = StatFlags.StatOptionNone;
if (ClearStatOnUpdate)
{
flags |= StatFlags.ClearOnUpdate;
}
m_statId = RegisterStatsDescriptor(StatName, flags);
}
public void SetStatValue(float value)
{
SetStatAsFloat(m_statId, value);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e13d4ddd08ed1b247bb7f401bb9702ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
{
"name": "Unity.XR.OpenXR.Tests",
"rootNamespace": "",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"Unity.XR.OpenXR",
"Unity.XR.Management",
"Unity.XR.Management.Editor",
"Unity.XR.OpenXR.Editor",
"Unity.InputSystem",
"Unity.XR.OpenXR.Features.MockRuntime",
"Unity.XR.OpenXR.Features.ConformanceAutomation"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2023fa18bf9504e98b11fe2174802802
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,95 @@
using NUnit.Framework;
using System.Runtime.InteropServices;
namespace UnityEngine.XR.OpenXR.Tests
{
internal class UnityVersionTests
{
public static readonly string[] s_ValidStrings =
{
"2020.3.17f1",
"2022.1.0a12",
"2020.2.0b16",
"2020.2.0rc16",
"2020.2.0rc1",
"2020.2.0p1",
"2020.3.17F1",
"6000.0.0b16",
"7000.1.2f11",
"8000.2.12b2"
};
public static readonly string[] s_InvalidStrings =
{
"20.20.3.17f1",
"2022.1.0x12",
"2020.2.b16",
"a.1.1rc1",
"1.a.1rc1",
"1.1.1rc",
"2020.2,1p1",
"2300.1.11f1",
"6001.0.0b16",
"7123.3.5f15"
};
private static readonly string[] s_SequentialVersions =
{
"2019.4.0a1",
"2020.4.0a1",
"2020.5.0a1",
"2020.5.0b1",
"2020.5.0rc1",
"2020.5.0f1",
"2020.5.0p1",
"2020.5.1p1",
"2020.5.1p2",
"6000.0.0a1",
"6000.0.0b1",
"6000.0.0f1",
"6000.1.0a1",
"7000.0.0a1",
"8000.0.0a1"
};
[Test]
public void ValidStrings([ValueSource(nameof(s_ValidStrings))] string versionString)
{
var version = Internal_GetUnityVersion(versionString);
Assert.IsTrue(version != 0);
}
[Test]
public void InvalidStrings([ValueSource(nameof(s_InvalidStrings))] string versionString)
{
var version = Internal_GetUnityVersion(versionString);
Assert.IsTrue(version == 0);
}
[Test]
public void NumericalCorrectness()
{
// Convert all of the version strings to numbers
var versions = new ulong[s_SequentialVersions.Length];
for (int i = 0; i < versions.Length; i++)
{
versions[i] = Internal_GetUnityVersion(s_SequentialVersions[i]);
Assert.IsFalse(versions[i] == 0, $"StringToVersion failed on `{s_SequentialVersions[i]}`");
}
// Make sure all versions are greater than all versions before them in the list
for (int i = 1; i < versions.Length; i++)
{
for (int j = i - 1; j >= 0; j--)
{
Assert.IsTrue(versions[i] > versions[j], $"{s_SequentialVersions[i]} was not greater than {s_SequentialVersions[j]}");
}
}
}
private const string LibraryName = "UnityOpenXR";
[DllImport(LibraryName, EntryPoint = "NativeConfig_GetUnityVersion", CharSet = CharSet.Ansi)]
static extern uint Internal_GetUnityVersion(string unityVersion);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12d280e0ba13c87498a380f6e8cc0e4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,76 @@
using System;
using NUnit.Framework;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Custom yield instruction that waits for the OpenXRRestarter to restart the loader.
/// Note that this will wait until a loader restart has been performed even if a restart
/// is not in progress when created.
/// </summary>
internal sealed class WaitForLoaderRestart : CustomYieldInstruction
{
private float m_Timeout = 0;
private Action m_OldAfterRestart;
private Action m_OldAfterCoroutine;
private Action m_OldAfterSuccessfulRestart;
private bool m_Done;
public WaitForLoaderRestart(float timeout = 5.0f, bool mustBeSuccessfulRestart = false)
{
m_Timeout = Time.realtimeSinceStartup + timeout;
var restarter = OpenXRRestarter.Instance;
m_OldAfterRestart = restarter.onAfterRestart;
m_OldAfterCoroutine = restarter.onAfterCoroutine;
m_OldAfterSuccessfulRestart = restarter.onAfterSuccessfulRestart;
if (mustBeSuccessfulRestart)
{
// Wait for a successful restart, then wait for that particular coroutine to finish.
restarter.onAfterSuccessfulRestart = () =>
{
restarter.onAfterCoroutine = () => m_Done = true;
};
}
else
{
restarter.onAfterRestart = () =>
{
restarter.onAfterCoroutine = () => m_Done = true;
};
}
}
private void RestoreCallbacks()
{
var restarter = OpenXRRestarter.Instance;
restarter.onAfterRestart = m_OldAfterRestart;
restarter.onAfterCoroutine = m_OldAfterCoroutine;
restarter.onAfterSuccessfulRestart = m_OldAfterSuccessfulRestart;
}
public override bool keepWaiting
{
get
{
// Wait until the coroutine is done
if (m_Done)
{
RestoreCallbacks();
return false;
}
// Did we time out waiting?
if (Time.realtimeSinceStartup > m_Timeout)
{
Assert.Fail("WaitForLoaderRestart: Timeout");
RestoreCallbacks();
return false;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e1f8c68ce3b3964ab84854b5c64cc5b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
using NUnit.Framework;
using System;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Custom yield instruction that waits for the OpenXRRestarter to finish shuting down the loader
/// without restarting the loader.
/// Note that this will wait until a loader shutdown has been performed even if a shutdown
/// is not in progress when created.
/// </summary>
internal sealed class WaitForLoaderShutdown : CustomYieldInstruction
{
private float m_Timeout = 0;
private Action m_OldAfterShutdown;
private Action m_OldAfterRestart;
private Action m_OldAfterCoroutine;
private bool m_Shutdown;
private bool m_Restarted;
private bool m_Done;
public WaitForLoaderShutdown(float timeout = 5.0f)
{
m_Timeout = Time.realtimeSinceStartup + timeout;
var restarter = OpenXRRestarter.Instance;
m_OldAfterShutdown = restarter.onAfterShutdown;
m_OldAfterRestart = restarter.onAfterRestart;
m_OldAfterCoroutine = restarter.onAfterCoroutine;
restarter.onAfterShutdown = () =>
{
m_Shutdown = true;
restarter.onAfterRestart = () => m_Restarted = true;
restarter.onAfterCoroutine = () => m_Done = true;
};
}
private void RestoreCallbacks()
{
var restarter = OpenXRRestarter.Instance;
restarter.onAfterShutdown = m_OldAfterShutdown;
restarter.onAfterRestart = m_OldAfterRestart;
restarter.onAfterCoroutine = m_OldAfterCoroutine;
}
public override bool keepWaiting
{
get
{
if (m_Done)
{
if (!m_Shutdown)
{
Assert.Fail("WaitForLoaderShutdown: Coroutine finished without shutting down");
}
else if (m_Restarted)
{
Assert.Fail("WaitForLoaderShutdown: Waiting for shutdown but loader was restarted");
}
RestoreCallbacks();
return false;
}
// Did we time out waiting?
if (Time.realtimeSinceStartup > m_Timeout)
{
Assert.Fail("WaitForLoaderShutdown: Timeout");
RestoreCallbacks();
return false;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5fbc19af10596b48b2014bf03c9e8e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System;
using NUnit.Framework;
using UnityEngine.XR.OpenXR.Features.Mock;
using UnityEngine.XR.OpenXR.NativeTypes;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Custom yield instruction that waits for a null OpenXRLoaderBase after initialization has started.
/// </summary>
internal sealed class WaitForNullXRLoader : CustomYieldInstruction
{
private float m_Timeout = 0;
public bool m_startListening = false;
public WaitForNullXRLoader(float timeout = 5.0f)
{
m_Timeout = Time.realtimeSinceStartup + timeout;
}
public void StartListening()
{
m_startListening = true;
}
public override bool keepWaiting
{
get
{
// Wait until the coroutine is done
if (m_startListening && OpenXRLoaderBase.Instance == null)
{
return false;
}
// Did we time out waiting?
if (Time.realtimeSinceStartup > m_Timeout)
{
Assert.Fail("WaitForDestroyInstanceCall: Timeout");
return false;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf6c0f80d1eca304fb720a7daa4bf10c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using NUnit.Framework;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Custom yield instruction that waits for the OpenXRRestarter to finish if it is running.
/// Note that ulink WaitForLoaderRestart and WaitForLoaderShutdown this yield instruction
/// will not wait if the restarter is not already running.
/// </summary>
internal sealed class WaitForRestarter : CustomYieldInstruction
{
private float m_Timeout = 0;
public WaitForRestarter(float timeout = 5.0f)
{
m_Timeout = Time.realtimeSinceStartup + timeout;
}
public override bool keepWaiting
{
get
{
// Wait until the restarter is finished
if (!OpenXRRestarter.Instance.isRunning && OpenXRRestarter.PauseAndRestartCoroutineCount == 0)
{
return false;
}
// Did we time out waiting?
if (Time.realtimeSinceStartup > m_Timeout)
{
Assert.Fail("WaitForLoaderRestart: Timeout");
return false;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dae12eb48b9554a46a7dfdccf1913a0f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using NUnit.Framework;
using System.Runtime.InteropServices;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Custom yield instruction that waits for the tracking origin to regenerate.
/// </summary>
internal sealed class WaitForTrackingOriginRegeneration : CustomYieldInstruction
{
[DllImport("UnityOpenXR", EntryPoint = "unity_ext_GetRegenerateTrackingOriginFlag")]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool GetRegenerateTrackingOriginFlag();
private float m_Timeout = 0;
public WaitForTrackingOriginRegeneration(float timeout = 5.0f)
{
m_Timeout = Time.realtimeSinceStartup + timeout;
}
public override bool keepWaiting
{
get
{
if (!GetRegenerateTrackingOriginFlag())
{
return false;
}
// Did we time out waiting?
if (Time.realtimeSinceStartup > m_Timeout)
{
Assert.Fail("WaitForTrackingOriginRegeneration: Timeout");
return false;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9ebf2d64363af445b053d57a30e09c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
using System.Diagnostics;
using UnityEngine.XR.OpenXR.Features.Mock;
using NUnit.Framework;
namespace UnityEngine.XR.OpenXR.Tests
{
/// <summary>
/// Custom yield instruction that waits for xrEndFrame to be called within OpenXR
/// </summary>
internal class WaitForXrFrame : CustomYieldInstruction
{
private int m_Frames = 0;
private long m_Timeout;
private Stopwatch m_Timer;
public override bool keepWaiting
{
get
{
if (m_Frames <= 0)
return false;
if (m_Timer.ElapsedMilliseconds < m_Timeout)
return true;
MockRuntime.onScriptEvent -= OnScriptEvent;
Assert.Fail("WaitForXrFrame: Timeout");
return false;
}
}
public WaitForXrFrame(int frames = 1, float timeout = 10.0f)
{
m_Frames = frames;
m_Timeout = (long)(timeout * 1000.0);
if (frames == 0)
return;
// Start waiting for a new frame count
MockRuntime.onScriptEvent += OnScriptEvent;
m_Timer = new Stopwatch();
m_Timer.Restart();
}
private void OnScriptEvent(MockRuntime.ScriptEvent evt, ulong param)
{
if (evt != MockRuntime.ScriptEvent.EndFrame)
return;
m_Frames--;
if (m_Frames > 0)
return;
m_Frames = 0;
MockRuntime.onScriptEvent -= OnScriptEvent;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c13e63b99a8248eda9d91f10eb39538d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: