Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flaky test: cursorconverter_convertto_unknowntoinstancedescriptor_throwsnotsupportedexception #8373

Closed
Tracked by #8607
runfoapp bot opened this issue Dec 13, 2022 · 5 comments · Fixed by #8865
Closed
Tracked by #8607

Comments

@runfoapp
Copy link

runfoapp bot commented Dec 13, 2022

Runfo Tracking Issue: Flaky test: cursorconverter_convertto_unknowntoinstancedescriptor_throwsnotsupportedexception

Build Definition Kind Run Name
207837 dotnet-winforms CI PR 8865 Windows_x64-xunit
207837 dotnet-winforms CI PR 8865 Windows_arm64-xunit
207837 dotnet-winforms CI PR 8865 Windows_x64-xunit
207837 dotnet-winforms CI PR 8865 Windows_x86-xunit

Build Result Summary

Day Hit Count Week Hit Count Month Hit Count
1 1 1
@elachlan
Copy link
Contributor

Here is the exception from the build. The CursorConverter.ConvertTo has the following comment about NotSupportedException:

/// <summary>
/// Converts the given object to another type. The most common types to convert
/// are to and from a string object. The default implementation will make a call
/// to ToString on the object if the object is valid and if the destination
/// type is string. If this cannot convert to the destination type, this will
/// throw a NotSupportedException.
/// </summary>
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)

This is the failing test:

[Fact]
public void CursorConverter_ConvertTo_UnknownToInstanceDescriptor_ThrowsNotSupportedException()
{
var converter = new CursorConverter();
Assert.Throws<NotSupportedException>(() => converter.ConvertTo(new Cursor((IntPtr)2), typeof(InstanceDescriptor)));
}

So somehow during debug locally the CursorConverter throws NotSupportedException, but in CI it somehow doesn't trigger that failure path and somehow gets to loading the cursor in Cursor.LoadPicture. It looks like the CursorConverter is converting the IntPtr to HSplit for whatever reason.

Error message

Assert.Throws() Failure\r\nExpected: typeof(System.NotSupportedException)\r\nActual:   typeof(System.Reflection.TargetInvocationException): Exception has been thrown by the target of an invocation.\r\n---- System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.\r\n-------- System.ArgumentException : Image format is not valid. The image file may be corrupted. (Parameter 'stream')\r\n------------ System.Runtime.InteropServices.COMException : Error HRESULT E_FAIL has been returned from a call to a COM component.

Stack Trace

   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at System.Windows.Forms.CursorConverter.ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType) in /_/src/System.Windows.Forms/src/System/Windows/Forms/CursorConverter.cs:line 129
   at System.ComponentModel.TypeConverter.ConvertTo(Object value, Type destinationType)
   at System.Windows.Forms.Tests.CursorConverterTests.<>c__DisplayClass10_0.<CursorConverter_ConvertTo_UnknownToInstanceDescriptor_ThrowsNotSupportedException>b__0() in /_/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/CursorConverterTests.cs:line 117
----- Inner Stack Trace -----
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at System.Windows.Forms.CursorConverter.ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType) in /_/src/System.Windows.Forms/src/System/Windows/Forms/CursorConverter.cs:line 129
   at System.ComponentModel.TypeConverter.ConvertTo(Object value, Type destinationType)
   at System.Windows.Forms.Tests.CursorConverterTests.<>c__DisplayClass10_0.<CursorConverter_ConvertTo_UnknownToInstanceDescriptor_ThrowsNotSupportedException>b__0() in /_/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/CursorConverterTests.cs:line 117
----- Inner Stack Trace -----
   at System.Windows.Forms.Cursor.LoadPicture(Interface stream, String paramName) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 437
   at System.Windows.Forms.Cursor..ctor(Stream stream) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 90
   at System.Windows.Forms.Cursor..ctor(Type type, String resource) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 73
   at System.Windows.Forms.Cursors.get_HSplit() in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursors.cs:line 69
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
----- Inner Stack Trace -----
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode, IntPtr errorInfo)
   at Windows.Win32.Foundation.HRESULT.ThrowOnFailure(IntPtr errorInfo) in /_/src/System.Windows.Forms.Primitives/src/Microsoft.Windows.CsWin32/Microsoft.Windows.CsWin32.SourceGenerator/Windows.Win32.HRESULT.g.cs:line 63
   at Windows.Win32.System.Com.IPersistStream.Load(IStream* pStm) in /_/src/System.Windows.Forms.Primitives/src/Microsoft.Windows.CsWin32/Microsoft.Windows.CsWin32.SourceGenerator/Windows.Win32.IPersistStream.g.cs:line 156
   at System.Windows.Forms.Cursor.LoadPicture(Interface stream, String paramName) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 410

