/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.testng.Assert; import org.testng.TestNG; import org.testng.annotations.Test; import org.testng.annotations.BeforeSuite; import org.testng.annotations.DataProvider; import jdk.test.lib.Platform; import jdk.test.lib.Utils; import sun.misc.Signal; import sun.misc.SignalHandler; /* * @test * @library /test/lib * @modules jdk.unsupported * java.base/jdk.internal.misc * @build jdk.test.lib.Platform jdk.test.lib.Utils * @run testng/othervm -Xrs -DXrs=true SunMiscSignalTest * @run testng/othervm SunMiscSignalTest * @summary sun.misc.Signal test */ @Test public class SunMiscSignalTest { // Set to true to enable additional debug output static boolean debug = true; // True to test while running with -Xrs static boolean RUNNING_WITH_Xrs = Boolean.getBoolean("Xrs"); /** * Print a debug message if enabled. * * @param format the format * @param args the arguments */ static void printf(String format, Object... args) { if (debug) { System.out.printf(" " + format, args); } } enum IsSupported {NO, YES} enum CanRegister {NO, YES} enum CanRaise {NO, YES} enum Invoked {NO, YES} enum RestrictedSignals {NORMAL, XRS} @BeforeSuite static void setup() { System.out.printf("-Xrs: %s%n", RUNNING_WITH_Xrs); } // Provider of signals to be tested with variations for -Xrs and // platform dependencies // -Xrs restricted signals signals the VM will not handle SIGINT, SIGTERM, SIGHUP and others @DataProvider(name = "supportedSignals") static Object[][] supportedSignals() { RestrictedSignals rs = RUNNING_WITH_Xrs ? RestrictedSignals.XRS : RestrictedSignals.NORMAL; CanRegister registerXrs = RUNNING_WITH_Xrs ? CanRegister.NO : CanRegister.YES; CanRaise raiseXrs = RUNNING_WITH_Xrs ? CanRaise.NO : CanRaise.YES; Invoked invokedXrs = RUNNING_WITH_Xrs ? Invoked.NO : Invoked.YES; Object[][] commonSignals = new Object[][]{ {"INT", IsSupported.YES, registerXrs, raiseXrs, invokedXrs}, {"TERM", IsSupported.YES, registerXrs, raiseXrs, invokedXrs}, {"ABRT", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, }; Object[][] posixSignals = { {"HUP", IsSupported.YES, registerXrs, raiseXrs, invokedXrs}, {"QUIT", IsSupported.YES, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"BUS", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"USR1", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"USR2", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"PIPE", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"ALRM", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"CHLD", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"CONT", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"TSTP", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"TTIN", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"TTOU", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"URG", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"XCPU", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"XFSZ", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"VTALRM", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"PROF", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"WINCH", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"IO", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, {"SYS", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, }; Object[][] windowsSignals = { {"HUP", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"QUIT", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"BUS", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"USR1", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"USR2", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"PIPE", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"ALRM", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"CHLD", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"CONT", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"TSTP", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"TTIN", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"TTOU", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"URG", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"XCPU", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"XFSZ", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"VTALRM", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"PROF", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"WINCH", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"IO", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, {"SYS", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, }; return concatArrays(commonSignals, (Platform.isWindows() ? windowsSignals : posixSignals)); } // Provider of invalid signal names @DataProvider(name = "invalidSunMiscSignalNames") Object[][] invalidSunMiscSignalNames() { return new Object[][]{ {""}, {"I"}, {"SIG"}, {"SIGabc"}, {"SIGINT"}, // prefix not allowed {"abc"}, }; } static Object[][] concatArrays(Object[][]... arrays) { int l = 0; for (Object[][] a : arrays) { l += a.length; } Object[][] newArray = new Object[l][]; l = 0; for (int i = 0; i < arrays.length; i++) { System.arraycopy(arrays[i], 0, newArray, l, arrays[i].length); l += arrays[i].length; } return newArray; } // Return true if the signal is one of the shutdown signals known to the VM private static boolean isShutdownSignal(Signal signal) { String name = signal.getName(); return name.equals("INT") || name.equals("HUP") || name.equals("TERM"); } /** * Quick verification of supported signals using sun.misc.Signal. * * @param name the signal name * @throws InterruptedException would be an error if thrown */ @Test(dataProvider = "supportedSignals") static void testSunMisc(String name, IsSupported supported, CanRegister register, CanRaise raise, Invoked invoked) throws InterruptedException { Handler h = new Handler(); SignalHandler orig = null; Signal signal = null; try { signal = new Signal(name); Assert.assertEquals(supported, IsSupported.YES, "Unexpected support for " + name); Assert.assertEquals(signal.getName(), name, "getName() mismatch, "); Assert.assertEquals(signal.toString(), "SIG" + name, "toString() mismatch, "); try { orig = Signal.handle(signal, h); printf("oldHandler: %s%n", orig); Assert.assertEquals(CanRegister.YES, register, "Unexpected handle succeeded " + name); try { Signal.raise(signal); Assert.assertEquals(CanRaise.YES, raise, "Unexpected raise success for " + name); Invoked inv = h.semaphore().tryAcquire(Utils.adjustTimeout(100L), TimeUnit.MILLISECONDS) ? Invoked.YES : Invoked.NO; if (!isShutdownSignal(signal)) { // Normal case Assert.assertEquals(inv, invoked, "handler not invoked;"); } else { if (orig == SignalHandler.SIG_IGN) { Assert.assertEquals(inv, Invoked.NO, "handler should not be invoked"); } else { Assert.assertEquals(inv, invoked, "handler not invoked;"); } } } catch (IllegalArgumentException uoe3) { Assert.assertNotEquals(CanRaise.YES, raise, "raise failed for " + name + ": " + uoe3.getMessage()); } } catch (IllegalArgumentException uoe2) { Assert.assertNotEquals(CanRegister.YES, register, "handle failed for: " + name + ": " + uoe2.getMessage()); } } catch (IllegalArgumentException uoe) { Assert.assertNotEquals(IsSupported.YES, supported, "Support missing for " + name + ": " + uoe.getMessage()); return; } finally { // Restore original signal handler if (orig != null && signal != null) { Signal.handle(signal, orig); } } } // Test Signal is equal to itself and not equals to others @Test(dataProvider = "supportedSignals") static void testEquals(String name, IsSupported supported, CanRegister register, CanRaise raise, Invoked invoked) { Object[][] data = supportedSignals(); for (int i = 0; i < data.length; i++) { IsSupported otherSupported = (IsSupported) data[i][1]; if (supported == IsSupported.NO || otherSupported == IsSupported.NO) { continue; } String otherName = (String) data[i][0]; Signal sig1 = new Signal(name); Signal sig2 = new Signal(otherName); if (name.equals(otherName)) { Assert.assertEquals(sig1, sig2, "Equals failed; "); Assert.assertEquals(sig1.hashCode(), sig2.hashCode(), "HashCode wrong; "); } else { Assert.assertNotEquals(sig1, sig2, "NotEquals failed; "); Assert.assertNotEquals(sig1.hashCode(), sig2.hashCode(), "HashCode wrong; "); } } } @Test(dataProvider = "invalidSunMiscSignalNames") static void testSunMiscIAE(String name) { try { new Signal(name); Assert.fail("Should have thrown IAE for signal: " + name); } catch (IllegalArgumentException iae) { Assert.assertEquals(iae.getMessage(), "Unknown signal: " + name, "getMessage() incorrect; "); } } // Note: JDK 8 did not check/throw NPE, passing null resulted in a segv @Test(expectedExceptions = NullPointerException.class) static void nullSignal() { new Signal(null); } // Test expected exception when raising a signal when no handler defined @Test static void testRaiseNoConsumer() { Signal signal = new Signal("INT"); SignalHandler orig = null; try { orig = Signal.handle(signal, SignalHandler.SIG_DFL); printf("oldHandler: %s%n", orig); if (orig == SignalHandler.SIG_IGN) { // SIG_IGN for TERM means it cannot be handled return; } Signal.raise(signal); Assert.fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { printf("IAE message: %s%n", iae.getMessage()); } finally { // Restore original signal handler if (orig != null && signal != null) { Signal.handle(signal, orig); } } } /** * The thread that runs the handler for sun.misc.Signal should be a * Daemon thread. */ @Test static void isDaemonThread() throws InterruptedException { if (RUNNING_WITH_Xrs) { return; } Handler handler = new Handler(); Signal signal = new Signal("INT"); SignalHandler orig = Signal.handle(signal, handler); printf("oldHandler: %s%n", orig); if (orig == SignalHandler.SIG_IGN) { // SIG_IGN for INT means it cannot be handled return; } Signal.raise(signal); boolean handled = handler.semaphore() .tryAcquire(Utils.adjustTimeout(100L), TimeUnit.MILLISECONDS); if (!handled) { // For debug try again printf("Second try to see signal"); handled = handler.semaphore() .tryAcquire(Utils.adjustTimeout(2L), TimeUnit.SECONDS); } Assert.assertEquals(handled, !RUNNING_WITH_Xrs, "raising s.m.Signal did not get a callback;"); Assert.assertTrue(handler.wasDaemon(), "Thread.isDaemon running the handler; "); } // Check that trying to invoke SIG_DFL.handle throws UnsupportedOperationException. @Test(expectedExceptions = UnsupportedOperationException.class) static void cannotHandleSIGDFL() { Signal signal = new Signal("INT"); Assert.assertNotNull(SignalHandler.SIG_DFL, "SIG_DFL null; "); SignalHandler.SIG_DFL.handle(signal); } // Check that trying to invoke SIG_IGN.handle throws UnsupportedOperationException. @Test(expectedExceptions = UnsupportedOperationException.class) static void cannotHandleSIGIGN() { Signal signal = new Signal("INT"); Assert.assertNotNull(SignalHandler.SIG_IGN, "SIG_IGN null; "); SignalHandler.SIG_IGN.handle(signal); } // Check that setting a Signal handler returns the previous handler. @Test() static void checkLastHandler() { if (RUNNING_WITH_Xrs) { return; } Signal signal = new Signal("TERM"); Handler h1 = new Handler(); Handler h2 = new Handler(); SignalHandler orig = Signal.handle(signal, h1); if (orig == SignalHandler.SIG_IGN) { // SIG_IGN for TERM means it cannot be handled return; } try { SignalHandler prev = Signal.handle(signal, h2); Assert.assertSame(prev, h1, "prev handler mismatch"); prev = Signal.handle(signal, h1); Assert.assertSame(prev, h2, "prev handler mismatch"); } finally { if (orig != null && signal != null) { Signal.handle(signal, orig); } } } /** * Test Handler, a SignalHandler for Signal notifications. * Signals a semaphore when invoked and records whether * the thread calling the Handler was a daemon. */ static class Handler implements SignalHandler { // A semaphore to check for accept being called Semaphore sema = new Semaphore(0); Boolean wasDaemon = null; Semaphore semaphore() { return sema; } synchronized Boolean wasDaemon() { return wasDaemon; } /** * Releases the semaphore when called as SignalHandler.handle. * * @param signal the Signal that occurred */ @Override public void handle(Signal signal) { synchronized (this) { wasDaemon = Thread.currentThread().isDaemon(); } sema.release(); printf("sun.misc.handle sig: %s, num: %d%n", signal.getName(), signal.getNumber()); } public String toString() { return "Handler: sem: " + sema.getQueueLength() + ", wasDaemon: " + Objects.toString(wasDaemon()); } } // Main can be used to run the tests from the command line with only testng.jar. @SuppressWarnings("raw_types") @Test(enabled = false) public static void main(String[] args) { Class[] testclass = {SunMiscSignalTest.class}; TestNG testng = new TestNG(); testng.setTestClasses(testclass); testng.run(); } }