1 /*
2  * Copyright (C) 2018 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 package android.signature.cts.tests;
17 
18 import android.signature.cts.ApiPresenceChecker;
19 import android.signature.cts.ClassProvider;
20 import android.signature.cts.ExcludingClassProvider;
21 import android.signature.cts.FailureType;
22 import android.signature.cts.JDiffClassDescription;
23 import android.signature.cts.ResultObserver;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.lang.reflect.Modifier;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.function.Consumer;
32 
33 import org.junit.Assert;
34 
35 /**
36  * Base class for tests of implementations of {@link ApiPresenceChecker}.
37  */
38 public abstract class ApiPresenceCheckerTest<T extends ApiPresenceChecker> {
39 
40     static final String VALUE = "VALUE";
41 
createClassProvider(String[] excludedRuntimeClasses)42     private static ClassProvider createClassProvider(String[] excludedRuntimeClasses) {
43         ClassProvider provider = new TestClassesProvider();
44         if (excludedRuntimeClasses.length != 0) {
45             provider = new ExcludingClassProvider(provider,
46                     name -> Arrays.stream(excludedRuntimeClasses)
47                             .anyMatch(myname -> myname.equals(name)));
48         }
49         return provider;
50     }
51 
createClass(String name)52     protected static JDiffClassDescription createClass(String name) {
53         JDiffClassDescription clz = new JDiffClassDescription(
54                 "android.signature.cts.tests.data", name);
55         clz.setType(JDiffClassDescription.JDiffType.CLASS);
56         clz.setModifier(Modifier.PUBLIC);
57         return clz;
58     }
59 
createAbstractClass(String name)60     protected static JDiffClassDescription createAbstractClass(String name) {
61         JDiffClassDescription clz = new JDiffClassDescription(
62                 "android.signature.cts.tests.data", name);
63         clz.setType(JDiffClassDescription.JDiffType.CLASS);
64         clz.setModifier(Modifier.PUBLIC | Modifier.ABSTRACT);
65         return clz;
66     }
67 
addConstructor(JDiffClassDescription clz, String... paramTypes)68     protected static void addConstructor(JDiffClassDescription clz, String... paramTypes) {
69         JDiffClassDescription.JDiffConstructor constructor =
70             new JDiffClassDescription.JDiffConstructor(clz.getShortClassName(), Modifier.PUBLIC);
71         if (paramTypes != null) {
72             for (String type : paramTypes) {
73                 constructor.addParam(type);
74             }
75         }
76         clz.addConstructor(constructor);
77     }
78 
addPublicVoidMethod(JDiffClassDescription clz, String name)79     protected static void addPublicVoidMethod(JDiffClassDescription clz, String name) {
80         clz.addMethod(method(name, Modifier.PUBLIC, "void"));
81     }
82 
addPublicBooleanField(JDiffClassDescription clz, String name)83     protected static void addPublicBooleanField(JDiffClassDescription clz, String name) {
84         JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
85                 name, "boolean", Modifier.PUBLIC, VALUE);
86         clz.addField(field);
87     }
88 
checkSignatureCompliance(JDiffClassDescription classDescription, String... excludedRuntimeClassNames)89     void checkSignatureCompliance(JDiffClassDescription classDescription,
90             String... excludedRuntimeClassNames) {
91         try (NoFailures resultObserver = new NoFailures()) {
92             checkSignatureCompliance(classDescription, resultObserver,
93                     excludedRuntimeClassNames);
94         }
95     }
96 
checkSignatureCompliance(JDiffClassDescription classDescription, ResultObserver resultObserver, String... excludedRuntimeClasses)97     void checkSignatureCompliance(JDiffClassDescription classDescription,
98             ResultObserver resultObserver, String... excludedRuntimeClasses) {
99         runWithApiChecker(resultObserver,
100                 checker -> checker.checkSignatureCompliance(classDescription),
101                 excludedRuntimeClasses);
102     }
103 
runWithApiChecker( ResultObserver resultObserver, Consumer<T> consumer, String... excludedRuntimeClasses)104     void runWithApiChecker(
105             ResultObserver resultObserver, Consumer<T> consumer, String... excludedRuntimeClasses) {
106         ClassProvider provider = createClassProvider(excludedRuntimeClasses);
107         T checker = createChecker(resultObserver, provider);
108         consumer.accept(checker);
109     }
110 
createChecker(ResultObserver resultObserver, ClassProvider provider)111     protected abstract T createChecker(ResultObserver resultObserver, ClassProvider provider);
112 
createInterface(String name)113     protected JDiffClassDescription createInterface(String name) {
114         JDiffClassDescription clz = new JDiffClassDescription(
115                 "android.signature.cts.tests.data", name);
116         clz.setType(JDiffClassDescription.JDiffType.INTERFACE);
117         clz.setModifier(Modifier.PUBLIC | Modifier.ABSTRACT);
118         return clz;
119     }
120 
ctor(String name, int modifiers)121     protected static JDiffClassDescription.JDiffConstructor ctor(String name, int modifiers) {
122         return new JDiffClassDescription.JDiffConstructor(name, modifiers);
123     }
124 
method( String name, int modifiers, String returnType)125     protected static JDiffClassDescription.JDiffMethod method(
126             String name, int modifiers, String returnType) {
127         return new JDiffClassDescription.JDiffMethod(name, modifiers, returnType);
128     }
129 
130     protected static class Failure {
131         private final FailureType type;
132         private final String name;
133         private final String errorMessage;
134         private final Throwable throwable;
135 
Failure(FailureType type, String name, String errorMessage, Throwable throwable)136         public Failure(FailureType type, String name, String errorMessage, Throwable throwable) {
137             this.type = type;
138             this.name = name;
139             this.errorMessage = errorMessage;
140             this.throwable = throwable;
141         }
142 
143         @Override
toString()144         public String toString() {
145             String exception = "<none>";
146             if (throwable != null) {
147                 StringWriter out = new StringWriter();
148                 throwable.printStackTrace(new PrintWriter(out));
149                 exception = out.toString();
150             }
151             return "Failure{" +
152                     "type=" + type +
153                     ", name='" + name + '\'' +
154                     ", errorMessage='" + errorMessage + '\'' +
155                     ", throwable=" + exception +
156                     '}';
157         }
158     }
159 
160     /**
161      * Collect failures and check them after the test has run.
162      *
163      * <p>Throwing an exception in the {@link #notifyFailure} method causes that exception to be
164      * reported as another failure which then fails again failing the test and losing information
165      * about the original exception.</p>
166      */
167     protected static abstract class FailureGathererObserver
168             implements ResultObserver, AutoCloseable {
169 
170         private final List<Failure> failures;
171 
FailureGathererObserver()172         public FailureGathererObserver() {
173             failures = new ArrayList<>();
174         }
175 
176         @Override
notifyFailure(FailureType type, String name, String errorMessage, Throwable throwable)177         public final void notifyFailure(FailureType type, String name, String errorMessage,
178                 Throwable throwable) {
179             failures.add(new Failure(type, name, errorMessage, throwable));
180         }
181 
182         @Override
close()183         public void close() {
184             checkFailures(failures);
185         }
186 
checkFailures(List<Failure> failures)187         public abstract void checkFailures(List<Failure> failures);
188     }
189 
190     protected static class NoFailures extends FailureGathererObserver {
191 
192         @Override
checkFailures(List<Failure> failures)193         public void checkFailures(List<Failure> failures) {
194             Assert.assertEquals("Unexpected test failure", Collections.emptyList(), failures);
195         }
196     }
197 
198     protected static class ExpectFailure extends FailureGathererObserver {
199 
200         private final FailureType expectedType;
201 
ExpectFailure(FailureType expectedType)202         ExpectFailure(FailureType expectedType) {
203             this.expectedType = expectedType;
204         }
205 
206         @Override
checkFailures(List<Failure> failures)207         public void checkFailures(List<Failure> failures) {
208             int count = failures.size();
209             boolean ok = count == 1;
210             if (ok) {
211                 Failure failure = failures.get(0);
212                 if (failure.type != expectedType) {
213                     ok = false;
214                 }
215             }
216 
217             if (!ok) {
218                 Assert.fail("Expect one failure of type " + expectedType + " but found " + count
219                         + " failures: " + failures);
220             }
221         }
222     }
223 }
224