Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.qameta.allure.model.Attachment;
import io.qameta.allure.model.ExecutableItem;
import io.qameta.allure.model.Globals;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.model.TestResultContainer;

Expand Down Expand Up @@ -51,6 +52,15 @@ public interface AllureResults {
*/
List<TestResultContainer> getTestResultContainers();

/**
* Returns the global artifacts.
*
* @return the global artifacts
*/
default List<Globals> getGlobals() {
return List.of();
}

/**
* Returns the attachments.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.qameta.allure.test;

import io.qameta.allure.AllureResultsWriter;
import io.qameta.allure.model.Globals;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.model.TestResultContainer;
import org.apache.commons.io.IOUtils;
Expand All @@ -37,6 +38,7 @@ public class AllureResultsWriterStub implements AllureResultsWriter, AllureResul

private final List<TestResult> testResults = new CopyOnWriteArrayList<>();
private final List<TestResultContainer> testContainers = new CopyOnWriteArrayList<>();
private final List<Globals> globals = new CopyOnWriteArrayList<>();
private final Map<String, byte[]> attachments = new ConcurrentHashMap<>();

/**
Expand All @@ -55,6 +57,14 @@ public void write(final TestResultContainer testResultContainer) {
testContainers.add(testResultContainer);
}

/**
* {@inheritDoc}
*/
@Override
public void write(final Globals value) {
globals.add(value);
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -84,6 +94,14 @@ public List<TestResultContainer> getTestResultContainers() {
return testContainers;
}

/**
* {@inheritDoc}
*/
@Override
public List<Globals> getGlobals() {
return globals;
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT;
Expand Down Expand Up @@ -99,6 +100,18 @@ public static void attach(final AllureResults allureResults) {
}
});

allureResults.getGlobals().forEach(globals -> {
try {
Allure.attachment(
UUID.randomUUID() + AllureConstants.GLOBALS_FILE_SUFFIX,
JSON_TYPE,
WRITER.writeValueAsString(globals)
);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(e);
}
});

allureResults.getAttachments().forEach(
(fileName, body) -> Allure
.attachment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import io.qameta.allure.Allure;
import io.qameta.allure.model.Attachment;
import io.qameta.allure.model.GlobalError;
import io.qameta.allure.model.Globals;
import io.qameta.allure.model.StepResult;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.model.TestResultContainer;
Expand All @@ -25,24 +27,28 @@
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class AllureResultsWriterStubTest {

@Test
void shouldStoreResultsContainersAndAttachments() {
void shouldStoreResultsContainersGlobalsAndAttachments() {
final AllureResultsWriterStub writer = new AllureResultsWriterStub();
final TestResult testResult = new TestResult()
.setUuid("test-uuid")
.setName("demo");
final TestResultContainer container = new TestResultContainer()
.setUuid("container-uuid")
.setChildren(List.of("test-uuid"));
final Globals globals = new Globals()
.setErrors(List.of(new GlobalError().setMessage("global error").setTimestamp(123L)));

Allure.step("Store a test result, its container, and an attachment", () -> {
Allure.step("Store a test result, its container, globals, and an attachment", () -> {
writer.write(testResult);
writer.write(container);
writer.write(globals);
writer.write("payload.txt", new ByteArrayInputStream("payload".getBytes(StandardCharsets.UTF_8)));
});

Expand All @@ -51,11 +57,44 @@ void shouldStoreResultsContainersAndAttachments() {
.isSameAs(testResult);
assertThat(writer.getTestResultContainersForTestResult(testResult))
.containsExactly(container);
assertThat(writer.getGlobals())
.containsExactly(
new Globals()
.setErrors(
List.of(
new GlobalError()
.setMessage("global error")
.setTimestamp(123L)
)
)
);
assertThat(writer.getAttachments().get("payload.txt"))
.isEqualTo("payload".getBytes(StandardCharsets.UTF_8));
});
}

@Test
void shouldDefaultGlobalsToEmptyForLegacyResultsImplementations() {
final AllureResults legacyResults = new AllureResults() {
@Override
public List<TestResult> getTestResults() {
return List.of();
}

@Override
public List<TestResultContainer> getTestResultContainers() {
return List.of();
}

@Override
public Map<String, byte[]> getAttachments() {
return Map.of();
}
};

assertThat(legacyResults.getGlobals()).isEmpty();
}

@Test
void shouldExposeNestedAttachmentsAndContent() {
final AllureResultsWriterStub writer = new AllureResultsWriterStub();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
package io.qameta.allure.test;

import io.qameta.allure.Allure;
import io.qameta.allure.AllureConstants;
import io.qameta.allure.model.Attachment;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.TestResult;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -50,18 +54,30 @@ void shouldCaptureFailureStatusWithinSyntheticTestContext() {
void shouldAttachNestedRunArtifactsToOuterLifecycle() {
final AllureResults results = Allure
.step("Execute a nested synthetic run and capture its emitted attachments", () -> RunUtils.runWithinTestContext(() -> RunUtils.runWithinTestContext(() -> {
Allure.globalError(new IllegalStateException("nested global error"));
})
)
);

Allure.attachment("nested-attachment-keys", String.join("\n", results.getAttachments().keySet()));
Allure.step("Verify the outer lifecycle receives serialized artifacts from the nested run", () -> {
final List<String> attachmentContents = results.getAttachments().values().stream()
.map(bytes -> new String(bytes, StandardCharsets.UTF_8))
.collect(Collectors.toList());
assertThat(results.getAttachments())
.isNotEmpty();
assertThat(results.getAttachments().values())
.anySatisfy(
bytes -> assertThat(new String(bytes, StandardCharsets.UTF_8))
.contains("\"uuid\"")
assertThat(results.getAttachmentsRecursively())
.extracting(Attachment::getName)
.anyMatch(
name -> name != null
&& name.endsWith(AllureConstants.GLOBALS_FILE_SUFFIX)
);
assertThat(attachmentContents)
.anyMatch(content -> content.contains("\"uuid\""));
assertThat(attachmentContents)
.anyMatch(
content -> content.contains("\"errors\"")
&& content.contains("nested global error")
);
});
}
Expand Down
45 changes: 45 additions & 0 deletions allure-java-commons/src/main/java/io/qameta/allure/Allure.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@

import io.qameta.allure.http.HttpExchange;
import io.qameta.allure.http.HttpExchangeSerializer;
import io.qameta.allure.model.GlobalError;
import io.qameta.allure.model.Globals;
import io.qameta.allure.model.Label;
import io.qameta.allure.model.Link;
import io.qameta.allure.model.Parameter;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.StatusDetails;
import io.qameta.allure.model.StepResult;
import io.qameta.allure.util.ExceptionUtils;

Expand Down Expand Up @@ -409,6 +412,48 @@ public static void descriptionHtml(final String descriptionHtml) {
getLifecycle().updateTestMetadata(metadata -> metadata.setDescriptionHtml(descriptionHtml));
}

/**
* Adds an error from the given throwable that belongs to the test run rather than to a test or fixture.
* The error is passed synchronously to the configured results writer before this method returns.
* Writers that support globals store it in a new globals artifact.
*
* @param throwable the error to add
*/
public static void globalError(final Throwable throwable) {
Objects.requireNonNull(throwable, "throwable");
final StatusDetails statusDetails = getStatusDetails(throwable).orElseThrow();
globalError(statusDetails);
}

/**
* Adds an error with the given status details that belongs to the test run rather than to a test or fixture.
* The error is passed synchronously to the configured results writer before this method returns.
* Writers that support globals store it in a new globals artifact.
* The status detail fields are copied without normalization, and the timestamp is assigned when this method
* is called.
*
* @param statusDetails the error details to add
*/
public static void globalError(final StatusDetails statusDetails) {
Objects.requireNonNull(statusDetails, "statusDetails");
final GlobalError globalError = toGlobalError(statusDetails)
.setTimestamp(System.currentTimeMillis());
final Globals globals = new Globals();
globals.getErrors().add(globalError);
getLifecycle().writeGlobals(globals);
}

private static GlobalError toGlobalError(final StatusDetails statusDetails) {
return new GlobalError()
.setKnown(statusDetails.isKnown())
.setMuted(statusDetails.isMuted())
.setFlaky(statusDetails.isFlaky())
.setMessage(statusDetails.getMessage())
.setTrace(statusDetails.getTrace())
.setActual(statusDetails.getActual())
.setExpected(statusDetails.getExpected());
}

/**
* Adds attachment.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ public final class AllureConstants {
*/
public static final String TEST_RESULT_CONTAINER_FILE_GLOB = "*-container.json";

/**
* File suffix used for global artifacts.
*/
public static final String GLOBALS_FILE_SUFFIX = "-globals.json";

/**
* Glob pattern used to find global artifacts.
*/
public static final String GLOBALS_FILE_GLOB = "*-globals.json";

/**
* File suffix used for attachment artifacts.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.qameta.allure.listener.TestLifecycleListener;
import io.qameta.allure.model.Attachment;
import io.qameta.allure.model.FixtureResult;
import io.qameta.allure.model.Globals;
import io.qameta.allure.model.Label;
import io.qameta.allure.model.Parameter;
import io.qameta.allure.model.ScopeFixtureResult;
Expand Down Expand Up @@ -155,6 +156,17 @@ public AllureLifecycle(final AllureResultsWriter writer) {
this.threadContext = new AllureThreadContext();
}

// ── Globals ──────────────────────────────────────────────────────────────────────────────

/**
* Writes test-run-level attachments and errors immediately.
*
* @param globals the global attachments and errors
*/
public void writeGlobals(final Globals globals) {
writer.write(Objects.requireNonNull(globals, "globals"));
}

// ── Scopes ───────────────────────────────────────────────────────────────────────────────

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.qameta.allure;

import io.qameta.allure.model.Globals;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.model.TestResultContainer;

Expand Down Expand Up @@ -43,6 +44,21 @@ public interface AllureResultsWriter {
*/
void write(TestResultContainer testResultContainer);

/**
* Writes Allure globals bean.
*
* <p>The default implementation preserves compatibility with custom writers
* that predate global artifacts. Such writers do not persist globals until
* they override this method.</p>
*
* @param globals the given bean to write.
* @throws AllureResultsWriteException if an overriding implementation fails
* during operation.
*/
default void write(final Globals globals) {
// Compatibility no-op for custom writers that predate global artifacts.
}

/**
* Writes given attachment. Will close the given stream.
*
Expand Down
Loading
Loading