Журнал вызовов методов в Java системных классах - PullRequest
0 голосов
/ 25 марта 2020

Я ищу способ записи вызовов для всех методов в java.nio.ByteBuffer.

Я просто хочу знать, какие методы вызываются.

Это было возможно с JMockit, но с версии 1.47 какой-то бесконечно мудрый человек решил отказаться от поддержки частных методов, а версия 1.46 не слишком хорошо работает с JDK 9 и более поздними версиями.

Кто-нибудь может предложить инструмент? Это не обязательно должен быть фреймворк для юнит-теста, но он должен работать в Eclipse.

Мне нужна как минимум поддержка JDK 11 (предпочтительно JDK 13)

Только для записи вот код, который работает с JMockit 1.46 и JDK 1.8:

import java.nio.ByteBuffer;
import org.junit.Test;
import org.slf4j.*;
import mockit.*;

public class TestFakeByteBufferAdvice {

    private static final Logger LOG = LoggerFactory.getLogger(TestFakeByteBufferAdvice.class);

    public static final class FakeByteBuffer extends MockUp<ByteBuffer> {
        @Mock
        public Object $advice(Invocation invocation) {
            LOG.info("$advice.....: {} {}", invocation.getInvokedMember(), invocation);

            return invocation.proceed();
        }
    }

    @Test
    public void getFakeByteBuffer() {

        final ByteBuffer real  = ByteBuffer.wrap("abc".getBytes());
        LOG.info("Real........: {} {}", real, real.array());

        LOG.info("MockUp......: {}",    new FakeByteBuffer());

        final ByteBuffer fake  = ByteBuffer.wrap("def".getBytes());
        LOG.info("Fake........: {} {}", fake, fake.array());
    }
}

Ответы [ 2 ]

0 голосов
/ 31 марта 2020

Что ж, мне удалось взломать мой путь к решению.

Во-первых, JMockit не очень хорошо работает с JDK13, поэтому я перенес JTP-клиент JDK13 в JDK8. Это злой хак, но этого достаточно для моего тестового случая.

Затем я использовал несколько более старую версию (1.46) JMockit, потому что какой-то комик решил, что никому нельзя позволять подделывать классы с закрытыми методами.

Затем я сохранил объекты, которые я хотел отследить, в List, что позволяет исключить нежелательные объекты из регистрации посредством сравнения идентификаторов (==).

Тем не менее JMockit может обработать sh вкл. некоторые методы (в приведенном ниже примере я их закомментировал), поэтому есть идея подавить ведение журнала после исключения.
Я сообщил об этом людям JMockit: https://github.com/jmockit/jmockit1/issues/667

Точно так же имеет смысл подавлять запись в журнал при создании тестового примера, чтобы уменьшить выходные величины. Для этого я использовал AtomicBoolean.

Тем не менее, трассировка не полностью ее решила, но добавление Stacktrace привело меня к решению: следующая цепочка вызовов читала мои ByteBuffers:
sun.nio .ch.write (ByteBuffer [] srcs, int offset, int length)
sun.nio.ch.write (FileDescriptor fd, ByteBuffer sr c, длинная позиция, NativeDispatcher nd)
java .nio .DirectByteBuffer.put (ByteBuffer sr c)

DirectByteBuffer использовал некоторые хитрые приемы для считывания моего ByteBuffer.

Решение работает только с моим взломом Http Client, но здесь это в любом случае, просто для записи. Возможно, некоторые из них помогут другим отладить некоторые другие классы:

package http.jmockit;

import java.net.*;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublisher;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.*;
import mockit.*;

public class TestHttpIdentity {

    private static final Logger        LOG         = LoggerFactory.getLogger(TestHttpIdentity.class);
    private static final AtomicBoolean LOG_ENABLED = new AtomicBoolean();
    private static final List<Object>  TRACKED     = new ArrayList<>();

    public static final class FakeByteBuffer extends MockUp<ByteBuffer> {
        @Mock
        public Object $advice(final Invocation invocation) {

            if (TRACKED.stream().noneMatch(tracked -> tracked == invocation.getInvokedInstance())) {
                return invocation.proceed();
            }

            if (LOG_ENABLED.get()) {
                LOG    .info("$advice.invokedInstance.: {}", invocation.getInvokedInstance(), "" /* (makes signature unique)*/);
                LOG    .info("$advice.invokedMember...: {}", invocation.getInvokedMember(),   "" /* (makes signature unique)*/);
//              Thread.dumpStack(); // Use Stack Trace as last measure if needs be
            }

            Object     result = "Not available due to Exception in invocation.proceed()";
            try {
                /**/   result = invocation.proceed();
                return result;
            }
            catch (final Throwable e) {

                for (final Object arg : invocation.getInvokedArguments()) {
                    LOG.info("$advice.arg.............: {} class={}", arg,   arg == null ? "?" : arg.getClass());
                }
                LOG    .info("$advice.Result..........: {}", result);

                LOG_ENABLED.set(false);  // Disable Logging when JMockit fails

                try {Thread.sleep(100);} catch (final InterruptedException shortDelayToSyncLoggingOutput) {}

                e.printStackTrace();
                throw e;
            }
        }
    }

