FailedConstructionHelpersTest.java
package tools.jackson.core.unittest.base;
import java.io.Closeable;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import tools.jackson.core.base.DecorableTSFactory;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.json.JsonFactory;
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 for {@link DecorableTSFactory} failed-construction cleanup helpers, called
* from a format backend (that is: a sub-class in another package, the way backends
* that extend {@code DecorableTSFactory} directly -- like {@code IonFactory} -- have
* to, since they implement their own {@code File} / {@code Path} create methods).
*/
@SuppressWarnings("serial")
class FailedConstructionHelpersTest extends JacksonCoreTestBase
{
/**
* Stand-in for a format backend: only exposes the {@code protected} helpers so
* that they can be exercised directly.
*/
static class BackendFactory extends JsonFactory {
public BackendFactory(RecyclerPool<BufferRecycler> pool) {
super(JsonFactory.builder().recyclerPool(pool));
}
public IOContext newContext() {
return _createContext(_createContentReference(this), true);
}
public void release(IOContext ioCtxt, RuntimeException failure) {
_releaseOnFailedConstruction(ioCtxt, failure);
}
public void close(Closeable toClose, Closeable rawFallback, RuntimeException failure) {
_closeOnFailedConstruction(toClose, rawFallback, failure);
}
}
static class TrackedCloseable implements Closeable {
private final boolean _failOnClose;
public int closeCount;
public TrackedCloseable(boolean failOnClose) { _failOnClose = failOnClose; }
@Override
public void close() throws IOException {
++closeCount;
if (_failOnClose) {
throw new IOException("Test-induced close failure");
}
}
}
private BackendFactory factoryWith(RecyclerPool<BufferRecycler> pool) {
return new BackendFactory(pool);
}
// Context is released, returning `BufferRecycler` lease to the pool
@Test
void releaseReturnsRecyclerToPool() {
RecyclerPool<BufferRecycler> pool = JsonRecyclerPools.newBoundedPool(5);
BackendFactory f = factoryWith(pool);
IOContext ioCtxt = f.newContext();
assertEquals(0, pool.pooledCount());
RuntimeException failure = new IllegalStateException("Construction failed");
f.release(ioCtxt, failure);
assertEquals(1, pool.pooledCount());
assertEquals(0, failure.getSuppressed().length);
}
// Backends create the context within their `try` block, so handler may be
// reached before there is anything to release
@Test
void releaseAllowsNullContext() {
BackendFactory f = factoryWith(JsonRecyclerPools.newBoundedPool(5));
RuntimeException failure = new IllegalStateException("Construction failed");
assertDoesNotThrow(() -> f.release(null, failure));
assertEquals(0, failure.getSuppressed().length);
}
// Closing wrapper is expected to close what it wraps: no double close
@Test
void closeOnlyClosesWrapperIfItSucceeds() {
BackendFactory f = factoryWith(JsonRecyclerPools.newBoundedPool(5));
TrackedCloseable raw = new TrackedCloseable(false);
TrackedCloseable wrapper = new TrackedCloseable(false);
RuntimeException failure = new IllegalStateException("Construction failed");
f.close(wrapper, raw, failure);
assertEquals(1, wrapper.closeCount);
assertEquals(0, raw.closeCount);
assertEquals(0, failure.getSuppressed().length);
}
// ... but if it fails, source/target Jackson opened must not be left leaked
@Test
void closeFallsBackToRawIfWrapperCloseFails() {
BackendFactory f = factoryWith(JsonRecyclerPools.newBoundedPool(5));
TrackedCloseable raw = new TrackedCloseable(false);
TrackedCloseable wrapper = new TrackedCloseable(true);
RuntimeException failure = new IllegalStateException("Construction failed");
f.close(wrapper, raw, failure);
assertEquals(1, wrapper.closeCount);
assertEquals(1, raw.closeCount);
assertEquals(1, failure.getSuppressed().length);
}
// Same if wrapper was never created (failure before decoration)
@Test
void closeUsesRawIfWrapperNotCreated() {
BackendFactory f = factoryWith(JsonRecyclerPools.newBoundedPool(5));
TrackedCloseable raw = new TrackedCloseable(false);
RuntimeException failure = new IllegalStateException("Construction failed");
f.close(null, raw, failure);
assertEquals(1, raw.closeCount);
assertEquals(0, failure.getSuppressed().length);
}
// And nothing to close is fine too
@Test
void closeAllowsNothingToClose() {
BackendFactory f = factoryWith(JsonRecyclerPools.newBoundedPool(5));
RuntimeException failure = new IllegalStateException("Construction failed");
assertDoesNotThrow(() -> f.close(null, null, failure));
assertEquals(0, failure.getSuppressed().length);
}
}