Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,9 @@

package org.junit.platform.launcher;

import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;
import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.Serial;
import java.io.Serializable;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -50,10 +42,6 @@ public final class TestIdentifier implements Serializable {

@Serial
private static final long serialVersionUID = 1L;
@Serial
@SuppressWarnings("UnusedVariable")
private static final ObjectStreamField[] serialPersistentFields = ObjectStreamClass.lookup(
SerializedForm.class).getFields();

// These are effectively final but not technically due to late initialization when deserializing
private /* final */ UniqueId uniqueId;
Expand All @@ -64,7 +52,7 @@ public final class TestIdentifier implements Serializable {
private /* final */ String legacyReportingName;

private /* final */ @Nullable TestSource source;
private /* final */ Set<TestTag> tags;
private /* final */ LinkedHashSet<TestTag> tags;
private /* final */ Type type;

/**
Expand All @@ -90,19 +78,11 @@ private TestIdentifier(UniqueId uniqueId, String displayName, @Nullable TestSour
this.parentId = parentId;
this.displayName = displayName;
this.source = source;
this.tags = copyOf(tags);
this.tags = new LinkedHashSet<>(tags);
this.type = type;
this.legacyReportingName = legacyReportingName;
}

private Set<TestTag> copyOf(Set<TestTag> tags) {
return switch (tags.size()) {
case 0 -> emptySet();
case 1 -> Set.of(getOnlyElement(tags));
default -> new LinkedHashSet<>(tags);
};
}

/**
* Get the unique ID of the represented test or container as a
* {@code String}.
Expand Down Expand Up @@ -272,87 +252,4 @@ public String toString() {
// @formatter:on
}

@Serial
private void writeObject(ObjectOutputStream s) throws IOException {
SerializedForm serializedForm = new SerializedForm(this);
serializedForm.serialize(s);
}

@Serial
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
SerializedForm serializedForm = SerializedForm.deserialize(s);
uniqueId = UniqueId.parse(serializedForm.uniqueId);
displayName = serializedForm.displayName;
source = serializedForm.source;
tags = serializedForm.tags;
type = serializedForm.type;
String parentId = serializedForm.parentId;
this.parentId = parentId == null ? null : UniqueId.parse(parentId);
legacyReportingName = serializedForm.legacyReportingName;
}

/**
* Represents the serialized output of {@code TestIdentifier}. The fields on this
* class match the fields that {@code TestIdentifier} had prior to 1.8.
*/
private static class SerializedForm implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

private final String uniqueId;

@Nullable
private final String parentId;

private final String displayName;
private final String legacyReportingName;

@Nullable
private final TestSource source;

@SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (see TestIdentifier#copyOf())
private final Set<TestTag> tags;
private final Type type;

SerializedForm(TestIdentifier testIdentifier) {
this.uniqueId = testIdentifier.uniqueId.toString();
UniqueId parentId = testIdentifier.parentId;
this.parentId = parentId == null ? null : parentId.toString();
this.displayName = testIdentifier.displayName;
this.legacyReportingName = testIdentifier.legacyReportingName;
this.source = testIdentifier.source;
this.tags = testIdentifier.tags;
this.type = testIdentifier.type;
}

@SuppressWarnings("unchecked")
private SerializedForm(ObjectInputStream.GetField fields) throws IOException, ClassNotFoundException {
this.uniqueId = (String) fields.get("uniqueId", null);
this.parentId = (String) fields.get("parentId", null);
this.displayName = (String) fields.get("displayName", null);
this.legacyReportingName = (String) fields.get("legacyReportingName", null);
this.source = (TestSource) fields.get("source", null);
this.tags = (Set<TestTag>) fields.get("tags", null);
this.type = (Type) fields.get("type", null);
}

void serialize(ObjectOutputStream s) throws IOException {
ObjectOutputStream.PutField fields = s.putFields();
fields.put("uniqueId", uniqueId);
fields.put("parentId", parentId);
fields.put("displayName", displayName);
fields.put("legacyReportingName", legacyReportingName);
fields.put("source", source);
fields.put("tags", tags);
fields.put("type", type);
s.writeFields();
}

static SerializedForm deserialize(ObjectInputStream s) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = s.readFields();
return new SerializedForm(fields);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,6 @@ void currentVersionCanBeSerializedAndDeserialized() throws Exception {
assertDeepEquals(originalIdentifier, deserializedIdentifier);
}

@Test
void initialVersionCanBeDeserialized() throws Exception {
try (var inputStream = getClass().getResourceAsStream("/serialized-test-identifier")) {
var bytes = inputStream.readAllBytes();
var deserializedIdentifier = (TestIdentifier) deserialize(bytes);
assertDeepEquals(createOriginalTestIdentifier(), deserializedIdentifier);
}
}

@Test
void identifierWithNoParentCanBeSerializedAndDeserialized() throws Exception {
TestIdentifier originalIdentifier = TestIdentifier.from(
Expand Down
Binary file not shown.