• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 tests.util;
18 
19 import static org.junit.Assert.fail;
20 
21 import java.io.ByteArrayOutputStream;
22 import java.io.PrintStream;
23 import java.security.Provider;
24 import java.security.Security;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.Set;
30 
31 /**
32  * A utility for testing all the implementations of a particular service (such as MessageDigest or
33  * KeyGenerator).
34  * <p>
35  * An instance of this class may only be used to run one test.
36  */
37 public final class ServiceTester {
38 
39   public interface Test {
40     /**
41      * Run the test for the given provider and algorithm.  This method should throw an exception
42      * if the test fails or do nothing if it passes.
43      */
test(Provider p, String algorithm)44     void test(Provider p, String algorithm) throws Exception;
45   }
46 
47   private static final String SEPARATOR = "||";
48   private final String service;
49   private final Set<Provider> providers = new LinkedHashSet<>();
50   private final Set<Provider> skipProviders = new HashSet<>();
51   private final Set<String> algorithms = new LinkedHashSet<>();
52   private final Set<String> skipAlgorithms = new HashSet<>();
53   private final Set<String> skipCombinations = new HashSet<>();
54 
ServiceTester(String service)55   private ServiceTester(String service) {
56     this.service = service;
57   }
58 
59   /**
60    * Create a new ServiceTester for the given service.
61    */
test(String service)62   public static ServiceTester test(String service) {
63     if (service.equalsIgnoreCase("Cipher")) {
64       // Cipher is complicated because the parameterized transformations mean that you have
65       // to check for a lot of combinations (eg, a test for AES/CBC/NoPadding might be satisfied by
66       // a provider providing AES, AES/CBC, AES//NoPadding, or AES/CBC/NoPadding).  We don't
67       // really need it, so we haven't implemented it.
68       throw new IllegalArgumentException("ServiceTester doesn't support Cipher");
69     }
70     return new ServiceTester(service);
71   }
72 
73   /**
74    * Specifies the list of providers to test.  If this method is called multiple times, the
75    * collections are combined.  If this method is never called, this will test all installed
76    * providers.
77    *
78    * @throws IllegalArgumentException if a named provider is not installed
79    */
withProviders(Collection<String> providers)80   public ServiceTester withProviders(Collection<String> providers) {
81     for (String name : providers) {
82       Provider p = Security.getProvider(name);
83       if (p == null) {
84         throw new IllegalArgumentException("No such provider: " + name);
85       }
86       this.providers.add(p);
87     }
88     return this;
89   }
90 
91   /**
92    * Causes the given provider to be omitted from this instance's testing.  If the given provider
93    * is not installed, does nothing.
94    */
skipProvider(String provider)95   public ServiceTester skipProvider(String provider) {
96     Provider p = Security.getProvider(provider);
97     if (p != null) {
98       skipProviders.add(p);
99     }
100     return this;
101   }
102 
103   /**
104    * Specifies the algorithm to test.  If this method and/or {@link #withAlgorithms(Collection)}}
105    * are called multiple times, all values are combined.  If neither method is called, this will
106    * test all algorithms supported by any tested provider.
107    */
withAlgorithm(String algorithm)108   public ServiceTester withAlgorithm(String algorithm) {
109     this.algorithms.add(algorithm);
110     return this;
111   }
112 
113   /**
114    * Specifies the algorithms to test.  If this method and/or {@link #withAlgorithm(String)}}
115    * are called multiple times, all values are combined.  If neither method is called, this will
116    * test all algorithms supported by any tested provider.
117    */
withAlgorithms(Collection<String> algorithms)118   public ServiceTester withAlgorithms(Collection<String> algorithms) {
119     this.algorithms.addAll(algorithms);
120     return this;
121   }
122 
123   /**
124    * Causes the given algorithm to be omitted from this instance's testing.  If no tested provider
125    * provides the given algorithm, does nothing.
126    */
skipAlgorithm(String algorithm)127   public ServiceTester skipAlgorithm(String algorithm) {
128     skipAlgorithms.add(algorithm);
129     return this;
130   }
131 
132   /**
133    * Causes the given combination of provider and algorithm to be omitted from this instance's
134    * testing. If no tested provider provides the given algorithm, does nothing.
135    */
skipCombination(String provider, String algorithm)136   public ServiceTester skipCombination(String provider, String algorithm) {
137     Provider p = Security.getProvider(provider);
138     if (p != null) {
139       skipCombinations.add(makeCombination(provider, algorithm));
140     }
141     return this;
142   }
143 
144   /**
145    * Runs the given test against the configured combination of providers and algorithms.  Continues
146    * running all combinations even if some fail.  If any of the test runs fail, this throws
147    * an exception with the details of the failure(s).
148    */
run(Test test)149   public void run(Test test) {
150     if (providers.isEmpty()) {
151       providers.addAll(Arrays.asList(Security.getProviders()));
152     }
153     providers.removeAll(skipProviders);
154     final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
155     PrintStream errors = new PrintStream(errBuffer);
156     for (Provider p : providers) {
157       if (algorithms.isEmpty()) {
158         for (Provider.Service s : p.getServices()) {
159           if (s.getType().equals(service)
160               && !skipAlgorithms.contains(s.getAlgorithm())
161               && !shouldSkipCombination(p.getName(), s.getAlgorithm())) {
162             doTest(test, p, s.getAlgorithm(), errors);
163           }
164         }
165       } else {
166         algorithms.removeAll(skipAlgorithms);
167         for (String algorithm : algorithms) {
168           if (p.getService(service, algorithm) != null
169               && !shouldSkipCombination(p.getName(), algorithm)) {
170             doTest(test, p, algorithm, errors);
171           }
172         }
173       }
174     }
175     errors.flush();
176     if (errBuffer.size() > 0) {
177       fail("Tests failed:\n\n" + errBuffer.toString());
178     }
179   }
180 
makeCombination(String provider, String algorithm)181   private String makeCombination(String provider, String algorithm) {
182     return provider + SEPARATOR + algorithm;
183   }
184 
shouldSkipCombination(String provider, String algorithm)185   private boolean shouldSkipCombination(String provider, String algorithm) {
186     return skipCombinations.contains(makeCombination(provider, algorithm));
187   }
188 
doTest(Test test, Provider p, String algorithm, PrintStream errors)189   private void doTest(Test test, Provider p, String algorithm, PrintStream errors) {
190     try {
191       test.test(p, algorithm);
192     } catch (Exception|AssertionError e) {
193       errors.append("Failure testing " + service + ":" + algorithm
194           + " from provider " + p.getName() + ":\n");
195       e.printStackTrace(errors);
196     }
197   }
198 
199 }
200