/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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.kie.workbench.common.services.backend.builder.core;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.guvnor.common.services.builder.IncrementalBuilderExecutorManagerFactory;
import org.guvnor.common.services.project.builder.model.BuildResults;
import org.guvnor.common.services.project.builder.service.BuildService;
import org.guvnor.structure.server.config.ConfigGroup;
import org.guvnor.structure.server.config.ConfigType;
import org.guvnor.structure.server.config.ConfigurationFactory;
import org.guvnor.structure.server.config.ConfigurationService;
import org.guvnor.test.WeldJUnitRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kie.workbench.common.services.shared.project.KieProject;
import org.kie.workbench.common.services.shared.project.KieProjectService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.backend.server.util.Paths;
import org.uberfire.backend.vfs.Path;
import org.uberfire.java.nio.fs.file.SimpleFileSystemProvider;
import static org.junit.Assert.*;
@RunWith( WeldJUnitRunner.class )
public class ResourceChangeIncrementalBuilderConcurrencyTest {
private static final Logger logger = LoggerFactory.getLogger( ResourceChangeIncrementalBuilderConcurrencyTest.class );
private static final String GLOBAL_SETTINGS = "settings";
private static final int THREADS = 200;
private final SimpleFileSystemProvider fs = new SimpleFileSystemProvider( );
@Inject
private Paths paths;
@Inject
private ConfigurationService configurationService;
@Inject
private ConfigurationFactory configurationFactory;
@Inject
private BuildService buildService;
@Inject
private KieProjectService projectService;
@Inject
private BeanManager beanManager;
@Inject
private IncrementalBuilderExecutorManagerFactory executorManagerFactory;
@Inject
private org.guvnor.common.services.builder.ResourceChangeIncrementalBuilder buildChangeListener;
private Path pomPath;
private Path resourcePath;
private ThreadPoolExecutor executor;
@Before
public void setUp( ) throws Exception {
//Define mandatory properties
List< ConfigGroup > globalConfigGroups = configurationService.getConfiguration( ConfigType.GLOBAL );
boolean globalSettingsDefined = false;
for ( ConfigGroup globalConfigGroup : globalConfigGroups ) {
if ( GLOBAL_SETTINGS.equals( globalConfigGroup.getName( ) ) ) {
globalSettingsDefined = true;
break;
}
}
if ( !globalSettingsDefined ) {
configurationService.addConfiguration( getGlobalConfiguration( ) );
}
final URL pomUrl = this.getClass( ).getResource( "/BuildChangeListenerRepo/pom.xml" );
final org.uberfire.java.nio.file.Path nioPomPath = fs.getPath( pomUrl.toURI( ) );
pomPath = paths.convert( nioPomPath );
final URL resourceUrl = this.getClass( ).getResource( "/BuildChangeListenerRepo/src/main/resources/update.drl" );
final org.uberfire.java.nio.file.Path nioResourcePath = fs.getPath( resourceUrl.toURI( ) );
resourcePath = paths.convert( nioResourcePath );
executor = ( ThreadPoolExecutor ) Executors.newCachedThreadPool( );
executorManagerFactory.getExecutorManager( ).setExecutorService( executor );
}
private ConfigGroup getGlobalConfiguration( ) {
//Global Configurations used by many of Drools Workbench editors
final ConfigGroup group = configurationFactory.newConfigGroup( ConfigType.GLOBAL,
GLOBAL_SETTINGS,
"" );
group.addConfigItem( configurationFactory.newConfigItem( "build.enable-incremental",
"true" ) );
return group;
}
@Test
public void testConcurrentResourceUpdates( ) throws URISyntaxException {
//Force full build before attempting incremental changes
ensureProjectBuild( );
//for every thread launched, internally a new task will be launched in the IncrementalBuilderExecutorManager
final int totalTasks = THREADS * 2;
final Result result = new Result( );
for ( int i = 0; i < THREADS; i++ ) {
//launch an incremental build for the resource
executor.execute( newIncrementalBuildTask( resourcePath, result ) );
}
awaitTasksCompletion( executor, totalTasks );
if ( result.isFailed( ) ) {
fail( result.getMessage( ) );
}
}
@Test
public void testConcurrentResourceUpdatesWithProjectChanges( ) throws URISyntaxException {
//Force full build before attempting incremental changes
ensureProjectBuild( );
//for every thread launched, internally a new task will be launched in the IncrementalBuilderExecutorManager
final int totalTasks = THREADS * 2;
final Result result = new Result( );
for ( int i = 0; i < THREADS; i++ ) {
final Path p = ( i % 5 == 0 ) ? pomPath : resourcePath;
//launch an incremental build for the resource or the project.
executor.execute( newIncrementalBuildTask( p, result ) );
}
awaitTasksCompletion( executor, totalTasks );
if ( result.isFailed( ) ) {
fail( result.getMessage( ) );
}
}
private void ensureProjectBuild( ) {
final KieProject project = projectService.resolveProject( resourcePath );
final BuildResults buildResults = buildService.build( project );
assertNotNull( buildResults );
assertEquals( 0,
buildResults.getErrorMessages( ).size( ) );
assertEquals( 1,
buildResults.getInformationMessages( ).size( ) );
}
private Runnable newIncrementalBuildTask( Path resource, Result result ) {
return ( ) -> {
try {
logger.debug( "Thread " + Thread.currentThread( ).getName( ) + " has started for " + resource.toURI( ) );
buildChangeListener.updateResource( resource );
logger.debug( "Thread " + Thread.currentThread( ).getName( ) + " has completed " + resource.toURI( ) );
} catch ( Throwable e ) {
result.setFailed( true );
result.setMessage( e.getMessage( ) );
logger.debug( e.getMessage( ) );
}
};
}
private void awaitTasksCompletion( ThreadPoolExecutor executor, int totalTasks ) {
boolean allTasksLaunched = false;
int MAX_TIME = 5;
int totalSleep = 0;
while ( !allTasksLaunched ) {
try {
if ( executor.getTaskCount( ) < totalTasks ) {
//let's wait until all tasks has been launched.
TimeUnit.SECONDS.sleep( 5 );
totalSleep += 5;
if ( TimeUnit.SECONDS.toMinutes( totalSleep ) > MAX_TIME ) {
//something has gone wrong with the test
fail( "Test expected time of " + MAX_TIME + " minutes has elapsed." );
}
} else {
//let's start the shutdown.
allTasksLaunched = true;
executor.shutdown( );
executor.awaitTermination( MAX_TIME, TimeUnit.MINUTES );
}
} catch ( InterruptedException e ) {
allTasksLaunched = true;
fail( e.getMessage( ) );
}
}
}
private static class Result {
private boolean failed = false;
private String message = "";
public synchronized boolean isFailed( ) {
return failed;
}
public synchronized void setFailed( boolean failed ) {
this.failed = failed;
}
public synchronized String getMessage( ) {
return message;
}
public synchronized void setMessage( String message ) {
this.message = message;
}
}
}