8186265: Make toString() methods of "task" objects more useful
Reviewed-by: martin, psandoz, rriggs, dholmes, darcy
Contributed-by: Charles Munger <clm@google.com>
--- 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 ---
/**