/* * 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.factory; import static com.github.olivergondza.dumpling.Util.currentProcessOut; import static com.github.olivergondza.dumpling.Util.only; import static com.github.olivergondza.dumpling.Util.pause; import static com.github.olivergondza.dumpling.Util.processTerminatedPrematurely; import static com.github.olivergondza.dumpling.model.ProcessThread.nameIs; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import javax.annotation.Nonnull; import com.github.olivergondza.dumpling.model.ModelObject; import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; import org.junit.rules.MethodRule; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; import com.github.olivergondza.dumpling.Util; import com.github.olivergondza.dumpling.model.StackTrace; import com.github.olivergondza.dumpling.model.ThreadLock; import com.github.olivergondza.dumpling.model.ThreadStatus; import com.github.olivergondza.dumpling.model.dump.ThreadDumpRuntime; import com.github.olivergondza.dumpling.model.dump.ThreadDumpThread; /** * Make sure threaddump generated by vendor of current JVM can be read by dumpling. * * Rule will start groovy script from <tt>src/test/resources/com/github/olivergondza/dumpling/factory/ThreadDumpFactoryVendorTest/METHOD_NAME.groovy</tt> * in separated process. SUT is picked up by <tt>sut.runtime()</tt> or * <tt>sut.thread("name")</tt>, the method will wait for process to be ready. * * This belongs to core module though it is convenient to use Dumpling CLI as groovy interpreter. * * @author ogondza */ public class ThreadDumpFactoryVendorTest { public @Rule Runner sut = new Runner(); @Test public void busyWaiting() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.RUNNABLE)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingToLock(), nullValue()); } @Test public void waiting() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.IN_OBJECT_WAIT)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingToLock(), nullValue()); assertThat(main.getWaitingOnLock().getClassName(), equalTo("java.lang.Object")); } @Test public void waitingTimed() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.IN_OBJECT_WAIT_TIMED)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingToLock(), nullValue()); assertThat(main.getWaitingOnLock().getClassName(), equalTo("java.lang.Object")); } @Test public void sleeping() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.SLEEPING)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingToLock(), nullValue()); assertThat( main.getStackTrace().getElement(0), equalTo(StackTrace.nativeElement("java.lang.Thread", "sleep")) ); } @Test public void monitorDeadlock() { ThreadDumpThread main = sut.thread("main"); ThreadDumpThread other = sut.thread("other"); assertThat(main.getStatus(), equalTo(ThreadStatus.BLOCKED)); assertThat(other.getStatus(), equalTo(ThreadStatus.BLOCKED)); assertThat(main.getWaitingToLock(), equalTo(only(other.getAcquiredMonitors()))); assertThat(other.getWaitingToLock(), equalTo(only(main.getAcquiredMonitors()))); assertThat(other.getAcquiredSynchronizers(), Matchers.<ThreadLock>empty()); assertThat(main.getAcquiredSynchronizers(), Matchers.<ThreadLock>empty()); assertThat(main.getBlockingThread(), equalTo(other)); assertThat(other.getBlockingThread(), equalTo(main)); assertThat(main.getBlockedThreads().onlyThread(), equalTo(other)); assertThat(other.getBlockedThreads().onlyThread(), equalTo(main)); } @Test public void synchronizerDeadlock() { ThreadDumpThread main = sut.thread("main"); ThreadDumpThread other = sut.thread("other"); assertThat(main.getStatus(), equalTo(ThreadStatus.PARKED)); assertThat(other.getStatus(), equalTo(ThreadStatus.PARKED)); assertThat(main.getWaitingOnLock(), equalTo(only(other.getAcquiredSynchronizers()))); assertThat(other.getWaitingOnLock(), equalTo(only(main.getAcquiredSynchronizers()))); assertThat(other.getAcquiredMonitors(), Matchers.<ThreadLock>empty()); assertThat(main.getAcquiredMonitors(), Matchers.<ThreadLock>empty()); } @Test public void reacquireMonitorAfterWait() { ThreadDumpThread reacquiring = sut.thread("reacquiring"); ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.SLEEPING)); assertThat(reacquiring.getStatus(), equalTo(ThreadStatus.BLOCKED)); assertThat(only(main.getAcquiredLocks()), equalTo(reacquiring.getWaitingToLock())); assertThat(reacquiring.getAcquiredLocks().size(), equalTo(2)); assertThat( reacquiring.getStackTrace().getElement(0), equalTo(StackTrace.nativeElement("java.lang.Object", "wait")) ); } @Test public void ignoreWaitLock() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.IN_OBJECT_WAIT)); assertThat(main.getWaitingToLock(), nullValue()); assertThat(only(main.getAcquiredLocks()).getClassName(), equalTo("java.lang.Integer")); assertTrue(main.getBlockedThreads().isEmpty()); assertThat(main.getBlockingThread(), nullValue()); assertThat(main.getWaitingOnLock().getClassName(), equalTo("java.lang.Double")); } @Test public void parked() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.PARKED)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingToLock(), nullValue()); assertThat(main.getWaitingOnLock(), nullValue()); assertThat( main.getStackTrace().getElement(0).getMethodName(), equalTo("park") ); } @Test public void parkedTimed() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.PARKED_TIMED)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingToLock(), nullValue()); assertThat(main.getWaitingOnLock(), nullValue()); assertThat( main.getStackTrace().getElement(0).getMethodName(), equalTo("park") ); } @Test public void parkedWithBlocker() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.PARKED)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingOnLock().getClassName(), equalTo("java.lang.Object")); assertThat( main.getStackTrace().getElement(0).getMethodName(), equalTo("park") ); } @Test public void parkedTimedWithBlocker() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getStatus(), equalTo(ThreadStatus.PARKED_TIMED)); assertThat(main.getAcquiredLocks(), Matchers.<ThreadLock>empty()); assertThat(main.getWaitingOnLock().getClassName(), equalTo("java.lang.Object")); assertThat( main.getStackTrace().getElement(0).getMethodName(), equalTo("park") ); } @Test public void threadNameWithQuotes() { ThreadDumpThread t = sut.thread("a\"thread\""); assertThat(t.getName(), equalTo("a\"thread\"")); } @Test public void threadNameWithLinebreak() { String name = "thread" + System.getProperty("line.separator") + "name"; ThreadDumpThread t = sut.thread(name); assertThat(t.getName(), equalTo(name)); } @Test public void multipleMonitorsOnSingleStackFrame() { ThreadDumpThread main = sut.thread("main"); assertThat(main.getAcquiredLocks(), equalTo(main.getAcquiredMonitors())); // Stack depth is not exposed, checking just the order ArrayList<ThreadLock> lockList = new ArrayList<ThreadLock>(main.getAcquiredLocks()); assertThat(lockList.get(0).getClassName(), equalTo("java.lang.Double")); assertThat(lockList.get(1).getClassName(), equalTo("java.lang.Integer")); assertThat(lockList.get(2).getClassName(), equalTo("java.lang.Object")); } private static final class Runner implements MethodRule { public static final ThreadDumpFactory THREAD_DUMP_FACTORY = new ThreadDumpFactory().failOnErrors(true); private final PidRuntimeFactory prf = new PidRuntimeFactory() { @Override protected ThreadDumpRuntime createRuntime(Process process) { threaddump = Util.asString(process.getInputStream()); return THREAD_DUMP_FACTORY.fromString(threaddump); } }; private Process process; private String processLine; private ThreadDumpRuntime runtime = null; private File pidFile = null; private File scriptFile = null; private String threaddump = null; @Override public Statement apply(final Statement base, final FrameworkMethod method, Object target) { try { String init = Util.asString(Util.resource( ThreadDumpFactoryVendorTest.class, "_init.groovy" )); String script = Util.asString(Util.resource( ThreadDumpFactoryVendorTest.class, method.getName() + ".groovy" )); run(init + script); } catch (Exception ex) { throw new AssertionError(ex); } return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable ex) { if (runtime != null) { System.err.printf("%s has failed with %s: %s%n", method.getName(), ex.getClass(), ex.getMessage()); System.err.println("Original runtime:"); System.err.println(threaddump); System.err.println("Parsed runtime:"); runtime.toString(System.err, ModelObject.Mode.MACHINE); } throw ex; } finally { if (pidFile != null && pidFile.exists()) pidFile.delete(); if (scriptFile != null && scriptFile.exists()) scriptFile.delete(); if (process != null) process.destroy(); } } }; } private void run(String script) throws IOException { pidFile = File.createTempFile("dumpling", getClass().getName() + ".pid"); pidFile.deleteOnExit(); scriptFile = File.createTempFile("dumpling", getClass().getName() + ".script"); scriptFile.deleteOnExit(); PrintWriter writer = new PrintWriter(scriptFile); try { writer.print(script); } finally { writer.close(); } ProcessBuilder pb = Util.processBuilder().command( System.getProperty("java.home") + "/bin/java", "-cp", System.getProperty("surefire.real.class.path"), // Inherit from surefire process "com.github.olivergondza.dumpling.cli.Main", "groovy", "--script", scriptFile.getAbsolutePath(), pidFile.getAbsolutePath() ); processLine = pb.command().toString(); process = pb.start(); } public ThreadDumpThread thread(@Nonnull String name) { return runtime().getThreads().where(nameIs(name)).onlyThread(); } public ThreadDumpRuntime runtime() { if (runtime != null) return runtime; try { int exit = process.exitValue(); throw processTerminatedPrematurely(process, exit, processLine); } catch (IllegalThreadStateException ex) { // Still running as expected try { return runtime = waitForInitialized(); } catch (IOException e) { String message = String.format( "Unable to get Runtime from %s, Exit code: %d%nSTDOUT: %s%nSTDERR: %s", processLine, getExitIfDone(), currentProcessOut(process.getInputStream()), currentProcessOut(process.getErrorStream()) ); throw new Error(message, e); } catch (InterruptedException e) { throw new Error("Interrupted: " + processLine, e); } } } private Integer getExitIfDone() { try { return process.exitValue(); } catch (IllegalThreadStateException ex) { return null; } } private ThreadDumpRuntime waitForInitialized() throws IOException, InterruptedException { int pid = getPid(); ThreadDumpRuntime runtime = prf.fromProcess(pid); for (int i = 0; i < 10; i++) { pause(500); ThreadDumpThread main = runtime.getThreads().where(nameIs("main")).onlyThread(); for (StackTraceElement elem: main.getStackTrace().getElements()) { if ("dumpling-script".equals(elem.getClassName()) && "run".equals(elem.getMethodName())) { return runtime; } } runtime = prf.fromProcess(pid); } throw new IOException("Process under test not initialized in time: " + runtime.getThreads()); } private int getPid() throws IOException { if (pidFile == null) throw new IOException("pidfile not initialized"); IOException ex = null; // Wait for pidfile to be written for (int i = 0; i < 5; i++) { if (pidFile.exists()) { try { return Integer.parseInt(Util.fileContents(pidFile).trim()); } catch (NumberFormatException e) { ex = new IOException("unable to read PID from " + pidFile, e); } } pause(1000); } if (ex != null) throw ex; throw new IOException("SUT process not running. pidfile: " + pidFile); } } }