@elachlan
Copy link
Contributor

Ok I have been digging into this a bit more.

The code hits this if block and checks each property on Cursors to see if its equal to the cursor passed to the typeconverter.

else if (destinationType == typeof(InstanceDescriptor))
{
PropertyInfo[] props = GetProperties();
foreach (PropertyInfo prop in props)
{
if (prop.GetValue(null, null) == value)
{
return new InstanceDescriptor(prop, null);
}
}
}

For this test it would usually exhaust the search and hit the base case. But occasionally in CI, it errors out when accessing the Cursors.HSplit property.

Its failing with the following:

   at System.Windows.Forms.Cursor.LoadPicture(Interface stream, String paramName) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 437
   at System.Windows.Forms.Cursor..ctor(Stream stream) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 90
   at System.Windows.Forms.Cursor..ctor(Type type, String resource) in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs:line 73
   at System.Windows.Forms.Cursors.get_HSplit() in /_/src/System.Windows.Forms/src/System/Windows/Forms/Cursors.cs:line 69

The associated constructor for Cursor that takes stream has a null check on stream. So the stream is being passed in okay, but erroring out when we hit LoadPicture. It fails on IPersistStream.Load(). The failure indicated is HRESULT E_FAIL, meaning we aren't out of memory.

https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersiststream-load

I believe the issue might be with the constructor and the stream parameters position. We haven't checked its at 0.

@elachlan
Copy link
Contributor

I wrote a test to pass a stream with a position other than zero to validate this, I get an CTL_E_INVALIDPICTURE not E_FAIL. I also tested passing an empty stream, which also results in CTL_E_INVALIDPICTURE.

[Fact]
public void CursorConverter_ConvertTo_Test()
{
    Type type = typeof(Cursor);
    var stream = type.Module.Assembly.GetManifestResourceStream(type, "hsplit.cur");
    stream.Position = 5;
    Cursor cursor = new Cursor(stream);
}

Stack trace:

System.ArgumentException
  HResult=0x80070057
  Message=Image format is not valid. The image file may be corrupted.
  Source=System.Windows.Forms
  StackTrace:
   at System.Windows.Forms.Cursor.LoadPicture(Interface stream, String paramName) in D:\Development\winforms\src\System.Windows.Forms\src\System\Windows\Forms\Cursor.cs:line 439

  This exception was originally thrown at this call stack:
    [External Code]
    Windows.Win32.Foundation.HRESULT.ThrowOnFailure(nint) in Windows.Win32.HRESULT.g.cs
    Windows.Win32.System.Com.IPersistStream.Load(Windows.Win32.System.Com.IStream*) in Windows.Win32.IPersistStream.g.cs
    System.Windows.Forms.Cursor.LoadPicture(Windows.Win32.System.Com.IStream.Interface, string) in Cursor.cs

Inner Exception 1:
COMException: 0x800A01E1 (CTL_E_INVALIDPICTURE)

@ghost ghost added 🚧 work in progress Work that is current in progress and removed 🚧 work in progress Work that is current in progress labels Jan 10, 2023
@Tanya-Solyanik
Copy link
Member

Tanya-Solyanik commented Jan 19, 2023

The first step to stabilize cursor tests might be to Dispose of all cursor objects that the test news up. Next might be to make them WinformsFacts for thread affinity.

@Tanya-Solyanik
Copy link
Member

Suggestion from @JeremyKuhne - see what other tests are using the same image files We shouldn't access the same files from different threads. Maybe create dedicated images for each test? Then we don't have to use WinformsFact.

@ghost ghost added 🚧 work in progress Work that is current in progress and removed 🚧 work in progress Work that is current in progress labels Mar 14, 2023
@ghost ghost added the 🚧 work in progress Work that is current in progress label Mar 16, 2023
@ghost ghost removed the 🚧 work in progress Work that is current in progress label Mar 17, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Apr 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
2 participants