eu.trentorise.opendata.jackan.test.ckan.CkanTestReporter.java Source code

Java tutorial

Introduction

Here is the source code for eu.trentorise.opendata.jackan.test.ckan.CkanTestReporter.java

Source

/* 
 * Copyright 2015 Trento Rise  (trentorise.eu) 
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.trentorise.opendata.jackan.test.ckan;

import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.escape.Escaper;
import com.google.common.html.HtmlEscapers;
import eu.trentorise.opendata.commons.BuildInfo;
import eu.trentorise.opendata.commons.TodConfig;
import eu.trentorise.opendata.jackan.CkanClient;
import eu.trentorise.opendata.jackan.test.JackanTestConfig;
import eu.trentorise.opendata.commons.TodUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import static org.rendersnake.HtmlAttributesFactory.class_;
import static org.rendersnake.HtmlAttributesFactory.href;
import static org.rendersnake.HtmlAttributesFactory.style;

import org.rendersnake.HtmlAttributes;
import org.rendersnake.HtmlCanvas;

/**
 * Little app to test a list of catalogs and produce an HTML report.
 *
 * @author David Leoni
 */
public class CkanTestReporter {

    private static final Logger logger = Logger.getLogger(CkanTestReporter.class.getName());

    /**
     * Tests names from {@link ReadCkanIT}
     */
    public static final List<String> ALL_TEST_NAMES = ImmutableList.of("testApiVersionSupported", "testDatasetList",
            "testDatasetListWithLimit", "testSearchDatasetsByText", "testDatasetAndResource", "testLicenseList",
            "testTagList", "testTagNameList", "testUserList", "testUser", "testGroupList", "testGroupNames",
            "testGroup", "testOrganizationList", "testOrganization", "testOrganizationNames", "testFormatList",
            "testSearchDatasetsByGroups", "testSearchDatasetsByOrganization", "testSearchDatasetsByTags",
            "testSearchDatasetsByLicenseIds");

    private static final String ERROR_CLASS = "jackan-error";
    private static final String PASSED_CLASS = "jackan-passed";
    private static final String JACKAN_TABLE_CLASS = "jackan-table";

    /**
     * Takes as first argument the catalog list files to be used. If not
     * provided, by default test/resources/ckan-instances.txt file is used.
     */
    public static void main(String[] args) {

        JackanTestConfig.of().loadConfig();

        String catFilename;

        if (args.length == 2) {
            catFilename = args[1];
            logger.log(Level.INFO, "Using provided catalogs file {0}", catFilename);
        } else {
            catFilename = "ckan-instances.txt";
            logger.log(Level.INFO,
                    "Using default catalogs file {0}. If you wish to provide yours pass filename as first argument.",
                    catFilename);
        }

        Map<String, String> catalogsNames = readCatalogsList(catFilename);

        List<String> testNames = ALL_TEST_NAMES;

        RunSuite testResults = runTests(catalogsNames, testNames);

        String content = renderRunSuite(catalogsNames, testNames, testResults);

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd--HH-mm-ss");
        saveToDirectory(new File("reports/" + REPORT_PREFIX + "-" + formatter.format(new Date())), content,
                testResults);

        File latestDir = new File("reports/" + REPORT_PREFIX + "-latest");

        FileUtils.deleteQuietly(latestDir);
        saveToDirectory(latestDir, content, testResults);

    }

