changeset 402:ac4185bdf1b2

6964018: 3/4 AnonLoggerWeakRefLeak and LoggerWeakRefLeak can fail in JPRT Summary: Refactor test/sun/tools/common/* code and refactor AnonLoggerWeakRefLeak and LoggerWeakRefLeak to use it. Reviewed-by: ohair, alanb
author dcubed
date Fri, 23 Jul 2010 13:54:36 -0700
parents 4b141a1c9c57
children a76011619e85
files test/java/util/logging/AnonLoggerWeakRefLeak.java test/java/util/logging/AnonLoggerWeakRefLeak.sh test/java/util/logging/LoggerWeakRefLeak.java test/java/util/logging/LoggerWeakRefLeak.sh test/sun/tools/common/ApplicationSetup.sh test/sun/tools/common/CommonSetup.sh test/sun/tools/common/CommonTests.sh test/sun/tools/common/ShutdownSimpleApplication.java test/sun/tools/common/SimpleApplication.java test/sun/tools/common/SleeperApplication.java test/sun/tools/jhat/ParseTest.sh test/sun/tools/jinfo/Basic.sh test/sun/tools/jmap/Basic.sh test/sun/tools/jstack/Basic.sh
diffstat 14 files changed, 1015 insertions(+), 375 deletions(-) [+]
line wrap: on
line diff
--- a/test/java/util/logging/AnonLoggerWeakRefLeak.java	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/java/util/logging/AnonLoggerWeakRefLeak.java	Fri Jul 23 13:54:36 2010 -0700
@@ -23,24 +23,32 @@
 
 import java.util.logging.*;
 
-public class AnonLoggerWeakRefLeak {
-    public static int DEFAULT_LOOP_TIME = 60;  // time is in seconds
+public class AnonLoggerWeakRefLeak extends SimpleApplication {
+    // The test driver script will allow this program to run until we
+    // reach DEFAULT_LOOP_TIME or a decrease in instance counts is
+    // observed. For this particular WeakReference leak, the count
+    // was always observed to be increasing so if we get a decreasing
+    // count, then the leak is fixed in the bits being tested.
+    // Two minutes has been enough time to observe a decrease in
+    // fixed bits on overloaded systems, but the test will likely
+    // finish more quickly.
+    public static int DEFAULT_LOOP_TIME = 120;  // time is in seconds
 
-    public static void main(String[] args) {
+    // execute the AnonLoggerWeakRefLeak app work
+    public void doMyAppWork(String[] args) throws Exception {
         int loop_time = 0;
         int max_loop_time = DEFAULT_LOOP_TIME;
 
-        if (args.length == 0) {
+        // args[0] is the port-file
+        if (args.length < 2) {
             System.out.println("INFO: using default time of "
                 + max_loop_time + " seconds.");
         } else {
             try {
-                max_loop_time = Integer.parseInt(args[0]);
+                max_loop_time = Integer.parseInt(args[1]);
             } catch (NumberFormatException nfe) {
-                System.err.println("Error: '" + args[0]
+                throw new RuntimeException("Error: '" + args[1]
                     + "': is not a valid seconds value.");
-                System.err.println("Usage: AnonLoggerWeakRefLeak [seconds]");
-                System.exit(1);
             }
         }
 
@@ -73,4 +81,12 @@
 
         System.out.println("INFO: final loop count = " + count);
     }
+
+    public static void main(String[] args) throws Exception {
+        AnonLoggerWeakRefLeak myApp = new AnonLoggerWeakRefLeak();
+
+        SimpleApplication.setMyApp(myApp);
+
+        SimpleApplication.main(args);
+    }
 }
--- a/test/java/util/logging/AnonLoggerWeakRefLeak.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/java/util/logging/AnonLoggerWeakRefLeak.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,3 +1,5 @@
+#!/bin/sh
+
 #
 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@@ -26,72 +28,21 @@
 # @summary Check for WeakReference leak in anonymous Logger objects
 # @author Daniel D. Daugherty
 #
-# @run build AnonLoggerWeakRefLeak
-# @run shell/timeout=180 AnonLoggerWeakRefLeak.sh
+# @library ../../../sun/tools/common
+# @build SimpleApplication ShutdownSimpleApplication
+# @build AnonLoggerWeakRefLeak
+# @run shell/timeout=240 AnonLoggerWeakRefLeak.sh
 
-# The timeout is: 2 minutes for infrastructure and 1 minute for the test
+# The timeout is: 2 minutes for infrastructure and 2 minutes for the test
 #
 
-if [ "${TESTJAVA}" = "" ]
-then
-  echo "TESTJAVA not set.  Test cannot execute.  Failed."
-  exit 1
-fi
+. ${TESTSRC}/../../../sun/tools/common/CommonSetup.sh
+. ${TESTSRC}/../../../sun/tools/common/ApplicationSetup.sh
 
-if [ "${TESTSRC}" = "" ]
-then
-  echo "TESTSRC not set.  Test cannot execute.  Failed."
-  exit 1
-fi
-
-if [ "${TESTCLASSES}" = "" ]
-then
-  echo "TESTCLASSES not set.  Test cannot execute.  Failed."
-  exit 1
-fi
-
-JAVA="${TESTJAVA}"/bin/java
-JMAP="${TESTJAVA}"/bin/jmap
-JPS="${TESTJAVA}"/bin/jps
-
-set -eu
 
 TEST_NAME="AnonLoggerWeakRefLeak"
 TARGET_CLASS="java\.lang\.ref\.WeakReference"
 
-is_cygwin=false
-is_mks=false
-is_windows=false
-
-case `uname -s` in
-CYGWIN*)
-    is_cygwin=true
-    is_windows=true
-    ;;
-Windows_*)
-    is_mks=true
-    is_windows=true
-    ;;
-*)
-    ;;
-esac
-
-
-# wrapper for grep
-#
-grep_cmd() {
-    set +e
-    if $is_windows; then
-        # need dos2unix to get rid of CTRL-M chars from java output
-        dos2unix | grep "$@"
-        status="$?"
-    else
-        grep "$@"
-        status="$?"
-    fi
-    set -e
-}
-
 
 # MAIN begins here
 #
@@ -104,62 +55,39 @@
 # see if this version of jmap supports the '-histo:live' option
 jmap_option="-histo:live"
 set +e
-"${JMAP}" "$jmap_option" 0 > "$TEST_NAME.jmap" 2>&1
-grep '^Usage: ' "$TEST_NAME.jmap" > /dev/null 2>&1
+"${JMAP}" 2>&1 | grep ':live' > /dev/null 2>&1
 status="$?"
 set -e
-if [ "$status" = 0 ]; then
+if [ "$status" != 0 ]; then
     echo "INFO: switching jmap option from '$jmap_option'\c"
     jmap_option="-histo"
     echo " to '$jmap_option'."
 fi
 
-"${JAVA}" ${TESTVMOPTS} -classpath "${TESTCLASSES}" \
-    "$TEST_NAME" $seconds > "$TEST_NAME.log" 2>&1 &
-test_pid="$!"
-echo "INFO: starting $TEST_NAME as pid = $test_pid"
+# Start application and use TEST_NAME.port for coordination
+startApplication "$TEST_NAME" "$TEST_NAME.port" $seconds
 
-# wait for test program to get going
-count=0
-while [ "$count" -lt 30 ]; do
-    sleep 2
-    grep_cmd '^INFO: call count = 0$' < "$TEST_NAME.log" > /dev/null 2>&1
-    if [ "$status" = 0 ]; then
-        break
-    fi
-    count=`expr $count + 1`
-done
-
-if [ "$count" -ge 30 ]; then
-    echo "ERROR: $TEST_NAME failed to get going." >&2
-    echo "INFO: killing $test_pid"
-    kill "$test_pid"
-    exit 1
-elif [ "$count" -gt 1 ]; then
-    echo "INFO: $TEST_NAME took $count loops to start."
-fi
-
-if $is_cygwin; then
-    # We need the Windows pid for jmap and not the Cygwin pid.
-    # Note: '\t' works on Cygwin, but doesn't seem to work on Solaris.
-    jmap_pid=`"${JPS}"| grep_cmd "[ \t]$TEST_NAME$" | sed 's/[ \t].*//'`
-    if [ -z "$jmap_pid" ]; then
-        echo "FAIL: jps could not map Cygwin pid to Windows pid." >&2
-        echo "INFO: killing $test_pid"
-        kill "$test_pid"
-        exit 2
-    fi
-    echo "INFO: pid = $test_pid maps to Windows pid = $jmap_pid"
-else
-    jmap_pid="$test_pid"
-fi
+finished_early=false
 
 decreasing_cnt=0
 increasing_cnt=0
 loop_cnt=0
 prev_instance_cnt=0
 
