001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.lang.reflect.Modifier;
023import java.util.stream.Stream;
024import org.apache.yetus.audience.InterfaceAudience;
025import org.junit.jupiter.api.extension.ExtensionConfigurationException;
026import org.junit.jupiter.api.extension.ExtensionContext;
027import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
028import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
029import org.junit.jupiter.params.provider.Arguments;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * The entry point class for supporting JUnit4 like Parameterized test, where we can use constructor
035 * to pass parameters.
036 * <p>
037 * JUnit5's {@link org.junit.jupiter.params.ParameterizedClass} will create separated test classes,
038 * which is different with JUnit4 and {@link org.junit.jupiter.params.ParameterizedTest} does not
039 * support passing parameters through constructors.
040 * <p>
041 * When you want to use this provider, annotation the test class with
042 * {@link HBaseParameterizedTestTemplate}, and provide a static method named "parameters" for
043 * providing the arguments. The method must have no parameter, and return a Stream&lt;Arguments&gt;.
044 * All the test method should be marked with {@link org.junit.jupiter.api.TestTemplate}, not
045 * {@link org.junit.jupiter.api.Test} or {@link org.junit.jupiter.params.ParameterizedTest}.
046 * @see HBaseParameterizedTestTemplate
047 * @see HBaseParameterizedInvocationContext
048 * @see HBaseParameterizedParameterResolver
049 */
050@InterfaceAudience.Private
051public class HBaseParameterizedTemplateProvider implements TestTemplateInvocationContextProvider {
052
053  private static final Logger LOG =
054    LoggerFactory.getLogger(HBaseParameterizedTemplateProvider.class);
055
056  private static final String PARAMETERS_METHOD_NAME = "parameters";
057
058  @Override
059  public boolean supportsTestTemplate(ExtensionContext context) {
060    return context.getTestClass()
061      .map(c -> c.isAnnotationPresent(HBaseParameterizedTestTemplate.class)).orElse(false);
062  }
063
064  private Method findParametersMethod(final Class<?> testClass) {
065    Class<?> clazz = testClass;
066    for (;;) {
067      try {
068        return clazz.getDeclaredMethod(PARAMETERS_METHOD_NAME);
069      } catch (NoSuchMethodException e) {
070        Class<?> parentClass = clazz.getSuperclass();
071        LOG.debug("Can not find method {} in class {}, try its parent class {}",
072          PARAMETERS_METHOD_NAME, clazz, parentClass);
073        if (parentClass == null) {
074          throw new ExtensionConfigurationException("Can not find a static "
075            + PARAMETERS_METHOD_NAME + " method in class " + testClass + "or its super classes");
076        }
077        clazz = parentClass;
078      }
079    }
080
081  }
082
083  @Override
084  public Stream<TestTemplateInvocationContext>
085    provideTestTemplateInvocationContexts(ExtensionContext context) {
086    Class<?> testClass = context.getRequiredTestClass();
087    // get parameters
088    Method method = findParametersMethod(testClass);
089    if (!Modifier.isStatic(method.getModifiers())) {
090      throw new ExtensionConfigurationException(PARAMETERS_METHOD_NAME + " method must be static");
091    }
092    if (method.getParameterCount() > 0) {
093      throw new ExtensionConfigurationException(
094        PARAMETERS_METHOD_NAME + " method must not have any parameters");
095    }
096
097    Stream<Arguments> args;
098    try {
099      args = (Stream<Arguments>) method.invoke(null);
100    } catch (IllegalAccessException | InvocationTargetException e) {
101      throw new ExtensionConfigurationException("failed to invoke parameters method", e);
102    }
103    // get display name
104    String namePattern = testClass.getAnnotation(HBaseParameterizedTestTemplate.class).name();
105
106    return args.map(arg -> new HBaseParameterizedInvocationContext(arg, namePattern));
107  }
108
109}