diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index dc057a1e03e..6e462210d7f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -188,3 +188,4 @@ This pattern ensures proper encoding, timestamps, and file attributes are handle - **MSBuild:** Test in isolation, validate inputs - **Device:** Use update directories for rapid Debug iteration - **Performance:** See `../Documentation/guides/profiling.md` and `../Documentation/guides/tracing.md` +- **CI failures:** Use the `azdo-build-investigator` skill to investigate Azure DevOps pipeline failures. The `az` CLI tool is available and authenticated — use it to fetch build timelines, download logs, and analyze `.binlog` artifacts from failing CI runs. This applies to both the public (`dev.azure.com/dnceng-public`) and internal (`devdiv.visualstudio.com`) pipelines. diff --git a/Configuration.props b/Configuration.props index 4239731a034..6196c65c032 100644 --- a/Configuration.props +++ b/Configuration.props @@ -166,8 +166,8 @@ 13114758_latest $(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder)\bin - 9364964 - 32.1.9 + 15004761 + 36.4.10 $(AndroidSdkFullPath)\emulator emulator emulator.exe diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs index 6683807633e..fdfa92d142a 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs @@ -267,22 +267,96 @@ public void SetArrayItem_JavaLangString () public void GetObjectArray () { using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + string jniClassName = JNIEnv.GetClassNameFromInstance (byteArray.Handle); + int jniArrayLength = JNIEnv.GetArrayLength (byteArray.Handle); + Log ($"GetObjectArray byte[]: JNI class='{jniClassName}', length={jniArrayLength}"); + object[] data = JNIEnv.GetObjectArray (byteArray.Handle, new[]{typeof (byte), typeof (byte), typeof (byte)}); + Log ($"GetObjectArray byte[]: data.Length={data?.Length}"); + if (data != null) { + for (int i = 0; i < data.Length; i++) + Log ($"GetObjectArray byte[]: data[{i}] = {data [i]} (type: {data [i]?.GetType ()})"); + } AssertArrays ("GetObjectArray", data, (object) 1, (object) 2, (object) 3); } + + // Collect all diagnostics before asserting so we can see the full picture in CI + var diagnostics = new System.Text.StringBuilder (); + object[]? values = null; + Exception? marshalingException = null; + using (var objectArray = new Java.Lang.Object ( JNIEnv.NewArray ( new Java.Lang.Object[]{Application.Context, 42L, "string"}, typeof (Java.Lang.Object)), JniHandleOwnership.TransferLocalRef)) { - object[] values = JNIEnv.GetObjectArray (objectArray.Handle, new[]{typeof(Context), typeof (int)}); - Assert.AreEqual (3, values.Length); - Assert.IsTrue (object.ReferenceEquals (values [0], Application.Context)); - Assert.IsTrue (values [1] is int); - Assert.AreEqual (42, (int)values [1]); - Assert.AreEqual ("string", values [2].ToString ()); + string jniClassName = JNIEnv.GetClassNameFromInstance (objectArray.Handle); + int jniArrayLength = JNIEnv.GetArrayLength (objectArray.Handle); + diagnostics.AppendLine ($"mixed[]: JNI class='{jniClassName}', length={jniArrayLength}"); + + for (int i = 0; i < jniArrayLength; i++) { + IntPtr elemHandle = JNIEnv.GetObjectArrayElement (objectArray.Handle, i); + string elemClass = elemHandle != IntPtr.Zero ? JNIEnv.GetClassNameFromInstance (elemHandle) : "(null)"; + diagnostics.AppendLine ($" raw[{i}] JNI class='{elemClass}', handle=0x{elemHandle:x}"); + JNIEnv.DeleteLocalRef (elemHandle); + } + + try { + values = JNIEnv.GetObjectArray (objectArray.Handle, new[]{typeof(Context), typeof (int)}); + } catch (Exception ex) { + marshalingException = ex; + } } + + if (marshalingException != null) { + diagnostics.AppendLine ($"GetObjectArray THREW: {marshalingException}"); + Log (diagnostics.ToString ()); + Assert.Fail ($"GetObjectArray threw: {marshalingException.Message}\n{diagnostics}"); + return; + } + + diagnostics.AppendLine ($"values.Length={values?.Length}"); + if (values != null) { + for (int i = 0; i < values.Length; i++) { + var v = values [i]; + diagnostics.AppendLine ($" values[{i}] = {v} (type: {v?.GetType ()}, IJavaPeerable: {v is Java.Interop.IJavaPeerable})"); + } + + if (values.Length >= 1) { + bool refEqual = object.ReferenceEquals (values [0], Application.Context); + diagnostics.AppendLine ($" ReferenceEquals(values[0], Context) = {refEqual}"); + diagnostics.AppendLine ($" values[0] type = {values [0]?.GetType ()}, hash = {values [0]?.GetHashCode ()}"); + diagnostics.AppendLine ($" Context type = {Application.Context?.GetType ()}, hash = {Application.Context?.GetHashCode ()}"); + } + if (values.Length >= 2) { + diagnostics.AppendLine ($" values[1] is int = {values [1] is int}"); + if (values [1] is Java.Interop.IJavaPeerable jp) + diagnostics.AppendLine ($" values[1] peer JNI type = {JNIEnv.GetClassNameFromInstance (jp.PeerReference.Handle)}"); + } + if (values.Length >= 3) { + diagnostics.AppendLine ($" values[2].ToString() = '{values [2]}'"); + } + } + + string diag = diagnostics.ToString (); + Log (diag); + + // Now assert with full diagnostic context + Assert.AreEqual (3, values!.Length, $"Expected 3 elements\n{diag}"); + Assert.IsTrue (object.ReferenceEquals (values [0], Application.Context), + $"values[0] should be ReferenceEquals to Application.Context\n{diag}"); + Assert.IsTrue (values [1] is int, + $"values[1] should be int\n{diag}"); + Assert.AreEqual (42, (int)values [1], + $"values[1] should be 42\n{diag}"); + Assert.AreEqual ("string", values [2].ToString (), + $"values[2] should be 'string'\n{diag}"); + } + + static void Log (string message) + { + Console.WriteLine (message); } [Test] @@ -354,9 +428,11 @@ public void BoundArrayPropertiesHaveSetters () static void AssertArrays (string message, IList actual, params T[] expected) { - Assert.AreEqual (expected.Length, actual.Count, message); + Assert.AreEqual (expected.Length, actual.Count, + $"{message}: expected length {expected.Length}, got {actual.Count}"); for (int i = 0; i < expected.Length; ++i) - Assert.AreEqual (expected [i], actual [i], message); + Assert.AreEqual (expected [i], actual [i], + $"{message}[{i}]: expected '{expected [i]}' ({expected [i]?.GetType ()}), got '{actual [i]}' ({actual [i]?.GetType ()})"); } } }