Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.autofillservice.cts;
     18 
     19 import androidx.annotation.NonNull;
     20 import android.util.Log;
     21 
     22 import org.junit.rules.TestRule;
     23 import org.junit.runner.Description;
     24 import org.junit.runners.model.Statement;
     25 
     26 import java.io.IOException;
     27 import java.io.PrintWriter;
     28 import java.io.StringWriter;
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 import java.util.concurrent.Callable;
     32 
     33 /**
     34  * Rule used to safely run clean up code after a test is finished, so that exceptions thrown by
     35  * the cleanup code don't hide exception thrown by the test body
     36  */
     37 // TODO: move to common CTS code
     38 public final class SafeCleanerRule implements TestRule {
     39 
     40     private static final String TAG = "SafeCleanerRule";
     41 
     42     private final List<Runnable> mCleaners = new ArrayList<>();
     43     private final List<Callable<List<Throwable>>> mExtraThrowables = new ArrayList<>();
     44     private final List<Throwable> mThrowables = new ArrayList<>();
     45     private Dumper mDumper;
     46 
     47     /**
     48      * Runs {@code cleaner} after the test is finished, catching any {@link Throwable} thrown by it.
     49      */
     50     public SafeCleanerRule run(@NonNull Runnable cleaner) {
     51         mCleaners.add(cleaner);
     52         return this;
     53     }
     54 
     55     /**
     56      * Adds exceptions directly.
     57      *
     58      * <p>Typically used when exceptions were caught asychronously during the test execution.
     59      */
     60     public SafeCleanerRule add(@NonNull Callable<List<Throwable>> exceptions) {
     61         mExtraThrowables.add(exceptions);
     62         return this;
     63     }
     64 
     65     /**
     66      * Sets a {@link Dumper} used to log errors.
     67      */
     68     public SafeCleanerRule setDumper(@NonNull Dumper dumper) {
     69         mDumper = dumper;
     70         return this;
     71     }
     72 
     73     @Override
     74     public Statement apply(Statement base, Description description) {
     75         return new Statement() {
     76             @Override
     77             public void evaluate() throws Throwable {
     78                 // First run the test
     79                 try {
     80                     base.evaluate();
     81                 } catch (Throwable t) {
     82                     Log.w(TAG, "Adding exception from main test");
     83                     mThrowables.add(t);
     84                 }
     85 
     86                 // Then the cleanup runners
     87                 for (Runnable runner : mCleaners) {
     88                     try {
     89                         runner.run();
     90                     } catch (Throwable t) {
     91                         Log.w(TAG, "Adding exception from cleaner");
     92                         mThrowables.add(t);
     93                     }
     94                 }
     95 
     96                 // And finally add the extra exceptions
     97                 for (Callable<List<Throwable>> extraThrowablesCallable : mExtraThrowables) {
     98                     final List<Throwable> extraThrowables = extraThrowablesCallable.call();
     99                     if (extraThrowables != null) {
    100                         Log.w(TAG, "Adding " + extraThrowables.size() + " extra exceptions");
    101                         mThrowables.addAll(extraThrowables);
    102                     }
    103                 }
    104 
    105                 // Finally, throw up!
    106                 if (mThrowables.isEmpty()) return;
    107 
    108                 final int numberExceptions = mThrowables.size();
    109                 if (numberExceptions == 1) {
    110                     fail(description, mThrowables.get(0));
    111                 }
    112                 fail(description, new MultipleExceptions(mThrowables));
    113             }
    114 
    115         };
    116     }
    117 
    118     private void fail(Description description, Throwable t) throws Throwable {
    119         if (mDumper != null) {
    120             mDumper.dump(description.getDisplayName(), t);
    121         }
    122         throw t;
    123     }
    124 
    125     private static String toMesssage(List<Throwable> throwables) {
    126         String msg = "D'OH!";
    127         try {
    128             try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
    129                 sw.write("Caught " + throwables.size() + " exceptions\n");
    130                 for (int i = 0; i < throwables.size(); i++) {
    131                     sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
    132                     final Throwable exception = throwables.get(i);
    133                     exception.printStackTrace(pw);
    134                     sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
    135                 }
    136                 msg = sw.toString();
    137             }
    138         } catch (IOException e) {
    139             // ignore close() errors - should not happen...
    140             Log.e(TAG, "Exception closing StringWriter: " + e);
    141         }
    142         return msg;
    143     }
    144 
    145     // VisibleForTesting
    146     static class MultipleExceptions extends AssertionError {
    147         private final List<Throwable> mThrowables;
    148 
    149         private MultipleExceptions(List<Throwable> throwables) {
    150             super(toMesssage(throwables));
    151 
    152             this.mThrowables = throwables;
    153         }
    154 
    155         List<Throwable> getThrowables() {
    156             return mThrowables;
    157         }
    158     }
    159 
    160     /**
    161      * Optional interface used to dump an error.
    162      */
    163     public interface Dumper {
    164 
    165         /**
    166          * Dumps an error.
    167          */
    168         void dump(@NonNull String testName, @NonNull Throwable t);
    169     }
    170 }
    171