/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.drill.exec.testing; import java.util.List; import org.apache.drill.exec.proto.CoordinationProtos.DrillbitEndpoint; import com.google.common.collect.Lists; public class Controls { private static final String EMPTY_CONTROLS = "{\"injections\" : []}"; /** * Returns a builder that can be used to add injections. * * @return a builder instance */ public static Builder newBuilder() { return new Builder(); } /** * Constructor. To disallow building controls without the builder. */ private Controls() { } /** * A builder to create a controls string, a JSON that holds a list of injections that are to be injected in code for * testing purposes. This string is passed through the * {@link org.apache.drill.exec.ExecConstants#DRILLBIT_CONTROL_INJECTIONS} session option. * <p/> * The builder class can be reused; it is safe to call build() multiple times to build multiple controls strings in * series. Each new controls string contains all the injections added to the builder before it. */ public static class Builder { private final List<String> injections = Lists.newArrayList(); public Builder() { } /** * Adds an exception injection to the controls builder with the given parameters. * * @param siteClass class where the exception should be thrown * @param desc descriptor for the exception site in the site class * @param exceptionClass class of the exception to throw * @param nSkip number of times to skip before firing * @param nFire number of times to fire the exception * @return this builder */ public Builder addException(final Class<?> siteClass, final String desc, final Class<? extends Throwable> exceptionClass, final int nSkip, final int nFire) { injections.add(ControlsInjectionUtil.createException(siteClass, desc, nSkip, nFire, exceptionClass)); return this; } /** * Adds an exception injection to the controls builder with the given parameters. The injection is not skipped, and * the exception is thrown when execution reaches the site. * * @param siteClass class where the exception should be thrown * @param desc descriptor for the exception site in the site class * @param exceptionClass class of the exception to throw * @return this builder */ public Builder addException(final Class<?> siteClass, final String desc, final Class<? extends Throwable> exceptionClass) { return addException(siteClass, desc, exceptionClass, 0, 1); } /** * Adds an exception injection (for the specified drillbit) to the controls builder with the given parameters. * * @param siteClass class where the exception should be thrown * @param desc descriptor for the exception site in the site class * @param exceptionClass class of the exception to throw * @param endpoint the endpoint of the drillbit on which to inject * @param nSkip number of times to skip before firing * @param nFire number of times to fire the exception * @return this builder */ public Builder addExceptionOnBit(final Class<?> siteClass, final String desc, final Class<? extends Throwable> exceptionClass, final DrillbitEndpoint endpoint, final int nSkip, final int nFire) { injections.add(ControlsInjectionUtil.createExceptionOnBit(siteClass, desc, nSkip, nFire, exceptionClass, endpoint)); return this; } /** * Adds an exception injection (for the specified drillbit) to the controls builder with the given parameters. The * injection is not skipped, and the exception is thrown when execution reaches the site on the specified drillbit. * * @param siteClass class where the exception should be thrown * @param desc descriptor for the exception site in the site class * @param exceptionClass class of the exception to throw * @param endpoint endpoint of the drillbit on which to inject * @return this builder */ public Builder addExceptionOnBit(final Class<?> siteClass, final String desc, final Class<? extends Throwable> exceptionClass, final DrillbitEndpoint endpoint) { return addExceptionOnBit(siteClass, desc, exceptionClass, endpoint, 0, 1); } /** * Adds a pause injection to the controls builder with the given parameters. * * @param siteClass class where the pause should happen * @param desc descriptor for the pause site in the site class * @param nSkip number of times to skip before firing * @return this builder */ public Builder addPause(final Class<?> siteClass, final String desc, final int nSkip) { injections.add(ControlsInjectionUtil.createPause(siteClass, desc, nSkip)); return this; } /** * Adds a pause injection to the controls builder with the given parameters. The pause is not skipped i.e. the pause * happens when execution reaches the site. * * @param siteClass class where the pause should happen * @param desc descriptor for the pause site in the site class * @return this builder */ public Builder addPause(final Class<?> siteClass, final String desc) { return addPause(siteClass, desc, 0); } /** * Adds a pause injection (for the specified drillbit) to the controls builder with the given parameters. * * @param siteClass class where the pause should happen * @param desc descriptor for the pause site in the site class * @param nSkip number of times to skip before firing * @return this builder */ public Builder addPauseOnBit(final Class<?> siteClass, final String desc, final DrillbitEndpoint endpoint, final int nSkip) { injections.add(ControlsInjectionUtil.createPauseOnBit(siteClass, desc, nSkip, endpoint)); return this; } /** * Adds a pause injection (for the specified drillbit) to the controls builder with the given parameters. The pause * is not skipped i.e. the pause happens when execution reaches the site. * * @param siteClass class where the pause should happen * @param desc descriptor for the pause site in the site class * @return this builder */ public Builder addPauseOnBit(final Class<?> siteClass, final String desc, final DrillbitEndpoint endpoint) { return addPauseOnBit(siteClass, desc, endpoint, 0); } /** * Adds a count down latch to the controls builder with the given parameters. * * @param siteClass class where the latch should be injected * @param desc descriptor for the latch in the site class * @return this builder */ public Builder addLatch(final Class<?> siteClass, final String desc) { injections.add(ControlsInjectionUtil.createLatch(siteClass, desc)); return this; } /** * Builds the controls string. * * @return a validated controls string with the added injections * @throws java.lang.AssertionError if controls cannot be validated using * {@link org.apache.drill.exec.testing.ExecutionControls#controlsOptionMapper} */ public String build() { if (injections.size() == 0) { return EMPTY_CONTROLS; } final StringBuilder builder = new StringBuilder("{ \"injections\" : ["); for (final String injection : injections) { builder.append(injection) .append(","); } builder.setLength(builder.length() - 1); // remove the extra "," builder.append("]}"); final String controls = builder.toString(); ControlsInjectionUtil.validateControlsString(controls); return controls; } } }