diff --git a/build.gradle b/build.gradle index c8a687ec..6b4a96f3 100644 --- a/build.gradle +++ b/build.gradle @@ -51,9 +51,6 @@ configure(subprojects) { description "${rootProject.description} - Module ${project.name}" dependencies { - implementation platform(libs.groovy.bom) - implementation 'org.codehaus.groovy:groovy' - testImplementation platform(libs.spock) testImplementation "org.spockframework:spock-core" testImplementation "org.spockframework:spock-junit4" @@ -89,10 +86,6 @@ configure(subprojects) { jacocoTestReport { dependsOn check // tests are required to run before generating the report } - - // The CodeNarc plugin performs quality checks on your project’s - // Groovy source files using CodeNarc and generates reports from these checks. - // apply plugin: 'codenarc' } tasks.register("integrationTestOnly") { diff --git a/htmlSanityCheck-core/build.gradle b/htmlSanityCheck-core/build.gradle index acc3b3b1..691445a3 100644 --- a/htmlSanityCheck-core/build.gradle +++ b/htmlSanityCheck-core/build.gradle @@ -15,7 +15,8 @@ dependencies { testCompileOnly libs.lombok testAnnotationProcessor libs.lombok - implementation 'org.codehaus.groovy:groovy-xml' + testImplementation platform(libs.groovy.bom) + testImplementation 'org.codehaus.groovy:groovy-xml' } publishing { diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/filesystem/FileCollector.groovy b/htmlSanityCheck-core/src/main/groovy/org/aim42/filesystem/FileCollector.groovy deleted file mode 100644 index c5edeb15..00000000 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/filesystem/FileCollector.groovy +++ /dev/null @@ -1,103 +0,0 @@ -package org.aim42.filesystem - -import groovy.io.FileType - -// see end-of-file for license information - -// TODO: add exclude-patterns - -class FileCollector { - - public final static String IMAGE_FILE_EXTENSION_PATTERN = ~/(?i).+\.(jpg|jpeg|png|gif|bmp|svg)?$/ - - /** - * checks if @param fileName represents an image file name, - * ignoring the files' content. - * @param fileName - */ - public static Boolean isImageFileName(String fileName) { - return (fileName ==~ IMAGE_FILE_EXTENSION_PATTERN) - } - - /** - * returns all configured image files as Set - * - */ - public static Set getConfiguredImageFiles( - File srcDir, - Set sourceDocs) { - // first case: no document names given -> return all html files - // in directory tree - if ((sourceDocs == null) || (sourceDocs?.empty)) { - return getAllImageFilesFromDirectory(srcDir) - } else { - return getAllConfiguredImageFiles(srcDir, sourceDocs) - } - } - - /** - * returns all image files in a given directory. - * (recursively looks in subdirectories) - * @param dir where to look for matching files - * @return all files with appropriate extension - */ - public static Set getAllImageFilesFromDirectory(File dir) { - Set files = new HashSet() - - // scan only files, not directories - dir.eachFileRecurse(FileType.FILES) { file -> - if (FileCollector.isImageFileName(file.getName())) { - files.add(file) - } - } - - return files - } - - /** - * returns all configured image files from @param srcDocs - * which really exist below @param srcDocs - */ - public static Set getAllConfiguredImageFiles(File srcDir, Set srcDocs) { - Set files = new HashSet() - - srcDocs.each { srcFileName -> - // add only existing image files - File file = new File(srcDir, srcFileName) - if (file.exists() && isImageFileName(file.getName())) { - files.add(file) - } - } - - return files - - } - - /** - * convert Set of Files to Set of file-names without path prefix! - */ - public static Set collectFileNamesFromFiles(Set files) { - // remark: could also achieve that with java.nio.file.Paths - but getName() seems simpler - return files*.getName() - - - } -} - -/*======================================================================== - Copyright Gernot Starke and aim42 contributors - - 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. - ========================================================================*/ - diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/filesystem/FileUtil.groovy b/htmlSanityCheck-core/src/main/groovy/org/aim42/filesystem/FileUtil.groovy deleted file mode 100644 index 90041df4..00000000 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/filesystem/FileUtil.groovy +++ /dev/null @@ -1,55 +0,0 @@ -package org.aim42.filesystem - -import java.nio.file.Path - -/************************************************************************ - * This is free software - without ANY guarantee! - * - * - * Copyright Dr. Gernot Starke, arc42.org - * - * 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. - * - *********************************************************************** */ - -class FileUtil { - - - static File commonPath(Set files) { - if (!files) { - return null - } - if (files.size() == 1) { - return files.first().parentFile - } - Path initial = files.first().toPath().parent - Path common = files.collect { it.toPath() }.inject(initial) { - Path acc, Path val -> - if (!acc || !val) { - return null - } - - int idx = 0 - Path p1 = acc, p2 = val.parent - def iter1 = p1.iterator(), iter2 = p2.iterator() - while (iter1.hasNext() && iter2.hasNext() && iter1.next() == iter2.next()) { - idx++ - } - if (idx == 0) { - return null - } - p1.subpath(0, idx) - } - common ? (initial?.root ? initial.root.resolve(common).toFile() : common.toFile()) : null - } -} diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/AllChecksRunner.groovy b/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/AllChecksRunner.groovy deleted file mode 100644 index aaf7d5e8..00000000 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/AllChecksRunner.groovy +++ /dev/null @@ -1,219 +0,0 @@ -package org.aim42.htmlsanitycheck - -import org.aim42.htmlsanitycheck.check.Checker -import org.aim42.htmlsanitycheck.check.CheckerCreator -import org.aim42.htmlsanitycheck.collect.PerRunResults -import org.aim42.htmlsanitycheck.collect.SinglePageResults -import org.aim42.htmlsanitycheck.html.HtmlPage -import org.aim42.htmlsanitycheck.report.ConsoleReporter -import org.aim42.htmlsanitycheck.report.HtmlReporter -import org.aim42.htmlsanitycheck.report.JUnitXmlReporter -import org.aim42.htmlsanitycheck.report.LoggerReporter -import org.aim42.htmlsanitycheck.report.Reporter -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -// see end-of-file for license information -/** - * Coordinates and runs all available html sanity checks. Convenience class, - * delegates (most) work to an @see ChecksRunner - *

- *

    - *
  1. parse the html file
  2. - *
  3. initialize and run image file checker
  4. - *
  5. - *
  6. - *
- *

- * Uses @see Checker instances (they implement the - * - * template pattern) - **/ - -class AllChecksRunner { - - // we check a collection of files: - private Set filesToCheck = new HashSet() - - // where do we put our results - private File checkingResultsDir - - // where do we put our junit results - private File junitResultsDir - - /** Determines if the report is output to the console. */ - boolean consoleReport = true - - // checker instances - private List checkers - - // keep all results - private PerRunResults resultsForAllPages - - - // keep your own configuration (in case we have multiple parallel instances running...) - private Configuration myConfig - - - private static final Logger logger = LoggerFactory.getLogger(AllChecksRunner.class) - - /** - * runs all available checks - * - */ - - AllChecksRunner( Configuration pConfig ) { - super() - - myConfig = pConfig - - this.filesToCheck = myConfig.getSourceDocuments() - - // TODO: #185 (checker classes shall be detected automatically (aka CheckerFactory) - // CheckerFactory needs the configuration - List checkerClasses = myConfig.getChecksToExecute() - this.checkers = CheckerCreator.createCheckerClassesFrom( checkerClasses, myConfig ) - - this.resultsForAllPages = new PerRunResults() - - this.checkingResultsDir = myConfig.getCheckingResultsDir() - this.junitResultsDir = myConfig.getJunitResultsDir() - - logger.debug("AllChecksRunner created with ${this.checkers.size()} checkers for ${filesToCheck.size()} files") - } - - - - /** - * performs all available checks on pageToCheck - * - */ - PerRunResults performAllChecks() { - - logger.debug "entered performAllChecks" - - filesToCheck.each { file -> - resultsForAllPages.addPageResults( - performChecksForOneFile(file)) - } - - // after all checks, stop the timer... - resultsForAllPages.stopTimer() - - // and then report the results - reportCheckingResultsOnLogger() - if (consoleReport) { - reportCheckingResultsOnConsole() - } - if (checkingResultsDir) { - reportCheckingResultsAsHTML(checkingResultsDir.absolutePath) - } - if (junitResultsDir) { - reportCheckingResultsAsJUnitXml(junitResultsDir.absolutePath) - } - - return resultsForAllPages - - } - - - /** - * performs all configured checks on a single HTML file. - * - * Creates a {@link org.aim42.htmlsanitycheck.collect.SinglePageResults} instance to keep checking results. - */ - protected SinglePageResults performChecksForOneFile(File thisFile) { - - // the currently processed (parsed) HTML page - HtmlPage pageToCheck = HtmlPage.parseHtml(thisFile) - - // initialize results for this page - SinglePageResults collectedResults = - new SinglePageResults( - pageFilePath: thisFile.canonicalPath, - pageFileName: thisFile.name, - pageTitle: pageToCheck.getDocumentTitle(), - pageSize: pageToCheck.documentSize - ) - - // apply every checker to this page - // ToDo: parallelize with GPARS? - checkers.each { checker -> - def singleCheckResults = checker.performCheck(pageToCheck ) - collectedResults.addResultsForSingleCheck(singleCheckResults) - } - - return collectedResults - } - - - /** - * reports results on stdout - * TODO: - */ - private void reportCheckingResultsOnConsole() { - Reporter reporter = new ConsoleReporter(resultsForAllPages) - - reporter.reportFindings() - - } - - /** - * reports results to logger - * TODO: report results to logger - */ - private void reportCheckingResultsOnLogger() { - Reporter reporter = new LoggerReporter(resultsForAllPages, logger) - - reporter.reportFindings() - - } - - /** - * report results in HTML file(s) - */ - private void reportCheckingResultsAsHTML(String resultsDir) { - - Reporter reporter = new HtmlReporter(resultsForAllPages, resultsDir) - reporter.reportFindings() - } - - /** - * report results in JUnit XML - */ - private void reportCheckingResultsAsJUnitXml(String resultsDir) { - - Reporter reporter = new JUnitXmlReporter(resultsForAllPages, resultsDir) - reporter.reportFindings() - } - /** - * runs the checks from the command - * line with default settings... - * @param args - */ - static void main(String[] args) { - // TODO: read parameter from command line - - // empty method is currently marked "prio-2" issue by CodeNarc... we'll ignore that - } - - -} - -/*======================================================================== - Copyright Gernot Starke and aim42 contributors - - 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. - ========================================================================*/ - diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/ConsoleReporter.groovy b/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/ConsoleReporter.groovy deleted file mode 100644 index bd91a20c..00000000 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/ConsoleReporter.groovy +++ /dev/null @@ -1,111 +0,0 @@ -package org.aim42.htmlsanitycheck.report - -import groovy.transform.InheritConstructors -import org.aim42.htmlsanitycheck.ProductVersion -import org.aim42.htmlsanitycheck.collect.PerRunResults -import org.aim42.htmlsanitycheck.collect.SingleCheckResults -import org.aim42.htmlsanitycheck.collect.SinglePageResults - -/************************************************************************ - * This is free software - without ANY guarantee! - * - * - * Copyright 2013, Dr. Gernot Starke, arc42.org - * - * 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. - * - *********************************************************************** */ - -@InheritConstructors -class ConsoleReporter extends Reporter { - protected Closure printer = { line -> println(line) } - - // from AllChecksRunner - create ConsoleReporter with PerRunResults - public ConsoleReporter( PerRunResults runResults ) { - super( runResults ) - } - - - @Override - void initReport() { - Long millis = runResults.checkingTookHowManyMillis() - - printer "********* HTML Sanity Checker findings report *********" - printer "created on $createdOnDate by version ${ProductVersion.getVersion()}" - printer "checking took $millis msecs." - printer "" - } - - - @Override - void reportOverallSummary() { - int percentageSuccessful = - SummarizerUtil.percentSuccessful( totalNrOfChecks(), totalNrOfFindings()) - - String pageStr = (totalNrOfPages() > 1) ? "pages" : "page" - String issueStr = (totalNrOfFindings() > 1) ? "issues" : "issue" - - printer "Summary for all pages:" - printer "======================" - printer "checked ${totalNrOfChecks()} items on ${totalNrOfPages()} $pageStr, " - printer "found ${totalNrOfFindings()} $issueStr, $percentageSuccessful% successful." - printer "-" * 50 - } - - - @Override - void reportPageSummary( SinglePageResults pageResult ) { - printer "Summary for file ${pageResult.pageFileName}\n" - printer "page path : " + pageResult.pageFilePath - printer "page title : " + pageResult.pageTitle - printer "page size : " + pageResult.pageSize + " bytes" - - } - - - - @Override - protected void reportPageFooter() { - printer "="*50 - } - - @Override - protected void reportSingleCheckSummary( SingleCheckResults checkResults ) { - printer "\n" - printer "-"*50 - checkResults.each { result -> - printer "Results for ${result.whatIsChecked}" - printer "${result.nrOfItemsChecked} $result.sourceItemName checked," - printer "${result.nrOfProblems()} $result.targetItemName found.\n" - printer "${result.generalRemark}" - } - } - - @Override - protected void reportSingleCheckDetails( SingleCheckResults checkResults ) { - checkResults.findings.each { finding -> - printer finding.toString() - - } - - printer "-" * 50 - - } - - @Override - void closeReport() { - printer "thanx for using HtmlSanityChecker." - } - -} - diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/HtmlReporter.groovy b/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/HtmlReporter.groovy deleted file mode 100644 index bfd3a80c..00000000 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/HtmlReporter.groovy +++ /dev/null @@ -1,426 +0,0 @@ -// see end-of-file for license information -package org.aim42.htmlsanitycheck.report - -import groovy.util.logging.Slf4j -import org.aim42.htmlsanitycheck.collect.PerRunResults -import org.aim42.htmlsanitycheck.collect.SingleCheckResults -import org.aim42.htmlsanitycheck.collect.SinglePageResults - -import java.nio.file.Files -import java.nio.file.StandardCopyOption - -/** - * write the findings report to HTML - */ -@Slf4j -public class HtmlReporter extends Reporter { - - // - private final static String REPORT_FILENAME = "index.html" - private String resultsOutputDir - - private String completeOutputFilePath - - private FileWriter writer - - - HtmlReporter(PerRunResults runResults, String outputDir) { - super(runResults) - this.resultsOutputDir = outputDir - } - - - @Override - void initReport() { - - // determine a path where we can write our output file... - completeOutputFilePath = determineOutputFilePath(resultsOutputDir, REPORT_FILENAME) - - // init the private writer object - writer = createWriter(completeOutputFilePath) - - initWriterWithHtmlHeader() - - - // we have a writer, therefore an existing output directory! - - def requiredResourceFiles = ["arrow-up.png", "htmlsanitycheck-logo.png", - "htmlsc-style.css", "scroll.css", - "jquery.min.js"] - copyRequiredResourceFiles( resultsOutputDir, requiredResourceFiles ) - } - - - /* - we need some static files next to the report html.. css, js and logo stuff. - - Originally I posted this as a question to the gradle forum: - http://forums.gradle.org/gradle/topics/-html-checking-plugin-how-to-copy-required-css-to-output-directory - - Answers were: - http://stackoverflow.com/questions/10308221/how-to-copy-file-inside-jar-to-outside-the-jar - - https://github.com/gradle/gradle/blob/master/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ReportGenerator.java#L50-50 - - */ - private void copyResourceFromJarToDirectory(String resourceName, File outputDirectory) { - URL resource = getClass().getClassLoader().getResource(resourceName); - - // https://github.com/aim42/htmlSanityCheck/issues/305 - // unfortunately we currently are not able to use try-with-ressources yet. - InputStream stream = null; - try { - stream = resource.openStream() - Files.copy(stream, new File(outputDirectory, resourceName).toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } finally { - try { - stream.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - - - private initWriterWithHtmlHeader() { - writer << """ - - -HTML Sanity Check Results - - - - - - - - - -""" - } - - /* - * copy css, javaScript and image/icon files to the html output directory, - * - */ - private void copyRequiredResourceFiles( String outputDirectoryPath, List requiredResources ) { - File outputDir = new File( outputDirectoryPath ) - - requiredResources.each { resource -> - copyResourceFromJarToDirectory( resource, outputDir ) - } - - } - - - - /* - create and open a FileWriter to write the generated HTML - */ - - private FileWriter createWriter(String directoryAndFileName) { - writer = new FileWriter(directoryAndFileName) - - } - - @Override - void reportOverallSummary() { - - writer << "\"htmlSC\"" - writer << "

HTML Sanity Check Results

" - - writer << overallSummaryInfoBox() - - writer << allPagesSummaryTable() - - writer << "
" - - } - - private String overallSummaryInfoBox() { - int percentageSuccessful = SummarizerUtil.percentSuccessful(totalNrOfChecks(), totalNrOfFindings()) - String pageStr = (totalNrOfPages() > 1) ? "pages" : "page" - String issueStr = (totalNrOfFindings() > 1) ? "issues" : "issue" - Float f = runResults.checkingTookHowManyMillis() / 1000 - String duration = f.trunc(3).toString() + "sec" - - - return (infoBoxHeader() - // pages - + infoBoxColumn("pages", totalNrOfPages().toString(), pageStr) - // checks - + infoBoxColumn("checks", totalNrOfChecks().toString(), "checks") - // findings/issues - + infoBoxColumn("findings", totalNrOfFindings().toString(), issueStr) - // timer - + infoBoxColumn("duration", duration, "duration") - - + infoBoxSeparator() - - + infoBoxPercentage(percentageSuccessful) - - + infoBoxFooter()) - } - - // TODO: write summary table for pages - private String allPagesSummaryTable() { - - return ("

Results by Page

" + - allPagesSummaryTableHead() + - allPagesSummaryTableBody() + - allPagesSummaryTableFooter()) - - } - - private static String allPagesSummaryTableHead() { - return """ - - - - - - - """ - - } - - private String allPagesSummaryTableBody() { - String resultStr = "" - pageResults.each { pageResult -> - resultStr += pageSummaryTableLine(pageResult) - } - return resultStr - } - - private static String pageSummaryTableLine(SinglePageResults spr) { - String classStr = (spr.nrOfFindingsOnPage() == 0) ? "success" : "failures" - int percentageSuccessful = - SummarizerUtil.percentSuccessful(spr.nrOfItemsCheckedOnPage(), spr.nrOfFindingsOnPage()) - - String pageHref = - CreateLinkUtil.convertToLink(spr.pageFileName) - - return """ - - - - - - """ - } - - private static String allPagesSummaryTableFooter() { - return """\n - -
Page Checks Findings Success rate
${spr.pageFileName}${spr.nrOfItemsCheckedOnPage()}${spr.nrOfFindingsOnPage()}$percentageSuccessful%
""" - } - - private static String infoBoxHeader() { - // outer table - return """\n -\n" - } - - private static String infoBoxPercentage(int percentageSuccessful) { - String percentageClass = (percentageSuccessful != 100) ? "infoBox failures" : "infoBox success" - return """ - """ - } - - private static String infoBoxFooter() { - return "
-\n""" - } - - - private static String infoBoxColumn(String id, String countStr, String label) { - return """\n - """ - } - - private static String infoBoxSeparator() { - return "\n
\n -
\n -
$countStr
\n - $label
\n -
\n -
$percentageSuccessful%
- successful
-
\n\n" - } - - - @Override - void reportPageSummary(SinglePageResults pageResult) { - - String pageID = - CreateLinkUtil.convertToLink(pageResult.pageFileName) - - writer << """\n\n

Results for ${pageResult.pageFileName}

\n""" - - writer << """location : ${pageResult.pageFilePath}

""" - - // generate the InfoBox for this page - - int nrOfItemsChecked = pageResult.nrOfItemsCheckedOnPage() - int nrOfFindings = pageResult.nrOfFindingsOnPage() - - int percentageSuccessful = - SummarizerUtil.percentSuccessful( - nrOfItemsChecked, - nrOfFindings) - String issueStr = (nrOfFindings > 1) ? "issues" : "issue" - - writer << infoBoxHeader() - - // size - int pageSize = pageResult.pageSize - String sizeUnit = (pageSize >= 1_000_000) ? "MByte" : "kByte" - - String pageSizeStr = SummarizerUtil.threeDigitTwoDecimalPlaces(pageSize) - - writer << infoBoxColumn("size", pageSizeStr, sizeUnit) - - // checks - writer << infoBoxColumn("checks", nrOfItemsChecked.toString(), "checks") - - // findings/issues - writer << infoBoxColumn("findings", nrOfFindings.toString(), issueStr) - // end left table - writer << infoBoxSeparator() - - writer << infoBoxPercentage(percentageSuccessful) - - writer << infoBoxFooter() - - } - - - @Override - protected void reportPageFooter() { - writer << "


" - - } - - - // TODO: add "GeneralRemark" to output, if it exists - @Override - protected void reportSingleCheckSummary(SingleCheckResults singleCheckResults) { - - singleCheckResults.each { result -> - // colorize failed checks with failure-class, successful with success-class - String headerClass = (result.nrOfProblems() > 0) ? "failures" : "success" - - writer << """\n

${result.whatIsChecked}

""" - - writer << """\n\n - ${result.nrOfItemsChecked} $result.sourceItemName checked, - ${result.nrOfProblems()} $result.targetItemName found.
- ${result.generalRemark}""" - - } - } - - protected void reportSingleCheckDetails(SingleCheckResults checkResults) { - if (checkResults.findings.size() > 0) { - writer << "\n
    " - checkResults.findings.each { finding -> - writer << """\n
  • ${finding.toString()}
  • """ - } - writer << "\n
\n" - } - } - - /** - * tries to find a writable directory. First tries dirName, - * if that does not work takes User.dir as second choice. - * @param dirName : e.g. /Users/aim42/projects/htmlsc/build/report/htmlchecks - * @param fileName : default "index.html" - * @return complete path to a writable file that does not currently exist. - */ - private static String determineOutputFilePath(String dirName, String fileName) { - String realPath = "${dirName}${File.separator}" - - File realDir = new File(realPath) - - if (realDir.isDirectory() && realDir.canWrite()) { - realPath = realPath + fileName - } else realPath = System.getProperty("user.dir") + File.separator + fileName - - // make sure we really have an existing file! - assert realDir.exists() - - return realPath - } - - - @Override - void closeReport() { - writer << """ - -
- to top -
- -""" - writer << "" - writer.flush() - - log.info "wrote report to ${resultsOutputDir}${File.separatorChar}$REPORT_FILENAME" - } - - - - -} -/*====================================================================== - -Copyright Gernot Starke and aim42 contributors - - 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. - ======================================================================*/ diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.groovy b/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.groovy deleted file mode 100644 index d6f42fc3..00000000 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.groovy +++ /dev/null @@ -1,101 +0,0 @@ -package org.aim42.htmlsanitycheck.report - -import groovy.transform.InheritConstructors -import groovy.xml.MarkupBuilder -import org.aim42.htmlsanitycheck.collect.PerRunResults -import org.aim42.htmlsanitycheck.collect.SingleCheckResults -import org.aim42.htmlsanitycheck.collect.SinglePageResults - -/************************************************************************ - * This is free software - without ANY guarantee! - * - * - * Copyright 2016, Patrick Double, https://github.com/double16 - * - * 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. - * - *********************************************************************** */ - -/** - * Write the findings report to JUnit XML. Allows tools processing JUnit to - * include the findings. - */ -@InheritConstructors -class JUnitXmlReporter extends Reporter { - File outputPath - - JUnitXmlReporter( PerRunResults runResults, String outputPath) { - super( runResults ) - this.outputPath = new File(outputPath) - } - - @Override - void initReport() { - if (!outputPath.canWrite() && !outputPath.mkdirs()) { - throw new IOException("Cannot create or write to ${outputPath}") - } - } - - @Override - void reportOverallSummary() { - } - - @Override - void reportPageSummary( SinglePageResults pageResult ) { - String name = pageResult.pageFilePath ?: pageResult.pageTitle ?: UUID.randomUUID() - String sanitizedPath = name.replaceAll(~/[^A-Za-z0-9_-]+/, '_') - File testOutputFile = new File(outputPath, "TEST-unit-html-${sanitizedPath}.xml") - testOutputFile.withWriter { writer -> - def builder = new MarkupBuilder(writer) - builder.doubleQuotes = true - builder.testsuite( - tests: pageResult.nrOfItemsCheckedOnPage(), - failures: pageResult.nrOfFindingsOnPage(), - errors:0, - time:0, - name:name) { - pageResult.singleCheckResults?.each { singleCheckResult -> - testcase( - assertions:singleCheckResult.nrOfItemsChecked, - time:0, - name:(singleCheckResult.whatIsChecked ?: '') - ) { - singleCheckResult.findings.each { finding -> - failure( - type:"${[singleCheckResult.sourceItemName, singleCheckResult.targetItemName].findAll().join(' - ')}", - message:finding.whatIsTheProblem, - finding.suggestions?.join(', ') ?: '') - } - } - } - } - } - } - - @Override - protected void reportPageFooter() { - } - - @Override - protected void reportSingleCheckSummary( SingleCheckResults checkResults ) { - } - - @Override - protected void reportSingleCheckDetails( SingleCheckResults checkResults ) { - } - - @Override - void closeReport() { - } - -} diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/filesystem/FileCollector.java b/htmlSanityCheck-core/src/main/java/org/aim42/filesystem/FileCollector.java new file mode 100644 index 00000000..26e41a43 --- /dev/null +++ b/htmlSanityCheck-core/src/main/java/org/aim42/filesystem/FileCollector.java @@ -0,0 +1,102 @@ +package org.aim42.filesystem; + +// see end-of-file for license information + +// TODO: add exclude-patterns + +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class FileCollector { + + private static final Pattern IMAGE_FILE_EXTENSION_PATTERN = Pattern.compile("(?i).+\\.(jpg|jpeg|png|gif|bmp|svg)?$"); + + public static boolean isImageFileName(String fileName) { + return IMAGE_FILE_EXTENSION_PATTERN.matcher(fileName).matches(); + } + + /** + * @return all configured image files as Set + */ + public static Set getConfiguredImageFiles(File srcDir, Set sourceDocs) { + // first case: no document names given -> return all html files + // in a directory tree + if (sourceDocs == null || sourceDocs.isEmpty()) { + return getAllImageFilesFromDirectory(srcDir); + } else { + return getAllConfiguredImageFiles(srcDir, sourceDocs); + } + } + + /** + * Returns all image files in a given directory. + * (recursively looks in subdirectories) + * + * @param dir where to look for matching files + * @return all files with the appropriate extension + */ + public static Set getAllImageFilesFromDirectory(File dir) { + Set files = new HashSet<>(); + File[] foundFiles = dir.listFiles(); + + if (foundFiles != null) { + for (File file : foundFiles) { + if (file.isFile()) { + if (isImageFileName(file.getName())) { + files.add(file); + } + } else if (file.isDirectory()) { + files.addAll(getAllImageFilesFromDirectory(file)); + } + } + } + + return files; + } + + /** + * Returns all configured image files from @param srcDocs + * which really exist below @param srcDocs + */ + public static Set getAllConfiguredImageFiles(File srcDir, Set srcDocs) { + Set files = new HashSet<>(); + for (String srcFileName : srcDocs) { + File file = new File(srcDir, srcFileName); + if (file.exists() && isImageFileName(file.getName())) { + files.add(file); + } + } + return files; + } + + /** + * Convert Set of Files to Set of file-names without a path prefix! + */ + public static Set collectFileNamesFromFiles(Set files) { + Set fileNames = new HashSet<>(); + for (File file : files) { + fileNames.add(file.getName()); + } + return fileNames; + } +} + +/*======================================================================== + Copyright Gernot Starke and aim42 contributors + + 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. + ========================================================================*/ + diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/AllChecksRunner.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/AllChecksRunner.java new file mode 100644 index 00000000..744dd09a --- /dev/null +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/AllChecksRunner.java @@ -0,0 +1,192 @@ +package org.aim42.htmlsanitycheck; + +import org.aim42.htmlsanitycheck.check.Checker; +import org.aim42.htmlsanitycheck.check.CheckerCreator; +import org.aim42.htmlsanitycheck.collect.PerRunResults; +import org.aim42.htmlsanitycheck.collect.SingleCheckResults; +import org.aim42.htmlsanitycheck.collect.SinglePageResults; +import org.aim42.htmlsanitycheck.html.HtmlPage; +import org.aim42.htmlsanitycheck.report.ConsoleReporter; +import org.aim42.htmlsanitycheck.report.HtmlReporter; +import org.aim42.htmlsanitycheck.report.JUnitXmlReporter; +import org.aim42.htmlsanitycheck.report.LoggerReporter; +import org.aim42.htmlsanitycheck.report.Reporter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Coordinates and runs all available html sanity checks. Convenience class, + * delegates (most) work to a @see ChecksRunner + *

+ *

    + *
  1. parse the html file
  2. + *
  3. initialize and run image file checker
  4. + *
  5. + *
  6. + *
+ *

+ * Uses @see Checker instances (they implement the + * + * template pattern) + **/ + +public class AllChecksRunner { + + // we check a collection of files: + private final Set filesToCheck; + + // where do we put our results? + private final File checkingResultsDir; + + // where do we put our junit results? + private final File junitResultsDir; + + // checker instances + private final List checkers; + + // keep all results + private final PerRunResults resultsForAllPages; + + private static final Logger logger = LoggerFactory.getLogger(AllChecksRunner.class); + + /** + * runs all available checks + * + */ + + public AllChecksRunner(Configuration configuration) { + super(); + + this.filesToCheck = configuration.getSourceDocuments(); + + // TODO: #185 (checker classes shall be detected automatically (aka CheckerFactory) + // CheckerFactory needs the configuration + List> checkerClasses = configuration.getChecksToExecute(); + this.checkers = CheckerCreator.createCheckerClassesFrom(checkerClasses, configuration); + + this.resultsForAllPages = new PerRunResults(); + + this.checkingResultsDir = configuration.getCheckingResultsDir(); + this.junitResultsDir = configuration.getJunitResultsDir(); + + logger.debug("AllChecksRunner created with " + this.checkers.size() + " checkers for " + filesToCheck.size() + " files"); + } + + /** + * Performs all available checks on pageToCheck + * + */ + public PerRunResults performAllChecks() throws IOException { + + logger.debug("entered performAllChecks"); + + for (File file : filesToCheck) { + resultsForAllPages.addPageResults( + performChecksForOneFile(file)); + } + + // after all checks, stop the timer... + resultsForAllPages.stopTimer(); + + // and then report the results + reportCheckingResultsOnLogger(); + /* Determines if the report is output to the console. */ + reportCheckingResultsOnConsole(); + if (checkingResultsDir != null) { + reportCheckingResultsAsHTML(checkingResultsDir.getAbsolutePath()); + } + if (junitResultsDir != null) { + reportCheckingResultsAsJUnitXml(junitResultsDir.getAbsolutePath()); + } + + return resultsForAllPages; + + } + + /** + * Performs all configured checks on a single HTML file. + *

+ * Creates a {@link org.aim42.htmlsanitycheck.collect.SinglePageResults} instance to keep checking results. + */ + protected SinglePageResults performChecksForOneFile(File thisFile) throws IOException { + + // the currently processed (parsed) HTML page + HtmlPage pageToCheck = HtmlPage.parseHtml(thisFile); + + // initialize results for this page + SinglePageResults collectedResults = + new SinglePageResults( + thisFile.getName(), + thisFile.getCanonicalPath(), + pageToCheck.getDocumentTitle(), + pageToCheck.getDocumentSize(), + new ArrayList<>() + ); + + // apply every checker to this page + // ToDo: parallelize with GPARS? + + for (Checker checker : checkers) { + SingleCheckResults singleCheckResults = checker.performCheck(pageToCheck); + collectedResults.addResultsForSingleCheck(singleCheckResults); + } + + return collectedResults; + } + + /** + * reports results on stdout + * TODO: + */ + private void reportCheckingResultsOnConsole() { + Reporter reporter = new ConsoleReporter(resultsForAllPages); + + reporter.reportFindings(); + + } + + /** + * reports results to logger + * TODO: report results to logger + */ + private void reportCheckingResultsOnLogger() { + Reporter reporter = new LoggerReporter(resultsForAllPages, logger); + + reporter.reportFindings(); + + } + + /** + * Report results in HTML file(s) + */ + private void reportCheckingResultsAsHTML(String resultsDir) { + + Reporter reporter = new HtmlReporter(resultsForAllPages, resultsDir); + reporter.reportFindings(); + } + + /** + * Report results in JUnit XML + */ + private void reportCheckingResultsAsJUnitXml(String resultsDir) { + + Reporter reporter = new JUnitXmlReporter(resultsForAllPages, resultsDir); + reporter.reportFindings(); + } + + /** + * Runs the checks from the command + * line with default settings... + */ + public static void main(String[] args) { + // TODO: read parameter from command line + + // Empty method is currently marked "prio-2" issue by CodeNarc... we'll ignore that + } +} \ No newline at end of file diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/Finding.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/Finding.java index 7f470181..cb9ec9eb 100644 --- a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/Finding.java +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/Finding.java @@ -9,6 +9,7 @@ import java.util.List; /** + * A single "finding" from any check, i.e.: * A single "finding" from any check, i.e.: * - a missing image file * - a missing label/id/bookmark (== broken link) @@ -22,6 +23,7 @@ public class Finding { int nrOfOccurrences;// how often does this specific finding occur in the checked-page // suggestions are ordered: getAt(0) yields the best, getAt(1) the second and so forth @Setter + @Getter List suggestions; public Finding() { diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/SinglePageResults.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/SinglePageResults.java index abc4f0a4..b9785a35 100644 --- a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/SinglePageResults.java +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/collect/SinglePageResults.java @@ -1,14 +1,17 @@ package org.aim42.htmlsanitycheck.collect; +import lombok.AllArgsConstructor; + import java.util.ArrayList; import java.util.List; /** * Collects checking results {@link Finding} of a single html page. *

- * Contains information about the page itself, e.g. its filename and title. + * Contains information about the page itself, e.g., its filename and title. * Instances of this class will be contained in {@link SingleCheckResults} */ +@AllArgsConstructor public class SinglePageResults implements PageResults { public String pageFileName; // from where we read the HTML @@ -22,7 +25,7 @@ public class SinglePageResults implements PageResults { // some variants for construction public SinglePageResults() { - this.singleCheckResults = new ArrayList(); + this.singleCheckResults = new ArrayList<>(); this.pageFileName = ""; this.pageFilePath = ""; this.pageTitle = ""; @@ -36,7 +39,7 @@ public SinglePageResults(SingleCheckResults scr) { /** - * allows to add the results of a single check + * Allows adding the results of a single check **/ public void addResultsForSingleCheck(SingleCheckResults resultsForSingleCheck) { @@ -64,19 +67,18 @@ public String getPageFilePath() { // query the results @Override public int nrOfItemsCheckedOnPage() { - return singleCheckResults.stream().map(it -> it.getNrOfItemsChecked()).reduce(0, (a, b) -> a + b); + return singleCheckResults.stream().map(SingleCheckResults::getNrOfItemsChecked).reduce(0, Integer::sum); } @Override public int nrOfFindingsOnPage() { - return singleCheckResults.stream().map(it -> it.nrOfProblems()).reduce(0, (a, b) -> a + b); + return singleCheckResults.stream().map(SingleCheckResults::nrOfProblems).reduce(0, Integer::sum); } /** - * returns the number of distinct checker types that have run + * Returns the number of distinct checker types that have run * (by the number of SingleCheckResults available) * - * @return */ @Override public int howManyCheckersHaveRun() { diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/ConsoleReporter.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/ConsoleReporter.java new file mode 100644 index 00000000..cd75c9b2 --- /dev/null +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/ConsoleReporter.java @@ -0,0 +1,98 @@ +package org.aim42.htmlsanitycheck.report; + +import org.aim42.htmlsanitycheck.ProductVersion; +import org.aim42.htmlsanitycheck.collect.Finding; +import org.aim42.htmlsanitycheck.collect.PerRunResults; +import org.aim42.htmlsanitycheck.collect.SingleCheckResults; +import org.aim42.htmlsanitycheck.collect.SinglePageResults; + +import java.util.Collections; +import java.util.function.Consumer; + +/** + * This is free software - without ANY guarantee! + *

+ * Copyright 2013, Dr. Gernot Starke, arc42.org + *

+ * 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 + *

+ * ... + *

+ * 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. + */ + +public class ConsoleReporter extends Reporter { + protected Consumer printer = System.out::println; + + // from AllChecksRunner - create ConsoleReporter with PerRunResults + public ConsoleReporter(PerRunResults runResults) { + super(runResults); + } + + @Override + public void initReport() { + Long millis = runResults.checkingTookHowManyMillis(); + + printer.accept("********* HTML Sanity Checker findings report *********"); + printer.accept(String.format("created on %s by version %s", createdOnDate, ProductVersion.getVersion())); + printer.accept(String.format("checking took %s msecs.", millis)); + printer.accept(""); + } + + @Override + protected void reportOverallSummary() { + int percentageSuccessful = SummarizerUtil.percentSuccessful(totalNrOfChecks(), totalNrOfFindings()); + + String pageStr = (totalNrOfPages() > 1) ? "pages" : "page"; + String issueStr = (totalNrOfFindings() > 1) ? "issues" : "issue"; + + printer.accept("Summary for all pages:"); + printer.accept("======================"); + printer.accept(String.format("checked %d items on %d %s,", totalNrOfChecks(), totalNrOfPages(), pageStr)); + printer.accept(String.format("found %d %s, %d%% successful.", totalNrOfFindings(), issueStr, percentageSuccessful)); + printer.accept(String.join("", Collections.nCopies(50, "-"))); + } + + @Override + protected void reportPageSummary(SinglePageResults pageResult) { + printer.accept(String.format("Summary for file %s\n", pageResult.getPageFileName())); + printer.accept(String.format("page path : %s", pageResult.getPageFilePath())); + printer.accept(String.format("page title : %s", pageResult.getPageTitle())); + printer.accept(String.format("page size : %d bytes", pageResult.pageSize)); + } + + @Override + protected void reportPageFooter() { + printer.accept(String.join("", Collections.nCopies(50, "="))); + } + + @Override + protected void reportSingleCheckSummary(SingleCheckResults singleCheckResults) { + for (Finding finding : singleCheckResults.getFindings()) { + printer.accept(finding.toString()); + } + + printer.accept(String.join("", Collections.nCopies(50, "-"))); + } + + @Override + protected void reportSingleCheckDetails(SingleCheckResults singleCheckResults) { + for (Finding finding : singleCheckResults.findings) { + printer.accept(finding.toString()); + } + printer.accept("\n"); + printer.accept(String.join("", Collections.nCopies(50, "-"))); + } + + @Override + protected void closeReport() { + printer.accept("thanx for using HtmlSanityChecker."); + } + +} \ No newline at end of file diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/HtmlReporter.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/HtmlReporter.java new file mode 100644 index 00000000..71bd3d3e --- /dev/null +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/HtmlReporter.java @@ -0,0 +1,425 @@ +package org.aim42.htmlsanitycheck.report; + +import org.aim42.htmlsanitycheck.collect.PerRunResults; +import org.aim42.htmlsanitycheck.collect.SingleCheckResults; +import org.aim42.htmlsanitycheck.collect.SinglePageResults; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; + +public class HtmlReporter extends Reporter { + + private static final Logger log = LoggerFactory.getLogger(HtmlReporter.class); + + private static final String REPORT_FILENAME = "index.html"; + private String resultsOutputDir; + private FileWriter writer; + + public void write(String content) { + try { + writer.write(content); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public HtmlReporter(PerRunResults runResults, String outputDir) { + super(runResults); + this.resultsOutputDir = outputDir; + } + + @Override + public void initReport() { + // determine a path where we can write our output file... + File outputFile = createOutputFile(resultsOutputDir, REPORT_FILENAME); + + // init the private writer object + try { + writer = createWriter(outputFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + + initWriterWithHtmlHeader(); + + // we have a writer, therefore an existing output directory! + + List requiredResourceFiles = Arrays.asList("arrow-up.png", "htmlsanitycheck-logo.png", + "htmlsc-style.css", "scroll.css", + "jquery.min.js"); + copyRequiredResourceFiles(resultsOutputDir, requiredResourceFiles); + } + + /* + we need some static files next to the report html.. css, js and logo stuff. + + Originally I posted this as a question to the gradle forum: + http://forums.gradle.org/gradle/topics/-html-checking-plugin-how-to-copy-required-css-to-output-directory + + Answers were: + http://stackoverflow.com/questions/10308221/how-to-copy-file-inside-jar-to-outside-the-jar + + https://github.com/gradle/gradle/blob/master/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/results/ReportGenerator.java#L50-50 + + */ + private void copyResourceFromJarToDirectory(String resourceName, File outputDirectory) { + URL resource = getClass().getClassLoader().getResource(resourceName); + + // https://github.com/aim42/htmlSanityCheck/issues/305 + // unfortunately we currently are not able to use try-with-ressources yet. + InputStream stream = null; + try { + stream = resource.openStream(); + Files.copy(stream, new File(outputDirectory, resourceName).toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } finally { + try { + stream.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + + private void initWriterWithHtmlHeader() { + write( + "\n" + + "\n" + + "HTML Sanity Check Results\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"); + } + + /* + * copy css, javaScript and image/icon files to the html output directory, + * + */ + private void copyRequiredResourceFiles(String outputDirectoryPath, List requiredResources) { + File outputDir = new File(outputDirectoryPath); + + requiredResources.forEach(resource -> copyResourceFromJarToDirectory(resource, outputDir)); + } + + + /* + create and open a FileWriter to write the generated HTML + */ + + private FileWriter createWriter(File outputFile) throws IOException { + return new FileWriter(outputFile); + } + + @Override + public void reportOverallSummary() { + write("\"htmlSC\""); + write("

HTML Sanity Check Results

"); + write(overallSummaryInfoBox()); + write(allPagesSummaryTable()); + write("
"); + } + + private String overallSummaryInfoBox() { + int percentageSuccessful = SummarizerUtil.percentSuccessful(totalNrOfChecks(), totalNrOfFindings()); + String pageStr = (totalNrOfPages() > 1) ? "pages" : "page"; + String issueStr = (totalNrOfFindings() > 1) ? "issues" : "issue"; + Float f = Float.valueOf(runResults.checkingTookHowManyMillis()) / 1000; + String duration = String.format("%.3f", f) + "sec"; + + return (infoBoxHeader() + // pages + + infoBoxColumn("pages", String.valueOf(totalNrOfPages()), pageStr) + // checks + + infoBoxColumn("checks", String.valueOf(totalNrOfChecks()), "checks") + // findings/issues + + infoBoxColumn("findings", String.valueOf(totalNrOfFindings()), issueStr) + // timer + + infoBoxColumn("duration", duration, "duration") + + infoBoxSeparator() + + infoBoxPercentage(percentageSuccessful) + + infoBoxFooter()); + } + + // TODO: write summary table for pages + private String allPagesSummaryTable() { + return ("

Results by Page

" + + allPagesSummaryTableHead() + + allPagesSummaryTableBody() + + allPagesSummaryTableFooter()); + + } + + private static String allPagesSummaryTableHead() { + return "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " "; + } + + private String allPagesSummaryTableBody() { + StringBuilder resultStr = new StringBuilder(); + for (SinglePageResults pageResult : pageResults) { + resultStr.append(pageSummaryTableLine(pageResult)); + } + return resultStr.toString(); + } + + private static String pageSummaryTableLine(SinglePageResults spr) { + String classStr = (spr.nrOfFindingsOnPage() == 0) ? "success" : "failures"; + int percentageSuccessful = + SummarizerUtil.percentSuccessful(spr.nrOfItemsCheckedOnPage(), spr.nrOfFindingsOnPage()); + + String pageHref = + CreateLinkUtil.convertToLink(spr.pageFileName); + + return String.format( + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "", + classStr, pageHref, spr.pageFileName, spr.nrOfItemsCheckedOnPage(), spr.nrOfFindingsOnPage(), classStr, percentageSuccessful); + } + + private static String allPagesSummaryTableFooter() { + return "\n" + + "
Page Checks Findings Success rate
%s%d%d%d%%
"; + } + + private static String infoBoxHeader() { + return "\n\n"; + } + + private static String infoBoxPercentage(int percentageSuccessful) { + String percentageClass = (percentageSuccessful != 100) ? "infoBox failures" : "infoBox success"; + return String.format( + "\n", + percentageClass, percentageSuccessful + ); + } + + private static String infoBoxFooter() { + return "
\n" + + "\n" + + ""; + } + + private static String infoBoxColumn(String id, String countStr, String label) { + return String.format( + "\n", + id, countStr, label + ); + } + + private static String infoBoxSeparator() { + return "\n" + + "
\n" + + "
\n" + + "
%s
\n" + + " %s
\n" + + "
\n" + + "
\n" + + "
%d%%
\n" + + "successful
\n" + + "
\n" + + "\n"; + } + + @Override + protected void reportPageSummary(SinglePageResults pageResult) { + String pageID = CreateLinkUtil.convertToLink(pageResult.pageFileName); + + + write(String.format( + "\n\n

Results for %s

\n", + pageID, pageResult.pageFileName + )); + + write(String.format( + "location : %s

\n", + pageResult.pageFilePath + )); + + int nrOfItemsChecked = pageResult.nrOfItemsCheckedOnPage(); + int nrOfFindings = pageResult.nrOfFindingsOnPage(); + + int percentageSuccessful = SummarizerUtil.percentSuccessful( + nrOfItemsChecked, + nrOfFindings + ); + String issueStr = (nrOfFindings > 1) ? "issues" : "issue"; + + write(infoBoxHeader()); + + int pageSize = pageResult.pageSize; + String sizeUnit = (pageSize >= 1_000_000) ? "MByte" : "kByte"; + + String pageSizeStr = String.valueOf(SummarizerUtil.threeDigitTwoDecimalPlaces(pageSize)); + + write(infoBoxColumn("size", pageSizeStr, sizeUnit)); + + write(infoBoxColumn("checks", String.valueOf(nrOfItemsChecked), "checks")); + + write(infoBoxColumn("findings", String.valueOf(nrOfFindings), issueStr)); + + write(infoBoxSeparator()); + + write(infoBoxPercentage(percentageSuccessful)); + + write(infoBoxFooter()); + } + + @Override + protected void reportPageFooter() { + String footerContent = "Your footer content here"; + + write(String.format("%s\n", footerContent)); + } + + // TODO: add "GeneralRemark" to output, if it exists + @Override + protected void reportSingleCheckSummary(SingleCheckResults singleCheckResults) { + String headerClass = (singleCheckResults.nrOfProblems() > 0) ? "failures" : "success"; + + write(String.format( + "\n

%s

\n\n", + headerClass, singleCheckResults.getWhatIsChecked() + )); + + write(String.format( + "%d %s checked,\n" + + "%d %s found.
\n" + + "%s\n", + singleCheckResults.getNrOfItemsChecked(), + singleCheckResults.getSourceItemName(), + singleCheckResults.nrOfProblems(), + singleCheckResults.getTargetItemName(), + singleCheckResults.getGeneralRemark() + )); + } + + @Override + protected void reportSingleCheckDetails(SingleCheckResults checkResults) { + if (checkResults.getFindings().size() > 0) { + + write("\n
    \n"); + checkResults.getFindings().forEach(finding -> { + + write(String.format("
  • %s
  • \n", finding.toString())); + }); + write("
\n"); + } + } + + /** + * tries to find a writable directory. First tries dirName, + * if that does not work takes User.dir as second choice. + * + * @param dirName : e.g. /Users/aim42/projects/htmlsc/build/report/htmlchecks + * @param fileName : default "index.html" + * @return complete path to a writable file that does not currently exist. + */ + private File createOutputFile(String dirName, String fileName) { + File outputFolder = new File(dirName); + + if (!outputFolder.isDirectory() || !outputFolder.canWrite()) { + outputFolder = new File (System.getProperty("user.dir")); + log.warn("Could not write to '{}', using '{}' instead.", dirName, outputFolder); + } + + // make sure we really have an existing file! + File outputFile = new File(outputFolder, fileName); + + return outputFile; + } + + + @Override + protected void closeReport() { + try { + write(String.format( + "\n" + + "
\n" + + "\"to\n" + + "
\n" + + "
\n" + + "Generated by htmlSanityCheck (version %s) at %s\n" + + "
\n", createdByHSCVersion, createdOnDate + )); + write("\n"); + writer.flush(); + + String logMessage = String.format( + "wrote report to %s%s%s\n", + resultsOutputDir, File.separatorChar, REPORT_FILENAME + ); + log.info(logMessage); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} +/*====================================================================== + +Copyright Gernot Starke and aim42 contributors + + 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. + ======================================================================*/ diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.java new file mode 100644 index 00000000..bd6facb7 --- /dev/null +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.java @@ -0,0 +1,116 @@ +package org.aim42.htmlsanitycheck.report; + +import org.aim42.htmlsanitycheck.collect.Finding; +import org.aim42.htmlsanitycheck.collect.PerRunResults; +import org.aim42.htmlsanitycheck.collect.SingleCheckResults; +import org.aim42.htmlsanitycheck.collect.SinglePageResults; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.UUID; + +/************************************************************************ + * This is free software - without ANY guarantee! + * + * + * Copyright 2016, Patrick Double, https://github.com/double16 + * + * 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. + * + *********************************************************************** */ + +/** + * Write the findings report to JUnit XML. Allows tools processing JUnit to + * include the findings. + */ +public class JUnitXmlReporter extends Reporter { + File outputPath; + + public JUnitXmlReporter(PerRunResults runResults, String outputPath) { + super(runResults); + this.outputPath = new File(outputPath); + } + + @Override + protected void initReport() { + if (!outputPath.canWrite() && !outputPath.mkdirs()) { + throw new RuntimeException("Cannot create or write to " + outputPath); + } + } + + @Override + protected void reportOverallSummary() { + } + + @Override + protected void reportPageSummary(SinglePageResults pageResult) { + String name = pageResult.getPageFilePath() != null ? pageResult.getPageFilePath() : + (pageResult.getPageTitle() != null ? pageResult.getPageTitle() : UUID.randomUUID().toString()); + String sanitizedPath = name.replaceAll("[^A-Za-z0-9_-]+", "_"); + File testOutputFile = new File(outputPath, "TEST-unit-html-" + sanitizedPath + ".xml"); + + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + try (FileWriter fileWriter = new FileWriter(testOutputFile)) { + XMLStreamWriter writer = factory.createXMLStreamWriter(fileWriter); + + writer.writeStartDocument(); + writer.writeStartElement("testsuite"); + writer.writeAttribute("tests", String.valueOf(pageResult.nrOfItemsCheckedOnPage())); + writer.writeAttribute("failures", String.valueOf(pageResult.nrOfFindingsOnPage())); + writer.writeAttribute("errors", "0"); + writer.writeAttribute("time", "0"); + writer.writeAttribute("name", name); + + for (SingleCheckResults singleCheckResult : pageResult.singleCheckResults) { + writer.writeStartElement("testcase"); + writer.writeAttribute("assertions", String.valueOf(singleCheckResult.getNrOfItemsChecked())); + writer.writeAttribute("time", "0"); + writer.writeAttribute("name", singleCheckResult.getWhatIsChecked() != null ? singleCheckResult.getWhatIsChecked() : ""); + + for (Finding finding : singleCheckResult.getFindings()) { + writer.writeStartElement("failure"); + writer.writeAttribute("type", singleCheckResult.getSourceItemName() + " - " + singleCheckResult.getTargetItemName()); + writer.writeAttribute("message", finding.getWhatIsTheProblem()); + writer.writeCharacters(finding.getSuggestions() != null ? String.join(", ", finding.getSuggestions()) : ""); + writer.writeEndElement(); // end of + } + + writer.writeEndElement(); // end of + } + + writer.writeEndElement(); // end of + writer.writeEndDocument(); + + writer.flush(); + } catch (IOException | XMLStreamException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void reportPageFooter() { + } + + @Override + protected void reportSingleCheckSummary( SingleCheckResults checkResults ) { + } + + @Override + protected void reportSingleCheckDetails( SingleCheckResults checkResults ) { + } + +} diff --git a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/LoggerReporter.groovy b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/LoggerReporter.java similarity index 68% rename from htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/LoggerReporter.groovy rename to htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/LoggerReporter.java index 692fc957..42ed5ef1 100644 --- a/htmlSanityCheck-core/src/main/groovy/org/aim42/htmlsanitycheck/report/LoggerReporter.groovy +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/LoggerReporter.java @@ -1,8 +1,7 @@ -package org.aim42.htmlsanitycheck.report +package org.aim42.htmlsanitycheck.report; -import groovy.transform.InheritConstructors -import org.aim42.htmlsanitycheck.collect.PerRunResults -import org.slf4j.Logger +import org.aim42.htmlsanitycheck.collect.PerRunResults; +import org.slf4j.Logger; /************************************************************************ * This is free software - without ANY guarantee! @@ -24,13 +23,10 @@ * *********************************************************************** */ -@InheritConstructors -class LoggerReporter extends ConsoleReporter { +public class LoggerReporter extends ConsoleReporter { - public LoggerReporter( PerRunResults runResults, Logger logger ) { - super( runResults ) - printer = { line -> logger.info(line) } + public LoggerReporter(PerRunResults runResults, Logger logger) { + super(runResults); + this.printer = logger::info; } - -} - +} \ No newline at end of file diff --git a/htmlSanityCheck-gradle-plugin/build.gradle b/htmlSanityCheck-gradle-plugin/build.gradle index 77f63390..1be1e27e 100755 --- a/htmlSanityCheck-gradle-plugin/build.gradle +++ b/htmlSanityCheck-gradle-plugin/build.gradle @@ -1,6 +1,10 @@ plugins { id 'java-gradle-plugin' id 'jacoco-report-aggregation' + + // The CodeNarc plugin performs quality checks on your project’s + // Groovy source files using CodeNarc and generates reports from these checks. + // id 'codenarc' } tasks.named('check') {