Skip to content

Commit 0e5ab97

Browse files
committed
Fix bugs in UniqueId and UniqueIdFormat
- UniqueId now tracks the UniqueIdFormat it was created with in order to avoid accidentally switching to the default format when appending new segments to an existing UniqueId. - UniqueIdFormat properly quotes custom characters for matching. - UniqueIdFormat no longer hard codes opening and closing characters for segments to '[' and ']' when formatting a UniqueId.
1 parent d5b7d1d commit 0e5ab97

File tree

3 files changed

+135
-53
lines changed

3 files changed

+135
-53
lines changed

junit-engine-api/src/main/java/org/junit/gen5/engine/UniqueId.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,56 +10,64 @@
1010

1111
package org.junit.gen5.engine;
1212

13+
import static org.junit.gen5.commons.meta.API.Usage.Experimental;
14+
1315
import java.util.ArrayList;
1416
import java.util.Collections;
1517
import java.util.List;
18+
import java.util.Objects;
1619
import java.util.Optional;
1720

1821
import org.junit.gen5.commons.meta.API;
22+
import org.junit.gen5.commons.util.ToStringBuilder;
1923

2024
/**
21-
* {@code UniqueId} is a class to encapsulate the creation, parsing and display of unique IDs for {@link TestDescriptor}.
25+
* {@code UniqueId} encapsulates the creation, parsing, and display of unique IDs
26+
* for {@link TestDescriptor TestDescriptors}.
2227
*
2328
* <p>Instances of this class have value semantics and are immutable.</p>
2429
*
2530
* @since 5.0
2631
*/
27-
@API(API.Usage.Experimental)
32+
@API(Experimental)
2833
public class UniqueId implements Cloneable {
2934

3035
private static final String TYPE_ENGINE = "engine";
3136

3237
/**
33-
* Create a {@code UniqueId} by parsing its string representation {@code uniqueIdString}.
34-
*
35-
* <p>Throws {@link org.junit.gen5.commons.JUnitException} if the string cannot be parsed.
38+
* Parse a {@code UniqueId} from the supplied string representation using the
39+
* default format.
3640
*
3741
* @return a properly constructed {@code UniqueId}
42+
* @throws org.junit.gen5.commons.JUnitException if the string cannot be parsed
3843
*/
3944
public static UniqueId parse(String uniqueIdString) {
40-
return uniqueIdFormat.parse(uniqueIdString);
45+
return UniqueIdFormat.getDefault().parse(uniqueIdString);
4146
}
4247

43-
private static final UniqueIdFormat uniqueIdFormat = UniqueIdFormat.getDefault();
44-
45-
private final List<Segment> segments = new ArrayList<>();
46-
4748
/**
48-
* Create an engine's unique ID by providing {@code engineId}
49+
* Create an engine's unique ID by from its {@code engineId} using the default
50+
* format.
4951
*/
5052
public static UniqueId forEngine(String engineId) {
5153
return root(TYPE_ENGINE, engineId);
5254
}
5355

5456
/**
55-
* Create a root unique ID by providing the node type {@code segmentType} and {@code nodeValue}
57+
* Create a root unique ID from the supplied {@code segmentType} and
58+
* {@code nodeValue} using the default format.
5659
*/
5760
public static UniqueId root(String segmentType, String nodeValue) {
5861
List<Segment> segments = Collections.singletonList(new Segment(segmentType, nodeValue));
59-
return new UniqueId(segments);
62+
return new UniqueId(UniqueIdFormat.getDefault(), segments);
6063
}
6164

62-
UniqueId(List<Segment> segments) {
65+
private final UniqueIdFormat uniqueIdFormat;
66+
67+
private final List<Segment> segments = new ArrayList<>();
68+
69+
UniqueId(UniqueIdFormat uniqueIdFormat, List<Segment> segments) {
70+
this.uniqueIdFormat = uniqueIdFormat;
6371
this.segments.addAll(segments);
6472
}
6573

@@ -97,7 +105,7 @@ public UniqueId append(String segmentType, String value) {
97105
}
98106

99107
public UniqueId append(Segment segment) {
100-
UniqueId clone = new UniqueId(segments);
108+
UniqueId clone = new UniqueId(this.uniqueIdFormat, this.segments);
101109
clone.segments.add(segment);
102110
return clone;
103111
}
@@ -140,11 +148,11 @@ public static class Segment {
140148
}
141149

142150
public String getType() {
143-
return type;
151+
return this.type;
144152
}
145153

146154
public String getValue() {
147-
return value;
155+
return this.value;
148156
}
149157

150158
@Override
@@ -154,19 +162,25 @@ public boolean equals(Object o) {
154162
if (o == null || getClass() != o.getClass())
155163
return false;
156164

157-
Segment segment = (Segment) o;
158-
return type.equals(segment.type) && value.equals(segment.value);
165+
Segment that = (Segment) o;
166+
return Objects.equals(this.type, that.type) && Objects.equals(this.value, that.value);
159167

160168
}
161169

162170
@Override
163171
public String toString() {
164-
return String.format("[%s:%s]", getType(), getValue());
172+
// @formatter:off
173+
return new ToStringBuilder(this)
174+
.append("type", this.type)
175+
.append("value", this.value)
176+
.toString();
177+
// @formatter:on
165178
}
166179

167180
@Override
168181
public int hashCode() {
169-
return 31 * type.hashCode() + value.hashCode();
182+
return Objects.hash(this.type, this.value);
170183
}
171184
}
185+
172186
}

