8165500: TestJpsJar shouldn't jar all test.classpath directories
authordsamersoff
Tue, 25 Oct 2016 14:49:35 +0300
changeset 42101 e75889484926
parent 42100 5590be0da3c8
child 42102 3e0a2861efe1
8165500: TestJpsJar shouldn't jar all test.classpath directories Summary: Refactor test to better handle errors Reviewed-by: sspitsyn
jdk/test/ProblemList.txt
jdk/test/sun/tools/jps/JpsBase.java
jdk/test/sun/tools/jps/JpsHelper.java
jdk/test/sun/tools/jps/LingeredApp.java
jdk/test/sun/tools/jps/LingeredAppForJps.java
jdk/test/sun/tools/jps/TestJps.java
jdk/test/sun/tools/jps/TestJpsClass.java
jdk/test/sun/tools/jps/TestJpsJar.java
jdk/test/sun/tools/jps/TestJpsJarRelative.java
jdk/test/sun/tools/jps/TestJpsSanity.java
--- a/jdk/test/ProblemList.txt	Mon Oct 24 14:52:07 2016 +0300
+++ b/jdk/test/ProblemList.txt	Tue Oct 25 14:49:35 2016 +0300
@@ -289,10 +289,6 @@
 
 sun/tools/jhsdb/HeapDumpTest.java                               8168397 macosx-all
 
-sun/tools/jps/TestJpsJar.java                                   8165500 generic-all
-
-sun/tools/jps/TestJpsJarRelative.java                           6456333 generic-all
-
 sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java    8057732 generic-all
 
 demo/jvmti/compiledMethodLoad/CompiledMethodLoadTest.java       8151899 generic-all
