// Copyright 2015 The Bazel Authors. 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. package com.google.devtools.build.lib.skyframe; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import com.google.devtools.build.lib.skyframe.ActionExecutionInactivityWatchdog.InactivityMonitor; import com.google.devtools.build.lib.skyframe.ActionExecutionInactivityWatchdog.InactivityReporter; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.List; /** Tests for ActionExecutionInactivityWatchdog. */ @RunWith(JUnit4.class) public class ActionExecutionInactivityWatchdogTest { private void assertInactivityWatchdogReports(final boolean shouldReport) throws Exception { // The monitor implementation below is a state machine. This variable indicates which state // it is in. final int[] monitorState = new int[] {0}; // Object that the test thread will wait on. final Object monitorFinishedIndicator = new Object(); // Reported number of action completions in each call to waitForNextCompletion. final int[] actionCompletions = new int[] {1, 0, 3, 0, 0, 0, 0, 2}; // Simulated delay of action completions in each call to waitForNextCompletion. final int[] waits = new int[] {5, 10, 3, 10, 30, 60, 60, 1}; // Log of all Sleep.sleep and InactivityMonitor.waitForNextCompletion calls. final List<String> sleepsAndWaits = new ArrayList<>(); // Mock monitor for this test. InactivityMonitor monitor = new InactivityMonitor() { @Override public int waitForNextCompletion(int timeoutMilliseconds) throws InterruptedException { // Simulate the following sequence of events (see actionCompletions): // 1. return in 5s (within timeout), 1 action completed; caller will sleep // 2. return in 10s (after timeout), 0 action completed; caller will wait // 3. return in 3s (within timeout), 3 actions completed (this is possible, since the // waiting (thread doesn't necessarily wake up immediately); caller will sleep // 4. return in 10s (after timeout), 0 action completed; caller will wait 30s // 5. return in 30s (after timeout), 0 action completed still; caller will wait 60s // 6. return in 60s (after timeout), 0 action completed still; caller will wait 60s // 7. return in 60s (after timeout), 0 action completed still; caller will wait 60s // 8. return in 1s (within timeout), 2 actions completed; caller will sleep, but we // won't record that, because monitorState reached its maximum synchronized (monitorFinishedIndicator) { if (monitorState[0] >= actionCompletions.length) { // Notify the test thread that the test is over. monitorFinishedIndicator.notify(); return 1; } else { int index = monitorState[0]; sleepsAndWaits.add("wait:" + waits[index]); ++monitorState[0]; return actionCompletions[index]; } } } @Override public boolean hasStarted() { return true; } @Override public int getPending() { int index = monitorState[0]; if (index >= actionCompletions.length) { return 0; } int result = actionCompletions[index]; while (result == 0) { ++index; result = actionCompletions[index]; } return result; } }; final boolean[] didReportInactivity = new boolean[] {false}; InactivityReporter reporter = new InactivityReporter() { @Override public void maybeReportInactivity() { if (shouldReport) { didReportInactivity[0] = true; } } }; // Mock sleep object; just logs how much the caller's thread would've slept. ActionExecutionInactivityWatchdog.Sleep sleep = new ActionExecutionInactivityWatchdog.Sleep() { @Override public void sleep(int durationMilliseconds) throws InterruptedException { if (monitorState[0] < actionCompletions.length) { sleepsAndWaits.add("sleep:" + durationMilliseconds); } } }; ActionExecutionInactivityWatchdog watchdog = new ActionExecutionInactivityWatchdog(monitor, reporter, 0, sleep); try { synchronized (monitorFinishedIndicator) { watchdog.start(); long startTime = System.currentTimeMillis(); boolean done = false; while (!done) { try { monitorFinishedIndicator.wait(5000); done = true; assertWithMessage("test didn't finish under 5 seconds") .that(System.currentTimeMillis() - startTime) .isLessThan(5000L); } catch (InterruptedException ie) { // so-called Spurious Wakeup; ignore } } } } finally { watchdog.stop(); } assertEquals(shouldReport, didReportInactivity[0]); assertThat(sleepsAndWaits) .containsExactly( "wait:5", "sleep:1000", "wait:10", "wait:3", "sleep:1000", "wait:10", "wait:30", "wait:60", "wait:60", "wait:1") .inOrder(); } @Test public void testInactivityWatchdogReportsWhenItShould() throws Exception { assertInactivityWatchdogReports(true); } @Test public void testInactivityWatchdogDoesNotReportWhenItShouldNot() throws Exception { assertInactivityWatchdogReports(false); } }