/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.tools.ant.taskdefs;

import java.net.*;
import junit.framework.*;
import java.io.*;

/**
 * Simple testcase for the ExecuteWatchdog class.
 *
 * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
 */
public class ExecuteWatchdogTest extends TestCase {

	private final static int TIME_OUT = 2000;

	private final static String TEST_CLASSPATH = getTestClassPath();

	private ExecuteWatchdog watchdog;

	public ExecuteWatchdogTest(String name) {
		super(name);
	}

	protected void setUp(){
		watchdog = new ExecuteWatchdog(TIME_OUT);
	}

	/**
	 * Dangerous method to obtain the classpath for the test. This is
	 * severely tighted to the build.xml properties.
	 */
	private static String getTestClassPath(){
		String classpath = System.getProperty("build.tests");
		if (classpath == null) {
			System.err.println("WARNING: 'build.tests' property is not available !");
			classpath = System.getProperty("java.class.path");
		}
		System.out.println("Using classpath: " + classpath);		
		return classpath;
	}

	private Process getProcess(int timetorun) throws Exception {
		String[] cmdArray = {
			"java", "-classpath", TEST_CLASSPATH,
			TimeProcess.class.getName(), String.valueOf(timetorun)
		};
		//System.out.println("Testing with classpath: " + System.getProperty("java.class.path"));
		return Runtime.getRuntime().exec(cmdArray);
	}

	private String getErrorOutput(Process p) throws Exception {
		BufferedReader err = new BufferedReader( new InputStreamReader(p.getErrorStream()) );
		StringBuffer buf = new StringBuffer();
		String line;
		while ( (line = err.readLine()) != null){
			buf.append(line);
		}
		return buf.toString();
	}
	
	private int waitForEnd(Process p) throws Exception {
		int retcode = p.waitFor();
		if (retcode != 0){
			String err = getErrorOutput(p);
			if (err.length() > 0){
				System.err.println("ERROR:");
				System.err.println(err);
			}
		}
		return retcode;
	}

	public void testNoTimeOut() throws Exception {
		Process process = getProcess(TIME_OUT/2);
		watchdog.start(process);
		int retCode = waitForEnd(process);
		assert("process should not have been killed", !watchdog.killedProcess());
		assertEquals(0, retCode);
	}

	// test that the watchdog ends the process
	public void testTimeOut() throws Exception {
		Process process = getProcess(TIME_OUT*2);
		long now = System.currentTimeMillis();
		watchdog.start(process);
		int retCode = process.waitFor();
		long elapsed = System.currentTimeMillis() - now;
		assert("process should have been killed", watchdog.killedProcess());
		assert("return code is invalid: " + retCode, retCode!=0);
		assert("elapse time is less than timeout value", elapsed > TIME_OUT);
		assert("elapse time is greater than run value", elapsed < TIME_OUT*2);
	}

	// test a process that runs and failed
	public void testFailed() throws Exception {
		Process process = getProcess(-1); // process should abort
		watchdog.start(process);
		int retCode = process.waitFor();
		assert("process should not have been killed", !watchdog.killedProcess());
		assert("return code is invalid: " + retCode, retCode!=0);
	}

	public void testManualStop() throws Exception {
		final Process process = getProcess(TIME_OUT*2);
		watchdog.start(process);

		// I assume that starting this takes less than TIME_OUT/2 ms...
		Thread thread = new Thread(){
				public void run(){
					try {
						process.waitFor();
					} catch(InterruptedException e){
						// not very nice but will do the job
						fail("process interrupted in thread");
					}
				}
		};
		thread.start();

		// wait for TIME_OUT/2, there should be about TIME_OUT/2 ms remaining before timeout
		thread.join(TIME_OUT/2);

		 // now stop the watchdog.
		watchdog.stop();

		// wait for the thread to die, should be the end of the process
		thread.join();

		// process should be dead and well finished
		assertEquals(0, process.exitValue());
		assert("process should not have been killed", !watchdog.killedProcess());
	}

	public static class TimeProcess {
		public static void main(String[] args) throws Exception {
			int time = Integer.parseInt(args[0]);
			if (time < 1) {
				throw new IllegalArgumentException("Invalid time: " + time);
			}
			Thread.sleep(time);
		}
	}
}