+MAX_JMAP_TRY_CNT=10
+jmap_retry_cnt=0
+loop_cnt_on_retry=0
+
 while true; do
+    # see if the target process has finished its run and bail if it has
+    set +e
+    grep "^INFO: final loop count = " "$appOutput" > /dev/null 2>&1
+    status="$?"
+    set -e
+    if [ "$status" = 0 ]; then
+        break
+    fi
+
     # Output format for 'jmap -histo' in JDK1.5.0:
     #
     #     <#bytes> <#instances> <class_name>
@@ -169,38 +97,70 @@
     #     <num>: <#instances> <#bytes> <class_name>
     #
     set +e
-    "${JMAP}" "$jmap_option" "$jmap_pid" > "$TEST_NAME.jmap" 2>&1
+    "${JMAP}" "$jmap_option" "$appJavaPid" > "$TEST_NAME.jmap" 2>&1
     status="$?"
     set -e
 
     if [ "$status" != 0 ]; then
         echo "INFO: jmap exited with exit code = $status"
-        if [ "$loop_cnt" = 0 ]; then
-            echo "INFO: on the first iteration so no samples were taken."
-            echo "INFO: start of jmap output:"
-            cat "$TEST_NAME.jmap"
-            echo "INFO: end of jmap output."
+
+        # There are intermittent jmap failures; see 6498448.
+        #
+        # So far the following have been observed in a jmap call
+        # that was not in a race with target process termination:
+        #
+        # (Solaris specific, 2nd sample)
+        # <pid>: Unable to open door: target process not responding or HotSpot VM not loaded
+        # The -F option can be used when the target process is not responding
+        #
+        # (on Solaris so far)
+        # java.io.IOException
+        #
+        # (on Solaris so far, 1st sample)
+        # <pid>: Permission denied
+        #
+        sed 's/^/INFO: /' "$TEST_NAME.jmap"
+
+        if [ "$loop_cnt" = "$loop_cnt_on_retry" ]; then
+            # loop count hasn't changed
+            jmap_retry_cnt=`expr $jmap_retry_cnt + 1`
+        else
+            # loop count has changed so remember it
+            jmap_retry_cnt=1
+            loop_cnt_on_retry="$loop_cnt"
+        fi
+
+        # This is '-ge' because we have the original attempt plus
+        # MAX_JMAP_TRY_CNT - 1 retries.
+        if [ "$jmap_retry_cnt" -ge "$MAX_JMAP_TRY_CNT" ]; then
+            echo "INFO: jmap failed $MAX_JMAP_TRY_CNT times in a row" \
+                "without making any progress."
             echo "FAIL: jmap is unable to take any samples." >&2
-            echo "INFO: killing $test_pid"
-            kill "$test_pid"
+            killApplication
             exit 2
         fi
-        echo "INFO: The likely reason is that $TEST_NAME has finished running."
-        break
+
+        # short delay and try again
+        # Note: sleep 1 didn't help with "<pid>: Permission denied"
+        sleep 2
+        echo "INFO: retrying jmap (retry=$jmap_retry_cnt, loop=$loop_cnt)."
+        continue
     fi
 
-    instance_cnt=`grep_cmd "[ 	]$TARGET_CLASS$" \
-        < "$TEST_NAME.jmap" \
+    set +e
+    instance_cnt=`grep "${PATTERN_WS}${TARGET_CLASS}${PATTERN_EOL}" \
+        "$TEST_NAME.jmap" \
         | sed '
             # strip leading whitespace; does nothing in JDK1.5.0
-            s/^[ 	][ 	]*//
+            s/^'"${PATTERN_WS}${PATTERN_WS}"'*//
             # strip <#bytes> in JDK1.5.0; does nothing otherwise
-            s/^[1-9][0-9]*[ 	][ 	]*//
+            s/^[1-9][0-9]*'"${PATTERN_WS}${PATTERN_WS}"'*//
             # strip <num>: field; does nothing in JDK1.5.0
-            s/^[1-9][0-9]*:[ 	][ 	]*//
+            s/^[1-9][0-9]*:'"${PATTERN_WS}${PATTERN_WS}"'*//
             # strip <class_name> field
-            s/[ 	].*//
+            s/'"${PATTERN_WS}"'.*//
             '`
+    set -e
     if [ -z "$instance_cnt" ]; then
         echo "INFO: instance count is unexpectedly empty"
         if [ "$loop_cnt" = 0 ]; then
@@ -210,8 +170,7 @@
             cat "$TEST_NAME.jmap"
             echo "INFO: end of jmap output."
             echo "FAIL: cannot find the instance count value." >&2
-            echo "INFO: killing $test_pid"
-            kill "$test_pid"
+            killApplication
             exit 2
         fi
     else
@@ -220,7 +179,17 @@
         if [ "$instance_cnt" -gt "$prev_instance_cnt" ]; then
             increasing_cnt=`expr $increasing_cnt + 1`
         else
+            # actually decreasing or the same
             decreasing_cnt=`expr $decreasing_cnt + 1`
+
+            # For this particular WeakReference leak, the count was
+            # always observed to be increasing so if we get a decreasing
+            # or the same count, then the leak is fixed in the bits
+            # being tested.
+            echo "INFO: finishing early due to non-increasing instance count."
+            finished_early=true
+            killApplication
+            break
         fi
         prev_instance_cnt="$instance_cnt"
     fi
@@ -231,8 +200,22 @@
     loop_cnt=`expr $loop_cnt + 1`
 done
 
+if [ $finished_early = false ]; then
+    stopApplication "$TEST_NAME.port"
+    waitForApplication
+fi
+
+echo "INFO: $TEST_NAME has finished running."
 echo "INFO: increasing_cnt = $increasing_cnt"
 echo "INFO: decreasing_cnt = $decreasing_cnt"
+if [ "$jmap_retry_cnt" -gt 0 ]; then
+    echo "INFO: jmap_retry_cnt = $jmap_retry_cnt (in $loop_cnt iterations)"
+fi
+
+if [ "$loop_cnt" = 0 ]; then
+    echo "FAIL: jmap is unable to take any samples." >&2
+    exit 2
+fi
 
 echo "INFO: The instance count of" `eval echo $TARGET_CLASS` "objects"
 if [ "$decreasing_cnt" = 0 ]; then
@@ -241,6 +224,6 @@
     exit 2
 fi
 
-echo "INFO: is both increasing and decreasing."
+echo "INFO: is not always increasing."
 echo "PASS: This indicates that there is not a memory leak."
 exit 0
--- a/test/java/util/logging/LoggerWeakRefLeak.java	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/java/util/logging/LoggerWeakRefLeak.java	Fri Jul 23 13:54:36 2010 -0700
@@ -23,27 +23,32 @@
 
 import java.util.logging.*;
 
