/*! ******************************************************************************
*
* 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.concurrency;
import org.junit.Assert;
import org.junit.Test;
import org.pentaho.di.core.BlockingRowSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* We have a {@link org.pentaho.di.core.BaseRowSet} with a bunch of attributes (originStepName, originStepCopy,
* destinationStepName,destinationStepCopy and others).
*
* The goal of this test is to verify that attributes from one BaseRowSet doesn't mix with attributes of
* another BaseRowSet in concurrent environment when executing
* {@link org.pentaho.di.core.BaseRowSet#setThreadNameFromToCopy(String, int, String, int)}.
*
* We have a {@link BlockingRowSet} that will be share across {@link Mutator} and {@link Getter}
* We take {@link BlockingRowSet} here because {@link org.pentaho.di.core.BaseRowSet} has a package level visibility
* and is not reachable from this package (and this package is designed collects all concurrent tests and concurrent
* framework runner)
*
* {@link Mutator} generates consistent blockingRowSet's. By consistence the following is meant:
* Each blockingRowSet has it's id (random int) and all his fields are named in pattern:
* - SOME_STRING + blockingRowSetId (in case it is a string)
* - blockingRowSetIs (in case it is a number)
*
* Then mutator mutates blockingRowSet by calling:
* {@link org.pentaho.di.core.BaseRowSet#setThreadNameFromToCopy(String, int, String, int)}.
* where all inputs have the same id.
*
* It is expected that shared blockingRowSet will always be in consistent state (all his fields will end
* with the same id).
*
* And that's exactly what {@link Getter} does:
*
* It calls toString method of shared blockingRowSet and verifies it's consistency.
*
*/
public class BaseRowSetConcurrentTest {
private static final int MUTATE_CIRCLES = 100;
private static final int NUMBER_OF_MUTATORS = 20;
private static final int NUMBER_OF_GETTERS = 20;
@Test
public void test() throws Exception {
BlockingRowSet sharedBlockingRowSet = new BlockingRowSet( 100 );
// fill data with initial values
sharedBlockingRowSet.setThreadNameFromToCopy( "1", 1, "1", 1 );
AtomicBoolean condition = new AtomicBoolean( true );
List<Mutator> mutators = generateMutators( sharedBlockingRowSet, condition );
List<Getter> getters = generateGetters( sharedBlockingRowSet, condition );
ConcurrencyTestRunner.runAndCheckNoExceptionRaised( mutators, getters, condition );
}
private class Mutator extends StopOnErrorCallable<Object> {
private static final String STRING_DEFAULT = "<def>";
private final BlockingRowSet blockingRowSet;
private final Random random;
public Mutator( BlockingRowSet blockingRowSet, AtomicBoolean condition ) {
super( condition );
this.blockingRowSet = blockingRowSet;
random = new Random();
}
@Override
Object doCall() throws Exception {
for ( int i = 0; i < MUTATE_CIRCLES; i++ ) {
final int id = generateId();
blockingRowSet.setThreadNameFromToCopy( STRING_DEFAULT + id, id, STRING_DEFAULT + id, id );
}
return null;
}
private int generateId() {
return random.nextInt();
}
}
private class Getter extends StopOnErrorCallable<Object> {
private final BlockingRowSet blockingRowSet;
private Getter( BlockingRowSet blockingRowSet, AtomicBoolean condition ) {
super( condition );
this.blockingRowSet = blockingRowSet;
}
@Override
Object doCall() throws Exception {
while ( condition.get() ) {
checkConsistency();
}
return null;
}
private void checkConsistency() {
Set<String> ids = extractIds( blockingRowSet.toString() );
// we expect that all ids (and all digits are ids here) refer to the same set,
// that means that they are equal.
Assert.assertEquals( 1, ids.size() );
}
/**
* Goal of this method is to extract all numbers (that are expected to be ids) from
* a string and populate it into a set.
*
* Example:
* input -> 123-124
* output -> set with values "123", "124" in it.
*
*/
Set<String> extractIds( String string ) {
Set<String> ids = new HashSet<>();
Pattern pattern = Pattern.compile( "\\d+" );
Matcher matcher = pattern.matcher( string );
while ( matcher.find() ) {
ids.add( matcher.group() );
}
return ids;
}
}
private List<Getter> generateGetters( BlockingRowSet blockingRowSet, AtomicBoolean condition ) {
List<Getter> getters = new ArrayList<>( NUMBER_OF_GETTERS );
for ( int i = 0; i < NUMBER_OF_GETTERS; i++ ) {
getters.add( new Getter( blockingRowSet, condition ) );
}
return getters;
}
private List<Mutator> generateMutators( BlockingRowSet blockingRowSet, AtomicBoolean condition ) {
List<Mutator> mutators = new ArrayList<>( NUMBER_OF_MUTATORS );
for ( int i = 0; i < NUMBER_OF_MUTATORS; i++ ) {
mutators.add( new Mutator( blockingRowSet, condition ) );
}
return mutators;
}
}