From 20fdf280284efc6003c463378aa2c48e69eed856 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 17 Mar 2026 12:52:50 +0100 Subject: [PATCH] [tests] Fix flaky RecordTest.DeskCase_83099_InmutableDictionary on macOS CI The test fails intermittently when the keychain has stale entries from previous runs, causing Query to return ItemNotFound while Add returns DuplicateItem. Fix the flakiness with three changes: - Clean up any stale keychain entry before the test starts - In SaveUserPassword, handle the DuplicateItem race by removing the stale entry and retrying the add - Wrap the test body in try/finally to ensure cleanup on failure - Fix the missing $ in TEST 3's interpolated string assertion Fixes https://github.com/dotnet/macios/issues/24860 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/monotouch-test/Security/RecordTest.cs | 61 +++++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/tests/monotouch-test/Security/RecordTest.cs b/tests/monotouch-test/Security/RecordTest.cs index 79e6aa29f7ee..af4a398bb6d4 100644 --- a/tests/monotouch-test/Security/RecordTest.cs +++ b/tests/monotouch-test/Security/RecordTest.cs @@ -310,29 +310,38 @@ public void DeskCase_83099_InmutableDictionary () { var testUsername = "testusername"; - //TEST 1: Save a keychain value - var test1 = SaveUserPassword (testUsername, "testValue1", out var queryCode, out var addCode, out var updateCode); - Assert.IsTrue (test1, $"Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}"); + // Clean up any stale keychain entries from previous test runs to avoid + // the keychain returning ItemNotFound on query but DuplicateItem on add. + ForceRemoveUserPassword (testUsername); - //TEST 2: Get the saved keychain value - var test2 = GetUserPassword (testUsername); - Assert.IsTrue (StringUtil.StringsEqual (test2, "testValue1", false)); + try { + //TEST 1: Save a keychain value + var test1 = SaveUserPassword (testUsername, "testValue1", out var queryCode, out var addCode, out var updateCode); + Assert.IsTrue (test1, $"Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}"); + + //TEST 2: Get the saved keychain value + var test2 = GetUserPassword (testUsername); + Assert.IsTrue (StringUtil.StringsEqual (test2, "testValue1", false)); - //TEST 3: Update the keychain value - var test3 = SaveUserPassword (testUsername, "testValue2", out queryCode, out addCode, out updateCode); - Assert.IsTrue (test3, "Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}"); + //TEST 3: Update the keychain value + var test3 = SaveUserPassword (testUsername, "testValue2", out queryCode, out addCode, out updateCode); + Assert.IsTrue (test3, $"Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}"); - //TEST 4: Get the updated keychain value - var test4 = GetUserPassword (testUsername); - Assert.IsTrue (StringUtil.StringsEqual (test4, "testValue2", false)); + //TEST 4: Get the updated keychain value + var test4 = GetUserPassword (testUsername); + Assert.IsTrue (StringUtil.StringsEqual (test4, "testValue2", false)); - //TEST 5: Clear the keychain values - var test5 = ClearUserPassword (testUsername); - Assert.IsTrue (test5, "Password could not be cleared from keychain"); + //TEST 5: Clear the keychain values + var test5 = ClearUserPassword (testUsername); + Assert.IsTrue (test5, "Password could not be cleared from keychain"); - //TEST 6: Verify no keychain value - var test6 = GetUserPassword (testUsername); - Assert.IsNull (test6, "No password should exist here"); + //TEST 6: Verify no keychain value + var test6 = GetUserPassword (testUsername); + Assert.IsNull (test6, "No password should exist here"); + } finally { + // Always clean up to avoid leaving stale entries for subsequent runs + ForceRemoveUserPassword (testUsername); + } } public static string GetUserPassword (string username) @@ -367,6 +376,13 @@ record = CreateSecRecord (SecKind.InternetPassword, ); addCode = SecKeyChain.Add (record); success = (addCode == SecStatusCode.Success); + // Handle inconsistent keychain state: query returned ItemNotFound + // but add returned DuplicateItem. Force-remove and retry. + if (addCode == SecStatusCode.DuplicateItem) { + SecKeyChain.Remove (searchRecord); + addCode = SecKeyChain.Add (record); + success = (addCode == SecStatusCode.Success); + } } if (queryCode == SecStatusCode.Success && record is not null) { record.ValueData = NSData.FromString (password); @@ -376,6 +392,15 @@ record = CreateSecRecord (SecKind.InternetPassword, return success; } + public static void ForceRemoveUserPassword (string username) + { + var searchRecord = CreateSecRecord (SecKind.InternetPassword, + server: "Test1", + account: username.ToLower () + ); + SecKeyChain.Remove (searchRecord); + } + public static bool ClearUserPassword (string username) { var success = false;