    /**
     * @param catalogListFilepath
     *            absolute file path
     */
    public static Map<String, String> readCatalogsList(String catalogListFilepath) {

        // catalog url, name
        ImmutableMap.Builder<String, String> catalogsBuilder = ImmutableMap.builder();

        InputStream is;

        try {
            is = new FileInputStream(catalogListFilepath);
        } catch (FileNotFoundException fex) {

            logger.log(Level.INFO, "Trying to take file {0} from test resources", catalogListFilepath);
            is = CkanTestReporter.class.getClassLoader().getResourceAsStream(catalogListFilepath);
            if (is == null) {
                throw new RuntimeException("Couldn't find file " + catalogListFilepath);
            }

        }
        try {
            String str;

            BufferedReader reader = new BufferedReader(new InputStreamReader(is));

            boolean readingName = true;
            String name = "";
            while ((str = reader.readLine()) != null) {
                if (readingName) {
                    name = str;
                } else {
                    catalogsBuilder.put(TodUtils.removeTrailingSlash(str), name);
                }

                readingName = !readingName;
            }

        } catch (IOException ex) {
            Logger.getLogger(CkanTestReporter.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (Throwable ignore) {
            }
        }
        /*
         * TODO validate urls: URL catURL; try { catURL = new URL(catalogURL); }
         * catch (MalformedURLException ex) { html.tr() .td(class_(ERROR_CLASS))
         * // todo we are skipping columns.... .write("Bad catalog URL: " +
         * catalogURL + " for catalog " + catalogs.get(catalogURL)) ._td()
         * ._tr(); continue; }
         */
        return catalogsBuilder.build();
    }

    public static final String REPORT_PREFIX = "jackan-scan";
    public static final String TEST_RESULT_PREFIX = "test-result-";

    private static TestResult runTest(int testId, CkanClient client, String catalogName, String testName) {
        Optional<Throwable> error;
        ReadCkanIT ckanTests = new ReadCkanIT();
        try {
            java.lang.reflect.Method method;

            method = ReadCkanIT.class.getMethod(testName, CkanClient.class);
            method.invoke(ckanTests, client);
            error = Optional.absent();
        } catch (Throwable t) {
            error = Optional.of(t);
        }
        return new TestResult(testId, testName, client.getCatalogUrl(), catalogName, error);
    }

    public static class RunSuite {

        private Date startTime;
        private Date endTime;
        private ImmutableList<TestResult> results;

        public RunSuite(Date startTime, Date endTime, List<TestResult> results) {
            this.startTime = startTime;
            this.endTime = endTime;
            this.results = ImmutableList.copyOf(results);
        }

        /**
         * Returns the number of passed tests under the given name.
         */
        public List<TestResult> getPassedTestsByName(String testName) {
            ImmutableList.Builder<TestResult> retb = ImmutableList.builder();
            for (TestResult tr : results) {
                if (tr.passed() && testName.equals(tr.getTestName())) {
                    retb.add(tr);
                }
            }
            return retb.build();
        }

        /**
         * Returns the number of passed tests of provided catalog.
         */
        public List<TestResult> getPassedTestsByCatalogUrl(String catalogUrl) {
            String cat = TodUtils.removeTrailingSlash(catalogUrl);
            ImmutableList.Builder<TestResult> retb = ImmutableList.builder();
            for (TestResult tr : results) {
                if (tr.passed() && cat.equals(tr.getCatalogURL())) {
                    retb.add(tr);
                }
            }
            return retb.build();
        }

        public Date getStartTime() {
            return startTime;
        }

        public Date getEndTime() {
            return endTime;
        }

        public ImmutableList<TestResult> getResults() {
            return results;
        }

    }

    public static RunSuite runTests(Map<String, String> catalogNames, List<String> testNames) {

        Date startTime = new Date();

        Map<String, CkanClient> clients = new HashMap();

        for (Entry<String, String> e : catalogNames.entrySet()) {
            clients.put(e.getKey(), new CkanClient(e.getKey()));
        }

        ImmutableList.Builder<TestResult> results = ImmutableList.builder();

        int testCounter = 0;

        for (String testName : testNames) { // so we don't stress one catalog
                                            // with all tests in sequence
            for (String catalogUrl : catalogNames.keySet()) {
                results.add(runTest(testCounter, clients.get(catalogUrl), catalogNames.get(catalogUrl), testName));
                testCounter += 1;
            }
        }

        return new RunSuite(startTime, new Date(), results.build());
    }

    /**
     * Formats date time up to the day, in English format
     */
    private static String formatDateUpToDay(Date date) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

        return formatter.format(date);
    }

