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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System;
using System.Collections;
using System.Linq;
using NUnit.Framework;
using UnityEngine.InputSystem;
using UnityEngine.TestTools;

/// <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 correctlyUsedDevice;
private Keyboard incorrectlyUsedDevice;

[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);
}

[OneTimeTearDown]
public void UnityTearDown()
{
// Once InputTestFixture.TearDown() has executed, the state stack will have been popped and the keyboard
// we added before entering the test fixture should have been restored.
Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), 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);

// 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();
correctlyUsedDevice = InputSystem.AddDevice<Keyboard>();

// 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);
}

[TearDown]
public override void TearDown()
{
// This is expected usage pattern, we might want to do something with the device here, but it needs
// to happen before base.TearDown() since it would delete the fake device.
Assert.That(InputSystem.devices.Contains(correctlyUsedDevice), Is.True);
InputSystem.RemoveDevice(correctlyUsedDevice);

// 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(correctlyUsedDevice), Is.False);
}

#region Editor playmode tests

[Test]
public void Press_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
Press(correctlyUsedDevice.spaceKey);
Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.True);
}

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

[Test]
public void Release_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
Press(correctlyUsedDevice.spaceKey);
Release(correctlyUsedDevice.spaceKey);
Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.False);
}

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

[Test]
public void PressAndRelease_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
PressAndRelease(correctlyUsedDevice.spaceKey);
Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.False);
}

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

[Test]
public void Click_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
{
Click(correctlyUsedDevice.spaceKey);
Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.False);
}

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

// TODO Add remaining

#endregion // Playmode tests

#region // Edit-mode tests

[UnityTest]
public IEnumerator Press_ShouldMutateDeviceState_WithinEditModeTestFixtureContext()
{
Press(correctlyUsedDevice.spaceKey);
yield return null; // Need to yield control to see change in next frame when running in edit-mode
Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.True);
}

// TODO Add remaining tests

#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.

4 changes: 3 additions & 1 deletion Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ however, it has to be formatted properly to pass verification tests.

## [Unreleased] - yyyy-mm-dd


### Fixed
- Fixed an issue in `DeltaStateEvent.From` where unsafe code would throw exception or crash if internal pointer `currentStatePtr` would be `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 @@ -996,6 +996,9 @@ protected internal uint stateOffsetRelativeToDeviceRoot
}
}

// Allows determining if the device has an associated state pointer
internal unsafe bool hasState => currentStatePtr != null;

// This data is initialized by InputDeviceBuilder.
internal InternedString m_Name;
internal string m_Path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public static NativeArray<byte> From(InputControl control, out InputEventPtr eve
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}' do not have an associated state");

ref var deviceStateBlock = ref device.m_StateBlock;
ref var controlStateBlock = ref control.m_StateBlock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ namespace UnityEngine.InputSystem
/// 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 <see cref="NUnit.Framework.OneTimeSetUpAttribute"/> and
/// <see cref="NUnit.Framework.OneTimeTearDownAttribute" /> 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.
/// </remarks>
public class InputTestFixture
{
Expand Down Expand Up @@ -545,8 +553,15 @@ public void Set<TValue>(InputControl<TValue> control, TValue state, double time
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));
}
if (!control.hasState)
{
throw new ArgumentException($"Control '{control}' does not have any associated state. " +
"Make sure the control or device was added after executing Setup().", nameof(control));
}

if (IsUnityTest())
queueEventOnly = true;
Expand Down