-public class LoggerWeakRefLeak {
-    // AnonLoggerWeakRefLeak checks for one weak reference leak.
-    // LoggerWeakRefLeak checks for two weak reference leaks so
-    // this test runs twice as long, by default.
+public class LoggerWeakRefLeak extends SimpleApplication {
+    // The test driver script will allow this program to run until we
+    // reach DEFAULT_LOOP_TIME or a decrease in instance counts is
+    // observed. For these particular WeakReference leaks, the count
+    // was always observed to be increasing so if we get a decreasing
+    // count, then the leaks are fixed in the bits being tested.
+    // Two minutes has been enough time to observe a decrease in
+    // fixed bits on overloaded systems, but the test will likely
+    // finish more quickly.
     public static int DEFAULT_LOOP_TIME = 120;  // time is in seconds
 
-    public static void main(String[] args) {
+    // execute the LoggerWeakRefLeak app work
+    public void doMyAppWork(String[] args) throws Exception {
         int loop_time = 0;
         int max_loop_time = DEFAULT_LOOP_TIME;
 
-        if (args.length == 0) {
+        // args[0] is the port-file
+        if (args.length < 2) {
             System.out.println("INFO: using default time of "
                 + max_loop_time + " seconds.");
         } else {
             try {
-                max_loop_time = Integer.parseInt(args[0]);
+                max_loop_time = Integer.parseInt(args[1]);
             } catch (NumberFormatException nfe) {
-                System.err.println("Error: '" + args[0]
+                throw new RuntimeException("Error: '" + args[1]
                     + "': is not a valid seconds value.");
-                System.err.println("Usage: LoggerWeakRefLeak [seconds]");
-                System.exit(1);
             }
         }
 
@@ -86,4 +91,12 @@
 
         System.out.println("INFO: final loop count = " + count);
     }
+
+    public static void main(String[] args) throws Exception {
+        AnonLoggerWeakRefLeak myApp = new AnonLoggerWeakRefLeak();
+
+        SimpleApplication.setMyApp(myApp);
+
+        SimpleApplication.main(args);
+    }
 }
--- a/test/java/util/logging/LoggerWeakRefLeak.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/java/util/logging/LoggerWeakRefLeak.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,3 +1,5 @@
+#!/bin/sh
+
 #
 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@@ -26,72 +28,21 @@
 # @summary Check for WeakReference leak in Logger objects
 # @author Daniel D. Daugherty
 #
-# @run build LoggerWeakRefLeak
+# @library ../../../sun/tools/common
+# @build SimpleApplication ShutdownSimpleApplication
+# @build LoggerWeakRefLeak
 # @run shell/timeout=240 LoggerWeakRefLeak.sh
 
-# The timeout is: 2 minutes for infrastructure and 1 minute for the test
+# The timeout is: 2 minutes for infrastructure and 2 minutes for the test
 #
 
-if [ "${TESTJAVA}" = "" ]
-then
-  echo "TESTJAVA not set.  Test cannot execute.  Failed."
-  exit 1
-fi
+. ${TESTSRC}/../../../sun/tools/common/CommonSetup.sh
+. ${TESTSRC}/../../../sun/tools/common/ApplicationSetup.sh
 
-if [ "${TESTSRC}" = "" ]
-then
-  echo "TESTSRC not set.  Test cannot execute.  Failed."
-  exit 1
-fi
-
-if [ "${TESTCLASSES}" = "" ]
-then
-  echo "TESTCLASSES not set.  Test cannot execute.  Failed."
-  exit 1
-fi
-
-JAVA="${TESTJAVA}"/bin/java
-JMAP="${TESTJAVA}"/bin/jmap
-JPS="${TESTJAVA}"/bin/jps
-
-set -eu
 
 TEST_NAME="LoggerWeakRefLeak"
 TARGET_CLASS="java\.lang\.ref\.WeakReference"
 
-is_cygwin=false
-is_mks=false
-is_windows=false
-
-case `uname -s` in
-CYGWIN*)
-    is_cygwin=true
-    is_windows=true
-    ;;
-Windows_*)
-    is_mks=true
-    is_windows=true
-    ;;
-*)
-    ;;
-esac
-
-
-# wrapper for grep
-#
-grep_cmd() {
-    set +e
-    if $is_windows; then
-        # need dos2unix to get rid of CTRL-M chars from java output
-        dos2unix | grep "$@"
-        status="$?"
-    else
-        grep "$@"
-        status="$?"
-    fi
-    set -e
-}
-
 
 # MAIN begins here
 #
@@ -104,62 +55,39 @@
 # see if this version of jmap supports the '-histo:live' option
 jmap_option="-histo:live"
 set +e
-"${JMAP}" "$jmap_option" 0 > "$TEST_NAME.jmap" 2>&1
-grep '^Usage: ' "$TEST_NAME.jmap" > /dev/null 2>&1
+"${JMAP}" 2>&1 | grep ':live' > /dev/null 2>&1
 status="$?"
 set -e
-if [ "$status" = 0 ]; then
+if [ "$status" != 0 ]; then
     echo "INFO: switching jmap option from '$jmap_option'\c"
     jmap_option="-histo"
     echo " to '$jmap_option'."
 fi
 
-"${JAVA}" ${TESTVMOPTS} -classpath "${TESTCLASSES}" \
-    "$TEST_NAME" $seconds > "$TEST_NAME.log" 2>&1 &
-test_pid="$!"
-echo "INFO: starting $TEST_NAME as pid = $test_pid"
+# Start application and use TEST_NAME.port for coordination
+startApplication "$TEST_NAME" "$TEST_NAME.port" $seconds
 
-# wait for test program to get going
-count=0
-while [ "$count" -lt 30 ]; do
-    sleep 2
-    grep_cmd '^INFO: call count = 0$' < "$TEST_NAME.log" > /dev/null 2>&1
-    if [ "$status" = 0 ]; then
-        break
-    fi
-    count=`expr $count + 1`
-done
-
-if [ "$count" -ge 30 ]; then
-    echo "ERROR: $TEST_NAME failed to get going." >&2
-    echo "INFO: killing $test_pid"
-    kill "$test_pid"
-    exit 1
-elif [ "$count" -gt 1 ]; then
-    echo "INFO: $TEST_NAME took $count loops to start."
-fi
-
-if $is_cygwin; then
-    # We need the Windows pid for jmap and not the Cygwin pid.
-    # Note: '\t' works on Cygwin, but doesn't seem to work on Solaris.
-    jmap_pid=`"${JPS}"| grep_cmd "[ \t]$TEST_NAME$" | sed 's/[ \t].*//'`
-    if [ -z "$jmap_pid" ]; then
-        echo "FAIL: jps could not map Cygwin pid to Windows pid." >&2
-        echo "INFO: killing $test_pid"
-        kill "$test_pid"
-        exit 2
-    fi
-    echo "INFO: pid = $test_pid maps to Windows pid = $jmap_pid"
-else
-    jmap_pid="$test_pid"
-fi
+finished_early=false
 
 decreasing_cnt=0
 increasing_cnt=0
 loop_cnt=0
 prev_instance_cnt=0
 
+MAX_JMAP_TRY_CNT=10
+jmap_retry_cnt=0
+loop_cnt_on_retry=0
+
 while true; do
+    # see if the target process has finished its run and bail if it has
+    set +e
+    grep "^INFO: final loop count = " "$appOutput" > /dev/null 2>&1
+    status="$?"
+    set -e
+    if [ "$status" = 0 ]; then
+        break
+    fi
+
     # Output format for 'jmap -histo' in JDK1.5.0:
     #
     #     <#bytes> <#instances> <class_name>
@@ -169,38 +97,70 @@
     #     <num>: <#instances> <#bytes> <class_name>
     #
     set +e
-    "${JMAP}" "$jmap_option" "$jmap_pid" > "$TEST_NAME.jmap" 2>&1
+    "${JMAP}" "$jmap_option" "$appJavaPid" > "$TEST_NAME.jmap" 2>&1
     status="$?"
     set -e
 
     if [ "$status" != 0 ]; then
         echo "INFO: jmap exited with exit code = $status"
-        if [ "$loop_cnt" = 0 ]; then
-            echo "INFO: on the first iteration so no samples were taken."
-            echo "INFO: start of jmap output:"
-            cat "$TEST_NAME.jmap"
-            echo "INFO: end of jmap output."
+
+        # There are intermittent jmap failures; see 6498448.
+        #
+        # So far the following have been observed in a jmap call
+        # that was not in a race with target process termination:
+        #
+        # (Solaris specific, 2nd sample)
+        # <pid>: Unable to open door: target process not responding or HotSpot VM not loaded
+        # The -F option can be used when the target process is not responding
+        #
+        # (on Solaris so far)
+        # java.io.IOException
+        #
+        # (on Solaris so far, 1st sample)
+        # <pid>: Permission denied
+        #
+        sed 's/^/INFO: /' "$TEST_NAME.jmap"
+
+        if [ "$loop_cnt" = "$loop_cnt_on_retry" ]; then
+            # loop count hasn't changed
+            jmap_retry_cnt=`expr $jmap_retry_cnt + 1`
+        else
+            # loop count has changed so remember it
+            jmap_retry_cnt=1
+            loop_cnt_on_retry="$loop_cnt"
+        fi
+
+        # This is '-ge' because we have the original attempt plus
+        # MAX_JMAP_TRY_CNT - 1 retries.
+        if [ "$jmap_retry_cnt" -ge "$MAX_JMAP_TRY_CNT" ]; then
+            echo "INFO: jmap failed $MAX_JMAP_TRY_CNT times in a row" \
+                "without making any progress."
             echo "FAIL: jmap is unable to take any samples." >&2
-            echo "INFO: killing $test_pid"
-            kill "$test_pid"
+            killApplication
             exit 2
         fi
-        echo "INFO: The likely reason is that $TEST_NAME has finished running."
-        break
+
+        # short delay and try again
+        # Note: sleep 1 didn't help with "<pid>: Permission denied"
+        sleep 2
+        echo "INFO: retrying jmap (retry=$jmap_retry_cnt, loop=$loop_cnt)."
+        continue
     fi
 
-    instance_cnt=`grep_cmd "[ 	]$TARGET_CLASS$" \
-        < "$TEST_NAME.jmap" \
+    set +e
+    instance_cnt=`grep "${PATTERN_WS}${TARGET_CLASS}${PATTERN_EOL}" \
+        "$TEST_NAME.jmap" \
         | sed '
             # strip leading whitespace; does nothing in JDK1.5.0
-            s/^[ 	][ 	]*//
+            s/^'"${PATTERN_WS}${PATTERN_WS}"'*//
             # strip <#bytes> in JDK1.5.0; does nothing otherwise
-            s/^[1-9][0-9]*[ 	][ 	]*//
+            s/^[1-9][0-9]*'"${PATTERN_WS}${PATTERN_WS}"'*//
             # strip <num>: field; does nothing in JDK1.5.0
-            s/^[1-9][0-9]*:[ 	][ 	]*//
+            s/^[1-9][0-9]*:'"${PATTERN_WS}${PATTERN_WS}"'*//
             # strip <class_name> field
-            s/[ 	].*//
+            s/'"${PATTERN_WS}"'.*//
             '`
+    set -e
     if [ -z "$instance_cnt" ]; then
         echo "INFO: instance count is unexpectedly empty"
         if [ "$loop_cnt" = 0 ]; then
@@ -210,8 +170,7 @@
             cat "$TEST_NAME.jmap"
             echo "INFO: end of jmap output."
             echo "FAIL: cannot find the instance count value." >&2
-            echo "INFO: killing $test_pid"
-            kill "$test_pid"
+            killApplication
             exit 2
         fi
     else
@@ -220,7 +179,17 @@
         if [ "$instance_cnt" -gt "$prev_instance_cnt" ]; then
             increasing_cnt=`expr $increasing_cnt + 1`
         else
+            # actually decreasing or the same
             decreasing_cnt=`expr $decreasing_cnt + 1`
+
+            # For these particular WeakReference leaks, the count was
+            # always observed to be increasing so if we get a decreasing
+            # or the same count, then the leaks are fixed in the bits
+            # being tested.
+            echo "INFO: finishing early due to non-increasing instance count."
+            finished_early=true
+            killApplication
+            break
         fi
         prev_instance_cnt="$instance_cnt"
     fi
@@ -231,8 +200,22 @@
     loop_cnt=`expr $loop_cnt + 1`
 done
 
+if [ $finished_early = false ]; then
+    stopApplication "$TEST_NAME.port"
+    waitForApplication
+fi
+
+echo "INFO: $TEST_NAME has finished running."
 echo "INFO: increasing_cnt = $increasing_cnt"
 echo "INFO: decreasing_cnt = $decreasing_cnt"
+if [ "$jmap_retry_cnt" -gt 0 ]; then
+    echo "INFO: jmap_retry_cnt = $jmap_retry_cnt (in $loop_cnt iterations)"
+fi
+
+if [ "$loop_cnt" = 0 ]; then
+    echo "FAIL: jmap is unable to take any samples." >&2
+    exit 2
+fi
 
 echo "INFO: The instance count of" `eval echo $TARGET_CLASS` "objects"
 if [ "$decreasing_cnt" = 0 ]; then
@@ -241,6 +224,6 @@
     exit 2
 fi
 
-echo "INFO: is both increasing and decreasing."
+echo "INFO: is not always increasing."
 echo "PASS: This indicates that there is not a memory leak."
 exit 0
--- a/test/sun/tools/common/ApplicationSetup.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/common/ApplicationSetup.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -24,50 +24,187 @@
 #
 
 
-# Support function to start and stop a given application
+# Support functions to start, stop, wait for or kill a given SimpleApplication
 
-# Starts a given application as background process, usage:
-#   startApplication <class> [args...]
+# Starts a given app as background process, usage:
+#   startApplication <class> port-file [args...]
 #
-# Waits for application to print something to indicate it is running
-# (and initialized). Output is directed to ${TESTCLASSES}/Application.out.
-# Sets $pid to be the process-id of the application.
-
+# The following variables are set:
+#
+# appJavaPid  - application's Java pid
+# appOtherPid - pid associated with the app other than appJavaPid
+# appPidList  - all pids associated with the app
+# appOutput   - file containing stdout and stderr from the app
+#
+# Waits for at least one line of output from the app to indicate
+# that it is up and running.
+#
 startApplication()
 {
-  OUTPUTFILE=${TESTCLASSES}/Application.out
-  ${JAVA} $1 $2 $3 $4 $5 $6 > ${OUTPUTFILE} &
-  pid="$!"
-                                                                                                     
-  # MKS creates an intermediate shell to launch ${JAVA} so
-  # ${pid} is not the actual pid. We have put in a small sleep
-  # to give the intermediate shell process time to launch the
-  # "java" process.
-  if [ "$OS" = "Windows" ]; then
-    sleep 2
-    realpid=`ps -o pid,ppid,comm|grep ${pid}|grep "java"|cut -c1-6`
-    pid=${realpid}
-  fi
-                                                                                                     
-  echo "Waiting for Application to initialize..."
-  attempts=0
+  appOutput="${TESTCLASSES}/Application.out"
+
+  ${JAVA} -classpath "${TESTCLASSES}" "$@" > "$appOutput" 2>&1 &
+  appJavaPid="$!"
+  appOtherPid=
+  appPidList="$appJavaPid"
+
+  echo "INFO: waiting for $1 to initialize..."
+  _cnt=0
   while true; do
+    # if the app doesn't start then the JavaTest/JTREG timeout will
+    # kick in so this isn't really a endless loop
     sleep 1
-    out=`tail -1 ${OUTPUTFILE}`
-    if [ ! -z "$out" ]; then
+    out=`tail -1 "$appOutput"`
+    if [ -n "$out" ]; then
+      # we got some output from the app so it's running
       break
     fi
-    attempts=`expr $attempts + 1`
-    echo "Waiting $attempts second(s) ..."
+    _cnt=`expr $_cnt + 1`
+    echo "INFO: waited $_cnt second(s) ..."
   done
+  unset _cnt
 
-  echo "Application is process $pid"
+  if $isWindows; then
+    # Windows requires special handling
+    appOtherPid="$appJavaPid"
+
+    if $isCygwin; then
+      appJavaPid=`ps -p "$appOtherPid" \
+        | sed -n '
+          # See if $appOtherPid is in PID column; there are sometimes
+          # non-blanks in column 1 (I and S observed so far)
+          /^.'"${PATTERN_WS}${PATTERN_WS}*${appOtherPid}${PATTERN_WS}"'/{
+            # strip PID column
+            s/^.'"${PATTERN_WS}${PATTERN_WS}*${appOtherPid}${PATTERN_WS}${PATTERN_WS}"'*//
+            # strip PPID column
+            s/^[1-9][0-9]*'"${PATTERN_WS}${PATTERN_WS}"'*//
+            # strip PGID column
+            s/^[1-9][0-9]*'"${PATTERN_WS}${PATTERN_WS}"'*//
+            # strip everything after WINPID column
+            s/'"${PATTERN_WS}"'.*//
+            p
+            q
+          }
+        '`
+      echo "INFO: Cygwin pid=$appOtherPid maps to Windows pid=$appJavaPid"
+    else
+      # show PID, PPID and COMM columns only
+      appJavaPid=`ps -o pid,ppid,comm \
+        | sed -n '
+          # see if appOtherPid is in either PID or PPID columns
+          /'"${PATTERN_WS}${appOtherPid}${PATTERN_WS}"'/{
+            # see if this is a java command
+            /java'"${PATTERN_EOL}"'/{
+              # strip leading white space
+              s/^'"${PATTERN_WS}${PATTERN_WS}"'*//
+              # strip everything after the first word
+              s/'"${PATTERN_WS}"'.*//
+              # print the pid and we are done
+              p
+              q
+            }
+          }
+        '`
+      echo "INFO: MKS shell pid=$appOtherPid; Java pid=$appJavaPid"
+    fi
+
+    if [ -z "$appJavaPid" ]; then
+      echo "ERROR: could not find app's Java pid." >&2
+      killApplication
+      exit 2
+    fi
+    appPidList="$appOtherPid $appJavaPid"
+  fi
+
+  echo "INFO: $1 is process $appJavaPid"
+  echo "INFO: $1 output is in $appOutput"
 }
 
-# Stops an application by invoking the given class and argument, usage:
-#   stopApplication <class> <argument>
+
+# Stops a simple application by invoking ShutdownSimpleApplication
+# class with a specific port-file, usage:
+#   stopApplication port-file
+#
+# Note: When this function returns, the SimpleApplication (or a subclass)
+# may still be running because the application has not yet reached the
+# shutdown check.
+#
 stopApplication()
 {
-  $JAVA -classpath "${TESTCLASSES}" $1 $2
+  $JAVA -classpath "${TESTCLASSES}" ShutdownSimpleApplication $1
 }
 
+
+# Wait for a simple application to stop running.
+#
+waitForApplication() {
+  if [ $isWindows = false ]; then
+    # non-Windows is easy; just one process
+    echo "INFO: waiting for $appJavaPid"
+    set +e
+    wait "$appJavaPid"
+    set -e
+
+  elif $isCygwin; then
+    # Cygwin pid and not the Windows pid
+    echo "INFO: waiting for $appOtherPid"
+    set +e
+    wait "$appOtherPid"
+    set -e
+
+  else # implied isMKS
+    # MKS has intermediate shell and Java process
+    echo "INFO: waiting for $appJavaPid"
+
+    # appJavaPid can be empty if pid search in startApplication() failed
+    if [ -n "$appJavaPid" ]; then
+      # only need to wait for the Java process
+      set +e
+      wait "$appJavaPid"
+      set -e
+    fi
+  fi
+}
+
+
+# Kills a simple application by sending a SIGTERM to the appropriate
+# process(es); on Windows SIGQUIT (-9) is used.
+#
+killApplication()
+{
+  if [ $isWindows = false ]; then
+    # non-Windows is easy; just one process
+    echo "INFO: killing $appJavaPid"
+    set +e
+    kill -TERM "$appJavaPid"  # try a polite SIGTERM first
+    sleep 2
+    # send SIGQUIT (-9) just in case SIGTERM didn't do it
+    # but don't show any complaints
+    kill -QUIT "$appJavaPid" > /dev/null 2>&1
+    wait "$appJavaPid"
+    set -e
+
+  elif $isCygwin; then
+    # Cygwin pid and not the Windows pid
+    echo "INFO: killing $appOtherPid"
+    set +e
+    kill -9 "$appOtherPid"
+    wait "$appOtherPid"
+    set -e
+
+  else # implied isMKS
+    # MKS has intermediate shell and Java process
+    echo "INFO: killing $appPidList"
+    set +e
+    kill -9 $appPidList
+    set -e
+
+    # appJavaPid can be empty if pid search in startApplication() failed
+    if [ -n "$appJavaPid" ]; then
+      # only need to wait for the Java process
+      set +e
+      wait "$appJavaPid"
+      set -e
+    fi
+  fi
+}
--- a/test/sun/tools/common/CommonSetup.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/common/CommonSetup.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -24,51 +24,94 @@
 #
 
 
-# Common setup for tool tests. 
+# Common setup for tool tests and other tests that use jtools.
 # Checks that TESTJAVA, TESTSRC, and TESTCLASSES environment variables are set.
-# Creates the following for use by the tool tests
-#   JAVA     java launcher
-#   JSTACK   jstack utility
-#   JMAP     jmap utility
-#   JINFO    jinfo utility
-#   JHAT     jhat utility
-#   PS       path separator (";" or ":")
-#   OS       operating system 
+#
+# Creates the following constants for use by the caller:
+#   JAVA        - java launcher
+#   JHAT        - jhat utility
+#   JINFO       - jinfo utility
+#   JMAP        - jmap utility
+#   JPS         - jps utility
+#   JSTACK      - jstack utility
+#   OS          - operating system name
+#   PATTERN_EOL - grep or sed end-of-line pattern
+#   PATTERN_WS  - grep or sed whitespace pattern
+#   PS          - path separator (";" or ":")
+#
+# Sets the following variables:
+#
+#   isCygwin  - true if environment is Cygwin
+#   isMKS     - true if environment is MKS
+#   isLinux   - true if OS is Linux
+#   isSolaris - true if OS is Solaris
+#   isWindows - true if OS is Windows
 
 
-if [ "${TESTJAVA}" = "" ]
-then
-  echo "TESTJAVA not set.  Test cannot execute.  Failed."
+if [ -z "${TESTJAVA}" ]; then
+  echo "ERROR: TESTJAVA not set.  Test cannot execute.  Failed."
   exit 1
 fi
-                                                                                                     
-if [ "${TESTSRC}" = "" ]
-then
-  echo "TESTSRC not set.  Test cannot execute.  Failed."
+
+if [ -z "${TESTSRC}" ]; then
+  echo "ERROR: TESTSRC not set.  Test cannot execute.  Failed."
   exit 1
 fi
-                                                                                                     
-if [ "${TESTCLASSES}" = "" ]
-then
-  echo "TESTCLASSES not set.  Test cannot execute.  Failed."
+
+if [ -z "${TESTCLASSES}" ]; then
+  echo "ERROR: TESTCLASSES not set.  Test cannot execute.  Failed."
   exit 1
 fi
-                                                                                                     
+
+# only enable these after checking the expected incoming env variables
+set -eu
+
 JAVA="${TESTJAVA}/bin/java"
+JHAT="${TESTJAVA}/bin/jhat"
+JINFO="${TESTJAVA}/bin/jinfo"
+JMAP="${TESTJAVA}/bin/jmap"
+JPS="${TESTJAVA}/bin/jps"
 JSTACK="${TESTJAVA}/bin/jstack"
-JMAP="${TESTJAVA}/bin/jmap"
-JINFO="${TESTJAVA}/bin/jinfo"
-JHAT="${TESTJAVA}/bin/jhat"
+
+isCygwin=false
+isMKS=false
+isLinux=false
+isSolaris=false
+isUnknownOS=false
+isWindows=false
 
 OS=`uname -s`
 
+# start with some UNIX like defaults
+PATTERN_EOL='$'
+# blank and tab
+PATTERN_WS='[ 	]'
+PS=":"
+
 case "$OS" in
+  CYGWIN* )
+    OS="Windows"
+    PATTERN_EOL='[
]*$'
+    # blank and tab
+    PATTERN_WS='[ \t]'
+    isCygwin=true
+    isWindows=true
+    ;;
+  Linux )
+    OS="Linux"
+    isLinux=true
+    ;;
+  SunOS )
+    OS="Solaris"
+    isSolaris=true
+    ;;
   Windows* )
