Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5cc05c2
Extended documentation of InputTestFixture.
ekcoh Oct 6, 2025
d133a2d
Updated xmldoc of InputTestFixture
ekcoh Oct 6, 2025
d355b21
FIX: Fixed an issue where accessing a device from an outer context wi…
ekcoh Oct 7, 2025
76c8b59
Added detection for editor assembly UnityTest, added missing xmldocs …
ekcoh Oct 8, 2025
f85b9a0
Merge branch 'develop' into isxb-1637-crash-on-memcpy
ekcoh Oct 8, 2025
e183345
Added more docs.
ekcoh Oct 8, 2025
a7251f6
Merge branch 'isxb-1637-crash-on-memcpy' of github.com:Unity-Technolo…
ekcoh Oct 8, 2025
8c40143
Update Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEv…
ekcoh Oct 8, 2025
56c36a1
Update Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixt…
ekcoh Oct 8, 2025
59a44ea
Update Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixt…
ekcoh Oct 8, 2025
19290a1
Update Packages/com.unity.inputsystem/CHANGELOG.md
ekcoh Oct 8, 2025
57ef549
Added more tests to cover touch and vector2 APIs as well. Added a mis…
ekcoh Oct 8, 2025
6116b96
Merge branch 'isxb-1637-crash-on-memcpy' of github.com:Unity-Technolo…
ekcoh Oct 8, 2025
1bfc5ea
Merge branch 'develop' into isxb-1637-crash-on-memcpy
ekcoh Oct 8, 2025
ef7b23d
Fixed xmldoc warning
ekcoh Oct 8, 2025
32b6ddb
Corrections to xmldoc
ekcoh Oct 8, 2025
adcbf04
Adopted solving this problem to tiered checks in InputTestFixture and…
ekcoh Oct 10, 2025
7e429e7
Reenabled check
ekcoh Oct 10, 2025
5ba4292
Merge branch 'develop' into isxb-1637-crash-on-memcpy
ekcoh Oct 13, 2025
e1707e8
Merge branch 'develop' into isxb-1637-crash-on-memcpy
ekcoh Oct 13, 2025
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
226 changes: 226 additions & 0 deletions Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System;
using System.Collections;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.TestTools;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

/// <summary>
/// Test suite to verify test fixture API published in <see cref="InputTestFixture"/>.
/// </summary>
/// <remarks>
/// This test fixture captures confusion around usage reported in
/// https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637.
/// </remarks>
internal class InputTestFixtureTests : InputTestFixture
{
private Keyboard correctlyUsedKeyboard;

private Keyboard incorrectlyUsedDevice;
private Gamepad incorrectlyUsedGamepad;
private Touchscreen incorrectlyUsedTouchscreen;

[OneTimeSetUp]
public void UnitySetup()
{
// This is incorrect use since it will add a device to the actual input system since it executes before
// the InputTestFixture.Setup() method. Hence, after Setup() has executed the device is not part of
// the test input system instance.
incorrectlyUsedDevice = InputSystem.AddDevice<Keyboard>();
Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);

incorrectlyUsedGamepad = InputSystem.AddDevice<Gamepad>();
Assert.That(InputSystem.devices.Contains(incorrectlyUsedGamepad), Is.True);

incorrectlyUsedTouchscreen = InputSystem.AddDevice<Touchscreen>();
Assert.That(InputSystem.devices.Contains(incorrectlyUsedTouchscreen), Is.True);
}

[OneTimeTearDown]
public void UnityTearDown()
{
// Once InputTestFixture.TearDown() has executed, the state stack will have been popped and the correctlyUsedKeyboard
// we added before entering the test fixture should have been restored.
Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedGamepad), Is.True);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedTouchscreen), Is.True);
}

[SetUp]
public override void Setup()
{
// At this point we are still using the actual system so our device created from UnitySetup() should still
// exist with the context.
Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedGamepad), Is.True);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedTouchscreen), Is.True);

// This is expected usage pattern, first calling base.Setup() when overriding Setup like this, then
// creating a fake device via the test fixture instance that only lives with the test context.
base.Setup();

// Since we have now entered a temporary test state our device created in UnitySetup() will no longer exist
// with this context.
Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.False);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedGamepad), Is.False);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedTouchscreen), Is.False);
}

[TearDown]
public override void TearDown()
{
// Restore state
base.TearDown();

// Our test device should no longer exist with the system since we are back to real instance
Assert.That(InputSystem.devices.Contains(correctlyUsedKeyboard), Is.False);

Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedGamepad), Is.True);
Assert.That(InputSystem.devices.Contains(incorrectlyUsedTouchscreen), Is.True);
}

#region Editor playmode tests

[Test]
public void Press_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Press(correctlyUsedKeyboard.spaceKey);
Assert.That(correctlyUsedKeyboard.spaceKey.isPressed, Is.True);
}

[Test]
public void Press_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
{
Assert.Throws<ArgumentException>(() => Press(incorrectlyUsedDevice.spaceKey));
}

