OSDN Git Service

TwitterApiConnection.GetAsyncメソッドを削除
[opentween/open-tween.git] / OpenTween.Tests / Connection / TwitterApiConnectionTest.cs
index a897a9f..8960fce 100644 (file)
@@ -29,6 +29,7 @@ using System.Net.Http.Headers;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Web;
 using Moq;
@@ -41,11 +42,9 @@ namespace OpenTween.Connection
     public class TwitterApiConnectionTest
     {
         public TwitterApiConnectionTest()
-        {
-            this.MyCommonSetup();
-        }
+            => this.MyCommonSetup();
 
-        public void MyCommonSetup()
+        private void MyCommonSetup()
         {
             var mockAssembly = new Mock<_Assembly>();
             mockAssembly.Setup(m => m.GetName()).Returns(new AssemblyName("OpenTween"));
@@ -54,444 +53,227 @@ namespace OpenTween.Connection
         }
 
         [Fact]
-        public async Task GetAsync_Test()
+        public async Task SendAsync_Test()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            {
-                apiConnection.http = http;
+            using var mockHandler = new HttpMessageHandlerMock();
+            using var http = new HttpClient(mockHandler);
+            using var apiConnection = new TwitterApiConnection();
+            apiConnection.Http = http;
 
-                mockHandler.Enqueue(x =>
-                {
-                    Assert.Equal(HttpMethod.Get, x.Method);
-                    Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                        x.RequestUri.GetLeftPart(UriPartial.Path));
-
-                    var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
+            mockHandler.Enqueue(x =>
+            {
+                Assert.Equal(HttpMethod.Get, x.Method);
+                Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
+                    x.RequestUri.GetLeftPart(UriPartial.Path));
 
-                    Assert.Equal("1111", query["aaaa"]);
-                    Assert.Equal("2222", query["bbbb"]);
+                var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
 
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Content = new StringContent("\"hogehoge\""),
-                    };
-                });
+                Assert.Equal("1111", query["aaaa"]);
+                Assert.Equal("2222", query["bbbb"]);
 
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-                var param = new Dictionary<string, string>
+                return new HttpResponseMessage(HttpStatusCode.OK)
                 {
-                    ["aaaa"] = "1111",
-                    ["bbbb"] = "2222",
+                    Content = new StringContent("\"hogehoge\""),
                 };
+            });
 
-                var result = await apiConnection.GetAsync<string>(endpoint, param, endpointName: "/hoge/tetete")
-                    .ConfigureAwait(false);
-                Assert.Equal("hogehoge", result);
-
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
-        }
-
-        [Fact]
-        public async Task GetAsync_AbsoluteUriTest()
-        {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
+            var request = new GetRequest
             {
-                apiConnection.http = http;
-
-                mockHandler.Enqueue(x =>
-                {
-                    Assert.Equal(HttpMethod.Get, x.Method);
-                    Assert.Equal("http://example.com/hoge/tetete.json",
-                        x.RequestUri.GetLeftPart(UriPartial.Path));
-
-                    var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
-
-                    Assert.Equal("1111", query["aaaa"]);
-                    Assert.Equal("2222", query["bbbb"]);
-
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Content = new StringContent("\"hogehoge\""),
-                    };
-                });
-
-                var endpoint = new Uri("http://example.com/hoge/tetete.json", UriKind.Absolute);
-                var param = new Dictionary<string, string>
+                RequestUri = new("hoge/tetete.json", UriKind.Relative),
+                Query = new Dictionary<string, string>
                 {
                     ["aaaa"] = "1111",
                     ["bbbb"] = "2222",
-                };
-
-                await apiConnection.GetAsync<string>(endpoint, param, endpointName: "/hoge/tetete")
-                    .ConfigureAwait(false);
-
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
-        }
+                },
+                EndpointName = "/hoge/tetete",
+            };
 
