    001    /*
    002     * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved.
    003     *
    004     * Project and contact information: http://www.cascading.org/
    005     *
    006     * This file is part of the Cascading project.
    007     *
    008     * Licensed under the Apache License, Version 2.0 (the "License");
    009     * you may not use this file except in compliance with the License.
    010     * You may obtain a copy of the License at
    011     *
    012     *     http://www.apache.org/licenses/LICENSE-2.0
    013     *
    014     * Unless required by applicable law or agreed to in writing, software
    015     * distributed under the License is distributed on an "AS IS" BASIS,
    016     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    017     * See the License for the specific language governing permissions and
    018     * limitations under the License.
    019     */
    020
    021    package cascading.platform;
    022
    023    import java.io.IOException;
    024    import java.io.InputStream;
    025    import java.lang.annotation.Retention;
    026    import java.lang.annotation.RetentionPolicy;
    027    import java.lang.reflect.Method;
    028    import java.net.URL;
    029    import java.util.ArrayList;
    030    import java.util.Arrays;
    031    import java.util.Collections;
    032    import java.util.Enumeration;
    033    import java.util.HashSet;
    034    import java.util.LinkedHashSet;
    035    import java.util.List;
    036    import java.util.Properties;
    037    import java.util.Set;
    038
    039    import cascading.PlatformTestCase;
    040    import junit.framework.Test;
    041    import org.junit.internal.runners.JUnit38ClassRunner;
    042    import org.junit.runner.Description;
    043    import org.junit.runner.Runner;
    044    import org.junit.runner.notification.RunNotifier;
    045    import org.junit.runners.BlockJUnit4ClassRunner;
    046    import org.junit.runners.ParentRunner;
    047    import org.junit.runners.model.InitializationError;
    048    import org.slf4j.Logger;
    049    import org.slf4j.LoggerFactory;
    050
    051    /**
    052     * Class ParentRunner is a JUnit {@link Runner} sub-class for injecting different platform and planners
    053     * into the *PlatformTest classes.
    054     * <p/>
    055     * It works by loading the {@code platform.classname} property from the {@code cascading/platform/platform.properties}
    056     * resource. Every new platform should provide this resource.
    057     * <p/>
    058     * To test against a specific platform, simply make sure the above resource for the platform in question is in the
    059     * test CLASSPATH. The simplest way is to add it as a dependency.
    060     */
    061    public class PlatformRunner extends ParentRunner<Runner>
    062      {
    063      public static final String PLATFORM_INCLUDES = "test.platform.includes";
    064      public static final String PLATFORM_RESOURCE = "cascading/platform/platform.properties";
    065      public static final String PLATFORM_CLASSNAME = "platform.classname";
    066
    067      private static final Logger LOG = LoggerFactory.getLogger( PlatformRunner.class );
    068
    069      private Set<String> includes = new HashSet<String>();
    070      private List<Runner> runners;
    071
    072      @Retention(RetentionPolicy.RUNTIME)
    073      public @interface Platform
    074        {
    075        Class<? extends TestPlatform>[] value();
    076        }
    077
    078      public PlatformRunner( Class<PlatformTestCase> testClass ) throws Throwable
    079        {
    080        super( testClass );
    081
    082        setIncludes();
    083        makeRunners();
    084        }
    085
    086      private void setIncludes()
    087        {
    088        String includesString = System.getProperty( PLATFORM_INCLUDES );
    089
    090        if( includesString == null || includesString.isEmpty() )
    091          {
    092          return;
    093          }
    094
    095        String[] split = includesString.split( "," );
    096
    097        for( String include : split )
    098          {
    099          includes.add( include.trim().toLowerCase() );
    100          }
    101        }
    102
    103      public static TestPlatform makeInstance( Class<? extends TestPlatform> type )
    104        {
    105        try
    106          {
    107          return type.newInstance();
    108          }
    109        catch( NoClassDefFoundError exception )
    110          {
    111          return null;
    112          }
    113        catch( InstantiationException exception )
    114          {
    115          throw new RuntimeException( exception );
    116          }
    117        catch( IllegalAccessException exception )
    118          {
    119          throw new RuntimeException( exception );
    120          }
    121        }
    122
    123      @Override
    124      protected List<Runner> getChildren()
    125        {
    126        return runners;
    127        }
    128
    129      private List<Runner> makeRunners() throws Throwable
    130        {
    131        Class<?> javaClass = getTestClass().getJavaClass();
    132
    133        runners = new ArrayList<Runner>();
    134
    135        // test for use of annotation
    136        Set<Class<? extends TestPlatform>> classes = getPlatformClassesFromAnnotation( javaClass );
    137
    138        // if no platforms declared from the annotation, test classpath
    139        if( classes.isEmpty() )
    140          classes = getPlatformClassesFromClasspath( javaClass.getClassLoader() );
    141
    142        for( Class<? extends TestPlatform> platformClass : classes )
    143          {
    144          addPlatform( javaClass, platformClass );
    145          }
    146
    147        return runners;
    148        }
    149
    150      private Set<Class<? extends TestPlatform>> getPlatformClassesFromAnnotation( Class<?> javaClass ) throws Throwable
    151        {
    152        PlatformRunner.Platform annotation = javaClass.getAnnotation( PlatformRunner.Platform.class );
    153
    154        if( annotation == null )
    155          return Collections.EMPTY_SET;
    156
    157        HashSet<Class<? extends TestPlatform>> classes = new LinkedHashSet<Class<? extends TestPlatform>>( Arrays.asList( annotation.value() ) );
    158
    159        LOG.info( "found {} test platforms from Platform annotation", classes.size() );
    160
    161        return classes;
    162        }
    163
    164      protected static Set<Class<? extends TestPlatform>> getPlatformClassesFromClasspath( ClassLoader classLoader ) throws IOException, ClassNotFoundException
    165        {
    166        Set<Class<? extends TestPlatform>> classes = new LinkedHashSet<Class<? extends TestPlatform>>();
    167        Properties properties = new Properties();
    168
    169        LOG.debug( "classloader: {}", classLoader );
    170
    171        Enumeration<URL> urls = classLoader.getResources( PLATFORM_RESOURCE );
    172
    173        while( urls.hasMoreElements() )
    174          {
    175          InputStream stream = urls.nextElement().openStream();
    176          classes.add( (Class<? extends TestPlatform>) getPlatformClass( classLoader, properties, stream ) );
    177          }
    178
    179        if( classes.isEmpty() )
    180          {
    181          LOG.warn( "no platform tests will be run" );
    182          LOG.warn( "did not find {} in the classpath, no {} instances found", PLATFORM_RESOURCE, TestPlatform.class.getCanonicalName() );
    183          LOG.warn( "add cascading-local, cascading-hadoop, and/or external planner library to the test classpath" );
    184          }
    185        else
    186          {
    187          LOG.info( "found {} test platforms from classpath", classes.size() );
    188          }
    189
    190        return classes;
    191        }
    192
    193      private static Class<?> getPlatformClass( ClassLoader classLoader, Properties properties, InputStream stream ) throws IOException, ClassNotFoundException
    194        {
    195        if( stream == null )
    196          {
    197          throw new IllegalStateException( "platform provider resource not found: " + PLATFORM_RESOURCE );
    198          }
    199
    200        properties.load( stream );
    201
    202        String classname = properties.getProperty( PLATFORM_CLASSNAME );
    203
    204        if( classname == null )
    205          {
    206          throw new IllegalStateException( "platform provider value not found: " + PLATFORM_CLASSNAME );
    207          }
    208
    209        Class<?> type = classLoader.loadClass( classname );
    210
    211        if( type == null )
    212          {
    213          throw new IllegalStateException( "platform provider class not found: " + classname );
    214          }
    215
    216        return type;
    217        }
    218
    219      private void addPlatform( final Class<?> javaClass, Class<? extends TestPlatform> type ) throws Throwable
    220        {
    221        final TestPlatform testPlatform = makeInstance( type );
    222
    223        // test platform dependencies not installed, so skip
    224        if( testPlatform == null )
    225          {
    226          return;
    227          }
    228
    229        final String platformName = testPlatform.getName();
    230
    231        if( !includes.isEmpty() && !includes.contains( platformName.toLowerCase() ) )
    232          {
    233          LOG.info( "ignoring platform: {}", platformName );
    234          return;
    235          }
    236
    237        LOG.info( "installing platform: {}", platformName );
    238        LOG.info( "running test: {}", javaClass.getName() );
    239
    240        PlatformSuite suiteAnnotation = javaClass.getAnnotation( PlatformSuite.class );
    241        if( suiteAnnotation != null )
    242          {
    243          runners.add( makeSuiteRunner( javaClass, suiteAnnotation.method(), testPlatform ) );
    244          }
    245        else
    246          {
    247          runners.add( makeClassRunner( javaClass, testPlatform, platformName ) );
    248          }
    249        }
    250
    251      private JUnit38ClassRunner makeSuiteRunner( Class<?> javaClass, String suiteMethod, final TestPlatform testPlatform ) throws Throwable
    252        {
    253        Method method = javaClass.getMethod( suiteMethod, TestPlatform.class );
    254
    255        return new JUnit38ClassRunner( (Test) method.invoke( null, testPlatform ) );
    256        }
    257
    258      private BlockJUnit4ClassRunner makeClassRunner( final Class<?> javaClass, final TestPlatform testPlatform, final String platformName ) throws InitializationError
    259        {
    260        return new BlockJUnit4ClassRunner( javaClass )
    261        {
    262        @Override
    263        protected String getName()
    264          {
    265          return String.format( "%s[%s]", super.getName(), platformName );
    266          }
    267
    268    //        @Override
    269    //        protected String testName( FrameworkMethod method )
    270    //          {
    271    //          return String.format( "%s[%s]", super.testName( method ), platformName );
    272    //          }
    273
    274        @Override
    275        protected Object createTest() throws Exception
    276          {
    277          PlatformTestCase testCase = (PlatformTestCase) super.createTest();
    278
    279          testCase.installPlatform( testPlatform );
    280
    281          return testCase;
    282          }
    283        };
    284        }
    285
    286      @Override
    287      protected Description describeChild( Runner runner )
    288        {
    289        return runner.getDescription();
    290        }
    291
    292      @Override
    293      protected void runChild( Runner runner, RunNotifier runNotifier )
    294        {
    295        runner.run( runNotifier );
    296        }
    297      }