/* * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.caliper.runner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.caliper.config.InvalidConfigurationException; import com.google.caliper.util.InvalidCommandException; import junit.framework.AssertionFailedError; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.PrintWriter; import java.io.StringWriter; /** * Unit test covering common user mistakes in benchmark classes. */ @RunWith(JUnit4.class) public class MalformedBenchmarksTest { // Put the expected messages together here, which may promote some kind of // consistency in their wording. :) private static final String ABSTRACT = "Class '%s' is abstract"; private static final String NO_CONSTRUCTOR = "Benchmark class %s does not have a publicly visible default constructor"; private static final String NO_METHODS = "There were no experiments to be performed for the class %s using the instruments " + "[allocation, runtime]"; private static final String STATIC_BENCHMARK = "Benchmark methods must not be static: timeIt"; private static final String WRONG_ARGUMENTS = "Benchmark methods must have no arguments or accept a single int or long parameter: timeIt"; private static final String STATIC_PARAM = "Parameter field 'oops' must not be static"; private static final String RESERVED_PARAM = "Class '%s' uses reserved parameter name 'vm'"; private static final String NO_CONVERSION = "Type 'Object' of parameter field 'oops' " + "has no recognized String-converting method; see for details"; private static final String CONVERT_FAILED = // granted this one's a little weird (and brittle) "Cannot convert value 'oops' to type 'int': For input string: \"oops\""; @Test public void abstractBenchmark() throws Exception { expectException(ABSTRACT, AbstractBenchmark.class); } abstract static class AbstractBenchmark {} @Test public void noSuitableConstructor() throws Exception { expectException(String.format(NO_CONSTRUCTOR, BadConstructorBenchmark.class.getName()), BadConstructorBenchmark.class); } @SuppressWarnings("unused") static class BadConstructorBenchmark { BadConstructorBenchmark(String damnParam) {} @Benchmark void timeIt(int reps) {} } @Test public void noBenchmarkMethods() throws Exception { expectException(NO_METHODS, NoMethodsBenchmark.class); } @SuppressWarnings("unused") static class NoMethodsBenchmark { void timeIt(int reps) {} // not annotated } @Test public void staticBenchmarkMethod() throws Exception { expectException(STATIC_BENCHMARK, StaticBenchmarkMethodBenchmark.class); } @SuppressWarnings("unused") static class StaticBenchmarkMethodBenchmark { @Benchmark public static void timeIt(int reps) {} } @Test public void wrongSignature() throws Exception { expectException(WRONG_ARGUMENTS, BoxedParamBenchmark.class); expectException(WRONG_ARGUMENTS, ExtraParamBenchmark.class); } @SuppressWarnings("unused") static class BoxedParamBenchmark { @Benchmark void timeIt(Integer reps) {} } @SuppressWarnings("unused") static class ExtraParamBenchmark { @Benchmark void timeIt(int reps, int what) {} } @Test public void hasBenchmarkOverloads() throws Exception { // N.B. baz is fine since although it has an overload, its overload is not a benchmark method. expectException( "Overloads are disallowed for benchmark methods, found overloads of [bar, foo] in " + "benchmark OverloadsAnnotatedBenchmark", OverloadsAnnotatedBenchmark.class); } @SuppressWarnings("unused") static class OverloadsAnnotatedBenchmark { @Benchmark public void foo(long reps) {} @Benchmark public void foo(int reps) {} @Benchmark public void bar(long reps) {} @Benchmark public void bar(int reps) {} @Benchmark public void baz(int reps) {} public void baz(long reps, boolean thing) {} public void baz(long reps) {} } @Test public void staticParam() throws Exception { expectException(STATIC_PARAM, StaticParamBenchmark.class); } static class StaticParamBenchmark { @Param static String oops; } @Test public void reservedParameterName() throws Exception { expectException(RESERVED_PARAM, ReservedParamBenchmark.class); } static class ReservedParamBenchmark { @Param String vm; } @Test public void unparsableParamType() throws Exception { expectException(NO_CONVERSION, UnparsableParamTypeBenchmark.class); } static class UnparsableParamTypeBenchmark { @Param Object oops; } @Test public void unparsableParamDefault() throws Exception { expectException(CONVERT_FAILED, UnparsableParamDefaultBenchmark.class); } static class UnparsableParamDefaultBenchmark { @Param({"1", "2", "oops"}) int number; } // end of tests private void expectException(String expectedMessageFmt, Class benchmarkClass) throws InvalidCommandException, InvalidConfigurationException { try { CaliperMain.exitlessMain( new String[] {"--instrument=allocation,runtime", "--dry-run", benchmarkClass.getName()}, new PrintWriter(new StringWriter()), new PrintWriter(new StringWriter())); fail("no exception thrown"); } catch (InvalidBenchmarkException e) { try { String expectedMessageText = String.format(expectedMessageFmt, benchmarkClass.getSimpleName()); assertEquals(expectedMessageText, e.getMessage()); // don't swallow our real stack trace } catch (AssertionFailedError afe) { afe.initCause(e); throw afe; } } } }