1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.test.integration

File XWikiExecutor.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

46
157
31
2
555
373
59
0.38
5.06
15.5
1.9

Classes

Class Line # Actions
XWikiExecutor 52 157 0% 59 40
0.8290598482.9%
XWikiExecutor.Response 113 0 - 0 0
-1.0 -
 

Contributing tests

This file is covered by 21 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.test.integration;
21   
22    import java.io.File;
23    import java.io.FileInputStream;
24    import java.io.FileNotFoundException;
25    import java.io.FileOutputStream;
26    import java.util.HashMap;
27    import java.util.Map;
28    import java.util.Properties;
29   
30    import org.apache.commons.configuration.PropertiesConfiguration;
31    import org.apache.commons.exec.CommandLine;
32    import org.apache.commons.exec.DefaultExecuteResultHandler;
33    import org.apache.commons.exec.DefaultExecutor;
34    import org.apache.commons.exec.ExecuteWatchdog;
35    import org.apache.commons.exec.PumpStreamHandler;
36    import org.apache.commons.exec.ShutdownHookProcessDestroyer;
37    import org.apache.commons.exec.environment.EnvironmentUtils;
38    import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
39    import org.apache.commons.httpclient.HttpClient;
40    import org.apache.commons.httpclient.methods.GetMethod;
41    import org.apache.commons.httpclient.params.HttpMethodParams;
42    import org.apache.commons.lang3.SystemUtils;
43    import org.slf4j.Logger;
44    import org.slf4j.LoggerFactory;
45   
46    /**
47    * Start and stop an XWiki instance.
48    *
49    * @version $Id: 7c4a1b32abb6cda8f05cdbfd3065b1b95de95f82 $
50    * @since 2.0RC1
51    */
 
