/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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.coretests;

import java.lang.reflect.Method;

import junit.framework.Protectable;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import dalvik.annotation.KnownFailure;
import dalvik.annotation.SideEffect;

/**
 * A special TestResult implementation that is able to filter out annotated
 * tests and handles our known failures properly (expects them to fail).
 * Handy when running the Core Libraries tests on Android, the bare-metal
 * Dalvik VM, or the RI.
 */
public class CoreTestResult extends TestResult {

    /**
     * The flags the user specified for this test run.
     */
    protected int fFlags;

    /**
     * The timeout the user specified for this test run.
     */
    protected int fTimeout;

    /**
     * The total number of tests in the original suite.
     */
    protected int fTotalTestCount;

    /**
     * The number of Android-only tests in the original suite.
     */
    protected int fAndroidOnlyCount;

    /**
     * The number of broken tests in the original suite.
     */
    protected int fBrokenTestCount;

    /**
     * The number of known failures in the original suite.
     */
    protected int fKnownFailureCount;

    /**
     * The number of side-effective tests in the original suite.
     */
    protected int fSideEffectCount;

    /**
     * The number of normal (non-annotated) tests in the original suite.
     */
    protected int fNormalTestCount;

    /**
     * The number of ignored tests, that is, the number of tests that were
     * excluded from this suite due to their annotations.
     */
    protected int fIgnoredCount;

    /**
     * Creates a new CoreTestResult with the given flags and timeout.
     */
    public CoreTestResult(int flags, int timeout) {
        super();

        fFlags = flags;
        fTimeout = timeout;
    }

    /**
     * Checks whether the given TestCase method has the given annotation.
     */
    @SuppressWarnings("unchecked")
    boolean hasAnnotation(TestCase test, Class clazz) {
        try {
            Method method = test.getClass().getMethod(test.getName());
            return method.getAnnotation(clazz) != null;
        } catch (Exception e) {
            // Ignore
        }

        return false;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void runProtected(final Test test, Protectable p) {
        if ((fFlags & CoreTestSuite.DRY_RUN) == 0) {
            if (test instanceof TestCase) {
                TestCase testCase = (TestCase)test;

                // Check whether we need to invert the test result (known failures)
                boolean invert = hasAnnotation(testCase, KnownFailure.class) &&
                        (fFlags & CoreTestSuite.INVERT_KNOWN_FAILURES) != 0;

                // Check whether we need to isolate the test (side effects)
                boolean isolate = hasAnnotation(testCase, SideEffect.class) &&
                        (fFlags & CoreTestSuite.ISOLATE_NONE) == 0 ||
                        (fFlags & CoreTestSuite.ISOLATE_ALL) != 0;

                CoreTestRunnable runnable = new CoreTestRunnable(
                        testCase, this, p, invert, isolate);

                if (fTimeout > 0) {
                    Thread thread = new Thread(runnable);
                    thread.start();
                    try {
                        thread.join(fTimeout * 1000);
                    } catch (InterruptedException ex) {
                        // Ignored
                    }
                    if (thread.isAlive()) {
                        StackTraceElement[] trace = thread.getStackTrace();
                        runnable.stop();
                        thread.stop();
                        try {
                            thread.join(fTimeout * 1000);
                        } catch (InterruptedException ex) {
                            // Ignored
                        }

                        CoreTestTimeout timeout = new CoreTestTimeout("Test timed out");
                        timeout.setStackTrace(trace);
                        addError(test, timeout);
                    }
                } else {
                    runnable.run();
                }
            }
        }
    }

    /**
     * Updates the statistics in this TestResult. Called from the TestSuite,
     * since, once the original suite has been filtered, we don't actually see
     * these tests here anymore.
     */
    void updateStats(int total, int androidOnly, int broken, int knownFailure,
            int normal, int ignored, int sideEffect) {

        this.fTotalTestCount += total;
        this.fAndroidOnlyCount += androidOnly;
        this.fBrokenTestCount += broken;
        this.fKnownFailureCount += knownFailure;
        this.fNormalTestCount += normal;
        this.fIgnoredCount += ignored;
        this.fSideEffectCount += sideEffect;
    }
}
