Package fi.jumi.threadsafetyagent

Source Code of fi.jumi.threadsafetyagent.AddThreadSafetyChecksTest$InterfaceAnnotatedNotThreadSafe

// Copyright © 2011-2013, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package fi.jumi.threadsafetyagent;

import fi.jumi.threadsafetyagent.util.TransformationTestClassLoader;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.util.CheckClassAdapter;

import javax.annotation.concurrent.*;
import java.lang.instrument.ClassFileTransformer;

import static fi.jumi.threadsafetyagent.ThreadUtil.runInNewThread;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

@SuppressWarnings({"UnusedDeclaration", "ThrowableResultOfMethodCallIgnored"})
public class AddThreadSafetyChecksTest {

    private static final String DUMMY_EXCEPTION = "dummy exception";

    @Rule
    public final ExpectedException thrown = ExpectedException.none().handleAssertionErrors();

//    @Test
//    public void experiment() throws Exception {
//        ASMifierClassVisitor.main(new String[]{"-debug", ReferenceImplementation.class.getName()});
//    }

    @Test
    public void reference_implementation_checks_current_thread() throws Throwable {
        Runnable target = new ReferenceImplementation();
        assertChecksThreadSafety(target);
    }

    @Test
    public void classes_annotated_NotThreadSafe_are_transformed() throws Throwable {
        Runnable target = (Runnable) newInstrumentedInstance(NotThreadSafeClass.class);
        assertChecksThreadSafety(target);
    }

    @Test
    public void non_annotated_and_thread_safe_classes_are_not_transformed() throws Throwable {
        assertDoesNotCheckThreadSafety((Runnable) newInstrumentedInstance(NonAnnotatedClass.class));
        assertDoesNotCheckThreadSafety((Runnable) newInstrumentedInstance(ThreadSafeClass.class));
        assertDoesNotCheckThreadSafety((Runnable) newInstrumentedInstance(ImmutableClass.class));
    }

    @Test
    public void interfaces_are_not_transformed() throws Exception {
        instrumentClass(InterfaceAnnotatedNotThreadSafe.class);
    }

    @Test
    public void static_methods_are_not_transformed() throws Exception {
        Class<?> clazz = instrumentClass(NotThreadSafeClassWithStaticMethods.class);
        clazz.getMethod("staticMethod").invoke(null);
    }

    @Test
    public void stack_trace_for_generated_code_contains_a_line_number() throws Throwable {
        Runnable instrumented = (Runnable) newInstrumentedInstance(NotThreadSafeClass.class);
        int generatedLine = getThreadSafetyCheckerExceptionLineNumber(instrumented);

        assertThat(generatedLine, is(greaterThanOrEqualTo(1)));
    }

    @Test
    public void line_number_for_generated_bytecode_is_the_first_line_of_the_method() throws Exception {
        int secondLine = getDummyExceptionLineNumber(new ThrowExceptionOnSecondLine());
        int firstLine = secondLine - 1;

        Runnable instrumented = (Runnable) newInstrumentedInstance(ThrowExceptionOnSecondLine.class);
        int generatedLine = getThreadSafetyCheckerExceptionLineNumber(instrumented);

        assertThat(generatedLine, is(firstLine));
    }


    // bytecode generation helpers

    private void assertChecksThreadSafety(Runnable target) throws Throwable {
        runInNewThread("T1", target);
        thrown.expect(AssertionError.class);
        runInNewThread("T2", target);
    }

    private void assertDoesNotCheckThreadSafety(Runnable target) throws Throwable {
        runInNewThread("T1", target);
        runInNewThread("T2", target);
    }

    private static Object newInstrumentedInstance(Class<?> cls) throws Exception {
        return instrumentClass(cls).newInstance();
    }

    private static Class<?> instrumentClass(Class<?> cls) throws Exception {
        ClassFileTransformer transformer = new ThreadSafetyCheckerTransformer() {
            @Override
            protected ClassVisitor getAdapters(ClassVisitor cv) {
                return super.getAdapters(new CheckClassAdapter(cv));
            }
        };
        ClassLoader loader = new TransformationTestClassLoader(cls.getName(), transformer, null);
        return loader.loadClass(cls.getName());
    }


    // guinea pigs

    public static class ReferenceImplementation implements Runnable {
        private final ThreadSafetyChecker checker = new ThreadSafetyChecker();

        @Override
        public void run() {
            checker.checkCurrentThread();
        }
    }

    @NotThreadSafe
    public static class NotThreadSafeClass implements Runnable {
        @Override
        public void run() {
        }
    }

    @ThreadSafe
    public static class ThreadSafeClass implements Runnable {
        @Override
        public void run() {
        }
    }

    @Immutable
    public static class ImmutableClass implements Runnable {
        @Override
        public void run() {
        }
    }

    public static class NonAnnotatedClass implements Runnable {
        @Override
        public void run() {
        }
    }

    @NotThreadSafe
    public static interface InterfaceAnnotatedNotThreadSafe {
        void shouldNotAddCodeToThisMethod();
    }

    @NotThreadSafe
    public static class NotThreadSafeClassWithStaticMethods {
        public static void staticMethod() {
            // should not add code to this method
        }
    }


    // line number helpers

    private static int getDummyExceptionLineNumber(Runnable notInstrumented) {
        Throwable t = getDummyExceptionThrownBy(notInstrumented);
        return getLineNumber(notInstrumented.getClass(), "run", t);
    }

    private static int getThreadSafetyCheckerExceptionLineNumber(Runnable instrumented) {
        Throwable t = getThreadSafetyException(instrumented);
        return getLineNumber(instrumented.getClass(), "run", t.getCause());
    }

    private static Throwable getDummyExceptionThrownBy(Runnable notInstrumented) {
        try {
            notInstrumented.run();
            return null;
        } catch (Exception e) {
            assertThat("class should NOT have been instrumented", e.getMessage(), is(DUMMY_EXCEPTION));
            return e;
        }
    }

    private static Throwable getThreadSafetyException(Runnable instrumented) {
        try {
            runInNewThread("T1", instrumented);
        } catch (Throwable throwable) {
            // ignore any exception naturally thrown by the method
        }
        try {
            runInNewThread("T2", instrumented);
            return null;
        } catch (Throwable t) {
            assertThat("class should have been instrumented", t.getMessage(), is(not(DUMMY_EXCEPTION)));
            return t;
        }
    }

    private static int getLineNumber(Class<?> clazz, String methodName, Throwable t) {
        for (StackTraceElement stackFrame : t.getStackTrace()) {
            if (stackFrame.getClassName().equals(clazz.getName())
                    && stackFrame.getMethodName().equals(methodName)) {
                return stackFrame.getLineNumber();
            }
        }
        throw new IllegalArgumentException("stack trace did not contain calls to method " + clazz.getName() + "." + methodName, t);
    }

    @NotThreadSafe
    public static class ThrowExceptionOnSecondLine implements Runnable {
        @Override
        public void run() {
            // Dummy line, to make the exception throw to be the second line.
            // This tests against duplicate line number entries, in which case
            // the JVM would apparently use the last matching entry in the line number table.
            Thread.yield();
            throw new RuntimeException(DUMMY_EXCEPTION);
        }
    }
}
TOP

Related Classes of fi.jumi.threadsafetyagent.AddThreadSafetyChecksTest$InterfaceAnnotatedNotThreadSafe

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.