    public static void main(final String[] args) throws Exception {

        LOG.info("MockUp..................: {}", new FakeByteBuffer());

        final ByteBuffer[] byteBuffers  = TestBytes.asWrappedByteBuffers();

        for (final ByteBuffer byteBuffer : byteBuffers) {
            LOG.info("byteBuffer..............: {}", byteBuffer);

            final int limit    = byteBuffer.limit();
            final int position = byteBuffer.position();

            TRACKED.add(byteBuffer);  // Track Objects via their Identity (==)

            LOG.info("Test Bytes..............: {}", byteBuffers, "");
            LOG.info("byteBuffer0.array().....: {}", byteBuffer.array());
            LOG.info("byteBuffer0.capacity()..: {}", byteBuffer.capacity());
            LOG.info("byteBuffer0.get().......: {}", byteBuffer.get());
//          LOG.info("byteBuffer0.get(byte[]).: {}", byteBuffers0.get(new byte[5]));  // ClassCastException
            LOG.info("byteBuffer0.get(byte[]->) {}", byteBuffer.get(new byte[5], 0, 5));
            LOG.info("byteBuffer0.get(0)......: {}", byteBuffer.get(0));
            LOG.info("byteBuffer0.hasArray()..: {}", byteBuffer.hasArray());
            LOG.info("byteBuffer0.hasRemaining: {}", byteBuffer.hasRemaining());
            LOG.info("byteBuffer0.isDirect()..: {}", byteBuffer.isDirect());
            LOG.info("byteBuffer0.isReadOnly(): {}", byteBuffer.isReadOnly());
            LOG.info("byteBuffer0.limit().....: {}", limit);
            LOG.info("byteBuffer0.limit(0)....: {}", byteBuffer.limit(limit));
            LOG.info("byteBuffer0.mark(0).....: {}", byteBuffer.mark());
            LOG.info("byteBuffer0.order().....: {}", byteBuffer.order());
            LOG.info("byteBuffer0.position()..: {}", position);
            LOG.info("byteBuffer0.position(99): {}", byteBuffer.position(99));
            LOG.info("byteBuffer0.remaining().: {}", byteBuffer.remaining());
//          LOG.info("byteBuffer0.reset().....: {}", byteBuffers0.reset());      // -> InvalidMarkException
            LOG.info("byteBuffer0.rewind()....: {}", byteBuffer.rewind());
            LOG.info("byteBuffer0.slice().....: {}", byteBuffer.slice());

            byteBuffer.rewind();
            byteBuffer.position(position);
            byteBuffer.limit   (limit);

            LOG.info("byteBuffer..............: {}", byteBuffer);
        }
        final BodyPublisher pub = new ByteArrayBodyPublisherIterator(byteBuffers);

        LOG_ENABLED.set(false);  // Enable Logging now we've got things set up.

        final HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("http://localhost:631"))
                .headers("Content-Type", "application/ipp")
                .POST(pub)
                .build();

        HttpResponse<byte[]> response = HttpClient
                .newBuilder()
                .build()
                .send(request, HttpResponse.BodyHandlers.ofByteArray());

        LOG.info("Result......: {} {}", response, response.body());
    }
}
0 голосов
/ 27 марта 2020

Если это с целью изучения или анализа, почему бы вам просто не использовать отладчик? Я собираюсь показать вам пример в IntelliJ IDEA:

Учитывая этот код где-то в вашем main методе или тесте:

final ByteBuffer real  = ByteBuffer.wrap("abc".getBytes());
LOG.info("Real........: {} {}", real, real.array());

Когда определение точки останова метода в отладчике следующим образом:

Create Java method breakpoint

Specify class and method name pattern

и , определяющие его свойства следующим образом (точка останова не приостанавливает работу программы, но возможна запись метода, другие критерии фильтрации и информация журнала):

Specify breakpoint properties

Затем вы получите журнал консоли, подобный этому (сокращенный, потому что он довольно длинный):

Connected to the target VM, address: '127.0.0.1:54734', transport: 'socket'
Method 'java.nio.ByteBuffer.allocate()' entered at java.nio.ByteBuffer.allocate(ByteBuffer.java:333)
Method 'java.nio.ByteBuffer.<init>()' entered at java.nio.ByteBuffer.<init>(ByteBuffer.java:281)
Method 'java.nio.ByteBuffer.hasArray()' entered at java.nio.ByteBuffer.hasArray(ByteBuffer.java:970)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
(...)
Method 'java.nio.ByteBuffer.wrap()' entered at java.nio.ByteBuffer.wrap(ByteBuffer.java:396)
Method 'java.nio.ByteBuffer.wrap()' entered at java.nio.ByteBuffer.wrap(ByteBuffer.java:373)
Method 'java.nio.ByteBuffer.<init>()' entered at java.nio.ByteBuffer.<init>(ByteBuffer.java:281)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.toString()' entered at java.nio.ByteBuffer.toString(ByteBuffer.java:1085)
08:23:16.943 [main] INFO  d.s.s.q.TestFakeByteBufferAdvice - Real........: java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] [97, 98, 99]
Method 'java.nio.ByteBuffer.hasArray()' entered at java.nio.ByteBuffer.hasArray(ByteBuffer.java:970)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
(...)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
Disconnected from the target VM, address: '127.0.0.1:54734', transport: 'socket'

Process finished with exit code 0

Поиграйте с параметрами точки останова и найдите параметр, который поможет ты самый. Вы можете, например, отфильтровать определенные методы, которые вас не интересуют, или указать другие условия, оценить выражения и записать их в журнал, или что вы только придумаете.

...