LingeredApp.java revision 2224:2a8815d86b93
1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package jdk.test.lib.apps;
25
26import java.io.BufferedReader;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.InputStreamReader;
30import java.nio.file.Files;
31import java.nio.file.NoSuchFileException;
32import java.nio.file.Path;
33import java.nio.file.Paths;
34import java.nio.file.attribute.BasicFileAttributes;
35import java.nio.file.attribute.FileTime;
36import java.util.ArrayList;
37import java.util.Date;
38import java.util.List;
39import java.util.Map;
40import java.util.UUID;
41
42/**
43 * This is a framework to launch an app that could be synchronized with caller
44 * to make further attach actions reliable across supported platforms
45
46 * Caller example:
47 *   SmartTestApp a = SmartTestApp.startApp(cmd);
48 *     // do something
49 *   a.stopApp();
50 *
51 *   or fine grained control
52 *
53 *   a = new SmartTestApp("MyLock.lck");
54 *   a.createLock();
55 *   a.runApp();
56 *   a.waitAppReady();
57 *     // do something
58 *   a.deleteLock();
59 *   a.waitAppTerminate();
60 *
61 *  Then you can work with app output and process object
62 *
63 *   output = a.getAppOutput();
64 *   process = a.getProcess();
65 *
66 */
67public class LingeredApp {
68
69    private static final long spinDelay = 1000;
70    private static final int appWaitTime = 100;
71
72    private final String lockFileName;
73    private long lockCreationTime;
74    private Process appProcess;
75    private final ArrayList<String> storedAppOutput;
76
77    /*
78     * Drain child process output, store it into string array
79     */
80    class InputGobbler extends Thread {
81
82        InputStream is;
83        List<String> astr;
84
85        InputGobbler(InputStream is, List<String> astr) {
86            this.is = is;
87            this.astr = astr;
88        }
89
90        public void run() {
91            try {
92                InputStreamReader isr = new InputStreamReader(is);
93                BufferedReader br = new BufferedReader(isr);
94                String line = null;
95                while ((line = br.readLine()) != null) {
96                    astr.add(line);
97                }
98            } catch (IOException ex) {
99                // pass
100            }
101        }
102    }
103
104    /**
105     * Create LingeredApp object on caller side. Lock file have be a valid filename
106     * at writable location
107     *
108     * @param lockFileName - the name of lock file
109     */
110    public LingeredApp(String lockFileName) {
111        this.lockFileName = lockFileName;
112        this.storedAppOutput = new ArrayList<String>();
113    }
114
115    public LingeredApp() {
116        final String lockName = UUID.randomUUID().toString() + ".lck";
117        this.lockFileName = lockName;
118        this.storedAppOutput = new ArrayList<String>();
119    }
120
121    /**
122     *
123     * @return name of lock file
124     */
125    public String getLockFileName() {
126        return this.lockFileName;
127    }
128
129    /**
130     *
131     * @return name of testapp
132     */
133    public String getAppName() {
134        return this.getClass().getName();
135    }
136
137    /**
138     *
139     *  @return pid of java process running testapp
140     */
141    public long getPid() {
142        if (appProcess == null) {
143            throw new RuntimeException("Process is not alive");
144        }
145        return appProcess.getPid();
146    }
147
148    /**
149     *
150     * @return process object
151     */
152    public Process getProcess() {
153        return appProcess;
154    }
155
156    /**
157     *
158     * @return application output as string array. Empty array if application produced no output
159     */
160    public List<String> getAppOutput() {
161        if (appProcess.isAlive()) {
162            throw new RuntimeException("Process is still alive. Can't get its output.");
163        }
164        return storedAppOutput;
165    }
166
167    /* Make sure all part of the app use the same method to get dates,
168     as different methods could produce different results
169     */
170    private static long epoch() {
171        return new Date().getTime();
172    }
173
174    private static long lastModified(String fileName) throws IOException {
175        Path path = Paths.get(fileName);
176        BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
177        return attr.lastModifiedTime().toMillis();
178    }
179
180    private static void setLastModified(String fileName, long newTime) throws IOException {
181        Path path = Paths.get(fileName);
182        FileTime fileTime = FileTime.fromMillis(newTime);
183        Files.setLastModifiedTime(path, fileTime);
184    }
185
186    /**
187     * create lock
188     *
189     * @throws IOException
190     */
191    public void createLock() throws IOException {
192        Path path = Paths.get(lockFileName);
193        // Files.deleteIfExists(path);
194        Files.createFile(path);
195        lockCreationTime = lastModified(lockFileName);
196    }
197
198    /**
199     * Delete lock
200     *
201     * @throws IOException
202     */
203    public void deleteLock() throws IOException {
204        try {
205            Path path = Paths.get(lockFileName);
206            Files.delete(path);
207        } catch (NoSuchFileException ex) {
208            // Lock already deleted. Ignore error
209        }
210    }
211
212    public void waitAppTerminate() {
213        while (true) {
214            try {
215                appProcess.waitFor();
216                break;
217            } catch (InterruptedException ex) {
218                // pass
219            }
220        }
221    }
222
223    /**
224     * The app touches the lock file when it's started
225     * wait while it happens. Caller have to delete lock on wait error.
226     *
227     * @param timeout
228     * @throws java.io.IOException
229     */
230    public void waitAppReady(long timeout) throws IOException {
231        long here = epoch();
232        while (true) {
233            long epoch = epoch();
234            if (epoch - here > (timeout * 1000)) {
235                throw new IOException("App waiting timeout");
236            }
237
238            // Live process should touch lock file every second
239            long lm = lastModified(lockFileName);
240            if (lm > lockCreationTime) {
241                break;
242            }
243
244            // Make sure process didn't already exit
245            if (!appProcess.isAlive()) {
246                throw new IOException("App exited unexpectedly with " + appProcess.exitValue());
247            }
248
249            try {
250                Thread.sleep(spinDelay);
251            } catch (InterruptedException ex) {
252                // pass
253            }
254        }
255    }
256
257    /**
258     * Run the app
259     *
260     * @param vmArguments
261     * @throws IOException
262     */
263    public void runApp(List<String> vmArguments)
264            throws IOException {
265
266        // We should always use testjava or throw an exception,
267        // so we can't use JDKToolFinder.getJDKTool("java");
268        // that falls back to compile java on error
269        String jdkPath = System.getProperty("test.jdk");
270        if (jdkPath == null) {
271            // we are not under jtreg, try env
272            Map<String, String> env = System.getenv();
273            jdkPath = env.get("TESTJAVA");
274        }
275
276        if (jdkPath == null) {
277            throw new RuntimeException("Can't determine jdk path neither test.jdk property no TESTJAVA env are set");
278        }
279
280        String osname = System.getProperty("os.name");
281        String javapath = jdkPath + ((osname.startsWith("window")) ? "/bin/java.exe" : "/bin/java");
282
283        List<String> cmd = new ArrayList<String>();
284        cmd.add(javapath);
285
286
287        if (vmArguments == null) {
288            // Propagate test.vm.options to LingeredApp, filter out possible empty options
289            String testVmOpts[] = System.getProperty("test.vm.opts","").split("\\s+");
290            for (String s : testVmOpts) {
291                if (!s.equals("")) {
292                    cmd.add(s);
293                }
294            }
295        }
296        else{
297            // Lets user manage LingeredApp options
298            cmd.addAll(vmArguments);
299        }
300
301        // Make sure we set correct classpath to run the app
302        cmd.add("-cp");
303        String classpath = System.getProperty("test.class.path");
304        cmd.add((classpath == null) ? "." : classpath);
305
306        cmd.add(this.getAppName());
307        cmd.add(lockFileName);
308
309        // Reporting
310        StringBuilder cmdLine = new StringBuilder();
311        for (String strCmd : cmd) {
312            cmdLine.append("'").append(strCmd).append("' ");
313        }
314
315        // A bit of verbosity
316        System.out.println("Command line: [" + cmdLine.toString() + "]");
317
318        ProcessBuilder pb = new ProcessBuilder(cmd);
319        // we don't expect any error output but make sure we are not stuck on pipe
320        // pb.redirectErrorStream(false);
321        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
322
323        appProcess = pb.start();
324
325        // Create pipe reader for process, and read stdin and stderr to array of strings
326        InputGobbler gb = new InputGobbler(appProcess.getInputStream(), storedAppOutput);
327        gb.start();
328    }
329
330    /**
331     * Delete lock file that signals app to terminate, then
332     * wait until app is actually terminated.
333     * @throws IOException
334     */
335    public void stopApp() throws IOException {
336        deleteLock();
337        waitAppTerminate();
338        int exitcode = appProcess.exitValue();
339        if (exitcode != 0) {
340            throw new IOException("LingeredApp terminated with non-zero exit code " + exitcode);
341        }
342    }
343
344    /**
345     *  High level interface for test writers
346     */
347    /**
348     * Factory method that creates LingeredApp object with ready to use application
349     * lock name is autogenerated
350     * @param cmd - vm options, could be null to auto add testvm.options
351     * @return LingeredApp object
352     * @throws IOException
353     */
354    public static LingeredApp startApp(List<String> cmd) throws IOException {
355        LingeredApp a = new LingeredApp();
356        a.createLock();
357        try {
358            a.runApp(cmd);
359            a.waitAppReady(appWaitTime);
360        } catch (Exception ex) {
361            a.deleteLock();
362            throw ex;
363        }
364
365        return a;
366    }
367
368    /**
369     * Factory method that starts pre-created LingeredApp
370     * lock name is autogenerated
371     * @param cmd - vm options, could be null to auto add testvm.options
372     * @param theApp - app to start
373     * @return LingeredApp object
374     * @throws IOException
375     */
376
377    public static void startApp(List<String> cmd, LingeredApp theApp) throws IOException {
378        theApp.createLock();
379        try {
380            theApp.runApp(cmd);
381            theApp.waitAppReady(appWaitTime);
382        } catch (Exception ex) {
383            theApp.deleteLock();
384            throw ex;
385        }
386    }
387
388    public static LingeredApp startApp() throws IOException {
389        return startApp(null);
390    }
391
392    public static void stopApp(LingeredApp app) throws IOException {
393        if (app != null) {
394            // LingeredApp can throw an exception during the intialization,
395            // make sure we don't have cascade NPE
396            app.stopApp();
397        }
398    }
399
400    /**
401     * LastModified time might not work correctly in some cases it might
402     * cause later failures
403     */
404
405    public static boolean isLastModifiedWorking() {
406        boolean sane = true;
407        try {
408            long lm = lastModified(".");
409            if (lm == 0) {
410                System.err.println("SANITY Warning! The lastModifiedTime() doesn't work on this system, it returns 0");
411                sane = false;
412            }
413
414            long now = epoch();
415            if (lm > now) {
416                System.err.println("SANITY Warning! The Clock is wrong on this system lastModifiedTime() > getTime()");
417                sane = false;
418            }
419
420            setLastModified(".", epoch());
421            long lm1 = lastModified(".");
422            if (lm1 <= lm) {
423                System.err.println("SANITY Warning! The setLastModified doesn't work on this system");
424                sane = false;
425            }
426        }
427        catch(IOException e) {
428            System.err.println("SANITY Warning! IOException during sanity check " + e);
429            sane = false;
430        }
431
432        return sane;
433    }
434
435    /**
436     * This part is the application it self
437     */
438    public static void main(String args[]) {
439
440        if (args.length != 1) {
441            System.err.println("Lock file name is not specified");
442            System.exit(7);
443        }
444
445        String theLockFileName = args[0];
446
447        try {
448            Path path = Paths.get(theLockFileName);
449
450            while (Files.exists(path)) {
451                // Touch the lock to indicate our readiness
452                setLastModified(theLockFileName, epoch());
453                Thread.sleep(spinDelay);
454            }
455        } catch (NoSuchFileException ex) {
456            // Lock deleted while we are setting last modified time.
457            // Ignore error and lets the app exits
458        } catch (Exception ex) {
459            System.err.println("LingeredApp ERROR: " + ex);
460            // Leave exit_code = 1 to Java launcher
461            System.exit(3);
462        }
463
464        System.exit(0);
465    }
466}
467