1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Globalization;
6 using System.Runtime.InteropServices;
7 using System.Runtime.Serialization.Formatters.Tests;
8 using System.Text;
9 using Xunit;
10 
11 namespace System.ComponentModel.Tests
12 {
13     public static partial class Win32ExceptionTestType
14     {
15         private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
16         private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
17         private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
18         private const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
19         private const int FirstPassBufferSize = 256;
20         private const int E_FAIL = unchecked((int)0x80004005);
21 
22         [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "FormatMessageW", SetLastError = true, BestFitMapping = true)]
FormatMessage( int dwFlags, IntPtr lpSource_mustBeNull, uint dwMessageId, int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr[] arguments)23         private static extern int FormatMessage(
24             int dwFlags,
25             IntPtr lpSource_mustBeNull,
26             uint dwMessageId,
27             int dwLanguageId,
28             StringBuilder lpBuffer,
29             int nSize,
30             IntPtr[] arguments);
31 
IsExceptionMessageLong(int errorCode)32         private static bool IsExceptionMessageLong(int errorCode)
33         {
34             StringBuilder sb = new StringBuilder(FirstPassBufferSize); // Buffer length in the first pass in the implementation.
35 
36             int result = FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
37                                        FORMAT_MESSAGE_FROM_SYSTEM |
38                                        FORMAT_MESSAGE_ARGUMENT_ARRAY,
39                                        IntPtr.Zero, (uint)errorCode, 0, sb, sb.Capacity,
40                                        null);
41             if (result == 0)
42             {
43                 return (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER);
44             }
45 
46             return false;
47         }
48 
49         [Fact]
InstantiateException()50         public static void InstantiateException()
51         {
52             int error = 5;
53             string message = "This is an error message.";
54             string toStringStart = string.Format(
55                 CultureInfo.InvariantCulture,
56                 "{0} ({1})",
57                 typeof(Win32Exception).ToString(),
58                 PlatformDetection.IsFullFramework ? $"0x{E_FAIL:X8}" : error.ToString(CultureInfo.InvariantCulture));
59 
60             Exception innerException = new FormatException();
61 
62             // Test each of the constructors and validate the properties of the resulting instance
63 
64             Win32Exception ex = new Win32Exception();
65             Assert.Equal(expected: E_FAIL, actual: ex.HResult);
66 
67             ex = new Win32Exception(error);
68             Assert.Equal(expected: E_FAIL, actual: ex.HResult);
69             Assert.Equal(expected: error, actual: ex.NativeErrorCode);
70             Assert.StartsWith(expectedStartString: toStringStart, actualString: ex.ToString(), comparisonType: StringComparison.Ordinal);
71 
72             ex = new Win32Exception(message);
73             Assert.Equal(expected: E_FAIL, actual: ex.HResult);
74             Assert.Equal(expected: message, actual: ex.Message);
75 
76             ex = new Win32Exception(error, message);
77             Assert.Equal(expected: E_FAIL, actual: ex.HResult);
78             Assert.Equal(expected: error, actual: ex.NativeErrorCode);
79             Assert.Equal(expected: message, actual: ex.Message);
80             Assert.StartsWith(expectedStartString: toStringStart, actualString: ex.ToString(), comparisonType: StringComparison.Ordinal);
81 
82             ex = new Win32Exception(message, innerException);
83             Assert.Equal(expected: E_FAIL, actual: ex.HResult);
84             Assert.Equal(expected: message, actual: ex.Message);
85             Assert.Same(expected: innerException, actual: ex.InnerException);
86         }
87 
88         [Fact]
89         [PlatformSpecific(TestPlatforms.Windows)]  // Uses P/Invokes to check whether the exception resource length >256 chars
InstantiateExceptionWithLongErrorString()90         public static void InstantiateExceptionWithLongErrorString()
91         {
92             // This test checks that Win32Exception supports error strings greater than 256 characters.
93             // Since we will have to rely on a message associated with an error code,
94             // we try to reduce the flakiness by doing the following.
95             // 1. Call FormatMessage to check whether the exception resource length >256 chars.
96             // 2. If true, we validate that Win32Exception class can retrieve the complete resource string.
97             // 3. If not we skip testing.
98             int errorCode = 0x268;
99             if (IsExceptionMessageLong(errorCode)) // Localized error string for 0x268 is not guaranteed to be >256 chars.
100             {
101                 Win32Exception ex = new Win32Exception(errorCode);
102                 Assert.NotEqual("Unknown error (0x268)", ex.Message);
103                 Assert.True(ex.Message.Length > FirstPassBufferSize);
104 
105                 ex = new Win32Exception(0x23);
106                 Assert.Equal(expected: "Unknown error (0x23)", actual: ex.Message);
107             }
108         }
109     }
110 }
111