/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.olivergondza.dumpling;
import static com.github.olivergondza.dumpling.Util.pause;
import static com.github.olivergondza.dumpling.Util.processBuilder;
import static com.github.olivergondza.dumpling.Util.processTerminatedPrematurely;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnull;
/**
* SUT thread to be observed from tests.
*
* Can be run as local thread of remote process.
*/
public final class TestThread {
public static final @Nonnull String JMX_HOST = "localhost";
public static final @Nonnull String JMX_USER = "user";
public static final @Nonnull String JMX_PASSWD = "secret_passwd";
public static final String MARKER = "DUMPLING-SUT-IS-READY";
// Observable process entry point - not to be invoked directly
public static synchronized void main(String... args) throws InterruptedException {
runThread();
System.out.println(MARKER);
// Block process forever
TestThread.class.wait();
throw new Error("Should never get here");
}
public static Thread runThread() {
final CountDownLatch cdl = new CountDownLatch(1);
Thread thread = new Thread("remotely-observed-thread") {
@Override
public synchronized void run() {
try {
cdl.countDown();
this.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
};
thread.setDaemon(true);
thread.setPriority(7);
thread.start();
try {
cdl.await();
} catch (InterruptedException ex) {
throw new AssertionError(ex);
}
return thread;
}
public static Thread setupSleepingThreadWithLock() {
final ReentrantLock lock = new ReentrantLock();
Thread thread = new Thread("sleepingThreadWithLock") {
@Override
public void run() {
lock.lock();
pause(10000);
}
};
thread.start();
while(!lock.isLocked()) {
pause(1000);
}
return thread;
}
private static int getFreePort() throws IOException {
ServerSocket serverSocket = new ServerSocket(0);
try {
return serverSocket.getLocalPort();
} finally {
serverSocket.close();
}
}
/* Client is expected to dispose the thread */
public static JMXProcess runJmxObservableProcess(boolean auth) throws Exception {
int jmxPort = getFreePort();
String //cp = "target/test-classes:target/classes"; // Current module
// Use file to convert URI to a path platform FS would understand,
cp = new File(TestThread.class.getProtectionDomain().getCodeSource().getLocation().toURI().getSchemeSpecificPart()).getAbsolutePath();
List<String> args = new ArrayList<String>();
args.add("java");
args.add("-cp");
args.add(cp);
args.add("-Djava.util.logging.config.file=" + Util.asFile(Util.resource(TestThread.class, "logging.properties")).getAbsolutePath());
args.add("-Dcom.sun.management.jmxremote");
args.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
args.add("-Dcom.sun.management.jmxremote.local.only=false");
args.add("-Dcom.sun.management.jmxremote.authenticate=" + auth);
args.add("-Dcom.sun.management.jmxremote.ssl=false");
args.add("-Djava.rmi.server.hostname=127.0.0.1");
if (auth) {
args.add("-Dcom.sun.management.jmxremote.password.file=" + getCredFile("jmxremote.password"));
args.add("-Dcom.sun.management.jmxremote.access.file=" + getCredFile("jmxremote.access"));
}
args.add("com.github.olivergondza.dumpling.TestThread");
ProcessBuilder pb = processBuilder().command(args);
String processLine = pb.command().toString();
final Process process = pb.start();
BufferedInputStream bis = new BufferedInputStream(process.getInputStream());
bis.mark(1024 * 1024 * 1);
String out = "never_tried";
for (int i = 0; i < 10; i++) {
out = isUp(bis);
if (out != null) return new JMXProcess(process, jmxPort);
try {
int exit = process.exitValue();
throw processTerminatedPrematurely(process, exit, processLine);
} catch (IllegalThreadStateException ex) {
// Still running
}
Thread.sleep(500);
}
throw new AssertionError("Unable to bring to SUT up in time: " + out);
}
public static final class JMXProcess extends Process {
private final Process p;
public final int JMX_PORT;
public final @Nonnull String JMX_CONNECTION;
public final @Nonnull String JMX_AUTH_CONNECTION;
public JMXProcess(Process p, int port) {
this.p = p;
JMX_PORT = port;
JMX_CONNECTION = JMX_HOST + ":" + JMX_PORT;
JMX_AUTH_CONNECTION = JMX_USER + ":" + JMX_PASSWD + "@" + JMX_CONNECTION;
}
@Override public OutputStream getOutputStream() {
return p.getOutputStream();
}
@Override public InputStream getInputStream() {
return p.getInputStream();
}
@Override public InputStream getErrorStream() {
return p.getErrorStream();
}
@Override public int waitFor() throws InterruptedException {
return p.waitFor();
}
@Override public int exitValue() {
return p.exitValue();
}
@Override public void destroy() {
p.destroy();
}
public Process getNativeProcess() {
return p;
}
}
private static String isUp(BufferedInputStream is) throws IOException {
is.reset();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
String line = new String(buffer, 0, length);
if (line.contains(MARKER)) return line;
}
return null;
}
private static String getCredFile(String path) throws Exception {
String file = Util.asFile(Util.resource(TestThread.class, path)).getAbsolutePath();
// Workaround http://jira.codehaus.org/browse/MRESOURCES-132
Process process = new ProcessBuilder("chmod", "600", file).start();
if (process.waitFor() != 0) {
throw new RuntimeException(
"Failed to adjust permissions: " + Util.asString(process.getErrorStream())
);
}
return file;
}
}