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

com.oracle.truffle.tools.coverage.impl.CoverageInstrument Maven / Gradle / Ivy

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.truffle.tools.coverage.impl;

import static com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.function.Function;

import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionStability;
import org.graalvm.options.OptionType;
import org.graalvm.options.OptionValues;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Instrument;

import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.tools.coverage.CoverageTracker;
import com.oracle.truffle.tools.coverage.SourceCoverage;

@Registration(id = CoverageInstrument.ID, name = "Code Coverage", version = CoverageInstrument.VERSION, services = CoverageTracker.class, website = "https://www.graalvm.org/tools/code-coverage/")
public class CoverageInstrument extends TruffleInstrument {

    public static final String ID = "coverage";
    static final String VERSION = "0.1.0";
    static final OptionType CLI_OUTPUT_TYPE = new OptionType<>("Output",
                    new Function() {
                        @Override
                        public Output apply(String s) {
                            try {
                                return Output.valueOf(s.toUpperCase());
                            } catch (IllegalArgumentException e) {
                                StringBuilder message = new StringBuilder("Output can be one of: ");
                                for (Output output : Output.values()) {
                                    message.append(output.toString().toLowerCase());
                                    message.append(" ");
                                }
                                throw new IllegalArgumentException(message.toString());
                            }
                        }
                    });
    // @formatter:off
    @Option(name = "", help = "Enable Coverage (default: false).", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey ENABLED = new OptionKey<>(false);
    @Option(help = "Keep count of each element's coverage (default: false).", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey Count = new OptionKey<>(false);
    @Option(name = "Output", help = "Can be: human readable 'histogram' (per file coverage summary) or 'detailed' (per line coverage summary), machine readable 'json', tool compliant 'lcov' (default: histogram).",
            usageSyntax = "histogram|detailed|json|lcov", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey OUTPUT = new OptionKey<>(Output.HISTOGRAM, CLI_OUTPUT_TYPE);
    @Option(name = "FilterRootName", help = "Wildcard filter for program roots. (eg. Math.*) (default: no filter)", usageSyntax = "", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey FILTER_ROOT = new OptionKey<>(WildcardFilter.DEFAULT, WildcardFilter.WILDCARD_FILTER_TYPE);
    @Option(name = "FilterFile", help = "Wildcard filter for source file paths. (eg. *program*.sl)  (default: no filter).", usageSyntax = "", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey FILTER_FILE = new OptionKey<>(WildcardFilter.DEFAULT, WildcardFilter.WILDCARD_FILTER_TYPE);
    @Option(name = "FilterMimeType", help = "Only track languages with mime-type. (default: no filter)", usageSyntax = "", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey FILTER_MIME_TYPE = new OptionKey<>("");
    @Option(name = "FilterLanguage", help = "Only track languages with given ID. (eg. js) (default: no filter).", usageSyntax = "", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey FILTER_LANGUAGE = new OptionKey<>("");
    @Option(name = "TrackInternal", help = "Track internal elements. (default: false)", category = OptionCategory.INTERNAL)
    static final OptionKey TRACK_INTERNAL = new OptionKey<>(false);
    @Option(name = "OutputFile", help = "Save output to the given file. Output is printed to standard output stream by default.", usageSyntax = "", category = OptionCategory.USER, stability = OptionStability.STABLE)
    static final OptionKey OUTPUT_FILE = new OptionKey<>("");
    @Option(help = "Consider a source code line covered only if covered in it's entirety (default: true)", usageSyntax = "true|false", category = OptionCategory.USER, stability = OptionStability.EXPERIMENTAL)
    static final OptionKey StrictLines = new OptionKey<>(true);
    // @formatter:on

    private static Function factory;

    static {
        // Be sure that the factory is initialized:
        try {
            Class.forName(CoverageTracker.class.getName(), true, CoverageTracker.class.getClassLoader());
        } catch (ClassNotFoundException cannotHappen) {
            // Can not happen
            throw new AssertionError();
        }
    }

    private CoverageTracker tracker;
    private Boolean enabled;

    public static CoverageTracker getTracker(Engine engine) {
        Instrument instrument = engine.getInstruments().get(ID);
        if (instrument == null) {
            throw new CoverageException("Tracker is not installed.");
        }
        return instrument.lookup(CoverageTracker.class);
    }

    public static void setFactory(Function factory) {
        if (factory == null || !factory.getClass().getName().startsWith("com.oracle.truffle.tools.coverage")) {
            throw new IllegalArgumentException("Wrong factory: " + factory);
        }
        CoverageInstrument.factory = factory;
    }

    protected static PrintStream chooseOutputStream(TruffleInstrument.Env env, OptionKey option) {
        try {
            if (option.hasBeenSet(env.getOptions())) {
                final String outputPath = option.getValue(env.getOptions());
                final File file = new File(outputPath);
                new PrintStream(env.out()).println("Printing output to " + file.getAbsolutePath());
                return new PrintStream(new FileOutputStream(file));
            } else {
                return new PrintStream(env.out());
            }
        } catch (FileNotFoundException e) {
            throw new AbstractTruffleException() {
                static final long serialVersionUID = -1;

                @Override
                public String getMessage() {
                    return "File IO Exception caught during output printing.";
                }
            };
        }
    }

    private static SourceSectionFilter getSourceSectionFilter(OptionValues options) {
        final WildcardFilter filterFile = FILTER_FILE.getValue(options);
        final String filterMimeType = FILTER_MIME_TYPE.getValue(options);
        final String filterLanguage = FILTER_LANGUAGE.getValue(options);
        final Boolean internals = TRACK_INTERNAL.getValue(options);
        final SourceSectionFilter.Builder builder = SourceSectionFilter.newBuilder();
        builder.sourceIs(source -> {
            boolean internal = (internals || !source.isInternal());
            boolean file = filterFile.testWildcardExpressions(source.getPath());
            boolean mimeType = filterMimeType.equals("") || filterMimeType.equals(source.getMimeType());
            final boolean languageId = filterLanguage.equals("") || filterMimeType.equals(source.getLanguage());
            return internal && file && mimeType && languageId;
        });
        final WildcardFilter filterRootName = FILTER_ROOT.getValue(options);
        builder.rootNameIs(filterRootName::testWildcardExpressions);
        return builder.build();
    }

    @Override
    protected void onCreate(Env env) {
        tracker = factory.apply(env);
        env.registerService(tracker);
        final OptionValues options = env.getOptions();
        enabled = ENABLED.getValue(options);
        if (enabled) {
            tracker.start(new CoverageTracker.Config(getSourceSectionFilter(options), Count.getValue(options)));
        }
    }

    @Override
    protected void onFinalize(Env env) {
        if (enabled) {
            SourceCoverage[] coverage = tracker.getCoverage();
            final OptionValues options = env.getOptions();
            final boolean strictLines = StrictLines.getValue(options);
            PrintStream out = chooseOutputStream(env, OUTPUT_FILE);
            switch (OUTPUT.getValue(options)) {
                case HISTOGRAM:
                    new CoverageCLI(out, coverage, strictLines).printHistogramOutput();
                    break;
                case DETAILED:
                    new CoverageCLI(out, coverage, strictLines).printLinesOutput();
                    break;
                case JSON:
                    new JSONPrinter(out, coverage).print();
                    break;
                case LCOV:
                    new LCOVPrinter(out, coverage, strictLines).print();
                    break;
            }
        }
    }

    @Override
    protected void onDispose(Env env) {
        if (enabled) {
            tracker.close();
        }
    }

    @Override
    protected OptionDescriptors getOptionDescriptors() {
        return new CoverageInstrumentOptionDescriptors();
    }

    enum Output {
        HISTOGRAM,
        DETAILED,
        JSON,
        LCOV;

        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy