1/*
2 * Copyright (C) 2013 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package com.google.caliper.runner;
16
17import static com.google.common.base.Preconditions.checkState;
18
19import com.google.caliper.config.InvalidConfigurationException;
20import com.google.caliper.model.Trial;
21import com.google.caliper.util.InvalidCommandException;
22import com.google.common.collect.ImmutableList;
23import com.google.common.collect.Lists;
24import com.google.common.io.Files;
25
26import org.junit.rules.TestWatcher;
27import org.junit.runner.Description;
28
29import java.io.File;
30import java.io.PrintWriter;
31import java.io.StringWriter;
32import java.util.Arrays;
33import java.util.List;
34
35/**
36 * A {@link TestWatcher} that can be used to configure a run of caliper.
37 *
38 * <p>Provides common test configuration utilities and redirects output to a buffer and only dumps
39 * it during a failure.
40 *
41 * <p>TODO(lukes,gak): This is a bad name since it isn't just watching the tests, it is helping you
42 * run the tests.
43 */
44public final class CaliperTestWatcher extends TestWatcher {
45  // N.B. StringWriter is internally synchronized and is safe to write to from multiple threads.
46  private StringWriter stdout;
47  private final StringWriter stderr = new StringWriter();
48  private File workerOutput;
49
50  private String instrument;
51  private Class<?> benchmarkClass;
52  private List<String> extraOptions = Lists.newArrayList();
53
54  CaliperTestWatcher forBenchmark(Class<?> benchmarkClass) {
55    this.benchmarkClass = benchmarkClass;
56    return this;
57  }
58
59  CaliperTestWatcher instrument(String instrument) {
60    this.instrument = instrument;
61    return this;
62  }
63
64  CaliperTestWatcher options(String... extraOptions) {
65    this.extraOptions = Arrays.asList(extraOptions);
66    return this;
67  }
68
69  void run() throws InvalidCommandException, InvalidBenchmarkException,
70      InvalidConfigurationException {
71    checkState(benchmarkClass != null, "You must configure a benchmark!");
72    workerOutput = Files.createTempDir();
73    // configure a custom dir so the files aren't deleted when CaliperMain returns
74    List<String> options = Lists.newArrayList(
75        "-Cworker.output=" + workerOutput.getPath(),
76        "-Cresults.file.class=",
77        "-Cresults.upload.class=" + InMemoryResultsUploader.class.getName());
78    if (instrument != null) {
79      options.add("-i");
80      options.add(instrument);
81    }
82    options.addAll(extraOptions);
83    options.add(benchmarkClass.getName());
84    this.stdout = new StringWriter();
85    CaliperMain.exitlessMain(
86        options.toArray(new String[0]),
87        new PrintWriter(stdout,  true),
88        new PrintWriter(stderr,  true));
89  }
90
91  @Override protected void finished(Description description) {
92    if (workerOutput != null) {
93      for (File f : workerOutput.listFiles()) {
94        f.delete();
95      }
96      workerOutput.delete();
97    }
98  }
99
100  @Override protected void failed(Throwable e, Description description) {
101    // don't log if run was never called.
102    if (stdout != null) {
103      System.err.println("Caliper failed with the following output (stdout):\n"
104          + stdout.toString() + "stderr:\n" + stderr.toString());
105    }
106  }
107
108  ImmutableList<Trial> trials() {
109    return InMemoryResultsUploader.trials();
110  }
111
112  public StringWriter getStderr() {
113    return stderr;
114  }
115
116  public StringWriter getStdout() {
117    return stdout;
118  }
119
120  File workerOutputDirectory() {
121    return workerOutput;
122  }
123}
124