001/** 002 * 003 * Copyright 2019 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jxmpp.strings.testframework; 018 019import java.io.IOException; 020import java.time.ZonedDateTime; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.List; 025import java.util.concurrent.ExecutorService; 026import java.util.concurrent.Executors; 027import java.util.concurrent.Phaser; 028import java.util.function.BiConsumer; 029import java.util.function.Function; 030import java.util.stream.Collectors; 031 032import org.jxmpp.jid.Jid; 033import org.jxmpp.jid.impl.JidCreate; 034import org.jxmpp.stringprep.XmppStringprepException; 035import org.parboiled.common.FileUtils; 036 037public class StringsTestframework { 038 039 private static final ExecutorService EXECUTOR = Executors 040 .newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 041 042 private final Configuration configuration; 043 044 private final Phaser phaser = new Phaser(0); 045 046 /** 047 * Construct a new instance of the test framework with the default configuration. 048 */ 049 public StringsTestframework() { 050 this(Configuration.builder().withAllKnownXmppStringpreppers().build()); 051 } 052 053 /** 054 * Construct a new instance of the test framework with the given configuration. 055 * 056 * @param configuration the configuration to use. 057 */ 058 public StringsTestframework(Configuration configuration) { 059 this.configuration = configuration; 060 } 061 062 /** 063 * Run the test framework. 064 * 065 * @return <code>true</code> if there where no errors. 066 */ 067 public synchronized Result runTests() { 068 phaser.register(); 069 070 List<ValidJid> validJids = parseValidJids(); 071 List<InvalidJid> invalidJids = parseInvalidJids(); 072 073 074 List<XmppStringPrepperState> xmppStringPreppersState = configuration.xmppStringPreppers 075 .stream() 076 .map(s -> new XmppStringPrepperState(s, validJids.size(), invalidJids.size())) 077 .collect(Collectors.toList()); 078 079 executeForEach(xmppStringPreppersState, validJids, StringsTestframework::testValidJids); 080 081 executeForEach(xmppStringPreppersState, invalidJids, StringsTestframework::testInvalidJids); 082 083 phaser.arriveAndAwaitAdvance(); 084 085 return new Result(xmppStringPreppersState); 086 } 087 088 private <T, U> void executeForEach(List<? extends T> smallList, Collection<? extends U> bigCollection, 089 BiConsumer<T, U> biConsumer) { 090 // Create a childPhaser for every item in smallList. 091 List<Phaser> childPhasers = smallList.stream() 092 .map(__ -> new Phaser(phaser, bigCollection.size() + 1)) 093 .collect(Collectors.toList()); 094 095 for (U u : bigCollection) { 096 for (int i = 0; i < smallList.size(); i++) { 097 T t = smallList.get(i); 098 Phaser phaser = childPhasers.get(i); 099 100 EXECUTOR.execute(() -> { 101 try { 102 biConsumer.accept(t, u); 103 } finally { 104 phaser.arrive(); 105 } 106 }); 107 } 108 } 109 110 childPhasers.forEach(p -> p.arrive()); 111 } 112 113 private static void testValidJids(XmppStringPrepperState xmppStringPrepperState, ValidJid validJid) { 114 XmppStringPrepper xmppStringPrepper = xmppStringPrepperState.xmppStringPrepper; 115 Jid jid; 116 117 long startNanos = System.nanoTime(); 118 try { 119 jid = JidCreate.from(validJid.unnormalizedJid, xmppStringPrepper.context); 120 } catch (XmppStringprepException e) { 121 long stopNanos = System.nanoTime(); 122 ValidJidTestresult.FailedBecauseException failed = new ValidJidTestresult.FailedBecauseException( 123 xmppStringPrepper, startNanos, stopNanos, validJid, e); 124 xmppStringPrepperState.validJidFailedTestresults.add(failed); 125 return; 126 } 127 long stopNanos = System.nanoTime(); 128 129 boolean domainpartMismatch = false, localpartMismatch = false, resourcepartMismatch = false; 130 131 String domainpart = jid.getDomain().toString(); 132 if (!domainpart.equals(validJid.domainpart)) { 133 domainpartMismatch = true; 134 } 135 136 if (!validJid.localpart.isEmpty()) { 137 if (!jid.hasLocalpart()) { 138 localpartMismatch = true; 139 } else { 140 String localpart = jid.getLocalpartOrThrow().toString(); 141 if (!localpart.equals(validJid.localpart)) { 142 localpartMismatch = true; 143 } 144 } 145 } 146 147 if (!validJid.resourcepart.isEmpty()) { 148 if (jid.hasNoResource()) { 149 resourcepartMismatch = true; 150 } else { 151 String resourcepart = jid.getResourceOrThrow().toString(); 152 if (!resourcepart.equals(validJid.resourcepart)) { 153 resourcepartMismatch = true; 154 } 155 } 156 } 157 158 if (domainpartMismatch || localpartMismatch || resourcepartMismatch) { 159 ValidJidTestresult.FailedBecauseMismatch failed = new ValidJidTestresult.FailedBecauseMismatch( 160 xmppStringPrepper, startNanos, stopNanos, validJid, jid, domainpartMismatch, localpartMismatch, 161 resourcepartMismatch); 162 xmppStringPrepperState.validJidFailedTestresults.add(failed); 163 } else { 164 ValidJidTestresult.Successful successful = new ValidJidTestresult.Successful(xmppStringPrepper, startNanos, 165 stopNanos, validJid, jid); 166 xmppStringPrepperState.validJidSuccessfulTestresults.add(successful); 167 } 168 } 169 170 private static void testInvalidJids(XmppStringPrepperState xmppStringPrepperState, InvalidJid invalidJid) { 171 XmppStringPrepper xmppStringPrepper = xmppStringPrepperState.xmppStringPrepper; 172 Jid jid; 173 174 long startNanos = System.nanoTime(); 175 try { 176 jid = JidCreate.from(invalidJid.invalidJid, xmppStringPrepper.context); 177 } catch (XmppStringprepException e) { 178 long stopNanos = System.nanoTime(); 179 InvalidJidTestresult.Successful successful = new InvalidJidTestresult.Successful(xmppStringPrepper, 180 startNanos, stopNanos, invalidJid, e); 181 xmppStringPrepperState.invalidJidSuccessfulTestresults.add(successful); 182 return; 183 } 184 long stopNanos = System.nanoTime(); 185 186 InvalidJidTestresult.Failed failed = new InvalidJidTestresult.Failed(xmppStringPrepper, startNanos, stopNanos, 187 invalidJid, jid); 188 xmppStringPrepperState.invalidJidFailedTestresults.add(failed); 189 } 190 191 static List<ValidJid> parseValidJids() { 192 String mainValidJids = FileUtils.readAllTextFromResource("xmpp-strings/jids/valid/main"); 193 ValidJidCorpusParser parser = new ValidJidCorpusParser(mainValidJids); 194 List<ValidJid> validJids = parser.parse(); 195 return validJids; 196 } 197 198 static List<InvalidJid> parseInvalidJids() { 199 String mainInvalidJids = FileUtils.readAllTextFromResource("xmpp-strings/jids/invalid/main"); 200 InvalidJidCorpusParser parser = new InvalidJidCorpusParser(mainInvalidJids); 201 List<InvalidJid> invalidJids = parser.parse(); 202 return invalidJids; 203 } 204 205 private static class XmppStringPrepperState { 206 207 private final XmppStringPrepper xmppStringPrepper; 208 209 private final List<ValidJidTestresult.Successful> validJidSuccessfulTestresults; 210 private final List<ValidJidTestresult.Failed> validJidFailedTestresults; 211 212 private final List<InvalidJidTestresult.Successful> invalidJidSuccessfulTestresults; 213 private final List<InvalidJidTestresult.Failed> invalidJidFailedTestresults; 214 215 private XmppStringPrepperState(XmppStringPrepper xmppStringPrepper, int validJidCorpusSize, 216 int invalidJidCorpusSize) { 217 this.xmppStringPrepper = xmppStringPrepper; 218 219 this.validJidSuccessfulTestresults = Collections.synchronizedList(new ArrayList<>(validJidCorpusSize)); 220 this.validJidFailedTestresults = Collections.synchronizedList(new ArrayList<>()); 221 222 this.invalidJidSuccessfulTestresults = Collections.synchronizedList(new ArrayList<>(invalidJidCorpusSize)); 223 this.invalidJidFailedTestresults = Collections.synchronizedList(new ArrayList<>()); 224 } 225 226 } 227 228 public static class Configuration { 229 final List<XmppStringPrepper> xmppStringPreppers; 230 231 private Configuration(Builder builder) { 232 xmppStringPreppers = Collections.unmodifiableList(new ArrayList<>(builder.xmppStringPreppers)); 233 } 234 235 /** 236 * Construct a new builder. 237 * 238 * @return a newly constructed builder. 239 */ 240 public static Builder builder() { 241 return new Builder(); 242 } 243 244 public static final class Builder { 245 List<String> validJidCorpusResources = new ArrayList<>(); 246 List<XmppStringPrepper> xmppStringPreppers = new ArrayList<>(); 247 248 private Builder() { 249 } 250 251 /** 252 * Use the given XMPP String Prepper with the given name. 253 * 254 * @param xmppStringPrepperName the name of the XMPP String Prepper. 255 * @return an reference to this builder instance. 256 */ 257 public Builder withXmppStringPrepper(String xmppStringPrepperName) { 258 XmppStringPrepper xmppStringPrepper = XmppStringPrepper.lookup(xmppStringPrepperName); 259 if (xmppStringPrepper == null) { 260 throw new IllegalArgumentException( 261 "No XMPP stringprepper with the name '" + xmppStringPrepperName + "' known."); 262 } 263 264 xmppStringPreppers.add(xmppStringPrepper); 265 return this; 266 } 267 268 /** 269 * Use all known XMPP String Preppers. 270 * 271 * @return an reference to this builder instance. 272 */ 273 public Builder withAllKnownXmppStringpreppers() { 274 List<XmppStringPrepper> xmppStringPreppers = XmppStringPrepper.getKnownXmppStringpreppers(); 275 this.xmppStringPreppers.addAll(xmppStringPreppers); 276 return this; 277 } 278 279 /** 280 * Add the given collection of XMPP String Preppers to the list of used preppers. 281 * 282 * @param xmppStringPreppers a collection of XMPP String Preppers. 283 * @return an reference to this builder instance. 284 */ 285 public Builder addXmppStringPreppers(Collection<? extends XmppStringPrepper> xmppStringPreppers) { 286 this.xmppStringPreppers.addAll(xmppStringPreppers); 287 return this; 288 } 289 290 /** 291 * Build the configuration. 292 * 293 * @return the build configuration. 294 */ 295 public Configuration build() { 296 return new Configuration(this); 297 } 298 } 299 } 300 301 public static class XmppStringPrepperResult implements Comparable<XmppStringPrepperResult> { 302 303 public final XmppStringPrepper xmppStringPrepper; 304 305 public final List<ValidJidTestresult.Successful> validJidSuccessfulTestresults; 306 public final List<ValidJidTestresult.Failed> validJidFailedTestresults; 307 308 public final List<InvalidJidTestresult.Successful> invalidJidSuccessfulTestresults; 309 public final List<InvalidJidTestresult.Failed> invalidJidFailedTestresults; 310 311 public final int totalSuccessful, totalFailed; 312 313 private XmppStringPrepperResult(XmppStringPrepperState xmppStringPrepperState) { 314 xmppStringPrepper = xmppStringPrepperState.xmppStringPrepper; 315 316 validJidSuccessfulTestresults = Collections 317 .unmodifiableList(new ArrayList<>(xmppStringPrepperState.validJidSuccessfulTestresults)); 318 validJidFailedTestresults = Collections 319 .unmodifiableList(new ArrayList<>(xmppStringPrepperState.validJidFailedTestresults)); 320 321 invalidJidSuccessfulTestresults = Collections 322 .unmodifiableList(new ArrayList<>(xmppStringPrepperState.invalidJidSuccessfulTestresults)); 323 invalidJidFailedTestresults = Collections 324 .unmodifiableList(new ArrayList<>(xmppStringPrepperState.invalidJidFailedTestresults)); 325 326 totalSuccessful = validJidSuccessfulTestresults.size() + invalidJidSuccessfulTestresults.size(); 327 totalFailed = validJidFailedTestresults.size() + invalidJidFailedTestresults.size(); 328 } 329 330 @Override 331 public int compareTo(XmppStringPrepperResult o) { 332 if (totalSuccessful != o.totalSuccessful) { 333 return Integer.compare(totalSuccessful, o.totalSuccessful); 334 } 335 336 int myValidJidSuccessfulCount = validJidFailedTestresults.size(); 337 int otherValidJidSuccessfulCount = o.validJidSuccessfulTestresults.size(); 338 if (myValidJidSuccessfulCount != otherValidJidSuccessfulCount) { 339 return Integer.compare(myValidJidSuccessfulCount, otherValidJidSuccessfulCount); 340 } 341 342 return 0; 343 } 344 345 } 346 347 public static class Result { 348 public final List<XmppStringPrepperResult> xmppStringPrepperResults; 349 350 public final List<ValidJidTestresult.Successful> validJidSuccessfulTestresults; 351 public final List<ValidJidTestresult.Failed> validJidFailedTestresults; 352 353 public final List<InvalidJidTestresult.Successful> invalidJidSuccessfulTestresults; 354 public final List<InvalidJidTestresult.Failed> invalidJidFailedTestresults; 355 356 public final boolean wasSuccessful; 357 public final int totalSuccessful; 358 public final int totalFailed; 359 360 public final ZonedDateTime time = ZonedDateTime.now(); 361 362 Result(List<XmppStringPrepperState> xmppStringPreppersState) { 363 xmppStringPrepperResults = xmppStringPreppersState.stream() 364 .map(s -> new XmppStringPrepperResult(s)) 365 .collect(Collectors.collectingAndThen(Collectors.toList(), 366 results -> { 367 Collections.sort(results); 368 return Collections.unmodifiableList(results); 369 })); 370 371 validJidSuccessfulTestresults = gather(xmppStringPrepperResults, s -> s.validJidSuccessfulTestresults); 372 validJidFailedTestresults = gather(xmppStringPrepperResults, s -> s.validJidFailedTestresults); 373 374 invalidJidSuccessfulTestresults = gather(xmppStringPrepperResults, s -> s.invalidJidSuccessfulTestresults); 375 invalidJidFailedTestresults = gather(xmppStringPrepperResults, s -> s.invalidJidFailedTestresults); 376 377 totalSuccessful = validJidSuccessfulTestresults.size() + invalidJidSuccessfulTestresults.size(); 378 totalFailed = validJidFailedTestresults.size() + invalidJidFailedTestresults.size(); 379 wasSuccessful = totalFailed == 0; 380 } 381 382 private static <I,R> List<R> gather(Collection<I> inputCollection, 383 Function<? super I, Collection<? extends R>> mapper) { 384 return inputCollection.stream() 385 .map(mapper) 386 .flatMap(l -> l.stream()) 387 // TODO Use Collectors.toUnmodifiableList() once jXMPP is on Java 10 or higher. 388 .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); 389 } 390 391 /** 392 * Write a report of the results to the given appendable. 393 * 394 * @param appendable the appendable to write to. 395 * @throws IOException any IOExceptions while writing. 396 */ 397 public void writeResults(Appendable appendable) throws IOException { 398 appendable.append( 399 "jXMPP Strings Testframework Result\n" + 400 "===================================\n" + 401 "Successful Tests: " + totalSuccessful + '\n' + 402 "Failed Tests : " + totalFailed + '\n' 403 ); 404 if (!wasSuccessful) { 405 appendable.append( 406 "Some tests FAILED! :(\n"); 407 for (ValidJidTestresult.Failed failed : validJidFailedTestresults) { 408 appendable.append(failed.toString()).append('\n'); 409 } 410 for (InvalidJidTestresult.Failed failed : invalidJidFailedTestresults) { 411 appendable.append(failed.toString()).append('\n'); 412 } 413 } 414 } 415 416 @Override 417 public String toString() { 418 StringBuilder sb = new StringBuilder(); 419 try { 420 writeResults(sb); 421 } catch (IOException e) { 422 throw new AssertionError(e); 423 } 424 return sb.toString(); 425 } 426 } 427}