    /**
     * Formats date time up to the second, in English format
     */
    private static String formatDateUpToSecond(Date date) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        return formatter.format(date);
    }

    private static HtmlAttributes errClass(int passed, int total) {
        if (passed == total) {
            return class_(PASSED_CLASS);
        } else {
            return class_(ERROR_CLASS);
        }
    }

    /**
     * Puts in bold label name and a value after it
     */
    private static HtmlCanvas labelValue(HtmlCanvas html, String label, String value) {
        try {
            return html.br().b().write(label)._b().span().write(value)._span();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Puts in bold label name and a span after it. Remember to close tag with
     * _span()
     */
    private static HtmlCanvas labelValue(HtmlCanvas html, String label) {
        try {
            return html.br().b().write(label)._b().span();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static String renderRunSuite(Map<String, String> catalogs, List<String> testNames, RunSuite runSuite) {
        String outputFileContent;

        BuildInfo buildInfo = TodConfig.of(JackanTestConfig.class).getBuildInfo();

        try {

            HtmlCanvas html = new HtmlCanvas();

            html.html().head().title().content("Jackan Test Results")
                    // .meta(name("description").add("content","Jackan test
                    // analyzer",false))
                    // .macros().stylesheet("htdocs/style-01.css"))
                    // .render(JQueryLibrary.core("1.4.3"))
                    // .render(JQueryLibrary.ui("1.8.6"))
                    // .render(JQueryLibrary.baseTheme("1.8"))
                    .style().write("." + ERROR_CLASS + " {color:red}").write("." + PASSED_CLASS + " {color:black}")
                    .write("." + JACKAN_TABLE_CLASS
                            + " { border-collapse:collapse; table-layout: fixed; width: 100%;  }")
                    .write("." + JACKAN_TABLE_CLASS
                            + " td, th { border: 1px solid black; vertical-align: top; padding:10px; width:100px;}")
                    .write("." + JACKAN_TABLE_CLASS + " th { position:absolute; left:0;  width:230px;}")
                    .write(".outer {position:relative}").write(".inner {\n" + "  overflow-x:scroll;\n"
                            + "  overflow-y:visible;\n" + "  margin-left:250px;\n" + "}")
                    ._style()._head();

            html.body().h1().a(href("https://github.com/opendatatrentino/jackan").target("_blank")).write("Jackan")
                    ._a().write(" Report - " + formatDateUpToDay(runSuite.getStartTime()))._h1().b().write("NOTE: ")
                    ._b().span()
                    .write("Some tests might fail due to missing items in the target catalog (i.e. catalog has no tags or no organizations). "
                            + "Also, some catalogs might only be ckan-compatible and support a subset of ckan functionality (i.e. DKAN). ")
                    ._span().br();
            labelValue(html, "Jackan Version: ").write(buildInfo.getVersion() + " ")
                    .a(href("https://github.com/opendatatrentino/jackan/commit/" + buildInfo.getGitSha())
                            .target("_blank"))
                    .write("Git commit")._a()._span();
            labelValue(html, "Started: ", formatDateUpToSecond(runSuite.getStartTime()));
            labelValue(html, "Finished: ", formatDateUpToSecond(runSuite.getEndTime()));
            labelValue(html, "Catalogs scanned: ", Integer.toString(catalogs.keySet().size()));
            labelValue(html, "Tests per catalog executed: ", Integer.toString(testNames.size()));
            html.br().br();

            Escaper escaper = HtmlEscapers.htmlEscaper();

            html.div(class_("outer")).div(class_("inner"));
            html.table(class_(JACKAN_TABLE_CLASS)).tr().th(style("height:100%")).write("test")._th();

            html.td().write("Passed")._td();
            for (String catalogUrl : catalogs.keySet()) {
                html.td().a(href(catalogUrl).target("_blank")).write(escaper.escape(catalogs.get(catalogUrl)))._a()
                        ._td();
            }
            html._tr();

            html.tr();
            html.th().write("Passed")._th().td()._td();
            for (String catalogUrl : catalogs.keySet()) {
                int passedByCat = runSuite.getPassedTestsByCatalogUrl(catalogUrl).size();
                html.td(errClass(passedByCat, testNames.size())).write(passedByCat + "/" + testNames.size())._td();
            }
            html._tr();

            Iterator<TestResult> resultIterator = runSuite.getResults().iterator();

            for (String testName : testNames) {
                html.tr();
                html.th().b().write(testName)._b()._th();
                int passedByName = runSuite.getPassedTestsByName(testName).size();
                html.td().b().write(passedByName + "/" + catalogs.size())._b()._td();

                for (String catalogURL : catalogs.keySet()) {

                    TestResult result = resultIterator.next();
                    if (result.passed()) {
                        html.td()
                                // .a(href(TEST_RESULT_PREFIX + result.getId() +
                                // ".html").target("_blank"))
                                .write("PASSED")._td();
                    } else {
                        html.td().a(href(TEST_RESULT_PREFIX + result.getId() + ".html").target("_blank")
                                .class_(ERROR_CLASS)).write("ERROR")._a()._td();
                    }
                }
                html._tr();
            }

            html._table()._div()._div()._body()._html();

            outputFileContent = html.toHtml();
        } catch (IOException ex) {
            outputFileContent = "HTML generation problem!" + ex;
        }
        return outputFileContent;
    }

    private static int catalogsWithErrors(RunSuite runSuite) {
        Map<String, Integer> map = new HashMap();
        for (TestResult tr : runSuite.getResults()) {
            if (!tr.passed()) {
                String key = tr.getCatalogURL();
                if (map.containsKey(key)) {
                    map.put(key, map.get(key) + 1);
                } else {
                    map.put(key, 1);
                }
            }
        }
        return map.size();
    }

    /**
     * Returns a new html page with test result.
     */
    public static String renderTestResult(TestResult result) {

        HtmlCanvas html = new HtmlCanvas();

        try {
            html.html().head().title().content("Jackan Test #" + result.getId())
                    // .meta(name("description").add("content","Jackan test
                    // anal",false))
                    // .macros().stylesheet("htdocs/style-01.css"))
                    // .render(JQueryLibrary.core("1.4.3"))
                    // .render(JQueryLibrary.ui("1.8.6"))
                    // .render(JQueryLibrary.baseTheme("1.8"))
                    .style().write("." + ERROR_CLASS + " {color:red}")._style()._head();

            html.body().h1().write("Jackan Test #" + result.getId())._h1();

            if (result.passed()) {
                html.h2().write("Test passed!")._h2();

            } else {
                html.h2().write("Test didn't pass!")._h2();
                html.pre().write(Throwables.getStackTraceAsString(result.getError()))._pre();
            }

            html._body();
            return html.toHtml();
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error while rendering Jackan Test " + result, ex);
            return "<html><body>Error while rendering Jackan Test #" + result.getId() + " </body></html>";
        }

    }

    public static void saveToDirectory(File outputDirectory, String indexContent, RunSuite runSuite) {

        outputDirectory.mkdirs();

        PrintWriter outIndex;
        try {
            outIndex = new PrintWriter(outputDirectory + "/index.html");
            outIndex.write(indexContent);
            outIndex.close();

            for (TestResult result : runSuite.getResults()) {
                PrintWriter outResult;
                String resultHtml = renderTestResult(result);
                outResult = new PrintWriter(outputDirectory + "/" + TEST_RESULT_PREFIX + result.getId() + ".html");
                outResult.write(resultHtml);
                outResult.close();
            }

            logger.log(Level.INFO, "Report is now available at {0}{1}index.html",
                    new Object[] { outputDirectory.getAbsolutePath(), File.separator });
        } catch (FileNotFoundException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

}