/* * Copyright 2015-2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.timers; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Collection; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import ccre.channel.CancelOutput; import ccre.channel.EventOutput; import ccre.log.LogLevel; import ccre.log.VerifyingLogger; import ccre.scheduler.VirtualTime; @SuppressWarnings("javadoc") @RunWith(Parameterized.class) public class TickerTest { private static final String ERROR_STRING = "Ticker purposeful failure."; private Ticker ticker; private final int period; private final boolean fixedRate; private int counter; private final EventOutput cb = () -> { synchronized (TickerTest.this) { counter++; } }; public TickerTest(int period, boolean fixedRate) { this.period = period; this.fixedRate = fixedRate; } @Before public void setUp() { VirtualTime.startFakeTime(); ticker = new Ticker(period, fixedRate); counter = 0; VerifyingLogger.begin(); } @Parameters public static Collection<Object[]> generateData() { ArrayList<Object[]> out = new ArrayList<>(); // the different timings here aren't really relevant - this just tests // more birds with two stones. out.add(new Object[] { 770, false }); out.add(new Object[] { 760, true }); return out; } @After public void tearDown() { VerifyingLogger.checkAndEnd(); ticker.terminate(); ticker = null; VirtualTime.endFakeTime(); } private synchronized void check(int ctr) throws InterruptedException { if (ctr != counter) { fail("expected <" + ctr + "> but got <" + counter + "> in " + period); } } @Test public void testCounting() throws InterruptedException { start(cb); for (int i = 0; i < 4; i++) { VirtualTime.forward(period); } check(4); } private CancelOutput start(EventOutput eo) throws InterruptedException { return ticker.send(eo); } @Test public void testSlowCounting() throws InterruptedException { if (period > 1) { start(cb); for (int i = 0; i < 15; i++) { VirtualTime.forward(period / 2); VirtualTime.forward(period - (period / 2)); } check(15); } } @Test public void testFastCounting() throws InterruptedException { start(cb); VirtualTime.forward(period * 20); // skipping time that fast doesn't work well with this version of Ticker // - that's what the option is for! check(fixedRate ? 20 : 1); // flaky } @Test public void testUnbind() throws InterruptedException { CancelOutput unbind = start(cb); for (int i = 0; i < 20; i++) { VirtualTime.forward(period); } check(20); unbind.cancel(); VirtualTime.forward(period * 10); check(20);// nothing more for (int i = 0; i < 5; i++) { VirtualTime.forward(period); } check(20);// nothing more } @Test public void testRebind() throws InterruptedException { start(cb).cancel();// bind and unbind start(cb);// rebind for (int i = 0; i < 5; i++) { VirtualTime.forward(period); } check(5); } @Test public void testLoopAFewErrors() throws InterruptedException { start(() -> { synchronized (TickerTest.this) { counter++; if (counter < 6) { throw new RuntimeException(ERROR_STRING); } } }); for (int i = 0; i < 20; i++) { if (i < 5) { VerifyingLogger.configure(LogLevel.SEVERE, "Top-level failure in scheduled event", (t) -> t.getClass() == RuntimeException.class && ERROR_STRING.equals(t.getMessage())); } VirtualTime.forward(period); VerifyingLogger.check(); } check(20); } @Test public void testLoopScatteredErrors() throws InterruptedException { start(() -> { synchronized (TickerTest.this) { counter++; if (counter % 10 == 0) { throw new RuntimeException(ERROR_STRING); } } }); for (int i = 0; i < 70; i++) { if (i % 10 == 9) { VerifyingLogger.configure(LogLevel.SEVERE, "Top-level failure in scheduled event", (t) -> t.getClass() == RuntimeException.class && ERROR_STRING.equals(t.getMessage())); } VirtualTime.forward(period); VerifyingLogger.check(); } check(70); } }