/*
 * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.graalvm.visualvm.lib.jfluid.results.cpu;

import java.lang.Thread.State;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.openmbean.CompositeData;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.graalvm.visualvm.lib.jfluid.results.CCTNode;
import static org.junit.Assert.*;


/**
 *
 * @author Jaroslav Bachorik, Tomas Hurka
 */
public class StackTraceSnapshotBuilderTest {

    private StackTraceSnapshotBuilder instance;

    private final StackTraceElement[] elements0 = new StackTraceElement[] {
        new StackTraceElement("test.Class1", "method3", "Class1.java", 30),
        new StackTraceElement("test.Class1", "method2", "Class1.java", 20),
        new StackTraceElement("test.Class1", "method1", "Class1.java", 10)
    };

    private final StackTraceElement[] elementsDif = new StackTraceElement[] {
        new StackTraceElement("test.Class1", "method3", "Class1.java", 40),
        new StackTraceElement("test.Class1", "method4", "Class1.java", 30),
        new StackTraceElement("test.Class1", "method2", "Class1.java", 20),
        new StackTraceElement("test.Class1", "method1", "Class1.java", 10)
    };

    private final StackTraceElement[] elementsPlus = new StackTraceElement[] {
        new StackTraceElement("test.Class1", "method4", "Class1.java", 40),
        new StackTraceElement("test.Class1", "method3", "Class1.java", 30),
        new StackTraceElement("test.Class1", "method2", "Class1.java", 20),
        new StackTraceElement("test.Class1", "method1", "Class1.java", 10)
    };

    private final StackTraceElement[] elementsMinus = new StackTraceElement[] {
        new StackTraceElement("test.Class1", "method2", "Class1.java", 20),
        new StackTraceElement("test.Class1", "method1", "Class1.java", 10)
    };

    private final StackTraceElement[] elementsDup = new StackTraceElement[] {
        new StackTraceElement("test.Class1", "method3", "Class1.java", 30),
        new StackTraceElement("test.Class1", "method2", "Class1.java", 21),
        new StackTraceElement("test.Class1", "method1", "Class1.java", 10)
    };

    private Thread thread0;
    private Thread thread1;
    private Thread thread2;

    private java.lang.management.ThreadInfo[] stack0;
    private java.lang.management.ThreadInfo[] stackPlus;
    private java.lang.management.ThreadInfo[] stackMinus;
    private java.lang.management.ThreadInfo[] stackDif;
    private java.lang.management.ThreadInfo[] stackDup;
    

    public StackTraceSnapshotBuilderTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
    }

    @Before
    public void setUp() {
        instance = new StackTraceSnapshotBuilder();

        thread0 = new Thread("Test thread 0");
        thread1 = new Thread("Test thread 1");
        thread2 = new Thread("Test thread 2");
        
        stack0 = new java.lang.management.ThreadInfo[] {
                createThreadInfo(thread0, elements0),
                createThreadInfo(thread1, elements0)
        };

        stackPlus = new java.lang.management.ThreadInfo[] {
                createThreadInfo(thread0, elementsPlus),
                createThreadInfo(thread1, elements0),
                createThreadInfo(thread2, elements0)
        };

        stackMinus = new java.lang.management.ThreadInfo[] {
                createThreadInfo(thread0, elementsMinus)
        };

        stackDif = new java.lang.management.ThreadInfo[] {
                createThreadInfo(thread0, elementsDif)
        };

        stackDup = new java.lang.management.ThreadInfo[] {
                createThreadInfo(thread0, elementsDup),
                createThreadInfo(thread1, elements0)
        };

    }

    @After
    public void tearDown() {
        instance = null;
    }

    /**
     * Test of createSnapshot method, of class StackTraceSnapshotBuilder.
     * Empty data
     */
    @Test
    public void testCreateSnapshotEmpty() {
        System.out.println("create snapshot : empty");

        try {
            instance.createSnapshot(System.currentTimeMillis());
            fail("Attempt to create an empty snapshot should throw NoDataAvailableException");
        } catch (CPUResultsSnapshot.NoDataAvailableException ex) {
        }
    }

    @Test
    public void testCreateSnapshotOneSample() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : one sample");

        instance.addStacktrace(stack0, 0);
        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
    }

    @Test
    public void testCreateSnapshotNoChanges() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : two samples");

        instance.addStacktrace(stack0, 0);
        instance.addStacktrace(stack0, 500000);

        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
    }

    @Test
    public void testCreateSnapshotMinus() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : minus");

        instance.addStacktrace(stack0, 0);
        instance.addStacktrace(stackMinus, 500000);

        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
    }

    @Test
    public void testCreateSnapshotPlus() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : plus");

        instance.addStacktrace(stack0, 0);
        instance.addStacktrace(stackPlus, 500000);

        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
    }

    @Test
    public void testCreateSnapshotPlusMinus() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : plus->minus");

        instance.addStacktrace(stack0, 0);
        instance.addStacktrace(stackPlus, 500000);
        instance.addStacktrace(stackMinus, 1000000);

        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
    }

    @Test
    public void testCreateSnapshotMinusPlus() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : minus->plus");

        instance.addStacktrace(stack0, 0);
        instance.addStacktrace(stackMinus, 500000);
        instance.addStacktrace(stackPlus, 1000000);

        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
    }

    @Test
    public void testCreateSnapshotDup() throws CPUResultsSnapshot.NoDataAvailableException {
        System.out.println("create snapshot : dup");

        instance.addStacktrace(stack0, 0);
        instance.addStacktrace(stackDup, 500000);

        CPUResultsSnapshot snapshot = instance.createSnapshot(System.currentTimeMillis());
        assertTrue(snapshot.collectingTwoTimeStamps);
        assertEquals(instance.methodInfos.size(), snapshot.nInstrMethods);
        CPUCCTContainer container = snapshot.getContainerForThread((int) stack0[0].getThreadId(), CPUResultsSnapshot.METHOD_LEVEL_VIEW);
        assertEquals(container.getThreadName(),thread0.getName());
        PrestimeCPUCCTNode root = container.getRootNode();
        assertEquals(1, root.getNCalls());
        CCTNode[] childrens = root.getChildren();
        assertEquals(1, childrens.length);
        PrestimeCPUCCTNode ch = (PrestimeCPUCCTNode) childrens[0];
        assertEquals("test.Class1.method1()", ch.getNodeName());
        assertEquals(1, ch.getNCalls());
        CCTNode[] childrens1 = ch.getChildren();
        assertEquals(2, childrens1.length);
        PrestimeCPUCCTNode ch1 = (PrestimeCPUCCTNode) childrens1[0];
        if (ch1.isSelfTimeNode()) {
            ch1 = (PrestimeCPUCCTNode) childrens1[1];
        }
        assertEquals("test.Class1.method2()", ch1.getNodeName());
        assertEquals(1, ch1.getNCalls());
        CCTNode[] childrens2 = ch1.getChildren();
        assertEquals(2, childrens2.length);
        PrestimeCPUCCTNode ch2 = (PrestimeCPUCCTNode) childrens2[0];
        if (ch2.isSelfTimeNode()) {
            ch2 = (PrestimeCPUCCTNode) childrens2[1];
        }
        assertEquals("test.Class1.method3()", ch2.getNodeName());
        assertEquals(2, ch2.getNCalls());
    }

    @Test
    public void testAddStacktrace() {
        System.out.println("add stacktrace");

        instance.addStacktrace(stack0, 0);
        assertTrue(instance.methodInfos.size()-1  == elements0.length);
        assertTrue(instance.threadIds.size() == stack0.length);
        assertTrue(instance.threadNames.size() == stack0.length);
        assertFalse(-1L == instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceDuplicate() {
        System.out.println("add stacktrace : duplicate");

        long stamp = 0;
        instance.addStacktrace(stack0, stamp);

        int miSize = instance.methodInfos.size()-1;
        int tIdSize = instance.threadIds.size();
        long timestamp = instance.currentDumpTimeStamp;

        instance.addStacktrace(stack0, stamp);
        assertTrue(instance.stackTraceCount == 1);

    }

    @Test
    public void testAddStacktracePlus() {
        System.out.println("add stacktrace : plus");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;

        instance.addStacktrace(stackPlus, timestamp);

        assertEquals(Math.max(stack0.length, stackPlus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsPlus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktracePlusWaiting() {
        System.out.println("add stacktrace : plus/waiting");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackPlus[0],Thread.State.WAITING);

        instance.addStacktrace(stackPlus, timestamp);

        assertEquals(Math.max(stack0.length, stackPlus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsPlus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktracePlusWaitingThread() {
        System.out.println("add stacktrace : plus/waiting; additional thread");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackPlus[2],Thread.State.WAITING);

        instance.addStacktrace(stackPlus, timestamp);

        assertEquals(Math.max(stack0.length, stackPlus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsPlus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceWaitingPlus() {
        System.out.println("add stacktrace : waiting/plus");

        setState(stack0[0],Thread.State.WAITING);
        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackPlus[2],Thread.State.RUNNABLE);

        instance.addStacktrace(stackPlus, timestamp);

        assertEquals(Math.max(stack0.length, stackPlus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsPlus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceMinus() {
        System.out.println("add stacktrace : minus");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;

        instance.addStacktrace(stackMinus, timestamp);

        assertEquals(Math.max(stack0.length, stackMinus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsMinus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceMinusWaiting() {
        System.out.println("add stacktrace : minus/waiting");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackMinus[0], Thread.State.WAITING);
        instance.addStacktrace(stackMinus, timestamp);

        assertEquals(Math.max(stack0.length, stackMinus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsMinus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceMinusWaitingThread() {
        System.out.println("add stacktrace : minus/waiting; additional thread");

        setState(stack0[1], Thread.State.WAITING);
        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackMinus[0], Thread.State.WAITING);
        instance.addStacktrace(stackMinus, timestamp);

        assertEquals(Math.max(stack0.length, stackMinus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsMinus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceWaitingMinus() {
        System.out.println("add stacktrace : waiting/minus");

        setState(stack0[0], Thread.State.WAITING);
        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackMinus[0], Thread.State.RUNNABLE);
        instance.addStacktrace(stackMinus, timestamp);

        assertEquals(Math.max(stack0.length, stackMinus.length), instance.threadIds.size());
        assertEquals(Math.max(elements0.length, elementsMinus.length), instance.methodInfos.size()-1);
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceDif() {
        System.out.println("add stacktrace : diff");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;

        instance.addStacktrace(stackDif, timestamp);

        assertEquals(Math.max(stack0.length, stackDif.length), instance.threadIds.size());
        for(StackTraceElement element : elements0) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        for(StackTraceElement element : elementsDif) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceDifWaiting() {
        System.out.println("add stacktrace : diff/waiting");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackDif[0], Thread.State.WAITING);
        
        instance.addStacktrace(stackDif, timestamp);

        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());

        assertEquals(Math.max(stack0.length, stackDif.length), instance.threadIds.size());
        for(StackTraceElement element : elements0) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        for(StackTraceElement element : elementsDif) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceDifWaitingBlocked() {
        System.out.println("add stacktrace : diff/waiting/blocked");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackDif[0], Thread.State.WAITING);

        instance.addStacktrace(stackDif, timestamp);

        setState(stack0[0], Thread.State.BLOCKED);

        timestamp += 500000;

        instance.addStacktrace(stack0, timestamp);

        assertEquals(Thread.State.BLOCKED, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());

        assertEquals(Math.max(stack0.length, stackDif.length), instance.threadIds.size());
        for(StackTraceElement element : elements0) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        for(StackTraceElement element : elementsDif) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceDifBlockedWaiting() {
        System.out.println("add stacktrace : diff/blocked/waiting");

        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;
        setState(stackDif[0], Thread.State.BLOCKED);

        instance.addStacktrace(stackDif, timestamp);

        setState(stack0[0], Thread.State.WAITING);

        timestamp += 500000;

        instance.addStacktrace(stack0, timestamp);

        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());

        assertEquals(Math.max(stack0.length, stackDif.length), instance.threadIds.size());
        for(StackTraceElement element : elements0) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        for(StackTraceElement element : elementsDif) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceWaitingDif() {
        System.out.println("add stacktrace : waiting/diff");

        setState(stack0[0], Thread.State.WAITING);
        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;


        instance.addStacktrace(stackDif, timestamp);

        assertEquals(Math.max(stack0.length, stackDif.length), instance.threadIds.size());
        for(StackTraceElement element : elements0) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        for(StackTraceElement element : elementsDif) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStacktraceWaitingDifRunnable() {
        System.out.println("add stacktrace : waiting/diff/runnable");

        setState(stack0[0], Thread.State.WAITING);
        instance.addStacktrace(stack0, 0);

        long timestamp = 500000;

        setState(stackDif[0], Thread.State.RUNNABLE);
        instance.addStacktrace(stackDif, timestamp);

        assertEquals(Math.max(stack0.length, stackDif.length), instance.threadIds.size());
        for(StackTraceElement element : elements0) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        for(StackTraceElement element : elementsDif) {
            if (!instance.methodInfos.contains(new StackTraceSnapshotBuilder.MethodInfo(element))) {
                fail();
            }
        }
        assertEquals(timestamp, instance.currentDumpTimeStamp);
    }

    @Test
    public void testAddStackTraceNew() {
        System.out.println("add stacktrace : new");

        setState(stack0[0], Thread.State.NEW);

        try {
            instance.addStacktrace(stack0, 500000);
            assertFalse(instance.threadNames.contains(stack0[0].getThreadName()));
        } catch (IllegalStateException ex) {}
    }

    @Test
    public void testAddStackTraceWasTerminated() {
        System.out.println("add stacktrace : terminated->runnable");

        setState(stack0[0], Thread.State.TERMINATED);

        try {
            instance.addStacktrace(stack0, 0);
            setState(stack0[0], Thread.State.RUNNABLE);
            instance.addStacktrace(stack0, 500000);
            fail();
        } catch (IllegalStateException ex) {}
    }


    @Test
    public void testAddStackTraceRunnable() {
        System.out.println("add stacktrace : runnable");

        setState(stack0[0], Thread.State.RUNNABLE);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.RUNNABLE, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceWaiting() {
        System.out.println("add stacktrace : waiting");

        setState(stack0[0], Thread.State.WAITING);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceTimedWaiting() {
        System.out.println("add stacktrace : timed waiting");

        setState(stack0[0], Thread.State.TIMED_WAITING);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.TIMED_WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceBlocked() {
        System.out.println("add stacktrace : blocked");

        setState(stack0[0], Thread.State.BLOCKED);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.BLOCKED, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceTerminated() {
        System.out.println("add stacktrace : terminated");

        setState(stack0[0], Thread.State.TERMINATED);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.TERMINATED, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceWaitRun() {
        System.out.println("add stacktrace : wait->run");

        instance.addStacktrace(stack0, 0);
        setState(stack0[0], Thread.State.WAITING);
        
        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());

        setState(stack0[0], Thread.State.RUNNABLE);
        instance.addStacktrace(stack0, 1000000);

        assertEquals(1000000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.RUNNABLE, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceWaitWait() {
        System.out.println("add stacktrace : wait->wait");

        instance.addStacktrace(stack0, 0);
        setState(stack0[0], Thread.State.WAITING);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
        instance.addStacktrace(stack0, 1000000);

        assertEquals(1000000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }

    @Test
    public void testAddStackTraceWaitBlocked() {
        System.out.println("add stacktrace : wait->blocked");

        instance.addStacktrace(stack0, 0);
        setState(stack0[0], Thread.State.WAITING);

        instance.addStacktrace(stack0, 500000);

        assertEquals(500000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.WAITING, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
        setState(stack0[0], Thread.State.BLOCKED);
        instance.addStacktrace(stack0, 1000000);

        assertEquals(1000000, instance.currentDumpTimeStamp);
        assertEquals(Thread.State.BLOCKED, instance.lastStackTrace.get().get(thread0.getId()).getThreadState());
    }



    @Test
    public void testReset() {
        System.out.println("reset");
        ThreadMXBean tbean = ManagementFactory.getThreadMXBean();
        instance.addStacktrace(tbean.getThreadInfo(tbean.getAllThreadIds(), Integer.MAX_VALUE), System.nanoTime());
        instance.addStacktrace(tbean.getThreadInfo(tbean.getAllThreadIds(), Integer.MAX_VALUE), System.nanoTime());

        instance.reset();
        assertTrue(instance.methodInfos.size()-1 == 0);
        assertTrue(instance.threadIds.isEmpty());
        assertTrue(instance.threadNames.isEmpty());
        assertEquals(-1L, instance.currentDumpTimeStamp);
        //assertEquals(-1L, instance.firstDumpTimeStamp);
        assertEquals(0, instance.stackTraceCount);

        try {
            instance.createSnapshot(System.currentTimeMillis());
            fail();
        } catch (CPUResultsSnapshot.NoDataAvailableException ex) {
        }
    }

    @Test
    public void testIgnoredThreadName() {
        System.out.println("ignored thread name");

        String ignoredThread = "Thread 0";
        instance.setIgnoredThreads(Collections.singleton(ignoredThread));

        instance.addStacktrace(stack0, 0);
        assertFalse(instance.threadNames.contains(ignoredThread));
    }

    private java.lang.management.ThreadInfo createThreadInfo(Thread t, StackTraceElement[] stack) {
        try {
            Constructor tinfoConstructor = java.lang.management.ThreadInfo.class.getDeclaredConstructor(
                    Thread.class,Integer.TYPE,Object.class,Thread.class,Long.TYPE,Long.TYPE,
                    Long.TYPE,Long.TYPE,StackTraceElement[].class);
            tinfoConstructor.setAccessible(true);
            ThreadInfo tinfo =  (ThreadInfo) tinfoConstructor.newInstance(t,0,null,null,0,0,0,0,stack);
            setState(tinfo,State.RUNNABLE);
            return tinfo;
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
        }
        return null;
    }

    private void setState(java.lang.management.ThreadInfo tinfo, State s) {
        try {
            Field tstateField = tinfo.getClass().getDeclaredField("threadState");
            tstateField.setAccessible(true);
            tstateField.set(tinfo, s);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchFieldException ex) {
            Logger.getLogger(StackTraceSnapshotBuilderTest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
