This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY-9381
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY-9381 by this push:
     new 57cf83c9e0 GROOVY-9381: Support async/await like ES7(backend)
57cf83c9e0 is described below

commit 57cf83c9e03cf040c3cb5b75ced0627fef16c289
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Oct 26 15:49:49 2025 +0900

    GROOVY-9381: Support async/await like ES7(backend)
---
 .../util/concurrent/async/SimplePromiseTest.groovy | 181 +++++++++++++++++++++
 1 file changed, 181 insertions(+)

diff --git 
a/src/test/groovy/groovy/util/concurrent/async/SimplePromiseTest.groovy 
b/src/test/groovy/groovy/util/concurrent/async/SimplePromiseTest.groovy
index 02f2e0fcf0..1e43f53079 100644
--- a/src/test/groovy/groovy/util/concurrent/async/SimplePromiseTest.groovy
+++ b/src/test/groovy/groovy/util/concurrent/async/SimplePromiseTest.groovy
@@ -19,9 +19,11 @@
 package groovy.util.concurrent.async
 
 import groovy.transform.CompileStatic
+import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Nested
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.function.Executable
 
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.CompletionException
@@ -32,10 +34,12 @@ import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
 import java.util.concurrent.atomic.AtomicReference
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow
 import static org.junit.jupiter.api.Assertions.assertEquals
 import static org.junit.jupiter.api.Assertions.assertFalse
 import static org.junit.jupiter.api.Assertions.assertNotNull
 import static org.junit.jupiter.api.Assertions.assertNotSame
+import static org.junit.jupiter.api.Assertions.assertNull
 import static org.junit.jupiter.api.Assertions.assertSame
 import static org.junit.jupiter.api.Assertions.assertThrows
 import static org.junit.jupiter.api.Assertions.assertTrue
@@ -66,6 +70,183 @@ class SimplePromiseTest {
         }
     }
 
