pitest / pitclipse

Mutation testing for Java in Eclipse IDE. Based on PIT (Pitest).
https://pitest.org
Apache License 2.0
59 stars 17 forks source link

Test failures with Java 17 #215

Closed LorenzoBettini closed 5 months ago

LorenzoBettini commented 1 year ago

These two tests always fail when the underlying JVM is Java 17:

org.pitest.pitclipse.runner.io.ObjectStreamSocketTest.writeThrowsException()

org.pitest.pitclipse.runner.io.ObjectStreamSocketTest.readThrowsException()

They mock the socket like that:

@Test
public void readThrowsException() throws IOException, ClassNotFoundException {
    InputStream inputStream = spy(new ByteArrayInputStream(asBytes(expectedObject)));
    OutputStream outputStream = new ByteArrayOutputStream();
    when(underlyingSocket.getInputStream()).thenReturn(inputStream);
    when(underlyingSocket.getOutputStream()).thenReturn(outputStream);
    objectSocket = ObjectStreamSocket.make(underlyingSocket);
    objectSocket.read(); // succeeds
    try {
        objectSocket.read(); // EOF
        fail("should not get here");
    } catch (Exception e) {
        assertTrue(e.getClass().getCanonicalName(),
            e instanceof RuntimeException);
    }
}

The failure is when we call objectSocket = ObjectStreamSocket.make(underlyingSocket);

This is the interesting exception trace

Caused by: java.io.EOFException
    at java.base/java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2915)
    at java.base/java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3410)
    at java.base/java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:954)
    at java.base/java.io.ObjectInputStream.<init>(ObjectInputStream.java:392)
    at org.pitest.pitclipse.runner.io.ObjectStreamSocket.make(ObjectStreamSocket.java:51)
    ... 46 more

That is

public static ObjectStreamSocket make(Socket underlyingSocket) {
    try {
        ObjectOutputStream outputStream = new ObjectOutputStream(underlyingSocket.getOutputStream());
        ObjectInputStream inputStream = new ObjectInputStream(underlyingSocket.getInputStream());
        return make(underlyingSocket, inputStream, outputStream);
    } catch (IOException e) {
        throw new StreamInitialisationException(e);
    }
}

Maybe that is due to some changes in the implementation in Java 17: the constructor reads the header (maybe that was not the case in Java 11?), and this generates an EOF since we indeed have an empty socket:

/**
    * Creates an ObjectInputStream that reads from the specified InputStream.
    * A serialization stream header is read from the stream and verified.
    * This constructor will block until the corresponding ObjectOutputStream
    * has written and flushed the header.
    *
    * <p>The constructor initializes the deserialization filter to the filter returned
    * by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
    * and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
    *
    * <p>If a security manager is installed, this constructor will check for
    * the "enableSubclassImplementation" SerializablePermission when invoked
    * directly or indirectly by the constructor of a subclass which overrides
    * the ObjectInputStream.readFields or ObjectInputStream.readUnshared
    * methods.
    *
    * @param   in input stream to read from
    * @throws  StreamCorruptedException if the stream header is incorrect
    * @throws  IOException if an I/O error occurs while reading stream header
    * @throws  SecurityException if untrusted subclass illegally overrides
    *          security-sensitive methods
    * @throws  NullPointerException if {@code in} is {@code null}
    * @see     ObjectInputStream#ObjectInputStream()
    * @see     ObjectInputStream#readFields()
    * @see     ObjectOutputStream#ObjectOutputStream(OutputStream)
    */
public ObjectInputStream(InputStream in) throws IOException {
    verifySubclass();
    bin = new BlockDataInputStream(in);
    handles = new HandleTable(10);
    vlist = new ValidationList();
    streamFilterSet = false;
    serialFilter = Config.getSerialFilterFactorySingleton().apply(null, Config.getSerialFilter());
    enableOverride = false;
    readStreamHeader();
    bin.setBlockDataMode(true);
}
LorenzoBettini commented 1 year ago

In Java 8 we have

    public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }

    protected void readStreamHeader()
        throws IOException, StreamCorruptedException
    {
        short s0 = bin.readShort();
        short s1 = bin.readShort();
        if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
            throw new StreamCorruptedException(
                String.format("invalid stream header: %04X%04X", s0, s1));
        }
    }

        public short readShort() throws IOException {
            if (!blkmode) {
                pos = 0;
                in.readFully(buf, 0, 2);
            } else if (end - pos < 2) {
                return din.readShort();
            }
            short v = Bits.getShort(buf, pos);
            pos += 2;
            return v;
        }

So readFully was not used and we didn't get any exception. Probably it's like that also in Java 11.