blob: 0e197f67394c1ee25e6c927474d8e1bc2f6d3e48 [file] [log] [blame]
Jesse Wilson109c1282009-12-08 13:45:25 -08001/*
2 * Copyright (C) 2009 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
17package com.google.caliper;
18
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Type;
21import java.util.*;
22
23/**
24 * A convenience class for implementing benchmark suites in plain code.
25 * Implementing classes must have a no-arguments constructor.
26 *
27 * <h3>Benchmarks</h3>
28 * The benchmarks of a suite are defined by inner classes within the suite.
29 * These inner classes implement the {@link Benchmark} interface. They may be
30 * static. They are not permitted to take parameters in their constructors.
31 *
32 * <h3>Parameters</h3>
33 * Implementing classes may be configured using parameters. Each parameter is a
34 * property of a benchmark, plus the default values that fulfill it. Parameters
35 * are specified by annotated fields:
36 * <pre>
37 * {@literal @}Param int length;
38 * </pre>
39 * The available values for a parameter are specified by another field with the
40 * same name plus the {@code Values} suffix. The type of this field must be an
41 * {@code Iterable} of the parameter's type.
42 * <pre>
43 * Iterable&lt;Integer&gt; lengthValues = Arrays.asList(10, 100, 1000, 10000);
44 * </pre>
45 * Alternatively, the available values may be specified with a method. The
46 * method's name follows the same naming convention and returns the same type.
47 * Such methods may not accept parameters of their own.
48 * <pre>
49 * Iterable&lt;Integer&gt; lengthValues() {
50 * return Arrays.asList(10, 100, 1000, 10000);
51 * }
52 * </pre>
53 */
54public abstract class DefaultBenchmarkSuite extends BenchmarkSuite {
55
56 private final Map<String, Parameter<?>> parameters;
57 private final Map<Class<? extends Benchmark>, BenchmarkFactory> benchmarkFactories;
58
59 protected void setUp() throws Exception {}
60
61 protected DefaultBenchmarkSuite() {
62 parameters = Parameter.forClass(getClass());
63 benchmarkFactories = createBenchmarkFactories();
64
65 if (benchmarkFactories.isEmpty()) {
66 throw new ConfigurationException(
67 "No benchmarks defined in " + getClass().getName());
68 }
69 }
70
71 protected Set<Class<? extends Benchmark>> benchmarkClasses() {
72 return benchmarkFactories.keySet();
73 }
74
75 protected Set<String> parameterNames() {
76 return parameters.keySet();
77 }
78
79 protected Set<String> parameterValues(String parameterName) {
80 try {
81 TypeConverter typeConverter = new TypeConverter();
82 Parameter<?> parameter = parameters.get(parameterName);
83 if (parameter == null) {
84 throw new IllegalArgumentException();
85 }
86 Collection<?> values = parameter.values();
87 Type type = parameter.getType();
88 Set<String> result = new LinkedHashSet<String>();
89 for (Object value : values) {
90 result.add(typeConverter.toString(value, type));
91 }
92 return result;
93 } catch (Exception e) {
94 throw new ExecutionException(e);
95 }
96 }
97
98 protected Benchmark createBenchmark(Class<? extends Benchmark> benchmarkClass,
99 Map<String, String> parameterValues) {
100 TypeConverter typeConverter = new TypeConverter();
101
102 BenchmarkFactory benchmarkFactory = benchmarkFactories.get(benchmarkClass);
103 if (benchmarkFactory == null) {
104 throw new IllegalArgumentException();
105 }
106
107 if (!parameters.keySet().equals(parameterValues.keySet())) {
108 throw new IllegalArgumentException("Invalid parameters specified. Expected "
109 + parameters.keySet() + " but was " + parameterValues.keySet());
110 }
111
112 try {
113 DefaultBenchmarkSuite copyOfSelf = getClass().newInstance();
114 Benchmark benchmark = benchmarkFactory.create(copyOfSelf);
115 for (Map.Entry<String, String> entry : parameterValues.entrySet()) {
116 Parameter parameter = parameters.get(entry.getKey());
117 Object value = typeConverter.fromString(entry.getValue(), parameter.getType());
118 parameter.set(copyOfSelf, value);
119 }
120
121 copyOfSelf.setUp();
122 return benchmark;
123
124 } catch (Exception e) {
125 throw new ExecutionException(e);
126 }
127 }
128
129 /**
130 * Returns a spec for each benchmark defined in the specified class. The
131 * returned specs have no parameter values; those must be added separately.
132 */
133 private Map<Class<? extends Benchmark>, BenchmarkFactory> createBenchmarkFactories() {
134 Map<Class<? extends Benchmark>, BenchmarkFactory> result
135 = new LinkedHashMap<Class<? extends Benchmark>, BenchmarkFactory>();
136 for (Class<?> c : getClass().getDeclaredClasses()) {
137 if (!Benchmark.class.isAssignableFrom(c) || c.isInterface()) {
138 continue;
139 }
140
141 @SuppressWarnings("unchecked") // guarded by isAssignableFrom
142 Class<? extends Benchmark> benchmarkClass = (Class<? extends Benchmark>) c;
143
144 try {
145 final Constructor<? extends Benchmark> constructor
146 = benchmarkClass.getDeclaredConstructor();
147 constructor.setAccessible(true);
148 result.put(benchmarkClass, new BenchmarkFactory() {
149 public Benchmark create(BenchmarkSuite suite) throws Exception {
150 return constructor.newInstance();
151 }
152 });
153 continue;
154 } catch (NoSuchMethodException ignored) {
155 }
156
157 try {
158 final Constructor<? extends Benchmark> constructor
159 = benchmarkClass.getDeclaredConstructor(getClass());
160 constructor.setAccessible(true);
161 result.put(benchmarkClass, new BenchmarkFactory() {
162 public Benchmark create(BenchmarkSuite suite) throws Exception {
163 return constructor.newInstance(suite);
164 }
165 });
166 continue;
167 } catch (NoSuchMethodException ignored) {
168 }
169
170 throw new ConfigurationException("No usable constructor for "
171 + benchmarkClass.getName() + "\n Benchmarks may only use no arguments constructors.");
172 }
173
174 return result;
175 }
176
177 interface BenchmarkFactory {
178 Benchmark create(BenchmarkSuite suite) throws Exception;
179 }
180}