FailedConstructionRecyclerTest.java

package tools.jackson.core.unittest.json;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.json.JsonFactoryBuilder;
import tools.jackson.core.unittest.JacksonCoreTestBase;
import tools.jackson.core.util.BufferRecycler;
import tools.jackson.core.util.JsonRecyclerPools;
import tools.jackson.core.util.RecyclerPool;

import static org.junit.jupiter.api.Assertions.*;

/**
 * Tests to verify that {@link BufferRecycler} leased by {@link IOContext} gets
 * returned to the pool even if no parser or generator ends up being constructed:
 * context is otherwise only released when parser/generator that owns it is closed.
 */
class FailedConstructionRecyclerTest extends JacksonCoreTestBase
{
    /**
     * Factory that fails the way format backends can (say, when schema
     * validation fails), that is, after {@link IOContext} has been created.
     */
    static class FailingFactory extends JsonFactory {
        private static final long serialVersionUID = 1L;

        public FailingFactory(JsonFactoryBuilder b) { super(b); }

        @Override
        protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
                InputStream in) throws JacksonException {
            throw new IllegalStateException("Test-induced construction failure");
        }

        @Override
        protected JsonGenerator _createGenerator(ObjectWriteContext writeCtxt,
                IOContext ioCtxt, Writer out) throws JacksonException {
            throw new IllegalStateException("Test-induced construction failure");
        }

        @Override
        protected JsonGenerator _createUTF8Generator(ObjectWriteContext writeCtxt,
                IOContext ioCtxt, OutputStream out) throws JacksonException {
            throw new IllegalStateException("Test-induced construction failure");
        }
    }

    // Constraint violation trips before parser gets constructed
    @Test
    void releasesRecyclerOnFailedParserConstruction() throws Exception
    {
        RecyclerPool<BufferRecycler> pool = JsonRecyclerPools.newBoundedPool(5);
        JsonFactory f = JsonFactory.builder()
                .recyclerPool(pool)
                .streamReadConstraints(StreamReadConstraints.builder().maxDocumentLength(2).build())
                .build();
        assertEquals(0, pool.pooledCount());

        byte[] doc = utf8Bytes("{\"a\":1}");
        assertThrows(StreamConstraintsException.class,
                () -> f.createParser(ObjectReadContext.empty(), doc));
        assertEquals(1, pool.pooledCount());

        char[] chars = "{\"a\":1}".toCharArray();
        assertThrows(StreamConstraintsException.class,
                () -> f.createParser(ObjectReadContext.empty(), chars, 0, chars.length));
        assertEquals(1, pool.pooledCount());
    }

    // Caller-supplied bounds checked before parser gets constructed
    @Test
    void releasesRecyclerOnInvalidBounds() throws Exception
    {
        RecyclerPool<BufferRecycler> pool = JsonRecyclerPools.newBoundedPool(5);
        JsonFactory f = JsonFactory.builder().recyclerPool(pool).build();
        assertEquals(0, pool.pooledCount());

        byte[] doc = utf8Bytes("{\"a\":1}");
        assertThrows(StreamReadException.class,
                () -> f.createParser(ObjectReadContext.empty(), doc, 5, 100));
        assertEquals(1, pool.pooledCount());
    }

    @Test
    void releasesRecyclerOnFailedParserConstructionFromStream() throws Exception
    {
        RecyclerPool<BufferRecycler> pool = JsonRecyclerPools.newBoundedPool(5);
        FailingFactory f = new FailingFactory(JsonFactory.builder().recyclerPool(pool));
        assertEquals(0, pool.pooledCount());

        assertThrows(IllegalStateException.class,
                () -> f.createParser(ObjectReadContext.empty(),
                        new ByteArrayInputStream(utf8Bytes("{\"a\":1}"))));
        assertEquals(1, pool.pooledCount());
    }

    @Test
    void releasesRecyclerOnFailedGeneratorConstruction() throws Exception
    {
        for (JsonEncoding enc : new JsonEncoding[] { JsonEncoding.UTF8, JsonEncoding.UTF16_BE }) {
            RecyclerPool<BufferRecycler> pool = JsonRecyclerPools.newBoundedPool(5);
            FailingFactory f = new FailingFactory(JsonFactory.builder().recyclerPool(pool));
            assertEquals(0, pool.pooledCount(), enc.toString());

            assertThrows(IllegalStateException.class,
                    () -> f.createGenerator(ObjectWriteContext.empty(),
                            new ByteArrayOutputStream(), enc));
            assertEquals(1, pool.pooledCount(), enc.toString());
        }
    }
}