Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 384da4bc authored by Makoto Onuki's avatar Makoto Onuki
Browse files

More precise test summary

Now ravenwood-test-summary *should* match the atest output -- unless
the test uses junit3 based tests, or @NoRavenizer.

Flag: EXEMPT host test change only
Bug: 292141694
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh
    and run ./scripts/ravenwood-test-summary
    Also do this after breaking a test
Change-Id: I3c5c67416ff9f1eca0c194485b366741bd0829e8
parent 6050abf7
Loading
Loading
Loading
Loading
+3 −17
Original line number Diff line number Diff line
@@ -26,12 +26,10 @@ import android.annotation.Nullable;
import android.os.Bundle;
import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.annotations.internal.InnerRunner;
import android.platform.test.ravenwood.RavenwoodTestStats.Result;
import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
@@ -171,10 +169,11 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
        final var notifier = new RavenwoodRunNotifier(realNotifier);
        final var description = getDescription();

        RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);

        if (mRealRunner instanceof ClassSkippingTestRunner) {
            mRealRunner.run(notifier);
            Log.i(TAG, "onClassSkipped: description=" + description);
            RavenwoodTestStats.getInstance().onClassSkipped(description);
            mRealRunner.run(notifier);
            return;
        }

@@ -205,7 +204,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase

            if (!skipRunnerHook) {
                try {
                    RavenwoodTestStats.getInstance().onClassFinished(description);
                    mState.exitTestClass();
                } catch (Throwable th) {
                    notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
        // method-level annotations here.
        if (scope == Scope.Instance && order == Order.Outer) {
            if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
                RavenwoodTestStats.getInstance().onTestFinished(
                        classDescription, description, Result.Skipped);
                return false;
            }
        }
@@ -317,16 +313,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
            // End of a test method.
            mState.exitTestMethod();

            final Result result;
            if (th == null) {
                result = Result.Passed;
            } else if (th instanceof AssumptionViolatedException) {
                result = Result.Skipped;
            } else {
                result = Result.Failed;
            }

            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
        }

        // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
