/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, version 2 as published by the Free Software * Foundation. * * You should have received a copy of the GNU General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program 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 General Public License for more details. * * * Copyright 2006 - 2016 Pentaho Corporation. All rights reserved. */ package org.pentaho.reporting.platform.plugin.async; import com.google.common.util.concurrent.ListenableFuture; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.ISecurityHelper; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.security.SecurityHelper; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.event.ReportProgressListener; import org.pentaho.reporting.libraries.base.config.ModifiableConfiguration; import org.pentaho.reporting.platform.plugin.AuditWrapper; import org.pentaho.reporting.platform.plugin.SimpleReportingComponent; import org.pentaho.reporting.platform.plugin.staging.AsyncJobFileStagingHandler; import org.pentaho.reporting.platform.plugin.staging.IFixedSizeStreamingContent; import java.io.File; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Created by dima.prokopenko@gmail.com on 2/17/2016. */ public class PentahoAsyncExecutionTest { @Rule public Timeout globalTimeout = new Timeout( 10000 ); IPentahoSession userSession = mock( IPentahoSession.class ); SimpleReportingComponent component = mock( SimpleReportingComponent.class ); AsyncJobFileStagingHandler handler = mock( AsyncJobFileStagingHandler.class ); IFixedSizeStreamingContent input; MasterReport report = mock( MasterReport.class ); ModifiableConfiguration configuration = mock( ModifiableConfiguration.class ); private int autoSchedulerThreshold = 0; File temp; @Before public void before() throws Exception { PentahoSystem.clearObjectFactory(); PentahoSessionHolder.removeSession(); PentahoSessionHolder.setSession( userSession ); temp = File.createTempFile( "junit", "tmp" ); input = new AsyncJobFileStagingHandler.FixedSizeStagingContent( temp ); when( handler.getStagingContent() ).thenReturn( input ); when( report.getReportConfiguration() ).thenReturn( configuration ); when( component.getReport() ).thenReturn( report ); when( userSession.getId() ).thenReturn( "junit" ); when( userSession.getName() ).thenReturn( "junit" ); final ISecurityHelper iSecurityHelper = mock( ISecurityHelper.class ); when( iSecurityHelper.runAsUser( any(), any() ) ).thenAnswer( new Answer<Object>() { @Override public Object answer( InvocationOnMock invocation ) throws Throwable { final Object call = ( (Callable) invocation.getArguments()[ 1 ] ).call(); return call; } } ); SecurityHelper.setMockInstance( iSecurityHelper ); } @After public void after() { PentahoSystem.clearObjectFactory(); PentahoSessionHolder.removeSession(); } @Test public void testListenerSuccessExecution() throws Exception { when( component.execute() ).thenReturn( true ); PentahoAsyncReportExecution exec = createMockCallable(); // simulates queuing exec.notifyTaskQueued( UUID.randomUUID(), Collections.<ReportProgressListener>emptyList() ); assertEquals( AsyncExecutionStatus.QUEUED, exec.getState().getStatus() ); IFixedSizeStreamingContent returnStream = exec.call(); assertEquals( AsyncExecutionStatus.FINISHED, exec.getState().getStatus() ); assertEquals( input, returnStream ); verify( handler, times( 1 ) ).getStagingContent(); } private PentahoAsyncReportExecution createMockCallable() { return new PentahoAsyncReportExecution( "junit-path", component, handler, userSession, "not null", AuditWrapper.NULL ) { @Override protected AsyncReportStatusListener createListener( UUID id, List<? extends ReportProgressListener> callbackListener ) { return new AsyncReportStatusListener( "display_path", id, "text/html", callbackListener ); } }; } @Test public void testListenerFailExecution() throws Exception { when( component.execute() ).thenReturn( false ); PentahoAsyncReportExecution exec = createMockCallable(); // simulates queuing exec.notifyTaskQueued( UUID.randomUUID(), Collections.<ReportProgressListener>emptyList() ); assertEquals( AsyncExecutionStatus.QUEUED, exec.getState().getStatus() ); IFixedSizeStreamingContent returnStream = exec.call(); assertEquals( AsyncExecutionStatus.FAILED, exec.getState().getStatus() ); assertFalse( returnStream.equals( input ) ); //For cleanup verify( handler, times( 1 ) ).getStagingContent(); assertFalse( temp.exists() ); } @Test public void testCancellationFeature() throws InterruptedException { final AtomicBoolean run = new AtomicBoolean( false ); PentahoAsyncReportExecution spy = this.getSleepingSpy( run, this.userSession ); PentahoAsyncExecutor executor = new PentahoAsyncExecutor( 13, autoSchedulerThreshold ); UUID id = executor.addTask( spy, this.userSession ); Thread.sleep( 100 ); Future<IFixedSizeStreamingContent> fu = executor.getFuture( id, this.userSession ); assertFalse( fu.isDone() ); fu.cancel( true ); verify( spy, times( 1 ) ).cancel(); assertTrue( fu.isCancelled() ); } @Test public void testOnLogout() { // mock 2 separate sessions IPentahoSession session1 = mock( IPentahoSession.class ); IPentahoSession session2 = mock( IPentahoSession.class ); UUID sessionUid1 = UUID.randomUUID(); UUID sessionUid2 = UUID.randomUUID(); UUID uuid1 = UUID.randomUUID(); UUID uuid2 = UUID.randomUUID(); when( session1.getId() ).thenReturn( sessionUid1.toString() ); when( session2.getId() ).thenReturn( sessionUid2.toString() ); PentahoSessionHolder.setSession( session1 ); PentahoAsyncExecutor exec = new PentahoAsyncExecutor( 10, autoSchedulerThreshold ); AtomicBoolean run = new AtomicBoolean( false ); PentahoAsyncReportExecution napster = getSleepingSpy( run, session1 ); assertEquals( "Task created for fist session", session1, napster.safeSession ); // invoke never ending execution: UUID id1 = exec.addTask( napster, session1 ); // invoke logout for incorrect session PentahoSystem.invokeLogoutListeners( session2 ); Future<IFixedSizeStreamingContent> input1 = exec.getFuture( id1, session1 ); assertNotNull( input1 ); assertFalse( input1.isCancelled() ); // invoke logout for correct session PentahoSystem.invokeLogoutListeners( session1 ); Future<IFixedSizeStreamingContent> input11 = exec.getFuture( id1, session1 ); // unable to get link to a canceled future assertNull( input11 ); // but since we ALREADY has a link to it we can check cancel status: assertTrue( input1.isCancelled() ); } @Test public void testRequestedPage() throws Exception { final AsyncReportStatusListener listener = mock( AsyncReportStatusListener.class ); final PentahoAsyncReportExecution execution = new PentahoAsyncReportExecution( "junit-path", component, handler, userSession, "id", AuditWrapper.NULL ) { @Override protected AsyncReportStatusListener createListener( UUID instanceId, List<? extends ReportProgressListener> callbackListeners ) { return listener; } }; execution.notifyTaskQueued( UUID.randomUUID(), Collections.<ReportProgressListener>emptyList() ); execution.requestPage( 500 ); verify( listener, times( 1 ) ).setRequestedPage( 500 ); } @Test public void testDefaultWrapper() { final AbstractAsyncReportExecution<IAsyncReportState> abstractAsyncReportExecution = new AbstractAsyncReportExecution<IAsyncReportState>( "junit-path", component, handler, userSession, "id" ) { @Override public IFixedSizeStreamingContent call() throws Exception { return null; } @Override public IAsyncReportState getState() { return null; } }; assertEquals( abstractAsyncReportExecution.getAudit(), AuditWrapper.NULL ); } @Test( expected = IllegalStateException.class ) public void testAlreadyQueued() { final AbstractAsyncReportExecution<IAsyncReportState> abstractAsyncReportExecution = new AbstractAsyncReportExecution<IAsyncReportState>( "junit-path", component, handler, userSession, "id" ) { @Override public IFixedSizeStreamingContent call() throws Exception { return null; } @Override public IAsyncReportState getState() { return null; } }; abstractAsyncReportExecution.notifyTaskQueued( UUID.randomUUID(), Collections.emptyList() ); abstractAsyncReportExecution.notifyTaskQueued( UUID.randomUUID(), Collections.emptyList() ); } @Test @SuppressWarnings( "unchecked" ) public void testCancelScheduled() { final AbstractAsyncReportExecution<IAsyncReportState> abstractAsyncReportExecution = new AbstractAsyncReportExecution<IAsyncReportState>( "junit-path", component, handler, userSession, "id" ) { @Override public IFixedSizeStreamingContent call() throws Exception { return null; } @Override public IAsyncReportState getState() { return null; } }; abstractAsyncReportExecution.notifyTaskQueued( UUID.randomUUID(), Collections.emptyList() ); assertTrue( abstractAsyncReportExecution.schedule() ); assertFalse( abstractAsyncReportExecution.delegate( mock( ListenableFuture.class ) ).cancel( true ) ); } @Test public void testPreSchedule() { final PentahoAsyncReportExecution execution = new PentahoAsyncReportExecution( "junit-path", component, handler, userSession, "id", AuditWrapper.NULL ); execution.notifyTaskQueued( UUID.randomUUID(), Collections.emptyList() ); assertEquals( AsyncExecutionStatus.QUEUED, execution.getState().getStatus() ); assertTrue( execution.preSchedule() ); assertEquals( AsyncExecutionStatus.PRE_SCHEDULED, execution.getState().getStatus() ); assertTrue( execution.schedule() ); assertEquals( AsyncExecutionStatus.SCHEDULED, execution.getState().getStatus() ); assertFalse( execution.preSchedule() ); } @Test public void testCancelNoListener() { final AbstractAsyncReportExecution<IAsyncReportState> abstractAsyncReportExecution = new AbstractAsyncReportExecution<IAsyncReportState>( "junit-path", component, handler, userSession, "id" ) { @Override public IFixedSizeStreamingContent call() throws Exception { return null; } @Override public IAsyncReportState getState() { return null; } }; abstractAsyncReportExecution.cancel(); } private PentahoAsyncReportExecution getSleepingSpy( final AtomicBoolean run, IPentahoSession session ) { PentahoAsyncReportExecution exec = new PentahoAsyncReportExecution( "junit-path", component, handler, session, "junit", AuditWrapper.NULL ) { @Override public IFixedSizeStreamingContent call() throws Exception { while ( !run.get() && !Thread.currentThread().isInterrupted() ) { Thread.sleep( 10 ); } return null; } }; PentahoAsyncReportExecution spy = spy( exec ); return spy; } }