junit-engine-api/src/main/java/org/junit/gen5/engine/UniqueIdFormat.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,50 @@
1111
package org.junit.gen5.engine;
1212

1313
import static java.util.stream.Collectors.joining;
14+
import static java.util.stream.Collectors.toList;
1415

1516
import java.util.Arrays;
1617
import java.util.List;
1718
import java.util.regex.Matcher;
1819
import java.util.regex.Pattern;
19-
import java.util.stream.Collectors;
2020

2121
import org.junit.gen5.commons.JUnitException;
2222
import org.junit.gen5.commons.util.Preconditions;
2323
import org.junit.gen5.engine.UniqueId.Segment;
2424

2525
/**
26-
* Used to parse a unique ID string representation into a {@link UniqueId}
27-
* or to format a {@link UniqueId} into a string representation.
26+
* Used to {@link #parse} a {@link UniqueId} from a string representation
27+
* or to {@link #format} a {@link UniqueId} into a string representation.
2828
*
2929
* @since 5.0
3030
*/
3131
public class UniqueIdFormat {
3232

33+
private static final UniqueIdFormat defaultFormat = new UniqueIdFormat('[', ':', ']', '/');
34+
35+
public static UniqueIdFormat getDefault() {
36+
return defaultFormat;
37+
}
38+
3339
private final char openSegment;
3440
private final char closeSegment;
3541
private final char segmentDelimiter;
3642
private final char typeValueSeparator;
3743
private final Pattern segmentPattern;
3844

39-
public static UniqueIdFormat getDefault() {
40-
return new UniqueIdFormat('[', ':', ']', '/');
41-
}
42-
4345
public UniqueIdFormat(char openSegment, char typeValueSeparator, char closeSegment, char segmentDelimiter) {
4446
this.openSegment = openSegment;
4547
this.typeValueSeparator = typeValueSeparator;
4648
this.closeSegment = closeSegment;
4749
this.segmentDelimiter = segmentDelimiter;
4850
this.segmentPattern = Pattern.compile(
49-
String.format("\\%s(.+)\\%s(.+)\\%s", openSegment, typeValueSeparator, closeSegment));
51+
String.format("%s(.+)%s(.+)%s", quote(openSegment), quote(typeValueSeparator), quote(closeSegment)));
5052
}
5153

5254
public UniqueId parse(String source) {
53-
String[] parts = source.split(Character.toString(this.segmentDelimiter));
54-
List<Segment> segments = Arrays.stream(parts).map(this::createSegment).collect(Collectors.toList());
55-
return new UniqueId(segments);
55+
String[] parts = source.split(String.valueOf(this.segmentDelimiter));
56+
List<Segment> segments = Arrays.stream(parts).map(this::createSegment).collect(toList());
57+
return new UniqueId(this, segments);
5658
}
5759

5860
private Segment createSegment(String segmentString) {
@@ -85,12 +87,17 @@ public String format(UniqueId uniqueId) {
8587
// @formatter:off
8688
return uniqueId.getSegments().stream()
8789
.map(this::describe)
88-
.collect(joining(Character.toString(this.segmentDelimiter)));
90+
.collect(joining(String.valueOf(this.segmentDelimiter)));
8991
// @formatter:on
9092
}
9193

9294
private String describe(Segment segment) {
93-
return String.format("[%s%s%s]", segment.getType(), this.typeValueSeparator, segment.getValue());
95+
return String.format("%s%s%s%s%s", this.openSegment, segment.getType(), this.typeValueSeparator,
96+
segment.getValue(), this.closeSegment);
97+
}
98+
99+
private static String quote(char c) {
100+
return Pattern.quote(String.valueOf(c));
94101
}
95102

96103
}

junit-tests/src/test/java/org/junit/gen5/engine/UniqueIdFormatTests.java

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
package org.junit.gen5.engine;
1212