[Test]
public void Release_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Press(correctlyUsedKeyboard.spaceKey);
Release(correctlyUsedKeyboard.spaceKey);
Assert.That(correctlyUsedKeyboard.spaceKey.isPressed, Is.False);
}

[Test]
public void Release_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
{
Assert.Throws<ArgumentException>(() => Release(incorrectlyUsedDevice.spaceKey));
}

[Test]
public void PressAndRelease_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
PressAndRelease(correctlyUsedKeyboard.spaceKey);
Assert.That(correctlyUsedKeyboard.spaceKey.isPressed, Is.False);
}

[Test]
public void PressAndRelease_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
{
Assert.Throws<ArgumentException>(() => PressAndRelease(incorrectlyUsedDevice.spaceKey));
}

[Test]
public void Click_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Click(correctlyUsedKeyboard.spaceKey);
Assert.That(correctlyUsedKeyboard.spaceKey.isPressed, Is.False);
}

[Test]
public void Click_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
{
Assert.Throws<ArgumentException>(() => Click(incorrectlyUsedDevice.spaceKey));
}

[Test]
public void Move_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
var gamepad = InputSystem.AddDevice<Gamepad>();
Move(gamepad.leftStick, new Vector2(1.0f, 0.0f));
Assert.That(gamepad.leftStick.value, Is.EqualTo(new Vector2(1.0f, 0.0f)));
}

[Test]
public void Move_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
{
Assert.Throws<ArgumentException>(() => Move(incorrectlyUsedGamepad.leftStick, new Vector2(1.0f, 0.0f)));
}

[Test]
public void Touch_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
var touchscreen = InputSystem.AddDevice<Touchscreen>();
BeginTouch(1, Vector2.zero, screen: touchscreen);
Assert.That(touchscreen.touches.Count, Is.EqualTo(10));
Assert.That(touchscreen.touches[0].touchId.value, Is.EqualTo(1));
Assert.That(touchscreen.touches[0].position.value, Is.EqualTo(Vector2.zero));
Assert.That(touchscreen.touches[0].phase.value, Is.EqualTo(TouchPhase.Began));

MoveTouch(1, Vector2.one, screen: touchscreen);
Assert.That(touchscreen.touches.Count, Is.EqualTo(10));
Assert.That(touchscreen.touches[0].touchId.value, Is.EqualTo(1));
Assert.That(touchscreen.touches[0].position.value, Is.EqualTo(Vector2.one));
Assert.That(touchscreen.touches[0].phase.value, Is.EqualTo(TouchPhase.Moved));

EndTouch(1, Vector2.zero, screen: touchscreen);
Assert.That(touchscreen.touches.Count, Is.EqualTo(10));
Assert.That(touchscreen.touches[0].touchId.value, Is.EqualTo(1));
Assert.That(touchscreen.touches[0].position.value, Is.EqualTo(Vector2.zero));
Assert.That(touchscreen.touches[0].phase.value, Is.EqualTo(TouchPhase.Ended));
}

[Test]
public void Touch_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
{
Assert.Throws<ArgumentException>(() => BeginTouch(1, Vector2.zero, screen: incorrectlyUsedTouchscreen));
Assert.Throws<ArgumentException>(() => MoveTouch(1, Vector2.zero, screen: incorrectlyUsedTouchscreen));
Assert.Throws<ArgumentException>(() => EndTouch(1, Vector2.zero, screen: incorrectlyUsedTouchscreen));
}

#endregion // Playmode tests

#region // Edit-mode tests

[UnityTest]
public IEnumerator Press_ShouldThrow_WithinEditModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Assert.Throws<NotSupportedException>(() => Press(correctlyUsedKeyboard.spaceKey));
yield break;
}

[UnityTest]
public IEnumerator Release_ShouldThrow_WithinEditModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Assert.Throws<NotSupportedException>(() => Release(correctlyUsedKeyboard.spaceKey));
yield break;
}

[UnityTest]
public IEnumerator PressAndRelease_ShouldThrow_WithinEditModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Assert.Throws<NotSupportedException>(() => PressAndRelease(correctlyUsedKeyboard.spaceKey));
yield break;
}

[UnityTest]
public IEnumerator Click_ShouldThrow_WithinEditModeTestFixtureContext()
{
correctlyUsedKeyboard = InputSystem.AddDevice<Keyboard>();
Assert.Throws<NotSupportedException>(() => Click(correctlyUsedKeyboard.spaceKey));
yield break;
}

#endregion // Edit-mode tests
}
3 changes: 3 additions & 0 deletions Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs.meta

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

2 changes: 2 additions & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ however, it has to be formatted properly to pass verification tests.

### Fixed
- Fixed warnings being generated on Unity 6.3 (beta). (ISXB-1718).
- Fixed an issue in `DeltaStateEvent.From` where unsafe code would throw exception or crash if internal pointer `currentStatePtr` was `null`. [ISXB-1637](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637).
- Fixed an issue in `InputTestFixture.Set` where attempting to change state of a device not belonging to the test fixture context would result in null pointer exception or crash. [ISXB-1637](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637).

