Skip to content

Commit 734cfc9

Browse files
FIX: APIVerificationTests failing on scrapped API new format (#2391)
Co-authored-by: Morgan Hoarau <morgan.hoarau@unity3d.com> Co-authored-by: Morgan Hoarau <122548697+MorganHoarau@users.noreply.github.com>
1 parent deb5571 commit 734cfc9

1 file changed

Lines changed: 186 additions & 12 deletions

File tree

Assets/Tests/InputSystem/APIVerificationTests.cs

Lines changed: 186 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ public void API_MonoBehavioursHaveHelpUrls()
258258

259259
// The .api files are platform-specific so we can only compare on the platform
260260
// they were built on.
261-
#if UNITY_EDITOR_WIN
261+
#if UNITY_EDITOR_WIN
262262

263263
// We disable "API Verification" tests running as part of the validation suite as they give us
264264
// false positives (specifically, for setters having changes accessibility from private to protected).
@@ -541,6 +541,56 @@ public class DualShock4GamepadHID : DualShockGamepad
541541
public InputTestFixture.ActionConstraint Performed(InputAction action, InputControl control = default(InputControl), System.Nullable<double> time = default(System.Nullable<double>), System.Nullable<double> duration = default(System.Nullable<double>));
542542
public InputTestFixture.ActionConstraint Started(InputAction action, InputControl control = default(InputControl), System.Nullable<double> time = default(System.Nullable<double>));
543543
")]
544+
// API scraper output for these built-in XR controller types differs depending on installed XR replacement packages.
545+
[Property("Exclusions", @"1.0.0
546+
public class DaydreamController : UnityEngine.InputSystem.XR.XRController
547+
public class GearVRTrackedController : UnityEngine.InputSystem.XR.XRController
548+
public class OculusTouchController : UnityEngine.InputSystem.XR.XRControllerWithRumble
549+
public class HandedViveTracker : ViveTracker
550+
public class OpenVRControllerWMR : UnityEngine.InputSystem.XR.XRController
551+
public class OpenVROculusTouchController : UnityEngine.InputSystem.XR.XRControllerWithRumble
552+
public class ViveWand : UnityEngine.InputSystem.XR.XRControllerWithRumble
553+
")]
554+
// API scraper in 1.0.0 emitted incomplete default argument expressions for these overloads.
555+
[Property("Exclusions", @"1.0.0
556+
public static string GetBindingDisplayString(this InputAction action, int bindingIndex, InputBinding.DisplayStringOptions options = );
557+
public static string GetBindingDisplayString(this InputAction action, InputBinding bindingMask, InputBinding.DisplayStringOptions options = );
558+
public static string GetBindingDisplayString(this InputAction action, InputBinding.DisplayStringOptions options = , string group = default(string));
559+
public static string GetBindingDisplayString(this InputAction action, int bindingIndex, out string deviceLayoutName, out string controlPath, InputBinding.DisplayStringOptions options = );
560+
public string ToDisplayString(InputBinding.DisplayStringOptions options = , InputControl control = default(InputControl));
561+
public string ToDisplayString(out string deviceLayoutName, out string controlPath, InputBinding.DisplayStringOptions options = , InputControl control = default(InputControl));
562+
DontIncludeInteractions = 4,
563+
DontOmitDevice = 2,
564+
DontUseShortDisplayNames = 1,
565+
IgnoreBindingOverrides = 8,
566+
OmitDevice = 2,
567+
UseShortNames = 4,
568+
BufferedBytes = 256,
569+
Constant = 1,
570+
NonLinear = 16,
571+
NoPreferred = 32,
572+
NullState = 64,
573+
Relative = 4,
574+
Variable = 2,
575+
Volatile = 128,
576+
Wrap = 8,
577+
public FourCC(char a, char b = , char c = , char d = ) {}
578+
public static string ToHumanReadableString(string path, InputControlPath.HumanReadableStringOptions options = InputControlPath.HumanReadableStringOptions.None, InputControl control = default(InputControl));
579+
public static string ToHumanReadableString(string path, out string deviceLayoutName, out string controlPath, InputControlPath.HumanReadableStringOptions options = InputControlPath.HumanReadableStringOptions.None, InputControl control = default(InputControl));
580+
public class InputStateHistory<TValue> : InputStateHistory, System.Collections.Generic.IEnumerable<UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue>>, System.Collections.Generic.IReadOnlyCollection<UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue>>, System.Collections.Generic.IReadOnlyList<UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue>>, System.Collections.IEnumerable where TValue : struct, new()
581+
public UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> this[int index] { get; set; }
582+
public UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> AddRecord(UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> record);
583+
public System.Collections.Generic.IEnumerator<UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue>> GetEnumerator();
584+
public UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> RecordStateChange(UnityEngine.InputSystem.InputControl<TValue> control, TValue value, double time = -1d);
585+
public struct Record : System.IEquatable<UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue>>
586+
public UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> next { get; }
587+
public UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> owner { get; }
588+
public UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> previous { get; }
589+
public void CopyFrom(UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> record);
590+
public bool Equals(UnityEngine.InputSystem.LowLevel.InputStateHistory<TValue> other);
591+
public SteamHandle(ulong handle) {}
592+
public static ulong op_Explicit(UnityEngine.InputSystem.Steam.SteamHandle<TObject> handle);
593+
")]
544594
// Api scraper seems to be unstable with fields with default values, sometimes "= 0;" appears (locally) and sometimes (on CI) doesn't.
545595
[Property("Exclusions", @"1.0.0
546596
public int negative = 0;
@@ -555,6 +605,57 @@ public class DualShock4GamepadHID : DualShockGamepad
555605
[ScopedExclusionProperty("1.0.0", "UnityEngine.InputSystem.LowLevel", "public struct KeyboardState : IInputStateTypeInfo", "public fixed byte keys[14];")]
556606
// Allow Key.IMESelected to be marked as Obsolete
557607
[ScopedExclusionProperty("1.0.0", "UnityEngine.InputSystem", "public enum Key", "IMESelected = 111,")]
608+
609+
#if !UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
610+
// Steam support is conditional (#if UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT) and absent when
611+
// the steam plugin is not installed, so all Steam types are excluded from the comparison.
612+
[Property("Exclusions", @"1.0.0
613+
namespace UnityEngine.InputSystem.Steam
614+
public interface ISteamControllerAPI
615+
public void ActivateActionSet(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle, UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> actionSetHandle);
616+
public void ActivateActionSetLayer(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle, UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> actionSetLayerHandle);
617+
public void DeactivateActionSetLayer(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle, UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> actionSetLayerHandle);
618+
public void DeactivateAllActionSetLayers(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle);
619+
public UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> GetActionSetHandle(string actionSetName);
620+
public int GetActiveActionSetLayers(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle, out UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> handlesOut);
621+
public SteamAnalogActionData GetAnalogActionData(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle, UnityEngine.InputSystem.Steam.SteamHandle<InputAction> analogActionHandle);
622+
public UnityEngine.InputSystem.Steam.SteamHandle<InputAction> GetAnalogActionHandle(string actionName);
623+
public int GetConnectedControllers(UnityEngine.InputSystem.Steam.SteamHandle<SteamController>[] outHandles);
624+
public UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> GetCurrentActionSet(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle);
625+
public SteamDigitalActionData GetDigitalActionData(UnityEngine.InputSystem.Steam.SteamHandle<SteamController> controllerHandle, UnityEngine.InputSystem.Steam.SteamHandle<InputAction> digitalActionHandle);
626+
public UnityEngine.InputSystem.Steam.SteamHandle<InputAction> GetDigitalActionHandle(string actionName);
627+
public void RunFrame();
628+
public struct SteamAnalogActionData
629+
public bool active { get; set; }
630+
public Vector2 position { get; set; }
631+
public abstract class SteamController : InputDevice
632+
public bool autoActivateSets { get; set; }
633+
public UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> currentSteamActionSet { get; }
634+
public abstract UnityEngine.InputSystem.Utilities.ReadOnlyArray<SteamController.SteamActionSetInfo> steamActionSets { get; }
635+
public UnityEngine.InputSystem.Steam.SteamHandle<SteamController> steamControllerHandle { get; }
636+
protected SteamController() {}
637+
public void ActivateSteamActionSet(UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> actionSet);
638+
protected abstract void ResolveSteamActions(ISteamControllerAPI api);
639+
protected abstract void Update(ISteamControllerAPI api);
640+
public struct SteamActionSetInfo
641+
public UnityEngine.InputSystem.Steam.SteamHandle<InputActionMap> handle { get; set; }
642+
public struct SteamDigitalActionData
643+
public bool active { get; set; }
644+
public bool pressed { get; set; }
645+
public struct SteamHandle<TObject> : System.IEquatable<UnityEngine.InputSystem.Steam.SteamHandle<TObject>>
646+
public bool Equals(UnityEngine.InputSystem.Steam.SteamHandle<TObject> other);
647+
public static bool operator ==(UnityEngine.InputSystem.Steam.SteamHandle<TObject> a, UnityEngine.InputSystem.Steam.SteamHandle<TObject> b);
648+
public static bool operator !=(UnityEngine.InputSystem.Steam.SteamHandle<TObject> a, UnityEngine.InputSystem.Steam.SteamHandle<TObject> b);
649+
namespace UnityEngine.InputSystem.Steam.Editor
650+
public static class SteamIGAConverter
651+
public static string ConvertInputActionsToSteamIGA(System.Collections.Generic.IEnumerable<InputActionMap> actionMaps, string locale = @""english"");
652+
public static string ConvertInputActionsToSteamIGA(InputActionAsset asset, string locale = @""english"");
653+
public static string GenerateInputDeviceFromSteamIGA(string vdf, string namespaceAndClassName);
654+
public static string GetSteamControllerInputType(InputAction action);
655+
public static System.Collections.Generic.Dictionary<string, object> ParseVDF(string vdf);
656+
")]
657+
#endif
658+
558659
public void API_MinorVersionsHaveNoBreakingChanges()
559660
{
560661
var currentVersion = CoreTests.PackageJson.ReadVersion();
@@ -611,7 +712,7 @@ private static IEnumerable<string> MissingLines(string apiFile, string[] current
611712
var line = oldApiContents[i];
612713
if (line.Trim().StartsWith("{"))
613714
{
614-
scopeStack.Add(oldApiContents[i - 1]);
715+
scopeStack.Add(i > 0 ? oldApiContents[i - 1] : string.Empty);
615716
}
616717
else if (line.Trim().StartsWith("}"))
617718
{
@@ -623,33 +724,97 @@ private static IEnumerable<string> MissingLines(string apiFile, string[] current
623724
}
624725
}
625726

727+
// Matches hex literals (0xFF).
728+
private static readonly Regex s_HexLiteralRegex =
729+
new Regex(@"\b0x([0-9a-fA-F]+)\b", RegexOptions.Compiled);
730+
// Matches bitwise shift expressions (1 << 8).
731+
private static readonly Regex s_ShiftExprRegex =
732+
new Regex(@"\b(\d+) << (\d+)\b", RegexOptions.Compiled);
733+
626734
private static string FilterIgnoredChanges(string line)
627735
{
628736
if (line.Length == 0)
629737
return line;
630738

739+
// Older API scraper versions emitted fully-qualified C# primitive type names (System.UInt32),
740+
// while newer versions emit C# language aliases (uint). Normalize to aliases so that a scraper
741+
// version change does not produce false-positive breaking change reports.
742+
line = line
743+
.Replace("System.UInt64", "ulong")
744+
.Replace("System.UInt32", "uint")
745+
.Replace("System.UInt16", "ushort")
746+
.Replace("System.Int64", "long")
747+
.Replace("System.Int32", "int")
748+
.Replace("System.Int16", "short")
749+
.Replace("System.Boolean", "bool")
750+
.Replace("System.Single", "float")
751+
.Replace("System.Double", "double")
752+
.Replace("System.Byte", "byte")
753+
.Replace("System.SByte", "sbyte")
754+
.Replace("System.Char", "char");
755+
756+
// Normalize constant expressions that different scraper versions emit differently.
757+
// Older scrapers resolved expressions to decimal; newer scrapers may keep symbolic forms.
758+
line = line.Replace("uint.MaxValue", "4294967295")
759+
.Replace("uint.MinValue", "0");
760+
// Normalize hex literals (0xFF -> 255).
761+
line = s_HexLiteralRegex.Replace(line,
762+
m => Convert.ToUInt64(m.Groups[1].Value, 16).ToString());
763+
// Normalize bitwise shift expressions (1 << 8 -> 256).
764+
line = s_ShiftExprRegex.Replace(line,
765+
m => (ulong.Parse(m.Groups[1].Value) << int.Parse(m.Groups[2].Value)).ToString());
766+
631767
var pos = 0;
632768
while (true)
633769
{
634770
// Skip whitespace.
635771
while (pos < line.Length && char.IsWhiteSpace(line[pos]))
772+
{
636773
++pos;
774+
}
637775

638-
if (pos < line.Length && line[pos] != '[')
776+
if (pos >= line.Length || line[pos] != '[')
777+
{
639778
return line;
779+
}
640780

641781
var startPos = pos;
642782
++pos;
643-
while (pos < line.Length + 1 && !(line[pos] == ']' && line[pos + 1] == ' '))
644-
++pos;
645-
++pos;
646783

647-
var length = pos - startPos - 2;
648-
var attribute = line.Substring(startPos + 1, length);
649-
if (!attribute.StartsWith("System.Obsolete"))
784+
// Find the matching closing ']' using bracket depth tracking.
785+
// This correctly handles new[] syntax in attribute arguments:
786+
// [InputControl(aliases = new[] {@"a", @"b"})] public uint buttons;
787+
var depth = 1;
788+
while (pos < line.Length && depth > 0)
789+
{
790+
if (line[pos] == '[') depth++;
791+
else if (line[pos] == ']') depth--;
792+
if (depth > 0) ++pos;
793+
}
794+
795+
if (pos >= line.Length)
796+
{
797+
return line; // No matching ']' found, so out.
798+
}
799+
800+
++pos; // Move past the closing ']'.
801+
802+
// The attribute must be followed by a space.
803+
// If it is the last character there is nothing else to strip, so out.
804+
if (pos >= line.Length || line[pos] != ' ')
805+
return line;
806+
807+
// Extract the content between '[' and ']'.
808+
var closingBracket = pos - 1; // pos is now one past ']'
809+
var attributeContent = line.Substring(startPos + 1, closingBracket - startPos - 1);
810+
if (!attributeContent.StartsWith("System.Obsolete"))
650811
{
651812
line = line.Substring(0, startPos) + line.Substring(pos + 1); // Snip space after ']'.
652-
pos -= length + 2;
813+
pos = startPos;
814+
}
815+
else
816+
{
817+
++pos; // Skip the space after the kept Obsolete attribute.
653818
}
654819
}
655820
}
@@ -679,12 +844,21 @@ public bool IsMatch(List<string> scopeStack, string member)
679844
var namespaceScope = string.Empty;
680845
var typeScope = string.Empty;
681846

847+
// Walk inside-out so we pick up the innermost namespace and type scopes first.
682848
for (var i = scopeStack.Count - 1; i >= 0; i--)
683849
{
684850
if (scopeStack[i].StartsWith("namespace"))
685-
namespaceScope = scopeStack[i].Substring(scopeStack[i].IndexOf(' ') + 1);
686-
else
851+
{
852+
if (namespaceScope.Length == 0)
853+
namespaceScope = scopeStack[i].Substring(scopeStack[i].IndexOf(' ') + 1).Trim();
854+
}
855+
else if (typeScope.Length == 0)
856+
{
687857
typeScope = scopeStack[i].Trim();
858+
}
859+
860+
if (namespaceScope.Length > 0 && typeScope.Length > 0)
861+
break;
688862
}
689863

690864
return namespaceScope == Namespace && typeScope == Type && Members.Contains(member.Trim());

0 commit comments

Comments
 (0)