| /* | |
| * Copyright (c) 2016, 2019, 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. | |
| * | |
| * 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.Utils | |
| * jdk.test.lib.Asserts | |
| * jdk.test.lib.JDKToolFinder | |
| * jdk.test.lib.JDKToolLauncher | |
| * jdk.test.lib.Platform | |
| * jdk.test.lib.process.* | |
| * @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 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}, | |
| {"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[][] posixNonOSXSignals = { | |
| {"BUS", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs}, | |
| {"INFO", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO}, | |
| }; | |
| Object[][] posixOSXSignals = { | |
| {"BUS", IsSupported.YES, CanRegister.NO, CanRaise.NO, Invoked.NO}, | |
| {"INFO", 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}, | |
| }; | |
| Object[][] combinedPosixSignals = concatArrays(posixSignals, | |
| (Platform.isOSX() ? posixOSXSignals : posixNonOSXSignals)); | |
| return concatArrays(commonSignals, (Platform.isWindows() ? windowsSignals : combinedPosixSignals)); | |
| } | |
| // 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(); | |
| } | |
| } |