Skip to content

Commit c4983ac

Browse files
committed
Ensure backwards compatibility
1 parent 8ae54f3 commit c4983ac

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
import static org.apiguardian.api.API.Status.STABLE;
1414

15+
import java.io.IOException;
16+
import java.io.ObjectInputStream;
17+
import java.io.ObjectOutputStream;
18+
import java.io.ObjectStreamClass;
19+
import java.io.ObjectStreamField;
1520
import java.io.Serial;
1621
import java.io.Serializable;
1722
import java.lang.ref.SoftReference;
@@ -38,7 +43,12 @@
3843
public final class UniqueId implements Cloneable, Serializable {
3944

4045
@Serial
41-
private static final long serialVersionUID = 2L;
46+
private static final long serialVersionUID = 1L;
47+
48+
@Serial
49+
@SuppressWarnings("UnusedVariable")
50+
private static final ObjectStreamField[] serialPersistentFields = ObjectStreamClass.lookup(
51+
SerializedForm.class).getFields();
4252

4353
private static final String ENGINE_SEGMENT_TYPE = "engine";
4454

@@ -82,8 +92,10 @@ public static UniqueId root(String segmentType, String value) {
8292
return new UniqueId(new Segment(segmentType, value));
8393
}
8494

85-
@SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (List.copyOf())
86-
private final List<Segment> segments;
95+
@SuppressWarnings({ "serial", "RedundantSuppression" })
96+
// always used with serializable implementation (List.copyOf())
97+
// This is effectively final but not technically due to late initialization when deserializing
98+
private /* final */ List<Segment> segments;
8799

88100
// lazily computed
89101
private transient int hashCode;
@@ -227,6 +239,18 @@ protected Object clone() throws CloneNotSupportedException {
227239
return super.clone();
228240
}
229241

242+
@Serial
243+
private void writeObject(ObjectOutputStream s) throws IOException {
244+
SerializedForm serializedForm = new SerializedForm(this);
245+
serializedForm.serialize(s);
246+
}
247+
248+
@Serial
249+
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
250+
SerializedForm serializedForm = SerializedForm.deserialize(s);
251+
segments = serializedForm.segments;
252+
}
253+
230254
@Override
231255
public boolean equals(Object o) {
232256
if (this == o) {
@@ -354,4 +378,42 @@ public String toString() {
354378

355379
}
356380

381+
/**
382+
* Represents the serialized output of {@code UniqueId}. The fields on this
383+
* class match the fields that {@code UniqueId} had prior to 6.1.
384+
*/
385+
private static final class SerializedForm implements Serializable {
386+
387+
@Serial
388+
private static final long serialVersionUID = 1L;
389+
390+
@SuppressWarnings({ "serial", "RedundantSuppression" })
391+
// always used with serializable implementation (List.copyOf())
392+
private final List<Segment> segments;
393+
private final UniqueIdFormat uniqueIdFormat;
394+
395+
SerializedForm(UniqueId uniqueId) {
396+
this.segments = uniqueId.segments;
397+
this.uniqueIdFormat = UniqueIdFormat.getDefault();
398+
}
399+
400+
@SuppressWarnings("unchecked")
401+
private SerializedForm(ObjectInputStream.GetField fields) throws IOException, ClassNotFoundException {
402+
this.segments = (List<Segment>) fields.get("segments", null);
403+
this.uniqueIdFormat = UniqueIdFormat.getDefault();
404+
}
405+
406+
void serialize(ObjectOutputStream s) throws IOException {
407+
ObjectOutputStream.PutField fields = s.putFields();
408+
fields.put("segments", segments);
409+
fields.put("uniqueIdFormat", uniqueIdFormat);
410+
s.writeFields();
411+
}
412+
413+
static SerializedForm deserialize(ObjectInputStream s) throws IOException, ClassNotFoundException {
414+
ObjectInputStream.GetField fields = s.readFields();
415+
return new SerializedForm(fields);
416+
}
417+
}
418+
357419
}

platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
import static org.junit.jupiter.api.Assertions.assertTrue;
1919
import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor;
2020

21+
import java.io.ByteArrayInputStream;
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.IOException;
24+
import java.io.ObjectInputStream;
25+
import java.io.ObjectOutputStream;
26+
import java.util.Base64;
2127
import java.util.Optional;
2228

2329
import org.junit.jupiter.api.Nested;
@@ -292,6 +298,75 @@ void removesLastSegment() {
292298

293299
}
294300

301+
@Nested
302+
class Serialization {
303+
304+
final UniqueId uniqueId = UniqueId.root("engine", "junit-jupiter");
305+
306+
@Test
307+
void roundTrip() throws IOException, ClassNotFoundException {
308+
var bytesOut = new ByteArrayOutputStream();
309+
var out = new ObjectOutputStream(bytesOut);
310+
out.writeObject(uniqueId);
311+
312+
var bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
313+
var in = new ObjectInputStream(bytesIn);
314+
var actual = in.readObject();
315+
316+
assertEquals(uniqueId, actual);
317+
assertEquals(uniqueId.toString(), actual.toString());
318+
}
319+
320+
@Test
321+
void deserializeromJunit60() throws IOException, ClassNotFoundException {
322+
var uniqueIdFromJunit60 = Base64.getMimeDecoder().decode("""
323+
rO0ABXNyACJvcmcuanVuaXQucGxhdGZvcm0uZW5naW5lLlVuaXF1ZUlkAAAAAAAAAAECAAJMAAhzZWdtZW50c3QAEExqYXZhL3V0
324+
aWwvTGlzdDtMAA51bmlxdWVJZEZvcm1hdHQAKkxvcmcvanVuaXQvcGxhdGZvcm0vZW5naW5lL1VuaXF1ZUlkRm9ybWF0O3hwc3IA
325+
EWphdmEudXRpbC5Db2xsU2VyV46rtjobqBEDAAFJAAN0YWd4cAAAAAF3BAAAAAFzcgAqb3JnLmp1bml0LnBsYXRmb3JtLmVuZ2lu
326+
ZS5VbmlxdWVJZCRTZWdtZW50AAAAAAAAAAECAAJMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7TAAFdmFsdWVxAH4AB3hwdAAG
327+
ZW5naW5ldAANanVuaXQtanVwaXRlcnhzcgAob3JnLmp1bml0LnBsYXRmb3JtLmVuZ2luZS5VbmlxdWVJZEZvcm1hdAAAAAAAAAAB
328+
AgAGQwAMY2xvc2VTZWdtZW50QwALb3BlblNlZ21lbnRDABBzZWdtZW50RGVsaW1pdGVyQwASdHlwZVZhbHVlU2VwYXJhdG9yTAAT
329+
ZW5jb2RlZENoYXJhY3Rlck1hcHQAE0xqYXZhL3V0aWwvSGFzaE1hcDtMAA5zZWdtZW50UGF0dGVybnQAGUxqYXZhL3V0aWwvcmVn
330+
ZXgvUGF0dGVybjt4cABdAFsALwA6c3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNo
331+
b2xkeHA/QAAAAAAADHcIAAAAEAAAAAZzcgATamF2YS5sYW5nLkNoYXJhY3RlcjSLR9lrGiZ4AgABQwAFdmFsdWV4cAAldAADJTI1
332+
c3EAfgARADp0AAMlM0FzcQB+ABEAW3QAAyU1QnNxAH4AEQArdAADJTJCc3EAfgARAF10AAMlNURzcQB+ABEAL3QAAyUyRnhzcgAX
333+
amF2YS51dGlsLnJlZ2V4LlBhdHRlcm5GZ9VrbkkCDQIAAkkABWZsYWdzTAAHcGF0dGVybnEAfgAHeHAAAAAgdAAXXFFbXEUoLisp
334+
XFE6XEUoLispXFFdXEU=""");
335+
336+
var bytesIn = new ByteArrayInputStream(uniqueIdFromJunit60);
337+
var in = new ObjectInputStream(bytesIn);
338+
var actual = in.readObject();
339+
340+
assertEquals(uniqueId, actual);
341+
assertEquals(uniqueId.toString(), actual.toString());
342+
}
343+
344+
@Test
345+
void deserializeFromJunit60IgnoresCustomFormat() throws IOException, ClassNotFoundException {
346+
var uniqueIdWithCustomFormatFromJunit60 = Base64.getMimeDecoder().decode("""
347+
rO0ABXNyACJvcmcuanVuaXQucGxhdGZvcm0uZW5naW5lLlVuaXF1ZUlkAAAAAAAAAAECAAJMAAhzZWdtZW50c3QAEExqYXZhL3V0
348+
aWwvTGlzdDtMAA51bmlxdWVJZEZvcm1hdHQAKkxvcmcvanVuaXQvcGxhdGZvcm0vZW5naW5lL1VuaXF1ZUlkRm9ybWF0O3hwc3IA
349+
EWphdmEudXRpbC5Db2xsU2VyV46rtjobqBEDAAFJAAN0YWd4cAAAAAF3BAAAAAFzcgAqb3JnLmp1bml0LnBsYXRmb3JtLmVuZ2lu
350+
ZS5VbmlxdWVJZCRTZWdtZW50AAAAAAAAAAECAAJMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7TAAFdmFsdWVxAH4AB3hwdAAG
351+
ZW5naW5ldAANanVuaXQtanVwaXRlcnhzcgAob3JnLmp1bml0LnBsYXRmb3JtLmVuZ2luZS5VbmlxdWVJZEZvcm1hdAAAAAAAAAAB
352+
AgAGQwAMY2xvc2VTZWdtZW50QwALb3BlblNlZ21lbnRDABBzZWdtZW50RGVsaW1pdGVyQwASdHlwZVZhbHVlU2VwYXJhdG9yTAAT
353+
ZW5jb2RlZENoYXJhY3Rlck1hcHQAE0xqYXZhL3V0aWwvSGFzaE1hcDtMAA5zZWdtZW50UGF0dGVybnQAGUxqYXZhL3V0aWwvcmVn
354+
ZXgvUGF0dGVybjt4cAB9AHsALAA9c3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNo
355+
b2xkeHA/QAAAAAAADHcIAAAAEAAAAAZzcgATamF2YS5sYW5nLkNoYXJhY3RlcjSLR9lrGiZ4AgABQwAFdmFsdWV4cAAldAADJTI1
356+
c3EAfgARAHt0AAMlN0JzcQB+ABEAK3QAAyUyQnNxAH4AEQAsdAADJTJDc3EAfgARAH10AAMlN0RzcQB+ABEAPXQAAyUzRHhzcgAX
357+
amF2YS51dGlsLnJlZ2V4LlBhdHRlcm5GZ9VrbkkCDQIAAkkABWZsYWdzTAAHcGF0dGVybnEAfgAHeHAAAAAgdAAXXFF7XEUoLisp
358+
XFE9XEUoLispXFF9XEU=""");
359+
360+
var bytesIn = new ByteArrayInputStream(uniqueIdWithCustomFormatFromJunit60);
361+
var in = new ObjectInputStream(bytesIn);
362+
var actual = in.readObject();
363+
364+
assertEquals(uniqueId, actual);
365+
assertEquals(uniqueId.toString(), actual.toString());
366+
}
367+
368+
}
369+
295370
private static void assertSegment(Segment segment, String expectedType, String expectedValue) {
296371
assertEquals(expectedType, segment.getType(), "segment type");
297372
assertEquals(expectedValue, segment.getValue(), "segment value");

0 commit comments

Comments
 (0)