/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed 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 org.pentaho.di.trans;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.io.IOUtils;
import org.apache.commons.vfs2.FileObject;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.pentaho.di.core.KettleEnvironment;
import org.pentaho.di.core.ProgressMonitorListener;
import org.pentaho.di.core.database.Database;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.logging.StepLogTable;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.core.vfs.KettleVFS;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositoryDirectoryInterface;
import org.pentaho.di.trans.step.StepMetaDataCombi;
public class TransTest {
int count = 10000;
Trans trans;
TransMeta meta;
@BeforeClass
public static void beforeClass() throws KettleException {
KettleEnvironment.init();
}
@Before
public void beforeTest() throws KettleException {
meta = new TransMeta();
trans = new Trans( meta );
trans.setLog( Mockito.mock( LogChannelInterface.class ) );
trans.prepareExecution( null );
trans.startThreads();
}
/**
* PDI-14948 - Execution of trans with no steps never ends
*/
@Test( timeout = 1000 )
public void transWithNoStepsIsNotEndless() throws Exception {
Trans transWithNoSteps = new Trans( new TransMeta() );
transWithNoSteps = spy( transWithNoSteps );
transWithNoSteps.prepareExecution( new String[] {} );
transWithNoSteps.startThreads();
// check trans lifecycle is not corrupted
verify( transWithNoSteps ).fireTransStartedListeners();
verify( transWithNoSteps ).fireTransFinishedListeners();
}
@Test
public void testFindDatabaseWithEncodedConnectionName() {
DatabaseMeta dbMeta1 =
new DatabaseMeta( "encoded_DBConnection", "Oracle", "localhost", "access", "test", "111", "test", "test" );
dbMeta1.setDisplayName( "encoded.DBConnection" );
meta.addDatabase( dbMeta1 );
DatabaseMeta dbMeta2 =
new DatabaseMeta( "normalDBConnection", "Oracle", "localhost", "access", "test", "111", "test", "test" );
dbMeta2.setDisplayName( "normalDBConnection" );
meta.addDatabase( dbMeta2 );
DatabaseMeta databaseMeta = meta.findDatabase( dbMeta1.getDisplayName() );
assertNotNull( databaseMeta );
assertEquals( databaseMeta.getName(), "encoded_DBConnection" );
assertEquals( databaseMeta.getDisplayName(), "encoded.DBConnection" );
}
/**
* PDI-10762 - Trans and TransMeta leak
*/
@Test
public void testLoggingObjectIsNotLeakInMeta() {
String expected = meta.log.getLogChannelId();
meta.clear();
String actual = meta.log.getLogChannelId();
assertEquals( "Use same logChannel for empty constructors, or assign General level for clear() calls",
expected, actual );
}
/**
* PDI-10762 - Trans and TransMeta leak
*/
@Test
public void testLoggingObjectIsNotLeakInTrans() throws KettleException {
Repository rep = Mockito.mock( Repository.class );
RepositoryDirectoryInterface repInt = Mockito.mock( RepositoryDirectoryInterface.class );
Mockito.when(
rep.loadTransformation( Mockito.anyString(), Mockito.any( RepositoryDirectoryInterface.class ), Mockito
.any( ProgressMonitorListener.class ), Mockito.anyBoolean(), Mockito.anyString() ) ).thenReturn( meta );
Mockito.when( rep.findDirectory( Mockito.anyString() ) ).thenReturn( repInt );
Trans trans = new Trans( meta, rep, "junit", "junitDir", "fileName" );
assertEquals( "Log channel General assigned", LogChannel.GENERAL.getLogChannelId(), trans.log
.getLogChannelId() );
}
/**
* PDI-5229 - ConcurrentModificationException when restarting transformation Test that listeners can be accessed
* concurrently during transformation finish
*
* @throws KettleException
* @throws InterruptedException
*/
@Test
public void testTransFinishListenersConcurrentModification() throws KettleException, InterruptedException {
CountDownLatch start = new CountDownLatch( 1 );
TransFinishListenerAdder add = new TransFinishListenerAdder( trans, start );
TransFinishListenerFirer firer = new TransFinishListenerFirer( trans, start );
startThreads( add, firer, start );
assertEquals( "All listeners are added: no ConcurrentModificationException", count, add.c );
assertEquals( "All Finish listeners are iterated over: no ConcurrentModificationException", count, firer.c );
}
/**
* Test that listeners can be accessed concurrently during transformation start
*
* @throws InterruptedException
*/
@Test
public void testTransStartListenersConcurrentModification() throws InterruptedException {
CountDownLatch start = new CountDownLatch( 1 );
TransFinishListenerAdder add = new TransFinishListenerAdder( trans, start );
TransStartListenerFirer starter = new TransStartListenerFirer( trans, start );
startThreads( add, starter, start );
assertEquals( "All listeners are added: no ConcurrentModificationException", count, add.c );
assertEquals( "All Start listeners are iterated over: no ConcurrentModificationException", count, starter.c );
}
/**
* Test that transformation stop listeners can be accessed concurrently
*
* @throws InterruptedException
*/
@Test
public void testTransStoppedListenersConcurrentModification() throws InterruptedException {
CountDownLatch start = new CountDownLatch( 1 );
TransStoppedCaller stopper = new TransStoppedCaller( trans, start );
TransStopListenerAdder adder = new TransStopListenerAdder( trans, start );
startThreads( stopper, adder, start );
assertEquals( "All transformation stop listeners is added", count, adder.c );
assertEquals( "All stop call success", count, stopper.c );
}
@Test
public void testPDI12424ParametersFromMetaAreCopiedToTrans() throws KettleException, URISyntaxException, IOException {
String testParam = "testParam";
String testParamValue = "testParamValue";
TransMeta mockTransMeta = mock( TransMeta.class );
when( mockTransMeta.listVariables() ).thenReturn( new String[] {} );
when( mockTransMeta.listParameters() ).thenReturn( new String[] { testParam } );
when( mockTransMeta.getParameterValue( testParam ) ).thenReturn( testParamValue );
FileObject ktr = KettleVFS.createTempFile( "parameters", ".ktr", "ram://" );
OutputStream outputStream = ktr.getContent().getOutputStream( true );
try {
InputStream inputStream = new ByteArrayInputStream( "<transformation></transformation>".getBytes() );
IOUtils.copy( inputStream, outputStream );
} finally {
outputStream.close();
}
Trans trans = new Trans( mockTransMeta, null, null, null, ktr.getURL().toURI().toString() );
assertEquals( testParamValue, trans.getParameterValue( testParam ) );
}
@Test
public void testRecordsCleanUpMethodIsCalled() throws Exception {
Database mockedDataBase = mock( Database.class );
Trans trans = mock( Trans.class );
StepLogTable stepLogTable = StepLogTable.getDefault( mock( VariableSpace.class ), mock( HasDatabasesInterface.class ) );
stepLogTable.setConnectionName( "connection" );
TransMeta transMeta = new TransMeta( );
transMeta.setStepLogTable( stepLogTable );
when( trans.getTransMeta() ).thenReturn( transMeta );
when( trans.createDataBase( any( DatabaseMeta.class ) ) ).thenReturn( mockedDataBase );
when( trans.getSteps() ).thenReturn( new ArrayList<StepMetaDataCombi>() );
doCallRealMethod().when( trans ).writeStepLogInformation();
trans.writeStepLogInformation();
verify( mockedDataBase ).cleanupLogRecords( stepLogTable );
}
@Test
public void testFireTransFinishedListeners() throws Exception {
Trans trans = new Trans();
TransListener mockListener = mock( TransListener.class );
trans.setTransListeners( Collections.singletonList( mockListener ) );
trans.fireTransFinishedListeners();
verify( mockListener ).transFinished( trans );
}
@Test( expected = KettleException.class )
public void testFireTransFinishedListenersExceprionOnTransFinished() throws Exception {
Trans trans = new Trans();
TransListener mockListener = mock( TransListener.class );
doThrow( KettleException.class ).when( mockListener ).transFinished( trans );
trans.setTransListeners( Collections.singletonList( mockListener ) );
trans.fireTransFinishedListeners();
}
@Test
public void testFinishStatus() throws Exception {
while ( trans.isRunning() ) {
Thread.sleep( 1 );
}
assertEquals( Trans.STRING_FINISHED, trans.getStatus() );
}
private void startThreads( Runnable one, Runnable two, CountDownLatch start ) throws InterruptedException {
Thread th = new Thread( one );
Thread tt = new Thread( two );
th.start();
tt.start();
start.countDown();
th.join();
tt.join();
}
private abstract class TransKicker implements Runnable {
protected Trans tr;
protected int c = 0;
protected CountDownLatch start;
protected int max = count;
TransKicker( Trans tr, CountDownLatch start ) {
this.tr = tr;
this.start = start;
}
protected boolean isStopped() {
c++;
return c >= max;
}
}
private class TransStoppedCaller extends TransKicker {
TransStoppedCaller( Trans tr, CountDownLatch start ) {
super( tr, start );
}
@Override
public void run() {
try {
start.await();
} catch ( InterruptedException e ) {
throw new RuntimeException();
}
while ( !isStopped() ) {
trans.stopAll();
}
}
}
private class TransStopListenerAdder extends TransKicker {
TransStopListenerAdder( Trans tr, CountDownLatch start ) {
super( tr, start );
}
@Override
public void run() {
try {
start.await();
} catch ( InterruptedException e ) {
throw new RuntimeException();
}
while ( !isStopped() ) {
trans.addTransStoppedListener( transStoppedListener );
}
}
}
private class TransFinishListenerAdder extends TransKicker {
TransFinishListenerAdder( Trans tr, CountDownLatch start ) {
super( tr, start );
}
@Override
public void run() {
try {
start.await();
} catch ( InterruptedException e ) {
throw new RuntimeException();
}
// run
while ( !isStopped() ) {
tr.addTransListener( listener );
}
}
}
private class TransFinishListenerFirer extends TransKicker {
TransFinishListenerFirer( Trans tr, CountDownLatch start ) {
super( tr, start );
}
@Override
public void run() {
try {
start.await();
} catch ( InterruptedException e ) {
throw new RuntimeException();
}
// run
while ( !isStopped() ) {
try {
tr.fireTransFinishedListeners();
// clean array blocking queue
tr.waitUntilFinished();
} catch ( KettleException e ) {
throw new RuntimeException();
}
}
}
}
private class TransStartListenerFirer extends TransKicker {
TransStartListenerFirer( Trans tr, CountDownLatch start ) {
super( tr, start );
}
@Override
public void run() {
try {
start.await();
} catch ( InterruptedException e ) {
throw new RuntimeException();
}
// run
while ( !isStopped() ) {
try {
tr.fireTransStartedListeners();
} catch ( KettleException e ) {
throw new RuntimeException();
}
}
}
}
private final TransListener listener = new TransListener() {
@Override
public void transStarted( Trans trans ) throws KettleException {
}
@Override
public void transActive( Trans trans ) {
}
@Override
public void transFinished( Trans trans ) throws KettleException {
}
};
private final TransStoppedListener transStoppedListener = new TransStoppedListener() {
@Override
public void transStopped( Trans trans ) {
}
};
}