+    @Nested
+    @DisplayName("CompletedPromise")
+    class SimplePromiseCompletedPromiseTest {
+        @Test
+        void testCompletedPromiseWithValue() throws Exception {
+            Promise<Integer> p = SimplePromise.completedPromise(42);
+
+            assertTrue(p.isDone(), "promise should be done");
+            assertFalse(p.isCancelled(), "should not be cancelled");
+            assertFalse(p.isCompletedExceptionally(), "should not be completed 
exceptionally");
+
+            // blocking and non-blocking retrievals
+            assertEquals(42, p.join());
+            assertEquals(42, p.await());
+            assertEquals(42, p.get());
+            assertEquals(42, p.getNow(0));
+            assertEquals(42, p.toCompletableFuture().get());
+            assertEquals(42, p.get(1, TimeUnit.SECONDS));
+
+            // attempting to cancel or complete an already-completed promise 
returns false
+            assertFalse(p.cancel(true));
+            assertFalse(p.complete(99));
+        }
+
+        @Test
+        void testCompletedPromiseWithNull() throws Exception {
+            Promise<Object> p = SimplePromise.completedPromise(null);
+
+            assertTrue(p.isDone());
+            assertFalse(p.isCompletedExceptionally());
+            assertNull(p.join());
+            assertNull(p.await());
+            assertNull(p.getNow("absent"));
+            assertNull(p.toCompletableFuture().get());
+        }
+
+        @Test
+        void testThenApplyOnCompletedPromise() {
+            Promise<Integer> p = SimplePromise.completedPromise(5);
+
+            Promise<Integer> mapped = p.thenApply(x -> x * 11);
+
+            // mapping should produce the expected result immediately
+            assertEquals(55, mapped.join());
+            assertFalse(mapped.isCompletedExceptionally());
+        }
+
+        @Test
+        void testCompleteReturnsFalseAndObtrudeValueOverrides() {
+            Promise<Integer> p = SimplePromise.completedPromise(1);
+
+            // cannot complete again
+            assertFalse(p.complete(2));
+            assertEquals(1, p.join());
+
+            // obtrudeValue forcibly changes the stored value
+            p.obtrudeValue(2);
+            assertEquals(2, p.join());
+        }
+
+        @Test
+        void testObtrudeExceptionMakesPromiseExceptional() {
+            Promise<Integer> p = SimplePromise.completedPromise(3);
+
+            p.obtrudeException(new IllegalStateException("boom"));
+
+            assertTrue(p.isCompletedExceptionally());
+
+            // join throws CompletionException
+            assertThrows(CompletionException.class, p::join);
+
+            // await wraps thrown exception in AwaitException
+            assertThrows(AwaitException.class, p::await);
+        }
+    }
+
+    @Nested
+    @DisplayName("Completion Methods")
+    class SimplePromiseAllAnyTest {
+
+        private ExecutorService executor = Executors.newSingleThreadExecutor()
+
+        @AfterEach
+        void tearDown() {
+            executor.shutdownNow()
+        }
+
+        @Test
+        void testAllOfCompletesWhenAllComplete() throws Exception {
+            Promise<Integer> p1 = 
SimplePromise.of(CompletableFuture.completedFuture(1))
+            Promise<Integer> p2 = 
SimplePromise.of(CompletableFuture.completedFuture(2))
+            Promise<Integer> p3 = SimplePromise.of()
+
+            // complete p3 asynchronously after a short delay
+            executor.submit({
+                Thread.sleep(50)
+                p3.complete(3)
+            } as Runnable)
+
+            Promise<Void> all = SimplePromise.allOf(p1, p2, p3)
+
+            // should complete when the last promise completes
+            assertDoesNotThrow((Executable) () -> { all.join() })
+            assertTrue(all.isDone())
+            // allOf semantics mirror CompletableFuture.allOf (no aggregated 
result), expect null from join
+            assertNull(all.join())
+        }
+
+        @Test
+        void testAllOfPropagatesExceptionIfAnyFail() {
+            Promise<Integer> good = 
SimplePromise.of(CompletableFuture.completedFuture(7))
+            Promise<Integer> bad = SimplePromise.of()
+            bad.completeExceptionally(new RuntimeException("fail"))
+
+            Promise<Void> all = SimplePromise.allOf(good, bad)
+
+            assertTrue(all.isDone())
+            assertThrows(CompletionException.class, { all.join() })
+        }
+
+        @Test
+        void testAnyOfCompletesWithFirstValue() throws Exception {
+            Promise<String> slow = SimplePromise.of()
+            Promise<String> fast = SimplePromise.of()
+
+            // fast completes earlier
+            executor.submit({
+                Thread.sleep(30)
+                fast.complete("fast")
+            } as Runnable)
+
+            executor.submit({
+                Thread.sleep(80)
+                slow.complete("slow")
+            } as Runnable)
+
+            Promise<Object> any = SimplePromise.anyOf(slow, fast)
+
+            assertEquals("fast", any.join())
+            assertTrue(any.isDone())
+        }
+
+        @Test
+        void testAnyOfCompletesImmediatelyIfOneAlreadyCompleted() {
+            Promise<Integer> completed = 
SimplePromise.of(CompletableFuture.completedFuture(10))
+            Promise<Integer> pending = SimplePromise.of()
+
+            Promise<Object> any = SimplePromise.anyOf(completed, pending)
+
+            assertEquals(10, any.join())
+            assertTrue(any.isDone())
+        }
+
+        @Test
+        void testAnyOfPropagatesFirstExceptionIfItOccursFirst() throws 
Exception {
+            Promise<String> exc = SimplePromise.of()
+            Promise<String> value = SimplePromise.of()
+
+            // exception happens first
+            executor.submit({
+                Thread.sleep(20)
+                exc.completeExceptionally(new IllegalStateException("boom"))
+            } as Runnable)
+
+            // value completes slightly later
+            executor.submit({
+                Thread.sleep(60)
+                value.complete("ok")
+            } as Runnable)
+
+            Promise<Object> any = SimplePromise.anyOf(exc, value)
+
+            // first completion is exceptional -> any should be exceptional
+            assertThrows(CompletionException.class, { any.join() })
+        }
+    }
+
     @Nested
     @DisplayName("Completion Methods")
     class CompletionMethodsTest {

Reply via email to