Add CurrentTime utilities 75/55075/2
authorJim Hahn <jrh3@att.com>
Fri, 15 Jun 2018 13:10:00 +0000 (09:10 -0400)
committerJim Hahn <jrh3@att.com>
Tue, 19 Jun 2018 16:35:36 +0000 (12:35 -0400)
Add some comments.  Update license text.

Change-Id: I4cee208378c44d9730274aee337feb95f9026add
Issue-ID: POLICY-908
Signed-off-by: Jim Hahn <jrh3@att.com>
utils-test/pom.xml
utils-test/src/main/java/org/onap/policy/common/utils/time/TestTime.java [new file with mode: 0644]
utils-test/src/main/java/org/onap/policy/common/utils/time/TestTimeMulti.java [new file with mode: 0644]
utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeMultiTest.java [new file with mode: 0644]
utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeTest.java [new file with mode: 0644]
utils/src/main/java/org/onap/policy/common/utils/time/CurrentTime.java [new file with mode: 0644]
utils/src/test/java/org/onap/policy/common/utils/time/CurrentTimeTest.java [new file with mode: 0644]

index a7c2eae..933104b 100644 (file)
                        <groupId>ch.qos.logback</groupId>
                        <artifactId>logback-classic</artifactId>
                </dependency>
+               <dependency>
+                       <groupId>org.onap.policy.common</groupId>
+                       <artifactId>utils</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
        </dependencies>
 
        <build>
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTime.java b/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTime.java
new file mode 100644 (file)
index 0000000..3dfed4b
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.time;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * "Current" time, when running junit tests. This is intended to be injected into classes
+ * under test, to replace their {@link CurrentTime} objects. When {@link #sleep(long)} is
+ * invoked, it simply advances the notion of "current" time and returns immediately.
+ */
+public class TestTime extends CurrentTime {
+
+    /**
+     * "Current" time, in milliseconds, used by tests.
+     */
+    private AtomicLong tcur = new AtomicLong(System.currentTimeMillis());
+
+    /**
+     * 
+     */
+    public TestTime() {
+        super();
+    }
+
+    @Override
+    public long getMillis() {
+        return tcur.get();
+    }
+
+    @Override
+    public Date getDate() {
+        return new Date(tcur.get());
+    }
+
+    @Override
+    public void sleep(long sleepMs) throws InterruptedException {
+        tcur.addAndGet(sleepMs);
+    }
+}
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTimeMulti.java b/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTimeMulti.java
new file mode 100644 (file)
index 0000000..7a8277c
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.time;
+
+import java.util.Date;
+import java.util.PriorityQueue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * "Current" time, when running junit tests in multiple threads. This is intended to be
+ * injected into classes under test, to replace their {@link CurrentTime} objects. The
+ * {@link #sleep(long)} method blocks until all threads enter and then it moves the notion
+ * of "current" time forward, allowing threads to resume, as the end of their sleep time
+ * is reached. Additional threads do not resume until all threads have once again entered
+ * {@link #sleep(long)} or when {@link #threadCompleted()} is invoked to indicate that a
+ * thread will not re-enter {@link #sleep(long)}.
+ */
+public class TestTimeMulti extends CurrentTime {
+
+    /**
+     * Number of threads that will be sleeping simultaneously.
+     */
+    private int nthreads;
+
+    /**
+     * "Current" time, in milliseconds, used by tests.
+     */
+    private long tcur = System.currentTimeMillis();
+
+    /**
+     * Queue of sleeping threads waiting to be awakened.
+     */
+    private final PriorityQueue<Info> queue = new PriorityQueue<>();
+
+    /**
+     * Used to synchronize updates.
+     */
+    private final Object locker = new Object();
+
+    /**
+     * 
+     * @param nthreads number of threads that will be sleeping simultaneously
+     */
+    public TestTimeMulti(int nthreads) {
+        this.nthreads = nthreads;
+    }
+
+    @Override
+    public long getMillis() {
+        return tcur;
+    }
+
+    @Override
+    public Date getDate() {
+        return new Date(tcur);
+    }
+
+    @Override
+    public void sleep(long sleepMs) throws InterruptedException {
+        if (sleepMs <= 0) {
+            return;
+        }
+
+        Info info = new Info(tcur + sleepMs);
+
+        synchronized (locker) {
+            queue.add(info);
+
+            if (queue.size() == nthreads) {
+                // all threads are now sleeping - wake one up
+                wakeThreads();
+            }
+        }
+
+        // this MUST happen outside of the "synchronized" block
+        info.await();
+    }
+
+    /**
+     * Indicates that a thread has terminated or that it will no longer be invoking
+     * {@link #sleep(long)}. Awakens the next sleeping thread, if the queue is full after
+     * removing the terminated thread.
+     * 
+     * @throws IllegalStateException if the queue is already full
+     */
+    public void threadCompleted() {
+        synchronized (locker) {
+            int sz = queue.size();
+            if (sz >= nthreads) {
+                throw new IllegalStateException("too many threads still sleeping");
+            }
+
+            --nthreads;
+
+            if (sz == nthreads) {
+                // after removing terminated thread - queue is now full; awaken something
+                wakeThreads();
+            }
+        }
+    }
+
+    /**
+     * Advances the "current" time and awakens any threads sleeping until that time.
+     */
+    private void wakeThreads() {
+        Info info = queue.poll();
+        if(info == null) {
+            return;
+        }
+
+        tcur = info.getAwakenAtMs();
+        info.wake();
+
+        while ((info = queue.poll()) != null) {
+            if (tcur == info.getAwakenAtMs()) {
+                info.wake();
+
+            } else {
+                // not ready to wake this thread - put it back in the queue
+                queue.add(info);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Info about a sleeping thread.
+     */
+    private static class Info implements Comparable<Info> {
+
+        /**
+         * Time, in milliseconds, at which the associated thread should awaken.
+         */
+        private final long awakenAtMs;
+
+        /**
+         * This is triggered when the associated thread should awaken.
+         */
+        private final CountDownLatch latch = new CountDownLatch(1);
+
+        /**
+         * @param awakenAtMs time, in milliseconds, at which the associated thread should
+         *        awaken
+         */
+        public Info(long awakenAtMs) {
+            this.awakenAtMs = awakenAtMs;
+        }
+
+        public long getAwakenAtMs() {
+            return awakenAtMs;
+        }
+
+        /**
+         * Awakens the associated thread by decrementing its latch.
+         */
+        public void wake() {
+            latch.countDown();
+        }
+
+        /**
+         * Blocks the current thread until awakened (i.e., until its latch is
+         * decremented).
+         * 
+         * @throws InterruptedException
+         */
+        public void await() throws InterruptedException {
+            latch.await();
+        }
+
+        @Override
+        public int compareTo(Info o) {
+            int diff = Long.compare(awakenAtMs, o.awakenAtMs);
+
+            // this assumes that Object.toString() is unique for each Info object
+            if (diff == 0)
+                diff = this.toString().compareTo(o.toString());
+
+            return diff;
+        }
+
+    }
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeMultiTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeMultiTest.java
new file mode 100644 (file)
index 0000000..206ab5f
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.time;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class TestTimeMultiTest {
+
+    private static final int NTHREADS = 10;
+    private static final int NTIMES = 100;
+    private static final long WAIT_SEC = 5L;
+    private static final long MIN_SLEEP_MS = 5L;
+
+    private TestTimeMulti ttm;
+    private Semaphore done;
+
+    @Test
+    public void test() throws Exception {
+        ttm = new TestTimeMulti(NTHREADS);
+        done = new Semaphore(0);
+
+        long tbeg = ttm.getMillis();
+
+        // create threads
+        List<MyThread> threads = new ArrayList<>(NTHREADS);
+        for (int x = 0; x < NTHREADS; ++x) {
+            threads.add(new MyThread(x + MIN_SLEEP_MS));
+        }
+
+        // launch threads
+        for (MyThread thr : threads) {
+            thr.start();
+        }
+
+        // wait for each one to complete
+        for (MyThread thr : threads) {
+            assertTrue("complete " + thr.getSleepMs(), done.tryAcquire(WAIT_SEC, TimeUnit.SECONDS));
+            ttm.threadCompleted();
+        }
+
+        // check results
+        for (MyThread thr : threads) {
+            assertEquals("time " + thr.getSleepMs(), thr.texpected, thr.tactual);
+        }
+
+        assertTrue(ttm.getMillis() >= tbeg + NTIMES * MIN_SLEEP_MS);
+    }
+
+    private class MyThread extends Thread {
+
+        private final long sleepMs;
+
+        private volatile long texpected;
+        private volatile long tactual;
+
+        public MyThread(long sleepMs) {
+            this.sleepMs = sleepMs;
+
+            this.setDaemon(true);
+        }
+
+        public long getSleepMs() {
+            return sleepMs;
+        }
+
+        @Override
+        public void run() {
+            try {
+                for (int x = 0; x < NTIMES; ++x) {
+                    texpected = ttm.getMillis() + sleepMs;
+                    ttm.sleep(sleepMs);
+
+                    if ((tactual = ttm.getMillis()) != texpected) {
+                        break;
+                    }
+
+                    if ((tactual = ttm.getDate().getTime()) != texpected) {
+                        break;
+                    }
+                }
+
+            } catch (InterruptedException expected) {
+                Thread.currentThread().interrupt();
+            }
+
+            done.release();
+        }
+    }
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeTest.java
new file mode 100644 (file)
index 0000000..c1e15b3
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.time;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class TestTimeTest {
+
+    @Test
+    public void test() throws Exception {
+        TestTime tm = new TestTime();
+        TestTime tm2 = new TestTime();
+
+        long treal = System.currentTimeMillis();
+
+        long tcur = tm.getMillis();
+        assertEquals(tcur, tm.getDate().getTime());
+
+        long tsleep = 10000L;
+        long tcur2 = tcur;
+
+        // sleep a bit and then check values
+        tcur2 += tsleep;
+        tm2.sleep(tsleep);
+        assertEquals(tcur2, tm2.getMillis());
+        assertEquals(tcur2, tm2.getDate().getTime());
+
+        // sleep some more and then check values
+        tcur2 += tsleep;
+        tm2.sleep(tsleep);
+        assertEquals(tcur2, tm2.getMillis());
+        assertEquals(tcur2, tm2.getDate().getTime());
+
+        // check again - to ensure unchanged
+        assertEquals(tcur2, tm2.getMillis());
+        assertEquals(tcur2, tm2.getDate().getTime());
+
+        // original should also be unchanged
+        assertEquals(tcur, tm.getMillis());
+        assertEquals(tcur, tm.getDate().getTime());
+
+        // ensure that no real time has elapsed
+        assertTrue(System.currentTimeMillis() < treal + tsleep / 2);
+    }
+
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/time/CurrentTime.java b/utils/src/main/java/org/onap/policy/common/utils/time/CurrentTime.java
new file mode 100644 (file)
index 0000000..cab469e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.time;
+
+import java.util.Date;
+
+/**
+ * Methods to access the current time. Classes can use objects of this type to get current
+ * time information, while allowing the objects to be overridden by junit tests.
+ */
+public class CurrentTime {
+
+    /**
+     * 
+     */
+    public CurrentTime() {
+        super();
+    }
+
+    /**
+     * @return the current time, in milliseconds
+     */
+    public long getMillis() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * @return the current Date
+     */
+    public Date getDate() {
+        return new Date();
+    }
+
+    /**
+     * Sleeps for a period of time.
+     * 
+     * @param sleepMs amount of time to sleep, in milliseconds
+     * @throws InterruptedException
+     */
+    public void sleep(long sleepMs) throws InterruptedException {
+        Thread.sleep(sleepMs);
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/time/CurrentTimeTest.java b/utils/src/test/java/org/onap/policy/common/utils/time/CurrentTimeTest.java
new file mode 100644 (file)
index 0000000..694a3d2
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.time;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class CurrentTimeTest {
+
+    @Test
+    public void testGetMillis() {
+        long tcur = System.currentTimeMillis();
+        long tval = new CurrentTime().getMillis();
+        long tval2 = new CurrentTime().getMillis();
+        long tend = System.currentTimeMillis();
+
+        assertTrue(tval >= tcur && tval <= tend);
+        assertTrue(tval2 >= tcur && tval2 <= tend);
+    }
+
+    @Test
+    public void testGetDate() {
+        long tcur = System.currentTimeMillis();
+        long tval = new CurrentTime().getDate().getTime();
+        long tval2 = new CurrentTime().getDate().getTime();
+        long tend = System.currentTimeMillis();
+
+        assertTrue(tval >= tcur && tval <= tend);
+        assertTrue(tval2 >= tcur && tval2 <= tend);
+    }
+
+    @Test
+    public void testSleep() throws Exception {
+        long tcur = System.currentTimeMillis();
+        new CurrentTime().sleep(10);
+        long tend = System.currentTimeMillis();
+
+        assertTrue(tend >= tcur + 10 - 1);
+    }
+
+}