+    OS="Windows"
+    PATTERN_EOL='[
]*$'
     PS=";"
-    OS="Windows"
+    isWindows=true
     ;;
   * )
-    PS=":"
+    isUnknownOS=true
     ;;
 esac
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/tools/common/CommonTests.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -0,0 +1,314 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+
+# @test
+# @bug 6964018
+# @summary Unit test for common tools infrastructure.
+#
+# @build SimpleApplication SleeperApplication ShutdownSimpleApplication
+# @run shell CommonTests.sh
+
+. ${TESTSRC}/CommonSetup.sh
+. ${TESTSRC}/ApplicationSetup.sh
+
+# hope for the best:
+status=0
+
+
+# Test program path constants from CommonSetup.sh:
+#
+for name in JAVA JHAT JINFO JMAP JPS JSTACK; do
+    eval value=$`echo $name`
+
+    echo "INFO: $name=$value"
+    if [ -x "$value" ]; then
+        echo "INFO: '$value' is executable."
+    else
+        echo "ERROR: '$value' is not executable." >&2
+        status=1
+    fi
+done
+
+
+# Display flag values from CommonSetup.sh:
+#
+for name in isCygwin isMKS isLinux isSolaris isUnknownOS isWindows; do
+    eval value=$`echo $name`
+    echo "INFO: flag $name=$value"
+done
+
+
+# Test OS constant from CommonSetup.sh:
+#
+if [ -z "$OS" ]; then
+    echo "ERROR: OS constant cannot be empty." >&2
+    status=1
+fi
+
+
+# Display the PATTERN_EOL value:
+#
+echo "INFO: PATTERN_EOL="`echo "$PATTERN_EOL" | od -c`
+
+
+# Test PATTERN_EOL with 'grep' for a regular line.
+#
+TESTOUT="${TESTCLASSES}/testout.grep_reg_line_eol"
+set +e
+echo 'regular line' | grep "line${PATTERN_EOL}" > "$TESTOUT"
+set -e
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: PATTERN_EOL works for regular line with grep."
+else
+    echo "ERROR: PATTERN_EOL does not work for regular line with grep." >&2
+    status=1
+fi
+
+
+if $isWindows; then
+    # Test PATTERN_EOL with 'grep' for a CR line.
+    #
+    TESTOUT="${TESTCLASSES}/testout.grep_cr_line_eol"
+    set +e
+    echo 'CR line
' | grep "line${PATTERN_EOL}" > "$TESTOUT"
+    set -e
+    if [ -s "$TESTOUT" ]; then
+        echo "INFO: PATTERN_EOL works for CR line with grep."
+    else
+        echo "ERROR: PATTERN_EOL does not work for CR line with grep." >&2
+        status=1
+    fi
+fi
+
+
+# Test PATTERN_EOL with 'sed' for a regular line.
+#
+TESTOUT="${TESTCLASSES}/testout.sed_reg_line_eol"
+echo 'regular line' | sed -n "/line${PATTERN_EOL}/p" > "$TESTOUT"
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: PATTERN_EOL works for regular line with sed."
+else
+    echo "ERROR: PATTERN_EOL does not work for regular line with sed." >&2
+    status=1
+fi
+
+
+if $isWindows; then
+    # Test PATTERN_EOL with 'sed' for a CR line.
+    #
+    TESTOUT="${TESTCLASSES}/testout.sed_cr_line_eol"
+    echo 'CR line
' | sed -n "/line${PATTERN_EOL}/p" > "$TESTOUT"
+    if [ -s "$TESTOUT" ]; then
+        echo "INFO: PATTERN_EOL works for CR line with sed."
+    else
+        echo "ERROR: PATTERN_EOL does not work for CR line with sed." >&2
+        status=1
+    fi
+fi
+
+
+# Display the PATTERN_WS value:
+#
+echo "INFO: PATTERN_WS="`echo "$PATTERN_WS" | od -c`
+
+
+# Test PATTERN_WS with 'grep' for a blank.
+#
+TESTOUT="${TESTCLASSES}/testout.grep_blank"
+set +e
+echo 'blank: ' | grep "$PATTERN_WS" > "$TESTOUT"
+set -e
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: PATTERN_WS works for blanks with grep."
+else
+    echo "ERROR: PATTERN_WS does not work for blanks with grep." >&2
+    status=1
+fi
+
+
+# Test PATTERN_WS with 'grep' for a tab.
+#
+TESTOUT="${TESTCLASSES}/testout.grep_tab"
+set +e
+echo 'tab:	' | grep "$PATTERN_WS" > "$TESTOUT"
+set -e
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: PATTERN_WS works for tabs with grep."
+else
+    echo "ERROR: PATTERN_WS does not work for tabs with grep." >&2
+    status=1
+fi
+
+
+# Test PATTERN_WS with 'sed' for a blank.
+#
+TESTOUT="${TESTCLASSES}/testout.sed_blank"
+echo 'blank: ' | sed -n "/$PATTERN_WS/p" > "$TESTOUT"
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: PATTERN_WS works for blanks with sed."
+else
+    echo "ERROR: PATTERN_WS does not work for blanks with sed." >&2
+    status=1
+fi
+
+
+# Test PATTERN_WS with 'sed' for a tab.
+#
+TESTOUT="${TESTCLASSES}/testout.sed_tab"
+echo 'tab:	' | sed -n "/$PATTERN_WS/p" > "$TESTOUT"
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: PATTERN_WS works for tabs with sed."
+else
+    echo "ERROR: PATTERN_WS does not work for tabs with sed." >&2
+    status=1
+fi
+
+
+# Test startApplication and use PORTFILE for coordination
+# The app sleeps for 30 seconds.
+#
+PORTFILE="${TESTCLASSES}"/shutdown.port
+startApplication SleeperApplication "${PORTFILE}" 30
+
+
+# Test appJavaPid in "ps" cmd output.
+#
+TESTOUT="${TESTCLASSES}/testout.ps_app"
+set +e
+if $isCygwin; then
+    # On Cygwin, appJavaPid is the Windows pid for the Java process
+    # and appOtherPid is the Cygwin pid for the Java process.
+    ps -p "$appOtherPid" \
+        | grep "${PATTERN_WS}${appJavaPid}${PATTERN_WS}" > "$TESTOUT"
+else
+    # output only pid and comm columns to avoid mismatches
+    ps -eo pid,comm \
+        | grep "^${PATTERN_WS}*${appJavaPid}${PATTERN_WS}" > "$TESTOUT"
+fi
+set -e
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: begin appJavaPid=$appJavaPid in 'ps' cmd output:"
+    cat "$TESTOUT"
+    echo "INFO: end appJavaPid=$appJavaPid in 'ps' cmd output."
+else
+    echo "ERROR: 'ps' cmd should show appJavaPid=$appJavaPid." >&2
+    status=1
+fi
+
+if [ -n "$appOtherPid" ]; then
+    # Test appOtherPid in "ps" cmd output, if we have one.
+    #
+    TESTOUT="${TESTCLASSES}/testout.ps_other"
+    set +e
+    if $isCygwin; then
+        ps -p "$appOtherPid" \
+            | grep "${PATTERN_WS}${appOtherPid}${PATTERN_WS}" > "$TESTOUT"
+    else
+        # output only pid and comm columns to avoid mismatches
+        ps -eo pid,comm \
+            | grep "^${PATTERN_WS}*${appOtherPid}${PATTERN_WS}" > "$TESTOUT"
+    fi
+    set -e
+    if [ -s "$TESTOUT" ]; then
+        echo "INFO: begin appOtherPid=$appOtherPid in 'ps' cmd output:"
+        cat "$TESTOUT"
+        echo "INFO: end appOtherPid=$appOtherPid in 'ps' cmd output."
+    else
+        echo "ERROR: 'ps' cmd should show appOtherPid=$appOtherPid." >&2
+        status=1
+    fi
+fi
+
+
+# Test stopApplication and PORTFILE for coordination
+#
+stopApplication "${PORTFILE}"
+
+
+# Test application still running after stopApplication.
+#
+# stopApplication just lets the app know that it can stop, but the
+# app might still be doing work. This test just demonstrates that
+# fact and doesn't fail if the app is already done.
+#
+TESTOUT="${TESTCLASSES}/testout.after_stop"
+set +e
+if $isCygwin; then
+    # On Cygwin, appJavaPid is the Windows pid for the Java process
+    # and appOtherPid is the Cygwin pid for the Java process.
+    ps -p "$appOtherPid" \
+        | grep "${PATTERN_WS}${appJavaPid}${PATTERN_WS}" > "$TESTOUT"
+else
+    # output only pid and comm columns to avoid mismatches
+    ps -eo pid,comm \
+        | grep "^${PATTERN_WS}*${appJavaPid}${PATTERN_WS}" > "$TESTOUT"
+fi
+set -e
+if [ -s "$TESTOUT" ]; then
+    echo "INFO: it is okay for appJavaPid=$appJavaPid to still be running" \
+        "after stopApplication() is called."
+    echo "INFO: begin 'after_stop' output:"
+    cat "$TESTOUT"
+    echo "INFO: end 'after_stop' output."
+fi
+
+
+# Test waitForApplication
+#
+# The app might already be gone so this function shouldn't generate
+# a fatal error in either call.
+#
+waitForApplication
+
+if [ $isWindows = false ]; then
+    # Windows can recycle pids quickly so we can't use this test there
+    TESTOUT="${TESTCLASSES}/testout.after_kill"
+    set +e
+    # output only pid and comm columns to avoid mismatches
+    ps -eo pid,comm \
+        | grep "^${PATTERN_WS}*${appJavaPid}${PATTERN_WS}" > "$TESTOUT"
+    set -e
+    if [ -s "$TESTOUT" ]; then
+        echo "ERROR: 'ps' cmd should not show appJavaPid." >&2
+        echo "ERROR: begin 'after_kill' output:" >&2
+        cat "$TESTOUT" >&2
+        echo "ERROR: end 'after_kill' output." >&2
+        status=1
+    else
+        echo "INFO: 'ps' cmd does not show appJavaPid after" \
+            "waitForApplication() is called."
+    fi
+fi
+
+
+# Test killApplication
+#
+# The app is already be gone so this function shouldn't generate
+# a fatal error.
+#
+killApplication
+
+exit $status
--- a/test/sun/tools/common/ShutdownSimpleApplication.java	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/common/ShutdownSimpleApplication.java	Fri Jul 23 13:54:36 2010 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,10 +22,13 @@
  */
 
 /*
+ * Used to shutdown SimpleApplication (or a subclass). The argument to
+ * this class is the name of a file that contains the TCP port number
+ * on which SimpleApplication (or a subclass) is listening.
  *
- *
- * Used to shutdown SimpleApplication. The argument to this class is
- * the TCP port number where SimpleApplication is listening.
+ * Note: When this program returns, the SimpleApplication (or a subclass)
+ * may still be running because the application has not yet reached the
+ * shutdown check.
  */
 import java.net.Socket;
 import java.net.InetSocketAddress;