+2 −2
Original line number Diff line number Diff line
@@ -199,7 +199,7 @@ public class RavenwoodRuntimeEnvironmentController {
     */
    public static void init(RavenwoodAwareTestRunner runner) {
        if (RAVENWOOD_VERBOSE_LOGGING) {
            Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
            Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
        }
        if (sRunner == runner) {
            return;
@@ -310,7 +310,7 @@ public class RavenwoodRuntimeEnvironmentController {
     */
    public static void reset() {
        if (RAVENWOOD_VERBOSE_LOGGING) {
            Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
            Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
        }
        if (sRunner == null) {
            throw new RavenwoodRuntimeException("Internal error: reset() already called");
+109 −52
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ package android.platform.test.ravenwood;
import android.util.Log;

import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;

import java.io.File;
import java.io.IOException;
@@ -27,7 +30,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
@@ -39,7 +42,7 @@ import java.util.Map;
 */
public class RavenwoodTestStats {
    private static final String TAG = "RavenwoodTestStats";
    private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
    private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";

    private static RavenwoodTestStats sInstance;

@@ -66,7 +69,7 @@ public class RavenwoodTestStats {
    private final PrintWriter mOutputWriter;
    private final String mTestModuleName;

    public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
    public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();

    /** Ctor */
    public RavenwoodTestStats() {
@@ -115,75 +118,129 @@ public class RavenwoodTestStats {
        return cwd.getName();
    }

    private void addResult(Description classDescription, Description methodDescription,
    private void addResult(String className, String methodName,
            Result result) {
        mStats.compute(classDescription, (classDesc, value) -> {
        mStats.compute(className, (className_, value) -> {
            if (value == null) {
                value = new HashMap<>();
                value = new LinkedHashMap<>();
            }
            // If the result is already set, don't overwrite it.
            if (!value.containsKey(methodName)) {
                value.put(methodName, result);
            }
            value.put(methodDescription, result);
            return value;
        });
    }

    /**
     * Call it when a test class is skipped.
     */
    public void onClassSkipped(Description classDescription) {
        addResult(classDescription, Description.EMPTY, Result.Skipped);
        onClassFinished(classDescription);
    }

    /**
     * Call it when a test method is finished.
     */
    public void onTestFinished(Description classDescription, Description testDescription,
            Result result) {
        addResult(classDescription, testDescription, result);
    private void onTestFinished(String className, String testName, Result result) {
        addResult(className, testName, result);
    }

    /**
     * Call it when a test class is finished.
     * Dump all the results and clear it.
     */
    public void onClassFinished(Description classDescription) {
    private void dumpAllAndClear() {
        for (var entry : mStats.entrySet()) {
            int passed = 0;
            int skipped = 0;
            int failed = 0;
        var stats = mStats.get(classDescription);
        if (stats == null) {
            return;
        }
        for (var e : stats.values()) {
            var className = entry.getKey();

            for (var e : entry.getValue().values()) {
                switch (e) {
                case Passed: passed++; break;
                case Skipped: skipped++; break;
                case Failed: failed++; break;
                    case Passed:
                        passed++;
                        break;
                    case Skipped:
                        skipped++;
                        break;
                    case Failed:
                        failed++;
                        break;
                }
            }

        var testClass = extractTestClass(classDescription);

            mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
                mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
                classDescription, passed, failed, skipped);
                    mTestModuleName, className, getOuterClassName(className),
                    passed, failed, skipped);
        }
        mOutputWriter.flush();
        mStats.clear();
    }

    /**
     * Try to extract the class from a description, which is needed because
     * ParameterizedAndroidJunit4's description doesn't contain a class.
     */
    private Class<?> extractTestClass(Description desc) {
        if (desc.getTestClass() != null) {
            return desc.getTestClass();
    private static String getOuterClassName(String className) {
        // Just delete the '$', because I'm not sure if the className we get here is actaully a
        // valid class name that does exist. (it might have a parameter name, etc?)
        int p = className.indexOf('$');
        if (p < 0) {
            return className;
        }
        return className.substring(0, p);
    }
        // Look into the children.
        for (var child : desc.getChildren()) {
            var fromChild = extractTestClass(child);
            if (fromChild != null) {
                return fromChild;

    public void attachToRunNotifier(RunNotifier notifier) {
        notifier.addListener(mRunListener);
    }

    private final RunListener mRunListener = new RunListener() {
        @Override
        public void testSuiteStarted(Description description) {
            Log.d(TAG, "testSuiteStarted: " + description);
        }

        @Override
        public void testSuiteFinished(Description description) {
            Log.d(TAG, "testSuiteFinished: " + description);
        }
        return null;

        @Override
        public void testRunStarted(Description description) {
            Log.d(TAG, "testRunStarted: " + description);
        }

        @Override
        public void testRunFinished(org.junit.runner.Result result) {
            Log.d(TAG, "testRunFinished: " + result);

            dumpAllAndClear();
        }

        @Override
        public void testStarted(Description description) {
            Log.d(TAG, "  testStarted: " + description);
        }

        @Override
        public void testFinished(Description description) {
            Log.d(TAG, "  testFinished: " + description);

            // Send "Passed", but if there's already another result sent for this, this won't
            // override it.
            onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
        }

        @Override
        public void testFailure(Failure failure) {
            Log.d(TAG, "    testFailure: " + failure);

            var description = failure.getDescription();
            onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
        }

        @Override
        public void testAssumptionFailure(Failure failure) {
            Log.d(TAG, "    testAssumptionFailure: " + failure);
            var description = failure.getDescription();
            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
        }

        @Override
        public void testIgnored(Description description) {
            Log.d(TAG, "    testIgnored: " + description);
            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
        }
    };
}