/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.fabric.netty.util; import com.liferay.portal.fabric.netty.NettyTestUtil; import com.liferay.portal.kernel.concurrent.DefaultNoticeableFuture; import com.liferay.portal.kernel.test.CaptureHandler; import com.liferay.portal.kernel.test.JDKLoggerTestUtil; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor; import com.liferay.portal.kernel.util.Time; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; import io.netty.channel.SingleThreadEventLoop; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.local.LocalEventLoopGroup; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.ScheduledFuture; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; /** * @author Shuyang Zhou */ public class NettyUtilTest { @ClassRule public static final CodeCoverageAssertor codeCoverageAssertor = CodeCoverageAssertor.INSTANCE; @Test public void testBindShutdownSuccess() throws InterruptedException { MockEventLoopGroup masterEventLoopGroup = new MockEventLoopGroup(); MockEventLoopGroup salveEventLoopGroup = new MockEventLoopGroup(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( NettyUtil.class.getName(), Level.WARNING)) { NettyUtil.bindShutdown( masterEventLoopGroup, salveEventLoopGroup, 0, 10); Future<?> masterFuture = masterEventLoopGroup.shutdownGracefully(); SyncFutureListener syncFutureListener = new SyncFutureListener(); masterFuture.addListener(syncFutureListener); syncFutureListener.sync(); Future<?> slaveFuture = salveEventLoopGroup.terminationFuture(); slaveFuture.sync(); Assert.assertTrue(slaveFuture.isSuccess()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertTrue(logRecords.isEmpty()); } } @Test public void testBindShutdownTimeout() throws InterruptedException { MockEventLoopGroup masterEventLoopGroup = new MockEventLoopGroup(); MockEventLoopGroup salveEventLoopGroup = new MockEventLoopGroup() { @Override public boolean awaitTermination(long timeout, TimeUnit unit) { return false; } }; try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( NettyUtil.class.getName(), Level.WARNING)) { NettyUtil.bindShutdown( masterEventLoopGroup, salveEventLoopGroup, 0, 10); Future<?> masterFuture = masterEventLoopGroup.shutdownGracefully(); SyncFutureListener syncFutureListener = new SyncFutureListener(); masterFuture.addListener(syncFutureListener); syncFutureListener.sync(); Future<?> slaveFuture = salveEventLoopGroup.terminationFuture(); slaveFuture.sync(); Assert.assertTrue(slaveFuture.isSuccess()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Bind shutdown timeout " + salveEventLoopGroup, logRecord.getMessage()); } } @Test public void testConstructor() { new NettyUtil(); } @Test public void testCreateEmptyChannelPipeline() { ChannelPipeline channelPipeline = NettyUtil.createEmptyChannelPipeline(); Assert.assertEquals( Collections.<String, ChannelHandler>emptyMap(), channelPipeline.toMap()); Channel channel = channelPipeline.channel(); Assert.assertTrue(channel.isActive()); Assert.assertTrue(channel.isOpen()); Assert.assertTrue(channel.isRegistered()); } @Test public void testScheduleCancellation() throws Exception { // Normal finish without log MockEventLoopGroup mockEventLoopGroup = new MockEventLoopGroup(); ReflectionTestUtil.setFieldValue( _embeddedChannel, "eventLoop", mockEventLoopGroup.next()); DefaultNoticeableFuture<Object> defaultNoticeableFuture = new DefaultNoticeableFuture<>(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( NettyUtil.class.getName(), Level.OFF)) { NettyUtil.scheduleCancellation( _embeddedChannel, defaultNoticeableFuture, Time.HOUR); ScheduledFuture<?> scheduledFuture = mockEventLoopGroup.getScheduledFuture(); Assert.assertNotNull(scheduledFuture); Assert.assertFalse(scheduledFuture.isDone()); defaultNoticeableFuture.set(new Object()); Assert.assertTrue(scheduledFuture.isDone()); Assert.assertTrue(scheduledFuture.isCancelled()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertTrue(logRecords.isEmpty()); } // Normal finish with log defaultNoticeableFuture = new DefaultNoticeableFuture<>(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( NettyUtil.class.getName(), Level.FINEST)) { NettyUtil.scheduleCancellation( _embeddedChannel, defaultNoticeableFuture, Time.HOUR); ScheduledFuture<?> scheduledFuture = mockEventLoopGroup.getScheduledFuture(); Assert.assertNotNull(scheduledFuture); Assert.assertFalse(scheduledFuture.isDone()); defaultNoticeableFuture.set(new Object()); Assert.assertTrue(scheduledFuture.isDone()); Assert.assertTrue(scheduledFuture.isCancelled()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Cancelled scheduled cancellation for " + defaultNoticeableFuture, logRecord.getMessage()); } // Timeout cancel without log defaultNoticeableFuture = new DefaultNoticeableFuture<>(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( NettyUtil.class.getName(), Level.OFF)) { NettyUtil.scheduleCancellation( _embeddedChannel, defaultNoticeableFuture, 0); ScheduledFuture<?> scheduledFuture = mockEventLoopGroup.getScheduledFuture(); Assert.assertNotNull(scheduledFuture); scheduledFuture.get(1, TimeUnit.HOURS); Assert.assertFalse(scheduledFuture.isCancelled()); Assert.assertTrue(defaultNoticeableFuture.isCancelled()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertTrue(logRecords.isEmpty()); } // Timeout cancel with log defaultNoticeableFuture = new DefaultNoticeableFuture<>(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( NettyUtil.class.getName(), Level.WARNING)) { NettyUtil.scheduleCancellation( _embeddedChannel, defaultNoticeableFuture, 0); ScheduledFuture<?> scheduledFuture = mockEventLoopGroup.getScheduledFuture(); Assert.assertNotNull(scheduledFuture); scheduledFuture.get(1, TimeUnit.HOURS); Assert.assertFalse(scheduledFuture.isCancelled()); Assert.assertTrue(defaultNoticeableFuture.isCancelled()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Cancelled timeout " + defaultNoticeableFuture, logRecord.getMessage()); } mockEventLoopGroup.shutdownGracefully(); } protected class SyncFutureListener implements FutureListener<Object> { @Override public void operationComplete(Future<Object> f) throws Exception { _countDownLatch.countDown(); } public void sync() throws InterruptedException { _countDownLatch.await(); } private final CountDownLatch _countDownLatch = new CountDownLatch(1); } private final EmbeddedChannel _embeddedChannel = NettyTestUtil.createEmptyEmbeddedChannel(); private static class MockEventLoopGroup extends LocalEventLoopGroup { public MockEventLoopGroup() { super(1); } public ScheduledFuture<?> getScheduledFuture() { return _reference.get(); } @Override protected EventExecutor newChild( ThreadFactory threadFactory, Object... args) { return new SingleThreadEventLoop(this, threadFactory, true) { @Override public ScheduledFuture<?> schedule( Runnable command, long delay, TimeUnit unit) { ScheduledFuture<?> scheduledFuture = super.schedule( command, delay, unit); _reference.set(scheduledFuture); return scheduledFuture; } @Override protected void run() { while (!confirmShutdown()) { Runnable task = takeTask(); if (task != null) { task.run(); updateLastExecutionTime(); } } } }; } private final AtomicReference<ScheduledFuture<?>> _reference = new AtomicReference<>(); } }