2 * This file is part of the LibreOffice project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This file incorporates work covered by the following license notice:
10 * Licensed to the Apache Software Foundation (ASF) under one or more
11 * contributor license agreements. See the NOTICE file distributed
12 * with this work for additional information regarding copyright
13 * ownership. The ASF licenses this file to you under the Apache
14 * License, Version 2.0 (the "License"); you may not use this file
15 * except in compliance with the License. You may obtain a copy of
16 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 import java
.io
.InputStream
;
22 import java
.io
.PrintWriter
;
23 import java
.io
.PrintStream
;
24 import java
.io
.LineNumberReader
;
25 import java
.io
.InputStreamReader
;
26 import java
.io
.OutputStreamWriter
;
27 import lib
.TestParameters
;
28 import util
.PropertyName
;
32 * Class collect information from input stream in
33 * background (sparate thread) and outputs it to
34 * some log stream. I helps to avoid buffer overflow
35 * when output stream has small buffer size (e.g.
36 * in case when handling stdout from external
37 * <code>Process</code>)
39 * This class is currently used by ProcesHandler
42 class Pump
extends Thread
45 private final LineNumberReader reader
;
46 private final String pref
;
47 private final StringBuffer buf
= new StringBuffer(256);
48 private final PrintWriter log
;
49 private final boolean bOutput
;
52 * Creates Pump for specified <code>InputStream</code>.
53 * This Pump also synchronously output text read to
54 * log by prefixed lines. Constructor immediately
55 * starts reading in a separate thread.
57 * @param is Stream which requires permanent reading.
58 * @param log Writer where prefixed text lines to be output
59 * @param outPrefix A prefix which is printed at the
60 * beginning of each output line.
62 public Pump(InputStream is
, PrintWriter log
, String outPrefix
, boolean _bOutput
)
64 this.pref
= (outPrefix
== null) ?
"" : outPrefix
;
65 reader
= new LineNumberReader(new InputStreamReader(is
));
67 this.bOutput
= _bOutput
;
76 String line
= reader
.readLine();
81 log
.println(pref
+ line
);
84 buf
.append(line
).append('\n');
85 line
= reader
.readLine();
88 catch (java
.io
.IOException e
)
90 log
.println(pref
+ "Exception occurred: " + e
);
95 * Returns the text collected from input stream.
97 public String
getStringBuffer()
99 return buf
.toString();
104 * Class provides convenient way for running external program
105 * handle its standard streams, control execution and check results.
106 * Instance of this class must be created only for a single
107 * execution. If you need to execute the same command again you
108 * should create a new instance for this.
110 public class ProcessHandler
113 private String cmdLine
;
114 private String
[] cmdLineArray
;
115 private String
[] envVars
= null;
116 private File workDir
= null;
117 private PrintWriter log
;
118 private int exitValue
= -1;
119 private boolean isFinished
= false;
120 private boolean isStarted
= false;
121 private long mTimeOut
= 0;
122 private Pump stdout
= null;
123 private Pump stderr
= null;
124 private PrintStream stdIn
= null;
125 private Process m_aProcess
= null;
126 private TestParameters param
= null;
127 private boolean debug
= false;
128 private boolean bUseOutput
= true;
130 private int m_nProcessTimeout
= 0;
131 private ProcessWatcher m_aWatcher
;
134 * Creates instance with specified external command.
135 * Debug info and output
136 * of external command is printed to stdout.
138 public ProcessHandler(String cmdLine
)
140 this(cmdLine
, null, null, null, 0);
144 * Creates instance with specified external command and
145 * log stream where debug info and output
146 * of external command is printed out.
148 public ProcessHandler(String cmdLine
, PrintWriter log
)
150 this(cmdLine
, log
, null, null, 0);
154 * Creates instance with specified external command which
155 * will be executed in the some work directory and
157 * @param cmdLine the command to be executed
158 * @param log log stream where debug info and output
159 * of external command is printed .
160 * @param workDir The working directory of the new process
161 * @param envVars The specified environment variables are
162 * set for the new process.
163 * If log stream is null, logging is printed to stdout.
164 * @param timeOut When started sychronisly, the maximum time the
165 * process will live. When the process being destroyed
166 * a log will be written out. It can be asked on
167 * <code>isTimedOut()</code> if it has been terminated.
170 * Waits specified time in miliSeconds for
171 * process to exit and return its status.
174 * Waits for the process to end regulary
177 * Kills the process immediately
181 private ProcessHandler(String cmdLine
, PrintWriter log
, File workDir
, String
[] envVars
, long timeOut
)
183 this.cmdLine
= cmdLine
;
184 this.workDir
= workDir
;
186 this.cmdLine
= cmdLine
;
187 this.envVars
= envVars
;
190 this.log
= new PrintWriter(new OutputStreamWriter(System
.out
));
196 this.mTimeOut
= timeOut
;
200 * Creates instance with specified external command which
201 * will be executed in the some work directory and
202 * log stream where debug info and output of external command is printed.
203 * If log stream is null, logging is printed to stdout.
204 * From the <CODE>TestParameters</CODE> the <CODE>OfficeWachter</CODE> get a ping.
205 * @param shortWait If this parameter is true the <CODE>mTimeOut</CODE> is set to 5000 ms, else it is set to
206 * half of time out from parameter timeout.
207 * @param param the TestParameters
208 * @see lib.TestParameters
209 * @see helper.OfficeWatcher
211 public ProcessHandler(String
[] commands
, PrintWriter log
, File workDir
, int shortWait
, TestParameters param
)
213 this(null, log
, workDir
, null, 0);
214 this.cmdLineArray
= commands
;
218 this.mTimeOut
= shortWait
;
222 this.mTimeOut
= (long) (param
.getInt(PropertyName
.TIME_OUT
) / 1.3);
224 debug
= param
.getBool(PropertyName
.DEBUG_IS_ACTIVE
);
229 * This method do an asynchronous execution of the commands. To avoid a interruption on long running processes
230 * caused by <CODE>OfficeWatcher</CODE>, the OfficeWatcher get frequently a ping.
231 * @see helper.OfficeWatcher
233 public void runCommand()
236 boolean changedText
= true;
239 this.executeAsynchronously();
241 OfficeWatcher ow
= null;
244 ow
= (OfficeWatcher
) param
.get(PropertyName
.OFFICE_WATCHER
);
252 while (!this.isFinished() && changedText
)
254 waitFor(2000, false); // wait but don't kill
260 // check for changes in the output stream. If there are no changes, the process maybe hangs
261 if (!this.isFinished())
266 String sOutputText
= getOutputText();
267 if (sOutputText
.length() == memText
.length())
272 memText
= this.getOutputText();
277 if (!this.isFinished())
279 dbg("runCommand Process is not finished but there are no changes in output stream.");
285 * Executes the command and returns only when the process
288 * @return <code>true</code> if process was successfully
289 * started and correcly exits (exit code doesn't affect
292 public boolean executeSynchronously()
295 return waitFor(mTimeOut
);
299 * Executes the command immediately returns. The process
300 * remains in running state. Control of its state should
301 * be made by <code>waitFor</code> methods.
303 * @return <code>true</code> if process was successfully
306 public boolean executeAsynchronously()
312 public synchronized void kill()
318 boolean exit
= false;
320 while (counter
< 3 && !exit
)
322 m_aProcess
.destroy();
324 util
.utils
.pause(1000 * counter
);
327 final int exit_Value
= m_aProcess
.exitValue();
336 dbg("kill: process closed with exit code " + exit_Value
);
338 catch (java
.lang
.IllegalThreadStateException e
)
342 dbg("kill: Couldn't close process after " + counter
+ " attempts, trying again");
350 private void showEnvVars()
354 for (int i
= 0; i
< envVars
.length
; i
++)
356 log
.println("env: " + envVars
[i
]);
361 log
.println("env: null");
365 private void execute()
369 throw new RuntimeException(
370 "The process handler has already been executed.");
372 final Runtime runtime
= Runtime
.getRuntime();
377 log
.println(utils
.getDateTime() + "execute: Starting command from array: ");
378 for (int i
= 0; i
< cmdLineArray
.length
; i
++)
380 log
.println(cmdLineArray
[i
]);
384 initializeProcessKiller();
385 m_aProcess
= runtime
.exec(cmdLineArray
, envVars
);
391 log
.println(utils
.getDateTime() + "execute: Starting command: ");
392 log
.println(cmdLine
+ " path=" + workDir
.getAbsolutePath());
394 m_aProcess
= runtime
.exec(cmdLine
, envVars
, workDir
);
398 log
.println(utils
.getDateTime() + "execute: Starting command: ");
399 log
.println(cmdLine
);
401 m_aProcess
= runtime
.exec(cmdLine
, envVars
);
406 catch (java
.io
.IOException e
)
410 log
.println(utils
.getDateTime() + "execute: The command array can't be started: " + e
);
414 log
.println(utils
.getDateTime() + "execute: The command " + cmdLine
+ " can't be started: " + e
);
418 dbg("execute: pump io-streams");
419 stdout
= new Pump(m_aProcess
.getInputStream(), log
, "out > ", bUseOutput
);
420 stderr
= new Pump(m_aProcess
.getErrorStream(), log
, "err > ", bUseOutput
);
421 stdIn
= new PrintStream(m_aProcess
.getOutputStream());
423 dbg("execute: flush io-streams");
431 * This method is useful when the process was executed
432 * asynchronously. Waits during specified time for process
433 * to exit and return its status.
436 * Waits specified time in miliSeconds for
437 * process to exit and return its status.
440 * Waits for the process to end regulary
443 * Kills the process immediately
445 * @return <code>true</code> if process correctly exited
446 * (exit code doesn't affect to this result).
448 private boolean waitFor(long timeout
)
450 return waitFor(timeout
, true);
453 private boolean waitFor(long timeout
, boolean bKillProcessAfterTimeout
)
468 m_aProcess
.waitFor();
470 catch (InterruptedException e
)
472 log
.println("The process was interrupted: " + e
);
477 exitValue
= m_aProcess
.exitValue();
479 catch (IllegalThreadStateException e
)
487 while (!isFinished
&& timeout
> 0)
494 exitValue
= m_aProcess
.exitValue(); // throws exception if not finished
496 catch (IllegalThreadStateException e
)
503 log
.println("The process has timed out!");
506 catch (InterruptedException ex
)
508 log
.println("The process was interrupted: " + ex
);
512 if (bKillProcessAfterTimeout
)
516 log
.println("Going to destroy the process!!");
517 m_aProcess
.destroy();
518 log
.println("Process has been destroyed!");
525 private void flushInput()
539 * Returns the text output by external command to stdout.
540 * @return the text output by external command to stdout
542 public String
getOutputText()
550 return stdout
.getStringBuffer();
555 * Returns the text output by external command to stderr.
556 * @return the text output by external command to stderr
558 public String
getErrorText()
566 return stderr
.getStringBuffer();
573 * Returns information about was the command started or
576 * @return <code>true</code> if the external command was
577 * found and successfully started.
579 private boolean isStarted()
585 * Returns the information about the final state of command
588 * @return <code>true</code> if the command correctly starts,
589 * exits and was not interrupted due to timeout.
591 private boolean isFinished()
597 * Returns exit code of the external command.
599 * @return exit code of command if it was finished,
602 public int getExitCode()
606 exitValue
= m_aProcess
.exitValue();
615 private void dbg(String message
)
619 log
.println(utils
.getDateTime() + "PH." + message
);
623 public void noOutput()
628 private class ProcessWatcher
extends Thread
631 private int m_nTimeoutInSec
;
632 private final boolean m_bInterrupt
;
634 private ProcessWatcher(int _nTimeOut
)
636 m_nTimeoutInSec
= _nTimeOut
;
637 m_bInterrupt
= false;
641 * returns true, if the thread should hold on
643 public synchronized boolean isInHoldOn()
650 while (m_nTimeoutInSec
> 0)
653 util
.utils
.pause(1000);
664 * If the timeout only given by setProcessTimeout(int seconds) function is != 0,
665 * a extra thread is created and after time has run out, the ProcessKiller string
666 * given by function setProcessKiller(string) will execute.
667 * So it is possible to kill a running office after a given time of seconds.
669 private void initializeProcessKiller()
671 if (m_nProcessTimeout
!= 0)
673 m_aWatcher
= new ProcessWatcher(m_nProcessTimeout
);