-        [Fact]
-        public async Task GetAsync_UpdateRateLimitTest()
-        {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            {
-                apiConnection.http = http;
-
-                mockHandler.Enqueue(x =>
-                {
-                    Assert.Equal(HttpMethod.Get, x.Method);
-                    Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                        x.RequestUri.GetLeftPart(UriPartial.Path));
-
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Headers =
-                        {
-                            { "X-Rate-Limit-Limit", "150" },
-                            { "X-Rate-Limit-Remaining", "100" },
-                            { "X-Rate-Limit-Reset", "1356998400" },
-                            { "X-Access-Level", "read-write-directmessages" },
-                        },
-                        Content = new StringContent("\"hogehoge\""),
-                    };
-                });
+            using var response = await apiConnection.SendAsync(request);
 
-                var apiStatus = new TwitterApiStatus();
-                MyCommon.TwitterApiInfo = apiStatus;
+            Assert.Equal("hogehoge", await response.ReadAsJson<string>());
 
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-
-                await apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete")
-                    .ConfigureAwait(false);
-
-                Assert.Equal(apiStatus.AccessLevel, TwitterApiAccessLevel.ReadWriteAndDirectMessage);
-                Assert.Equal(apiStatus.AccessLimit["/hoge/tetete"], new ApiLimit(150, 100, new DateTime(2013, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime()));
-
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
+            Assert.Equal(0, mockHandler.QueueCount);
         }
 
         [Fact]
-        public async Task GetAsync_ErrorStatusTest()
+        public async Task SendAsync_UpdateRateLimitTest()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            {
-                apiConnection.http = http;
+            using var mockHandler = new HttpMessageHandlerMock();
+            using var http = new HttpClient(mockHandler);
+            using var apiConnection = new TwitterApiConnection();
+            apiConnection.Http = http;
 
-                mockHandler.Enqueue(x =>
-                {
-                    return new HttpResponseMessage(HttpStatusCode.BadGateway)
-                    {
-                        Content = new StringContent("### Invalid JSON Response ###"),
-                    };
-                });
-
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-
-                var exception = await Assert.ThrowsAsync<TwitterApiException>(() => apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete"))
-                    .ConfigureAwait(false);
-
-                // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する
-                Assert.Equal("BadGateway", exception.Message);
-                Assert.Null(exception.ErrorResponse);
-
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
-        }
-
-        [Fact]
-        public async Task GetAsync_ErrorJsonTest()
-        {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
+            mockHandler.Enqueue(x =>
             {
-                apiConnection.http = http;
+                Assert.Equal(HttpMethod.Get, x.Method);
+                Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
+                    x.RequestUri.GetLeftPart(UriPartial.Path));
 
-                mockHandler.Enqueue(x =>
+                return new HttpResponseMessage(HttpStatusCode.OK)
                 {
-                    return new HttpResponseMessage(HttpStatusCode.Forbidden)
+                    Headers =
                     {
-                        Content = new StringContent("{\"errors\":[{\"code\":187,\"message\":\"Status is a duplicate.\"}]}"),
-                    };
-                });
+                        { "X-Rate-Limit-Limit", "150" },
+                        { "X-Rate-Limit-Remaining", "100" },
+                        { "X-Rate-Limit-Reset", "1356998400" },
+                        { "X-Access-Level", "read-write-directmessages" },
+                    },
+                    Content = new StringContent("\"hogehoge\""),
+                };
+            });
 
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
+            var apiStatus = new TwitterApiStatus();
+            MyCommon.TwitterApiInfo = apiStatus;
 