@@ -35,6 +38,11 @@
 public class ShutdownSimpleApplication {
     public static void main(String args[]) throws Exception {
 
+        if (args.length != 1) {
+            throw new RuntimeException("Usage: ShutdownSimpleApplication" +
+                " port-file");
+        }
+
         // read the (TCP) port number from the given file
 
         File f = new File(args[0]);
@@ -42,21 +50,27 @@
         byte b[] = new byte[8];
         int n = fis.read(b);
         if (n < 1) {
-            throw new RuntimeException("Empty file");
+            throw new RuntimeException("Empty port-file");
         }
         fis.close();
 
         String str = new String(b, 0, n, "UTF-8");
-        System.out.println("Port number of application is: " + str);
+        System.out.println("INFO: Port number of SimpleApplication: " + str);
         int port = Integer.parseInt(str);
 
         // Now connect to the port (which will shutdown application)
 
-        System.out.println("Connecting to port " + port +
-            " to shutdown Application ...");
+        System.out.println("INFO: Connecting to port " + port +
+            " to shutdown SimpleApplication ...");
+        System.out.flush();
 
         Socket s = new Socket();
         s.connect( new InetSocketAddress(port) );
         s.close();
+
+        System.out.println("INFO: done connecting to SimpleApplication.");
+        System.out.flush();
+
+        System.exit(0);
     }
 }