52    public class XWikiExecutor
53    {
54    protected static final Logger LOGGER = LoggerFactory.getLogger(XWikiExecutor.class);
55   
56    /**
57    * If defined then we check for an existing running XWiki instance before trying to start XWiki.
58    */
59    public static final String VERIFY_RUNNING_XWIKI_AT_START =
60    System.getProperty("xwiki.test.verifyRunningXWikiAtStart", "true");
61   
62    public static final String BASEDIR = System.getProperty("basedir");
63   
64    public static final String URL = System.getProperty("xwiki.test.baseURL", "http://localhost");
65   
66    public static final String DEFAULT_PORT = System.getProperty("xwikiPort", "8080");
67   
68    public static final String DEFAULT_STOPPORT = System.getProperty("xwikiStopPort", "8079");
69   
70    public static final String DEFAULT_RMIPORT = System.getProperty("rmiPort", "9010");
71   
72    private static final String DEFAULT_EXECUTION_DIRECTORY = System.getProperty("xwikiExecutionDirectory");
73   
74    private static final String START_COMMAND = System.getProperty("xwikiExecutionStartCommand");
75   
76    private static final String STOP_COMMAND = System.getProperty("xwikiExecutionStopCommand");
77   
78    private static final boolean DEBUG = System.getProperty("debug", "false").equalsIgnoreCase("true");
79   
80    private static final String WEBINF_PATH = "/webapps/xwiki/WEB-INF";
81   
82    private static final String XWIKICFG_PATH = WEBINF_PATH + "/xwiki.cfg";
83   
84    private static final String XWIKIPROPERTIES_PATH = WEBINF_PATH + "/xwiki.properties";
85   
86    private static final int TIMEOUT_SECONDS = 120;
87   
88    private static final long PROCESS_FINISH_TIMEOUT = 5 * 60L * 1000L;
89   
90    private int port;
91   
92    private int stopPort;
93   
94    private int rmiPort;
95   
96    private String executionDirectory;
97   
98    private Map<String, String> environment = new HashMap<String, String>();
99   
100    private DefaultExecuteResultHandler startedProcessHandler;
101   
102    /**
103    * Was XWiki server already started. We don't try to stop it if it was already started.
104    */
105    private boolean wasStarted;
106   
107    /**
108    * If true we've been successful in starting XWiki and we can stop it when the test exits. If not then we shouldn't
109    * try to stop XWiki since it's been started successfully.
110    */
111    private boolean hasXWikiBeenStartedProperly;
112   
 
113    public class Response
114    {
115    public boolean timedOut;
116   
117    public byte[] responseBody;
118   
119    public int responseCode;
120    }
121   
 
122  41 toggle public XWikiExecutor(int index)
123    {
124    // resolve ports
125  41 String portString = System.getProperty("xwikiPort" + index);
126  41 this.port = portString != null ? Integer.valueOf(portString) : (Integer.valueOf(DEFAULT_PORT) + index);
127  41 String stopPortString = System.getProperty("xwikiStopPort" + index);
128  41 this.stopPort =
129  41 stopPortString != null ? Integer.valueOf(stopPortString) : (Integer.valueOf(DEFAULT_STOPPORT) - index);
130  41 String rmiPortString = System.getProperty("rmiPort" + index);
131  41 this.rmiPort =
132  41 rmiPortString != null ? Integer.valueOf(rmiPortString) : (Integer.valueOf(DEFAULT_RMIPORT) + index);
133   
134    // Resolve the execution directory, which should point to a location where an XWiki distribution is located
135    // and can be started (directory where the start_xwiki.sh|bat files are located).
136  41 this.executionDirectory = System.getProperty("xwikiExecutionDirectory" + index);
137  41 if (this.executionDirectory == null) {
138  41 this.executionDirectory = DEFAULT_EXECUTION_DIRECTORY;
139  41 if (this.executionDirectory == null) {
140  32 this.executionDirectory = BASEDIR + "/target/xwiki";
141    }
142  41 if (index > 0) {
143  1 this.executionDirectory += "-" + index;
144    }
145    }
146    }
147   
 
148  6820 toggle public int getPort()
149    {
150  6820 return this.port;
151    }
152   
 
153  164 toggle public int getStopPort()
154    {
155  164 return this.stopPort;
156    }
157   
 
158  82 toggle public int getRMIPort()
159    {
160  82 return this.rmiPort;
161    }
162   
 
163  186 toggle public String getExecutionDirectory()
164    {
165  186 if (this.executionDirectory == null) {
166  0 throw new RuntimeException("Invalid configuration for the execution directory. The "
167    + "[xwikiExecutionDirectory] system property must be specified.");
168    }
169  186 return this.executionDirectory;
170    }
171   
 
172  2 toggle public void setXWikiOpts(String opts)
173    {
174  2 addEnvironmentVariable("XWIKI_OPTS", opts);
175    }
176   
 
177  2 toggle public void addEnvironmentVariable(String key, String value)
178    {
179  2 this.environment.put(key, value);
180    }
181   
182    /**
183    * Start XWiki using the following strategy:
184    * <ul>
185    * <li>If the {@link #VERIFY_RUNNING_XWIKI_AT_START} property is set then checks if an XWiki instance is already
186    * running before trying to start XWiki and if so, reuse it and don't start XWiki</li>
187    * <li>If the {@link #VERIFY_RUNNING_XWIKI_AT_START} property is set to false then verify if some XWiki instance
188    * is already running by verifying if the port is free and fail if so. Otherwise start XWiki.</li>
189    * </ul>
190    */
 
191  41 toggle public void start() throws Exception
192    {
193  41 this.wasStarted = false;
194  41 if (VERIFY_RUNNING_XWIKI_AT_START.equals("true")) {
195  0 LOGGER.info("Checking if an XWiki server is already started at [{}]", getURL());
196    // First, verify if XWiki is started. If it is then don't start it again.
197  0 this.wasStarted = !isXWikiStarted(getURL(), 15).timedOut;
198    }
199   
200  41 if (!this.wasStarted) {
201  41 LOGGER.info("Stopping any potentially running XWiki server at [{}]", getURL());
202  41 stopInternal();
203  41 LOGGER.info("Starting XWiki server at [{}], using stop port [{}] and RMI port [{}]", getURL(),
204    getStopPort(), getRMIPort());
205  41 startXWiki();
206  41 waitForXWikiToLoad();
207  41 this.hasXWikiBeenStartedProperly = true;
208    } else {
209  0 LOGGER.info("XWiki server is already started at [{}]", getURL());
210    }
211    }
212   
 
213  123 toggle private DefaultExecuteResultHandler executeCommand(String commandLine) throws Exception
214    {
215    // The command line to execute
216  123 CommandLine command = CommandLine.parse(commandLine);
217   
218    // Execute the process asynchronously so that we don't block.
219  123 DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
220   
221    // Send Process output and error streams to our logger.
222  123 PumpStreamHandler streamHandler = new PumpStreamHandler(
223    new XWikiLogOutputStream(XWikiLogOutputStream.STDOUT),
224    new XWikiLogOutputStream(XWikiLogOutputStream.STDERR));
225   
226    // Make sure we end the process when the JVM exits
227  123 ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();
228   
229    // Prevent the process from running indefinitely and kill it after 1 hour...
230  123 ExecuteWatchdog watchDog = new ExecuteWatchdog(60L * 60L * 1000L);
231   
232    // The executor to execute the command
233  123 DefaultExecutor executor = new DefaultExecutor();
234  123 executor.setStreamHandler(streamHandler);
235  123 executor.setWorkingDirectory(new File(getExecutionDirectory()));
236  123 executor.setProcessDestroyer(processDestroyer);
237  123 executor.setWatchdog(watchDog);
238   
239    // Inherit the current process's environment variables and add the user-defined ones
240  123 @SuppressWarnings("unchecked")
241    Map<String, String> newEnvironment = EnvironmentUtils.getProcEnvironment();
242  123 newEnvironment.putAll(this.environment);
243   
244  123 executor.execute(command, newEnvironment, resultHandler);
245   
246  123 return resultHandler;
247    }
248   
249    /**
250    * Starts XWiki and returns immediately.
251    */
 
252  41 toggle private void startXWiki() throws Exception
253    {
254  41 File dir = new File(getExecutionDirectory());
255  41 if (dir.exists()) {
256  41 String startCommand = getDefaultStartCommand(getPort(), getStopPort(), getRMIPort());
257  41 LOGGER.debug("Executing command: [{}]", startCommand);
258  41 this.startedProcessHandler = executeCommand(startCommand);
259    } else {
260  0 throw new Exception(String.format("Invalid directory from where to start XWiki [%s]. If you're starting "
261    + "a functional test from your IDE, make sure to either have started an XWiki instance beforehand or "
262    + "configure your IDE so that either the [basedir] or [xwikiExecutionDirectory] properties have been "
263    + "defined so that the test framework can start XWiki for you. If you set [basedir] make it point to "
264    + "the directory containing the [target/] directory of your project. The test framework will then try "
265    + "to locate an XWiki instance in [<basedir>/target/xwiki]. If the XWiki instance you wish to start is "
266    + "elsewhere then define the [xwikiExecutionDirectory] System property to point to it.",
267    this.executionDirectory));
268    }
269    }
270   
 
271  41 toggle private void waitForXWikiToLoad() throws Exception
272    {
273    // Wait till the main page becomes available which means the server is started fine
274  41 LOGGER.info("Checking that XWiki is up and running...");
275   
276  41 Response response = isXWikiStarted(getURL(), TIMEOUT_SECONDS);
277  41 if (response.timedOut) {
278  0 String message = String.format("Failed to start XWiki in [%s] seconds, last error code [%s], message [%s]",
279    TIMEOUT_SECONDS, response.responseCode, new String(response.responseBody));
280  0 LOGGER.info(message);
281  0 stop();
282  0 throw new RuntimeException(message);
283    } else {
284  41 LOGGER.info("Server is answering to [{}]... cool", getURL());
285    }
286    }
287   
 
288  41 toggle public Response isXWikiStarted(String url, int timeout) throws Exception
289    {
290  41 HttpClient client = new HttpClient();
291   
292  41 boolean connected = false;
293  41 long startTime = System.currentTimeMillis();
294  41 Response response = new Response();
295  41 response.timedOut = false;
296  41 response.responseCode = -1;
297  41 response.responseBody = new byte[0];
298  776 while (!connected && !response.timedOut) {
299  735 GetMethod method = new GetMethod(url);
300   
301    // Don't retry automatically since we're doing that in the algorithm below
302  735 method.getParams()
303    .setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false));
304    // Set a socket timeout to ensure the server has no chance of not answering to our request...
305  735 method.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 10000);
306   
307  735 try {
308    // Execute the method.
309  735 response.responseCode = client.executeMethod(method);
310   
311    // We must always read the response body.
312  41 response.responseBody = method.getResponseBody();
313   
314  41 if (DEBUG) {
315  0 LOGGER.info("Result of pinging [{}] = [{}], Message = [{}]", url,
316    response.responseCode, new String(response.responseBody));
317    }
318   
319    // check the http response code is either not an error, either "unauthorized"
320    // (which is the case for products that deny view for guest, for example).
321  41 connected = (response.responseCode < 400 || response.responseCode == 401);
322    } catch (Exception e) {
323    // Do nothing as it simply means the server is not ready yet...
324    } finally {
325    // Release the connection.
326  735 method.releaseConnection();
327    }
328  735 Thread.sleep(500L);
329  735 response.timedOut = (System.currentTimeMillis() - startTime > timeout * 1000L);
330    }
331   
332  41 if (response.timedOut) {
333  0 LOGGER.info("No server is responding on [{}] after [{}] seconds", url, timeout);
334    }
335   
336  41 return response;
337    }
338   
 
