@@ -303,7 +303,136 @@ import Testing
303303 receiveProgress ( progress: interopChild)
304304 #expect( overallProgress2. totalUnitCount == 0 )
305305 }
306-
306+
307+ @Test func interopProgressParentProgressManagerChildAndGrandchildren( ) async throws {
308+ // Caller's NSProgress
309+ let overall = Progress . discreteProgress ( totalUnitCount: 10 )
310+ let subprogress = overall. subprogress ( assigningCount: 10 )
311+
312+ // Receiver calls start(), then assigns work to sub-tasks via ProgressReporter
313+ let child = subprogress. start ( totalCount: 2 )
314+ let task1 = ProgressManager ( totalCount: 100 )
315+ let task2 = ProgressManager ( totalCount: 100 )
316+ child. assign ( count: 1 , to: task1. reporter)
317+ child. assign ( count: 1 , to: task2. reporter)
318+
319+ // Task 1 reports partial progress
320+ task1. complete ( count: 50 )
321+ #expect( overall. fractionCompleted == 0.25 , " Task 1 50% of half should be 25% overall: expected 0.25, got \( overall. fractionCompleted) " )
322+
323+ // Task 1 completes
324+ task1. complete ( count: 50 )
325+ #expect( overall. fractionCompleted == 0.5 , " Task 1 complete should be 50% overall: expected 0.5, got \( overall. fractionCompleted) " )
326+
327+ // Task 2 completes
328+ task2. complete ( count: 100 )
329+ #expect( overall. fractionCompleted == 1.0 , " Both tasks complete should be 100%: expected 1.0, got \( overall. fractionCompleted) " )
330+ }
331+
332+ @Test func interopProgressParentProgressManagerGrandchildrenNonSequentialCompletion( ) async throws {
333+ let overall = Progress . discreteProgress ( totalUnitCount: 10 )
334+ let subprogress = overall. subprogress ( assigningCount: 10 )
335+
336+ let child = subprogress. start ( totalCount: 2 )
337+ let task1 = ProgressManager ( totalCount: 100 )
338+ let task2 = ProgressManager ( totalCount: 100 )
339+ child. assign ( count: 1 , to: task1. reporter)
340+ child. assign ( count: 1 , to: task2. reporter)
341+
342+ // Task 2 reports partial progress first
343+ task2. complete ( count: 50 )
344+ #expect( overall. fractionCompleted == 0.25 , " Task 2 50% of half should be 25% overall: expected 0.25, got \( overall. fractionCompleted) " )
345+
346+ // Task 2 completes before task 1
347+ task2. complete ( count: 50 )
348+ #expect( overall. fractionCompleted == 0.5 , " Task 2 complete should be 50% overall: expected 0.5, got \( overall. fractionCompleted) " )
349+
350+ // Task 1 completes
351+ task1. complete ( count: 100 )
352+ #expect( overall. fractionCompleted == 1.0 , " Both tasks complete should be 100%: expected 1.0, got \( overall. fractionCompleted) " )
353+ }
354+
355+ @Test func interopProgressParentProgressManagerManyGrandchildren( ) async throws {
356+ let overall = Progress . discreteProgress ( totalUnitCount: 10 )
357+ let subprogress = overall. subprogress ( assigningCount: 10 )
358+
359+ let child = subprogress. start ( totalCount: 5 )
360+ var tasks : [ ProgressManager ] = [ ]
361+ for _ in 0 ..< 5 {
362+ let task = ProgressManager ( totalCount: 100 )
363+ child. assign ( count: 1 , to: task. reporter)
364+ tasks. append ( task)
365+ }
366+
367+ // Complete each task one by one
368+ for i in 0 ..< 5 {
369+ tasks [ i] . complete ( count: 100 )
370+ let expected = Double ( i + 1 ) / 5.0
371+ #expect( overall. fractionCompleted == expected, " After \( i + 1 ) of 5 tasks complete: expected \( expected) , got \( overall. fractionCompleted) " )
372+ }
373+ }
374+
375+ @Test func interopProgressParentProgressManagerGrandchildrenPartialProgress( ) async throws {
376+ let overall = Progress . discreteProgress ( totalUnitCount: 10 )
377+ let subprogress = overall. subprogress ( assigningCount: 10 )
378+
379+ let child = subprogress. start ( totalCount: 2 )
380+ let task1 = ProgressManager ( totalCount: 100 )
381+ let task2 = ProgressManager ( totalCount: 100 )
382+ child. assign ( count: 1 , to: task1. reporter)
383+ child. assign ( count: 1 , to: task2. reporter)
384+
385+ // Task 1 at 25%
386+ task1. complete ( count: 25 )
387+ #expect( overall. fractionCompleted == 0.125 , " Task 1 25% of half: expected 0.125, got \( overall. fractionCompleted) " )
388+
389+ // Task 1 at 50%
390+ task1. complete ( count: 25 )
391+ #expect( overall. fractionCompleted == 0.25 , " Task 1 50% of half: expected 0.25, got \( overall. fractionCompleted) " )
392+
393+ // Task 2 at 50%
394+ task2. complete ( count: 50 )
395+ #expect( overall. fractionCompleted == 0.5 , " Both at 50%: expected 0.5, got \( overall. fractionCompleted) " )
396+
397+ // Task 1 completes
398+ task1. complete ( count: 50 )
399+ #expect( overall. fractionCompleted == 0.75 , " Task 1 done, task 2 at 50%: expected 0.75, got \( overall. fractionCompleted) " )
400+
401+ // Task 2 completes
402+ task2. complete ( count: 50 )
403+ #expect( overall. fractionCompleted == 1.0 , " Both complete: expected 1.0, got \( overall. fractionCompleted) " )
404+ }
405+
406+ @Test func interopProgressParentProgressManagerIndeterminateGrandchild( ) async throws {
407+ let overall = Progress . discreteProgress ( totalUnitCount: 10 )
408+ let subprogress = overall. subprogress ( assigningCount: 10 )
409+
410+ let child = subprogress. start ( totalCount: 2 )
411+ let task1 = ProgressManager ( totalCount: nil ) // indeterminate
412+ let task2 = ProgressManager ( totalCount: 100 )
413+ child. assign ( count: 1 , to: task1. reporter)
414+ child. assign ( count: 1 , to: task2. reporter)
415+
416+ // Indeterminate task should not affect overall progress
417+ #expect( overall. fractionCompleted == 0.0 , " No progress yet: expected 0.0, got \( overall. fractionCompleted) " )
418+
419+ // Determinate task reports progress
420+ task2. complete ( count: 50 )
421+ #expect( overall. fractionCompleted == 0.25 , " Task 2 50% of half: expected 0.25, got \( overall. fractionCompleted) " )
422+
423+ // Indeterminate task becomes determinate
424+ task1. setCounts { completed, total in
425+ total = 100
426+ }
427+ task1. complete ( count: 50 )
428+ #expect( overall. fractionCompleted == 0.5 , " Both at 50%: expected 0.5, got \( overall. fractionCompleted) " )
429+
430+ // Both complete
431+ task1. complete ( count: 50 )
432+ task2. complete ( count: 50 )
433+ #expect( overall. fractionCompleted == 1.0 , " Both complete: expected 1.0, got \( overall. fractionCompleted) " )
434+ }
435+
307436 #if FOUNDATION_EXIT_TESTS
308437 @Test func indirectParticipationOfProgressInAcyclicGraph( ) async throws {
309438 await #expect( processExitsWith: . failure) {
0 commit comments