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 }