339  41 toggle public void stop() throws Exception
340    {
341  41 LOGGER.debug("Checking if we need to stop the XWiki server running at [{}]...", getURL());
342   
343    // Do not try to stop XWiki if we've not been successful in starting it!
344  41 if (!this.hasXWikiBeenStartedProperly) {
345  0 return;
346    }
347   
348    // Stop XWiki if it was started by start()
349  41 if (!this.wasStarted) {
350  41 stopInternal();
351   
352    // Now wait for the start process to be stopped, waiting a max of 5 minutes!
353  41 if (this.startedProcessHandler != null) {
354  41 waitForProcessToFinish(this.startedProcessHandler, PROCESS_FINISH_TIMEOUT);
355    }
356   
357  40 LOGGER.info("XWiki server running at [{}] has been stopped", getURL());
358    } else {
359  0 LOGGER.info("XWiki server not stopped since we didn't start it (it was already started)");
360    }
361    }
362   
 
363  82 toggle private void stopInternal() throws Exception
364    {
365  82 String stopCommand = getDefaultStopCommand(getPort(), getStopPort());
366  82 LOGGER.debug("Executing command: [{}]", stopCommand);
367  82 DefaultExecuteResultHandler stopProcessHandler = executeCommand(stopCommand);
368   
369    // First wait for the stop process to have stopped, waiting a max of 5 minutes!
370    // It's going to stop the start process...
371  82 waitForProcessToFinish(stopProcessHandler, PROCESS_FINISH_TIMEOUT);
372    }
373   
 
