1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Threading . Tasks ;
4+ using NUnit . Framework ;
5+ using UnityScreenNavigator . Runtime . Foundation ;
6+
7+ namespace UnityScreenNavigator . Tests . PlayMode . Foundation . CoroutineSystem
8+ {
9+ internal sealed class AsyncStatusTest
10+ {
11+ [ Test ]
12+ public void Constructor_初期状態_IsCompletedFalse_IsFaultedFalse_ExceptionNull_AllExceptionsEmpty ( )
13+ {
14+ // Arrange & Act
15+ var status = new AsyncStatus ( ) ;
16+
17+ // Assert
18+ Assert . That ( status . IsCompleted , Is . False ) ;
19+ Assert . That ( status . IsFaulted , Is . False ) ;
20+ Assert . That ( status . Exception , Is . Null ) ;
21+ Assert . That ( status . AllExceptions , Is . Not . Null . And . Empty ) ;
22+ }
23+
24+ [ Test ]
25+ public void MarkCompleted_未完了の時_IsCompletedがTrueになること ( )
26+ {
27+ // Arrange
28+ var status = new AsyncStatus ( ) ;
29+
30+ // Act
31+ status . MarkCompleted ( ) ;
32+
33+ // Assert
34+ Assert . That ( status . IsCompleted , Is . True ) ;
35+ Assert . That ( status . IsFaulted , Is . False ) ; // 念のため確認
36+ }
37+
38+ [ Test ]
39+ public void MarkCompleted_既に完了している時_何も変わらないこと ( )
40+ {
41+ // Arrange
42+ var status = new AsyncStatus ( ) ;
43+ status . MarkCompleted ( ) ; // 最初に完了させる
44+
45+ // Act
46+ status . MarkCompleted ( ) ; // 再度呼び出し
47+
48+ // Assert
49+ Assert . That ( status . IsCompleted , Is . True ) ;
50+ Assert . That ( status . IsFaulted , Is . False ) ;
51+ }
52+
53+ [ Test ]
54+ public void MarkFaulted_単一例外で未完了の時_IsCompletedTrue_IsFaultedTrue_Exception設定_AllExceptionsに設定 ( )
55+ {
56+ // Arrange
57+ var status = new AsyncStatus ( ) ;
58+ var exception = new InvalidOperationException ( "Test exception" ) ;
59+
60+ // Act
61+ status . MarkFaulted ( exception ) ;
62+
63+ // Assert
64+ Assert . That ( status . IsCompleted , Is . True ) ;
65+ Assert . That ( status . IsFaulted , Is . True ) ;
66+ Assert . That ( status . Exception , Is . SameAs ( exception ) ) ;
67+ Assert . That ( status . AllExceptions , Is . Not . Null ) ;
68+ Assert . That ( status . AllExceptions . Count , Is . EqualTo ( 1 ) ) ;
69+ Assert . That ( status . AllExceptions [ 0 ] , Is . SameAs ( exception ) ) ;
70+ }
71+
72+ [ Test ]
73+ public void MarkFaulted_単一例外で既に完了している時_何も変わらないこと ( )
74+ {
75+ // Arrange
76+ var status = new AsyncStatus ( ) ;
77+ var initialException = new ArgumentNullException ( "Initial" ) ;
78+ status . MarkFaulted ( initialException ) ; // 最初に失敗させる
79+
80+ var subsequentException = new InvalidOperationException ( "Subsequent" ) ;
81+
82+ // Act
83+ status . MarkFaulted ( subsequentException ) ; // 完了後に再度呼び出し
84+
85+ // Assert
86+ Assert . That ( status . IsCompleted , Is . True ) ;
87+ Assert . That ( status . IsFaulted , Is . True ) ;
88+ Assert . That ( status . Exception , Is . SameAs ( initialException ) , "最初の例外が保持されるべき" ) ;
89+ Assert . That ( status . AllExceptions . Count , Is . EqualTo ( 1 ) , "例外リストの数は変わらないべき" ) ;
90+ Assert . That ( status . AllExceptions [ 0 ] , Is . SameAs ( initialException ) ) ;
91+ }
92+
93+ [ Test ]
94+ public void MarkFaulted_単一例外で正常完了している時_何も変わらないこと ( )
95+ {
96+ // Arrange
97+ var status = new AsyncStatus ( ) ;
98+ status . MarkCompleted ( ) ; // 最初に正常完了させる
99+
100+ var subsequentException = new InvalidOperationException ( "Subsequent" ) ;
101+
102+ // Act
103+ status . MarkFaulted ( subsequentException ) ; // 完了後にFaultedを呼び出し
104+
105+ // Assert
106+ Assert . That ( status . IsCompleted , Is . True ) ;
107+ Assert . That ( status . IsFaulted , Is . False , "正常完了後はFaultedにならない" ) ;
108+ Assert . That ( status . Exception , Is . Null ) ;
109+ Assert . That ( status . AllExceptions , Is . Empty ) ;
110+ }
111+
112+ [ Test ]
113+ public void MarkFaulted_複数例外リストで未完了の時_IsCompletedTrue_IsFaultedTrue_Exceptionは最初の例外_AllExceptionsに全設定 ( )
114+ {
115+ // Arrange
116+ var status = new AsyncStatus ( ) ;
117+ var ex1 = new InvalidOperationException ( "Ex1" ) ;
118+ var ex2 = new ArgumentException ( "Ex2" ) ;
119+ var exceptions = new List < Exception > { ex1 , ex2 } ;
120+
121+ // Act
122+ status . MarkFaulted ( exceptions ) ;
123+
124+ // Assert
125+ Assert . That ( status . IsCompleted , Is . True ) ;
126+ Assert . That ( status . IsFaulted , Is . True ) ;
127+ Assert . That ( status . Exception , Is . SameAs ( ex1 ) , "最初の例外がExceptionプロパティに設定される" ) ;
128+ Assert . That ( status . AllExceptions , Is . Not . Null ) ;
129+ Assert . That ( status . AllExceptions . Count , Is . EqualTo ( 2 ) ) ;
130+ Assert . That ( status . AllExceptions . Contains ( ex1 ) , Is . True ) ;
131+ Assert . That ( status . AllExceptions . Contains ( ex2 ) , Is . True ) ;
132+ }
133+
134+ [ Test ]
135+ public void MarkFaulted_空の複数例外リストで未完了の時_IsCompletedTrue_IsFaultedFalse_ExceptionNull ( )
136+ {
137+ // Arrange
138+ var status = new AsyncStatus ( ) ;
139+ var exceptions = new List < Exception > ( ) ; // 空のリスト
140+
141+ // Act
142+ status . MarkFaulted ( exceptions ) ;
143+
144+ // Assert
145+ Assert . That ( status . IsCompleted , Is . True , "IsCompleted は true になる" ) ;
146+ Assert . That ( status . IsFaulted , Is . False , "空の例外リストでは IsFaulted は false" ) ;
147+ Assert . That ( status . Exception , Is . Null , "Exception は null" ) ;
148+ Assert . That ( status . AllExceptions , Is . Empty , "AllExceptions は空" ) ;
149+ }
150+
151+
152+ [ Test ]
153+ public void MarkFaulted_複数例外リストで既に完了している時_何も変わらないこと ( )
154+ {
155+ // Arrange
156+ var status = new AsyncStatus ( ) ;
157+ var initialException = new ApplicationException ( "Initial" ) ;
158+ status . MarkFaulted ( initialException ) ; // 最初に失敗させる
159+
160+ var ex1 = new InvalidOperationException ( "Ex1" ) ;
161+ var ex2 = new ArgumentException ( "Ex2" ) ;
162+ var subsequentExceptions = new List < Exception > { ex1 , ex2 } ;
163+
164+ // Act
165+ status . MarkFaulted ( subsequentExceptions ) ; // 完了後に再度呼び出し
166+
167+ // Assert
168+ Assert . That ( status . IsCompleted , Is . True ) ;
169+ Assert . That ( status . IsFaulted , Is . True ) ;
170+ Assert . That ( status . Exception , Is . SameAs ( initialException ) ) ;
171+ Assert . That ( status . AllExceptions . Count , Is . EqualTo ( 1 ) ) ;
172+ Assert . That ( status . AllExceptions [ 0 ] , Is . SameAs ( initialException ) ) ;
173+ }
174+
175+ [ Test ]
176+ public void MarkFaulted_複数例外リストで正常完了している時_何も変わらないこと ( )
177+ {
178+ // Arrange
179+ var status = new AsyncStatus ( ) ;
180+ status . MarkCompleted ( ) ; // 最初に正常完了させる
181+
182+ var ex1 = new InvalidOperationException ( "Ex1" ) ;
183+ var subsequentExceptions = new List < Exception > { ex1 } ;
184+
185+ // Act
186+ status . MarkFaulted ( subsequentExceptions ) ;
187+
188+ // Assert
189+ Assert . That ( status . IsCompleted , Is . True ) ;
190+ Assert . That ( status . IsFaulted , Is . False ) ;
191+ Assert . That ( status . Exception , Is . Null ) ;
192+ Assert . That ( status . AllExceptions , Is . Empty ) ;
193+ }
194+
195+
196+ [ Test ]
197+ public void IsFaulted_例外がない場合_Falseを返すこと ( )
198+ {
199+ // Arrange
200+ var status = new AsyncStatus ( ) ;
201+
202+ // Act & Assert
203+ Assert . That ( status . IsFaulted , Is . False ) ;
204+ status . MarkCompleted ( ) ; // 正常完了しても Faulted ではない
205+ Assert . That ( status . IsFaulted , Is . False ) ;
206+ }
207+
208+ [ Test ]
209+ public void IsFaulted_例外がある場合_Trueを返すこと ( )
210+ {
211+ // Arrange
212+ var status = new AsyncStatus ( ) ;
213+ status . MarkFaulted ( new Exception ( "Test" ) ) ;
214+
215+ // Act & Assert
216+ Assert . That ( status . IsFaulted , Is . True ) ;
217+ }
218+
219+ [ Test ]
220+ public void Wait_未完了の時_IsCompletedがFalseの間nullを返し続けること ( )
221+ {
222+ // Arrange
223+ var status = new AsyncStatus ( ) ;
224+ var waiter = status . Wait ( ) ;
225+ var stepCount = 0 ;
226+
227+ // Act & Assert
228+ // 最初の数ステップは IsCompleted が false なので null を返すはず
229+ Assert . That ( waiter . MoveNext ( ) , Is . True , "MoveNext 1 should be true" ) ;
230+ Assert . That ( waiter . Current , Is . Null , "Current 1 should be null" ) ;
231+ stepCount ++ ;
232+
233+ Assert . That ( waiter . MoveNext ( ) , Is . True , "MoveNext 2 should be true" ) ;
234+ Assert . That ( waiter . Current , Is . Null , "Current 2 should be null" ) ;
235+ stepCount ++ ;
236+
237+ // 途中で完了させる
238+ status . MarkCompleted ( ) ;
239+
240+ Assert . That ( waiter . MoveNext ( ) , Is . False , "MoveNext after completion should be false" ) ;
241+ Assert . That ( stepCount , Is . EqualTo ( 2 ) , "Specified steps should have occurred before completion" ) ;
242+ }
243+
244+ [ Test ]
245+ public void Wait_最初から完了済みの時_すぐに終了すること ( )
246+ {
247+ // Arrange
248+ var status = new AsyncStatus ( ) ;
249+ status . MarkCompleted ( ) ;
250+ var waiter = status . Wait ( ) ;
251+
252+ // Act & Assert
253+ Assert . That ( waiter . MoveNext ( ) , Is . False ) ; // IsCompleted なのですぐに false
254+ }
255+
256+ [ Test ]
257+ public void Wait_最初から失敗済みの時_すぐに終了すること ( )
258+ {
259+ // Arrange
260+ var status = new AsyncStatus ( ) ;
261+ status . MarkFaulted ( new Exception ( "fail" ) ) ; // IsCompleted も true になる
262+ var waiter = status . Wait ( ) ;
263+
264+ // Act & Assert
265+ Assert . That ( waiter . MoveNext ( ) , Is . False ) ;
266+ }
267+
268+ // Createメソッドのテストは非同期処理の性質上、少し複雑になります。
269+ // MonitorAsyncがasync voidであるため、その完了を直接待つのが難しいです。
270+ // テストでは、Taskが完了するか例外をスローするのを待ってからStatusを検証します。
271+ // async voidメソッドのテストは通常、同期コンテキストのカスタマイズや、
272+ // 副作用(この場合はStatusの更新)をポーリングすることで行われます。
273+
274+ [ Test ]
275+ public async Task Create_Taskが正常に完了する場合_Statusが完了になること ( )
276+ {
277+ // Arrange
278+ var tcs = new TaskCompletionSource < bool > ( ) ;
279+ Func < Task > asyncFunc = ( ) => tcs . Task ;
280+
281+ // Act
282+ var status = AsyncStatus . Create ( asyncFunc ) ;
283+
284+ // Assert
285+ Assert . That ( status . IsCompleted , Is . False , "Task開始直後は未完了" ) ;
286+
287+ tcs . SetResult ( true ) ; // Taskを完了させる
288+
289+ // async void の MonitorAsync が完了するのを少し待つ
290+ // より堅牢なテストのためには、ポーリングやカスタム同期コンテキストが必要になる場合があります。
291+ // ここでは簡略化のため、Task.Delayを使用します。
292+ await Task . Delay ( 100 ) ; // MonitorAsync内のawaitとMarkCompletedが実行されるのに十分な時間
293+
294+ Assert . That ( status . IsCompleted , Is . True , "Task完了後、Statusは完了になるべき" ) ;
295+ Assert . That ( status . IsFaulted , Is . False ) ;
296+ }
297+
298+ [ Test ]
299+ public async Task Create_Taskが例外をスローする場合_Statusが失敗になること ( )
300+ {
301+ // Arrange
302+ var tcs = new TaskCompletionSource < bool > ( ) ;
303+ var testException = new InvalidOperationException ( "Async task failed" ) ;
304+ Func < Task > asyncFunc = ( ) =>
305+ {
306+ tcs . SetException ( testException ) ;
307+ return tcs . Task ;
308+ } ;
309+ // 代わりに Task.FromException を使うこともできます
310+ // Func<Task> asyncFunc = () => Task.FromException(testException);
311+
312+
313+ // Act
314+ var status = AsyncStatus . Create ( asyncFunc ) ;
315+
316+ // Assert
317+ Assert . That ( status . IsCompleted , Is . False , "Task開始直後は未完了" ) ;
318+
319+ // Taskが例外で完了するのを待つ (Create内では捕捉される)
320+ Exception ? caughtInTest = null ;
321+ try
322+ {
323+ await asyncFunc ( ) ; // これ自体は例外を再スローする
324+ }
325+ catch ( Exception ex )
326+ {
327+ caughtInTest = ex ; // Createに渡したasyncFuncがスローした例外をキャッチ
328+ }
329+
330+ Assert . That ( caughtInTest , Is . SameAs ( testException ) , "asyncFuncは指定した例外をスローするべき" ) ;
331+
332+
333+ // async void の MonitorAsync が完了するのを少し待つ
334+ await Task . Delay ( 100 ) ;
335+
336+ Assert . That ( status . IsCompleted , Is . True , "Task失敗後、Statusは完了になるべき" ) ;
337+ Assert . That ( status . IsFaulted , Is . True , "Task失敗後、StatusはFaultedになるべき" ) ;
338+ Assert . That ( status . Exception , Is . SameAs ( testException ) ) ;
339+ Assert . That ( status . AllExceptions . Contains ( testException ) , Is . True ) ;
340+ }
341+
342+ [ Test ]
343+ public async Task Create_Taskが既に完了している場合_Statusが即座に完了に近い状態になること ( )
344+ {
345+ // Arrange
346+ Func < Task > asyncFunc = ( ) => Task . CompletedTask ;
347+
348+ // Act
349+ var status = AsyncStatus . Create ( asyncFunc ) ;
350+
351+ // async void の MonitorAsync が完了するのを少し待つ
352+ await Task . Delay ( 50 ) ; // 通常は非常に速いはず
353+
354+ // Assert
355+ Assert . That ( status . IsCompleted , Is . True ) ;
356+ Assert . That ( status . IsFaulted , Is . False ) ;
357+ }
358+
359+ [ Test ]
360+ public async Task Create_Taskが既に失敗している場合_Statusが即座に失敗に近い状態になること ( )
361+ {
362+ // Arrange
363+ var testException = new OperationCanceledException ( "Cancelled" ) ;
364+ Func < Task > asyncFunc = ( ) => Task . FromException ( testException ) ;
365+
366+ // Act
367+ var status = AsyncStatus . Create ( asyncFunc ) ;
368+
369+ // async void の MonitorAsync が完了するのを少し待つ
370+ await Task . Delay ( 50 ) ;
371+
372+ // Assert
373+ Assert . That ( status . IsCompleted , Is . True ) ;
374+ Assert . That ( status . IsFaulted , Is . True ) ;
375+ Assert . That ( status . Exception , Is . SameAs ( testException ) ) ;
376+ }
377+ }
378+ }
0 commit comments