All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.truth.Expect Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.common.truth;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.padStart;
import static com.google.common.base.Strings.repeat;
import static com.google.common.base.Throwables.getStackTraceAsString;
import static com.google.common.truth.Expect.TestPhase.AFTER;
import static com.google.common.truth.Expect.TestPhase.BEFORE;
import static com.google.common.truth.Expect.TestPhase.DURING;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Throwables;
import com.google.common.truth.Truth.SimpleAssertionError;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.junit.internal.AssumptionViolatedException;
import org.junit.rules.ErrorCollector;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * A {@link TestRule} that batches up all failures encountered during a test, and reports them all
 * together at the end (similar to {@link ErrorCollector}). It is also useful for making assertions
 * from other threads or from within callbacks whose exceptions would be swallowed or logged, rather
 * than propagated out to fail the test. (AssertJ has a similar feature called "soft assertions"; however, soft
 * assertions are not safe for concurrent use.)
 *
 * 

Usage: * *

 * {@code @Rule public final Expect expect = Expect.create();}
 *
 * {@code ...}
 *
 * {@code   expect.that(results).containsExactly(...);}
 * {@code   expect.that(errors).isEmpty();}
 * 
* * If both of the assertions above fail, the test will fail with an exception that contains * information about both. * *

{@code Expect} may be used concurrently from multiple threads. However, multithreaded tests * still require care: * *

    *
  • {@code Expect} has no way of knowing when all your other test threads are done. It simply * checks for failures when the main thread finishes executing the test method. Thus, you must * ensure that any background threads complete their assertions before then, or your test may * ignore their results. *
  • Assertion failures are not the only exceptions that may occur in other threads. For maximum * safety, multithreaded tests should check for such exceptions regardless of whether they use * {@code Expect}. (Typically, this means calling {@code get()} on any {@code Future} returned * by a method like {@code executor.submit(...)}. It might also include checking for * unexpected log messages * or reading metrics that count failures.) If your tests already check for exceptions from a * thread, then that will cover any exception from plain {@code assertThat}. *
* *

To record failures for the purpose of testing that an assertion fails when it should, see * {@link ExpectFailure}. * *

For more on this class, see the documentation page. */ @GwtIncompatible("JUnit4") @J2ktIncompatible public final class Expect extends StandardSubjectBuilder implements TestRule { private static final class ExpectationGatherer implements FailureStrategy { @GuardedBy("this") private final List failures = new ArrayList<>(); @GuardedBy("this") private TestPhase inRuleContext = BEFORE; ExpectationGatherer() {} @Override public synchronized void fail(AssertionError failure) { record(failure); } synchronized void enterRuleContext() { checkState(inRuleContext == BEFORE); inRuleContext = DURING; } synchronized void leaveRuleContext(@Nullable Throwable caught) throws Throwable { try { if (caught == null) { doLeaveRuleContext(); } else { doLeaveRuleContext(caught); } /* * We'd like to check this even if an exception was thrown, but we don't want to override * the "real" failure. TODO(cpovirk): Maybe attach as a suppressed exception once we require * a newer version of Android. */ checkState(inRuleContext == DURING); } finally { inRuleContext = AFTER; } } synchronized void checkInRuleContext() { doCheckInRuleContext(null); } synchronized boolean hasFailures() { return !failures.isEmpty(); } @Override public synchronized String toString() { if (failures.isEmpty()) { return "No expectation failed."; } int numFailures = failures.size(); StringBuilder message = new StringBuilder() .append(numFailures) .append(numFailures > 1 ? " expectations" : " expectation") .append(" failed:\n"); int countLength = String.valueOf(failures.size() + 1).length(); int count = 0; for (AssertionError failure : failures) { count++; message.append(" "); message.append(padStart(String.valueOf(count), countLength, ' ')); message.append(". "); if (count == 1) { appendIndented(countLength, message, getStackTraceAsString(failure)); } else { appendIndented( countLength, message, printSubsequentFailure(failures.get(0).getStackTrace(), failure)); } message.append("\n"); } return message.toString(); } // String.repeat is not available under Java 8 and old versions of Android. @SuppressWarnings({"StringsRepeat", "InlineMeInliner"}) private static void appendIndented(int countLength, StringBuilder builder, String toAppend) { int indent = countLength + 4; // " " and ". " builder.append(toAppend.replace("\n", "\n" + repeat(" ", indent))); } private String printSubsequentFailure( StackTraceElement[] baseTraceFrames, AssertionError toPrint) { Exception e = new RuntimeException("__EXCEPTION_MARKER__", toPrint); e.setStackTrace(baseTraceFrames); String s = Throwables.getStackTraceAsString(e); // Force single line reluctant matching return s.replaceFirst("(?s)^.*?__EXCEPTION_MARKER__.*?Caused by:\\s+", ""); } @GuardedBy("this") private void doCheckInRuleContext(@Nullable AssertionError failure) { switch (inRuleContext) { case BEFORE: throw new IllegalStateException( "assertion made on Expect instance, but it's not enabled as a @Rule.", failure); case DURING: return; case AFTER: throw new IllegalStateException( "assertion made on Expect instance, but its @Rule has already completed. Maybe " + "you're making assertions from a background thread and not waiting for them to " + "complete, or maybe you've shared an Expect instance across multiple tests? " + "We're throwing this exception to warn you that your assertion would have been " + "ignored. However, this exception might not cause any test to fail, or it " + "might cause some subsequent test to fail rather than the test that caused the " + "problem.", failure); } throw new AssertionError(); } @GuardedBy("this") private void doLeaveRuleContext() { if (hasFailures()) { throw SimpleAssertionError.createWithNoStack(this.toString()); } } @GuardedBy("this") private void doLeaveRuleContext(Throwable caught) throws Throwable { if (hasFailures()) { String message = caught instanceof AssumptionViolatedException ? "Also, after those failures, an assumption was violated:" : "Also, after those failures, an exception was thrown:"; record(SimpleAssertionError.createWithNoStack(message, caught)); throw SimpleAssertionError.createWithNoStack(this.toString()); } else { throw caught; } } @GuardedBy("this") private void record(AssertionError failure) { doCheckInRuleContext(failure); failures.add(failure); } } private final ExpectationGatherer gatherer; /** Creates a new instance. */ public static Expect create() { return new Expect(new ExpectationGatherer()); } private Expect(ExpectationGatherer gatherer) { super(FailureMetadata.forFailureStrategy(gatherer)); this.gatherer = checkNotNull(gatherer); } public boolean hasFailures() { return gatherer.hasFailures(); } @Override void checkStatePreconditions() { gatherer.checkInRuleContext(); } @Override public Statement apply(Statement base, Description description) { checkNotNull(base); checkNotNull(description); return new Statement() { @Override public void evaluate() throws Throwable { gatherer.enterRuleContext(); Throwable caught = null; try { base.evaluate(); } catch (Throwable t) { caught = t; } finally { gatherer.leaveRuleContext(caught); } } }; } enum TestPhase { BEFORE, DURING, AFTER; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy