All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.coverage.data.CoverageData Maven / Gradle / Ivy

/*
 * Copyright (c) 2006 JMockit developers
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.coverage.data;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import mockit.coverage.CoveragePercentage;
import mockit.internal.util.Utilities;

import org.checkerframework.checker.index.qual.NonNegative;

/**
 * Coverage data captured for all source files exercised during a test run.
 */
public final class CoverageData implements Serializable {
    private static final long serialVersionUID = -4860004226098360259L;
    @NonNull
    private static final CoverageData instance = new CoverageData();

    @NonNull
    public static CoverageData instance() {
        return instance;
    }

    private boolean withCallPoints;

    @NonNull
    private final Map fileToFileData = new LinkedHashMap<>();
    @NonNull
    private final List indexedFileData = new ArrayList<>(100);

    public boolean isWithCallPoints() {
        return withCallPoints;
    }

    public void setWithCallPoints(boolean withCallPoints) {
        this.withCallPoints = withCallPoints;
    }

    @NonNull
    public Map getFileToFileData() {
        return fileToFileData;
    }

    @NonNull
    public FileCoverageData getOrAddFile(@NonNull String file, @Nullable String kindOfTopLevelType) {
        FileCoverageData fileData = fileToFileData.get(file);

        // For a class with nested/inner classes, a previous class in the same source file may already have been added.
        if (fileData == null) {
            int fileIndex = indexedFileData.size();
            fileData = new FileCoverageData(fileIndex, kindOfTopLevelType);
            indexedFileData.add(fileData);
            fileToFileData.put(file, fileData);
        } else if (kindOfTopLevelType != null) {
            fileData.kindOfTopLevelType = kindOfTopLevelType;
        }

        return fileData;
    }

    @NonNull
    public FileCoverageData getFileData(@NonNull String file) {
        return fileToFileData.get(file);
    }

    @NonNull
    public FileCoverageData getFileData(@NonNegative int fileIndex) {
        return indexedFileData.get(fileIndex);
    }

    public boolean isEmpty() {
        return fileToFileData.isEmpty();
    }

    public void clear() {
        fileToFileData.clear();
    }

    /**
     * Computes the coverage percentage over a subset of the available source files.
     *
     * @param fileNamePrefix
     *            a regular expression for matching the names of the source files to be considered, or null
     *            to consider all files
     *
     * @return the computed percentage from 0 to 100 (inclusive), or -1 if no
     *         meaningful value could be computed
     */
    public int getPercentage(@Nullable String fileNamePrefix) {
        int coveredItems = 0;
        int totalItems = 0;

        for (Entry fileAndFileData : fileToFileData.entrySet()) {
            String sourceFile = fileAndFileData.getKey();

            if (fileNamePrefix == null || sourceFile.startsWith(fileNamePrefix)) {
                FileCoverageData fileData = fileAndFileData.getValue();
                coveredItems += fileData.getCoveredItems();
                totalItems += fileData.getTotalItems();
            }
        }

        return CoveragePercentage.calculate(coveredItems, totalItems);
    }

    /**
     * Finds the source file with the smallest coverage percentage.
     *
     * @return the percentage value for the file found, or Integer.MAX_VALUE if no file is found with a
     *         meaningful percentage
     */
    @NonNegative
    public int getSmallestPerFilePercentage() {
        int minPercentage = Integer.MAX_VALUE;

        for (FileCoverageData fileData : fileToFileData.values()) {
            if (!fileData.wasLoadedAfterTestCompletion()) {
                int percentage = fileData.getCoveragePercentage();

                if (percentage >= 0 && percentage < minPercentage) {
                    minPercentage = percentage;
                }
            }
        }

        return minPercentage;
    }

