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

edu.hm.hafner.grading.AutoGradingRunner Maven / Gradle / Ivy

The newest version!
package edu.hm.hafner.grading;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringJoiner;

import org.apache.commons.lang3.StringUtils;

import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.util.FilteredLog;
import edu.hm.hafner.util.SecureXmlParserFactory;
import edu.hm.hafner.util.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * GitHub action entrypoint for the autograding action.
 *
 * @author Ullrich Hafner
 */
public class AutoGradingRunner {
    private static final String SINGLE_LINE = "--------------------------------------------------------------------------------";
    private static final String DOUBLE_LINE = "================================================================================";
    private static final int ERROR_CAPACITY = 1024;

    private final PrintStream outputStream;

    /**
     * Creates a new instance of {@link AutoGradingRunner}.
     *
     * @param outputStream
     *         the output stream to write the log to
     */
    public AutoGradingRunner(final PrintStream outputStream) {
        this.outputStream = outputStream;
    }

    /**
     * Creates a new instance of {@link AutoGradingRunner}. This runner writes all logs to {@link System#out}.
     */
    @SuppressWarnings("SystemOut")
    public AutoGradingRunner() {
        this(System.out);
    }

    /**
     * Returns the name of the default configuration file to use when the environment variable CONFIG is not set.
     *
     * @return the name of the default configuration file
     */
    protected String getDefaultConfigurationPath() {
        return "/default-config.json";
    }

    protected String getDisplayName() {
        return "Autograding";
    }

    private String getFullDisplayName(final FilteredLog log) {
        return String.format("%s %s (#%s)", getDisplayName(), readVersion(log), readSha(log));
    }

    /**
     * Runs the autograding.
     *
     * @return the grading score
     */
    public AggregatedScore run() {
        var log = new FilteredLog(getDisplayName() + " Errors:");

        var logHandler = new LogHandler(outputStream, log);

        log.logInfo(SINGLE_LINE);
        log.logInfo(center("Start", log));
        log.logInfo(SINGLE_LINE);

        var configuration = getConfiguration(log);
        var score = new AggregatedScore(configuration, log);
        logHandler.print();

        try {
            log.logInfo(DOUBLE_LINE);
            score.gradeTests(new FileSystemTestReportFactory());
            logHandler.print();

            log.logInfo(DOUBLE_LINE);

            score.gradeCoverage(new FileSystemCoverageReportFactory());
            logHandler.print();

            log.logInfo(DOUBLE_LINE);

            score.gradeAnalysis(new FileSystemAnalysisReportFactory());
            logHandler.print();

            log.logInfo(DOUBLE_LINE);
            var results = new GradingReport();
            log.logInfo(results.getTextSummary(score));
            log.logInfo(DOUBLE_LINE);

            logHandler.print();

            publishGradingResult(score, log);
        }
        catch (NoSuchElementException
               | ParsingException
               | SecureXmlParserFactory.ParsingException exception) {
            log.logInfo(DOUBLE_LINE);
            log.logException(exception, "An error occurred while grading");
            log.logInfo(DOUBLE_LINE);

            publishError(score, log, exception);
        }

        log.logInfo(SINGLE_LINE);
        log.logInfo(center("End", log));
        log.logInfo(SINGLE_LINE);
        logHandler.print();

        return score;
    }

    /**
     * Reads the Maven version information from the git.properties file.
     *
     * @param log
     *         the logger
     *
     * @return the version information
     */
    protected String readVersion(final FilteredLog log) {
        return readGitProperty("git.build.version", log);
    }

    /**
     * Reads the Git SHA from the git.properties file.
     *
     * @param log
     *         the logger
     *
     * @return the Git SHA
     */
    protected String readSha(final FilteredLog log) {
        return readGitProperty("git.commit.id.abbrev", log);
    }

    @SuppressFBWarnings(value = "UI_INHERITANCE_UNSAFE_GETRESOURCE",
            justification = "This is required to get the correct file from the classpath")
    protected String readGitProperty(final String key, final FilteredLog log) {
        try (var propertiesFile = getClass().getResourceAsStream("/git.properties")) {
            if (propertiesFile == null) {
                log.logError("Version information file '/git.properties' not found in class path");

                return StringUtils.EMPTY;
            }

            try {
                var gitProperties = new Properties();

                gitProperties.load(propertiesFile);

                return gitProperties.getProperty(key);
            }
            catch (IOException exception) {
                log.logError("Can't read version information in '/git.properties'.");
            }
            return StringUtils.EMPTY;
        }
        catch (IOException exception) {
            return StringUtils.EMPTY; // ignore exception on close
        }
    }

    private String center(final String message, final FilteredLog log) {
        return StringUtils.center(message + " " + getFullDisplayName(log), 80);
    }

    /**
     * Publishes the grading result. This default implementation does nothing.
     *
     * @param score
     *         the grading score
     * @param log
     *         the logger
     */
    protected void publishGradingResult(final AggregatedScore score, final FilteredLog log) {
        // empty default implementation
    }

    /**
     * Publishes errors during grading. This default implementation does nothing.
     *
     * @param score
     *         the grading score
     * @param log
     *         the logger
     * @param exception
     *         the exception that occurred
     */
    protected void publishError(final AggregatedScore score, final FilteredLog log, final Throwable exception) {
        // empty default implementation
    }

    /**
     * Creates a text in Markdown format that contains all error messages.
     *
     * @param log
     *         the log to get the error messages from
     *
     * @return the error messages in Markdown format
     */
    protected String createErrorMessageMarkdown(final FilteredLog log) {
        if (log.hasErrors()) {
            var errors = new StringBuilder(ERROR_CAPACITY);

            errors.append("\n## :construction: Error Messages\n```\n");
            var messages = new StringJoiner("\n");
            log.getErrorMessages().forEach(messages::add);
            errors.append(messages);
            errors.append("\n```\n");

            return errors.toString();
        }
        return StringUtils.EMPTY;
    }

    @VisibleForTesting
    String getConfiguration(final FilteredLog log) {
        var configuration = System.getenv("CONFIG");
        if (StringUtils.isBlank(configuration)) {
            log.logInfo("No configuration provided (environment variable CONFIG not set), using default configuration");

            return readDefaultConfiguration();
        }

        log.logInfo("Obtaining configuration from environment variable CONFIG");
        return configuration;
    }

    private String readDefaultConfiguration() {
        var name = getDefaultConfigurationPath();
        try (var defaultConfig = AutoGradingRunner.class.getResourceAsStream(name)) {
            if (defaultConfig == null) {
                throw new IllegalStateException("Can't find configuration in class path: " + name);
            }
            return new String(defaultConfig.readAllBytes(), StandardCharsets.UTF_8);
        }
        catch (IOException exception) {
            throw new IllegalStateException("Can't read default configuration: " + name, exception);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy