public static final String BASIC_TYPE_INVOKE_TEST = "BasicTypeInvokeTest"; public static final String COMPLEX_TEST = "ComplexTest"; public static final String CONSTRUCTOR_INVOKE_TEST = "ConstructorInvokeTest"; public static final String DIFFERENT_STATES_TEST = "MultipleSuspendStatesTest"; public static final String DOUBLE_RETURN_INVOKE_TEST = "DoubleReturnInvokeTest"; public static final String EMPTY_CONTINUATION_POINT_INVOKE_TEST = "EmptyContinuationPointInvokeTest"; public static final String EXCEPTION_SUSPEND_TEST = "ExceptionSuspendTest"; public static final String EXCEPTION_THEN_CONTINUE_INVOKE_TEST = "ExceptionThenContinueInvokeTest"; public static final String EXCEPTION_THROW_TEST = "ExceptionThrowTest"; public static final String INHERITANCE_INVOKE_TEST = "InheritanceInvokeTest"; public static final String INTERFACE_INVOKE_TEST = "InterfaceInvokeTest"; public static final String ISSUE_84_TEST = "Issue84Test"; public static final String JSR_EXCEPTION_SUSPEND_TEST = "JsrExceptionSuspendTest"; public static final String LAMBDA_INVOKE_TEST = "LambdaInvokeTest"; public static final String LONG_RETURN_INVOKE_TEST = "LongReturnInvokeTest"; public static final String MONITOR_INVOKE_TEST = "MonitorInvokeTest"; public static final String NORMAL_INVOKE_TEST = "NormalInvokeTest"; public static final String NULL_TYPE_IN_LOCAL_VARIABLE_TABLE_INVOKE_TEST = "NullTypeInLocalVariableTableInvokeTest"; public static final String NULL_TYPE_IN_OPERAND_STACK_INVOKE_TEST = "NullTypeInOperandStackInvokeTest"; public static final String PEERNETIC_FAILURE_TEST = "PeerneticFailureTest"; public static final String RECURSIVE_INVOKE_TEST = "RecursiveInvokeTest"; public static final String RETURN_INVOKE_TEST = "ReturnInvokeTest"; public static final String SANITY_TEST = "SanityTest"; public static final String STATIC_INVOKE_TEST = "StaticInvokeTest"; public static final String UNINITIALIZED_VARIABLE_INVOKE_TEST = "UninitializedVariableInvokeTest"; public enum MarkerType {
    /**
     * Generate no instructions.
     */
    NONE,
    /**
     * Generate instructions to load text as string constant and immediately pop it off the stack.
     */
    CONSTANT,
    /**
     * Generate instructions to print text to standard out.
     */
    STDOUT
}     public static List<File> getClasspath() {
        String pathSeparator = System.getProperty("path.separator");
        Validate.validState(pathSeparator != null);
        
        String classpath = System.getProperty("java.class.path");
        Validate.validState(classpath != null);
        List<File> classPathFiles = Arrays
                .stream(classpath.split(Pattern.quote(pathSeparator)))
                .map(x -> new File(x))
                .filter(x -> x.exists())
                .collect(Collectors.toList());

        // Commented out block is no longer valid as of Java9+
//        String bootClasspath = System.getProperty("sun.boot.class.path");
//        Validate.validState(bootClasspath != null);
//        List<File> bootClassPathFiles = Arrays
//                .stream(bootClasspath.split(Pattern.quote(pathSeparator)))
//                .map(x -> new File(x))
//                .filter(x -> x.exists())
//                .collect(Collectors.toList());

        ArrayList<File> ret = new ArrayList<>();
        ret.addAll(classPathFiles);
//        ret.addAll(bootClassPathFiles);
        
        return ret;
    } public static URLClassLoader loadClassesInZipResourceAndInstrument(String path) throws IOException {
    return loadClassesInZipResourceAndInstrument(path, new InstrumentationSettings(MarkerType.CONSTANT, false, true));
} public static Map<String, byte[]> readZipFromResource(String path) throws IOException {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL url = cl.getResource(path);
    Validate.isTrue(url != null);
        
    Map<String, byte[]> ret = new LinkedHashMap<>();
        
    try (InputStream is = url.openStream();
            ZipArchiveInputStream zais = new ZipArchiveInputStream(is)) {
        ZipArchiveEntry entry;
        while ((entry = zais.getNextZipEntry()) != null) {
            ret.put(entry.getName(), IOUtils.toByteArray(zais));
        }
    }
        
    return ret;
} public final class Continuation implements Serializable {
    private static final long serialVersionUID = 6L;
    
    /**
     * Do not use -- for internal use only.
     */
    public static final int MODE_NORMAL = 0;
    /**
     * Do not use -- for internal use only.
     */
    public static final int MODE_SAVING = 1;
    /**
     * Do not use -- for internal use only.
     */
    public static final int MODE_LOADING = 2;
    
    private MethodState firstPointer;
    
    private MethodState nextLoadPointer;
    private MethodState nextUnloadPointer;

    private MethodState firstCutpointPointer;
    
    private int mode = MODE_NORMAL;
    private Object context;

    // How should method states be handled? Imagine that we started off restoring the following call chain...
    // runA() <-- firstPointer[0]
    //  runB() <-- firstPointer[1]
    //   runC() <-- firstPointer[2]
    //    runD() <-- firstPointer[3]
    //     runE() <-- firstPointer[4]
    //
    // After the restore finishes, the following happens...
    // 1. runE() finishes running and returns
    // 2. then, runD() finishes running and returns
    // 3. then, runC() decided to call runX()
    // 4. then, runX() decided to call runY()
    // 5. then, runY() decides to call Continuation.suspend()
    //
    // So we left the bottom 2 existing method frames and entered in to 2 new method frames, and the callstack would now look like this...
    // runA()
    //  runB()
    //   runC()
    //    runX()
    //     runY()
    //
    //
    // Things happen in phases.
    //
    // PHASE1
    // ------
    // The first phase is loading. We call loadNextMethodState() to get the method state for the next method in the call chain. Pretty
    // straight forward.. runA -> runB -> runC -> runD -> runE.
    //
    //
    // PHASE2
    // ------
    // Once things are loaded... as we leave the restored continuation points in runE() and runD(), we call unloadCurrentMethodState() to
    // mark these method states as invalid. So after runE()+runD() return, we should be pointing to runC(). Everything after it is no longer
    // valid...
    //
    // runA() <-- firstPointer[0]
    //  runB() <-- firstPointer[1]
    //   runC() <-- firstPointer[2] / nextUnloadPointer
    //    runD() <-- firstPointer[3] (NO LONGER CONSIDERED VALID, BUT KEPT ANYWAS -- EXPLAINED FURTHER ON)
    //     runE() <-- firstPointer[4] (NO LONGER CONSIDERED VALID, BUT KEPT ANYWAS -- EXPLAINED FURTHER ON)
    //
    //
    // PHASE3
    // ------
    // As runX() and runY() suspend, they put their own method states as part of a NEW linked list: firstCutpointPointer. They do this by
    // calling pushNewMethodState().
    //   !!!WE ONLY CREATE METHOD STATES AND ADD THEM TO THIS NEW LIST AFTER THEY'RE SUSPEND! THIS IS REALLY IMPORTANT TO REMEMBER!!!
    //
    // runX() <-- firstCutpointPointer[0]
    //  runY()  <-- firstCutpointPointer[1]
    //
    //
    // Then, once we successfully make our way up and out of the callstack, we merge these two lists together...
    // runA() <-- savedMethodState[0]
    //  runB() <-- savedMethodState[1]
    //   runC() <-- savedMethodState[2] / nextUnloadPointer
    //    runX() <-- savedMethodState[3] / firstCutpointPointer[0]
    //     runY() <-- savedMethodState[4] / firstCutpointPointer[1]
    //
    //
    // Why do we use a separate list for new invocations (postCutPointMethodState)? Because if there's an uncaught exception, we
    // still want to keep the old one exactly the way it was. That's why technically we kept runD() and runE()s method states and just
    // shift around the pointers. It's only after we're successfuly that we "commit the changes".
    //
    //
    // ADDITIONAL NOTES
    // ----------------
    // These phases should always be done in order. If you don't do them in order (e.g. if you try to unloadCurrentMethodState() after
    // you've called pushNewMethodState()), things will likely not act right.
    
    Continuation() {
        // do nothing
    }
    
    /**
     * Do not use -- for internal use only.
     * @return n/a
     */
    public int getMode() {
        return mode;
    }

    /**
     * Do not use -- for internal use only.
     * @param mode n/a
     */
    public void setMode(int mode) {
        // if (!(mode == MODE_NORMAL || mode == MODE_SAVING || mode == MODE_LOADING)) {
        //    throw new IllegalArgumentException();
        // }
        this.mode = mode;
    }







    /**
     * Do not use -- for internal use only.
     * @return n/a
     */
    public MethodState loadNextMethodState() {
        MethodState ret = nextLoadPointer;
        nextLoadPointer = nextLoadPointer.getNext();
        
        // We've reached the end of load list, so set up the 'unload' list that gets called when a method continues execution from the point
        // where it's paused it.
        if (nextLoadPointer == null) {
            nextUnloadPointer = ret;
        }
        
        return ret;
    }

    /**
     * Do not use -- for internal use only.
     */
    public void unloadCurrentMethodState() {
        nextUnloadPointer = nextUnloadPointer.getPrevious();
    }

    /**
     * Do not use -- for internal use only.
     * @param methodState n/a
     */
    public void unloadMethodStateToBefore(MethodState methodState) {
        // REMEMBER: methodState being passed in must be in the linkedlist starting from firstPointer
        
        //if (methodState == null) {
        //    throw new NullPointerException();
        //}
        nextUnloadPointer = methodState.getPrevious();
    }

    /**
     * Do not use -- for internal use only.
     * @param methodState n/a
     */
    public void pushNewMethodState(MethodState methodState) {
        //if (methodState == null) {
        //    throw new NullPointerException();
        //}

        methodState.setNext(firstCutpointPointer);
        firstCutpointPointer = methodState;
    }

    /**
     * Do not use -- for internal use only.
     */
    public void reset() {
        firstPointer = null;
        nextLoadPointer = null;
        nextUnloadPointer = null;
        firstCutpointPointer = null;
        mode = MODE_NORMAL;
    }

    /**
     * Do not use -- for internal use only.
     */
    public void successExecutionCycle() {
        // FOR A PRIMER ON WHAT WE'RE DOING HERE, SEE LARGE BLOCK OF COMMENT AT BEGINNING OF CLASS

        if (nextUnloadPointer != null) {
            nextUnloadPointer.setNext(firstCutpointPointer);
        } else {
            firstPointer = firstCutpointPointer;
        }
        
        nextLoadPointer = firstPointer;     // reset next load pointer so we load from the beginning
        nextUnloadPointer = null;           // reset unload pointer
        firstCutpointPointer = null;        // reset cutpoint list
    }

    /**
     * Do not use -- for internal use only.
     */
    public void failedExecutionCycle() {
        // FOR A PRIMER ON WHAT WE'RE DOING HERE, SEE LARGE BLOCK OF COMMENT AT BEGINNING OF CLASS
        
        nextLoadPointer = firstPointer;     // reset next load pointer so we load from the beginning
        nextUnloadPointer = null;           // reset unload pointer
        firstCutpointPointer = null;        // reset cutpoint list
    }

    
    
    
    
    
    
    /**
     * Call to suspend/yield execution.
     * @throws UnsupportedOperationException if the caller has not been instrumented
     */
    public void suspend() {
        throw new UnsupportedOperationException("Caller not instrumented");
    }

    /**
     * Get the context.
     * @return context
     */
    public Object getContext() {
        return context;
    }

    /**
     * Set the context.
     * @param context context
     */
    public void setContext(Object context) {
        this.context = context;
    }
    
    
    
    
    
    
    
    
    /**
     * Do not use -- for internal use only. For testing.
     * @param idx n/a
     * @return n/a
     */
    public MethodState getSaved(int idx) {
        if (idx < 0) {
            throw new IllegalArgumentException();
        }

        MethodState state = firstPointer;
        for (int i = 0; i < idx; i++) {
            state = state.getNext();
            if (state == null) {
               throw new IllegalArgumentException();
            }
        }
        return state;
    }

    /**
     * Do not use -- for internal use only. For testing.
     * @return n/a
     */
    public int getSize() {
        int ret = 0;
        MethodState state = firstPointer;
        while (state != null) {
            ret++;
            state = state.getNext();
        }
        return ret;
    }
} public interface Coroutine extends Serializable {
    /**
     * Entry point of coroutine.
     * @param continuation used to suspend/yield the coroutine
     * @throws Exception if a problem occurs
     */
    void run(Continuation continuation) throws Exception;
} public final class CoroutineRunner implements Serializable {
    private static final long serialVersionUID = 6L;
    
    private Coroutine coroutine;
    private Continuation continuation;

    /**
     * Constructs a {@link CoroutineRunner} object.
     * @param coroutine coroutine to run
     * @throws NullPointerException if any argument is {@code null}
     */
    public CoroutineRunner(Coroutine coroutine) {
        if (coroutine == null) {
            throw new NullPointerException();
        }
        this.coroutine = coroutine;
        this.continuation = new Continuation();
    }
    
    CoroutineRunner(Coroutine coroutine, Continuation continuation) {
        if (coroutine == null || continuation == null) {
            throw new NullPointerException();
        }
        this.coroutine = coroutine;
        this.continuation = continuation;
    }

    /**
     * Starts/resumes execution of this coroutine. If the coroutine being executed reaches a suspension point (meaning that the method calls
     * {@link Continuation#suspend() }), this method will return {@code true}. If the coroutine has finished executing, this method will
     * return {@code false}.
     * <p>
     * Calling this method again after the coroutine has finished executing will restart the coroutine.
     * @return {@code false} if execution has completed (the method has return), {@code true} if execution was suspended.
     * @throws CoroutineException an exception occurred during execution of this coroutine, the saved execution stack and object state may
     * be out of sync at this point (meaning that unless you know what you're doing, you should not call {@link CoroutineRunner#execute() }
     * again)
     */
    public boolean execute() {
        try {
            coroutine.run(continuation);
            continuation.successExecutionCycle();
        } catch (Exception e) {
            continuation.failedExecutionCycle();
            throw new CoroutineException("Exception thrown during execution", e);
        }
        
        // if mode was not set to SAVING after return, it means the method finished executing
        if (continuation.getMode() != Continuation.MODE_SAVING) {
            continuation.reset(); // clear methodstates + set to normal
            return false;
        } else {
            continuation.setMode(Continuation.MODE_LOADING); // set to loading for next invokation
            return true;
        }
    }

    /**
     * Get the context. Accessible via the {@link Continuation} object that gets used by this coroutine.
     * @return context context
     */
    public Object getContext() {
        return continuation.getContext();
    }

    /**
     * Set the context. Accessible via the {@link Continuation} object that gets used by this coroutine.
     * @param context context
     */
    public void setContext(Object context) {
        continuation.setContext(context);
    }

    /**
     * Get the coroutine assigned to this runner.
     * @return coroutine assigned to this runner
     */
    public Coroutine getCoroutine() {
        return coroutine;
    }

    Continuation getContinuation() {
        return continuation;
    }
} public final class MethodState implements Serializable {
    private static final long serialVersionUID = 6L;

    private final String className;
    private final int methodId;
    private final int continuationPoint;
    private final Object[] data;
    private final LockState lockState;

    private MethodState next;
    private MethodState previous;

    /**
     * Do not use -- for internal use only.
     * <p>
     * Constructs a {@link MethodState} object.
     * @param className name of owner (class) for method at which state was saved
     * @param methodId identifier for method at which state was saved
     * @param continuationPoint point in the method at which state was saved (does not refer to offset, just an id that's generated by the
     * instrumenter to mark that point)
     * @param data locals and operand stack at the point which state was saved
     * @param lockState monitors entered at the point which state was saved (may be {@code null})
     */
    public MethodState(String className, int methodId, int continuationPoint, Object[] data, LockState lockState) {
        if (continuationPoint < 0) {
            throw new IllegalArgumentException();
        }
        if (data == null) {
            throw new NullPointerException();
        }
        this.className = className;
        this.methodId = methodId;
        this.continuationPoint = continuationPoint;
        this.data = data;
        this.lockState = lockState;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get name of owner (class) for method at which state was saved
     * @return name of owner (class) for method at which state was saved
     */
    public String getClassName() {
        return className;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get identifier of method for which state was saved
     * @return ID of method for which state was saved
     */
    public int getMethodId() {
        return methodId;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get the point in the code at which state was saved (does not refer to offset, just an id that's generated by the
     * instrumenter to mark that point)
     * @return point in the code at which state was saved
     */
    public int getContinuationPoint() {
        return continuationPoint;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get locals and operand stack at the point which state was saved.
     * @return locals and operand stack at the point which state was saved
     */
    public Object[] getData() {
        return data;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get the monitors entered at the point which state was saved.
     * @return monitors entered at the point which state was saved
     */
    public LockState getLockState() {
        return lockState;
    }



    
    
    
    
    
    
    
    /**
     * Do not use -- for internal use only.
     * <p>
     * Get the next method state.
     * @return next method state
     */
    MethodState getNext() {
        return next;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Set the next method state.
     * @param next next method state
     */
    void setNext(MethodState next) {
        this.next = next;
        if (next != null) {
            next.previous = this;
        }
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get the previous method state.
     * @return previous method state
     */
    MethodState getPrevious() {
        return previous;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Set the previous method state.
     * @param previous previous method state
     */
    void setPrevious(MethodState previous) {
        this.previous = previous;
        if (previous != null) {
            previous.next = this;
        }
    }





    /**
     * Do not use -- for internal use only.
     * <p>
     * Determine if this method state is valid. Valid means that the method that this method state is for exists and the method is the
     * correct version for this method state.
     * @param classLoader class loader to use to look for the class ({@code null} will attempt to use this Object's classloader / the
     * thread's context class loader)
     * @param className class name
     * @param methodId method id
     * @param continuationPointId continuation point id
     * @return {@code true} if method for this method state exists and is of the correct version, {@code false} otherwise
     * @throws NullPointerException if {@code className} is {@code null}
     * @throws IllegalArgumentException if {@code continuationPointId < 0}
     */
    public static boolean isValid(ClassLoader classLoader, String className, int methodId, int continuationPointId) {
        if (className == null) {
            throw new NullPointerException();
        }
        if (continuationPointId < 0) {
            throw new IllegalArgumentException();
        }

        Class cls = null;
        if (classLoader == null) {
            // Try to find the class from this object's classloader
            try {
                cls = MethodState.class.getClassLoader().loadClass(className);
            } catch (ClassNotFoundException cnfe) {
                // do nothing
            }
            
            // Try to find the class from this Thread's classloader
            if (cls == null) {
                try {
                    cls = Thread.currentThread().getContextClassLoader().loadClass(className);
                } catch (ClassNotFoundException cnfe) {
                    // do nothing
                }
            }
        } else {
            // Try to find the class the classloader provided
            try {
                cls = classLoader.loadClass(className);
            } catch (ClassNotFoundException cnfe) {
                // do nothing
            }
        }



        if (cls == null) {
            throw new IllegalStateException("Class this state is being deserialized for is missing: " + className);
        }



        String versionField = getIdentifyingFieldName(methodId, continuationPointId);
        try {
            cls.getDeclaredField(versionField);
        } catch (NoSuchFieldException nsfe) {
            return false;
        }

        return true;
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Get the name of the field that will be inserted into a class for some method id and version combination.
     * @param methodId method id
     * @param continuationPointId continuation point id
     * @return field name
     * @throws IllegalArgumentException if {@code continuationPointId < 0}
     */
    public static String getIdentifyingFieldName(int methodId, int continuationPointId) {
        if (continuationPointId < 0) {
            throw new IllegalArgumentException();
        }

        String methodIdStr = Integer.toString(methodId).replace('-', 'N');
        String continuationPointIdStr = Integer.toString(continuationPointId).replace('-', 'N');
        return "__COROUTINES_ID_" + methodIdStr + "_" + continuationPointIdStr;
    }
}
import static com.offbynull.coroutines.instrumenter.SharedConstants.BASIC_TYPE_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.COMPLEX_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.CONSTRUCTOR_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.DIFFERENT_STATES_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.DOUBLE_RETURN_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.EMPTY_CONTINUATION_POINT_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.EXCEPTION_SUSPEND_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.EXCEPTION_THEN_CONTINUE_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.EXCEPTION_THROW_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.INHERITANCE_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.INTERFACE_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.ISSUE_84_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.JSR_EXCEPTION_SUSPEND_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.LAMBDA_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.LONG_RETURN_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.MONITOR_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.NORMAL_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.NULL_TYPE_IN_LOCAL_VARIABLE_TABLE_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.NULL_TYPE_IN_OPERAND_STACK_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.PEERNETIC_FAILURE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.RECURSIVE_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.RETURN_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.SANITY_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.STATIC_INVOKE_TEST;
import static com.offbynull.coroutines.instrumenter.SharedConstants.UNINITIALIZED_VARIABLE_INVOKE_TEST;
import com.offbynull.coroutines.instrumenter.generators.DebugGenerators.MarkerType;
import static com.offbynull.coroutines.instrumenter.testhelpers.TestUtils.getClasspath;
import static com.offbynull.coroutines.instrumenter.testhelpers.TestUtils.loadClassesInZipResourceAndInstrument;
import static com.offbynull.coroutines.instrumenter.testhelpers.TestUtils.readZipFromResource;
import com.offbynull.coroutines.user.Continuation;
import com.offbynull.coroutines.user.Coroutine;
import com.offbynull.coroutines.user.CoroutineRunner;
import com.offbynull.coroutines.user.MethodState;
import java.io.File;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import static org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor;
import static org.apache.commons.lang3.reflect.FieldUtils.readField;
import static org.apache.commons.lang3.reflect.MethodUtils.invokeStaticMethod;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
package com.offbynull.coroutines.instrumenter;


public final class InstrumentationTest {

    @Test
    public void mustProperlyExecuteSanityTest() throws Exception {
        StringBuilder builder = new StringBuilder();

        try (URLClassLoader classLoader = loadClassesInZipResourceAndInstrument(SANITY_TEST + ".zip")) {
            Class<Coroutine> cls = (Class<Coroutine>) classLoader.loadClass(SANITY_TEST);
            Coroutine coroutine = invokeConstructor(cls, builder);

            CoroutineRunner runner = new CoroutineRunner(coroutine);

            assertTrue(runner.execute());
            assertFalse(runner.execute());
            assertTrue(runner.execute()); // coroutine finished executing here
            assertFalse(runner.execute());

            assertEquals("abab", builder.toString());
        }
    }

    @Test
    public void mustProperlySuspendInDifferentStackAndLocalsStatesTest() throws Exception {
        StringBuilder builder = new StringBuilder();

        try (URLClassLoader classLoader = loadClassesInZipResourceAndInstrument(DIFFERENT_STATES_TEST + ".zip")) {