// 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.shell; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests of the interaction of Thread.interrupt and Command.execute. * * Read http://www.ibm.com/developerworks/java/library/j-jtp05236/ * for background material. * * NOTE: This test is dependent on thread timings. Under extreme machine load * it's possible that this test could fail spuriously or intermittently. In * that case, adjust the timing constants to increase the tolerance. */ @RunWith(JUnit4.class) public class InterruptibleTest { private final Thread mainThread = Thread.currentThread(); // Interrupt main thread after 1 second. Hopefully by then /bin/sleep // should be running. private final Thread interrupter = new Thread() { @Override public void run() { try { Thread.sleep(1000); // 1 sec } catch (InterruptedException e) { throw new IllegalStateException("Unexpected interrupt!"); } mainThread.interrupt(); } }; private Command command; @Before public void setUp() throws Exception { Thread.interrupted(); // side effect: clear interrupted status assertFalse("Unexpected interruption!", mainThread.isInterrupted()); // We interrupt after 1 sec, so this gives us plenty of time for the library to notice the // subprocess exit. this.command = new Command(new String[] { "/bin/sleep", "20" }); interrupter.start(); } @After public void tearDown() throws Exception { interrupter.join(); Thread.interrupted(); // Clear interrupted status, or else other tests may fail. } /** * Test that interrupting a thread in an "uninterruptible" Command.execute * preserves the thread's interruptible status, and does not terminate the * subprocess. */ @Test public void testUninterruptibleCommandRunsToCompletion() throws Exception { command.execute(); // The interrupter thread should have exited about 1000ms ago. assertFalse("Interrupter thread is still alive!", interrupter.isAlive()); // The interrupter thread should have set the main thread's interrupt flag. assertTrue("Main thread was not interrupted during command execution!", mainThread.isInterrupted()); } /** * Test that interrupting a thread in an "interruptible" Command.execute * causes preserves the thread's interruptible status, terminates the * subprocess, and returns promptly. */ @Test public void testInterruptibleCommand() throws Exception { try { command.execute(Command.NO_INPUT, Command.NO_OBSERVER, System.out, System.err, true); // => interruptible fail("Subprocess not aborted!"); } catch (AbnormalTerminationException e) { assertEquals("Process terminated by signal 15", // SIGINT e.getMessage()); } // We don't assert that the interrupter thread has exited; due to prompt // termination it might still be running. // The interrupter thread should have set the main thread's interrupt flag. assertTrue("Main thread was not interrupted during command execution!", mainThread.isInterrupted()); } }