    public void fillLastModifiedTimesForAllClassFiles() {
        for (Iterator> itr = fileToFileData.entrySet().iterator(); itr.hasNext();) {
            Entry fileAndFileData = itr.next();
            long lastModified = getLastModifiedTimeForClassFile(fileAndFileData.getKey());

            if (lastModified > 0L) {
                FileCoverageData fileCoverageData = fileAndFileData.getValue();
                fileCoverageData.lastModified = lastModified;
                continue;
            }

            itr.remove();
        }
    }

    private long getLastModifiedTimeForClassFile(@NonNull String sourceFilePath) {
        String sourceFilePathNoExt = sourceFilePath.substring(0, sourceFilePath.lastIndexOf('.'));
        String className = sourceFilePathNoExt.replace('/', '.');

        Class coveredClass = findCoveredClass(className);

        if (coveredClass == null) {
            return 0L;
        }

        String locationPath = Utilities.getClassFileLocationPath(coveredClass);

        if (locationPath.endsWith(".jar")) {
            try {
                return getLastModifiedTimeFromJarEntry(sourceFilePathNoExt, locationPath);
            } catch (IOException ignore) {
                return 0L;
            }
        }

        String pathToClassFile = locationPath + sourceFilePathNoExt + ".class";

        return new File(pathToClassFile).lastModified();
    }

    private static long getLastModifiedTimeFromJarEntry(@NonNull String sourceFilePathNoExt,
            @NonNull String locationPath) throws IOException {

        try (JarFile jarFile = new JarFile(locationPath)) {
            JarEntry classEntry = jarFile.getJarEntry(sourceFilePathNoExt + ".class");
            return classEntry.getTime();
        }
    }

    @Nullable
    private Class findCoveredClass(@NonNull String className) {
        ClassLoader currentCL = getClass().getClassLoader();
        Class coveredClass = loadClass(className, currentCL);

        if (coveredClass == null) {
            ClassLoader systemCL = ClassLoader.getSystemClassLoader();

            if (systemCL != currentCL) {
                coveredClass = loadClass(className, systemCL);
            }

            if (coveredClass == null) {
                ClassLoader contextCL = Thread.currentThread().getContextClassLoader();

                if (contextCL != null && contextCL != systemCL) {
                    coveredClass = loadClass(className, contextCL);
                }
            }
        }

        return coveredClass;
    }

    @Nullable
    private static Class loadClass(@NonNull String className, @Nullable ClassLoader loader) {
        try {
            return Class.forName(className, false, loader);
        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
            return null;
        }
    }

    /**
     * Reads a serialized CoverageData object from the given file (normally, a "coverage.ser"
     * file generated at the end of a previous test run).
     *
     * @param dataFile
     *            the ".ser" file containing a serialized CoverageData instance
     *
     * @return a new object containing all coverage data resulting from a previous test run
     */
    @NonNull
    public static CoverageData readDataFromFile(@NonNull File dataFile) throws IOException {
        try (ObjectInputStream input = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile)))) {
            return (CoverageData) input.readObject();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(
                    "Serialized class in coverage data file \"" + dataFile + "\" not found in classpath", e);
        }
    }

    public void writeDataToFile(@NonNull File dataFile) throws IOException {
        try (ObjectOutputStream output = new ObjectOutputStream(
                new BufferedOutputStream(new FileOutputStream(dataFile)))) {
            output.writeObject(this);
        }
    }

    public void merge(@NonNull CoverageData previousData) {
        withCallPoints |= previousData.withCallPoints;

        for (Entry previousFileAndFileData : previousData.fileToFileData.entrySet()) {
            String previousFile = previousFileAndFileData.getKey();
            FileCoverageData previousFileData = previousFileAndFileData.getValue();
            FileCoverageData fileData = fileToFileData.get(previousFile);

            if (fileData == null) {
                fileToFileData.put(previousFile, previousFileData);
            } else if (fileData.lastModified > 0 && previousFileData.lastModified == fileData.lastModified) {
                fileData.mergeWithDataFromPreviousTestRun(previousFileData);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy