Home | History | Annotate | Download | only in rules
      1 package org.robolectric.junit.rules;
      2 
      3 import android.util.Log;
      4 import com.google.common.collect.ImmutableSet;
      5 import java.util.HashSet;
      6 import java.util.List;
      7 import java.util.Set;
      8 import org.junit.rules.TestRule;
      9 import org.junit.runner.Description;
     10 import org.junit.runners.model.Statement;
     11 import org.robolectric.shadows.ShadowLog;
     12 import org.robolectric.shadows.ShadowLog.LogItem;
     13 
     14 /**
     15  * Allows tests to assert about the presence of log messages, and turns logged errors that are not
     16  * explicitly expected into test failures.
     17  */
     18 public final class ExpectedLogMessagesRule implements TestRule {
     19   /** Tags that apps can't prevent. We whitelist them globally. */
     20   private static final ImmutableSet<String> UNPREVENTABLE_TAGS =
     21       ImmutableSet.of("Typeface", "RingtoneManager");
     22 
     23   private final Set<LogItem> expectedLogs = new HashSet<>();
     24   private final Set<LogItem> observedLogs = new HashSet<>();
     25   private final Set<String> expectedTags = new HashSet<>();
     26   private final Set<String> observedTags = new HashSet<>();
     27 
     28   private boolean shouldIgnoreMissingLoggedTags = false;
     29 
     30   @Override
     31   public Statement apply(final Statement base, Description description) {
     32     return new Statement() {
     33       @Override
     34       public void evaluate() throws Throwable {
     35         base.evaluate();
     36         List<LogItem> logs = ShadowLog.getLogs();
     37         for (LogItem log : logs) {
     38           LogItem throwLessLogItem = new LogItem(log.type, log.tag, log.msg, null);
     39           if (expectedLogs.contains(throwLessLogItem)) {
     40             observedLogs.add(throwLessLogItem);
     41             continue;
     42           }
     43           if (log.type >= Log.ERROR) {
     44             if (UNPREVENTABLE_TAGS.contains(log.tag)) {
     45               continue;
     46             }
     47             if (expectedTags.contains(log.tag)) {
     48               observedTags.add(log.tag);
     49               continue;
     50             }
     51             if (log.throwable != null) {
     52               throw new AssertionError(
     53                   "Unexpected error log message: " + log.tag + ": " + log.msg, log.throwable);
     54             } else {
     55               throw new AssertionError("Unexpected error log message: " + log.tag + ": " + log.msg);
     56             }
     57           }
     58         }
     59         if (!expectedLogs.equals(observedLogs)) {
     60           throw new AssertionError(
     61               "Some expected logs were not printed."
     62                   + "\nExpected: "
     63                   + expectedLogs
     64                   + "\nObserved: "
     65                   + observedLogs);
     66         }
     67         if (!expectedTags.equals(observedTags) && !shouldIgnoreMissingLoggedTags) {
     68           throw new AssertionError(
     69               "Some expected tags were not printed. "
     70                   + "Expected tags should not be used to suppress errors, only expect them."
     71                   + "\nExpected: "
     72                   + expectedTags
     73                   + "\nObserved: "
     74                   + observedTags);
     75         }
     76       }
     77     };
     78   }
     79 
     80   /**
     81    * Adds an expected log statement. If this log is not printed during test execution, the test case
     82    * will fail. Do not use this to suppress failures. Use this to test that expected error cases in
     83    * your code cause log messages to be printed.
     84    */
     85   public void expectLogMessage(int level, String tag, String message) {
     86     checkTag(tag);
     87     expectedLogs.add(new LogItem(level, tag, message, null));
     88   }
     89 
     90   /**
     91    * Blanket suppress test failures due to errors from a tag. If this tag is not printed at
     92    * Log.ERROR during test execution, the test case will fail (unless {@link
     93    * #ignoreMissingLoggedTags()} is used).
     94    *
     95    * <p>Avoid using this method when possible. Prefer to assert on the presence of a specific
     96    * message using {@link #expectLogMessage} in test cases that *intentionally* trigger an error.
     97    */
     98   public void expectErrorsForTag(String tag) {
     99     checkTag(tag);
    100     if (UNPREVENTABLE_TAGS.contains(tag)) {
    101       throw new AssertionError("Tag `" + tag + "` is already suppressed.");
    102     }
    103     expectedTags.add(tag);
    104   }
    105 
    106   /**
    107    * If set true, tests that call {@link #expectErrorsForTag()} but do not log errors for the given
    108    * tag will not fail. By default this is false.
    109    *
    110    * <p>Avoid using this method when possible. Prefer tests that print (or do not print) log
    111    * messages deterministically.
    112    */
    113   public void ignoreMissingLoggedTags(boolean shouldIgnore) {
    114     shouldIgnoreMissingLoggedTags = shouldIgnore;
    115   }
    116 
    117   private void checkTag(String tag) {
    118     if (tag.length() > 23) {
    119       throw new IllegalArgumentException("Tag length cannot exceed 23 characters: " + tag);
    120     }
    121   }
    122 }
    123