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