--- a/test/sun/tools/common/SimpleApplication.java	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/common/SimpleApplication.java	Fri Jul 23 13:54:36 2010 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,10 +22,12 @@
  */
 
 /*
+ * A simple application used by unit tests. The first argument to this
+ * class is the name of a file to which a TCP port number can be written.
  *
- *
- * A simple application used for tool unit tests. It does nothing else
- * bind to a TCP port and wait for a shutdown message.
+ * By default, this class does nothing other than bind to a TCP port,
+ * write the TCP port number to a file, and wait for an incoming connection
+ * in order to complete the application shutdown protocol.
  */
 import java.net.Socket;
 import java.net.ServerSocket;
@@ -33,25 +35,86 @@
 import java.io.FileOutputStream;
 
 public class SimpleApplication {
-    public static void main(String args[]) throws Exception {
+    private static SimpleApplication myApp;      // simple app or a subclass
+    private static String            myAppName;  // simple app name
+    private static int               myPort;     // coordination port #
+    private static ServerSocket      mySS;       // coordination socket
+
+    // protected so a subclass can extend it; not public so creation is
+    // limited.
+    protected SimpleApplication() {
+        // save simple app (or subclass) name for messages
+        myAppName = getClass().getName();
+    }
+
+    // return the simple application (or a subclass)
+    final public static SimpleApplication getMyApp() {
+        return myApp;
+    }
+
+    // set the simple application (for use by a subclass)
+    final public static void setMyApp(SimpleApplication _myApp) {
+        myApp = _myApp;
+    }
+
+    // execute the application finish protocol
+    final public void doMyAppFinish(String[] args) throws Exception {
+        System.out.println("INFO: " + myAppName + " is waiting on port: " +
+            myPort);
+        System.out.flush();
+
+        // wait for test harness to connect
+        Socket s = mySS.accept();
+        s.close();
+        mySS.close();
+
+        System.out.println("INFO: " + myAppName + " is shutting down.");
+        System.out.flush();
+    }
+
+    // execute the application start protocol
+    final public void doMyAppStart(String[] args) throws Exception {
+        if (args.length < 1) {
+            throw new RuntimeException("Usage: " + myAppName +
+                " port-file [arg(s)]");
+        }
+
         // bind to a random port
-        ServerSocket ss = new ServerSocket(0);
-        int port = ss.getLocalPort();
+        mySS = new ServerSocket(0);
+        myPort = mySS.getLocalPort();
 
         // Write the port number to the given file
         File f = new File(args[0]);
         FileOutputStream fos = new FileOutputStream(f);
-        fos.write( Integer.toString(port).getBytes("UTF-8") );
+        fos.write( Integer.toString(myPort).getBytes("UTF-8") );
         fos.close();
 
-        System.out.println("Application waiting on port: " + port);
+        System.out.println("INFO: " + myAppName + " created socket on port: " +
+            myPort);
+        System.out.flush();
+    }
+
+    // execute the app work (subclass can override this)
+    public void doMyAppWork(String[] args) throws Exception {
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (myApp == null) {
+            // create myApp since a subclass hasn't done so
+            myApp = new SimpleApplication();
+        }
+
+        myApp.doMyAppStart(args);   // do the app start protocol
+
+        System.out.println("INFO: " + myAppName + " is calling doMyAppWork()");
+        System.out.flush();
+        myApp.doMyAppWork(args);    // do the app work
+        System.out.println("INFO: " + myAppName + " returned from" +
+            " doMyAppWork()");
         System.out.flush();
 
-        // wait for test harness to connect
-        Socket s = ss.accept();
-        s.close();
-        ss.close();
+        myApp.doMyAppFinish(args);  // do the app finish protocol
 
-        System.out.println("Application shutdown.");
+        System.exit(0);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/tools/common/SleeperApplication.java	Fri Jul 23 13:54:36 2010 -0700
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * An example subclass of SimpleApplication that illustrates how to
+ * override the doMyAppWork() method.
+ */
+
+public class SleeperApplication extends SimpleApplication {
+    public static int DEFAULT_SLEEP_TIME = 60;  // time is in seconds
+
+    // execute the sleeper app work
+    public void doMyAppWork(String[] args) throws Exception {
+        int sleep_time = DEFAULT_SLEEP_TIME;
+
+        // args[0] is the port-file
+        if (args.length < 2) {
+            System.out.println("INFO: using default sleep time of "
+                + sleep_time + " seconds.");
+        } else {
+            try {
+                sleep_time = Integer.parseInt(args[1]);
+            } catch (NumberFormatException nfe) {
+                throw new RuntimeException("Error: '" + args[1] +
+                    "': is not a valid seconds value.");
+            }
+        }
+
+        Thread.sleep(sleep_time * 1000);  // our "work" is to sleep
+    }
+
+    public static void main(String[] args) throws Exception {
+        SleeperApplication myApp = new SleeperApplication();
+
+        SimpleApplication.setMyApp(myApp);
+
+        SimpleApplication.main(args);
+    }
+}
--- a/test/sun/tools/jhat/ParseTest.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/jhat/ParseTest.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -32,7 +32,11 @@
 # @run shell ParseTest.sh
 
 . ${TESTSRC}/../common/CommonSetup.sh
-. ${TESTSRC}/../common/ApplicationSetup.sh
+
+# all return statuses are checked in this test
+set +e
+
+failed=0
 
 DUMPFILE="minimal.bin"
 
--- a/test/sun/tools/jinfo/Basic.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/jinfo/Basic.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -35,53 +35,57 @@
 . ${TESTSRC}/../common/CommonSetup.sh
 . ${TESTSRC}/../common/ApplicationSetup.sh
 
-# Start application (send output to shutdown.port)
+# Start application and use PORTFILE for coordination
 PORTFILE="${TESTCLASSES}"/shutdown.port
-startApplication \
-    -classpath "${TESTCLASSES}" SimpleApplication "${PORTFILE}"
+startApplication SimpleApplication "${PORTFILE}"
+
+# all return statuses are checked in this test
+set +e
 
 failed=0
 
-if [ "$OS" != "Windows" ]; then
+if [ $isWindows = false ]; then
     # -sysprops option
-    ${JINFO} -sysprops $pid
+    ${JINFO} -sysprops $appJavaPid
     if [ $? != 0 ]; then failed=1; fi
 
     # -flags option
-    ${JINFO} -flags $pid
+    ${JINFO} -flags $appJavaPid
     if [ $? != 0 ]; then failed=1; fi
 
     # no option
-    ${JINFO} $pid
+    ${JINFO} $appJavaPid
     if [ $? != 0 ]; then failed=1; fi
 
 fi
 
 
 # -flag option
-${JINFO} -flag +PrintGC $pid
+${JINFO} -flag +PrintGC $appJavaPid
 if [ $? != 0 ]; then failed=1; fi 
 
-${JINFO} -flag -PrintGC $pid
+${JINFO} -flag -PrintGC $appJavaPid
 if [ $? != 0 ]; then failed=1; fi
 
-${JINFO} -flag PrintGC $pid
+${JINFO} -flag PrintGC $appJavaPid
 if [ $? != 0 ]; then failed=1; fi
 
-if [ "$OS" = "SunOS" ]; then
+if $isSolaris; then
 
-    ${JINFO} -flag +ExtendedDTraceProbes $pid
+    ${JINFO} -flag +ExtendedDTraceProbes $appJavaPid
     if [ $? != 0 ]; then failed=1; fi
 
-    ${JINFO} -flag -ExtendedDTraceProbes $pid
+    ${JINFO} -flag -ExtendedDTraceProbes $appJavaPid
     if [ $? != 0 ]; then failed=1; fi
 
-    ${JINFO} -flag ExtendedDTraceProbes $pid
+    ${JINFO} -flag ExtendedDTraceProbes $appJavaPid
     if [ $? != 0 ]; then failed=1; fi
 
 fi
 
-stopApplication ShutdownSimpleApplication "${PORTFILE}"
+set -e
+
+stopApplication "${PORTFILE}"
+waitForApplication
 
 exit $failed
-
--- a/test/sun/tools/jmap/Basic.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/jmap/Basic.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -35,24 +35,25 @@
 . ${TESTSRC}/../common/CommonSetup.sh
 . ${TESTSRC}/../common/ApplicationSetup.sh
 
-# Start application (send output to shutdown.port)
+# Start application and use PORTFILE for coordination
 PORTFILE="${TESTCLASSES}"/shutdown.port
-startApplication \
-    -classpath "${TESTCLASSES}" SimpleApplication "${PORTFILE}"
+startApplication SimpleApplication "${PORTFILE}"
+
+# all return statuses are checked in this test
+set +e
 
 failed=0
 
 # -histo[:live] option
-${JMAP} -histo $pid
+${JMAP} -histo $appJavaPid
 if [ $? != 0 ]; then failed=1; fi
 
-${JMAP} -histo:live $pid
+${JMAP} -histo:live $appJavaPid
 if [ $? != 0 ]; then failed=1; fi
 
 # -dump option
-p=`expr $pid`
-DUMPFILE="java_pid${p}.hprof"
-${JMAP} -dump:format=b,file=${DUMPFILE} $pid
+DUMPFILE="java_pid${appJavaPid}.hprof"
+${JMAP} -dump:format=b,file=${DUMPFILE} $appJavaPid
 if [ $? != 0 ]; then failed=1; fi
 
 # check that heap dump is parsable
@@ -63,7 +64,7 @@
 rm ${DUMPFILE}
 
 # -dump:live option
-${JMAP} -dump:live,format=b,file=${DUMPFILE} $pid
+${JMAP} -dump:live,format=b,file=${DUMPFILE} $appJavaPid
 if [ $? != 0 ]; then failed=1; fi
 
 # check that heap dump is parsable
@@ -71,9 +72,11 @@
 if [ $? != 0 ]; then failed=1; fi
 
 # dump file is large so remove it
-rm ${DUMPFILE}
+rm -f ${DUMPFILE}
 
-stopApplication ShutdownSimpleApplication "${PORTFILE}"
+set -e
+
+stopApplication "${PORTFILE}"
+waitForApplication
 
 exit $failed
-
--- a/test/sun/tools/jstack/Basic.sh	Fri Jul 23 13:53:50 2010 -0700
+++ b/test/sun/tools/jstack/Basic.sh	Fri Jul 23 13:54:36 2010 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -35,22 +35,26 @@
 . ${TESTSRC}/../common/CommonSetup.sh
 . ${TESTSRC}/../common/ApplicationSetup.sh
 
-# Start application (send output to shutdown.port)
+# Start application and use PORTFILE for coordination
 PORTFILE="${TESTCLASSES}"/shutdown.port
-startApplication \
-    -classpath "${TESTCLASSES}" SimpleApplication "${PORTFILE}"
+startApplication SimpleApplication "${PORTFILE}"
+
+# all return statuses are checked in this test
+set +e
 
 failed=0
 
 # normal
-$JSTACK $pid 2>&1
+$JSTACK $appJavaPid 2>&1
 if [ $? != 0 ]; then failed=1; fi
 
 # long
-$JSTACK -l $pid 2>&1
+$JSTACK -l $appJavaPid 2>&1
 if [ $? != 0 ]; then failed=1; fi
 
-stopApplication ShutdownSimpleApplication "${PORTFILE}"
+set -e
+
+stopApplication "${PORTFILE}"
+waitForApplication
  
 exit $failed
-