chromium/base/test/android/javatests/src/org/chromium/base/FakeTimeTestRule.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base;

import androidx.annotation.GuardedBy;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import org.chromium.base.TimeUtils.FakeClock;

/** Causes fake times to be used in TimeUtils. */
public class FakeTimeTestRule implements TestRule {
    private final Object mLock = new Object();

    // Milliseconds since booted, excluding deep sleep.
    @GuardedBy("mLock")
    private long mUptimeMillis;

    // Nanoseconds since booted, including deep sleep.
    @GuardedBy("mLock")
    private long mElapsedRealtimeNanos;

    // Per-thread CPU time.
    @GuardedBy("mLock")
    private ThreadLocal<Long> mThreadTimes;

    // epoch time.
    @GuardedBy("mLock")
    private long mCurrentTimeMillis;

    /** Resets to default time values. */
    public void resetTimes() {
        synchronized (mLock) {
            mUptimeMillis = 10000;
            mElapsedRealtimeNanos = 20000L * TimeUtils.NANOSECONDS_PER_MILLISECOND;
            mCurrentTimeMillis = 1653000000000L; // May 19 2022 18:40:00 GMT-0400

            mThreadTimes =
                    new ThreadLocal<Long>() {
                        @Override
                        protected Long initialValue() {
                            return 0L;
                        }
                    };
        }
    }

    private final TimeUtils.FakeClock mFakeClock =
            new FakeClock() {
                @Override
                public long uptimeMillis() {
                    synchronized (mLock) {
                        return mUptimeMillis;
                    }
                }

                @Override
                public long elapsedRealtimeNanos() {
                    synchronized (mLock) {
                        return mElapsedRealtimeNanos;
                    }
                }

                @Override
                public long currentThreadTimeMillis() {
                    synchronized (mLock) {
                        return mThreadTimes.get();
                    }
                }

                @Override
                public long currentTimeMillis() {
                    synchronized (mLock) {
                        return mCurrentTimeMillis;
                    }
                }
            };

    /** Advances uptime, elapsedRealtime, and the current thread's threadTime.. */
    public void advanceMillis(long increment) {
        assert increment > 0 : "Negative increment: " + increment;
        synchronized (mLock) {
            mCurrentTimeMillis += increment;
            mUptimeMillis += increment;
            mElapsedRealtimeNanos += increment * TimeUtils.NANOSECONDS_PER_MILLISECOND;
            mThreadTimes.set(mThreadTimes.get() + increment);
        }
    }

    /** Advances uptime and elapsedRealtime. */
    public void sleepMillis(long duration) {
        assert duration > 0 : "Negative duration: " + duration;
        synchronized (mLock) {
            mCurrentTimeMillis += duration;
            mUptimeMillis += duration;
            mElapsedRealtimeNanos += duration * TimeUtils.NANOSECONDS_PER_MILLISECOND;
        }
    }

    /** Advances elapsedRealtime. */
    public void deepSleepMillis(long duration) {
        assert duration > 0 : "Negative duration: " + duration;
        synchronized (mLock) {
            mCurrentTimeMillis += duration;
            mElapsedRealtimeNanos += duration * TimeUtils.NANOSECONDS_PER_MILLISECOND;
        }
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    resetTimes();
                    TimeUtils.sFakeClock = mFakeClock;
                    base.evaluate();
                } finally {
                    TimeUtils.sFakeClock = null;
                }
            }
        };
    }
}