net.mach6.listeners.DependencyReportingListener.java Source code

Java tutorial

Introduction

Here is the source code for net.mach6.listeners.DependencyReportingListener.java

Source

/*
 * Copyright (C) 2016 Doug Simmons
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance 
 * with the License.
 * 
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0  
 */

package net.mach6.listeners;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.logging.Logger;

import net.mach6.Dottable;
import net.mach6.JSONable;
import net.mach6.TestClassInfo;
import net.mach6.TestSuiteInfo;
import net.mach6.TestInfo;
import net.mach6.TestMethodInfo;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.testng.IReporter;
import org.testng.ISuite;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.SkipException;
import org.testng.internal.IResultListener2;
import org.testng.xml.XmlSuite;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class DependencyReportingListener implements IResultListener2, IReporter {
    private static final Logger LOGGER = Logger.getLogger(DependencyReportingListener.class.getName());
    private static final String OUTPUT_DIR = "/DependencyReporter";
    private static final String DASH_OPTION = "dependencyReporter";
    private static final String REPORT_FILENAME_JSON = "/report.json";
    private static final String REPORT_FILENAME_DOT = "/report.dot";
    private static List<String> dotFiles = new ArrayList<>();

    /**
     * Command line options for influencing the behavior of this listener/reporter <br>
     * Format - enumerated property name:value <br>
     * Default - first value in list <br>
     * <br>
     * For example:
     * 
     * <pre>
     * <code>prescan:false</code>, with "false" being the default value.
     * </pre>
     */
    public enum Option {
        ENABLED(Arrays.asList("true", "false")), PRESCAN(Arrays.asList("false", "true")), MODE(
                Arrays.asList("all", "suites", "tests", "classes", "methods", "groups", "configuration")), OUTPUT(
                        Arrays.asList("all", "dot", "png", "json"));

        private List<String> values;

        Option(List<String> values) {
            this.values = values;
        }

        public List<String> getValues() {
            return values;
        }

        public boolean isSet(String value) {
            String options = System.getProperty(DASH_OPTION, "");
            String parsed = parseOptions(options);
            return parsed.equalsIgnoreCase(value);
        }

        public boolean isSet(String... orValues) {
            for (String value : orValues) {
                if (isSet(value)) {
                    return true;
                }
            }
            return false;
        }

        private String parseOptions(String options) {
            // set the default
            String opt = this.values.get(0);

            // parse the supplied, overriding the default value if specified and a valid value
            String[] keyValuePairs = options.split(",");
            for (String pair : keyValuePairs) {
                String[] terms = pair.split(":");
                String key = terms[0].toUpperCase();
                if (!this.name().equals(key)) {
                    continue;
                }
                String value = terms[1].toLowerCase();
                if (this.getValues().contains(value)) {
                    opt = value;
                }
            }
            return opt;
        }
    }

    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        if (Option.ENABLED.isSet("false")) {
            return;
        }

        final String TOP_DIR = outputDirectory + OUTPUT_DIR;
        // Clean out any old results, if they exist
        FileUtils.deleteQuietly(new File(TOP_DIR));

        // Build the TestSuiteInfo Set
        Set<TestSuiteInfo> suiteInfoSet = new ConcurrentSkipListSet<>();
        for (ISuite suite : suites) {
            TestSuiteInfo suiteInfo = new TestSuiteInfo(suite);
            suiteInfoSet.add(suiteInfo);
        }

        generateOutput(suiteInfoSet, TOP_DIR);
        logCompletion(suiteInfoSet);
    }

    private void generateOutput(Set<TestSuiteInfo> suiteInfoSet, String directory) {
        toJson(suiteInfoSet, directory + REPORT_FILENAME_JSON);
        toDot(suiteInfoSet, directory + REPORT_FILENAME_DOT);
        generateOutputForTestSuiteInfo(suiteInfoSet, directory);
        generatePngFromDotFiles();
    }

    private void toDot(Set<TestSuiteInfo> suiteInfoSet, String fileName) {
        if (!Option.OUTPUT.isSet("dot", "png", "all")) {
            return;
        }

        StringBuilder builder = new StringBuilder("digraph g {\n");
        for (Dottable dottable : suiteInfoSet) {
            builder.append(dottable.toDot(true));
        }
        builder.append("}\n");
        writeDotFile(builder.toString(), fileName);
    }

    private void toJson(Object jsonable, String filename) {
        if (!Option.OUTPUT.isSet("json", "all")) {
            return;
        }

        LOGGER.fine("Creating " + filename);
        String json = "";
        if (jsonable instanceof JSONable) {
            json = ((JSONable) jsonable).toJSON();
        } else {
            Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
            json = gson.toJson(jsonable);
        }
        try {
            FileUtils.writeStringToFile(new File(filename), json, "UTF-8");
        } catch (IOException e) {
            throw new RuntimeException("Unable to create " + filename + " output file.", e);
        }
    }

    private void toDot(Dottable dottable, String fileName) {
        // NOTE png files require dot files as an intermediate format
        if (!Option.OUTPUT.isSet("dot", "png", "all")) {
            return;
        }

        String dot = dottable.toDot(false);
        writeDotFile(dot, fileName);
    }

    private void writeDotFile(String dot, String fileName) {
        // NOTE png files require dot files as an intermediate format
        if (!Option.OUTPUT.isSet("dot", "png", "all")) {
            return;
        }

        if (StringUtils.isEmpty(dot) || dot.equals("digraph g {\n}\n")) {
            return;
        }

        try {
            LOGGER.fine("Creating " + fileName);
            FileUtils.writeStringToFile(new File(fileName), dot, "UTF-8");
            dotFiles.add(fileName);
        } catch (IOException e) {
            throw new RuntimeException("Unable to create " + fileName + " output file.", e);
        }
    }

    private void generateOutputForTestSuiteInfo(Set<TestSuiteInfo> suites, String outputDirectory) {
        for (TestSuiteInfo suite : suites) {
            if (Option.MODE.isSet("all", "suites")) {
                LOGGER.fine("Generating reports for " + suite.getName());
                String fileName = suite.getName().replace(" ", "");
                toDot(suite, outputDirectory + "/suites/" + fileName + ".dot");
                toJson(suite, outputDirectory + "/suites/" + fileName + ".json");
            }

            doOutputForTestInfo(outputDirectory, suite);
        }
    }

    private void doOutputForTestInfo(String outputDirectory, TestSuiteInfo suiteInfo) {
        for (TestInfo testInfo : suiteInfo.getTests()) {
            if (Option.MODE.isSet("all", "suites", "tests")) {
                LOGGER.fine("Generating reports for " + testInfo.getName());
                String fileName = testInfo.getName().replace(" ", "");
                toDot(testInfo, outputDirectory + "/tests/" + fileName + ".dot");
                toJson(testInfo, outputDirectory + "/tests/" + fileName + ".json");
            }

            doOutputForTestClassInfo(outputDirectory, testInfo);
        }
    }

    private void doOutputForTestClassInfo(String outputDirectory, TestInfo testInfo) {
        for (TestClassInfo classInfo : testInfo.getTestClasses()) {
            if (Option.MODE.isSet("all", "suites", "tests", "classes")) {
                LOGGER.fine("Generating reports for " + classInfo.getName());
                String fileName = classInfo.getName();
                toDot(classInfo, outputDirectory + "/classes/" + fileName + ".dot");
                toJson(classInfo, outputDirectory + "/classes/" + fileName + ".json");
            }

            doOutputForMethodInfo(outputDirectory, classInfo);
        }
    }

    private void doOutputForMethodInfo(String outputDirectory, TestClassInfo classInfo) {
        if (!Option.MODE.isSet("all", "suites", "tests", "classes", "methods")) {
            return;
        }

        for (TestMethodInfo method : classInfo.getTestMethods()) {
            LOGGER.fine("Generating reports for " + method.getMethodName());
            String fileName = classInfo.getName() + "." + method.getMethodName();
            toDot(method, outputDirectory + "/methods/" + fileName + ".dot");
            toJson(method, outputDirectory + "/methods/" + fileName + ".json");
        }
    }

    private void generatePngFromDotFiles() {
        if (!Option.OUTPUT.isSet("png", "all")) {
            return;
        }

        for (String dotFile : dotFiles) {
            dotFile = FilenameUtils.getFullPath(dotFile) + FilenameUtils.getName(dotFile);
            try {
                final String cmd = "/usr/local/bin/dot " + dotFile + " -Grankdir=LR -Tpng -o "
                        + StringUtils.removeEnd(dotFile, ".dot").concat(".png");

                Process p = Runtime.getRuntime().exec(cmd, null);
                p.waitFor();
            } catch (IOException | InterruptedException e) {
                LOGGER.severe("Error generating png file due to " + e.getMessage());
                throw new RuntimeException("Error generating png file", e);
            } finally {
                // Delete the dot file if it is not a requested output format
                if (!Option.OUTPUT.isSet("dot", "all")) {
                    LOGGER.fine("deleting -> " + dotFile);
                    FileUtils.deleteQuietly(new File(dotFile));
                }
            }
        }
    }

    private void logCompletion(Set<TestSuiteInfo> suites) {
        for (TestSuiteInfo suite : suites) {
            LOGGER.info("Dependency report generation complete for " + suite.getName());
        }
    }

    @Override
    public void onTestStart(ITestResult result) {
        if (Option.PRESCAN.isSet("false") || Option.ENABLED.isSet("false")) {
            return;
        }
        throw new SkipException("Skipped for suite analysis");
    }

    @Override
    public void onTestSuccess(ITestResult result) {
    }

    @Override
    public void onTestFailure(ITestResult result) {
    }

    @Override
    public void onTestSkipped(ITestResult result) {
        if (Option.PRESCAN.isSet("false") || Option.ENABLED.isSet("false")) {
            return;
        }
        // result.setStatus(ITestResult.SUCCESS);
        // result.setThrowable(null);
    }

    @Override
    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
    }

    @Override
    public void onStart(ITestContext context) {
    }

    @Override
    public void onFinish(ITestContext context) {
    }

    @Override
    public void onConfigurationSuccess(ITestResult itr) {
    }

    @Override
    public void onConfigurationFailure(ITestResult itr) {
    }

    @Override
    public void onConfigurationSkip(ITestResult itr) {
        if (Option.PRESCAN.isSet("false") || Option.ENABLED.isSet("false")) {
            return;
        }
        // TODO :: figure out why status SKIP breaks some suites
        itr.setStatus(ITestResult.SUCCESS);
    }

    @Override
    public void beforeConfiguration(ITestResult tr) {
        if (Option.PRESCAN.isSet("false") || Option.ENABLED.isSet("false")) {
            return;
        }
        throw new SkipException("Skipped for suite analysis");
    }

}