8186265: Make toString() methods of "task" objects more useful
authordl
Tue, 03 Oct 2017 13:55:05 -0700
changeset 47306 90b7465b9ac7
parent 47305 62cd7fef87b6
child 47307 6864969a78ad
8186265: Make toString() methods of "task" objects more useful Reviewed-by: martin, psandoz, rriggs, dholmes, darcy Contributed-by: Charles Munger <clm@google.com>
src/java.base/share/classes/java/util/concurrent/CompletableFuture.java
src/java.base/share/classes/java/util/concurrent/Executors.java
src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
src/java.base/share/classes/java/util/concurrent/FutureTask.java
test/jdk/java/util/concurrent/CompletableFuture/Basic.java
test/jdk/java/util/concurrent/tck/CompletableFutureTest.java
test/jdk/java/util/concurrent/tck/ExecutorsTest.java
test/jdk/java/util/concurrent/tck/ForkJoinTaskTest.java
test/jdk/java/util/concurrent/tck/FutureTaskTest.java
test/jdk/java/util/concurrent/tck/JSR166TestCase.java
--- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java	Tue Oct 03 13:55:05 2017 -0700
@@ -2490,13 +2490,13 @@
         for (Completion p = stack; p != null; p = p.next)
             ++count;
         return super.toString() +
-            ((r == null) ?
-             ((count == 0) ?
-              "[Not completed]" :
-              "[Not completed, " + count + " dependents]") :
-             (((r instanceof AltResult) && ((AltResult)r).ex != null) ?
-              "[Completed exceptionally]" :
-              "[Completed normally]"));
+            ((r == null)
+             ? ((count == 0)
+                ? "[Not completed]"
+                : "[Not completed, " + count + " dependents]")
+             : (((r instanceof AltResult) && ((AltResult)r).ex != null)
+                ? "[Completed exceptionally: " + ((AltResult)r).ex + "]"
+                : "[Completed normally]"));
     }
 
     // jdk9 additions
--- a/src/java.base/share/classes/java/util/concurrent/Executors.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/src/java.base/share/classes/java/util/concurrent/Executors.java	Tue Oct 03 13:55:05 2017 -0700
@@ -514,6 +514,9 @@
             task.run();
             return result;
         }
+        public String toString() {
+            return super.toString() + "[Wrapped task = " + task + "]";
+        }
     }
 
     /**
@@ -540,6 +543,10 @@
                 throw e.getException();
             }
         }
+
+        public String toString() {
+            return super.toString() + "[Wrapped task = " + task + "]";
+        }
     }
 
     /**
@@ -592,6 +599,10 @@
                 throw e.getException();
             }
         }
+
+        public String toString() {
+            return super.toString() + "[Wrapped task = " + task + "]";
+        }
     }
 
     /**
--- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java	Tue Oct 03 13:55:05 2017 -0700
@@ -1375,6 +1375,9 @@
         public final void setRawResult(T v) { result = v; }
         public final boolean exec() { runnable.run(); return true; }
         public final void run() { invoke(); }
+        public String toString() {
+            return super.toString() + "[Wrapped task = " + runnable + "]";
+        }
         private static final long serialVersionUID = 5232453952276885070L;
     }
 
@@ -1392,6 +1395,9 @@
         public final void setRawResult(Void v) { }
         public final boolean exec() { runnable.run(); return true; }
         public final void run() { invoke(); }
+        public String toString() {
+            return super.toString() + "[Wrapped task = " + runnable + "]";
+        }
         private static final long serialVersionUID = 5232453952276885070L;
     }
 
@@ -1437,6 +1443,9 @@
             }
         }
         public final void run() { invoke(); }
+        public String toString() {
+            return super.toString() + "[Wrapped task = " + callable + "]";
+        }
         private static final long serialVersionUID = 2838392045355241008L;
     }
 
--- a/src/java.base/share/classes/java/util/concurrent/FutureTask.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/src/java.base/share/classes/java/util/concurrent/FutureTask.java	Tue Oct 03 13:55:05 2017 -0700
@@ -480,6 +480,41 @@
         }
     }
 
+    /**
+     * Returns a string representation of this FutureTask.
+     *
+     * @implSpec
+     * The default implementation returns a string identifying this
+     * FutureTask, as well as its completion state.  The state, in
+     * brackets, contains one of the strings {@code "Completed Normally"},
+     * {@code "Completed Exceptionally"}, {@code "Cancelled"}, or {@code
+     * "Not completed"}.
+     *
+     * @return a string representation of this FutureTask
+     */
+    public String toString() {
+        final String status;
+        switch (state) {
+        case NORMAL:
+            status = "[Completed normally]";
+            break;
+        case EXCEPTIONAL:
+            status = "[Completed exceptionally: " + outcome + "]";
+            break;
+        case CANCELLED:
+        case INTERRUPTING:
+        case INTERRUPTED:
+            status = "[Cancelled]";
+            break;
+        default:
+            final Callable<?> callable = this.callable;
+            status = (callable == null)
+                ? "[Not completed]"
+                : "[Not completed, task = " + callable + "]";
+        }
+        return super.toString() + status;
+    }
+
     // VarHandle mechanics
     private static final VarHandle STATE;
     private static final VarHandle RUNNER;
--- a/test/jdk/java/util/concurrent/CompletableFuture/Basic.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/test/jdk/java/util/concurrent/CompletableFuture/Basic.java	Tue Oct 03 13:55:05 2017 -0700
@@ -74,7 +74,7 @@
         check(!cf.isCompletedExceptionally(), "Expected isCompletedExceptionally to return false");
         check(!cf.isCancelled(), "Expected isCancelled to be false");
         check(!cf.cancel(true), "Expected cancel to return false");
-        check(cf.toString().contains("[Completed normally]"));
+        check(cf.toString().matches(".*\\[.*Completed normally.*\\]"));
         check(cf.complete(null) == false, "Expected complete() to fail");
         check(cf.completeExceptionally(new Throwable()) == false,
               "Expected completeExceptionally() to fail");
@@ -106,7 +106,7 @@
         check(cf.isCompletedExceptionally(), "Expected isCompletedExceptionally");
         check(cf.isCancelled() == cancelled, "Expected isCancelled: " + cancelled + ", got:"  + cf.isCancelled());
         check(cf.cancel(true) == cancelled, "Expected cancel: " + cancelled + ", got:"  + cf.cancel(true));
-        check(cf.toString().contains("[Completed exceptionally]"));  // ## TODO: 'E'xceptionally
+        check(cf.toString().matches(".*\\[.*Completed exceptionally.*\\]"));  // ## TODO: 'E'xceptionally
         check(cf.complete((T)new Object()) == false, "Expected complete() to fail");
         check(cf.completeExceptionally(new Throwable()) == false,
               "Expected completeExceptionally() to fail, already completed");
--- a/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java	Tue Oct 03 13:55:05 2017 -0700
@@ -86,7 +86,7 @@
     void checkIncomplete(CompletableFuture<?> f) {
         assertFalse(f.isDone());
         assertFalse(f.isCancelled());
-        assertTrue(f.toString().contains("Not completed"));
+        assertTrue(f.toString().matches(".*\\[.*Not completed.*\\]"));
         try {
             assertNull(f.getNow(null));
         } catch (Throwable fail) { threadUnexpectedException(fail); }
@@ -109,7 +109,7 @@
         assertTrue(f.isDone());
         assertFalse(f.isCancelled());
         assertFalse(f.isCompletedExceptionally());
-        assertTrue(f.toString().contains("[Completed normally]"));
+        assertTrue(f.toString().matches(".*\\[.*Completed normally.*\\]"));
     }
 
     /**
@@ -165,7 +165,7 @@
         assertFalse(f.isCancelled());
         assertTrue(f.isDone());
         assertTrue(f.isCompletedExceptionally());
-        assertTrue(f.toString().contains("[Completed exceptionally]"));
+        assertTrue(f.toString().matches(".*\\[.*Completed exceptionally.*\\]"));
     }
 
     void checkCompletedWithWrappedCFException(CompletableFuture<?> f) {
@@ -220,7 +220,7 @@
         assertTrue(f.isDone());
         assertTrue(f.isCompletedExceptionally());
         assertTrue(f.isCancelled());
-        assertTrue(f.toString().contains("[Completed exceptionally]"));
+        assertTrue(f.toString().matches(".*\\[.*Completed exceptionally.*\\]"));
     }
 
     /**
@@ -356,23 +356,40 @@
     /**
      * toString indicates current completion state
      */
-    public void testToString() {
-        CompletableFuture<String> f;
-
-        f = new CompletableFuture<String>();
-        assertTrue(f.toString().contains("[Not completed]"));
-
+    public void testToString_incomplete() {
+        CompletableFuture<String> f = new CompletableFuture<String>();
+        assertTrue(f.toString().matches(".*\\[.*Not completed.*\\]"));
+        if (testImplementationDetails)
+            assertEquals(identityString(f) + "[Not completed]",
+                         f.toString());
+    }
+
+    public void testToString_normal() {
+        CompletableFuture<String> f = new CompletableFuture<String>();
         assertTrue(f.complete("foo"));
-        assertTrue(f.toString().contains("[Completed normally]"));
-
-        f = new CompletableFuture<String>();
+        assertTrue(f.toString().matches(".*\\[.*Completed normally.*\\]"));
+        if (testImplementationDetails)
+            assertEquals(identityString(f) + "[Completed normally]",
+                         f.toString());
+    }
+
+    public void testToString_exception() {
+        CompletableFuture<String> f = new CompletableFuture<String>();
         assertTrue(f.completeExceptionally(new IndexOutOfBoundsException()));
-        assertTrue(f.toString().contains("[Completed exceptionally]"));
-
+        assertTrue(f.toString().matches(".*\\[.*Completed exceptionally.*\\]"));
+        if (testImplementationDetails)
+            assertTrue(f.toString().startsWith(
+                               identityString(f) + "[Completed exceptionally: "));
+    }
+
+    public void testToString_cancelled() {
         for (boolean mayInterruptIfRunning : new boolean[] { true, false }) {
-            f = new CompletableFuture<String>();
+            CompletableFuture<String> f = new CompletableFuture<String>();
             assertTrue(f.cancel(mayInterruptIfRunning));
-            assertTrue(f.toString().contains("[Completed exceptionally]"));
+            assertTrue(f.toString().matches(".*\\[.*Completed exceptionally.*\\]"));
+            if (testImplementationDetails)
+                assertTrue(f.toString().startsWith(
+                                   identityString(f) + "[Completed exceptionally: "));
         }
     }
 
--- a/test/jdk/java/util/concurrent/tck/ExecutorsTest.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/test/jdk/java/util/concurrent/tck/ExecutorsTest.java	Tue Oct 03 13:55:05 2017 -0700
@@ -613,4 +613,56 @@
         } catch (NullPointerException success) {}
     }
 
+    /**
+     * callable(runnable, x).toString() contains toString of wrapped task
+     */
+    public void testCallable_withResult_toString() {
+        if (testImplementationDetails) {
+            Runnable r = () -> {};
+            Callable<String> c = Executors.callable(r, "");
+            assertEquals(
+                identityString(c) + "[Wrapped task = " + r.toString() + "]",
+                c.toString());
+        }
+    }
+
+    /**
+     * callable(runnable).toString() contains toString of wrapped task
+     */
+    public void testCallable_toString() {
+        if (testImplementationDetails) {
+            Runnable r = () -> {};
+            Callable<Object> c = Executors.callable(r);
+            assertEquals(
+                identityString(c) + "[Wrapped task = " + r.toString() + "]",
+                c.toString());
+        }
+    }
+
+    /**
+     * privilegedCallable(callable).toString() contains toString of wrapped task
+     */
+    public void testPrivilegedCallable_toString() {
+        if (testImplementationDetails) {
+            Callable<String> c = () -> "";
+            Callable<String> priv = Executors.privilegedCallable(c);
+            assertEquals(
+                identityString(priv) + "[Wrapped task = " + c.toString() + "]",
+                priv.toString());
+        }
+    }
+
+    /**
+     * privilegedCallableUsingCurrentClassLoader(callable).toString()
+     * contains toString of wrapped task
+     */
+    public void testPrivilegedCallableUsingCurrentClassLoader_toString() {
+        if (testImplementationDetails) {
+            Callable<String> c = () -> "";
+            Callable<String> priv = Executors.privilegedCallableUsingCurrentClassLoader(c);
+            assertEquals(
+                identityString(priv) + "[Wrapped task = " + c.toString() + "]",
+                priv.toString());
+        }
+    }
 }
--- a/test/jdk/java/util/concurrent/tck/ForkJoinTaskTest.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/test/jdk/java/util/concurrent/tck/ForkJoinTaskTest.java	Tue Oct 03 13:55:05 2017 -0700
@@ -35,6 +35,7 @@
 
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
@@ -1675,4 +1676,42 @@
         testInvokeOnPool(mainPool(), a);
     }
 
+    /**
+     * adapt(runnable).toString() contains toString of wrapped task
+     */
+    public void testAdapt_Runnable_toString() {
+        if (testImplementationDetails) {
+            Runnable r = () -> {};
+            ForkJoinTask<?> task = ForkJoinTask.adapt(r);
+            assertEquals(
+                identityString(task) + "[Wrapped task = " + r.toString() + "]",
+                task.toString());
+        }
+    }
+
+    /**
+     * adapt(runnable, x).toString() contains toString of wrapped task
+     */
+    public void testAdapt_Runnable_withResult_toString() {
+        if (testImplementationDetails) {
+            Runnable r = () -> {};
+            ForkJoinTask<String> task = ForkJoinTask.adapt(r, "");
+            assertEquals(
+                identityString(task) + "[Wrapped task = " + r.toString() + "]",
+                task.toString());
+        }
+    }
+
+    /**
+     * adapt(callable).toString() contains toString of wrapped task
+     */
+    public void testAdapt_Callable_toString() {
+        if (testImplementationDetails) {
+            Callable<String> c = () -> "";
+            ForkJoinTask<String> task = ForkJoinTask.adapt(c);
+            assertEquals(
+                identityString(task) + "[Wrapped task = " + c.toString() + "]",
+                task.toString());
+        }
+    }
 }
--- a/test/jdk/java/util/concurrent/tck/FutureTaskTest.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/test/jdk/java/util/concurrent/tck/FutureTaskTest.java	Tue Oct 03 13:55:05 2017 -0700
@@ -861,4 +861,45 @@
         }
     }
 
+    /**
+     * toString indicates current completion state
+     */
+    public void testToString_incomplete() {
+        FutureTask<String> f = new FutureTask<String>(() -> "");
+        assertTrue(f.toString().matches(".*\\[.*Not completed.*\\]"));
+        if (testImplementationDetails)
+            assertTrue(f.toString().startsWith(
+                               identityString(f) + "[Not completed, task ="));
+    }
+
+    public void testToString_normal() {
+        FutureTask<String> f = new FutureTask<String>(() -> "");
+        f.run();
+        assertTrue(f.toString().matches(".*\\[.*Completed normally.*\\]"));
+        if (testImplementationDetails)
+            assertEquals(identityString(f) + "[Completed normally]",
+                         f.toString());
+    }
+
+    public void testToString_exception() {
+        FutureTask<String> f = new FutureTask<String>(
+                () -> { throw new ArithmeticException(); });
+        f.run();
+        assertTrue(f.toString().matches(".*\\[.*Completed exceptionally.*\\]"));
+        if (testImplementationDetails)
+            assertTrue(f.toString().startsWith(
+                               identityString(f) + "[Completed exceptionally: "));
+    }
+
+    public void testToString_cancelled() {
+        for (boolean mayInterruptIfRunning : new boolean[] { true, false }) {
+            FutureTask<String> f = new FutureTask<String>(() -> "");
+            assertTrue(f.cancel(mayInterruptIfRunning));
+            assertTrue(f.toString().matches(".*\\[.*Cancelled.*\\]"));
+            if (testImplementationDetails)
+                assertEquals(identityString(f) + "[Cancelled]",
+                             f.toString());
+        }
+    }
+
 }
--- a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java	Tue Oct 03 13:50:09 2017 -0700
+++ b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java	Tue Oct 03 13:55:05 2017 -0700
@@ -571,6 +571,7 @@
                 "DoubleAdderTest",
                 "ForkJoinPool8Test",
                 "ForkJoinTask8Test",
+                "HashMapTest",
                 "LinkedBlockingDeque8Test",
                 "LinkedBlockingQueue8Test",
                 "LongAccumulatorTest",
@@ -1940,6 +1941,18 @@
         Collections.shuffle(Arrays.asList(array), ThreadLocalRandom.current());
     }
 
+    /**
+     * Returns the same String as would be returned by {@link
+     * Object#toString}, whether or not the given object's class
+     * overrides toString().
+     *
+     * @see System#identityHashCode
+     */
+    static String identityString(Object x) {
+        return x.getClass().getName()
+            + "@" + Integer.toHexString(System.identityHashCode(x));
+    }
+
     // --- Shared assertions for Executor tests ---
 
     /**