jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
458 stars 238 forks source link

JMockit does not work with newest versions of JaCoCo plugin #748

Open pksilen opened 6 months ago

pksilen commented 6 months ago

Some unit tests throw ArrayIndexOutOfBoundsException when using newest version 0.8.11 of the JaCoCo plugin. An older version of JaCoCo plugin like 0.8.3 works ok.

The expection is thrown in a test case where I mock a singleton class. If I remove the mocking the test case works ok.

Mocking of the below class in a test case field using the @Mocked annotation cause all test cases in the module to fail with the ArrayIndexOutOfBoundsException :

package com.nokia.cni.exportica.batchwriter.common.metrics;

import com.nokia.cni.exportica.batchwriter.application.config.ApplicationConfig;
import com.nokia.cni.exportica.batchwriter.common.configproperties.ConfigProperties;
import com.nokia.cni.exportica.batchwriter.common.metrics.counter.CounterDescriptions;
import com.nokia.cni.exportica.batchwriter.common.metrics.counter.CounterKey;
import com.nokia.cni.exportica.batchwriter.common.metrics.counter.CounterNames;
import com.nokia.cni.exportica.batchwriter.common.metrics.gauge.GaugeDescriptions;
import com.nokia.cni.exportica.batchwriter.common.metrics.gauge.GaugeKey;
import com.nokia.cni.exportica.batchwriter.common.metrics.gauge.GaugeNames;
import com.nokia.cni.exportica.batchwriter.common.metrics.label.LabelKey;
import com.nokia.cni.exportica.batchwriter.common.metrics.label.LabelNames;
import com.nokia.cni.exportica.batchwriter.messagequeue.MessageQueueConfig;
import com.nokia.cni.exportica.batchwriter.output.file.writer.config.FileWriterConfig;
import io.prometheus.client.Counter;
import io.prometheus.client.Gauge;

import java.util.*;

@SuppressWarnings("UseOfConcreteClass")
public final class Metrics {
    private static Metrics instance;
    final Map<CounterKey, Counter> keyToCounter = new EnumMap<>(CounterKey.class);
    final Map<GaugeKey, Gauge> keyToGauge = new EnumMap<>(GaugeKey.class);
    private final double msgQueueMaxLength;
    private final String productName;
    private final String useCaseName;
    private final String podName;
    private final String outputType;

    public static void createInstance(final ConfigProperties configProperties) {
        if (instance == null) {
            instance = new Metrics(configProperties);
        }
    }

    public static Metrics getInstance() {
        return instance;
    }

    @SuppressWarnings("LawOfDemeter")
    private Metrics(final ConfigProperties configProperties) {
        final var namesConfig = new ApplicationConfig(configProperties).namesConfig;
        productName = namesConfig.productName;
        useCaseName = namesConfig.useCaseName;
        podName = namesConfig.podName;
        outputType = new FileWriterConfig(configProperties).destType;
        msgQueueMaxLength = new MessageQueueConfig(configProperties).maxLength;
        createCounters();
        createGauges();
    }

    public void incrementCounter(final CounterKey key, final long value, final String kafkaTopic) {
        final var counter = keyToCounter.get(key);
        counter.labels(getLabelValues(kafkaTopic)).inc(value);
    }

    public void updateMsgQueueGauges(final int msgQueueLength) {
        keyToGauge.get(GaugeKey.MESSAGE_QUEUE_LENGTH).labels(getLabelValues("")).set(msgQueueLength);
        keyToGauge.get(GaugeKey.MESSAGE_QUEUE_UTILIZATION).labels(getLabelValues("")).set(msgQueueLength / msgQueueMaxLength);
    }

    private void createCounters() {
        Arrays.stream(CounterKey.values()).forEach(counterKey -> keyToCounter.put(counterKey, createCounter(counterKey)));
    }

    private Counter createCounter(final CounterKey counterKey) {
        return Counter.build()
                .name(createProductNamespacedMetricName(CounterNames.get(counterKey)))
                .help(CounterDescriptions.get(counterKey))
                .labelNames(getLabelNames())
                .register();
    }

    private void createGauges() {
        Arrays.stream(GaugeKey.values()).forEach(gaugeKey -> keyToGauge.put(gaugeKey, createGauge(gaugeKey)));
    }

    private Gauge createGauge(final GaugeKey gaugeKey) {
        return Gauge.build()
                .name(createProductNamespacedMetricName(GaugeNames.get(gaugeKey)))
                .help(GaugeDescriptions.get(gaugeKey))
                .labelNames(getLabelNames())
                .register();
    }

    private String createProductNamespacedMetricName(final String metricName) {
        final var productNameInMetricName = productName.replace("-", "_");
        return productNameInMetricName + '_' + metricName;
    }

    private static String[] getLabelNames() {
        return Arrays.stream(LabelKey.values()).map(LabelNames::get).toArray(String[]::new);
    }

    private String[] getLabelValues(final String kafkaTopic) {
        return new String[]{useCaseName, podName, outputType, kafkaTopic};
    }
}
shivanisky commented 6 days ago

You might want to migrate to mockito, as I hit the same issue.

Have built on @tinder-dthomson 's great work with much help from @timtebeek and added automation for more statements and improved robustness, including Jmockit Expectations, JMockit Verifications (v 8.29.0) and Jmockit NonStrictExpectations (v 8.30.0) . It's now looking like it would cover the majority of cases and may be worth exploring for migration to mockito

https://docs.openrewrite.org/recipes/java/testing/jmockit/jmockittomockito