Skip to content

Commit 3b4d32d

Browse files
authored
rft steps names processing, process attachment names (fixes #50, via #60)
1 parent 58c38e0 commit 3b4d32d

File tree

8 files changed

+199
-93
lines changed

8 files changed

+199
-93
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
* @return the attachment name.
2222
*/
23-
String value() default "{method}";
23+
String value() default "";
2424

2525
/**
2626
* Valid attachment MimeType, for example "text/plain" or "application/json".

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import java.nio.charset.StandardCharsets;
1313
import java.util.Objects;
1414

15+
import static io.qameta.allure.util.AspectUtils.getParametersMap;
16+
import static io.qameta.allure.util.NamingUtils.processNameTemplate;
17+
1518
/**
1619
* Aspects (AspectJ) for handling {@link Attachment}.
1720
*
@@ -69,6 +72,10 @@ public void attachment(final JoinPoint joinPoint, final Object result) {
6972
.getAnnotation(Attachment.class);
7073
final byte[] bytes = (result instanceof byte[]) ? (byte[]) result : result.toString()
7174
.getBytes(StandardCharsets.UTF_8);
72-
getLifecycle().addAttachment(attachment.value(), attachment.type(), attachment.fileExtension(), bytes);
75+
76+
final String name = attachment.value().isEmpty()
77+
? methodSignature.getName()
78+
: processNameTemplate(attachment.value(), getParametersMap(methodSignature, joinPoint.getArgs()));
79+
getLifecycle().addAttachment(name, attachment.type(), attachment.fileExtension(), bytes);
7380
}
7481
}

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

Lines changed: 8 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io.qameta.allure.Allure;
44
import io.qameta.allure.AllureLifecycle;
55
import io.qameta.allure.Step;
6-
import io.qameta.allure.model.Parameter;
76
import io.qameta.allure.model.Status;
87
import io.qameta.allure.model.StepResult;
98
import org.aspectj.lang.JoinPoint;
@@ -14,22 +13,15 @@
1413
import org.aspectj.lang.annotation.Pointcut;
1514
import org.aspectj.lang.reflect.MethodSignature;
1615

17-
import java.util.Collection;
1816
import java.util.Objects;
1917
import java.util.UUID;
20-
import java.util.function.Function;
21-
import java.util.regex.Matcher;
22-
import java.util.regex.Pattern;
23-
import java.util.stream.IntStream;
24-
import java.util.stream.Stream;
2518

19+
import static io.qameta.allure.util.AspectUtils.getParameters;
20+
import static io.qameta.allure.util.AspectUtils.getParametersMap;
21+
import static io.qameta.allure.util.NamingUtils.processNameTemplate;
2622
import static io.qameta.allure.util.ResultsUtils.getStatus;
2723
import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
2824
import static io.qameta.allure.util.ResultsUtils.processDescription;
29-
import static java.util.Arrays.asList;
30-
import static java.util.Optional.ofNullable;
31-
import static java.util.stream.Collectors.toList;
32-
import static org.joor.Reflect.on;
3325

3426
/**
3527
* @author Dmitry Baev charlie@yandex-team.ru
@@ -54,9 +46,13 @@ public void anyMethod() {
5446
@Before("anyMethod() && withStepAnnotation()")
5547
public void stepStart(final JoinPoint joinPoint) {
5648
final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
49+
final Step step = methodSignature.getMethod().getAnnotation(Step.class);
50+
final String name = step.value().isEmpty()
51+
? methodSignature.getName()
52+
: processNameTemplate(step.value(), getParametersMap(methodSignature, joinPoint.getArgs()));
5753
final String uuid = UUID.randomUUID().toString();
5854
final StepResult result = new StepResult()
59-
.withName(getName(methodSignature, joinPoint.getArgs()))
55+
.withName(name)
6056
.withParameters(getParameters(methodSignature, joinPoint.getArgs()));
6157
processDescription(getClass().getClassLoader(), methodSignature.getMethod(), result);
6258
getLifecycle().startStep(uuid, result);
@@ -91,81 +87,4 @@ public static AllureLifecycle getLifecycle() {
9187
}
9288
return lifecycle;
9389
}
94-
95-
private static Parameter[] getParameters(final MethodSignature signature, final Object... args) {
96-
return IntStream.range(0, args.length).mapToObj(index -> {
97-
final String name = signature.getParameterNames()[index];
98-
final String value = Objects.toString(args[index]);
99-
return new Parameter().withName(name).withValue(value);
100-
}).toArray(Parameter[]::new);
101-
}
102-
103-
private static String getMatchingPropertyValue(final String input,
104-
final Function<String, Object> parameterExtractor) {
105-
final Matcher matcher = Pattern.compile("\\{([^}]*)}").matcher(input);
106-
String output = input;
107-
108-
while (matcher.find()) {
109-
final String[] matches = matcher.group(1).split("\\.");
110-
final Object parameter = parameterExtractor.apply(matches[0]);
111-
112-
output = parameter != null
113-
? output.replace(matcher.group(0), getParameterPropertyValue(parameter, matches))
114-
: output;
115-
}
116-
117-
return output;
118-
}
119-
120-
private static Object getMatchingParameterValue(final MethodSignature signature, final String parameterName,
121-
final Object... args) {
122-
return IntStream.range(0, args.length)
123-
.filter(i -> parameterName.equals(signature.getParameterNames()[i]))
124-
.mapToObj(i -> args[i])
125-
.findFirst()
126-
.orElse(null);
127-
}
128-
129-
private static String getParameterPropertyValue(final Object rootObject, final String... fieldNames) {
130-
Object currentObject = rootObject;
131-
132-
for (int i = 1; i < fieldNames.length; i++) {
133-
final int currentIndex = i;
134-
135-
if (currentObject == null) {
136-
break;
137-
} else if (currentObject instanceof Collection) {
138-
currentObject = Stream.of((Collection<?>) currentObject)
139-
.map(ob -> extractProperty(ob, fieldNames[currentIndex]))
140-
.collect(toList());
141-
} else if (currentObject instanceof Object[]) {
142-
currentObject = Stream.of((Object[]) currentObject)
143-
.map(ob -> extractProperty(ob, fieldNames[currentIndex]))
144-
.toArray();
145-
} else {
146-
currentObject = extractProperty(currentObject, fieldNames[currentIndex]);
147-
}
148-
}
149-
150-
return ofNullable(currentObject)
151-
.filter(ob -> ob instanceof Object[])
152-
.map(ob -> Objects.toString(asList((Object[]) ob)))
153-
.orElse(Objects.toString(currentObject));
154-
}
155-
156-
private static Object extractProperty(final Object rootObject, final String fileName) {
157-
try {
158-
return on(rootObject).get(fileName);
159-
} catch (Exception ignored) {
160-
return rootObject;
161-
}
162-
}
163-
164-
private static String getName(final MethodSignature signature, final Object... args) {
165-
return ofNullable(signature.getMethod().getAnnotation(Step.class))
166-
.map(Step::value)
167-
.filter(value -> !value.isEmpty())
168-
.map(value -> getMatchingPropertyValue(value, arg -> getMatchingParameterValue(signature, arg, args)))
169-
.orElseGet(signature::getName);
170-
}
17190
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.qameta.allure.util;
2+
3+
import io.qameta.allure.model.Parameter;
4+
import org.aspectj.lang.reflect.MethodSignature;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import java.util.stream.IntStream;
10+
11+
/**
12+
* @author charlie (Dmitry Baev).
13+
*/
14+
public final class AspectUtils {
15+
16+
private AspectUtils() {
17+
throw new IllegalStateException("Do not instance");
18+
}
19+
20+
public static Parameter[] getParameters(final MethodSignature signature, final Object... args) {
21+
return IntStream.range(0, args.length).mapToObj(index -> {
22+
final String name = signature.getParameterNames()[index];
23+
final String value = Objects.toString(args[index]);
24+
return new Parameter().withName(name).withValue(value);
25+
}).toArray(Parameter[]::new);
26+
}
27+
28+
public static Map<String, Object> getParametersMap(final MethodSignature signature, final Object... args) {
29+
final String[] parameterNames = signature.getParameterNames();
30+
final Map<String, Object> params = new HashMap<>();
31+
for (int i = 0; i < Math.max(parameterNames.length, args.length); i++) {
32+
params.put(parameterNames[i], args[i]);
33+
}
34+
return params;
35+
}
36+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.qameta.allure.util;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.util.Arrays;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import java.util.Optional;
10+
import java.util.Spliterator;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
13+
import java.util.stream.Collector;
14+
import java.util.stream.Collectors;
15+
import java.util.stream.Stream;
16+
import java.util.stream.StreamSupport;
17+
18+
import static org.joor.Reflect.on;
19+
20+
/**
21+
* @author charlie (Dmitry Baev).
22+
*/
23+
public final class NamingUtils {
24+
25+
private static final Logger LOGGER = LoggerFactory.getLogger(NamingUtils.class);
26+
27+
private static final Collector<CharSequence, ?, String> JOINER = Collectors.joining(", ", "[", "]");
28+
29+
private NamingUtils() {
30+
throw new IllegalStateException("Do not instance");
31+
}
32+
33+
public static String processNameTemplate(final String template, final Map<String, Object> params) {
34+
final Matcher matcher = Pattern.compile("\\{([^}]*)}").matcher(template);
35+
final StringBuffer sb = new StringBuffer();
36+
while (matcher.find()) {
37+
final String pattern = matcher.group(1);
38+
final String replacement = processPattern(pattern, params).orElseGet(matcher::group);
39+
matcher.appendReplacement(sb, replacement);
40+
}
41+
matcher.appendTail(sb);
42+
return sb.toString();
43+
}
44+
45+
@SuppressWarnings("ReturnCount")
46+
private static Optional<String> processPattern(final String pattern, final Map<String, Object> params) {
47+
if (pattern.isEmpty()) {
48+
LOGGER.error("Could not process empty pattern");
49+
return Optional.empty();
50+
}
51+
final String[] parts = pattern.split("\\.");
52+
final String parameterName = parts[0];
53+
if (!params.containsKey(parameterName)) {
54+
LOGGER.error("Could not find parameter " + parameterName);
55+
return Optional.empty();
56+
}
57+
final Object param = params.get(parameterName);
58+
return Optional.ofNullable(extractProperties(param, parts, 1));
59+
}
60+
61+
@SuppressWarnings("ReturnCount")
62+
private static String extractProperties(final Object object, final String[] parts, final int index) {
63+
if (Objects.isNull(object)) {
64+
return "null";
65+
}
66+
if (index < parts.length) {
67+
if (object instanceof Object[]) {
68+
return Stream.of((Object[]) object)
69+
.map(child -> extractProperties(child, parts, index))
70+
.collect(JOINER);
71+
}
72+
if (object instanceof Iterable) {
73+
final Spliterator<?> iterator = ((Iterable) object).spliterator();
74+
return StreamSupport.stream(iterator, false)
75+
.map(child -> extractProperties(child, parts, index))
76+
.collect(JOINER);
77+
}
78+
final Object child = on(object).get(parts[index]);
79+
return extractProperties(child, parts, index + 1);
80+
}
81+
if (object instanceof Object[]) {
82+
return Arrays.toString((Object[]) object);
83+
}
84+
return String.valueOf(object);
85+
}
86+
}

allure-java-commons/src/test/java/io/qameta/allure/AttachmentsTests.java renamed to allure-java-commons/src/test/java/io/qameta/allure/AttachmentsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
/**
2929
* @author sskorol (Sergey Korol).
3030
*/
31-
public class AttachmentsTests {
31+
public class AttachmentsTest {
3232

3333
private static final List<CompletableFuture<InputStream>> STREAM_FUTURE = new CopyOnWriteArrayList<>();
3434

allure-java-commons/src/test/java/io/qameta/allure/StepsTests.java renamed to allure-java-commons/src/test/java/io/qameta/allure/StepsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
/**
1818
* @author sskorol (Sergey Korol)
1919
*/
20-
public class StepsTests {
20+
public class StepsTest {
2121

2222
@Test
2323
public void shouldTransformPlaceholdersToPropertyValues() {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.qameta.allure.util;
2+
3+
import io.qameta.allure.testdata.DummyUser;
4+
import org.junit.Test;
5+
import org.junit.runner.RunWith;
6+
import org.junit.runners.Parameterized;
7+
8+
import java.util.Arrays;
9+
import java.util.Collection;
10+
import java.util.Collections;
11+
import java.util.Map;
12+
13+
import static io.qameta.allure.util.NamingUtils.processNameTemplate;
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
16+
/**
17+
* @author charlie (Dmitry Baev).
18+
*/
19+
@RunWith(Parameterized.class)
20+
public class NamingUtilsTest {
21+
22+
@Parameterized.Parameter
23+
public String template;
24+
25+
@Parameterized.Parameter(1)
26+
public Map<String, Object> parameters;
27+
28+
@Parameterized.Parameter(2)
29+
public String expected;
30+
31+
@Parameterized.Parameters
32+
public static Collection<Object[]> data() {
33+
return Arrays.asList(
34+
new Object[]{"Hello word", Collections.emptyMap(), "Hello word"},
35+
new Object[]{"{missing}", Collections.emptyMap(), "{missing}"},
36+
new Object[]{"", Collections.singletonMap("a", "b"), ""},
37+
new Object[]{"", Collections.singletonMap("a", "b"), ""},
38+
new Object[]{"Hello {user}!", Collections.singletonMap("user", "Ivan"), "Hello Ivan!"},
39+
new Object[]{"Hello {user}", Collections.singletonMap("user", null), "Hello null"},
40+
new Object[]{"Hello {users}", Collections.singletonMap("users", Arrays.asList("Ivan", "Petr")), "Hello [Ivan, Petr]"},
41+
new Object[]{"Hello {users}", Collections.singletonMap("users", new String[]{"Ivan", "Petr"}), "Hello [Ivan, Petr]"},
42+
new Object[]{"Hello {users}", Collections.singletonMap("users", Collections.singletonMap("a", "b")), "Hello {a=b}"},
43+
new Object[]{"Password: {user.password}", Collections.singletonMap("user", new DummyUser(null, "123", null)), "Password: 123"},
44+
new Object[]{"Passwords: {users.password}", Collections.singletonMap("users", new DummyUser[]{new DummyUser(null, "123", null)}), "Passwords: [123]"},
45+
new Object[]{"Passwords: {users.password}", Collections.singletonMap("users", new DummyUser[]{null, new DummyUser(null, "123", null)}), "Passwords: [null, 123]"},
46+
new Object[]{"Passwords: {users.password}", Collections.singletonMap("users", new DummyUser[][]{null, {null, new DummyUser(null, "123", null)}}), "Passwords: [null, [null, 123]]"}
47+
);
48+
}
49+
50+
@Test
51+
public void shouldProcessTemplate() throws Exception {
52+
final String actual = processNameTemplate(template, parameters);
53+
54+
assertThat(actual)
55+
.describedAs("Should process template \"%s\" as \"%s\"", template, expected)
56+
.isEqualTo(expected);
57+
}
58+
}

0 commit comments

Comments
 (0)