--- a/jdk/test/sun/tools/jps/JpsBase.java	Mon Oct 24 14:52:07 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/*
- * Copyright (c) 2014, 2016, 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.
- */
-
-import java.io.File;
-import java.net.URL;
-import java.util.List;
-
-import jdk.testlibrary.OutputAnalyzer;
-import jdk.testlibrary.ProcessTools;
-
-/**
- * The base class for testing the jps utility.
- * The test sequence is to start jps with different combinations of arguments
- * and verify the output contains proper values.
- */
-public final class JpsBase {
-
-    /**
-     * The jps output should contain processes' names
-     * (except when jps is started in quite mode).
-     * The expected name of the test process is prepared here.
-     */
-
-    private static String getShortProcessName() {
-        URL url = JpsBase.class.getResource("JpsBase.class");
-        boolean isJar = url.getProtocol().equals("jar");
-        return (isJar) ? JpsBase.class.getSimpleName() + ".jar" : JpsBase.class.getSimpleName();
-    }
-
-    private static String getFullProcessName() {
-        URL url = JpsBase.class.getResource("JpsBase.class");
-        boolean isJar = url.getProtocol().equals("jar");
-        if (isJar) {
-            String urlPath = url.getPath();
-            File jar = new File(urlPath.substring(urlPath.indexOf("file:") + 5, urlPath.indexOf("jar!") + 3));
-            return jar.getAbsolutePath();
-        }
-
-        return JpsBase.class.getName();
-    }
-
-    private static boolean userDirSanityCheck(String fullProcessName) {
-        String userDir = System.getProperty("user.dir");
-        if (!fullProcessName.startsWith(userDir)) {
-            System.err.printf("Test skipped. user.dir '%s' is not a prefix of '%s'\n", userDir, fullProcessName);
-            return false;
-        }
-        return true;
-    }
-
-    public static void main(String[] args) throws Exception {
-        System.out.printf("INFO: user.dir:  '%s''\n", System.getProperty("user.dir"));
-        long pid = ProcessTools.getProcessId();
-
-        List<List<JpsHelper.JpsArg>> combinations = JpsHelper.JpsArg.generateCombinations();
-        for (List<JpsHelper.JpsArg> combination : combinations) {
-            OutputAnalyzer output = JpsHelper.jps(JpsHelper.JpsArg.asCmdArray(combination));
-            output.shouldHaveExitValue(0);
-
-            boolean isQuiet = false;
-            boolean isFull = false;
-            String pattern;
-            for (JpsHelper.JpsArg jpsArg : combination) {
-                switch (jpsArg) {
-                case q:
-                    // If '-q' is specified output should contain only a list of local VM identifiers:
-                    // 30673
-                    isQuiet = true;
-                    JpsHelper.verifyJpsOutput(output, "^\\d+$");
-                    output.shouldContain(Long.toString(pid));
-                    break;
-                case l:
-                    // If '-l' is specified output should contain the full package name for the application's main class
-                    // or the full path name to the application's JAR file:
-                    // 30673 /tmp/jtreg/jtreg-workdir/scratch/JpsBase.jar ...
-                    isFull = true;
-                    String fullProcessName = getFullProcessName();
-                    // Skip the test if user.dir is not a prefix of the current path
-                    // It's possible if the test is run from symlinked dir or windows alias drive
-                    if (userDirSanityCheck(fullProcessName)) {
-                        pattern = "^" + pid + "\\s+" + replaceSpecialChars(fullProcessName) + ".*";
-                        output.shouldMatch(pattern);
-                    }
-                    break;
-                case m:
-                    // If '-m' is specified output should contain the arguments passed to the main method:
-                    // 30673 JpsBase monkey ...
-                    for (String arg : args) {
-                        pattern = "^" + pid + ".*" + replaceSpecialChars(arg) + ".*";
-                        output.shouldMatch(pattern);
-                    }
-                    break;
-                case v:
-                    // If '-v' is specified output should contain VM arguments:
-                    // 30673 JpsBase -Xmx512m -XX:+UseParallelGC -XX:Flags=/tmp/jtreg/jtreg-workdir/scratch/vmflags ...
-                    for (String vmArg : JpsHelper.getVmArgs()) {
-                        pattern = "^" + pid + ".*" + replaceSpecialChars(vmArg) + ".*";
-                        output.shouldMatch(pattern);
-                    }
-                    break;
-                case V:
-                    // If '-V' is specified output should contain VM flags:
-                    // 30673 JpsBase +DisableExplicitGC ...
-                    pattern = "^" + pid + ".*" + replaceSpecialChars(JpsHelper.VM_FLAG) + ".*";
-                    output.shouldMatch(pattern);
-                    break;
-                }
-
-                if (isQuiet) {
-                    break;
-                }
-            }
-
-            if (!isQuiet) {
-                // Verify output line by line.
-                // Output should only contain lines with pids after the first line with pid.
-                JpsHelper.verifyJpsOutput(output, "^\\d+\\s+.*");
-                if (!isFull) {
-                    String shortProcessName = getShortProcessName();
-                    pattern = "^" + pid + "\\s+" + replaceSpecialChars(shortProcessName);
-                    if (combination.isEmpty()) {
-                        // If no arguments are specified output should only contain
-                        // pid and process name
-                        pattern += "$";
-                    } else {
-                        pattern += ".*";
-                    }
-                    output.shouldMatch(pattern);
-                }
-            }
-        }
-    }
-
-    private static String replaceSpecialChars(String str) {
-        String tmp = str.replace("\\", "\\\\");
-        tmp = tmp.replace("+", "\\+");
-        tmp = tmp.replace(".", "\\.");
-        tmp = tmp.replace("\n", "\\\\n");
-        tmp = tmp.replace("\r", "\\\\r");
-        return tmp;
-    }
-
-}
--- a/jdk/test/sun/tools/jps/JpsHelper.java	Mon Oct 24 14:52:07 2016 +0300
+++ b/jdk/test/sun/tools/jps/JpsHelper.java	Tue Oct 25 14:49:35 2016 +0300
@@ -204,44 +204,85 @@
                 "The ouput should contain all content of " + path.toAbsolutePath());
     }
 