374  123 toggle private void waitForProcessToFinish(DefaultExecuteResultHandler handler, long timeout) throws Exception
375    {
376    // Wait for the process to finish.
377  123 handler.waitFor(timeout);
378   
379    // Check the exit value and fail the test if the process has not properly finished.
380  123 if (handler.getExitValue() != 0 || !handler.hasResult()) {
381  1 String message =
382    String.format("Process failed to close properly after [%d] seconds, process ended [%b].", timeout,
383    handler.hasResult());
384  1 if (handler.hasResult()) {
385  1 message =
386    String.format("%s Exit code [%d], message [%s].", message, handler.getExitValue(), handler
387    .getException().getMessage());
388    }
389  1 message = String.format("%s Failing test.", message);
390  1 throw new RuntimeException(message);
391    }
392    }
393   
 
394  0 toggle public String getWebInfDirectory()
395    {
396  0 return getExecutionDirectory() + WEBINF_PATH;
397    }
398   
 
399  2 toggle public String getXWikiCfgPath()
400    {
401  2 return getExecutionDirectory() + XWIKICFG_PATH;
402    }
403   
 
404  20 toggle public String getXWikiPropertiesPath()
405    {
406  20 return getExecutionDirectory() + XWIKIPROPERTIES_PATH;
407    }
408   
 
409  1 toggle public Properties loadXWikiCfg() throws Exception
410    {
411  1 return getProperties(getXWikiCfgPath());
412    }
413   
 
414  0 toggle public Properties loadXWikiProperties() throws Exception
415    {
416  0 return getProperties(getXWikiPropertiesPath());
417    }
418   
 
419  10 toggle public PropertiesConfiguration loadXWikiPropertiesConfiguration() throws Exception
420    {
421  10 return getPropertiesConfiguration(getXWikiPropertiesPath());
422    }
423   
424    /**
425    * @deprecated since 4.2M1 use {@link #getPropertiesConfiguration(String)} instead
426    */
 
