/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.runtime.testutils;

import org.apache.flink.api.common.time.Deadline;
import org.apache.flink.util.FileUtils;
import org.apache.flink.util.function.SupplierWithException;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.concurrent.TimeoutException;

/**
 * This class contains auxiliary methods for unit tests.
 */
public class CommonTestUtils {

	private static final long RETRY_INTERVAL = 100L;

	/**
	 * Sleeps for a given set of milliseconds, uninterruptibly. If interrupt is called,
	 * the sleep will continue nonetheless.
	 *
	 * @param msecs The number of milliseconds to sleep.
	 */
	public static void sleepUninterruptibly(long msecs) {
		
		long now = System.currentTimeMillis();
		long sleepUntil = now + msecs;
		long remaining;
		
		while ((remaining = sleepUntil - now) > 0) {
			try {
				Thread.sleep(remaining);
			}
			catch (InterruptedException ignored) {}
			
			now = System.currentTimeMillis();
		}
	}

	/**
	 * Gets the classpath with which the current JVM was started.
	 *
	 * @return The classpath with which the current JVM was started.
	 */
	public static String getCurrentClasspath() {
		RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
		return bean.getClassPath();
	}

	/**
	 * Create a temporary log4j configuration for the test.
	 */
	public static File createTemporaryLog4JProperties() throws IOException {
		File log4jProps = File.createTempFile(
				FileUtils.getRandomFilename(""), "-log4j.properties");
		log4jProps.deleteOnExit();
		CommonTestUtils.printLog4jDebugConfig(log4jProps);

		return log4jProps;
	}

	/**
	 * Tries to get the java executable command with which the current JVM was started.
	 * Returns null, if the command could not be found.
	 *
	 * @return The java executable command.
	 */
	public static String getJavaCommandPath() {
		File javaHome = new File(System.getProperty("java.home"));

		String path1 = new File(javaHome, "java").getAbsolutePath();
		String path2 = new File(new File(javaHome, "bin"), "java").getAbsolutePath();

		try {
			ProcessBuilder bld = new ProcessBuilder(path1, "-version");
			Process process = bld.start();
			if (process.waitFor() == 0) {
				return path1;
			}
		}
		catch (Throwable t) {
			// ignore and try the second path
		}

		try {
			ProcessBuilder bld = new ProcessBuilder(path2, "-version");
			Process process = bld.start();
			if (process.waitFor() == 0) {
				return path2;
			}
		}
		catch (Throwable tt) {
			// no luck
		}
		return null;
	}

	/**
	 * Checks whether a process is still alive. Utility method for JVM versions before 1.8,
	 * where no direct method to check that is available.
	 *
	 * @param process The process to check.
	 * @return True, if the process is alive, false otherwise.
	 */
	public static boolean isProcessAlive(Process process) {
		if (process == null) {
			return false;

		}
		try {
			process.exitValue();
			return false;
		}
		catch(IllegalThreadStateException e) {
			return true;
		}
	}

	public static void printLog4jDebugConfig(File file) throws IOException {
		try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
			writer.println("log4j.rootLogger=DEBUG, console");
			writer.println("log4j.appender.console=org.apache.log4j.ConsoleAppender");
			writer.println("log4j.appender.console.target = System.err");
			writer.println("log4j.appender.console.layout=org.apache.log4j.PatternLayout");
			writer.println("log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n");
			writer.println("log4j.logger.org.eclipse.jetty.util.log=OFF");
			writer.println("log4j.logger.org.apache.zookeeper=OFF");
			writer.flush();
		}
	}

	public static void waitUntilCondition(SupplierWithException<Boolean, Exception> condition, Deadline timeout) throws Exception {
		waitUntilCondition(condition, timeout, RETRY_INTERVAL);
	}

	public static void waitUntilCondition(SupplierWithException<Boolean, Exception> condition, Deadline timeout, long retryIntervalMillis) throws Exception {
		while (timeout.hasTimeLeft() && !condition.get()) {
			Thread.sleep(Math.min(retryIntervalMillis, timeout.timeLeft().toMillis()));
		}

		if (!timeout.hasTimeLeft()) {
			throw new TimeoutException("Condition was not met in given timeout.");
		}
	}

	/**
	 * Utility class to read the output of a process stream and forward it into a StringWriter.
	 */
	public static class PipeForwarder extends Thread {

		private final StringWriter target;
		private final InputStream source;

		public PipeForwarder(InputStream source, StringWriter target) {
			super("Pipe Forwarder");
			setDaemon(true);

			this.source = source;
			this.target = target;

			start();
		}

		@Override
		public void run() {
			try {
				int next;
				while ((next = source.read()) != -1) {
					target.write(next);
				}
			}
			catch (IOException e) {
				// terminate
			}
		}
	}

	public static boolean isSteamContentEqual(InputStream input1, InputStream input2) throws IOException {

		if (!(input1 instanceof BufferedInputStream)) {
			input1 = new BufferedInputStream(input1);
		}
		if (!(input2 instanceof BufferedInputStream)) {
			input2 = new BufferedInputStream(input2);
		}

		int ch = input1.read();
		while (-1 != ch) {
			int ch2 = input2.read();
			if (ch != ch2) {
				return false;
			}
			ch = input1.read();
		}

		int ch2 = input2.read();
		return (ch2 == -1);
	}
}

@@ -4514,16 +4514,33 @@
 Pattern=
+%25d%7BHH:mm:ss,SSS%7D 
 %25-4r %5B%25t

Include timestamp in generated log4j config