Skip to content

Commit

Permalink
add carousel attachment feature (via #52)
Browse files Browse the repository at this point in the history
  • Loading branch information
eroshenkoam authored Sep 12, 2023
1 parent 330ea77 commit f85583e
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 13 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- uses: graalvm/setup-graalvm@v1
with:
distribution: 'zulu'
java-version: '11'
cache: 'gradle'
java-version: '17'
distribution: 'graalvm'
- run: ./gradlew build
- run: ./gradlew nativeImage
- run: ./gradlew nativeCompile
16 changes: 9 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
java
id("com.palantir.graal") version("0.12.0")
id("org.graalvm.buildtools.native") version "0.9.24"
}

group = "org.example"
Expand All @@ -22,12 +22,13 @@ tasks.withType(JavaCompile::class) {
options.encoding = "UTF-8"
}

graal {
graalVersion("22.2.0")
javaVersion("11")

mainClass("io.eroshenkoam.xcresults.XCResults")
outputName("xcresults")
graalvmNative {
toolchainDetection.set(true)
binaries {
named("main") {
mainClass.set("io.eroshenkoam.xcresults.XCResults")
}
}
}

repositories {
Expand All @@ -39,6 +40,7 @@ dependencies {

implementation("com.fasterxml.jackson.core:jackson-databind:2.10.2")
implementation("io.qameta.allure:allure-model:2.13.1")
implementation("org.freemarker:freemarker:2.3.32")
implementation("info.picocli:picocli:4.1.4")
implementation("commons-io:commons-io:2.6")

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=3.18-SNAPSHOT
# Graal VM
graalVersion=19.2.0
graalVersion=23.0.1
20 changes: 20 additions & 0 deletions src/main/java/io/eroshenkoam/xcresults/carousel/Carousel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.eroshenkoam.xcresults.carousel;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class Carousel implements Serializable {

private final List<CarouselImage> images;

public Carousel(final List<CarouselImage> images) {
this.images = Optional.ofNullable(images).orElseGet(ArrayList::new);
}

public List<CarouselImage> getImages() {
return images;
}

}
23 changes: 23 additions & 0 deletions src/main/java/io/eroshenkoam/xcresults/carousel/CarouselImage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.eroshenkoam.xcresults.carousel;

import java.io.Serializable;

public class CarouselImage implements Serializable {

private final String name;
private final String base64;

public CarouselImage(final String name, final String base64) {
this.name = name;
this.base64 = base64;
}

public String getName() {
return name;
}

public String getBase64() {
return base64;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.eroshenkoam.xcresults.carousel;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import freemarker.template.TemplateException;
import io.eroshenkoam.xcresults.export.ExportPostProcessor;
import io.eroshenkoam.xcresults.util.FreemarkerUtil;
import io.qameta.allure.model.Attachment;
import io.qameta.allure.model.ExecutableItem;
import io.qameta.allure.model.TestResult;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static io.eroshenkoam.xcresults.util.FormatUtil.getAttachmentFileName;

public class CarouselPostProcessor implements ExportPostProcessor {

private final ObjectMapper mapper = new ObjectMapper()
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

private final String templatePath;

public CarouselPostProcessor(final String templatePath) {
this.templatePath = templatePath;
}

@Override
public void processTestResults(final Path outputPath, final Map<Path, TestResult> testResults) {
System.out.println("Carousel attachment feature enabled");
Optional.ofNullable(templatePath)
.map(t -> String.format("Carousel template: %s", t))
.ifPresent(System.out::println);
testResults.forEach((path, result) -> processTestResult(outputPath, path, result));
}

private void processTestResult(final Path outputPath, final Path path, final TestResult testResult) {
final List<Attachment> attachments = getAttachment(testResult, (a) -> a.getName().endsWith(".jpeg"));
final List<CarouselImage> carouselImages = attachments.stream()
.map(a -> convert(outputPath, a))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
if (carouselImages.size() == 0) {
return;
}
try {
final Carousel carousel = new Carousel(carouselImages);
final Map<String, Object> data = Map.of("carousel", carousel);
final String carouselContent = Objects.nonNull(templatePath)
? FreemarkerUtil.render(Path.of(templatePath), data)
: FreemarkerUtil.render("templates/carousel.ftl", data);
final Path carouselPath = outputPath.resolve(getAttachmentFileName("html"));
Files.write(carouselPath, carouselContent.getBytes(StandardCharsets.UTF_8));
testResult.getAttachments().add(new Attachment()
.setName("Carousel")
.setSource(carouselPath.getFileName().toString()));
mapper.writeValue(path.toFile(), testResult);
} catch (IOException | TemplateException e) {
System.out.println("Can not create carousel attachment: " + e.getMessage());
}
}

private List<Attachment> getAttachment(final ExecutableItem item, final Predicate<Attachment> filter) {
final List<Attachment> attachments = new ArrayList<>();
item.getAttachments().stream()
.filter(filter)
.forEach(attachments::add);
if (Objects.nonNull(item.getSteps())) {
item.getSteps().forEach(step -> attachments.addAll(getAttachment(step, filter)));
}
return attachments;
}

private Optional<CarouselImage> convert(final Path outputPath, final Attachment attachment) {
try {
final byte[] bytes = Files.readAllBytes(outputPath.resolve(attachment.getSource()));
final String content = "data:image/jpeg;base64, " + Base64.getEncoder().encodeToString(bytes);
return Optional.of(new CarouselImage(attachment.getName(), content));
} catch (IOException e) {
return Optional.empty();
}

}

}
20 changes: 20 additions & 0 deletions src/main/java/io/eroshenkoam/xcresults/export/ExportCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.eroshenkoam.xcresults.carousel.CarouselPostProcessor;
import io.qameta.allure.model.ExecutableItem;
import io.qameta.allure.model.TestResult;
import org.apache.commons.io.FileUtils;
Expand Down Expand Up @@ -70,6 +71,18 @@ public class ExportCommand implements Runnable {
)
protected ExportFormat format = ExportFormat.allure2;

@CommandLine.Option(
names = {"--add-carousel-attachment"},
description = "Add carousel attachment to test results"
)
private Boolean addCarouselAttachment;

@CommandLine.Option(
names = {"--carousel-template-path"},
description = "Carousel attachment template path"
)
private String carouselTemplatePath;

@CommandLine.Parameters(
index = "0",
description = "The directories with *.xcresults"
Expand Down Expand Up @@ -134,6 +147,7 @@ private void runUnsafe() throws Exception {

System.out.printf("Export information about %s test summaries...%n", testSummaries.size());
final Map<String, String> attachmentsRefs = new HashMap<>();
final Map<Path, TestResult> testResults = new HashMap<>();
for (final Map.Entry<JsonNode, ExportMeta> entry : testSummaries.entrySet()) {
final JsonNode testSummary = entry.getKey();
final ExportMeta meta = entry.getValue();
Expand All @@ -154,13 +168,19 @@ private void runUnsafe() throws Exception {
}
});
});
testResults.put(testSummaryPath, testResult);
}
System.out.printf("Export information about %s attachments...%n", attachmentsRefs.size());
for (Map.Entry<String, String> attachment : attachmentsRefs.entrySet()) {
final String attachmentRef = attachment.getValue();
final Path attachmentPath = outputPath.resolve(attachment.getKey());
exportReference(attachmentRef, attachmentPath);
}
final List<ExportPostProcessor> postProcessors = new ArrayList<>();
if (Objects.nonNull(addCarouselAttachment)) {
postProcessors.add(new CarouselPostProcessor(carouselTemplatePath));
}
postProcessors.forEach(postProcessor -> postProcessor.processTestResults(outputPath, testResults));
}

private ExportMeta getTestMeta(final ExportMeta meta, final JsonNode testableSummary) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.eroshenkoam.xcresults.export;

import io.qameta.allure.model.TestResult;

import java.nio.file.Path;
import java.util.Map;

public interface ExportPostProcessor {

void processTestResults(Path outputPath, Map<Path, TestResult> testResults);

}
51 changes: 51 additions & 0 deletions src/main/java/io/eroshenkoam/xcresults/util/FreemarkerUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.eroshenkoam.xcresults.util;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;

import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

public class FreemarkerUtil {

private FreemarkerUtil() {
}

public static String render(final Path templatePath, final Map<String, Object> data)
throws IOException, TemplateException {
final Template template = new Template(
templatePath.getFileName().toString(), Files.readString(templatePath), getDefaultConfiguration()
);
return render(template, data);
}

public static String render(final String templateName, final Map<String, Object> data)
throws IOException, TemplateException {
return render(getDefaultConfiguration().getTemplate(templateName), data);
}

public static Configuration getDefaultConfiguration() {
Configuration configuration = new Configuration(Configuration.VERSION_2_3_30);
configuration.setClassForTemplateLoading(FreemarkerUtil.class, "/");
configuration.setDefaultEncoding("UTF-8");
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
configuration.setLogTemplateExceptions(false);
configuration.setWrapUncheckedExceptions(true);
configuration.setFallbackOnNullLoopVariable(false);
return configuration;
}

private static String render(final Template template, final Map<String, Object> data)
throws IOException, TemplateException {
try (StringWriter result = new StringWriter()) {
template.process(data, result);
return result.toString();
}
}

}
Binary file added src/main/resources/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"resources": {
"includes": [
{
"pattern": ".*\\.properties"
}
]
}
}
9 changes: 9 additions & 0 deletions src/main/resources/META-INF/native-image/resource-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"resources": {
"includes": [
{
"pattern": "templates/.*\\.ftl"
}
]
}
}
45 changes: 45 additions & 0 deletions src/main/resources/templates/carousel.ftl

Large diffs are not rendered by default.

0 comments on commit f85583e

Please sign in to comment.