From bf9c7993b08903b9b4efde73881f8c9cc961d867 Mon Sep 17 00:00:00 2001 From: Piotr Mionskowski Date: Tue, 4 Jan 2022 15:30:56 +0100 Subject: [PATCH] Add support for supplying input zip files next to directories With this change it is possible to run allure generate --report-dir ./out ./source-1.zip ./source-2.zip ./source-3 --- .../qameta/allure/ConfigurationBuilder.java | 4 +- .../qameta/allure/DummyReportGenerator.java | 2 + .../io/qameta/allure/ReportGenerator.java | 10 +- .../qameta/allure/allure2/Allure2Plugin.java | 4 + .../allure/zip/ZipResultsSourcePlugin.java | 85 +++++++++++ .../zip/ZipResultsSourcePluginTest.java | 132 ++++++++++++++++++ .../allure/junitxml/JunitXmlPlugin.java | 5 +- .../java/io/qameta/allure/trx/TrxPlugin.java | 9 +- .../io/qameta/allure/xctest/XcTestPlugin.java | 7 +- .../allure/xunitxml/XunitXmlPlugin.java | 5 +- 10 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 allure-generator/src/main/java/io/qameta/allure/zip/ZipResultsSourcePlugin.java create mode 100644 allure-generator/src/test/java/io/qameta/allure/zip/ZipResultsSourcePluginTest.java diff --git a/allure-generator/src/main/java/io/qameta/allure/ConfigurationBuilder.java b/allure-generator/src/main/java/io/qameta/allure/ConfigurationBuilder.java index 8d2a4413b..9394db94b 100644 --- a/allure-generator/src/main/java/io/qameta/allure/ConfigurationBuilder.java +++ b/allure-generator/src/main/java/io/qameta/allure/ConfigurationBuilder.java @@ -50,6 +50,7 @@ import io.qameta.allure.summary.SummaryPlugin; import io.qameta.allure.tags.TagsPlugin; import io.qameta.allure.timeline.TimelinePlugin; +import io.qameta.allure.zip.ZipResultsSourcePlugin; import java.util.ArrayList; import java.util.Arrays; @@ -107,7 +108,8 @@ public ConfigurationBuilder useDefault() { new Allure1Plugin(), new Allure1EnvironmentPlugin(), new Allure2Plugin(), - new GaPlugin() + new GaPlugin(), + new ZipResultsSourcePlugin() )); return this; } diff --git a/allure-generator/src/main/java/io/qameta/allure/DummyReportGenerator.java b/allure-generator/src/main/java/io/qameta/allure/DummyReportGenerator.java index f17061371..7593aa22d 100644 --- a/allure-generator/src/main/java/io/qameta/allure/DummyReportGenerator.java +++ b/allure-generator/src/main/java/io/qameta/allure/DummyReportGenerator.java @@ -51,6 +51,7 @@ import io.qameta.allure.summary.SummaryPlugin; import io.qameta.allure.tags.TagsPlugin; import io.qameta.allure.timeline.TimelinePlugin; +import io.qameta.allure.zip.ZipResultsSourcePlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,6 +111,7 @@ public final class DummyReportGenerator { new Allure1Plugin(), new Allure1EnvironmentPlugin(), new Allure2Plugin(), + new ZipResultsSourcePlugin(), new ReportWebPlugin() { @Override public void aggregate(final Configuration configuration, diff --git a/allure-generator/src/main/java/io/qameta/allure/ReportGenerator.java b/allure-generator/src/main/java/io/qameta/allure/ReportGenerator.java index 51dab9c12..3b3cfd1be 100644 --- a/allure-generator/src/main/java/io/qameta/allure/ReportGenerator.java +++ b/allure-generator/src/main/java/io/qameta/allure/ReportGenerator.java @@ -17,6 +17,7 @@ import io.qameta.allure.core.Configuration; import io.qameta.allure.core.LaunchResults; +import io.qameta.allure.zip.ZipResultsSourcePlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,6 +68,7 @@ private void generate(final Path outputDirectory, final Stream resultsDire .filter(this::isValidResultsDirectory) .map(this::readResults) .collect(Collectors.toList()); + aggregate(results, outputDirectory); } @@ -75,10 +77,14 @@ private boolean isValidResultsDirectory(final Path resultsDirectory) { LOGGER.warn("{} does not exist", resultsDirectory); return false; } - if (!Files.isDirectory(resultsDirectory)) { - LOGGER.warn("{} is not a directory", resultsDirectory); + + final boolean isDirectory = Files.isDirectory(resultsDirectory); + final boolean isZip = ZipResultsSourcePlugin.isZip(resultsDirectory); + if (!isDirectory && !isZip) { + LOGGER.warn("{} is neither a directory nor a zip file", resultsDirectory); return false; } + return true; } } diff --git a/allure-generator/src/main/java/io/qameta/allure/allure2/Allure2Plugin.java b/allure-generator/src/main/java/io/qameta/allure/allure2/Allure2Plugin.java index 1069cbe1c..e08b41c64 100644 --- a/allure-generator/src/main/java/io/qameta/allure/allure2/Allure2Plugin.java +++ b/allure-generator/src/main/java/io/qameta/allure/allure2/Allure2Plugin.java @@ -103,6 +103,10 @@ public class Allure2Plugin implements Reader { public void readResults(final Configuration configuration, final ResultsVisitor visitor, final Path resultsDirectory) { + if (!Files.isDirectory(resultsDirectory)) { + return; + } + final RandomUidContext context = configuration.requireContext(RandomUidContext.class); final List groups = readTestResultsContainers(resultsDirectory) .collect(Collectors.toList()); diff --git a/allure-generator/src/main/java/io/qameta/allure/zip/ZipResultsSourcePlugin.java b/allure-generator/src/main/java/io/qameta/allure/zip/ZipResultsSourcePlugin.java new file mode 100644 index 000000000..fb30c1af0 --- /dev/null +++ b/allure-generator/src/main/java/io/qameta/allure/zip/ZipResultsSourcePlugin.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Qameta Software OÜ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.zip; + +import io.qameta.allure.Reader; +import io.qameta.allure.core.Configuration; +import io.qameta.allure.core.ResultsVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class ZipResultsSourcePlugin implements Reader { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZipResultsSourcePlugin.class); + + @Override + public void readResults(final Configuration configuration, + final ResultsVisitor visitor, + final Path directory) { + if (!isZip(directory)) { + return; + } + + + try (FileSystem zipFile = FileSystems.newFileSystem(directory, (ClassLoader) null)) { + final Stream otherReaders = configuration + .getReaders() + .stream() + .filter(this::isNotZipResultsSourcePlugin); + + zipFile.getRootDirectories().forEach(directoryInZip -> + otherReaders.forEach(reader -> reader.readResults(configuration, visitor, directoryInZip)) + ); + + } catch (IOException e) { + LOGGER.warn("Failed to create zip file system from {}", directory, e); + } + } + + private boolean isNotZipResultsSourcePlugin(final Reader reader) { + return !getClass().isInstance(reader); + } + + public static boolean isZip(final Path path) { + boolean isZip = false; + + try { + isZip = "application/zip".equals(Files.probeContentType(path)); + } catch (IOException e) { + LOGGER.trace("Failed to probe content type", e); + } + + if (!isZip) { + try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "r")) { + final int fileSignature = raf.readInt(); + isZip = fileSignature == 0x504B0304 || fileSignature == 0x504B0506 || fileSignature == 0x504B0708; + } catch (IOException | UnsupportedOperationException e) { + LOGGER.trace("Failed to read {}", path, e); + } + } + + + return isZip; + } +} diff --git a/allure-generator/src/test/java/io/qameta/allure/zip/ZipResultsSourcePluginTest.java b/allure-generator/src/test/java/io/qameta/allure/zip/ZipResultsSourcePluginTest.java new file mode 100644 index 000000000..7ccf268c4 --- /dev/null +++ b/allure-generator/src/test/java/io/qameta/allure/zip/ZipResultsSourcePluginTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 Qameta Software OÜ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.zip; + +import io.qameta.allure.ConfigurationBuilder; +import io.qameta.allure.DefaultResultsVisitor; +import io.qameta.allure.core.Configuration; +import io.qameta.allure.core.LaunchResults; +import io.qameta.allure.entity.StageResult; +import io.qameta.allure.entity.TestResult; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +class ZipResultsSourcePluginTest { + + private Path zipFile; + + @BeforeEach + void setUp() throws IOException { + this.zipFile = Files.createTempFile("zip-results-source", ".zip"); + } + + @Test + void shouldRunOtherPlugins() throws Exception { + Set testResults = zipAndRunPlugin( + new ResourceToZipEntry("allure2/simple-testcase.json", "simple-testcase-result.json"), + new ResourceToZipEntry("allure2/first-testgroup.json", "first-testgroup-container.json"), + new ResourceToZipEntry("allure2/second-testgroup.json", "second-testgroup-container.json") + ).getResults(); + + assertThat(testResults) + .hasSize(1) + .flatExtracting(TestResult::getBeforeStages) + .hasSize(2) + .extracting(StageResult::getName) + .containsExactlyInAnyOrder("mockAuthorization", "loadTestConfiguration"); + } + + @Test + void shouldIgnoreEmptyZipFile() throws Exception { + Set testResults = zipAndRunPlugin().getResults(); + + assertThat(testResults).isEmpty(); + } + + @Test + void shouldIgnoreNonZipFile() throws Exception { + zipFile = Files.createTempDirectory("regular-dir"); + + Set testResults = runPlugin().getResults(); + + assertThat(testResults).isEmpty(); + } + + private LaunchResults zipAndRunPlugin(ResourceToZipEntry... strings) throws IOException { + try (ZipOutputStream zout = new ZipOutputStream(Files.newOutputStream(zipFile))) { + for (ResourceToZipEntry file : strings) { + copyFile(zout, file); + } + } + return runPlugin(); + } + + private LaunchResults runPlugin() { + ZipResultsSourcePlugin reader = new ZipResultsSourcePlugin(); + final Configuration configuration = new ConfigurationBuilder().useDefault().build(); + final DefaultResultsVisitor resultsVisitor = new DefaultResultsVisitor(configuration); + reader.readResults(configuration, resultsVisitor, zipFile); + return resultsVisitor.getLaunchResults(); + } + + private void copyFile(ZipOutputStream zipFile, ResourceToZipEntry resource) throws IOException { + try (InputStream is = getClass().getClassLoader().getResourceAsStream(resource.getResourceName())) { + ZipEntry zipEntry = new ZipEntry(resource.getZipEntry()); + zipFile.putNextEntry(zipEntry); + IOUtils.copy(Objects.requireNonNull(is), zipFile); + zipFile.closeEntry(); + } + } + + private static String generateTestResultName() { + return UUID.randomUUID() + "-result.json"; + } + + private static String generateTestResultContainerName() { + return UUID.randomUUID() + "-container.json"; + } + + static class ResourceToZipEntry { + private final String resourceName; + private final String zipEntry; + + public ResourceToZipEntry(String resourceName, String zipEntry) { + this.resourceName = resourceName; + this.zipEntry = zipEntry; + } + + public String getResourceName() { + return resourceName; + } + + public String getZipEntry() { + return zipEntry; + } + } +} diff --git a/plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java b/plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java index 6a96f5db8..e580997cd 100644 --- a/plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java +++ b/plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java @@ -38,6 +38,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -126,12 +127,12 @@ public void readResults(final Configuration configuration, final ResultsVisitor private void parseRootElement(final Path resultsDirectory, final Path parsedFile, final RandomUidContext context, final ResultsVisitor visitor) { - try { + try (InputStream is = Files.newInputStream(parsedFile)) { LOGGER.debug("Parsing file {}", parsedFile); final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); - final XmlElement rootElement = new XmlElement(builder.parse(parsedFile.toFile()).getDocumentElement()); + final XmlElement rootElement = new XmlElement(builder.parse(is).getDocumentElement()); final String elementName = rootElement.getName(); if (TEST_SUITE_ELEMENT_NAME.equals(elementName)) { diff --git a/plugins/trx-plugin/src/main/java/io/qameta/allure/trx/TrxPlugin.java b/plugins/trx-plugin/src/main/java/io/qameta/allure/trx/TrxPlugin.java index 2bae82174..e8a7f89f2 100644 --- a/plugins/trx-plugin/src/main/java/io/qameta/allure/trx/TrxPlugin.java +++ b/plugins/trx-plugin/src/main/java/io/qameta/allure/trx/TrxPlugin.java @@ -35,6 +35,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; +import java.io.InputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -102,12 +103,12 @@ public void readResults(final Configuration configuration, } protected void parseTestRun(final Path parsedFile, final RandomUidContext context, final ResultsVisitor visitor) { - try { - LOGGER.debug("Parsing file {}", parsedFile); - + try (InputStream is = Files.newInputStream(parsedFile)) { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); - final Document document = builder.parse(parsedFile.toFile()); + LOGGER.debug("Parsing file {}", parsedFile); + + final Document document = builder.parse(is); final XmlElement testRunElement = new XmlElement(document.getDocumentElement()); final String elementName = testRunElement.getName(); if (!TEST_RUN_ELEMENT_NAME.equals(elementName)) { diff --git a/plugins/xctest-plugin/src/main/java/io/qameta/allure/xctest/XcTestPlugin.java b/plugins/xctest-plugin/src/main/java/io/qameta/allure/xctest/XcTestPlugin.java index ad6864e57..0d6825a84 100644 --- a/plugins/xctest-plugin/src/main/java/io/qameta/allure/xctest/XcTestPlugin.java +++ b/plugins/xctest-plugin/src/main/java/io/qameta/allure/xctest/XcTestPlugin.java @@ -28,6 +28,7 @@ import xmlwise.XmlParseException; import java.io.IOException; +import java.io.InputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -77,9 +78,11 @@ public void readResults(final Configuration configuration, } private void readSummaries(final Path directory, final Path testSummariesPath, final ResultsVisitor visitor) { - try { + try (InputStream is = Files.newInputStream(testSummariesPath)) { + final Path plist = Files.createTempFile("plist-from-stream", "plist"); + Files.copy(is, plist); LOGGER.info("Parse file {}", testSummariesPath); - final Map loaded = Plist.load(testSummariesPath.toFile()); + final Map loaded = Plist.load(plist.toFile()); final List summaries = asList(loaded.getOrDefault(TESTABLE_SUMMARIES, emptyList())); summaries.forEach(summary -> parseSummary(directory, summary, visitor)); } catch (XmlParseException | IOException e) { diff --git a/plugins/xunit-xml-plugin/src/main/java/io/qameta/allure/xunitxml/XunitXmlPlugin.java b/plugins/xunit-xml-plugin/src/main/java/io/qameta/allure/xunitxml/XunitXmlPlugin.java index 8560438bb..af3eddf5d 100644 --- a/plugins/xunit-xml-plugin/src/main/java/io/qameta/allure/xunitxml/XunitXmlPlugin.java +++ b/plugins/xunit-xml-plugin/src/main/java/io/qameta/allure/xunitxml/XunitXmlPlugin.java @@ -33,6 +33,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -91,11 +92,11 @@ public void readResults(final Configuration configuration, } private void parseAssemblies(final Path parsedFile, final RandomUidContext context, final ResultsVisitor visitor) { - try { + try (InputStream is = Files.newInputStream(parsedFile)) { LOGGER.debug("Parsing file {}", parsedFile); final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); - final Document document = builder.parse(parsedFile.toFile()); + final Document document = builder.parse(is); final XmlElement assembliesElement = new XmlElement(document.getDocumentElement()); final String elementName = assembliesElement.getName(); if (!ASSEMBLIES_ELEMENT_NAME.equals(elementName)) {