-                var exception = await Assert.ThrowsAsync<TwitterApiException>(() => apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete"))
-                    .ConfigureAwait(false);
+            var request = new GetRequest
+            {
+                RequestUri = new("hoge/tetete.json", UriKind.Relative),
+                EndpointName = "/hoge/tetete",
+            };
 
-                // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する
-                Assert.Equal("DuplicateStatus", exception.Message);
+            using var response = await apiConnection.SendAsync(request);
 
-                Assert.Equal(TwitterErrorCode.DuplicateStatus, exception.ErrorResponse.Errors[0].Code);
-                Assert.Equal("Status is a duplicate.", exception.ErrorResponse.Errors[0].Message);
+            Assert.Equal(TwitterApiAccessLevel.ReadWriteAndDirectMessage, apiStatus.AccessLevel);
+            Assert.Equal(new ApiLimit(150, 100, new DateTimeUtc(2013, 1, 1, 0, 0, 0)), apiStatus.AccessLimit["/hoge/tetete"]);
 
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
+            Assert.Equal(0, mockHandler.QueueCount);
         }
 
         [Fact]
-        public async Task GetStreamAsync_Test()
+        public async Task SendAsync_ErrorStatusTest()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            using (var image = TestUtils.CreateDummyImage())
-            {
-                apiConnection.http = http;
+            using var mockHandler = new HttpMessageHandlerMock();
+            using var http = new HttpClient(mockHandler);
+            using var apiConnection = new TwitterApiConnection();
+            apiConnection.Http = http;
 
-                mockHandler.Enqueue(x =>
-                {
-                    Assert.Equal(HttpMethod.Get, x.Method);
-                    Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                        x.RequestUri.GetLeftPart(UriPartial.Path));
-
-                    var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
-
-                    Assert.Equal("1111", query["aaaa"]);
-                    Assert.Equal("2222", query["bbbb"]);
-
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Content = new ByteArrayContent(image.Stream.ToArray()),
-                    };
-                });
-
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-                var param = new Dictionary<string, string>
+            mockHandler.Enqueue(x =>
+            {
+                return new HttpResponseMessage(HttpStatusCode.BadGateway)
                 {
-                    ["aaaa"] = "1111",
-                    ["bbbb"] = "2222",
+                    Content = new StringContent("### Invalid JSON Response ###"),
                 };
+            });
 
-                var stream = await apiConnection.GetStreamAsync(endpoint, param)
-                    .ConfigureAwait(false);
+            var request = new GetRequest
+            {
+                RequestUri = new("hoge/tetete.json", UriKind.Relative),
+            };
 
-                using (var memoryStream = new MemoryStream())
-                {
-                    // 内容の比較のために MemoryStream にコピー
-                    await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+            var exception = await Assert.ThrowsAsync<TwitterApiException>(
+                () => apiConnection.SendAsync(request)
+            );
 
-                    Assert.Equal(image.Stream.ToArray(), memoryStream.ToArray());
-                }
+            // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する
+            Assert.Equal("BadGateway", exception.Message);
+            Assert.Null(exception.ErrorResponse);
 
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
+            Assert.Equal(0, mockHandler.QueueCount);
         }
 
         [Fact]
-        public async Task PostLazyAsync_Test()
+        public async Task SendAsync_ErrorJsonTest()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            {
-                apiConnection.http = http;
+            using var mockHandler = new HttpMessageHandlerMock();
+            using var http = new HttpClient(mockHandler);
+            using var apiConnection = new TwitterApiConnection();
+            apiConnection.Http = http;
 
-                mockHandler.Enqueue(async x =>
+            mockHandler.Enqueue(x =>
+            {
+                return new HttpResponseMessage(HttpStatusCode.Forbidden)
                 {
-                    Assert.Equal(HttpMethod.Post, x.Method);
-                    Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                        x.RequestUri.AbsoluteUri);
+                    Content = new StringContent("""{"errors":[{"code":187,"message":"Status is a duplicate."}]}"""),
+                };
+            });
 
-                    var body = await x.Content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
-                    var query = HttpUtility.ParseQueryString(body);
+            var request = new GetRequest
+            {
+                RequestUri = new("hoge/tetete.json", UriKind.Relative),
+            };
 
-                    Assert.Equal("1111", query["aaaa"]);
-                    Assert.Equal("2222", query["bbbb"]);
+            var exception = await Assert.ThrowsAsync<TwitterApiException>(
+                () => apiConnection.SendAsync(request)
+            );
 
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Content = new StringContent("\"hogehoge\""),
-                    };
-                });
+            // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する
+            Assert.Equal("DuplicateStatus", exception.Message);
 
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-                var param = new Dictionary<string, string>
-                {
-                    ["aaaa"] = "1111",
-                    ["bbbb"] = "2222",
-                };
+            Assert.Equal(TwitterErrorCode.DuplicateStatus, exception.Errors[0].Code);
+            Assert.Equal("Status is a duplicate.", exception.Errors[0].Message);
 