## [1.15.0] - 2025-10-03

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
if (!device.added)
throw new ArgumentException($"Device for control '{control}' has not been added to system",
nameof(control));
if (control.currentStatePtr == null) // Protects statePtr assignment below
throw new ArgumentNullException($"Control '{control}' does not have an associated state");

Check warning on line 80 in Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEvent.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEvent.cs#L80

Added line #L80 was not covered by tests

ref var deviceStateBlock = ref device.m_StateBlock;
ref var controlStateBlock = ref control.m_StateBlock;
Expand Down
5 changes: 5 additions & 0 deletions Packages/com.unity.inputsystem/InputSystem/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,11 @@ private void NotifyUsageChanged(InputDevice device)

////TODO: make sure that no device or control with a '/' in the name can creep into the system

internal bool HasDevice(InputDevice device)
{
return device.m_DeviceIndex < m_DevicesCount && ReferenceEquals(m_Devices[device.m_DeviceIndex], device);
}

public InputDevice AddDevice(Type type, string name = null)
{
if (type == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@
/// input and device discovery or removal notifications from platform code. This ensures
/// that while the test is running, input that may be generated on the machine running
/// the test will not infer with it.
///
/// Be cautious when using <c>NUnit.Framework.OneTimeSetUpAttribute</c> and
/// <c>NUnit.Framework.OneTimeTearDownAttribute</c> in combination with this test fixture.
/// For example, any devices created prior to execution of <see cref="Setup()"/> would be added to the actual
/// Input System instead of the test fixture system and after <see cref="Setup()"/> has executed such devices
/// will no longer be valid. You may of course use these NUnit features, but it is advised to not attempt affecting
/// the Input System under test from those methods since it would affect the real system and not the system
/// under test.
///
/// This test fixture is designed for play-mode tests and is generally not supported for edit-mode tests.
/// Both <c>[Test]</c> and <c>[UnityTest]</c> are supported, but only in play-mode.
/// </remarks>
public class InputTestFixture
{
Expand Down Expand Up @@ -533,6 +544,12 @@
/// Note that this parameter will be ignored if the test is a <c>[UnityTest]</c>. Multi-frame
/// playmode tests will automatically process input as part of the Unity player loop.</param>
/// <typeparam name="TValue">Value type of the given control.</typeparam>
/// <exception cref="ArgumentNullException">If control is null.</exception>
/// <exception cref="ArgumentException">If the device associated with <paramref name="control"/> has not
/// been added to the system or if the control does not have any associated state. The latter may only
/// happen if attempting to set a control of a device created outside the test context.</exception>
/// <exception cref="NotSupportedException">If attempting to set a control of a test device in an
/// editor assembly. [UnityTest] in editor assemblies is not supported by this test fixture.</exception>
/// <example>
/// <code>
/// var gamepad = InputSystem.AddDevice&lt;Gamepad&gt;();
Expand All @@ -544,12 +561,14 @@
{
if (control == null)
throw new ArgumentNullException(nameof(control));
if (!control.device.added)
throw new ArgumentException(
$"Device of control '{control}' has not been added to the system", nameof(control));
CheckValidity(control.device, control);

if (IsUnityTest())
{
if (IsEditMode())
throw new NotSupportedException("InputTestFixture.Set does not support edit mode (editor assembly) [UnityTest].");
queueEventOnly = true;
}

void SetUpAndQueueEvent(InputEventPtr eventPtr)
{
Expand Down Expand Up @@ -663,6 +682,7 @@
if (screen == null)
screen = InputSystem.AddDevice<Touchscreen>();
}
CheckValidity(screen);

InputSystem.QueueStateEvent(screen, new TouchState
{
Expand Down Expand Up @@ -913,6 +933,39 @@

#endif

private static void CheckValidity(InputDevice device, InputControl control)
{
if (!device.added)
{
throw new ArgumentException(

Check warning on line 940 in Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs#L939-L940

Added lines #L939 - L940 were not covered by tests
$"Device '{device}' has not been added to the system", nameof(device));
}

// Guards against a device from another scope being used. This is a direct way to evaluate whether
// the device is associated with the current manager state or not since device state isn't consistently
// pushed/popped in the current design.
var manager = InputSystem.s_Manager;
if (manager == null || !manager.HasDevice(device))
{
throw new ArgumentException($"Control '{control}' does not have any associated state. " +
"Make sure the control or device was added after executing Setup().", nameof(control));
}
}

private static void CheckValidity(InputControl control)
{
CheckValidity(control.device, control);
}

/// <summary>
/// Returns true if running inside an Edit Mode test (Editor assembly).
/// Returns false if running in Play Mode.
/// </summary>
private static bool IsEditMode()
{
return Application.isEditor && !Application.isPlaying;
}

#if UNITY_EDITOR
/// <summary>
/// Represents an analytics registration event captured by test harness.
Expand Down