1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.test.transit; 6 7 import org.chromium.base.ThreadUtils; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 12 /** Spot checks multiple {@link Condition}s to assert preconditions are still valid. */ 13 public class ConditionChecker { 14 15 /** The fulfillment status of a {@link Condition} being checked once. */ 16 private static class ConditionCheckStatus { 17 18 private final Condition mCondition; 19 private boolean mFulfilled; 20 private String mError; 21 ConditionCheckStatus(Condition condition)22 private ConditionCheckStatus(Condition condition) { 23 mCondition = condition; 24 } 25 update()26 private boolean update() { 27 try { 28 boolean fulfilled; 29 if (mCondition.isRunOnUiThread()) { 30 // TODO(crbug.com/1489445): Post multiple checks in parallel, the UI thread will 31 // run them sequentially. 32 fulfilled = ThreadUtils.runOnUiThreadBlocking(mCondition::check); 33 } else { 34 fulfilled = mCondition.check(); 35 } 36 37 if (fulfilled) { 38 reportFulfilled(); 39 return false; 40 } else { 41 reportUnfulfilled(); 42 return true; 43 } 44 } catch (Exception e) { 45 reportError(e); 46 return true; 47 } 48 } 49 reportFulfilled()50 private void reportFulfilled() { 51 mFulfilled = true; 52 } 53 reportUnfulfilled()54 private void reportUnfulfilled() { 55 mFulfilled = false; 56 } 57 reportError(Exception e)58 private void reportError(Exception e) { 59 mError = e.getMessage(); 60 } 61 isFulfilled()62 private boolean isFulfilled() { 63 return mFulfilled; 64 } 65 getError()66 private String getError() { 67 return mError; 68 } 69 } 70 71 /** 72 * Spot checks each of the {@link Condition}s. 73 * 74 * @param conditions the {@link Condition}s to check. 75 * @throws AssertionError if not all Conditions are fulfilled. 76 */ check(List<Condition> conditions)77 public static void check(List<Condition> conditions) { 78 boolean anyCriteriaMissing = false; 79 List<ConditionCheckStatus> checkStatuses = new ArrayList<>(); 80 for (Condition condition : conditions) { 81 checkStatuses.add(new ConditionCheckStatus(condition)); 82 } 83 84 for (ConditionCheckStatus status : checkStatuses) { 85 anyCriteriaMissing |= status.update(); 86 } 87 88 if (anyCriteriaMissing) { 89 throw buildCheckConditionsException(checkStatuses); 90 } 91 } 92 buildCheckConditionsException( List<ConditionCheckStatus> checkStatuses)93 private static AssertionError buildCheckConditionsException( 94 List<ConditionCheckStatus> checkStatuses) { 95 return new AssertionError( 96 "Preconditions not fulfilled:\n" + createCheckConditionsSummary(checkStatuses)); 97 } 98 createCheckConditionsSummary(List<ConditionCheckStatus> checkStatuses)99 private static String createCheckConditionsSummary(List<ConditionCheckStatus> checkStatuses) { 100 StringBuilder detailsString = new StringBuilder(); 101 102 int i = 1; 103 for (ConditionCheckStatus checkStatus : checkStatuses) { 104 String conditionDescription = checkStatus.mCondition.getDescription(); 105 106 String error = checkStatus.getError(); 107 String errorsString = null; 108 String statusString; 109 if (error != null) { 110 errorsString = String.format(" {error: %s}", error); 111 statusString = "[ERR ]"; 112 } else { 113 if (checkStatus.isFulfilled()) { 114 statusString = "[OK ]"; 115 } else { 116 statusString = "[FAIL]"; 117 } 118 } 119 120 detailsString 121 .append(" [") 122 .append(i) 123 .append(" ") 124 .append(statusString) 125 .append(" ") 126 .append(conditionDescription); 127 if (errorsString != null) { 128 detailsString.append(" ").append(errorsString); 129 } 130 detailsString.append('\n'); 131 i++; 132 } 133 return detailsString.toString(); 134 } 135 } 136