1*f585d8a3SJacky Wang /* 2*f585d8a3SJacky Wang * Copyright (C) 2022 The Dagger Authors. 3*f585d8a3SJacky Wang * 4*f585d8a3SJacky Wang * Licensed under the Apache License, Version 2.0 (the "License"); 5*f585d8a3SJacky Wang * you may not use this file except in compliance with the License. 6*f585d8a3SJacky Wang * You may obtain a copy of the License at 7*f585d8a3SJacky Wang * 8*f585d8a3SJacky Wang * http://www.apache.org/licenses/LICENSE-2.0 9*f585d8a3SJacky Wang * 10*f585d8a3SJacky Wang * Unless required by applicable law or agreed to in writing, software 11*f585d8a3SJacky Wang * distributed under the License is distributed on an "AS IS" BASIS, 12*f585d8a3SJacky Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*f585d8a3SJacky Wang * See the License for the specific language governing permissions and 14*f585d8a3SJacky Wang * limitations under the License. 15*f585d8a3SJacky Wang */ 16*f585d8a3SJacky Wang 17*f585d8a3SJacky Wang package dagger.testing.golden; 18*f585d8a3SJacky Wang 19*f585d8a3SJacky Wang import androidx.room.compiler.processing.util.Source; 20*f585d8a3SJacky Wang import com.google.common.io.Resources; 21*f585d8a3SJacky Wang import com.google.testing.compile.JavaFileObjects; 22*f585d8a3SJacky Wang import java.io.IOException; 23*f585d8a3SJacky Wang import java.net.URL; 24*f585d8a3SJacky Wang import java.nio.charset.StandardCharsets; 25*f585d8a3SJacky Wang import java.util.regex.Matcher; 26*f585d8a3SJacky Wang import java.util.regex.Pattern; 27*f585d8a3SJacky Wang import javax.tools.JavaFileObject; 28*f585d8a3SJacky Wang import org.junit.rules.TestRule; 29*f585d8a3SJacky Wang import org.junit.runner.Description; 30*f585d8a3SJacky Wang import org.junit.runners.model.Statement; 31*f585d8a3SJacky Wang 32*f585d8a3SJacky Wang 33*f585d8a3SJacky Wang /** A test rule that manages golden files for tests. */ 34*f585d8a3SJacky Wang public final class GoldenFileRule implements TestRule { 35*f585d8a3SJacky Wang /** The generated import used in the golden files */ 36*f585d8a3SJacky Wang private static final String GOLDEN_GENERATED_IMPORT = 37*f585d8a3SJacky Wang "import javax.annotation.processing.Generated;"; 38*f585d8a3SJacky Wang 39*f585d8a3SJacky Wang /** The generated import used with the current jdk version */ 40*f585d8a3SJacky Wang private static final String JDK_GENERATED_IMPORT = 41*f585d8a3SJacky Wang isBeforeJava9() 42*f585d8a3SJacky Wang ? "import javax.annotation.Generated;" 43*f585d8a3SJacky Wang : "import javax.annotation.processing.Generated;"; 44*f585d8a3SJacky Wang isBeforeJava9()45*f585d8a3SJacky Wang private static boolean isBeforeJava9() { 46*f585d8a3SJacky Wang try { 47*f585d8a3SJacky Wang Class.forName("java.lang.Module"); 48*f585d8a3SJacky Wang return false; 49*f585d8a3SJacky Wang } catch (ClassNotFoundException e) { 50*f585d8a3SJacky Wang return true; 51*f585d8a3SJacky Wang } 52*f585d8a3SJacky Wang } 53*f585d8a3SJacky Wang 54*f585d8a3SJacky Wang // Parameterized arguments in junit4 are added in brackets to the end of test methods, e.g. 55*f585d8a3SJacky Wang // `myTestMethod[testParam1=FOO,testParam2=BAR]`. This pattern captures theses into two separate 56*f585d8a3SJacky Wang // groups, `<GROUP1>[<GROUP2>]` to make it easier when generating the golden file name. 57*f585d8a3SJacky Wang private static final Pattern JUNIT_PARAMETERIZED_METHOD = Pattern.compile("(.*?)\\[(.*?)\\]"); 58*f585d8a3SJacky Wang 59*f585d8a3SJacky Wang private Description description; 60*f585d8a3SJacky Wang 61*f585d8a3SJacky Wang @Override apply(Statement base, Description description)62*f585d8a3SJacky Wang public Statement apply(Statement base, Description description) { 63*f585d8a3SJacky Wang this.description = description; 64*f585d8a3SJacky Wang return base; 65*f585d8a3SJacky Wang } 66*f585d8a3SJacky Wang 67*f585d8a3SJacky Wang /** 68*f585d8a3SJacky Wang * Returns the golden file as a {@link Source} containing the file's content. 69*f585d8a3SJacky Wang * 70*f585d8a3SJacky Wang * <p>If the golden file does not exist, the returned file object contains an error message 71*f585d8a3SJacky Wang * pointing to the location of the missing golden file. This can be used with scripting tools to 72*f585d8a3SJacky Wang * output the correct golden file in the proper location. 73*f585d8a3SJacky Wang */ goldenSource(String generatedFilePath)74*f585d8a3SJacky Wang public Source goldenSource(String generatedFilePath) { 75*f585d8a3SJacky Wang // Note: we wrap the IOException in a RuntimeException so that this can be called from within 76*f585d8a3SJacky Wang // the lambda required by XProcessing's testing APIs. We could avoid this by calling this method 77*f585d8a3SJacky Wang // outside of the lambda, but that seems like an non-worthwile hit to readability. 78*f585d8a3SJacky Wang try { 79*f585d8a3SJacky Wang return Source.Companion.java( 80*f585d8a3SJacky Wang generatedFilePath, goldenFileContent(generatedFilePath.replace('/', '.'))); 81*f585d8a3SJacky Wang } catch (IOException e) { 82*f585d8a3SJacky Wang throw new RuntimeException(e); 83*f585d8a3SJacky Wang } 84*f585d8a3SJacky Wang } 85*f585d8a3SJacky Wang 86*f585d8a3SJacky Wang /** 87*f585d8a3SJacky Wang * Returns the golden file as a {@link JavaFileObject} containing the file's content. 88*f585d8a3SJacky Wang * 89*f585d8a3SJacky Wang * If the golden file does not exist, the returned file object contain an error message pointing 90*f585d8a3SJacky Wang * to the location of the missing golden file. This can be used with scripting tools to output 91*f585d8a3SJacky Wang * the correct golden file in the proper location. 92*f585d8a3SJacky Wang */ goldenFile(String qualifiedName)93*f585d8a3SJacky Wang public JavaFileObject goldenFile(String qualifiedName) throws IOException { 94*f585d8a3SJacky Wang return JavaFileObjects.forSourceLines(qualifiedName, goldenFileContent(qualifiedName)); 95*f585d8a3SJacky Wang } 96*f585d8a3SJacky Wang 97*f585d8a3SJacky Wang /** 98*f585d8a3SJacky Wang * Returns the golden file content. 99*f585d8a3SJacky Wang * 100*f585d8a3SJacky Wang * If the golden file does not exist, the returned content contains an error message pointing 101*f585d8a3SJacky Wang * to the location of the missing golden file. This can be used with scripting tools to output 102*f585d8a3SJacky Wang * the correct golden file in the proper location. 103*f585d8a3SJacky Wang */ goldenFileContent(String qualifiedName)104*f585d8a3SJacky Wang public String goldenFileContent(String qualifiedName) throws IOException { 105*f585d8a3SJacky Wang String fileName = 106*f585d8a3SJacky Wang String.format( 107*f585d8a3SJacky Wang "%s_%s_%s", 108*f585d8a3SJacky Wang description.getTestClass().getSimpleName(), 109*f585d8a3SJacky Wang getFormattedMethodName(description), 110*f585d8a3SJacky Wang qualifiedName); 111*f585d8a3SJacky Wang 112*f585d8a3SJacky Wang URL url = description.getTestClass().getResource("goldens/" + fileName); 113*f585d8a3SJacky Wang return url == null 114*f585d8a3SJacky Wang // If the golden file does not exist, create a fake file with a comment pointing to the 115*f585d8a3SJacky Wang // missing golden file. This is helpful for scripts that need to generate golden files from 116*f585d8a3SJacky Wang // the test failures. 117*f585d8a3SJacky Wang ? "// Error: Missing golden file for goldens/" + fileName 118*f585d8a3SJacky Wang // The goldens are generated using jdk 11, so we use this replacement to allow the 119*f585d8a3SJacky Wang // goldens to also work when compiling using jdk < 9. 120*f585d8a3SJacky Wang : Resources.toString(url, StandardCharsets.UTF_8) 121*f585d8a3SJacky Wang .replace(GOLDEN_GENERATED_IMPORT, JDK_GENERATED_IMPORT); 122*f585d8a3SJacky Wang } 123*f585d8a3SJacky Wang 124*f585d8a3SJacky Wang /** 125*f585d8a3SJacky Wang * Returns the formatted method name for the given description. 126*f585d8a3SJacky Wang * 127*f585d8a3SJacky Wang * <p>If this is not a parameterized test, we return the method name as is. If it is a 128*f585d8a3SJacky Wang * parameterized test, we format it from {@code someTestMethod[PARAMETER]} to 129*f585d8a3SJacky Wang * {@code someTestMethod_PARAMETER} to avoid brackets in the name. 130*f585d8a3SJacky Wang */ getFormattedMethodName(Description description)131*f585d8a3SJacky Wang private static String getFormattedMethodName(Description description) { 132*f585d8a3SJacky Wang Matcher matcher = JUNIT_PARAMETERIZED_METHOD.matcher(description.getMethodName()); 133*f585d8a3SJacky Wang 134*f585d8a3SJacky Wang // If this is a parameterized method, separate the parameters with an underscore 135*f585d8a3SJacky Wang return matcher.find() ? matcher.group(1) + "_" + matcher.group(2) : description.getMethodName(); 136*f585d8a3SJacky Wang } 137*f585d8a3SJacky Wang } 138