1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Objects;
29import java.util.concurrent.Semaphore;
30import java.util.concurrent.TimeUnit;
31
32import org.testng.Assert;
33import org.testng.TestNG;
34import org.testng.annotations.Test;
35import org.testng.annotations.BeforeSuite;
36import org.testng.annotations.DataProvider;
37
38import jdk.test.lib.Platform;
39import jdk.test.lib.Utils;
40
41import sun.misc.Signal;
42import sun.misc.SignalHandler;
43
44/*
45 * @test
46 * @library /test/lib
47 * @modules jdk.unsupported
48 *          java.base/jdk.internal.misc
49 * @build jdk.test.lib.Utils
50 *        jdk.test.lib.Asserts
51 *        jdk.test.lib.JDKToolFinder
52 *        jdk.test.lib.JDKToolLauncher
53 *        jdk.test.lib.Platform
54 *        jdk.test.lib.process.*
55 * @run testng/othervm -Xrs -DXrs=true SunMiscSignalTest
56 * @run testng/othervm SunMiscSignalTest
57 * @summary sun.misc.Signal test
58 */
59
60@Test
61public class SunMiscSignalTest {
62
63    // Set to true to enable additional debug output
64    static boolean debug = true;
65
66    // True to test while running with -Xrs
67    static boolean RUNNING_WITH_Xrs = Boolean.getBoolean("Xrs");
68
69    /**
70     * Print a debug message if enabled.
71     *
72     * @param format the format
73     * @param args   the arguments
74     */
75    static void printf(String format, Object... args) {
76        if (debug) {
77            System.out.printf("    " + format, args);
78        }
79    }
80
81    enum IsSupported {NO, YES}
82
83    enum CanRegister {NO, YES}
84
85    enum CanRaise {NO, YES}
86
87    enum Invoked {NO, YES}
88
89    enum RestrictedSignals {NORMAL, XRS}
90
91    @BeforeSuite
92    static void setup() {
93        System.out.printf("-Xrs: %s%n", RUNNING_WITH_Xrs);
94    }
95
96    // Provider of signals to be tested with variations for -Xrs and
97    // platform dependencies
98    // -Xrs restricted signals signals the VM will not handle SIGINT, SIGTERM, SIGHUP and others
99    @DataProvider(name = "supportedSignals")
100    static Object[][] supportedSignals() {
101        RestrictedSignals rs = RUNNING_WITH_Xrs ? RestrictedSignals.XRS : RestrictedSignals.NORMAL;
102        CanRegister registerXrs = RUNNING_WITH_Xrs ? CanRegister.NO : CanRegister.YES;
103        CanRaise raiseXrs = RUNNING_WITH_Xrs ? CanRaise.NO : CanRaise.YES;
104        Invoked invokedXrs = RUNNING_WITH_Xrs ? Invoked.NO : Invoked.YES;
105
106        Object[][] commonSignals = new Object[][]{
107                {"INT",  IsSupported.YES, registerXrs, raiseXrs, invokedXrs},
108                {"TERM", IsSupported.YES, registerXrs, raiseXrs, invokedXrs},
109                {"ABRT", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
110        };
111
112        Object[][] posixSignals = {
113                {"HUP",  IsSupported.YES, registerXrs, raiseXrs, invokedXrs},
114                {"QUIT", IsSupported.YES, CanRegister.NO, CanRaise.NO, Invoked.NO},
115                {"BUS",  IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
116                {"USR1", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
117                {"USR2", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
118                {"PIPE", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
119                {"ALRM", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
120                {"CHLD", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
121                {"CONT", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
122                {"TSTP", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
123                {"TTIN", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
124                {"TTOU", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
125                {"URG",  IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
126                {"XCPU", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
127                {"XFSZ", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
128                {"VTALRM", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
129                {"PROF", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
130                {"WINCH", IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
131                {"IO",   IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
132                {"SYS",   IsSupported.YES, CanRegister.YES, CanRaise.YES, invokedXrs},
133        };
134
135        Object[][] windowsSignals = {
136                {"HUP",  IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
137                {"QUIT", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
138                {"BUS",  IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
139                {"USR1", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
140                {"USR2", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
141                {"PIPE", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
142                {"ALRM", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
143                {"CHLD", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
144                {"CONT", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
145                {"TSTP", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
146                {"TTIN", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
147                {"TTOU", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
148                {"URG",  IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
149                {"XCPU", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
150                {"XFSZ", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
151                {"VTALRM", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
152                {"PROF", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
153                {"WINCH", IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
154                {"IO",   IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
155                {"SYS",  IsSupported.NO, CanRegister.NO, CanRaise.NO, Invoked.NO},
156        };
157
158        return concatArrays(commonSignals, (Platform.isWindows() ? windowsSignals : posixSignals));
159    }
160
161    // Provider of invalid signal names
162    @DataProvider(name = "invalidSunMiscSignalNames")
163    Object[][] invalidSunMiscSignalNames() {
164        return new Object[][]{
165                {""},
166                {"I"},
167                {"SIG"},
168                {"SIGabc"},
169                {"SIGINT"},     // prefix not allowed
170                {"abc"},
171        };
172    }
173
174    static Object[][] concatArrays(Object[][]... arrays) {
175        int l = 0;
176        for (Object[][] a : arrays) {
177            l += a.length;
178        }
179
180        Object[][] newArray = new Object[l][];
181        l = 0;
182        for (int i = 0; i < arrays.length; i++) {
183            System.arraycopy(arrays[i], 0, newArray, l, arrays[i].length);
184            l += arrays[i].length;
185        }
186
187        return newArray;
188    }
189
190    // Return true if the signal is one of the shutdown signals known to the VM
191    private static boolean isShutdownSignal(Signal signal) {
192        String name = signal.getName();
193        return name.equals("INT") || name.equals("HUP") || name.equals("TERM");
194    }
195
196    /**
197     * Quick verification of supported signals using sun.misc.Signal.
198     *
199     * @param name the signal name
200     * @throws InterruptedException would be an error if thrown
201     */
202    @Test(dataProvider = "supportedSignals")
203    static void testSunMisc(String name, IsSupported supported, CanRegister register,
204                            CanRaise raise, Invoked invoked) throws InterruptedException {
205        Handler h = new Handler();
206        SignalHandler orig = null;
207        Signal signal = null;
208        try {
209            signal = new Signal(name);
210            Assert.assertEquals(supported, IsSupported.YES, "Unexpected support for " + name);
211
212            Assert.assertEquals(signal.getName(), name, "getName() mismatch, ");
213
214            Assert.assertEquals(signal.toString(), "SIG" + name, "toString() mismatch, ");
215
216            try {
217                orig = Signal.handle(signal, h);
218                printf("oldHandler: %s%n", orig);
219                Assert.assertEquals(CanRegister.YES, register, "Unexpected handle succeeded " + name);
220                try {
221                    Signal.raise(signal);
222                    Assert.assertEquals(CanRaise.YES, raise, "Unexpected raise success for " + name);
223                    Invoked inv = h.semaphore().tryAcquire(Utils.adjustTimeout(100L),
224                            TimeUnit.MILLISECONDS) ? Invoked.YES : Invoked.NO;
225                    if (!isShutdownSignal(signal)) {
226                        // Normal case
227                        Assert.assertEquals(inv, invoked, "handler not invoked;");
228                    } else {
229                        if (orig == SignalHandler.SIG_IGN) {
230                            Assert.assertEquals(inv, Invoked.NO, "handler should not be invoked");
231                        } else {
232                            Assert.assertEquals(inv, invoked, "handler not invoked;");
233                        }
234                    }
235                } catch (IllegalArgumentException uoe3) {
236                    Assert.assertNotEquals(CanRaise.YES, raise, "raise failed for " + name +
237                            ": " + uoe3.getMessage());
238                }
239            } catch (IllegalArgumentException uoe2) {
240                Assert.assertNotEquals(CanRegister.YES, register, "handle failed for: " + name +
241                        ": " + uoe2.getMessage());
242            }
243        } catch (IllegalArgumentException uoe) {
244            Assert.assertNotEquals(IsSupported.YES, supported, "Support missing for " + name +
245                    ": " + uoe.getMessage());
246            return;
247        } finally {
248            // Restore original signal handler
249            if (orig != null && signal != null) {
250                Signal.handle(signal, orig);
251            }
252        }
253    }
254
255    // Test Signal is equal to itself and not equals to others
256    @Test(dataProvider = "supportedSignals")
257    static void testEquals(String name, IsSupported supported, CanRegister register,
258                           CanRaise raise, Invoked invoked) {
259        Object[][] data = supportedSignals();
260        for (int i = 0; i < data.length; i++) {
261            IsSupported otherSupported = (IsSupported) data[i][1];
262            if (supported == IsSupported.NO || otherSupported == IsSupported.NO) {
263                continue;
264            }
265            String otherName = (String) data[i][0];
266
267            Signal sig1 = new Signal(name);
268            Signal sig2 = new Signal(otherName);
269            if (name.equals(otherName)) {
270                Assert.assertEquals(sig1, sig2, "Equals failed; ");
271                Assert.assertEquals(sig1.hashCode(), sig2.hashCode(), "HashCode wrong; ");
272            } else {
273                Assert.assertNotEquals(sig1, sig2, "NotEquals failed; ");
274                Assert.assertNotEquals(sig1.hashCode(), sig2.hashCode(), "HashCode wrong; ");
275            }
276        }
277    }
278
279    @Test(dataProvider = "invalidSunMiscSignalNames")
280    static void testSunMiscIAE(String name) {
281        try {
282            new Signal(name);
283            Assert.fail("Should have thrown IAE for signal: " + name);
284        } catch (IllegalArgumentException iae) {
285            Assert.assertEquals(iae.getMessage(), "Unknown signal: " + name, "getMessage() incorrect; ");
286        }
287    }
288
289    // Note: JDK 8 did not check/throw NPE, passing null resulted in a segv
290    @Test(expectedExceptions = NullPointerException.class)
291    static void nullSignal() {
292        new Signal(null);
293    }
294
295    // Test expected exception when raising a signal when no handler defined
296    @Test
297    static void testRaiseNoConsumer() {
298        Signal signal = new Signal("INT");
299        SignalHandler orig = null;
300        try {
301            orig = Signal.handle(signal, SignalHandler.SIG_DFL);
302            printf("oldHandler: %s%n", orig);
303            if (orig == SignalHandler.SIG_IGN) {
304                // SIG_IGN for TERM means it cannot be handled
305                return;
306            }
307            Signal.raise(signal);
308            Assert.fail("Should have thrown IllegalArgumentException");
309        } catch (IllegalArgumentException iae) {
310            printf("IAE message: %s%n", iae.getMessage());
311        } finally {
312            // Restore original signal handler
313            if (orig != null && signal != null) {
314                Signal.handle(signal, orig);
315            }
316        }
317    }
318
319    /**
320     * The thread that runs the handler for sun.misc.Signal should be a
321     * Daemon thread.
322     */
323    @Test
324    static void isDaemonThread() throws InterruptedException {
325        if (RUNNING_WITH_Xrs) {
326            return;
327        }
328        Handler handler = new Handler();
329        Signal signal = new Signal("INT");
330        SignalHandler orig = Signal.handle(signal, handler);
331        printf("oldHandler: %s%n", orig);
332        if (orig == SignalHandler.SIG_IGN) {
333            // SIG_IGN for INT means it cannot be handled
334            return;
335        }
336
337        Signal.raise(signal);
338        boolean handled = handler.semaphore()
339                .tryAcquire(Utils.adjustTimeout(100L), TimeUnit.MILLISECONDS);
340        if (!handled) {
341            // For debug try again
342            printf("Second try to see signal");
343            handled = handler.semaphore()
344                    .tryAcquire(Utils.adjustTimeout(2L), TimeUnit.SECONDS);
345        }
346        Assert.assertEquals(handled, !RUNNING_WITH_Xrs,
347                "raising s.m.Signal did not get a callback;");
348
349        Assert.assertTrue(handler.wasDaemon(), "Thread.isDaemon running the handler; ");
350    }
351
352    // Check that trying to invoke SIG_DFL.handle throws UnsupportedOperationException.
353    @Test(expectedExceptions = UnsupportedOperationException.class)
354    static void cannotHandleSIGDFL() {
355        Signal signal = new Signal("INT");
356        Assert.assertNotNull(SignalHandler.SIG_DFL, "SIG_DFL null; ");
357        SignalHandler.SIG_DFL.handle(signal);
358    }
359
360    // Check that trying to invoke SIG_IGN.handle throws UnsupportedOperationException.
361    @Test(expectedExceptions = UnsupportedOperationException.class)
362    static void cannotHandleSIGIGN() {
363        Signal signal = new Signal("INT");
364        Assert.assertNotNull(SignalHandler.SIG_IGN, "SIG_IGN null; ");
365        SignalHandler.SIG_IGN.handle(signal);
366    }
367
368    // Check that setting a Signal handler returns the previous handler.
369    @Test()
370    static void checkLastHandler() {
371        if (RUNNING_WITH_Xrs) {
372            return;
373        }
374        Signal signal = new Signal("TERM");
375        Handler h1 = new Handler();
376        Handler h2 = new Handler();
377        SignalHandler orig = Signal.handle(signal, h1);
378        if (orig == SignalHandler.SIG_IGN) {
379            // SIG_IGN for TERM means it cannot be handled
380            return;
381        }
382
383        try {
384            SignalHandler prev = Signal.handle(signal, h2);
385            Assert.assertSame(prev, h1, "prev handler mismatch");
386
387            prev = Signal.handle(signal, h1);
388            Assert.assertSame(prev, h2, "prev handler mismatch");
389        } finally {
390            if (orig != null && signal != null) {
391                Signal.handle(signal, orig);
392            }
393        }
394    }
395
396    /**
397     * Test Handler, a SignalHandler for Signal notifications.
398     * Signals a semaphore when invoked and records whether
399     * the thread calling the Handler was a daemon.
400     */
401    static class Handler implements SignalHandler {
402        // A semaphore to check for accept being called
403        Semaphore sema = new Semaphore(0);
404
405        Boolean wasDaemon = null;
406
407        Semaphore semaphore() {
408            return sema;
409        }
410
411        synchronized Boolean wasDaemon() {
412            return wasDaemon;
413        }
414
415        /**
416         * Releases the semaphore when called as SignalHandler.handle.
417         *
418         * @param signal the Signal that occurred
419         */
420        @Override
421        public void handle(Signal signal) {
422            synchronized (this) {
423                wasDaemon = Thread.currentThread().isDaemon();
424            }
425            sema.release();
426            printf("sun.misc.handle sig: %s, num: %d%n", signal.getName(), signal.getNumber());
427        }
428
429        public String toString() {
430            return "Handler: sem: " + sema.getQueueLength() +
431                    ", wasDaemon: " + Objects.toString(wasDaemon());
432        }
433    }
434
435    // Main can be used to run the tests from the command line with only testng.jar.
436    @SuppressWarnings("raw_types")
437    @Test(enabled = false)
438    public static void main(String[] args) {
439        Class<?>[] testclass = {SunMiscSignalTest.class};
440        TestNG testng = new TestNG();
441        testng.setTestClasses(testclass);
442        testng.run();
443    }
444
445}
446