-                var result = await apiConnection.PostLazyAsync<string>(endpoint, param)
-                    .ConfigureAwait(false);
-
-                Assert.Equal("hogehoge", await result.LoadJsonAsync()
-                    .ConfigureAwait(false));
-
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
+            Assert.Equal(0, mockHandler.QueueCount);
         }
 
         [Fact]
-        public async Task PostLazyAsync_MultipartTest()
+        public async Task HandleTimeout_SuccessTest()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
+            static async Task<int> AsyncFunc(CancellationToken token)
             {
-                apiConnection.httpUpload = http;
-
-                using (var image = TestUtils.CreateDummyImage())
-                using (var media = new MemoryImageMediaItem(image))
-                {
-                    mockHandler.Enqueue(async x =>
-                    {
-                        Assert.Equal(HttpMethod.Post, x.Method);
-                        Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                            x.RequestUri.AbsoluteUri);
-
-                        Assert.IsType<MultipartFormDataContent>(x.Content);
-
-                        var boundary = x.Content.Headers.ContentType.Parameters.Cast<NameValueHeaderValue>()
-                            .First(y => y.Name == "boundary").Value;
-
-                        // 前後のダブルクオーテーションを除去
-                        boundary = boundary.Substring(1, boundary.Length - 2);
-
-                        var expectedText =
-                            $"--{boundary}\r\n" +
-                            "Content-Type: text/plain; charset=utf-8\r\n" +
-                            "Content-Disposition: form-data; name=aaaa\r\n" +
-                            "\r\n" +
-                            "1111\r\n"+
-                            $"--{boundary}\r\n" +
-                            "Content-Type: text/plain; charset=utf-8\r\n" +
-                            "Content-Disposition: form-data; name=bbbb\r\n" +
-                            "\r\n" +
-                            "2222\r\n" +
-                            $"--{boundary}\r\n" +
-                            $"Content-Disposition: form-data; name=media1; filename={media.Name}; filename*=utf-8''{media.Name}\r\n" +
-                            "\r\n";
-
-                        var expected = Encoding.UTF8.GetBytes(expectedText)
-                            .Concat(image.Stream.ToArray())
-                            .Concat(Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n"));
-
-                        Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
-
-                        return new HttpResponseMessage(HttpStatusCode.OK)
-                        {
-                            Content = new StringContent("\"hogehoge\""),
-                        };
-                    });
-
-                    var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-                    var param = new Dictionary<string, string>
-                    {
-                        ["aaaa"] = "1111",
-                        ["bbbb"] = "2222",
-                    };
-                    var mediaParam = new Dictionary<string, IMediaItem>
-                    {
-                        ["media1"] = media,
-                    };
-
-                    var result = await apiConnection.PostLazyAsync<string>(endpoint, param, mediaParam)
-                        .ConfigureAwait(false);
+                await Task.Delay(10);
+                token.ThrowIfCancellationRequested();
+                return 1;
+            }
 
-                    Assert.Equal("hogehoge", await result.LoadJsonAsync()
-                        .ConfigureAwait(false));
+            var timeout = TimeSpan.FromMilliseconds(200);
+            var ret = await TwitterApiConnection.HandleTimeout(AsyncFunc, timeout);
 
-                    Assert.Equal(0, mockHandler.QueueCount);
-                }
-            }
+            Assert.Equal(1, ret);
         }
 
         [Fact]
