1 /* 2 * Copyright (C) 2024 The Android Open Source Project 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 17 package com.android.tools.metalava.model.junit4 18 19 import com.android.tools.metalava.model.junit4.ParameterizedRunner.TestArguments 20 import org.junit.runner.Runner 21 import org.junit.runners.Parameterized 22 import org.junit.runners.Parameterized.Parameters 23 import org.junit.runners.model.FrameworkMethod 24 import org.junit.runners.model.TestClass 25 import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory 26 import org.junit.runners.parameterized.ParametersRunnerFactory 27 import org.junit.runners.parameterized.TestWithParameters 28 29 /** 30 * Extends [ParameterizedRunner] to combine parameters from the test class (specified using 31 * [Parameters] and additional arguments from [argumentsProvider]. 32 * 33 * @param clazz the test class to run. 34 * @param argumentsProvider provider of [TestArguments] used by this runner. Is also passed any 35 * additional parameters (provided by the test class using the standard [Parameterized] 36 * mechanism), if any. They can be filtered and/or combined in some way with parameters provides 37 * by this. 38 * @param parametersRunnerFactory factory for creating a [Runner] from a [TestWithParameters]. 39 */ 40 abstract class CustomizableParameterizedRunner<A : Any>( 41 clazz: Class<*>, 42 private val argumentsProvider: (TestClass, List<Array<Any>>?) -> TestArguments<A>, 43 parametersRunnerFactory: ParametersRunnerFactory = 44 BlockJUnit4ClassRunnerWithParametersFactory(), 45 ) : ParameterizedRunner<A>(TestClass(clazz), parametersRunnerFactory) { 46 computeTestArgumentsnull47 override fun computeTestArguments(testClass: TestClass): TestArguments<A> { 48 // Get additional arguments (if any) from the actual test class. 49 val additionalArguments = getAdditionalArguments(testClass) 50 51 // Obtain [TestArguments] from the provider and store the list of argument sets in the 52 // [RunnersFactory.allParametersField]. 53 val testArguments = argumentsProvider(testClass, additionalArguments) 54 55 // Check to see if there is a parameters filter method provided. 56 val parametersFilterMethod = 57 testClass.getAnnotatedMethods(ParameterFilter::class.java).firstOrNull { 58 it.isPublic && it.isStatic 59 } 60 61 // If there is then apply it to the testArguments, otherwise return it unfiltered. 62 val filteredArguments = 63 if (parametersFilterMethod == null) { 64 testArguments 65 } else { 66 testArguments.copy( 67 argumentSets = 68 testArguments.argumentSets.filter { 69 invokeFilterMethod(parametersFilterMethod, it) 70 } 71 ) 72 } 73 74 return filteredArguments 75 } 76 77 /** 78 * Invoke the [parametersFilterMethod] on [argument]. 79 * 80 * Subclasses that wish to use filters must override this. 81 */ invokeFilterMethodnull82 protected open fun invokeFilterMethod( 83 parametersFilterMethod: FrameworkMethod, 84 argument: A 85 ): Boolean { 86 error("Subclass does not implement invokeFilterMethod(...) method") 87 } 88 89 companion object { 90 /** 91 * Get additional arguments, if any, provided by the [testClass] through use of a 92 * [Parameters] function. 93 * 94 * The returned values have been normalized so each entry is an `Array<Any>`. 95 */ getAdditionalArgumentsnull96 private fun getAdditionalArguments(testClass: TestClass): List<Array<Any>>? { 97 val parametersMethod = 98 testClass.getAnnotatedMethods(Parameters::class.java).firstOrNull { 99 it.isPublic && it.isStatic 100 } 101 ?: return null 102 return when (val parameters = parametersMethod.invokeExplosively(null)) { 103 is List<*> -> parameters 104 is Iterable<*> -> parameters.toList() 105 is Array<*> -> parameters.toList() 106 else -> 107 error( 108 "${testClass.name}.{${parametersMethod.name}() must return an Iterable of arrays." 109 ) 110 } 111 .filterNotNull() 112 .map { 113 if ( 114 it is Array<*> && 115 it.javaClass.isArray && 116 it.javaClass.componentType == Object::class.java 117 ) { 118 @Suppress("UNCHECKED_CAST") 119 it as Array<Any> 120 } else { 121 arrayOf(it) 122 } 123 } 124 } 125 } 126 } 127