chromium/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionChecker.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base.test.transit;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.transit.ConditionStatus.Status;

import java.util.ArrayList;
import java.util.List;

/** Spot checks multiple {@link Condition}s to assert preconditions are still valid. */
public class ConditionChecker {

    /** The process of checking a {@link Condition} only once. */
    private static class ConditionCheck {

        private final Condition mCondition;
        private ConditionStatus mStatus;

        private ConditionCheck(Condition condition) {
            mCondition = condition;
        }

        private boolean update() {
            try {
                if (mCondition.isRunOnUiThread()) {
                    // TODO(crbug.com/40284026): Post multiple checks in parallel, the UI thread
                    // will
                    // run them sequentially.
                    mStatus = ThreadUtils.runOnUiThreadBlocking(mCondition::check);
                } else {
                    mStatus = mCondition.check();
                }
            } catch (Exception e) {
                mStatus = Condition.error(e.toString());
            }

            return mStatus.isError() || !mStatus.isFulfilled();
        }

        private ConditionStatus getConditionStatus() {
            return mStatus;
        }
    }

    /**
     * Spot checks each of the {@link Condition}s.
     *
     * @param stateName the name of the state whose conditions we are checking.
     * @param conditions the {@link Condition}s to check.
     * @throws AssertionError if not all Conditions are fulfilled.
     */
    public static void check(String stateName, List<Condition> conditions) {
        boolean anyCriteriaMissing = false;
        List<ConditionCheck> checks = new ArrayList<>();
        for (Condition condition : conditions) {
            checks.add(new ConditionCheck(condition));
        }

        for (ConditionCheck check : checks) {
            anyCriteriaMissing |= check.update();
        }

        if (anyCriteriaMissing) {
            throw buildCheckConditionsException(stateName, checks);
        }
    }

    private static AssertionError buildCheckConditionsException(
            String stateName, List<ConditionCheck> checks) {
        return new AssertionError(
                "Preconditions not fulfilled for "
                        + stateName
                        + ":\n"
                        + createCheckConditionsSummary(checks));
    }

    private static String createCheckConditionsSummary(List<ConditionCheck> checks) {
        StringBuilder detailsString = new StringBuilder();

        int i = 1;
        for (ConditionCheck check : checks) {
            String conditionDescription = check.mCondition.getDescription();

            ConditionStatus status = check.getConditionStatus();
            String verdictString =
                    switch (status.getStatus()) {
                        case Status.FULFILLED -> "[OK  ]";
                        case Status.NOT_FULFILLED -> "[FAIL]";
                        case Status.ERROR -> "[ERR ]";
                        default -> null;
                    };

            StringBuilder historyString = new StringBuilder();
            if (status.getMessage() != null) {
                historyString.append("\n        ");
                historyString.append(status.getMessage());
            }

            detailsString
                    .append("    [")
                    .append(i)
                    .append(" ")
                    .append(verdictString)
                    .append(" ")
                    .append(conditionDescription);
            if (historyString.length() > 0) {
                detailsString.append(historyString);
            }
            detailsString.append('\n');
            i++;
        }
        return detailsString.toString();
    }
}