/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import org.junit.Assert; import org.junit.Test; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicInteger; public class ByteToMessageDecoderTest { @Test public void testRemoveItself() { EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { private boolean removed; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { Assert.assertFalse(removed); in.readByte(); ctx.pipeline().remove(this); removed = true; } }); ByteBuf buf = Unpooled.wrappedBuffer(new byte[] {'a', 'b', 'c'}); channel.writeInbound(buf.copy()); ByteBuf b = (ByteBuf) channel.readInbound(); Assert.assertEquals(b, buf.skipBytes(1)); b.release(); buf.release(); } @Test public void testRemoveItselfWriteBuffer() { final ByteBuf buf = Unpooled.buffer().writeBytes(new byte[]{'a', 'b', 'c'}); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { private boolean removed; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { Assert.assertFalse(removed); in.readByte(); ctx.pipeline().remove(this); // This should not let it keep call decode buf.writeByte('d'); removed = true; } }); channel.writeInbound(buf.copy()); ByteBuf b = (ByteBuf) channel.readInbound(); Assert.assertEquals(b, Unpooled.wrappedBuffer(new byte[] { 'b', 'c'})); buf.release(); b.release(); } /** * Verifies that internal buffer of the ByteToMessageDecoder is released once decoder is removed from pipeline. In * this case input is read fully. */ @Test public void testInternalBufferClearReadAll() { final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a'})); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { ByteBuf byteBuf = internalBuffer(); Assert.assertEquals(1, byteBuf.refCnt()); in.readByte(); // Removal from pipeline should clear internal buffer ctx.pipeline().remove(this); Assert.assertEquals(0, byteBuf.refCnt()); } }); Assert.assertFalse(channel.writeInbound(buf)); Assert.assertFalse(channel.finish()); } /** * Verifies that internal buffer of the ByteToMessageDecoder is released once decoder is removed from pipeline. In * this case input was not fully read. */ @Test public void testInternalBufferClearReadPartly() { final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a', 'b'})); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { ByteBuf byteBuf = internalBuffer(); Assert.assertEquals(1, byteBuf.refCnt()); in.readByte(); // Removal from pipeline should clear internal buffer ctx.pipeline().remove(this); Assert.assertEquals(0, byteBuf.refCnt()); } }); Assert.assertTrue(channel.writeInbound(buf)); Assert.assertTrue(channel.finish()); Assert.assertEquals(channel.readInbound(), Unpooled.wrappedBuffer(new byte[] {'b'})); Assert.assertNull(channel.readInbound()); } @Test public void testFireChannelReadCompleteOnInactive() throws InterruptedException { final BlockingQueue<Integer> queue = new LinkedBlockingDeque<Integer>(); final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a', 'b'})); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { in.skipBytes(in.readableBytes()); if (!ctx.channel().isActive()) { out.add("data"); } } }, new ChannelInboundHandlerAdapter() { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { queue.add(3); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { queue.add(1); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { if (!ctx.channel().isActive()) { queue.add(2); } } }); Assert.assertFalse(channel.writeInbound(buf)); channel.finish(); Assert.assertEquals(1, (int) queue.take()); Assert.assertEquals(2, (int) queue.take()); Assert.assertEquals(3, (int) queue.take()); Assert.assertTrue(queue.isEmpty()); } // See https://github.com/netty/netty/pull/3263 @Test public void testFireChannelReadCompleteOnlyWhenDecoded() { final AtomicInteger readComplete = new AtomicInteger(); EmbeddedChannel ch = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // Do nothing } }, new ChannelInboundHandlerAdapter() { @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { readComplete.incrementAndGet(); } }); Assert.assertFalse(ch.writeInbound(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII))); Assert.assertFalse(ch.finish()); Assert.assertEquals(0, readComplete.get()); } // See https://github.com/netty/netty/pull/3263 @Test public void testFireChannelReadCompleteWhenDecodeOnce() { final AtomicInteger readComplete = new AtomicInteger(); EmbeddedChannel ch = new EmbeddedChannel(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.fireChannelRead(msg); ctx.fireChannelRead(Unpooled.EMPTY_BUFFER); } }, new ByteToMessageDecoder() { private boolean first = true; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (first) { first = false; out.add(in.readSlice(in.readableBytes()).retain()); } } }, new ChannelInboundHandlerAdapter() { @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { readComplete.incrementAndGet(); } }); Assert.assertTrue(ch.writeInbound(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII))); Assert.assertTrue(ch.finish()); Assert.assertEquals(1, readComplete.get()); for (;;) { ByteBuf buf = (ByteBuf) ch.readInbound(); if (buf == null) { break; } buf.release(); } } }