13-
import org.junit.gen5.api.Assertions;
13+
import static org.junit.gen5.api.Assertions.assertEquals;
14+
import static org.junit.gen5.api.Assertions.assertTrue;
15+
import static org.junit.gen5.api.Assertions.expectThrows;
16+
1417
import org.junit.gen5.api.Nested;
1518
import org.junit.gen5.api.Test;
1619
import org.junit.gen5.commons.JUnitException;
@@ -23,61 +26,119 @@ class UniqueIdFormatTests {
2326

2427
static final String ENGINE_ID = "junit5";
2528

26-
private final UniqueIdFormat format = new UniqueIdFormat('[', ':', ']', '/');
27-
2829
@Nested
2930
class Formatting {
3031

32+
private final UniqueIdFormat format = UniqueIdFormat.getDefault();
33+
3134
@Test
3235
void uniqueIdOnly() {
3336
UniqueId uniqueId = UniqueId.root("engine", ENGINE_ID);
34-
Assertions.assertEquals("[engine:junit5]", uniqueId.getUniqueString());
37+
assertEquals("[engine:junit5]", uniqueId.getUniqueString());
38+
assertEquals(format.format(uniqueId), uniqueId.getUniqueString());
3539
}
3640

3741
@Test
3842
void withTwoSegments() {
3943
UniqueId engineId = UniqueId.root("engine", ENGINE_ID);
4044
UniqueId classId = engineId.append("class", "org.junit.MyClass");
41-
Assertions.assertEquals("[engine:junit5]/[class:org.junit.MyClass]", classId.getUniqueString());
45+
assertEquals("[engine:junit5]/[class:org.junit.MyClass]", classId.getUniqueString());
46+
assertEquals(format.format(classId), classId.getUniqueString());
4247
}
4348

4449
@Test
4550
void withManySegments() {
4651
UniqueId engineId = UniqueId.root("engine", ENGINE_ID);
4752
UniqueId uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3");
48-
Assertions.assertEquals("[engine:junit5]/[t1:v1]/[t2:v2]/[t3:v3]", uniqueId.getUniqueString());
53+
assertEquals("[engine:junit5]/[t1:v1]/[t2:v2]/[t3:v3]", uniqueId.getUniqueString());
54+
assertEquals(format.format(uniqueId), uniqueId.getUniqueString());
55+
}
56+
57+
}
58+
59+
@Nested
60+
class ParsingWithDefaultFormat implements ParsingTest {
61+
62+
private final UniqueIdFormat format = UniqueIdFormat.getDefault();
63+
64+
@Override
65+
public UniqueIdFormat getFormat() {
66+
return this.format;
67+
}
68+
69+
@Override
70+
public String getEngineUid() {
71+
return "[engine:junit5]";
72+
}
73+
74+
@Override
75+
public String getMethodUid() {
76+
return "[engine:junit5]/[class:MyClass]/[method:myMethod]";
4977
}
5078

5179
}
5280

5381
@Nested
54-
class Parsing {
82+
class ParsingWithCustomFormat implements ParsingTest {
83+
84+
private final UniqueIdFormat format = new UniqueIdFormat('{', '=', '}', ',');
85+
86+
@Override
87+
public UniqueIdFormat getFormat() {
88+
return this.format;
89+
}
90+
91+
@Override
92+
public String getEngineUid() {
93+
return "{engine=junit5}";
94+
}
95+
96+
@Override
97+
public String getMethodUid() {
98+
return "{engine=junit5},{class=MyClass},{method=myMethod}";
99+
}
100+
101+
}
102+
103+
// -------------------------------------------------------------------------
104+
105+
private static void assertSegment(Segment segment, String expectedType, String expectedValue) {
106+
assertEquals(expectedType, segment.getType(), "segment type");
107+
assertEquals(expectedValue, segment.getValue(), "segment value");
108+
}
109+
110+
interface ParsingTest {
111+
112+
UniqueIdFormat getFormat();
113+
114+
String getEngineUid();
115+
116+
String getMethodUid();
55117

56118
@Test
57-
void parseError() {
58-
Throwable throwable = Assertions.expectThrows(JUnitException.class, () -> format.parse("malformed uid"));
59-
Assertions.assertTrue(throwable.getMessage().contains("malformed uid"));
119+
default void parseMalformedUid() {
120+
Throwable throwable = expectThrows(JUnitException.class, () -> getFormat().parse("malformed UID"));
121+
assertTrue(throwable.getMessage().contains("malformed UID"));
60122
}
61123

62124
@Test
63-
void parseEngineIdOnly() {
64-
UniqueId parsedId = format.parse("[engine:junit5]");
125+
default void parseEngineUid() {
126+
UniqueId parsedId = getFormat().parse(getEngineUid());
65127
assertSegment(parsedId.getSegments().get(0), "engine", "junit5");
128+
assertEquals(getEngineUid(), getFormat().format(parsedId));
129+
assertEquals(getEngineUid(), parsedId.getUniqueString());
66130
}
67131

68132
@Test
69-
void parseLongerId() {
70-
UniqueId parsedId = format.parse("[engine:junit5]/[class:MyClass]/[method:myMethod]");
133+
default void parseMethodUid() {
134+
UniqueId parsedId = getFormat().parse(getMethodUid());
71135
assertSegment(parsedId.getSegments().get(0), "engine", "junit5");
72136
assertSegment(parsedId.getSegments().get(1), "class", "MyClass");
73137
assertSegment(parsedId.getSegments().get(2), "method", "myMethod");
138+
assertEquals(getMethodUid(), getFormat().format(parsedId));
139+
assertEquals(getMethodUid(), parsedId.getUniqueString());
74140
}
75141

76142
}
77143

78-
private void assertSegment(Segment segment, String expectedType, String expectedValue) {
79-
Assertions.assertEquals(expectedType, segment.getType(), "segment type");
80-
Assertions.assertEquals(expectedValue, segment.getValue(), "segment value");
81-
}
82-
83144
}

0 commit comments

Comments
 (0)