xref: /aosp_15_r20/external/dagger2/java/dagger/testing/golden/GoldenFileRule.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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