427  1 toggle @Deprecated
428    private Properties getProperties(String path) throws Exception
429    {
430  1 Properties properties = new Properties();
431   
432  1 FileInputStream fis;
433  1 try {
434  1 fis = new FileInputStream(path);
435   
436  1 try {
437  1 properties.load(fis);
438    } finally {
439  1 fis.close();
440    }
441    } catch (FileNotFoundException e) {
442  0 LOGGER.debug("Failed to load properties [{}]", path, e);
443    }
444   
445  1 return properties;
446    }
447   
448    /**
449    * @since 4.2M1
450    */
 
451  10 toggle private PropertiesConfiguration getPropertiesConfiguration(String path) throws Exception
452    {
453  10 PropertiesConfiguration properties = new PropertiesConfiguration();
454   
455  10 FileInputStream fis;
456  10 try {
457  10 fis = new FileInputStream(path);
458   
459  10 try {
460  10 properties.load(fis);
461    } finally {
462  10 fis.close();
463    }
464    } catch (FileNotFoundException e) {
465  0 LOGGER.debug("Failed to load properties [" + path + "]", e);
466    }
467   
468  10 return properties;
469    }
470   
 
471  1 toggle public void saveXWikiCfg(Properties properties) throws Exception
472    {
473  1 saveProperties(getXWikiCfgPath(), properties);
474    }
475   
476    /**
477    * @deprecated since 4.2M1 use {@link #saveXWikiProperties(PropertiesConfiguration)} instead
478    */
 
479  0 toggle @Deprecated
480    public void saveXWikiProperties(Properties properties) throws Exception
481    {
482  0 saveProperties(getXWikiPropertiesPath(), properties);
483    }
484   
485    /**
486    * @since 4.2M1
487    */
 
488  10 toggle public void saveXWikiProperties(PropertiesConfiguration properties) throws Exception
489    {
490  10 savePropertiesConfiguration(getXWikiPropertiesPath(), properties);
491    }
492   
 
493  1 toggle private void saveProperties(String path, Properties properties) throws Exception
494    {
495  1 FileOutputStream fos = new FileOutputStream(path);
496  1 try {
497  1 properties.store(fos, null);
498    } finally {
499  1 fos.close();
500    }
501    }
502   
 
503  10 toggle private void savePropertiesConfiguration(String path, PropertiesConfiguration properties) throws Exception
504    {
505  10 FileOutputStream fos = new FileOutputStream(path);
506  10 try {
507  10 properties.save(fos);
508    } finally {
509  10 fos.close();
510    }
511    }
512   
 
513  245 toggle public String getURL()
514    {
515    // We use "get" action for 2 reasons:
516    // 1) the page loads faster since it doesn't need to display the skin
517    // 2) if the page doesn't exist it won't return a 404 HTTP Response code
518  245 return URL + ":" + getPort() + "/xwiki/bin/get/Main/";
519    }
520   
 
521  41 toggle private String getDefaultStartCommand(int port, int stopPort, int rmiPort)
522    {
523  41 String startCommand = START_COMMAND;
524  41 if (startCommand == null) {
525  32 if (SystemUtils.IS_OS_WINDOWS) {
526  0 startCommand = String.format("cmd /c start_xwiki.bat %s %s", port, stopPort);
527    } else {
528  32 startCommand = String.format("bash start_xwiki.sh -p %s -sp %s", port, stopPort);
529    }
530    } else {
531  9 startCommand = startCommand.replaceFirst(DEFAULT_PORT, String.valueOf(port));
532  9 startCommand = startCommand.replaceFirst(DEFAULT_STOPPORT, String.valueOf(stopPort));
533  9 startCommand = startCommand.replaceFirst(DEFAULT_RMIPORT, String.valueOf(rmiPort));
534    }
535   
536  41 return startCommand;
537    }
538   
 
539  82 toggle private String getDefaultStopCommand(int port, int stopPort)
540    {
541  82 String stopCommand = STOP_COMMAND;
542  82 if (stopCommand == null) {
543  64 if (SystemUtils.IS_OS_WINDOWS) {
544  0 stopCommand = String.format("cmd /c stop_xwiki.bat %s", stopPort);
545    } else {
546  64 stopCommand = String.format("bash stop_xwiki.sh -p %s -sp %s", port, stopPort);
547    }
548    } else {
549  18 stopCommand = stopCommand.replaceFirst(DEFAULT_PORT, String.valueOf(port));
550  18 stopCommand = stopCommand.replaceFirst(DEFAULT_STOPPORT, String.valueOf(stopPort));
551    }
552   
553  82 return stopCommand;
554    }
555    }