Skip to content

Commit d5a8c73

Browse files
authored
support custom binary return types for @attachment (fixes #485, via #1336)
1 parent f45f5b3 commit d5a8c73

4 files changed

Lines changed: 123 additions & 5 deletions

File tree

allure-java-commons/src/main/java/io/qameta/allure/Attachment.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
/**
2525
* Used to mark methods that produce attachments. Returned value of such methods
2626
* will be copied and shown in the report as attachment.
27+
*
28+
* <p>Byte arrays are used as binary attachment content directly. Custom return types may implement
29+
* {@link AttachmentBytes}; all other values are converted to their UTF-8 string representation.</p>
2730
*/
2831
@Documented
2932
@Retention(RetentionPolicy.RUNTIME)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure;
17+
18+
/**
19+
* Provides binary attachment content for custom values returned by methods annotated with {@link Attachment}.
20+
*
21+
* <p>When an annotated method returns an implementation of this interface, the attachment aspect uses
22+
* {@link #attachmentBytes()} instead of converting the returned object to a string.</p>
23+
*/
24+
@FunctionalInterface
25+
public interface AttachmentBytes {
26+
27+
/**
28+
* Returns the binary attachment content.
29+
*
30+
* @return the attachment content, must not be {@code null}
31+
*/
32+
byte[] attachmentBytes();
33+
}

allure-java-commons/src/main/java/io/qameta/allure/aspects/AttachmentsAspects.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.qameta.allure.Allure;
1919
import io.qameta.allure.AllureLifecycle;
2020
import io.qameta.allure.Attachment;
21+
import io.qameta.allure.AttachmentBytes;
2122
import io.qameta.allure.AttachmentOptions;
2223
import org.aspectj.lang.JoinPoint;
2324
import org.aspectj.lang.annotation.AfterReturning;
@@ -57,7 +58,7 @@ public void anyMethod() {
5758

5859
/**
5960
* Handles the attachment callback.
60-
* If returned data is not a byte array, then use toString() method, and get bytes from it.
61+
* If returned data is neither a byte array nor {@link AttachmentBytes}, then use its string representation.
6162
*
6263
* @param joinPoint the join point to process
6364
* @param result the model object or framework result to process
@@ -76,10 +77,7 @@ public void attachment(final JoinPoint joinPoint, final Object result) {
7677
final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
7778
final Attachment attachment = methodSignature.getMethod()
7879
.getAnnotation(Attachment.class);
79-
final byte[] bytes = (result instanceof byte[])
80-
? (byte[]) result
81-
: Objects.toString(result)
82-
.getBytes(StandardCharsets.UTF_8);
80+
final byte[] bytes = toBytes(result);
8381

8482
final String name = attachment.value().isEmpty()
8583
? methodSignature.getName()
@@ -94,6 +92,16 @@ public void attachment(final JoinPoint joinPoint, final Object result) {
9492
);
9593
}
9694

95+
private static byte[] toBytes(final Object result) {
96+
if (result instanceof byte[]) {
97+
return (byte[]) result;
98+
}
99+
if (result instanceof AttachmentBytes) {
100+
return ((AttachmentBytes) result).attachmentBytes();
101+
}
102+
return Objects.toString(result).getBytes(StandardCharsets.UTF_8);
103+
}
104+
97105
/**
98106
* Returns the lifecycle.
99107
*
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.aspects;
17+
18+
import io.qameta.allure.Attachment;
19+
import io.qameta.allure.AttachmentBytes;
20+
import io.qameta.allure.Issue;
21+
import io.qameta.allure.test.AllureResults;
22+
import io.qameta.allure.test.IsolatedLifecycle;
23+
import org.junit.jupiter.api.Test;
24+
25+
import static io.qameta.allure.test.RunUtils.runWithinTestContext;
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
@SuppressWarnings("unused")
29+
@IsolatedLifecycle
30+
class AttachmentsAspectsTest {
31+
32+
@Issue("485")
33+
@Test
34+
void shouldUseBytesFromCustomAttachmentType() {
35+
final byte[] expected = new byte[]{0, 1, 2, -1};
36+
37+
final AllureResults results = runWithinTestContext(() -> customAttachment(expected));
38+
39+
assertThat(results.getAttachmentsRecursively())
40+
.singleElement()
41+
.satisfies(attachment -> {
42+
assertThat(attachment.getName()).isEqualTo("Custom attachment");
43+
assertThat(attachment.getType()).isEqualTo("application/octet-stream");
44+
assertThat(results.getAttachmentContent(attachment)).containsExactly(expected);
45+
});
46+
}
47+
48+
@Attachment(
49+
value = "Custom attachment",
50+
type = "application/octet-stream"
51+
)
52+
CustomAttachment customAttachment(final byte[] content) {
53+
return new CustomAttachment(content);
54+
}
55+
56+
static final class CustomAttachment implements AttachmentBytes {
57+
58+
private final byte[] content;
59+
60+
CustomAttachment(final byte[] content) {
61+
this.content = content;
62+
}
63+
64+
@Override
65+
public byte[] attachmentBytes() {
66+
return content;
67+
}
68+
69+
@Override
70+
public String toString() {
71+
return "fallback content";
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)