-    private static File getManifest(String className) throws IOException {
-        if (manifestFile == null) {
-            manifestFile = new File(className + ".mf");
-            try (BufferedWriter output = new BufferedWriter(new FileWriter(manifestFile))) {
-                output.write("Main-Class: " + className + Utils.NEW_LINE);
+    public static void runJpsVariants(Long pid, String processName, String fullProcessName, String argument) throws Exception {
+        System.out.printf("INFO: user.dir:  '%s''\n", System.getProperty("user.dir"));
+        List<List<JpsHelper.JpsArg>> combinations = JpsHelper.JpsArg.generateCombinations();
+        for (List<JpsHelper.JpsArg> combination : combinations) {
+            OutputAnalyzer output = JpsHelper.jps(JpsHelper.JpsArg.asCmdArray(combination));
+            output.shouldHaveExitValue(0);
+
+            boolean isQuiet = false;
+            boolean isFull = false;
+            String pattern;
+            for (JpsHelper.JpsArg jpsArg : combination) {
+                switch (jpsArg) {
+                case q:
+                    // If '-q' is specified output should contain only a list of local VM identifiers:
+                    // 30673
+                    isQuiet = true;
+                    JpsHelper.verifyJpsOutput(output, "^\\d+$");
+                    output.shouldContain(Long.toString(pid));
+                    break;
+                case l:
+                    // If '-l' is specified output should contain the full package name for the application's main class
+                    // or the full path name to the application's JAR file:
+                    // 30673 /tmp/jtreg/jtreg-workdir/scratch/LingeredAppForJps.jar ...
+                    isFull = true;
+                    pattern = "^" + pid + "\\s+" + replaceSpecialChars(fullProcessName) + ".*";
+                    output.shouldMatch(pattern);
+                    break;
+                case m:
+                    // If '-m' is specified output should contain the arguments passed to the main method:
+                    // 30673 LingeredAppForJps lockfilename ...
+                    pattern = "^" + pid + ".*" + replaceSpecialChars(argument) + ".*";
+                    output.shouldMatch(pattern);
+                    break;
+                case v:
+                    // If '-v' is specified output should contain VM arguments:
+                    // 30673 LingeredAppForJps -Xmx512m -XX:+UseParallelGC -XX:Flags=/tmp/jtreg/jtreg-workdir/scratch/vmflags ...
+                    for (String vmArg : JpsHelper.getVmArgs()) {
+                        pattern = "^" + pid + ".*" + replaceSpecialChars(vmArg) + ".*";
+                        output.shouldMatch(pattern);
+                    }
+                    break;
+                case V:
+                    // If '-V' is specified output should contain VM flags:
+                    // 30673 LingeredAppForJps +DisableExplicitGC ...
+                    pattern = "^" + pid + ".*" + replaceSpecialChars(JpsHelper.VM_FLAG) + ".*";
+                    output.shouldMatch(pattern);
+                    break;
+                }
+
+                if (isQuiet) {
+                    break;
+                }
+            }
+
+            if (!isQuiet) {
+                // Verify output line by line.
+                // Output should only contain lines with pids after the first line with pid.
+                JpsHelper.verifyJpsOutput(output, "^\\d+\\s+.*");
+                if (!isFull) {
+                    pattern = "^" + pid + "\\s+" + replaceSpecialChars(processName);
+                    if (combination.isEmpty()) {
+                        // If no arguments are specified output should only contain
+                        // pid and process name
+                        pattern += "$";
+                    } else {
+                        pattern += ".*";
+                    }
+                    output.shouldMatch(pattern);
+                }
             }
         }
-        return manifestFile;
     }
 
-    /**
-     * Build a jar of test classes in runtime
-     */
-    public static File buildJar(String className) throws Exception {
-        File jar = new File(className + ".jar");
-
-        List<String> jarArgs = new ArrayList<>();
-        jarArgs.add("-cfm");
-        jarArgs.add(jar.getAbsolutePath());
-        File manifestFile = getManifest(className);
-        jarArgs.add(manifestFile.getAbsolutePath());
-        String testClassPath = System.getProperty("test.class.path", "?");
-        for (String path : testClassPath.split(File.pathSeparator)) {
-            jarArgs.add("-C");
-            jarArgs.add(path);
-            jarArgs.add(".");
-        }
-
-        System.out.println("Running jar " + jarArgs.toString());
-        sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
-        if (!jarTool.run(jarArgs.toArray(new String[jarArgs.size()]))) {
-            throw new Exception("jar failed: args=" + jarArgs.toString());
-        }
-
-        manifestFile.delete();
-        jar.deleteOnExit();
-
-        return jar;
+    private static String replaceSpecialChars(String str) {
+        String tmp = str.replace("\\", "\\\\");
+        tmp = tmp.replace("+", "\\+");
+        tmp = tmp.replace(".", "\\.");
+        tmp = tmp.replace("\n", "\\\\n");
+        tmp = tmp.replace("\r", "\\\\r");
+        return tmp;
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/tools/jps/LingeredApp.java	Tue Oct 25 14:49:35 2016 +0300
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * This is a framework to launch an app that could be synchronized with caller
+ * to make further attach actions reliable across supported platforms
+
+ * Caller example:
+ *   SmartTestApp a = SmartTestApp.startApp(cmd);
+ *     // do something
+ *   a.stopApp();
+ *
+ *   or fine grained control
+ *
+ *   a = new SmartTestApp("MyLock.lck");
+ *   a.createLock();
+ *   a.runApp();
+ *   a.waitAppReady();
+ *     // do something
+ *   a.deleteLock();
+ *   a.waitAppTerminate();
+ *
+ *  Then you can work with app output and process object
+ *
+ *   output = a.getAppOutput();
+ *   process = a.getProcess();
+ *
+ */
+public class LingeredApp {
+
+    private static final long spinDelay = 1000;
+
+    private long lockCreationTime;
+    private final ArrayList<String> storedAppOutput;
+
+    protected Process appProcess;
+    protected static final int appWaitTime = 100;
+    protected final String lockFileName;
+
+    /*
+     * Drain child process output, store it into string array
+     */
+    class InputGobbler extends Thread {
+
+        InputStream is;
+        List<String> astr;
+
+        InputGobbler(InputStream is, List<String> astr) {
+            this.is = is;
+            this.astr = astr;
+        }
+
+        public void run() {
+            try {
+                InputStreamReader isr = new InputStreamReader(is);
+                BufferedReader br = new BufferedReader(isr);
+                String line = null;
+                while ((line = br.readLine()) != null) {
+                    astr.add(line);
+                }
+            } catch (IOException ex) {
+                // pass
+            }
+        }
+    }
+
+    /**
+     * Create LingeredApp object on caller side. Lock file have be a valid filename
+     * at writable location
+     *
+     * @param lockFileName - the name of lock file
+     */
+    public LingeredApp(String lockFileName) {
+        this.lockFileName = lockFileName;
+        this.storedAppOutput = new ArrayList<String>();
+    }
+
+    public LingeredApp() {
+        final String lockName = UUID.randomUUID().toString() + ".lck";
+        this.lockFileName = lockName;
+        this.storedAppOutput = new ArrayList<String>();
+    }
+
+    /**
+     *
+     * @return name of lock file
+     */
+    public String getLockFileName() {
+        return this.lockFileName;
+    }
+
+    /**
+     *
+     * @return name of testapp
+     */
+    public String getAppName() {
+        return this.getClass().getName();
+    }
+
+    /**
+     *
+     *  @return pid of java process running testapp
+     */
+    public long getPid() {
+        if (appProcess == null) {
+            throw new RuntimeException("Process is not alive");
+        }
+        return appProcess.getPid();
+    }
+
+    /**
+     *
+     * @return process object
+     */
+    public Process getProcess() {
+        return appProcess;
+    }
+
+    /**
+     *
+     * @return application output as string array. Empty array if application produced no output
+     */
+    public List<String> getAppOutput() {
+        if (appProcess.isAlive()) {
+            throw new RuntimeException("Process is still alive. Can't get its output.");
+        }
+        return storedAppOutput;
+    }
+
+    /* Make sure all part of the app use the same method to get dates,
+     as different methods could produce different results
+     */
+    private static long epoch() {
+        return new Date().getTime();
+    }
+
+    private static long lastModified(String fileName) throws IOException {
+        Path path = Paths.get(fileName);
+        BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
+        return attr.lastModifiedTime().toMillis();
+    }
+
+    private static void setLastModified(String fileName, long newTime) throws IOException {
+        Path path = Paths.get(fileName);
+        FileTime fileTime = FileTime.fromMillis(newTime);
+        Files.setLastModifiedTime(path, fileTime);
+    }
+
+    /**
+     * create lock
+     *
+     * @throws IOException
+     */
+    public void createLock() throws IOException {
+        Path path = Paths.get(lockFileName);
+        // Files.deleteIfExists(path);
+        Files.createFile(path);
+        lockCreationTime = lastModified(lockFileName);
+    }
+
+    /**
+     * Delete lock
+     *
+     * @throws IOException
+     */
+    public void deleteLock() throws IOException {
+        try {
+            Path path = Paths.get(lockFileName);
+            Files.delete(path);
+        } catch (NoSuchFileException ex) {
+            // Lock already deleted. Ignore error
+        }
+    }
+
+    public void waitAppTerminate() {
+        while (true) {
+            try {
+                appProcess.waitFor();
+                break;
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+    }
+
+    /**
+     * The app touches the lock file when it's started
+     * wait while it happens. Caller have to delete lock on wait error.
+     *
+     * @param timeout
+     * @throws java.io.IOException
+     */
+    public void waitAppReady(long timeout) throws IOException {
+        long here = epoch();
+        while (true) {
+            long epoch = epoch();
+            if (epoch - here > (timeout * 1000)) {
+                throw new IOException("App waiting timeout");
+            }
+
+            // Live process should touch lock file every second
+            long lm = lastModified(lockFileName);
+            if (lm > lockCreationTime) {
+                break;
+            }
+
+            // Make sure process didn't already exit
+            if (!appProcess.isAlive()) {
+                throw new IOException("App exited unexpectedly with " + appProcess.exitValue());
+            }
+
+            try {
+                Thread.sleep(spinDelay);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+    }
+
+    /**
+     * Analyze an environment and prepare a command line to
+     * run the app, app name should be added explicitly
+     */
+    public List<String> runAppPrepare(List<String> vmArguments) {
+        // We should always use testjava or throw an exception,
+        // so we can't use JDKToolFinder.getJDKTool("java");
+        // that falls back to compile java on error
+        String jdkPath = System.getProperty("test.jdk");
+        if (jdkPath == null) {
+            // we are not under jtreg, try env
+            Map<String, String> env = System.getenv();
+            jdkPath = env.get("TESTJAVA");
+        }
+
+        if (jdkPath == null) {
+            throw new RuntimeException("Can't determine jdk path neither test.jdk property no TESTJAVA env are set");
+        }
+
+        String osname = System.getProperty("os.name");
+        String javapath = jdkPath + ((osname.startsWith("window")) ? "/bin/java.exe" : "/bin/java");
+
+        List<String> cmd = new ArrayList<String>();
+        cmd.add(javapath);
+
+
+        if (vmArguments == null) {
+            // Propagate test.vm.options to LingeredApp, filter out possible empty options
+            String testVmOpts[] = System.getProperty("test.vm.opts","").split("\\s+");
+            for (String s : testVmOpts) {
+                if (!s.equals("")) {
+                    cmd.add(s);
+                }
+            }
+        }
+        else{
+            // Lets user manage LingeredApp options
+            cmd.addAll(vmArguments);
+        }
+
+        // Make sure we set correct classpath to run the app
+        cmd.add("-cp");
+        String classpath = System.getProperty("test.class.path");
+        cmd.add((classpath == null) ? "." : classpath);
+
+        return cmd;
+    }
+
+    /**
+     * Assemble command line to a printable string
+     */
+    public void printCommandLine(List<String> cmd) {
+        // A bit of verbosity
+        StringBuilder cmdLine = new StringBuilder();
+        for (String strCmd : cmd) {
+            cmdLine.append("'").append(strCmd).append("' ");
+        }
+
+        System.out.println("Command line: [" + cmdLine.toString() + "]");
+    }
+
+    public void startGobblerPipe() {
+      // Create pipe reader for process, and read stdin and stderr to array of strings
+      InputGobbler gb = new InputGobbler(appProcess.getInputStream(), storedAppOutput);
+      gb.start();
+    }
+
+    /**
+     * Run the app.
+     *
+     * @param vmArguments
+     * @throws IOException
+     */
+    public void runApp(List<String> vmArguments)
+            throws IOException {
+
+        List<String> cmd = runAppPrepare(vmArguments);
+
+        cmd.add(this.getAppName());
+        cmd.add(lockFileName);
+
+        printCommandLine(cmd);
+
+        ProcessBuilder pb = new ProcessBuilder(cmd);
+        // we don't expect any error output but make sure we are not stuck on pipe
+        // pb.redirectErrorStream(false);
+        // ProcessBuilder.start can throw IOException
+        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+        appProcess = pb.start();
+
+        startGobblerPipe();
+    }
+
+    /**
+     * Delete lock file that signals app to terminate, then
+     * wait until app is actually terminated.
+     * @throws IOException
+     */
+    public void stopApp() throws IOException {
+        deleteLock();
+        // The startApp() of the derived app can throw
+        // an exception before the LA actually starts
+        if (appProcess != null) {
+            waitAppTerminate();
+            int exitcode = appProcess.exitValue();
+            if (exitcode != 0) {
+                throw new IOException("LingeredApp terminated with non-zero exit code " + exitcode);
+            }
+        }
+    }
+
+    /**
+     *  High level interface for test writers
+     */
+    /**
+     * Factory method that creates LingeredApp object with ready to use application
+     * lock name is autogenerated
+     * @param cmd - vm options, could be null to auto add testvm.options
+     * @return LingeredApp object
+     * @throws IOException
+     */
+    public static LingeredApp startApp(List<String> cmd) throws IOException {
+        LingeredApp a = new LingeredApp();
+        a.createLock();
+        try {
+            a.runApp(cmd);
+            a.waitAppReady(appWaitTime);
+        } catch (Exception ex) {
+            a.deleteLock();
+            throw ex;
+        }
+
+        return a;
+    }
+
+    /**
+     * Factory method that starts pre-created LingeredApp
+     * lock name is autogenerated
+     * @param cmd - vm options, could be null to auto add testvm.options
+     * @param theApp - app to start
+     * @return LingeredApp object
+     * @throws IOException
+     */
+
+    public static void startApp(List<String> cmd, LingeredApp theApp) throws IOException {
+        theApp.createLock();
+        try {
+            theApp.runApp(cmd);
+            theApp.waitAppReady(appWaitTime);
+        } catch (Exception ex) {
+            theApp.deleteLock();
+            throw ex;
+        }
+    }
+
+    public static LingeredApp startApp() throws IOException {
+        return startApp(null);
+    }
+
+    public static void stopApp(LingeredApp app) throws IOException {
+        if (app != null) {
+            // LingeredApp can throw an exception during the intialization,
+            // make sure we don't have cascade NPE
+            app.stopApp();
+        }
+    }
+
+    /**
+     * LastModified time might not work correctly in some cases it might
+     * cause later failures
+     */
+
+    public static boolean isLastModifiedWorking() {
+        boolean sane = true;
+        try {
+            long lm = lastModified(".");
+            if (lm == 0) {
+                System.err.println("SANITY Warning! The lastModifiedTime() doesn't work on this system, it returns 0");
+                sane = false;
+            }
+
+            long now = epoch();
+            if (lm > now) {
+                System.err.println("SANITY Warning! The Clock is wrong on this system lastModifiedTime() > getTime()");
+                sane = false;
+            }
+
+            setLastModified(".", epoch());
+            long lm1 = lastModified(".");
+            if (lm1 <= lm) {
+                System.err.println("SANITY Warning! The setLastModified doesn't work on this system");
+                sane = false;
+            }
+        }
+        catch(IOException e) {
+            System.err.println("SANITY Warning! IOException during sanity check " + e);
+            sane = false;
+        }
+
+        return sane;
+    }
+
+    /**
+     * This part is the application it self
+     */
+    public static void main(String args[]) {
+
+        if (args.length != 1) {
+            System.err.println("Lock file name is not specified");
+            System.exit(7);
+        }
+
+        String theLockFileName = args[0];
+
+        try {
+            Path path = Paths.get(theLockFileName);
+
+            while (Files.exists(path)) {
+                // Touch the lock to indicate our readiness
+                setLastModified(theLockFileName, epoch());
+                Thread.sleep(spinDelay);
+            }
+        } catch (NoSuchFileException ex) {
+            // Lock deleted while we are setting last modified time.
+            // Ignore error and lets the app exits
+        } catch (Exception ex) {
+            System.err.println("LingeredApp ERROR: " + ex);
+            // Leave exit_code = 1 to Java launcher
+            System.exit(3);
+        }
+
+        System.exit(0);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/tools/jps/LingeredAppForJps.java	Tue Oct 25 14:49:35 2016 +0300
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.net.URL;
+
+public class LingeredAppForJps extends LingeredApp {
+
+    // Copy runApp logic here to be able to run an app from JarFile
+    public void runAppWithName(List<String> vmArguments, String runName)
+            throws IOException {
+
+        List<String> cmd = runAppPrepare(vmArguments);
+        if (runName.endsWith(".jar")) {
+            cmd.add("-Xdiag");
+            cmd.add("-jar");
+        }
+        cmd.add(runName);
+        cmd.add(lockFileName);
+
+        printCommandLine(cmd);
+
+        ProcessBuilder pb = new ProcessBuilder(cmd);
+        // we don't expect any error output but make sure we are not stuck on pipe
+        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+        appProcess = pb.start();
+        startGobblerPipe();
+    }
+
+    public static LingeredApp startAppJar(List<String> cmd, LingeredAppForJps app, File jar) throws IOException {
+        app.createLock();
+        try {
+            app.runAppWithName(cmd, jar.getAbsolutePath());
+            app.waitAppReady(appWaitTime);
+        } catch (Exception ex) {
+            app.deleteLock();
+            throw ex;
+        }
+
+        return app;
+    }
+
+    /**
+     * The jps output should contain processes' names
+     * (except when jps is started in quite mode).
+     * The expected name of the test process is prepared here.
+     */
+    public static String getProcessName() {
+        return LingeredAppForJps.class.getSimpleName();
+    }
+
+    public static String getProcessName(File jar) {
+        return jar.getName();
+    }
+
+    // full package name for the application's main class or the full path
+    // name to the application's JAR file:
+
+    public static String getFullProcessName() {
+        return LingeredAppForJps.class.getCanonicalName();
+    }
+
+    public static String getFullProcessName(File jar) {
+        return jar.getAbsolutePath();
+    }
+
+    public static File buildJar() throws IOException {
+        String className = LingeredAppForJps.class.getName();
+        File jar = new File(className + ".jar");
+        String testClassPath = System.getProperty("test.class.path", "?");
+
+        File manifestFile = new File(className + ".mf");
+        String nl = System.getProperty("line.separator");
+        try (BufferedWriter output = new BufferedWriter(new FileWriter(manifestFile))) {
+            output.write("Main-Class: " + className + nl);
+        }
+
+        List<String> jarArgs = new ArrayList<>();
+        jarArgs.add("-cfm");
+        jarArgs.add(jar.getAbsolutePath());
+        jarArgs.add(manifestFile.getAbsolutePath());
+
+        for (String path : testClassPath.split(File.pathSeparator)) {
+            String classFullName = path + File.separator + className + ".class";
+            File f = new File(classFullName);
+            if (f.exists()) {
+              jarArgs.add("-C");
+              jarArgs.add(path);
+              jarArgs.add(".");
+              System.out.println("INFO: scheduled to jar " + path);
+              break;
+            }
+        }
+
+        System.out.println("Running jar " + jarArgs.toString());
+        sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
+        if (!jarTool.run(jarArgs.toArray(new String[jarArgs.size()]))) {
+            throw new IOException("jar failed: args=" + jarArgs.toString());
+        }
+
+        manifestFile.delete();
+        jar.deleteOnExit();
+
+        // Print content of jar file
+        System.out.println("Content of jar file" + jar.getAbsolutePath());
+
+        jarArgs = new ArrayList<>();
+        jarArgs.add("-tvf");
+        jarArgs.add(jar.getAbsolutePath());
+
+        jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
+        if (!jarTool.run(jarArgs.toArray(new String[jarArgs.size()]))) {
+            throw new IOException("jar failed: args=" + jarArgs.toString());
+        }
+
+        return jar;
+    }
+
+    public static void main(String args[]) {
+        LingeredApp.main(args);
+    }
+ }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/tools/jps/TestJps.java	Tue Oct 25 14:49:35 2016 +0300
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2014, 2016, 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
+ * @library /lib/testlibrary /test/lib
+ * @modules jdk.jartool/sun.tools.jar
+ * @build LingeredAppForJps
+ * @build LingeredApp
+ * @run main/othervm TestJps
+ */
+
+ /*
+  * Notes:
+  *   @modules tag is ignored in driver mode, so need main/othervm
+  *
+  *   Launching the process with relative path to an app jar file is not tested
+  *
+  *   This test resides in default package, so correct appearance
+  *   of the full package name actually is not tested.
+  */
+
+import java.util.List;
+import java.io.File;
+
+public class TestJps {
+
+    public static void testJpsClass() throws Throwable {
+        LingeredApp app = new LingeredAppForJps();
+        try {
+            LingeredApp.startApp(JpsHelper.getVmArgs(), app);
+            JpsHelper.runJpsVariants(app.getPid(),
+                LingeredAppForJps.getProcessName(), LingeredAppForJps.getFullProcessName(), app.getLockFileName());
+
+        } finally {
+            LingeredApp.stopApp(app);
+        }
+    }
+
+    public static void testJpsJar() throws Throwable {
+        // Get any jar exception as early as possible
+        File jar = LingeredAppForJps.buildJar();
+
+        // Jar created go to the main test
+        LingeredAppForJps app = new LingeredAppForJps();
+        try {
+            LingeredAppForJps.startAppJar(JpsHelper.getVmArgs(), app, jar);
+            JpsHelper.runJpsVariants(app.getPid(),
+                LingeredAppForJps.getProcessName(jar), LingeredAppForJps.getFullProcessName(jar), app.getLockFileName());
+        } finally {
+            LingeredAppForJps.stopApp(app);
+        }
+
+    }
+
+    public static void main(String[] args) throws Throwable {
+        testJpsClass();
+        testJpsJar();
+    }
+}
--- a/jdk/test/sun/tools/jps/TestJpsClass.java	Mon Oct 24 14:52:07 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.
- */
-
-import java.util.ArrayList;
-import java.util.List;
-
-import jdk.testlibrary.OutputAnalyzer;
-import jdk.testlibrary.ProcessTools;
-
-/*
- * @test
- * @summary The test application will be started with java class:
- *          java JpsBase
- *          For all possible combinations of jps arguments a jps process
- *          will be started from within the test application.
- *          The output should contain proper values.
- * @library /lib/testlibrary
- * @modules jdk.jartool/sun.tools.jar
- *          java.management
- * @build jdk.testlibrary.* JpsHelper JpsBase
- * @run driver TestJpsClass
- */
-public class TestJpsClass {
-
-    public static void main(String[] args) throws Throwable {
-        String testJdk = System.getProperty("test.jdk", "?");
-        String testSrc = System.getProperty("test.src", "?");
-        String testClassPath = System.getProperty("test.class.path", "?");
-
-        List<String> cmd = new ArrayList<>();
-        cmd.addAll(JpsHelper.getVmArgs());
-        cmd.add("-Dtest.jdk=" + testJdk);
-        cmd.add("-Dtest.src=" + testSrc);
-        cmd.add("-cp");
-        cmd.add(testClassPath);
-        cmd.add("JpsBase");
-        cmd.add("monkey");
-
-        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(cmd.toArray(new String[cmd.size()]));
-        OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
-        System.out.println(output.getOutput());
-        output.shouldHaveExitValue(0);
-    }
-
-}
--- a/jdk/test/sun/tools/jps/TestJpsJar.java	Mon Oct 24 14:52:07 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/*
- * Copyright (c) 2014, 2016, 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.
- */
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import jdk.testlibrary.OutputAnalyzer;
-import jdk.testlibrary.ProcessTools;
-
-/*
- * @test
- * @summary The test application will be started with absolute jar:
- *          java -jar /tmp/jtreg/jtreg-workdir/scratch/JpsBase.jar
- *          For all possible combinations of jps arguments a jps process
- *          will be started from within the test application.
- *          The output should contain proper values.
- * @library /lib/testlibrary
- * @modules jdk.jartool/sun.tools.jar
- *          java.management
- * @build JpsHelper JpsBase
- * @run main/othervm TestJpsJar
- */
-public class TestJpsJar {
-
-    public static void main(String[] args) throws Throwable {
-        String testJdk = System.getProperty("test.jdk", "?");
-        String testSrc = System.getProperty("test.src", "?");
-        File jar = JpsHelper.buildJar("JpsBase");
-
-        List<String> cmd = new ArrayList<>();
-        cmd.addAll(JpsHelper.getVmArgs());
-        cmd.add("-Dtest.jdk=" + testJdk);
-        cmd.add("-Dtest.src=" + testSrc);
-        cmd.add("-Duser.dir=" + System.getProperty("user.dir"));
-        cmd.add("-jar");
-        cmd.add(jar.getAbsolutePath());
-        cmd.add("monkey");
-
-        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(cmd.toArray(new String[cmd.size()]));
-        OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
-        System.out.println(output.getOutput());
-        output.shouldHaveExitValue(0);
-    }
-
-}
--- a/jdk/test/sun/tools/jps/TestJpsJarRelative.java	Mon Oct 24 14:52:07 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.
- */
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import jdk.testlibrary.OutputAnalyzer;
-import jdk.testlibrary.ProcessTools;
-
-/*
- * @test
- * @summary The test application will be started with relative jar:
- *          java -jar ./JpsBase.jar
- *          For all possible combinations of jps arguments a jps process
- *          will be started from within the test application.
- *          The output should contain proper values.
- * @library /lib/testlibrary
- * @modules jdk.jartool/sun.tools.jar
- *          java.management
- * @build jdk.testlibrary.* JpsHelper JpsBase
- * @run main/othervm TestJpsJarRelative
- */
-public class TestJpsJarRelative {
-
-    public static void main(String[] args) throws Throwable {
-        String testJdk = System.getProperty("test.jdk", "?");
-        String testSrc = System.getProperty("test.src", "?");
-        File jar = JpsHelper.buildJar("JpsBase");
-
-        List<String> cmd = new ArrayList<>();
-        cmd.addAll(JpsHelper.getVmArgs());
-        cmd.add("-Dtest.jdk=" + testJdk);
-        cmd.add("-Dtest.src=" + testSrc);
-        cmd.add("-jar");
-        cmd.add("." + File.separator + jar.getName());
-        cmd.add("monkey");
-
-        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(cmd.toArray(new String[cmd.size()]));
-        OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
-        System.out.println(output.getOutput());
-        output.shouldHaveExitValue(0);
-    }
-
-}
--- a/jdk/test/sun/tools/jps/TestJpsSanity.java	Mon Oct 24 14:52:07 2016 +0300
+++ b/jdk/test/sun/tools/jps/TestJpsSanity.java	Tue Oct 25 14:49:35 2016 +0300
@@ -96,7 +96,7 @@
     }
 
     private static void testJpsUnknownHost() throws Exception {
-        String invalidHostName = "Oja781nh2ev7vcvbajdg-Sda1-C";
+        String invalidHostName = "Oja781nh2ev7vcvbajdg-Sda1-C.invalid";
         OutputAnalyzer output = JpsHelper.jps(invalidHostName);
         Asserts.assertNotEquals(output.getExitValue(), 0, "Exit code shouldn't be 0");
         Asserts.assertFalse(output.getStderr().isEmpty(), "Error output should not be empty");