-
Notifications
You must be signed in to change notification settings - Fork 329
FIX: ISXB-1584, ISXB-1688, ISXB-1692, ISXB-1693, ISXB-1694, ISXB-1701 InputActionReference bug fixes and improved test coverage. #2248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ekcoh
wants to merge
24
commits into
develop
Choose a base branch
from
isxb-1584-input-action-reference
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,135
−207
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
10f19c9
FIX: InputActionReference returning incorrect action when action has …
ekcoh f38dd04
Updated change log.
ekcoh 79c430d
Improvements to testing, refactoring of tests, bug fixes to InputActi…
ekcoh 27b0311
Updated changelog
ekcoh 7c5305a
FIX: RemoveActionMap does not remove contained actions. Additional bu…
ekcoh b02a52e
More bug fixes and test cases for input action reference
ekcoh 0dbc8f5
Additional simplification, fixes and test cases. Added test assembly …
ekcoh 651526e
Added more tests, code cleanup, refactored QA script to QA asset folder.
ekcoh b50c8c5
Merge branch 'develop' into isxb-1584-input-action-reference
ekcoh 769d73c
Updated CHANGELOG
ekcoh 58f9c28
Removed development comments from source file.
ekcoh 5497481
Undo changes to AssetDatabaseUtils that were not needed in the end.
ekcoh de71adb
Merge branch 'develop' into isxb-1584-input-action-reference
ekcoh 7611d03
xmldoc fixes
ekcoh 8f826ca
Merge branch 'isxb-1584-input-action-reference' of github.com:Unity-T…
ekcoh 6dd0f88
Replaced Conditional by preprocessor check since Conditional cannot w…
ekcoh 22be817
Strengthened existing test by adding verification that reference is r…
ekcoh 007412e
Corrected and updated inline doc for InputActionReference invalidation
ekcoh a146ecd
Simplified InputSystemProvider since there is no reason at all to use…
ekcoh 3af6d81
Undo bug fix of RemoveActionMap which was correct and filed bug was A…
ekcoh a499b40
Improved inline comment
ekcoh 4dfc4f4
Added missing guard to mimic inclusion of test based on PWA.
ekcoh 8c008bc
Clarified in xmldoc that removing an action map still leaves all cont…
ekcoh b68353d
Merge branch 'develop' into isxb-1584-input-action-reference
ekcoh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using UnityEngine; | ||
using UnityEngine.InputSystem; | ||
|
||
internal static class DumpInputActionReferences | ||
{ | ||
private static void DumpReferences(StringBuilder sb, string prefix, InputActionReference[] references) | ||
{ | ||
sb.Append(prefix + ":\n"); | ||
foreach (var reference in references) | ||
{ | ||
var s = reference.action != null ? "Yes" : "No"; | ||
sb.Append($"- {reference.name} (Resolved: {s}, Asset: {reference.asset})\n"); | ||
} | ||
} | ||
|
||
private static void DumpReferences() | ||
{ | ||
var sb = new StringBuilder(); | ||
DumpReferences(sb, "Loaded objects", Object.FindObjectsByType<InputActionReference>( | ||
FindObjectsInactive.Include, FindObjectsSortMode.InstanceID)); | ||
DumpReferences(sb, "All objects:", Resources.FindObjectsOfTypeAll<InputActionReference>()); | ||
Debug.Log(sb.ToString()); | ||
} | ||
|
||
[UnityEditor.MenuItem("QA Tools/Dump Input Action References to Console", false, 100)] | ||
private static void Dump() | ||
{ | ||
DumpReferences(); | ||
} | ||
} | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using UnityEditor; | ||
|
||
namespace Tests.InputSystem.Editor | ||
{ | ||
/// <summary> | ||
/// Utility to simplify editor tests with respect to editor preferences. | ||
/// </summary> | ||
internal static class EditorPrefsTestUtils | ||
{ | ||
private const string EnterPlayModeOptionsEnabledKey = "EnterPlayModeOptionsEnabled"; | ||
private const string EnterPlayModeOptionsKey = "EnterPlayModeOptions"; | ||
|
||
private static bool _savedEnterPlayModeOptionsEnabled; | ||
private static int _savedEnterPlayModeOptions; | ||
|
||
/// <summary> | ||
/// Call this from a tests SetUp routine to save editor preferences so they can be restored after the test. | ||
/// </summary> | ||
public static void SaveEditorPrefs() | ||
{ | ||
_savedEnterPlayModeOptionsEnabled = EditorPrefs.GetBool(EnterPlayModeOptionsEnabledKey, false); | ||
_savedEnterPlayModeOptions = EditorPrefs.GetInt(EnterPlayModeOptionsKey, (int)EnterPlayModeOptions.None); | ||
} | ||
|
||
/// <summary> | ||
/// Call this from a tests TearDown routine to restore editor preferences to the state it had before the test. | ||
/// </summary> | ||
/// <remarks>Note that if domain reloads have not been disabled and you have a domain reload mid-test, | ||
/// this utility will fail to restore editor preferences since the saved data will be lost.</remarks> | ||
public static void RestoreEditorPrefs() | ||
{ | ||
EditorPrefs.SetBool(EnterPlayModeOptionsEnabledKey, _savedEnterPlayModeOptionsEnabled); | ||
EditorPrefs.SetInt(EnterPlayModeOptionsKey, _savedEnterPlayModeOptions); | ||
} | ||
|
||
/// <summary> | ||
/// Returns whether domain reloads are disabled. | ||
/// </summary> | ||
/// <returns>true if domain reloads have been disabled, else false.</returns> | ||
public static bool IsDomainReloadsDisabled() | ||
{ | ||
return EditorPrefs.GetBool(EnterPlayModeOptionsEnabledKey, false) && | ||
(EditorPrefs.GetInt(EnterPlayModeOptionsKey, (int)EnterPlayModeOptions.None) & | ||
(int)EnterPlayModeOptions.DisableDomainReload) != 0; | ||
} | ||
|
||
/// <summary> | ||
/// Returns whether scene reloads are disabled. | ||
/// </summary> | ||
/// <returns>true if scene reloads have been disabled, else false.</returns> | ||
public static bool IsSceneReloadsDisabled() | ||
{ | ||
return EditorPrefs.GetBool(EnterPlayModeOptionsEnabledKey, false) && | ||
(EditorPrefs.GetInt(EnterPlayModeOptionsKey, (int)EnterPlayModeOptions.None) & | ||
(int)EnterPlayModeOptions.DisableSceneReload) != 0; | ||
} | ||
|
||
/// <summary> | ||
/// Call this from within a test to temporarily enable domain reload. | ||
/// </summary> | ||
public static void EnableDomainReload() | ||
{ | ||
EditorPrefs.SetBool(EnterPlayModeOptionsEnabledKey, false); | ||
} | ||
|
||
/// <summary> | ||
/// Call this from within a test to temporarily disable domain reload (and scene reloads). | ||
/// </summary> | ||
public static void DisableDomainReload() | ||
{ | ||
EditorPrefs.SetInt(EnterPlayModeOptionsKey, (int)(EnterPlayModeOptions.DisableDomainReload | | ||
EnterPlayModeOptions.DisableSceneReload)); | ||
EditorPrefs.SetBool(EnterPlayModeOptionsEnabledKey, true); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
197 changes: 197 additions & 0 deletions
197
Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Linq; | ||
using NUnit.Framework; | ||
using Tests.InputSystem.Editor; | ||
using UnityEditor; | ||
using UnityEditor.SceneManagement; | ||
using UnityEngine; | ||
using UnityEngine.InputSystem; | ||
using UnityEngine.InputSystem.Editor; | ||
using UnityEngine.SceneManagement; | ||
using UnityEngine.TestTools; | ||
using Object = UnityEngine.Object; | ||
|
||
/// <summary> | ||
/// Editor tests for <see cref="UnityEngine.InputSystem.InputActionReference"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// This test need fixed asset paths since mid-test domain reloads would otherwise discard data. | ||
/// | ||
/// Be aware that if you get failures in editor tests that switch between play mode and edit mode via coroutines | ||
/// you might get misleading stack traces that indicate errors in different places than they actually happen. | ||
/// At least this have been observered for exception stack traces. | ||
/// </remarks> | ||
internal class InputActionReferenceEditorTestsWithScene | ||
{ | ||
private const string TestCategory = "Editor"; | ||
|
||
private Scene m_Scene; | ||
|
||
private const string assetPath = "Assets/__InputActionReferenceEditorTests.inputactions"; | ||
private const string dummyPath = "Assets/__InputActionReferenceEditorTestsDummy.asset"; | ||
private const string scenePath = "Assets/__InputActionReferenceEditorTestsScene.unity"; | ||
|
||
private void CreateAsset() | ||
{ | ||
var asset = ScriptableObject.CreateInstance<InputActionAsset>(); | ||
|
||
var map1 = new InputActionMap("map1"); | ||
map1.AddAction("action1"); | ||
map1.AddAction("action2"); | ||
asset.AddActionMap(map1); | ||
|
||
System.IO.File.WriteAllText(assetPath, asset.ToJson()); | ||
Object.DestroyImmediate(asset); | ||
AssetDatabase.ImportAsset(assetPath); | ||
} | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
// This looks odd, but when we yield into play mode from our test coroutine we may get a domain reload | ||
// (depending on editor preferences) which will trigger another SetUp() mid-test. | ||
if (Application.isPlaying) | ||
return; | ||
|
||
EditorPrefsTestUtils.SaveEditorPrefs(); | ||
|
||
m_Scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); | ||
CreateAsset(); | ||
|
||
var go = new GameObject("Root"); | ||
var behaviour = go.AddComponent<InputActionBehaviour>(); | ||
var reference = InputActionImporter.LoadInputActionReferencesFromAsset(assetPath).First( | ||
r => "action1".Equals(r.action.name)); | ||
behaviour.referenceAsField = reference; | ||
behaviour.referenceAsReference = reference; | ||
|
||
TestUtils.SaveScene(m_Scene, scenePath); | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
// This looks odd, but when we yield into play mode from our test coroutine we may get a domain reload | ||
// (depending on editor preferences) which will trigger another TearDown() mid-test. | ||
if (Application.isPlaying) | ||
return; | ||
|
||
EditorPrefsTestUtils.RestoreEditorPrefs(); | ||
|
||
// Close scene | ||
EditorSceneManager.CloseScene(m_Scene, true); | ||
|
||
// Clean-up | ||
AssetDatabase.DeleteAsset(dummyPath); | ||
AssetDatabase.DeleteAsset(assetPath); | ||
AssetDatabase.DeleteAsset(scenePath); | ||
} | ||
|
||
private void DisableDomainReloads() | ||
{ | ||
// Assumes off before running tests. | ||
Debug.Assert(!EditorPrefsTestUtils.IsDomainReloadsDisabled()); | ||
Debug.Assert(!EditorPrefsTestUtils.IsSceneReloadsDisabled()); | ||
|
||
// Safe to store since state wouldn't be reset by domain reload. | ||
EditorPrefsTestUtils.DisableDomainReload(); | ||
} | ||
|
||
private static InputActionBehaviour GetBehaviour() => Object.FindObjectOfType<InputActionBehaviour>(); | ||
private static InputActionAsset GetAsset() => AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath); | ||
|
||
// For unclear reason, NUnit fails to assert throwing exceptions after transition into play-mode. | ||
// So until that can be sorted out, we do it manually (in the same way) ourselves. | ||
private static void AssertThrows<T>(Action action) where T : Exception | ||
{ | ||
var exceptionThrown = false; | ||
try | ||
{ | ||
action(); | ||
} | ||
catch (InvalidOperationException) | ||
{ | ||
exceptionThrown = true; | ||
} | ||
Assert.IsTrue(exceptionThrown, $"Expected exception of type {typeof(T)} to be thrown but it was not."); | ||
} | ||
|
||
private static bool[] _disableDomainReloadsValues = new bool[] { false, true }; | ||
|
||
[UnityTest] | ||
[Category(TestCategory)] | ||
[Description("https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1584")] | ||
public IEnumerator ReferenceSetInPlaymodeShouldBeRestored_WhenExitingPlaymode( | ||
[ValueSource(nameof(_disableDomainReloadsValues))] bool disableDomainReloads) | ||
{ | ||
if (disableDomainReloads) | ||
DisableDomainReloads(); | ||
|
||
// Edit-mode section | ||
{ | ||
// Sanity check our initial edit-mode state | ||
var obj = GetBehaviour(); | ||
Assert.That(obj.referenceAsField.action, Is.SameAs(GetAsset().FindAction("map1/action1"))); | ||
Assert.That(obj.referenceAsReference.action, Is.SameAs(GetAsset().FindAction("map1/action1"))); | ||
|
||
// Enter play-mode (This will lead to domain reload by default). | ||
yield return new EnterPlayMode(); | ||
} | ||
|
||
// Play-mode section | ||
{ | ||
var obj = GetBehaviour(); | ||
var editModeAction = GetAsset().FindAction("map1/action1"); | ||
var playModeAction = GetAsset().FindAction("map1/action2"); | ||
|
||
// Make sure our action reference is consistent in play-mode | ||
Assert.That(obj.referenceAsField.action, Is.SameAs(editModeAction)); | ||
|
||
// ISXB-1584: Attempting assignment of persisted input action reference in play-mode in editor. | ||
// Rationale: We cannot allow this since it would corrupt the source asset since changes applied to SO | ||
// mapped to an asset isn't reverted when exiting play-mode. | ||
// | ||
// Here we would like to do: | ||
// Assert.Throws<InvalidOperationException>(() => obj.reference.Set(null)); | ||
// | ||
// But we can't since it would fail with NullReferenceException. | ||
// It turns out that because of the domain reload / Unity’s internal serialization quirks, the obj is | ||
// sometimes null inside the lambda when NUnit captures it for execution. So to work around this we | ||
// instead do the same kind of check manually for now which doesn't seem to have this problem. | ||
// | ||
// It is odd since NUnit does basically does the same thing (apart from wrapping the lambda as a | ||
// TestDelegate). So the WHY for this problem remains unclear for now. | ||
AssertThrows<InvalidOperationException>(() => obj.referenceAsField.Set(playModeAction)); | ||
AssertThrows<InvalidOperationException>(() => obj.referenceAsReference.Set(editModeAction)); | ||
|
||
// Make sure there were no side-effects. | ||
Assert.That(obj.referenceAsField.action, Is.SameAs(editModeAction)); | ||
Assert.That(obj.referenceAsReference.action, Is.SameAs(editModeAction)); | ||
|
||
// Correct usage is to use a run-time assigned input action reference instead. It is up to the user | ||
// to decide whether this reference should additionally be persisted (which is possible by saving it to | ||
// and asset, or by using SerializeReference). | ||
obj.referenceAsField = InputActionReference.Create(playModeAction); | ||
obj.referenceAsReference = InputActionReference.Create(playModeAction); | ||
|
||
// Makes sure we have the expected reference. | ||
Assert.That(obj.referenceAsField.action, Is.SameAs(playModeAction)); | ||
Assert.That(obj.referenceAsReference.action, Is.SameAs(playModeAction)); | ||
|
||
// Exit play-mode (This will lead to domain reload by default). | ||
yield return new ExitPlayMode(); | ||
} | ||
|
||
// Edit-mode section | ||
{ | ||
// Make sure our reference is back to its initial edit mode state | ||
var obj = GetBehaviour(); | ||
var editModeAction = GetAsset().FindAction("map1/action1"); | ||
Assert.That(obj.referenceAsField.action, Is.SameAs(editModeAction)); | ||
Assert.That(obj.referenceAsReference.action, Is.SameAs(editModeAction)); | ||
} | ||
|
||
yield return null; | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
Assets/Tests/InputSystem.Editor/InputActionReferenceEditorTests.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be part of our Input Debugger interface instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, right now I just put it into our QA asset scripts since I did it to investigate bugs and felt it had more value to keep than removing it, but maybe someone would be interested in it. I would say there is no obvious user-value in it, but maybe for ourselves. However, you likely want snapshots and this was simple and sufficient to solve such problems. I do not intend to move it there as part of this PR at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However its handy to be able to see which InputActionReferences exist in memory and on disc as serialised SOs