1 /* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.caliper.runner; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.base.Throwables.propagateIfInstanceOf; 21 22 import com.google.caliper.AfterExperiment; 23 import com.google.caliper.BeforeExperiment; 24 import com.google.caliper.Param; 25 import com.google.caliper.api.SkipThisScenarioException; 26 import com.google.caliper.api.VmOptions; 27 import com.google.caliper.util.InvalidCommandException; 28 import com.google.caliper.util.Reflection; 29 import com.google.common.annotations.VisibleForTesting; 30 import com.google.common.base.Objects; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.collect.ImmutableSetMultimap; 33 34 import java.lang.reflect.InvocationTargetException; 35 import java.lang.reflect.Method; 36 import java.lang.reflect.Modifier; 37 import java.util.logging.Level; 38 import java.util.logging.Logger; 39 40 /** 41 * An instance of this type represents a user-provided class. It manages creating, setting up and 42 * destroying instances of that class. 43 */ 44 final class BenchmarkClass { 45 private static final Logger logger = Logger.getLogger(BenchmarkClass.class.getName()); 46 forClass(Class<?> theClass)47 static BenchmarkClass forClass(Class<?> theClass) throws InvalidBenchmarkException { 48 return new BenchmarkClass(theClass); 49 } 50 51 final Class<?> theClass; 52 private final ParameterSet userParameters; 53 private final ImmutableSet<String> benchmarkFlags; 54 BenchmarkClass(Class<?> theClass)55 private BenchmarkClass(Class<?> theClass) throws InvalidBenchmarkException { 56 this.theClass = checkNotNull(theClass); 57 58 if (!theClass.getSuperclass().equals(Object.class)) { 59 throw new InvalidBenchmarkException( 60 "%s must not extend any class other than %s. Prefer composition.", 61 theClass, Object.class); 62 } 63 64 if (Modifier.isAbstract(theClass.getModifiers())) { 65 throw new InvalidBenchmarkException("Class '%s' is abstract", theClass); 66 } 67 68 // TODO: check for nested, non-static classes (non-abstract, but no constructor?) 69 // this will fail later anyway (no way to declare parameterless nested constr., but 70 // maybe signal this better? 71 72 this.userParameters = ParameterSet.create(theClass, Param.class); 73 74 this.benchmarkFlags = getVmOptions(theClass); 75 } 76 beforeExperimentMethods()77 ImmutableSet<Method> beforeExperimentMethods() { 78 return Reflection.getAnnotatedMethods(theClass, BeforeExperiment.class); 79 } 80 afterExperimentMethods()81 ImmutableSet<Method> afterExperimentMethods() { 82 return Reflection.getAnnotatedMethods(theClass, AfterExperiment.class); 83 } 84 userParameters()85 public ParameterSet userParameters() { 86 return userParameters; 87 } 88 vmOptions()89 public ImmutableSet<String> vmOptions() { 90 return benchmarkFlags; 91 } 92 93 // TODO(gak): use these methods in the worker as well setUpBenchmark(Object benchmarkInstance)94 public void setUpBenchmark(Object benchmarkInstance) throws UserCodeException { 95 boolean setupSuccess = false; 96 try { 97 callSetUp(benchmarkInstance); 98 setupSuccess = true; 99 } finally { 100 // If setUp fails, we should call tearDown. If this method throws an exception, we 101 // need to call tearDown from here, because no one else has the reference to the 102 // Benchmark. 103 if (!setupSuccess) { 104 try { 105 callTearDown(benchmarkInstance); 106 } catch (UserCodeException e) { 107 // The exception thrown during setUp shouldn't be lost, as it's probably more 108 // important to the user. 109 logger.log( 110 Level.INFO, 111 "in @AfterExperiment methods called because @BeforeExperiment methods failed", 112 e); 113 } 114 } 115 } 116 } 117 cleanup(Object benchmark)118 public void cleanup(Object benchmark) throws UserCodeException { 119 callTearDown(benchmark); 120 } 121 benchmarkClass()122 @VisibleForTesting Class<?> benchmarkClass() { 123 return theClass; 124 } 125 name()126 public String name() { 127 return theClass.getName(); 128 } 129 equals(Object obj)130 @Override public boolean equals(Object obj) { 131 if (obj == this) { 132 return true; 133 } else if (obj instanceof BenchmarkClass) { 134 BenchmarkClass that = (BenchmarkClass) obj; 135 return this.theClass.equals(that.theClass); 136 } else { 137 return false; 138 } 139 } 140 hashCode()141 @Override public int hashCode() { 142 return Objects.hashCode(theClass); 143 } 144 toString()145 @Override public String toString() { 146 return name(); 147 } 148 callSetUp(Object benchmark)149 private void callSetUp(Object benchmark) throws UserCodeException { 150 for (Method method : beforeExperimentMethods()) { 151 try { 152 method.invoke(benchmark); 153 } catch (IllegalAccessException e) { 154 throw new AssertionError(e); 155 } catch (InvocationTargetException e) { 156 propagateIfInstanceOf(e.getCause(), SkipThisScenarioException.class); 157 throw new UserCodeException( 158 "Exception thrown from a @BeforeExperiment method", e.getCause()); 159 } 160 } 161 } 162 callTearDown(Object benchmark)163 private void callTearDown(Object benchmark) throws UserCodeException { 164 for (Method method : afterExperimentMethods()) { 165 try { 166 method.invoke(benchmark); 167 } catch (IllegalAccessException e) { 168 throw new AssertionError(e); 169 } catch (InvocationTargetException e) { 170 propagateIfInstanceOf(e.getCause(), SkipThisScenarioException.class); 171 throw new UserCodeException( 172 "Exception thrown from an @AfterExperiment method", e.getCause()); 173 } 174 } 175 } 176 getVmOptions(Class<?> benchmarkClass)177 private static ImmutableSet<String> getVmOptions(Class<?> benchmarkClass) { 178 VmOptions annotation = benchmarkClass.getAnnotation(VmOptions.class); 179 return (annotation == null) 180 ? ImmutableSet.<String>of() 181 : ImmutableSet.copyOf(annotation.value()); 182 } 183 validateParameters(ImmutableSetMultimap<String, String> parameters)184 void validateParameters(ImmutableSetMultimap<String, String> parameters) 185 throws InvalidCommandException { 186 for (String paramName : parameters.keySet()) { 187 Parameter parameter = userParameters.get(paramName); 188 if (parameter == null) { 189 throw new InvalidCommandException("unrecognized parameter: " + paramName); 190 } 191 try { 192 parameter.validate(parameters.get(paramName)); 193 } catch (InvalidBenchmarkException e) { 194 // TODO(kevinb): this is weird. 195 throw new InvalidCommandException(e.getMessage()); 196 } 197 } 198 } 199 } 200