-        public async Task PostLazyAsync_Multipart_NullTest()
+        public async Task HandleTimeout_TimeoutTest()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            {
-                apiConnection.httpUpload = http;
-
-                mockHandler.Enqueue(async x =>
-                {
-                    Assert.Equal(HttpMethod.Post, x.Method);
-                    Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                        x.RequestUri.AbsoluteUri);
-
-                    Assert.IsType<MultipartFormDataContent>(x.Content);
-
-                    var boundary = x.Content.Headers.ContentType.Parameters.Cast<NameValueHeaderValue>()
-                        .First(y => y.Name == "boundary").Value;
-
-                    // 前後のダブルクオーテーションを除去
-                    boundary = boundary.Substring(1, boundary.Length - 2);
-
-                    var expectedText =
-                        $"--{boundary}\r\n" +
-                        $"\r\n--{boundary}--\r\n";
-
-                    var expected = Encoding.UTF8.GetBytes(expectedText);
-
-                    Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
+            var tcs = new TaskCompletionSource<bool>();
 
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Content = new StringContent("\"hogehoge\""),
-                    };
-                });
-
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
-
-                var result = await apiConnection.PostLazyAsync<string>(endpoint, param: null, media: null)
-                    .ConfigureAwait(false);
+            async Task<int> AsyncFunc(CancellationToken token)
+            {
+                await Task.Delay(200);
+                tcs.SetResult(token.IsCancellationRequested);
+                return 1;
+            }
 
-                Assert.Equal("hogehoge", await result.LoadJsonAsync()
-                    .ConfigureAwait(false));
+            var timeout = TimeSpan.FromMilliseconds(10);
+            await Assert.ThrowsAsync<OperationCanceledException>(
+                () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
+            );
 
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
+            var cancelRequested = await tcs.Task;
+            Assert.True(cancelRequested);
         }
 
         [Fact]
-        public async Task PostJsonAsync_Test()
+        public async Task HandleTimeout_ThrowExceptionAfterTimeoutTest()
         {
-            using (var mockHandler = new HttpMessageHandlerMock())
-            using (var http = new HttpClient(mockHandler))
-            using (var apiConnection = new TwitterApiConnection("", ""))
-            {
-                apiConnection.http = http;
+            var tcs = new TaskCompletionSource<int>();
 
-                mockHandler.Enqueue(async x =>
-                {
-                    Assert.Equal(HttpMethod.Post, x.Method);
-                    Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
-                        x.RequestUri.AbsoluteUri);
-
-                    Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString());
+            async Task<int> AsyncFunc(CancellationToken token)
+            {
+                await Task.Delay(100);
+                tcs.SetResult(1);
+                throw new Exception();
+            }
 
-                    var body = await x.Content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
+            var timeout = TimeSpan.FromMilliseconds(10);
+            await Assert.ThrowsAsync<OperationCanceledException>(
+                () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
+            );
 
-                    Assert.Equal("{\"aaaa\": 1111}", body);
+            // キャンセル後に AsyncFunc で発生した例外が無視される(UnobservedTaskException イベントを発生させない)ことをチェックする
+            var error = false;
+            void UnobservedExceptionHandler(object s, UnobservedTaskExceptionEventArgs e)
+                => error = true;
 
-                    return new HttpResponseMessage(HttpStatusCode.OK)
-                    {
-                        Content = new StringContent("\"hogehoge\""),
-                    };
-                });
+            TaskScheduler.UnobservedTaskException += UnobservedExceptionHandler;
 
-                var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
+            await tcs.Task;
+            await Task.Delay(10);
+            GC.Collect(); // UnobservedTaskException は Task のデストラクタで呼ばれるため強制的に GC を実行する
+            await Task.Delay(10);
 
-                await apiConnection.PostJsonAsync(endpoint, "{\"aaaa\": 1111}")
-                    .ConfigureAwait(false);
+            Assert.False(error);
 
-                Assert.Equal(0, mockHandler.QueueCount);
-            }
+            TaskScheduler.UnobservedTaskException -= UnobservedExceptionHandler;
         }
     }
 }