• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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