diff --git a/build.gradle.kts b/build.gradle.kts index 77e353b..8651084 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,14 @@ plugins { java application + id("com.jfrog.bintray") version "1.8.5" } group = "org.example" -description = "Allure Server Java Client" +description = "Allure PDF" + +val gradleScriptDir by extra("${rootProject.projectDir}/gradle") tasks.withType(Wrapper::class) { gradleVersion = "6.1.1" @@ -15,21 +18,6 @@ application { mainClassName = "io.eroshenkoam.allure.AllurePDF" } -val startScripts by tasks.existing(CreateStartScripts::class) { - applicationName = "allure-pdf" - classpath = classpath?.plus(files("src/lib/config")) - doLast { - unixScript.writeText(unixScript.readText() - .replace(Regex("(?m)^APP_HOME="), "export APP_HOME=") - .replace("\$(uname)\" = \"Darwin", "") - ) - } -} - -tasks.build { - dependsOn(tasks.installDist) -} - repositories { mavenCentral() } @@ -58,3 +46,39 @@ dependencies { testImplementation("junit:junit:4.12") } + +val sourceJar by tasks.creating(Jar::class) { + from(sourceSets.getByName("main").allSource) + archiveClassifier.set("sources") +} + +val javadocJar by tasks.creating(Jar::class) { + from(tasks.getByName("javadoc")) + archiveClassifier.set("javadoc") +} + +apply(from = "$gradleScriptDir/bintray.gradle") +apply(from = "$gradleScriptDir/maven.gradle") + + +artifacts.add("archives", sourceJar) +artifacts.add("archives", javadocJar) + +tasks.withType(Javadoc::class) { + (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") +} + +val startScripts by tasks.existing(CreateStartScripts::class) { + applicationName = "allure-pdf" + classpath = classpath?.plus(files("src/lib/config")) + doLast { + unixScript.writeText(unixScript.readText() + .replace(Regex("(?m)^APP_HOME="), "export APP_HOME=") + .replace("\$(uname)\" = \"Darwin", "") + ) + } +} + +tasks.build { + dependsOn(tasks.installDist) +} diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle new file mode 100644 index 0000000..5715778 --- /dev/null +++ b/gradle/bintray.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.jfrog.bintray' + +bintray { + user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') + key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') + + configurations = ['archives'] + + publish = true + pkg { + userOrg = 'eroshenkoam' + repo = 'maven' + name = 'allure-pdf' + desc = 'Allure PDF' + websiteUrl = 'https://github.com/eroshenkoam/allure-pdf' + issueTrackerUrl = 'https://github.com/eroshenkoam/allure-pdf' + vcsUrl = 'https://github.com/eroshenkoam/allure-pdf.git' + licenses = ['Apache-2.0'] + + version { + name = project.version + released = new Date() + gpg { + sign = true + } + } + } +} diff --git a/gradle/maven.gradle b/gradle/maven.gradle new file mode 100644 index 0000000..a8ed324 --- /dev/null +++ b/gradle/maven.gradle @@ -0,0 +1,55 @@ +apply plugin: 'maven' + +install { + repositories.mavenInstaller { + customizePom(pom, project) + } +} + +def customizePom(pom, gradleProject) { + pom.whenConfigured { generatedPom -> + // eliminate test-scoped dependencies (no need in maven central poms) + generatedPom.dependencies.removeAll { dep -> + dep.scope == "test" + } + + // sort to make pom dependencies order consistent to ease comparison of older poms + generatedPom.dependencies = generatedPom.dependencies.sort { dep -> + "$dep.scope:$dep.groupId:$dep.artifactId" + } + + // add all items necessary for maven central publication + generatedPom.project { + name = gradleProject.description + description = gradleProject.description + url = "https://github.com/eroshenkoam/allure-pdf" + organization { + name = "Artem Eroshenko" + url = "https://github.com/eroshenkoam" + } + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution = 'repo' + } + } + scm { + url = 'https://github.com/eroshenkoam/allure-pdf' + connection = 'scm:git:git://github.com/eroshenkoam/allure-pdf' + developerConnection = 'scm:git:git://github.com/eroshenkoam/allure-pdf' + } + developers { + developer { + id = 'eroshenkoam' + name = 'Artem Eroshenko' + email = 'eroshenkoam@qameta.io' + } + } + issueManagement { + system = 'Github Issues' + url = 'https://github.com//eroshenkoam/allure-pdf/issues' + } + } + } +} diff --git a/src/main/java/io/eroshenkoam/allure/AllurePDFGenerator.java b/src/main/java/io/eroshenkoam/allure/AllurePDFGenerator.java new file mode 100644 index 0000000..a640c7c --- /dev/null +++ b/src/main/java/io/eroshenkoam/allure/AllurePDFGenerator.java @@ -0,0 +1,206 @@ +package io.eroshenkoam.allure; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.lowagie.text.Chunk; +import com.lowagie.text.Document; +import com.lowagie.text.Element; +import com.lowagie.text.Font; +import com.lowagie.text.ListItem; +import com.lowagie.text.PageSize; +import com.lowagie.text.Paragraph; +import com.lowagie.text.Phrase; +import com.lowagie.text.pdf.PdfWriter; +import io.eroshenkoam.allure.util.PdfUtil; +import io.qameta.allure.model.Attachment; +import io.qameta.allure.model.Label; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.model.TestResult; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static io.eroshenkoam.allure.FontHolder.loadArialFont; +import static io.eroshenkoam.allure.util.PdfUtil.addEmptyLine; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.mapping; + +public class AllurePDFGenerator { + + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ"); + + private final String reportName; + private final Path reportPath; + private final Map filter; + + public AllurePDFGenerator(final String reportName, final Path reportPath) { + this.filter = new HashMap<>(); + this.reportName = reportName; + this.reportPath = reportPath; + } + + public void filter(final Map tags) { + this.filter.putAll(tags); + } + + public void generate(final Path outputPath) throws IOException { + if (!Files.isDirectory(reportPath)) { + log("Input [%s] is not directory", reportPath.toAbsolutePath()); + return; + } + if (Files.notExists(reportPath)) { + log("Results directory [%s] does not exists", reportPath.toAbsolutePath()); + return; + } + + if (Files.notExists(outputPath)) { + log("Creating output file [%s] ...", outputPath.toAbsolutePath()); + Files.createFile(outputPath); + } + + final FontHolder fontHolder = loadArialFont(); + final List files = Files.walk(reportPath) + .filter(s -> s.toString().endsWith("-result.json")) + .collect(Collectors.toList()); + log("Found [%s] rest results ...", files.size()); + + try (final Document document = new Document(PageSize.A4)) { + PdfWriter.getInstance(document, Files.newOutputStream(outputPath)); + document.newPage(); + document.open(); + + addTitlePage(document, reportName, DATE_FORMAT, fontHolder); + + document.newPage(); + final Paragraph tableHeader = new Paragraph("Test Details", fontHolder.header2()); + addEmptyLine(tableHeader, 2); + document.add(tableHeader); + + for (Path path : files) { + final TestResult result = new ObjectMapper().readValue(path.toFile(), TestResult.class); + final Map> labels = result.getLabels().stream().collect( + Collectors.groupingBy(Label::getName, Collectors.mapping(Label::getValue, Collectors.toList())) + ); + boolean shouldSkip = Objects.nonNull(filter) + && !filter.isEmpty() + && !filter.entrySet().stream().allMatch(e -> labels.getOrDefault(e.getKey(), new ArrayList<>()).contains(e.getValue())); + if (!shouldSkip) { + printTestResultDetails(document, result, fontHolder); + } + } + } + } + + private void addTitlePage(final Document document, + final String exportName, + final DateFormat dateFormat, + final FontHolder fontHolder) { + final Paragraph preface = new Paragraph(); + addEmptyLine(preface, 5); + preface.add(new Phrase("Allure Report", fontHolder.header1())); + addEmptyLine(preface, 3); + preface.add(new Phrase(exportName, fontHolder.header3())); + addEmptyLine(preface, 2); + preface.add(new Phrase("Date: ", fontHolder.bold())); + preface.add(new Phrase(dateFormat.format(new Date()), fontHolder.normal())); + preface.setAlignment(Element.ALIGN_CENTER); + document.add(preface); + } + + private void printTestResultDetails(final Document document, + final TestResult testResult, + final FontHolder fontHolder) { + final Paragraph details = new Paragraph(); + details.add(PdfUtil.createEmptyLine()); + addTestResultHeader(testResult, fontHolder, details); + details.add(PdfUtil.createEmptyLine()); + addCustomFieldsSection(testResult, fontHolder, details); + details.add(PdfUtil.createEmptyLine()); + addSteps(testResult, fontHolder, details); + document.add(details); + } + + private void addTestResultHeader(final TestResult testResult, final FontHolder fontHolder, + final Paragraph details) { + final Chunk testResultName = new Chunk(testResult.getName(), fontHolder.header3()); + final Paragraph testResultStatus = new Paragraph(testResultName); + testResultStatus.add(String.format("%s", new Phrase(testResult.getStatus().name()))); + details.add(testResultStatus); + } + + private void addCustomFieldsSection(final TestResult testResult, + final FontHolder fontHolder, + final Paragraph details) { + if (CollectionUtils.isNotEmpty(testResult.getLabels())) { + final Map labels = testResult.getLabels().stream().collect( + groupingBy(Label::getName, mapping(Label::getValue, joining(", "))) + ); + details.add(new Paragraph("Labels", fontHolder.header4())); + final com.lowagie.text.List list = new com.lowagie.text.List(false); + labels.forEach((key, value) -> { + list.add(new ListItem(String.format("%s: %s", key, value), fontHolder.normal())); + }); + details.add(list); + } + } + + private void addSteps(final TestResult testResult, final FontHolder fontHolder, final Paragraph details) { + if (Objects.nonNull(testResult.getSteps())) { + details.add(new Paragraph("Scenario", fontHolder.header4())); + final com.lowagie.text.List list = new com.lowagie.text.List(true); + testResult.getSteps().stream() + .map(step -> createStepItem(step, fontHolder)) + .forEach(list::add); + details.add(list); + } + } + + private ListItem createStepItem(final StepResult step, final FontHolder fontHolder) { + final Font font = fontHolder.normal(); + final String stepTitle = String.format("%s [%s]", step.getName(), step.getStatus()); + final ListItem stepItem = new ListItem(stepTitle, font); + if (Objects.nonNull(step.getAttachments())) { + final com.lowagie.text.List attachments = new com.lowagie.text.List(false, false); + for (final Attachment attach : step.getAttachments()) { + final String attachmentTitle = String.format("%s (%s)", attach.getName(), attach.getType()); + final ListItem attachmentItem = new ListItem(attachmentTitle, font); + final com.lowagie.text.List content = new com.lowagie.text.List(false); + for (final String line : readFile(attach)) { + content.add(new ListItem(line.replace("\t", " "), font)); + } + attachmentItem.add(content); + attachments.add(attachmentItem); + } + stepItem.add(attachments); + } + return stepItem; + } + + private List readFile(final Attachment attachment) { + final Path file = reportPath.resolve(attachment.getSource()); + try (InputStream stream = Files.newInputStream(file)) { + return IOUtils.readLines(stream, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void log(String template, Object... values) { + System.out.println(String.format(template, values)); + } + +} diff --git a/src/main/java/io/eroshenkoam/allure/MainCommand.java b/src/main/java/io/eroshenkoam/allure/MainCommand.java index bcab6d0..ff47a47 100644 --- a/src/main/java/io/eroshenkoam/allure/MainCommand.java +++ b/src/main/java/io/eroshenkoam/allure/MainCommand.java @@ -1,51 +1,16 @@ package io.eroshenkoam.allure; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lowagie.text.Chunk; -import com.lowagie.text.Document; -import com.lowagie.text.Element; -import com.lowagie.text.Font; -import com.lowagie.text.ListItem; -import com.lowagie.text.PageSize; -import com.lowagie.text.Paragraph; -import com.lowagie.text.Phrase; -import com.lowagie.text.pdf.PdfWriter; -import io.eroshenkoam.allure.util.PdfUtil; -import io.qameta.allure.model.Attachment; -import io.qameta.allure.model.Label; -import io.qameta.allure.model.StepResult; -import io.qameta.allure.model.TestResult; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.io.IOUtils; import picocli.CommandLine; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import static io.eroshenkoam.allure.FontHolder.loadArialFont; -import static io.eroshenkoam.allure.util.PdfUtil.addEmptyLine; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.mapping; @CommandLine.Command( name = "allure-pdf", mixinStandardHelpOptions = true ) public class MainCommand implements Runnable { - private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ"); - @CommandLine.Parameters( index = "0", description = "The directories with allure result files" @@ -72,156 +37,13 @@ public class MainCommand implements Runnable { @Override public void run() { try { - runUnsafe(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void runUnsafe() throws IOException { - if (!Files.isDirectory(reportPath)) { - log("Input [%s] is not directory", reportPath.toAbsolutePath()); - return; - } - if (Files.notExists(reportPath)) { - log("Results directory [%s] does not exists", reportPath.toAbsolutePath()); - return; - } - - if (Files.notExists(outputPath)) { - log("Creating output file [%s] ...", outputPath.toAbsolutePath()); - Files.createFile(outputPath); - } - - final FontHolder fontHolder = loadArialFont(); - final List files = Files.walk(reportPath) - .filter(s -> s.toString().endsWith("-result.json")) - .collect(Collectors.toList()); - log("Found [%s] rest results ...", files.size()); - - try (final Document document = new Document(PageSize.A4)) { - PdfWriter.getInstance(document, Files.newOutputStream(outputPath)); - document.newPage(); - document.open(); - - addTitlePage(document, reportName, DATE_FORMAT, fontHolder); - - document.newPage(); - final Paragraph tableHeader = new Paragraph("Test Details", fontHolder.header2()); - addEmptyLine(tableHeader, 2); - document.add(tableHeader); - - for (Path path : files) { - final TestResult result = new ObjectMapper().readValue(path.toFile(), TestResult.class); - final Map> labels = result.getLabels().stream().collect( - Collectors.groupingBy(Label::getName, Collectors.mapping(Label::getValue, Collectors.toList())) - ); - boolean shouldSkip = Objects.nonNull(filter) - && !filter.isEmpty() - && !filter.entrySet().stream().allMatch(e -> labels.getOrDefault(e.getKey(), new ArrayList<>()).contains(e.getValue())); - if (!shouldSkip) { - printTestResultDetails(document, result, fontHolder); - } - } - } - } - - private void addTitlePage(final Document document, - final String exportName, - final DateFormat dateFormat, - final FontHolder fontHolder) { - final Paragraph preface = new Paragraph(); - addEmptyLine(preface, 5); - preface.add(new Phrase("Allure Report", fontHolder.header1())); - addEmptyLine(preface, 3); - preface.add(new Phrase(exportName, fontHolder.header3())); - addEmptyLine(preface, 2); - preface.add(new Phrase("Date: ", fontHolder.bold())); - preface.add(new Phrase(dateFormat.format(new Date()), fontHolder.normal())); - preface.setAlignment(Element.ALIGN_CENTER); - document.add(preface); - } - - private void printTestResultDetails(final Document document, - final TestResult testResult, - final FontHolder fontHolder) { - final Paragraph details = new Paragraph(); - details.add(PdfUtil.createEmptyLine()); - addTestResultHeader(testResult, fontHolder, details); - details.add(PdfUtil.createEmptyLine()); - addCustomFieldsSection(testResult, fontHolder, details); - details.add(PdfUtil.createEmptyLine()); - addSteps(testResult, fontHolder, details); - document.add(details); - } - - private void addTestResultHeader(final TestResult testResult, final FontHolder fontHolder, - final Paragraph details) { - final Chunk testResultName = new Chunk(testResult.getName(), fontHolder.header3()); - final Paragraph testResultStatus = new Paragraph(testResultName); - testResultStatus.add(String.format("%s", new Phrase(testResult.getStatus().name()))); - details.add(testResultStatus); - } - - private void addCustomFieldsSection(final TestResult testResult, - final FontHolder fontHolder, - final Paragraph details) { - if (CollectionUtils.isNotEmpty(testResult.getLabels())) { - final Map labels = testResult.getLabels().stream().collect( - groupingBy(Label::getName, mapping(Label::getValue, joining(", "))) - ); - details.add(new Paragraph("Labels", fontHolder.header4())); - final com.lowagie.text.List list = new com.lowagie.text.List(false); - labels.forEach((key, value) -> { - list.add(new ListItem(String.format("%s: %s", key, value), fontHolder.normal())); - }); - details.add(list); - } - } - - private void addSteps(final TestResult testResult, final FontHolder fontHolder, final Paragraph details) { - if (Objects.nonNull(testResult.getSteps())) { - details.add(new Paragraph("Scenario", fontHolder.header4())); - final com.lowagie.text.List list = new com.lowagie.text.List(true); - testResult.getSteps().stream() - .map(step -> createStepItem(step, fontHolder)) - .forEach(list::add); - details.add(list); - } - } - - private ListItem createStepItem(final StepResult step, final FontHolder fontHolder) { - final Font font = fontHolder.normal(); - final String stepTitle = String.format("%s [%s]", step.getName(), step.getStatus()); - final ListItem stepItem = new ListItem(stepTitle, font); - if (Objects.nonNull(step.getAttachments())) { - final com.lowagie.text.List attachments = new com.lowagie.text.List(false, false); - for (final Attachment attach : step.getAttachments()) { - final String attachmentTitle = String.format("%s (%s)", attach.getName(), attach.getType()); - final ListItem attachmentItem = new ListItem(attachmentTitle, font); - final com.lowagie.text.List content = new com.lowagie.text.List(false); - for (final String line : readFile(attach)) { - content.add(new ListItem(line.replace("\t", " "), font)); - } - attachmentItem.add(content); - attachments.add(attachmentItem); - } - stepItem.add(attachments); - } - return stepItem; - } - - private List readFile(final Attachment attachment) { - final Path file = reportPath.resolve(attachment.getSource()); - try (InputStream stream = Files.newInputStream(file)) { - return IOUtils.readLines(stream, StandardCharsets.UTF_8); + final AllurePDFGenerator generator = new AllurePDFGenerator(reportName, reportPath); + generator.filter(filter); + generator.generate(outputPath); + ; } catch (IOException e) { throw new RuntimeException(e); } } - private void log(String template, Object... values) { - System.out.println(String.format(template, values)); - } - }