diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index c4270fbe40b..bb008e81742 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -3401,7 +3401,7 @@ encode %{ } %} - /// mov envcodings + // mov encodings enc_class aarch64_enc_movw_imm(iRegI dst, immI src) %{ C2_MacroAssembler _masm(&cbuf); diff --git a/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp b/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp index 119cbbd79c5..a6354dc6a7c 100644 --- a/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp +++ b/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, 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 @@ -226,6 +226,16 @@ class RunExecutorWithMsgLoop { }; +void enableConsoleCtrlHandler(bool enable) { + if (!SetConsoleCtrlHandler(NULL, enable ? FALSE : TRUE)) { + JP_THROW(SysError(tstrings::any() << "SetConsoleCtrlHandler(NULL, " + << (enable ? "FALSE" : "TRUE") + << ") failed", + SetConsoleCtrlHandler)); + } +} + + void launchApp() { // [RT-31061] otherwise UI can be left in back of other windows. ::AllowSetForegroundWindow(ASFW_ANY); @@ -279,6 +289,19 @@ void launchApp() { exec.arg(arg); }); + exec.afterProcessCreated([&](HANDLE pid) { + // + // Ignore Ctrl+C in the current process. + // This will prevent child process termination without allowing + // it to handle Ctrl+C events. + // + // Disable the default Ctrl+C handler *after* the child process + // has been created as it is inheritable and we want the child + // process to have the default handler. + // + enableConsoleCtrlHandler(false); + }); + DWORD exitCode = RunExecutorWithMsgLoop::apply(exec); exit(exitCode); diff --git a/src/jdk.jpackage/windows/native/common/Executor.cpp b/src/jdk.jpackage/windows/native/common/Executor.cpp index edb850afdbb..dfb6b299e5d 100644 --- a/src/jdk.jpackage/windows/native/common/Executor.cpp +++ b/src/jdk.jpackage/windows/native/common/Executor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -161,6 +161,10 @@ UniqueHandle Executor::startProcess(UniqueHandle* threadHandle) const { } } + if (afterProcessCreatedCallback) { + afterProcessCreatedCallback(processInfo.hProcess); + } + // Return process handle. return UniqueHandle(processInfo.hProcess); } diff --git a/src/jdk.jpackage/windows/native/common/Executor.h b/src/jdk.jpackage/windows/native/common/Executor.h index a6edcbd4f76..09c9f85bac6 100644 --- a/src/jdk.jpackage/windows/native/common/Executor.h +++ b/src/jdk.jpackage/windows/native/common/Executor.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -26,6 +26,8 @@ #ifndef EXECUTOR_H #define EXECUTOR_H +#include + #include "tstrings.h" #include "UniqueHandle.h" @@ -97,6 +99,14 @@ class Executor { */ int execAndWaitForExit() const; + /** + * Call provided function after the process hass been created. + */ + Executor& afterProcessCreated(const std::function& v) { + afterProcessCreatedCallback = v; + return *this; + } + private: UniqueHandle startProcess(UniqueHandle* threadHandle=0) const; @@ -106,6 +116,7 @@ class Executor { HANDLE jobHandle; tstring_array argsArray; std::wstring appPath; + std::function afterProcessCreatedCallback; }; #endif // #ifndef EXECUTOR_H diff --git a/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c b/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c index 26d46ddaeac..8b3e6c3ea0f 100644 --- a/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c +++ b/src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -50,6 +50,8 @@ #include #pragma warning(pop) +#include + typedef unsigned __int32 juint; typedef unsigned __int64 julong; @@ -215,10 +217,10 @@ static PdhLookupPerfNameByIndexFunc PdhLookupPerfNameByIndex_i; */ typedef struct { HQUERY query; - uint64_t lastUpdate; // Last time query was updated (ticks) + uint64_t lastUpdate; // Last time query was updated (millis) } UpdateQueryS, *UpdateQueryP; -// Min time between query updates (ticks) +// Min time between query updates (millis) static const int MIN_UPDATE_INTERVAL = 500; /* @@ -993,7 +995,7 @@ bindPdhFunctionPointers(HMODULE h) { */ static int getPerformanceData(UpdateQueryP query, HCOUNTER c, PDH_FMT_COUNTERVALUE* value, DWORD format) { - clock_t now = clock(); + uint64_t now = GetTickCount64(); /* * Need to limit how often we update the query diff --git a/test/hotspot/jtreg/runtime/Thread/TooSmallStackSize.java b/test/hotspot/jtreg/runtime/Thread/TooSmallStackSize.java index 09db906b151..6fd1da6f2ae 100644 --- a/test/hotspot/jtreg/runtime/Thread/TooSmallStackSize.java +++ b/test/hotspot/jtreg/runtime/Thread/TooSmallStackSize.java @@ -28,6 +28,7 @@ * VMThreadStackSize values should result in an error message that shows * the minimum stack size value for each thread type. * @library /test/lib + * @requires vm.flagless * @modules java.base/jdk.internal.misc * java.management * @run driver TooSmallStackSize diff --git a/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest.java b/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest.java new file mode 100644 index 00000000000..0468f222774 --- /dev/null +++ b/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016, 2024, 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. + */ + +/* + * @test + * @bug 8169355 + * @summary Check if Spanish diacritical signs could be typed for TextField + * @requires (os.family == "windows") + * @library /java/awt/regtesthelpers + * @run main/manual SpanishDiacriticsTest +*/ + + +import java.util.concurrent.locks.LockSupport; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import javax.swing.JFrame; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; + +public class SpanishDiacriticsTest { + + static final String INSTRUCTIONS = """ + This test requires the following keyboard layout to be installed: + Windows OS: Spanish (United States) with 'Latin American' keyboard layout. + If using a US layout, the results should still be as described but + you have not tested the real bug. + + 1. A frame with a text field should be displayed. + 2. Set focus to the text field and switch to Spanish + with 'Latin American' keyboard layout. + 3. Type the following: ' ' o - i.e. single quote two times, then o. + If your keyboard has a US physical layout the [ key can be used + to type the single quote when in 'Latin American' keyboard mode. + 4. Type these characters at a normal speed but do NOT be concerned + that they take several seconds to display. That is an + expected behaviour for this test. + + If the text field displays the same three characters you typed: ''o + (i.e. two single quotes followed by o without an acute) + then press Pass; otherwise press Fail. + """; + + public static void main(String[] args) throws Exception { + + PassFailJFrame.builder() + .title("Spanish Diacritics") + .instructions(INSTRUCTIONS) + .rows(20) + .columns(50) + .testUI(SpanishDiacriticsTest::createTestUI) + .build() + .awaitAndCheck(); + } + + static JFrame createTestUI() { + JFrame frame = new JFrame("Spanish Diacritics Test Frame"); + JTextField textField = new JTextField(20); + textField.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + LockSupport.parkNanos(1_000_000_000L); + } + }); + frame.add(textField); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.pack(); + return frame; + } +} + diff --git a/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest/SpanishDiacriticsTest.html b/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest/SpanishDiacriticsTest.html deleted file mode 100644 index af6cd0c7610..00000000000 --- a/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest/SpanishDiacriticsTest.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - SpanishDiacriticsTest - - - - -Test run requires the following keyboard layout to be installed: -Windows OS: Spanish (United States) with 'Latin American' keyboard layout - -1. A frame with a text field should be displayed at upper left corner -2. Set focus to the text field and switch to Spanish with 'Latin American' keyboard layout -3. Type the following: ' ' o - i.e. single quote two times (using [ key on US keyboard) then o character. - -If you the text field displays ''o, (i.e. o should be without acute) then the test is passed; otherwise failed. - - diff --git a/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest/SpanishDiacriticsTest.java b/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest/SpanishDiacriticsTest.java deleted file mode 100644 index 78877061e60..00000000000 --- a/test/jdk/java/awt/InputMethods/SpanishDiacriticsTest/SpanishDiacriticsTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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. - * - * 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. - */ - -/* - * @test - * @bug 8169355 - * @summary Check if Spanish diacritical signs could be typed for TextField - * @author Dmitry Markov - * @run applet/manual=yesno SpanishDiacriticsTest.html -*/ - -import javax.swing.*; -import java.applet.Applet; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.concurrent.locks.LockSupport; - -public class SpanishDiacriticsTest extends Applet { - @Override - public void init() { - SwingUtilities.invokeLater(() -> { - JFrame frame = new JFrame(); - JTextField textField = new JTextField(20); - textField.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - LockSupport.parkNanos(1_000_000_000L); - } - }); - frame.add(textField); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.pack(); - frame.setVisible(true); - }); - } -} - diff --git a/test/jdk/java/awt/List/MouseDraggedOriginatedByScrollBarTest.java b/test/jdk/java/awt/List/MouseDraggedOriginatedByScrollBarTest.java index 600d38fe393..6858359d6b4 100644 --- a/test/jdk/java/awt/List/MouseDraggedOriginatedByScrollBarTest.java +++ b/test/jdk/java/awt/List/MouseDraggedOriginatedByScrollBarTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -24,41 +24,46 @@ /* * @test * @bug 6240151 + * @key headful * @summary XToolkit: Dragging the List scrollbar initiates DnD - * @library /java/awt/regtesthelpers - * @build PassFailJFrame - * @run main/manual MouseDraggedOriginatedByScrollBarTest + * @requires os.family == "linux" + * @run main MouseDraggedOriginatedByScrollBarTest */ +import java.awt.EventQueue; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.List; -import java.awt.event.MouseMotionAdapter; +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; public class MouseDraggedOriginatedByScrollBarTest { - - private static final String INSTRUCTIONS = """ - 1) Click and drag the scrollbar of the list. - 2) Keep dragging till the mouse pointer goes out the scrollbar. - 3) The test failed if you see messages about events. The test passed if you don't."""; + private static Frame frame; + private static volatile Point loc; + private static List list; + private static final int XOFFSET = 10; + private static final int YOFFSET = 20; public static void main(String[] args) throws Exception { - PassFailJFrame.builder() - .title("MouseDraggedOriginatedByScrollBarTest Instructions") - .instructions(INSTRUCTIONS) - .rows((int) INSTRUCTIONS.lines().count() + 2) - .columns(35) - .testUI(MouseDraggedOriginatedByScrollBarTest::createTestUI) - .logArea() - .build() - .awaitAndCheck(); + try { + EventQueue.invokeAndWait(() -> createUI()); + test(); + } finally { + EventQueue.invokeAndWait(() -> { + if (frame != null) { + frame.dispose(); + } + }); + } } - private static Frame createTestUI() { - Frame frame = new Frame(); - List list = new List(4, false); + private static void createUI() { + frame = new Frame(); + list = new List(4, false); list.add("000"); list.add("111"); @@ -77,27 +82,52 @@ private static Frame createTestUI() { new MouseMotionAdapter(){ @Override public void mouseDragged(MouseEvent me){ - PassFailJFrame.log(me.toString()); + System.out.println(me); + throw new RuntimeException("Mouse dragged event detected."); } }); list.addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent me) { - PassFailJFrame.log(me.toString()); + System.out.println(me); + throw new RuntimeException("Mouse pressed event detected."); } public void mouseReleased(MouseEvent me) { - PassFailJFrame.log(me.toString()); + System.out.println(me); + throw new RuntimeException("Mouse released event detected."); } public void mouseClicked(MouseEvent me){ - PassFailJFrame.log(me.toString()); + System.out.println(me); + throw new RuntimeException("Mouse clicked event detected."); } }); frame.setLayout(new FlowLayout()); frame.pack(); - return frame; + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + private static void test() throws Exception { + Robot robot = new Robot(); + robot.waitForIdle(); + robot.delay(1000); + robot.setAutoWaitForIdle(true); + + EventQueue.invokeAndWait(() -> { + Point p = list.getLocationOnScreen(); + p.translate(list.getWidth() - XOFFSET, YOFFSET); + loc = p; + }); + robot.mouseMove(loc.x, loc.y); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + for (int i = 0; i < 30; i++) { + robot.mouseMove(loc.x, loc.y + i); + } + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.delay(100); } } diff --git a/test/jdk/javax/swing/JSlider/6524424/bug6524424.html b/test/jdk/javax/swing/JSlider/6524424/bug6524424.html deleted file mode 100644 index 11a57b6b7fb..00000000000 --- a/test/jdk/javax/swing/JSlider/6524424/bug6524424.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - -To test fix follow the next steps: -1. Select a slider (do the next steps for every slider) -2. Check that the next keyboard buttons work correctly: - Up, Down, Left, Right, Page Up, Page Down -3. Press left mouse button on a free space of the slider and check - that thumb moves correctly - - diff --git a/test/jdk/javax/swing/JSlider/bug4186062.java b/test/jdk/javax/swing/JSlider/bug4186062.java new file mode 100644 index 00000000000..1db2f1bba6c --- /dev/null +++ b/test/jdk/javax/swing/JSlider/bug4186062.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 1999, 2025, 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. + */ + +/* + * @test + * @bug 4382876 + * @summary Tests if JSlider fires ChangeEvents when thumb is clicked and not moved + * @key headful + * @run main bug4186062 + */ + +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.InputEvent; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeListener; + +public class bug4186062 { + private static JFrame f; + private static JSlider slider; + private static volatile Point loc; + private static volatile int labelNum; + + public static void main(String[] args) throws Exception { + try { + SwingUtilities.invokeAndWait(() -> { + f = new JFrame("JSlider Click Value Test"); + f.setSize(400, 200); + f.setLocationRelativeTo(null); + f.setVisible(true); + JPanel panel = new JPanel(); + slider = new JSlider(); + final JLabel label = new JLabel("0"); + labelNum = 0; + + ChangeListener listener = e -> { + labelNum++; + label.setText("" + labelNum); + }; + slider.addChangeListener(listener); + + panel.add(slider); + panel.add(label); + f.add(panel); + }); + + Robot r = new Robot(); + r.setAutoDelay(100); + r.waitForIdle(); + r.delay(1000); + + SwingUtilities.invokeAndWait(() -> { + loc = slider.getLocationOnScreen(); + loc.setLocation(loc.x + (slider.getWidth() / 2), + loc.y + (slider.getHeight() / 2)); + }); + + r.mouseMove(loc.x, loc.y); + r.mousePress(InputEvent.BUTTON1_DOWN_MASK); + r.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + + if (labelNum > 0) { + throw new RuntimeException(labelNum + " ChangeEvents fired. " + + "Test failed"); + } + } finally { + SwingUtilities.invokeAndWait(() -> { + if (f != null) { + f.dispose(); + } + }); + } + } +} diff --git a/test/jdk/javax/swing/JSlider/bug4275631.java b/test/jdk/javax/swing/JSlider/bug4275631.java new file mode 100644 index 00000000000..4d0aa555721 --- /dev/null +++ b/test/jdk/javax/swing/JSlider/bug4275631.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2001, 2025, 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. + */ + +/* + * @test + * @bug 4275631 + * @summary Tests if vertical JSlider is properly aligned in large container + * @key headful + * @run main bug4275631 + */ + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Point; +import java.awt.Robot; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; + +public class bug4275631 { + private static final int OFFSET = 1; + private static JFrame f; + private static JSlider slider1; + private static JSlider slider2; + private static volatile Point loc1; + private static volatile Point loc2; + + public static void main(String[] args) throws Exception { + try { + SwingUtilities.invokeAndWait(() -> { + f = new JFrame("JSlider Alignment Test"); + f.setSize(400, 200); + f.setLocationRelativeTo(null); + + // Create two sliders, verify the alignment on the slider to be + // used in the border layout + slider1 = new JSlider(JSlider.VERTICAL, 0, 99, 50); + slider1.setInverted(true); + slider1.setMajorTickSpacing(10); + slider1.setMinorTickSpacing(1); + slider1.setPaintTicks(true); + slider1.setPaintLabels(true); + slider2 = new JSlider(JSlider.VERTICAL, 0, 99, 50); + slider2.setInverted(true); + slider2.setMajorTickSpacing(10); + slider2.setMinorTickSpacing(1); + slider2.setPaintTicks(true); + slider2.setPaintLabels(true); + + // Try to center the natural way, using a border layout in the "Center" + JPanel borderPanel = new JPanel(); + borderPanel.setLayout(new BorderLayout()); + borderPanel.setBorder(BorderFactory.createTitledBorder("BorderLayout")); + borderPanel.add(slider1, BorderLayout.CENTER); + borderPanel.setPreferredSize(new Dimension(200, 200)); + + // Try to center using GridBagLayout, with glue on left + // and right to squeeze slider into place + JPanel gridBagPanel = new JPanel(new GridBagLayout()); + gridBagPanel.setBorder(BorderFactory.createTitledBorder("GridBagLayout")); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 1; + c.fill = GridBagConstraints.VERTICAL; + c.weighty = 1.0; + gridBagPanel.add(slider2, c); + c.gridx = 0; + c.fill = GridBagConstraints.BOTH; + c.weighty = 0.0; + gridBagPanel.add(Box.createHorizontalGlue(), c); + c.gridx = 2; + c.fill = GridBagConstraints.BOTH; + gridBagPanel.add(Box.createHorizontalGlue(), c); + gridBagPanel.setPreferredSize(new Dimension(200, 200)); + + f.add(borderPanel, BorderLayout.WEST); + f.add(gridBagPanel, BorderLayout.EAST); + f.setVisible(true); + }); + + Robot r = new Robot(); + r.setAutoDelay(100); + r.waitForIdle(); + r.delay(1000); + + SwingUtilities.invokeAndWait(() -> { + loc1 = slider1.getLocationOnScreen(); + loc1.setLocation(loc1.x + (slider1.getWidth() / 2), + loc1.y + (slider1.getHeight() / 2)); + + loc2 = slider2.getLocationOnScreen(); + loc2.setLocation(loc2.x + (slider2.getWidth() / 2), + loc2.y + (slider2.getHeight() / 2)); + }); + + if (loc1.y > loc2.y + OFFSET || loc1.y < loc2.y - OFFSET) { + throw new RuntimeException("JSlider position is not aligned!"); + } + } finally { + SwingUtilities.invokeAndWait(() -> { + if (f != null) { + f.dispose(); + } + }); + } + } +} diff --git a/test/jdk/javax/swing/JSlider/bug4382876.java b/test/jdk/javax/swing/JSlider/bug4382876.java new file mode 100644 index 00000000000..b9ec64aab21 --- /dev/null +++ b/test/jdk/javax/swing/JSlider/bug4382876.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2001, 2025, 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. + */ + +/* + * @test + * @bug 4382876 + * @summary Tests how PgUp and PgDn keys work with JSlider + * @key headful + * @run main bug4382876 + */ + +import java.awt.BorderLayout; +import java.awt.ComponentOrientation; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Robot; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; + +public class bug4382876 { + private static Robot r; + private static JFrame f; + private static JSlider slider; + private static boolean upFail; + private static boolean downFail; + + public static void main(String[] args) throws Exception { + try { + SwingUtilities.invokeAndWait(() -> { + f = new JFrame("JSlider PageUp/Down Test"); + f.setSize(300, 200); + f.setLocationRelativeTo(null); + f.setVisible(true); + slider = new JSlider(-1000, -900, -1000); + slider.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); + slider.putClientProperty("JSlider.isFilled", Boolean.TRUE); + f.add(slider, BorderLayout.CENTER); + }); + + r = new Robot(); + r.setAutoDelay(100); + r.waitForIdle(); + r.delay(1000); + + r.keyPress(KeyEvent.VK_PAGE_UP); + SwingUtilities.invokeAndWait(() -> { + if (slider.getValue() < -1000) { + System.out.println("PAGE_UP VAL: " + slider.getValue()); + upFail = true; + } + }); + if (upFail) { + writeFailImage(); + throw new RuntimeException("Slider value did NOT change with PAGE_UP"); + } + r.keyPress(KeyEvent.VK_PAGE_DOWN); + SwingUtilities.invokeAndWait(() -> { + if (slider.getValue() > -1000) { + System.out.println("PAGE_DOWN VAL: " + slider.getValue()); + downFail = true; + } + }); + if (downFail) { + writeFailImage(); + throw new RuntimeException("Slider value did NOT change with PAGE_DOWN"); + } + } finally { + SwingUtilities.invokeAndWait(() -> { + if (f != null) { + f.dispose(); + } + }); + } + } + + private static void writeFailImage() throws IOException { + GraphicsConfiguration ge = GraphicsEnvironment + .getLocalGraphicsEnvironment().getDefaultScreenDevice() + .getDefaultConfiguration(); + BufferedImage failImage = r.createScreenCapture(ge.getBounds()); + ImageIO.write(failImage, "png", new File("failImage.png")); + } +} diff --git a/test/jdk/javax/swing/JSlider/6524424/bug6524424.java b/test/jdk/javax/swing/JSlider/bug6524424.java similarity index 61% rename from test/jdk/javax/swing/JSlider/6524424/bug6524424.java rename to test/jdk/javax/swing/JSlider/bug6524424.java index 3cb8b69d260..3ceff68b32b 100644 --- a/test/jdk/javax/swing/JSlider/6524424/bug6524424.java +++ b/test/jdk/javax/swing/JSlider/bug6524424.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, 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 @@ -21,46 +21,65 @@ * questions. */ -/* @test +/* + * @test * @bug 6524424 * @requires (os.family == "windows") - * @summary JSlider Clicking In Tracks Behavior Inconsistent For Different Tick Spacings - * @author Pavel Porvatov + * @summary JSlider clicking in tracks behavior inconsistent for different tick spacings * @modules java.desktop/com.sun.java.swing.plaf.windows - * @run applet/manual=done bug6524424.html + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual bug6524424 */ -import java.awt.*; -import javax.swing.*; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; import com.sun.java.swing.plaf.windows.WindowsLookAndFeel; -public class bug6524424 extends JApplet { - public static void main(String[] args) { +public class bug6524424 { + private static final String INSTRUCTIONS = """ + 1. Select a slider (do the next steps for every slider) + 2. Check that the next keyboard buttons work correctly: + Up, Down, Left, Right, Page Up, Page Down + 3. Press left mouse button on a free space of the slider + check if thumb moves correctly + press Pass else press Fail."""; + + public static void main(String[] args) throws Exception { + PassFailJFrame.builder() + .title("Slider Behavior Instructions") + .instructions(INSTRUCTIONS) + .rows(7) + .columns(30) + .testUI(bug6524424::createTestUI) + .build() + .awaitAndCheck(); + } + + private static JFrame createTestUI() { try { UIManager.setLookAndFeel(new WindowsLookAndFeel()); - } catch (UnsupportedLookAndFeelException e) { - e.printStackTrace(); - - return; + } catch (UnsupportedLookAndFeelException ex) { + PassFailJFrame.forceFail(ex.toString()); + return null; } TestPanel panel = new TestPanel(); - JFrame frame = new JFrame(); + JFrame frame = new JFrame("bug6524424"); frame.setContentPane(panel); - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.pack(); - frame.setLocationRelativeTo(null); - - frame.setVisible(true); - } - - public void init() { - TestPanel panel = new TestPanel(); - - setContentPane(panel); + return frame; } private static class TestPanel extends JPanel { diff --git a/test/jdk/javax/swing/plaf/windows/bug4991587.java b/test/jdk/javax/swing/plaf/windows/bug4991587.java new file mode 100644 index 00000000000..e4e4fde2b86 --- /dev/null +++ b/test/jdk/javax/swing/plaf/windows/bug4991587.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2004, 2025, 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. + */ + +/* + * @test + * @bug 4991587 + * @requires (os.family == "windows") + * @summary Tests that disabled JButton text is positioned properly in Windows L&F + * @modules java.desktop/com.sun.java.swing.plaf.windows + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual bug4991587 + */ + +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Rectangle; + +import javax.swing.AbstractButton; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.UIManager; + +import com.sun.java.swing.plaf.windows.WindowsButtonUI; + +public class bug4991587 { + static final String INSTRUCTIONS = """ + There are two buttons: enabled (left) and disabled (right). + Ensure that the disabled button text is painted entirely + inside the blue bounding rectangle, just like the enabled + button (use it as an example of how this should look like). + """; + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); + PassFailJFrame.builder() + .title("bug4991587 Test Instructions") + .instructions(INSTRUCTIONS) + .columns(50) + .testUI(bug4991587::createUI) + .build() + .awaitAndCheck(); + } + + static JFrame createUI() { + JFrame f = new JFrame("Disabled JButton Text Test"); + f.setLayout(new FlowLayout()); + f.setSize(400, 100); + + JButton button1 = new JButton("\u0114 Enabled JButton"); + button1.setUI(new MyButtonUI()); + f.add(button1); + + JButton button2 = new JButton("\u0114 Disabled JButton"); + button2.setEnabled(false); + button2.setUI(new MyButtonUI()); + f.add(button2); + + return f; + } + + static class MyButtonUI extends WindowsButtonUI { + protected void paintText(Graphics g, AbstractButton b, + Rectangle textRect, String text) { + g.setColor(Color.blue); + g.drawRect(textRect.x, + textRect.y, + textRect.width + 1, // add 1 for the shadow, otherwise it + // will be painted over the textRect + textRect.height); + super.paintText(g, b, textRect, text); + } + } +} diff --git a/test/jdk/tools/jpackage/apps/UseShutdownHook.java b/test/jdk/tools/jpackage/apps/UseShutdownHook.java new file mode 100644 index 00000000000..c558ee85701 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/UseShutdownHook.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, 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.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class UseShutdownHook { + + public static void main(String[] args) throws InterruptedException { + trace("Started"); + + var outputFile = Path.of(args[0]); + trace(String.format("Write output in [%s] file", outputFile)); + + var shutdownTimeoutSeconds = Integer.parseInt(args[1]); + trace(String.format("Automatically shutdown the app in %ss", shutdownTimeoutSeconds)); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + output(outputFile, "shutdown hook executed"); + } + }); + + var startTime = System.currentTimeMillis(); + var lock = new Object(); + do { + synchronized (lock) { + lock.wait(shutdownTimeoutSeconds * 1000); + } + } while ((System.currentTimeMillis() - startTime) < (shutdownTimeoutSeconds * 1000)); + + output(outputFile, "exit"); + } + + private static void output(Path outputFilePath, String msg) { + + trace(String.format("Writing [%s] into [%s]", msg, outputFilePath)); + + try { + Files.createDirectories(outputFilePath.getParent()); + Files.writeString(outputFilePath, msg, StandardOpenOption.APPEND, StandardOpenOption.CREATE); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static void trace(String msg) { + Date time = new Date(System.currentTimeMillis()); + msg = String.format("UseShutdownHook [%s]: %s", SDF.format(time), msg); + System.out.println(msg); + try { + Files.write(traceFile, List.of(msg), StandardOpenOption.APPEND, StandardOpenOption.CREATE); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss.SSS"); + + private static final Path traceFile = Path.of(System.getProperty("jpackage.test.trace-file")); +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java index ba2485516a9..52e8ecf819b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -25,61 +25,114 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; public final class CfgFile { - public String getValue(String section, String key) { - Objects.requireNonNull(section); - Objects.requireNonNull(key); + public String getValue(String sectionName, String key) { + var section = getSection(sectionName); + TKit.assertTrue(section != null, String.format( + "Check section [%s] is found in [%s] cfg file", sectionName, id)); - Map entries = data.get(section); - TKit.assertTrue(entries != null, String.format( - "Check section [%s] is found in [%s] cfg file", section, id)); - - String value = entries.get(key); + String value = section.getValue(key); TKit.assertNotNull(value, String.format( "Check key [%s] is found in [%s] section of [%s] cfg file", key, - section, id)); + sectionName, id)); return value; } - public String getValueUnchecked(String section, String key) { - Objects.requireNonNull(section); - Objects.requireNonNull(key); + public String getValueUnchecked(String sectionName, String key) { + var section = getSection(sectionName); + if (section != null) { + return section.getValue(key); + } else { + return null; + } + } - return Optional.ofNullable(data.get(section)).map(v -> v.get(key)).orElse( - null); + public CfgFile addValue(String sectionName, String key, String value) { + var section = getSection(sectionName); + if (section == null) { + section = new Section(sectionName, new ArrayList<>()); + data.add(section); + } + section.data.add(Map.entry(key, value)); + return this; + } + + public CfgFile add(CfgFile other) { + return combine(this, other); + } + + public CfgFile() { + this(new ArrayList<>(), "*"); + } + + public static CfgFile combine(CfgFile base, CfgFile mods) { + var cfgFile = new CfgFile(new ArrayList<>(), "*"); + for (var src : List.of(base, mods)) { + for (var section : src.data) { + for (var kvp : section.data) { + cfgFile.addValue(section.name, kvp.getKey(), kvp.getValue()); + } + } + } + return cfgFile; } - private CfgFile(Map> data, String id) { + private CfgFile(List
data, String id) { this.data = data; this.id = id; } - public static CfgFile readFromFile(Path path) throws IOException { + public CfgFile save(Path path) { + var lines = data.stream().flatMap(section -> { + return Stream.concat( + Stream.of(String.format("[%s]", section.name)), + section.data.stream().map(kvp -> { + return String.format("%s=%s", kvp.getKey(), kvp.getValue()); + })); + }); + TKit.createTextFile(path, lines); + return this; + } + + private Section getSection(String name) { + Objects.requireNonNull(name); + for (var section : data.reversed()) { + if (name.equals(section.name)) { + return section; + } + } + return null; + } + + public static CfgFile load(Path path) throws IOException { TKit.trace(String.format("Read [%s] jpackage cfg file", path)); final Pattern sectionBeginRegex = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*"); final Pattern keyRegex = Pattern.compile( "\\s*([^=]*)=(.*)" ); - Map> result = new HashMap<>(); + List
sections = new ArrayList<>(); String currentSectionName = null; - Map currentSection = new HashMap<>(); + List> currentSection = new ArrayList<>(); for (String line : Files.readAllLines(path)) { Matcher matcher = sectionBeginRegex.matcher(line); if (matcher.find()) { if (currentSectionName != null) { - result.put(currentSectionName, Collections.unmodifiableMap( - new HashMap<>(currentSection))); + sections.add(new Section(currentSectionName, + Collections.unmodifiableList(new ArrayList<>( + currentSection)))); } currentSectionName = matcher.group(1); currentSection.clear(); @@ -88,19 +141,31 @@ public static CfgFile readFromFile(Path path) throws IOException { matcher = keyRegex.matcher(line); if (matcher.find()) { - currentSection.put(matcher.group(1), matcher.group(2)); - continue; + currentSection.add(Map.entry(matcher.group(1), matcher.group(2))); } } if (!currentSection.isEmpty()) { - result.put(Optional.ofNullable(currentSectionName).orElse(""), - Collections.unmodifiableMap(currentSection)); + sections.add(new Section( + Optional.ofNullable(currentSectionName).orElse(""), + Collections.unmodifiableList(currentSection))); } - return new CfgFile(Collections.unmodifiableMap(result), path.toString()); + return new CfgFile(sections, path.toString()); + } + + private static record Section(String name, List> data) { + String getValue(String key) { + Objects.requireNonNull(key); + for (var kvp : data.reversed()) { + if (key.equals(kvp.getKey())) { + return kvp.getValue(); + } + } + return null; + } } - private final Map> data; + private final List
data; private final String id; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java index e94cbc79aac..c1c51f1ef55 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -54,7 +54,6 @@ public static Executor of(String... cmdline) { public Executor() { saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE)); - removePath = false; winEnglishOutput = false; } @@ -91,8 +90,8 @@ public Executor setExecutable(JavaTool v) { return setExecutable(v.getPath()); } - public Executor setRemovePath(boolean value) { - removePath = value; + public Executor removeEnvVar(String envVarName) { + removeEnvVars.add(Objects.requireNonNull(envVarName)); return this; } @@ -370,10 +369,12 @@ private Result runExecutable() throws IOException, InterruptedException { builder.directory(directory.toFile()); sb.append(String.format("; in directory [%s]", directory)); } - if (removePath) { - // run this with cleared Path in Environment - TKit.trace("Clearing PATH in environment"); - builder.environment().remove("PATH"); + if (!removeEnvVars.isEmpty()) { + final var envComm = Comm.compare(builder.environment().keySet(), removeEnvVars); + builder.environment().keySet().removeAll(envComm.common()); + envComm.common().forEach(envVar -> { + TKit.trace(String.format("Clearing %s in environment", envVar)); + }); } trace("Execute " + sb.toString() + "..."); @@ -512,7 +513,7 @@ private static void trace(String msg) { private Path executable; private Set saveOutputType; private Path directory; - private boolean removePath; + private Set removeEnvVars = new HashSet<>(); private boolean winEnglishOutput; private String winTmpDir = null; diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java index bc722e7acd9..46aa2f1375f 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -351,16 +351,6 @@ public static final class AppOutputVerifier { this.outputFilePath = TKit.workDir().resolve(OUTPUT_FILENAME); this.params = new HashMap<>(); this.defaultLauncherArgs = new ArrayList<>(); - - if (TKit.isWindows()) { - // When running app launchers on Windows, clear users environment (JDK-8254920) - removePath(true); - } - } - - public AppOutputVerifier removePath(boolean v) { - removePath = v; - return this; } public AppOutputVerifier saveOutput(boolean v) { @@ -442,10 +432,7 @@ public Executor.Result execute(String... args) { if (launcherNoExit) { return getExecutor(args).executeWithoutExitCodeCheck(); } else { - final int attempts = 3; - final int waitBetweenAttemptsSeconds = 5; - return getExecutor(args).executeAndRepeatUntilExitCode(expectedExitCode, attempts, - waitBetweenAttemptsSeconds); + return HelloApp.execute(expectedExitCode, getExecutor(args)); } } @@ -464,17 +451,17 @@ private Executor getExecutor(String...args) { } final List launcherArgs = List.of(args); - return new Executor() + final var executor = new Executor() .setDirectory(outputFile.getParent()) .saveOutput(saveOutput) .dumpOutput() - .setRemovePath(removePath) .setExecutable(executablePath) - .addArguments(launcherArgs); + .addArguments(List.of(args)); + + return configureEnvironment(executor); } private boolean launcherNoExit; - private boolean removePath; private boolean saveOutput; private final Path launcherPath; private Path outputFilePath; @@ -487,6 +474,29 @@ public static AppOutputVerifier assertApp(Path helloAppLauncher) { return new AppOutputVerifier(helloAppLauncher); } + public static Executor.Result configureAndExecute(int expectedExitCode, Executor executor) { + return execute(expectedExitCode, configureEnvironment(executor)); + } + + private static Executor.Result execute(int expectedExitCode, Executor executor) { + if (TKit.isLinux()) { + final int attempts = 3; + final int waitBetweenAttemptsSeconds = 5; + return executor.executeAndRepeatUntilExitCode(expectedExitCode, attempts, + waitBetweenAttemptsSeconds); + } else { + return executor.execute(expectedExitCode); + } + } + + private static Executor configureEnvironment(Executor executor) { + if (CLEAR_JAVA_ENV_VARS) { + executor.removeEnvVar("JAVA_TOOL_OPTIONS"); + executor.removeEnvVar("_JAVA_OPTIONS"); + } + return executor; + } + static final String OUTPUT_FILENAME = "appOutput.txt"; private final JavaAppDesc appDesc; @@ -496,4 +506,7 @@ public static AppOutputVerifier assertApp(Path helloAppLauncher) { private static final String CLASS_NAME = HELLO_JAVA.getFileName().toString().split( "\\.", 2)[0]; + + private static final boolean CLEAR_JAVA_ENV_VARS = Optional.ofNullable( + TKit.getConfigProperty("clear-app-launcher-java-env-vars")).map(Boolean::parseBoolean).orElse(false); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 28bdc483afd..acc46a46e54 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -622,8 +622,18 @@ public Path appLauncherCfgPath(String launcherName) { } public boolean isFakeRuntime(String msg) { - Path runtimeDir = appRuntimeDirectory(); + if (isFakeRuntime()) { + // Fake runtime + Path runtimeDir = appRuntimeDirectory(); + TKit.trace(String.format( + "%s because application runtime directory [%s] is incomplete", + msg, runtimeDir)); + return true; + } + return false; + } + private boolean isFakeRuntime() { final Collection criticalRuntimeFiles; if (TKit.isWindows()) { criticalRuntimeFiles = WindowsHelper.CRITICAL_RUNTIME_FILES; @@ -635,16 +645,9 @@ public boolean isFakeRuntime(String msg) { throw TKit.throwUnknownPlatformError(); } - if (!criticalRuntimeFiles.stream().anyMatch(v -> { - return runtimeDir.resolve(v).toFile().exists(); - })) { - // Fake runtime - TKit.trace(String.format( - "%s because application runtime directory [%s] is incomplete", - msg, runtimeDir)); - return true; - } - return false; + Path runtimeDir = appRuntimeDirectory(); + return !criticalRuntimeFiles.stream().map(runtimeDir::resolve).allMatch( + Files::exists); } public boolean canRunLauncher(String msg) { @@ -709,6 +712,13 @@ public JPackageCommand ignoreDefaultRuntime(boolean v) { return this; } + public JPackageCommand ignoreFakeRuntime() { + if (isFakeRuntime()) { + ignoreDefaultRuntime(true); + } + return this; + } + public JPackageCommand ignoreDefaultVerbose(boolean v) { verifyMutable(); ignoreDefaultVerbose = v; @@ -1000,7 +1010,7 @@ public CfgFile readLauncherCfgFile(String launcherName) { if (isRuntime()) { return null; } - return ThrowingFunction.toFunction(CfgFile::readFromFile).apply( + return ThrowingFunction.toFunction(CfgFile::load).apply( appLauncherCfgPath(launcherName)); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java index a695a3b693d..6220eb8fc0d 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -41,7 +41,7 @@ public final class LauncherAsServiceVerifier { - public final static class Builder { + public static final class Builder { public Builder setExpectedValue(String v) { expectedValue = v; @@ -204,7 +204,7 @@ private boolean canVerifyInstall(JPackageCommand cmd) throws IOException { if (cmd.isPackageUnpacked(msg) || cmd.isFakeRuntime(msg)) { return false; } - var cfgFile = CfgFile.readFromFile(cmd.appLauncherCfgPath(launcherName)); + var cfgFile = CfgFile.load(cmd.appLauncherCfgPath(launcherName)); if (!expectedValue.equals(cfgFile.getValueUnchecked("ArgOptions", "arguments"))) { TKit.trace(String.format( @@ -338,6 +338,6 @@ private Path appOutputFilePathVerify(JPackageCommand cmd) { private final String launcherName; private final Path appOutputFileName; - final static Set SUPPORTED_PACKAGES = Stream.of(LINUX, WINDOWS, + static final Set SUPPORTED_PACKAGES = Stream.of(LINUX, WINDOWS, Set.of(MAC_PKG)).flatMap(x -> x.stream()).collect(Collectors.toSet()); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java index d8930745847..108b0051fa9 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -273,7 +274,7 @@ PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa) { Files.deleteIfExists(appOutput); List expectedArgs = testRun.openFiles(testFiles); - TKit.waitForFileCreated(appOutput, 7); + TKit.waitForFileCreated(appOutput, Duration.ofSeconds(7), Duration.ofSeconds(3)); // Wait a little bit after file has been created to // make sure there are no pending writes into it. diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java index bd35a5abd20..70a727d157b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java @@ -36,8 +36,9 @@ import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; -import java.nio.file.WatchService; import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -531,49 +532,57 @@ public static Path createRelativePathCopy(final Path file) { return file; } - static void waitForFileCreated(Path fileToWaitFor, - long timeoutSeconds) throws IOException { + public static void waitForFileCreated(Path fileToWaitFor, + Duration timeout, Duration afterCreatedTimeout) throws IOException { + waitForFileCreated(fileToWaitFor, timeout); + // Wait after the file has been created to ensure it is fully written. + ThrowingConsumer.toConsumer(Thread::sleep).accept(afterCreatedTimeout); + } + + private static void waitForFileCreated(Path fileToWaitFor, Duration timeout) throws IOException { trace(String.format("Wait for file [%s] to be available", fileToWaitFor.toAbsolutePath())); - WatchService ws = FileSystems.getDefault().newWatchService(); - - Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent(); - watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY); - - long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000; - for (;;) { - long timeout = waitUntil - System.currentTimeMillis(); - assertTrue(timeout > 0, String.format( - "Check timeout value %d is positive", timeout)); - - WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout, - TimeUnit.MILLISECONDS)).get(); - if (key == null) { - if (fileToWaitFor.toFile().exists()) { - trace(String.format( - "File [%s] is available after poll timeout expired", - fileToWaitFor)); - return; + try (var ws = FileSystems.getDefault().newWatchService()) { + + Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent(); + watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY); + + var waitUntil = Instant.now().plus(timeout); + for (;;) { + Instant n = Instant.now(); + Duration remainderTimeout = Duration.between(n, waitUntil); + assertTrue(remainderTimeout.isPositive(), String.format( + "Check timeout value %dms is positive", remainderTimeout.toMillis())); + + WatchKey key = ThrowingSupplier.toSupplier(() -> { + return ws.poll(remainderTimeout.toMillis(), TimeUnit.MILLISECONDS); + }).get(); + if (key == null) { + if (Files.exists(fileToWaitFor)) { + trace(String.format( + "File [%s] is available after poll timeout expired", + fileToWaitFor)); + return; + } + assertUnexpected(String.format("Timeout %dms expired", remainderTimeout.toMillis())); } - assertUnexpected(String.format("Timeout expired", timeout)); - } - for (WatchEvent event : key.pollEvents()) { - if (event.kind() == StandardWatchEventKinds.OVERFLOW) { - continue; - } - Path contextPath = (Path) event.context(); - if (Files.isSameFile(watchDirectory.resolve(contextPath), - fileToWaitFor)) { - trace(String.format("File [%s] is available", fileToWaitFor)); - return; + for (WatchEvent event : key.pollEvents()) { + if (event.kind() == StandardWatchEventKinds.OVERFLOW) { + continue; + } + Path contextPath = (Path) event.context(); + if (Files.exists(fileToWaitFor) && Files.isSameFile(watchDirectory.resolve(contextPath), fileToWaitFor)) { + trace(String.format("File [%s] is available", fileToWaitFor)); + return; + } } - } - if (!key.reset()) { - assertUnexpected("Watch key invalidated"); + if (!key.reset()) { + assertUnexpected("Watch key invalidated"); + } } } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index ebf4494aa79..6b6846f325c 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -28,9 +28,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Functional.ThrowingRunnable; @@ -188,6 +191,90 @@ public static String getExecutableDesciption(Path pathToExeFile) { "Failed to get file description of [%s]", pathToExeFile)); } + public static void killProcess(long pid) { + Executor.of("taskkill", "/F", "/PID", Long.toString(pid)).dumpOutput(true).execute(); + } + + public static void killAppLauncherProcess(JPackageCommand cmd, + String launcherName, int expectedCount) { + var pids = findAppLauncherPIDs(cmd, launcherName); + try { + TKit.assertEquals(expectedCount, pids.length, String.format( + "Check [%d] %s app launcher processes found running", + expectedCount, Optional.ofNullable(launcherName).map( + str -> "[" + str + "]").orElse("
"))); + } finally { + if (pids.length != 0) { + killProcess(pids[0]); + } + } + } + + private static long[] findAppLauncherPIDs(JPackageCommand cmd, String launcherName) { + // Get the list of PIDs and PPIDs of app launcher processes. + // wmic process where (name = "foo.exe") get ProcessID,ParentProcessID + List output = Executor.of("wmic", "process", "where", "(name", + "=", + "\"" + cmd.appLauncherPath(launcherName).getFileName().toString() + "\"", + ")", "get", "ProcessID,ParentProcessID").dumpOutput(true). + saveOutput().executeAndGetOutput(); + + if ("No Instance(s) Available.".equals(output.getFirst().trim())) { + return new long[0]; + } + + String[] headers = Stream.of(output.getFirst().split("\\s+", 2)).map( + String::trim).map(String::toLowerCase).toArray(String[]::new); + Pattern pattern; + if (headers[0].equals("parentprocessid") && headers[1].equals( + "processid")) { + pattern = Pattern.compile("^(?\\d+)\\s+(?\\d+)\\s+$"); + } else if (headers[1].equals("parentprocessid") && headers[0].equals( + "processid")) { + pattern = Pattern.compile("^(?\\d+)\\s+(?\\d+)\\s+$"); + } else { + throw new RuntimeException( + "Unrecognizable output of \'wmic process\' command"); + } + + List processes = output.stream().skip(1).map(line -> { + Matcher m = pattern.matcher(line); + long[] pids = null; + if (m.matches()) { + pids = new long[]{Long.parseLong(m.group("pid")), Long. + parseLong(m.group("ppid"))}; + } + return pids; + }).filter(Objects::nonNull).toList(); + + switch (processes.size()) { + case 2 -> { + final long parentPID; + final long childPID; + if (processes.get(0)[0] == processes.get(1)[1]) { + parentPID = processes.get(0)[0]; + childPID = processes.get(1)[0]; + } else if (processes.get(1)[0] == processes.get(0)[1]) { + parentPID = processes.get(1)[0]; + childPID = processes.get(0)[0]; + } else { + TKit.assertUnexpected("App launcher processes unrelated"); + return null; // Unreachable + } + return new long[]{parentPID, childPID}; + } + case 1 -> { + return new long[]{processes.get(0)[0]}; + } + default -> { + TKit.assertUnexpected(String.format( + "Unexpected number of running processes [%d]", + processes.size())); + return null; // Unreachable + } + } + } + private static boolean isUserLocalInstall(JPackageCommand cmd) { return cmd.hasArgument("--win-per-user-install"); } @@ -418,15 +505,15 @@ private static String queryRegistryValueCache(String keyPath, "bin\\server\\jvm.dll")); // jtreg resets %ProgramFiles% environment variable by some reason. - private final static Path PROGRAM_FILES = Path.of(Optional.ofNullable( + private static final Path PROGRAM_FILES = Path.of(Optional.ofNullable( System.getenv("ProgramFiles")).orElse("C:\\Program Files")); - private final static Path USER_LOCAL = Path.of(System.getProperty( + private static final Path USER_LOCAL = Path.of(System.getProperty( "user.home"), "AppData", "Local"); - private final static String SYSTEM_SHELL_FOLDERS_REGKEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; - private final static String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; + private static final String SYSTEM_SHELL_FOLDERS_REGKEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; + private static final String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; private static final Map REGISTRY_VALUES = new HashMap<>(); } diff --git a/test/jdk/tools/jpackage/resources/Win8365790Test.ps1 b/test/jdk/tools/jpackage/resources/Win8365790Test.ps1 new file mode 100644 index 00000000000..3a7d8c9a90b --- /dev/null +++ b/test/jdk/tools/jpackage/resources/Win8365790Test.ps1 @@ -0,0 +1,83 @@ +# +# Copyright (c) 2025, 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. +# + +param ( + # Path to executable to start. + [Parameter(Mandatory=$true)] + [string]$Executable, + + # Timeout to wait after the executable has been started. + [Parameter(Mandatory=$true)] + [double]$TimeoutSeconds +) + +$type = @{ + TypeDefinition = @' +using System; +using System.Runtime.InteropServices; + +namespace Stuff { + + internal struct Details { + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId); + } + + public struct Facade { + public static void GenerateConsoleCtrlEvent() { + if (!Details.GenerateConsoleCtrlEvent(0, 0)) { + reportLastErrorAndExit("GenerateConsoleCtrlEvent"); + } + } + + internal static void reportLastErrorAndExit(String func) { + int errorCode = Marshal.GetLastWin32Error(); + Console.Error.WriteLine(func + " function failed with error code: " + errorCode); + Environment.Exit(100); + } + } +} +'@ +} +Add-Type @type + +Set-PSDebug -Trace 2 + +# Launch the target executable. +# `-NoNewWindow` parameter will attach the started process to the existing console. +$childProc = Start-Process -PassThru -NoNewWindow $Executable + +# Wait a bit to let the started process complete initialization. +Start-Sleep -Seconds $TimeoutSeconds + +# Call GenerateConsoleCtrlEvent to send a CTRL+C event to the launched executable. +# CTRL+C event will be sent to all processes attached to the console of the current process, +# i.e., it will be sent to this PowerShell process and to the started $Executable process because +# it was configured to attach to the existing console (the console of this PowerShell process). +[Stuff.Facade]::GenerateConsoleCtrlEvent() + +# Wait for child process termination +Wait-Process -InputObject $childProc + +Exit 0 diff --git a/test/jdk/tools/jpackage/share/AppLauncherEnvTest.java b/test/jdk/tools/jpackage/share/AppLauncherEnvTest.java index a16ff9c18f9..772370b0f8c 100644 --- a/test/jdk/tools/jpackage/share/AppLauncherEnvTest.java +++ b/test/jdk/tools/jpackage/share/AppLauncherEnvTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, 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 @@ -30,6 +30,7 @@ import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Executor; +import static jdk.jpackage.test.HelloApp.configureAndExecute; import jdk.jpackage.test.TKit; /** @@ -53,6 +54,7 @@ public static void test() throws Exception { JPackageCommand cmd = JPackageCommand .helloAppImage(TEST_APP_JAVA + "*Hello") + .ignoreFakeRuntime() .addArguments("--java-options", "-D" + testAddDirProp + "=$APPDIR"); @@ -60,16 +62,12 @@ public static void test() throws Exception { final String envVarName = envVarName(); - final int attempts = 3; - final int waitBetweenAttemptsSeconds = 5; - List output = new Executor() + List output = configureAndExecute(0, new Executor() .saveOutput() .setExecutable(cmd.appLauncherPath().toAbsolutePath()) .addArguments("--print-env-var=" + envVarName) .addArguments("--print-sys-prop=" + testAddDirProp) - .addArguments("--print-sys-prop=" + "java.library.path") - .executeAndRepeatUntilExitCode(0, attempts, - waitBetweenAttemptsSeconds).getOutput(); + .addArguments("--print-sys-prop=" + "java.library.path")).getOutput(); BiFunction getValue = (idx, name) -> { return output.get(idx).substring((name + "=").length()); diff --git a/test/jdk/tools/jpackage/share/BasicTest.java b/test/jdk/tools/jpackage/share/BasicTest.java index 15c8836e7ac..0b967373dc9 100644 --- a/test/jdk/tools/jpackage/share/BasicTest.java +++ b/test/jdk/tools/jpackage/share/BasicTest.java @@ -72,7 +72,7 @@ public void testJpackageProps() { .saveConsoleOutput(true) .addArguments("--app-version", appVersion, "--arguments", "jpackage.app-version jpackage.app-path") - .ignoreDefaultRuntime(true); + .ignoreFakeRuntime(); cmd.executeAndAssertImageCreated(); Path launcherPath = cmd.appLauncherPath(); diff --git a/test/jdk/tools/jpackage/windows/Win8365790Test.java b/test/jdk/tools/jpackage/windows/Win8365790Test.java new file mode 100644 index 00000000000..f0239de7eeb --- /dev/null +++ b/test/jdk/tools/jpackage/windows/Win8365790Test.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, 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 static jdk.jpackage.test.HelloApp.configureAndExecute; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import jdk.jpackage.test.AdditionalLauncher; +import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.CfgFile; +import jdk.jpackage.test.Executor; +import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.TKit; + +/** + * Test the child process has a chance to handle Ctrl+C signal. + */ + +/* + * @test + * @summary Test case for JDK-8365790 + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @build Win8365790Test + * @requires (os.family == "windows") + * @run main/othervm/timeout=100 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=Win8365790Test + */ +public class Win8365790Test { + + @Test + public void test() throws InterruptedException, IOException { + + var outputDir = TKit.createTempDirectory("response-dir"); + + var mainOutputFile = outputDir.resolve("output.txt"); + var mainTraceFile = outputDir.resolve("trace.txt"); + + var probeOutputFile = outputDir.resolve("probe-output.txt"); + var probeTraceFile = outputDir.resolve("probe-trace.txt"); + + var cmd = JPackageCommand + .helloAppImage(TEST_APP_JAVA + "*UseShutdownHook") + .ignoreFakeRuntime() + .addArguments("--java-options", "-Djpackage.test.trace-file=" + mainTraceFile.toString()) + .addArguments("--arguments", mainOutputFile.toString()) + .addArguments("--arguments", Long.toString(Duration.ofSeconds(TETS_APP_AUTOCLOSE_TIMEOUT_SECONDS).getSeconds())); + + new AdditionalLauncher("probe") { + @Override + protected void verify(JPackageCommand cmd) { + } + }.addJavaOptions("-Djpackage.test.trace-file=" + probeTraceFile.toString()) + .addDefaultArguments(probeOutputFile.toString(), Long.toString(Duration.ofSeconds(TETS_APP_AUTOCLOSE_TIMEOUT_SECONDS).getSeconds())) + .applyTo(cmd); + + cmd.executeAndAssertImageCreated(); + + cmd.readLauncherCfgFile("probe") + .add(new CfgFile().addValue("Application", "win.norestart", Boolean.TRUE.toString())) + .save(cmd.appLauncherCfgPath("probe")); + + // Try Ctrl+C signal on a launcher with disabled restart functionality. + // It will create a single launcher process instead of the parent and the child processes. + // Ctrl+C always worked for launcher with disabled restart functionality. + var probeOutput = runLauncher(cmd, "probe", probeTraceFile, probeOutputFile); + + if (!probeOutput.equals("shutdown hook executed")) { + // Ctrl+C signal didn't make it. Test environment doesn't support Ctrl+C signal + // delivery from the prowershell process to a child process, don't run the main + // test. + TKit.throwSkippedException( + "The environment does NOT support Ctrl+C signal delivery from the prowershell process to a child process"); + } + + var mainOutput = runLauncher(cmd, null, mainTraceFile, mainOutputFile); + + TKit.assertEquals("shutdown hook executed", mainOutput, "Check shutdown hook executed"); + } + + private static String runLauncher(JPackageCommand cmd, String launcherName, Path traceFile, Path outputFile) throws IOException { + // Launch the specified launcher and send Ctrl+C signal to it. + Thread.ofVirtual().start(() -> { + configureAndExecute(0, Executor.of("powershell", "-NonInteractive", "-NoLogo", "-NoProfile", "-ExecutionPolicy", "Unrestricted") + .addArgument("-File").addArgument(TEST_PS1) + .addArguments("-TimeoutSeconds", Long.toString(Duration.ofSeconds(5).getSeconds())) + .addArgument("-Executable").addArgument(cmd.appLauncherPath(launcherName)) + .dumpOutput()); + }); + + TKit.waitForFileCreated(traceFile, Duration.ofSeconds(20), Duration.ofSeconds(2)); + + try { + TKit.waitForFileCreated(outputFile, Duration.ofSeconds(TETS_APP_AUTOCLOSE_TIMEOUT_SECONDS * 2), Duration.ofSeconds(2)); + } finally { + TKit.traceFileContents(traceFile, "Test app trace"); + } + + TKit.assertFileExists(outputFile); + return Files.readString(outputFile); + } + + private static final long TETS_APP_AUTOCLOSE_TIMEOUT_SECONDS = 30; + + private static final Path TEST_APP_JAVA = TKit.TEST_SRC_ROOT.resolve("apps/UseShutdownHook.java"); + private static final Path TEST_PS1 = TKit.TEST_SRC_ROOT.resolve(Path.of("resources/Win8365790Test.ps1")).normalize(); +} diff --git a/test/jdk/tools/jpackage/windows/WinChildProcessTest.java b/test/jdk/tools/jpackage/windows/WinChildProcessTest.java index 8bab8eac5fb..a83ef837331 100644 --- a/test/jdk/tools/jpackage/windows/WinChildProcessTest.java +++ b/test/jdk/tools/jpackage/windows/WinChildProcessTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -27,7 +27,6 @@ * when System.exit(0) is invoked along with terminating java program. * @library /test/jdk/tools/jpackage/helpers * @requires os.family == "windows" - * @build WinChildProcessTest * @build jdk.jpackage.test.* * @build WinChildProcessTest * @run main/othervm -Xmx512m jdk.jpackage.test.Main @@ -41,29 +40,32 @@ import java.nio.file.Path; import jdk.jpackage.test.JPackageCommand; +import static jdk.jpackage.test.HelloApp.configureAndExecute; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Executor; import jdk.jpackage.test.TKit; +import static jdk.jpackage.test.WindowsHelper.killProcess; public class WinChildProcessTest { private static final Path TEST_APP_JAVA = TKit.TEST_SRC_ROOT .resolve("apps/ChildProcessAppLauncher.java"); @Test - public static void test() throws Throwable { + public static void test() { long childPid = 0; try { JPackageCommand cmd = JPackageCommand - .helloAppImage(TEST_APP_JAVA + "*Hello"); + .helloAppImage(TEST_APP_JAVA + "*Hello") + .ignoreFakeRuntime(); // Create the image of the third party application launcher cmd.executeAndAssertImageCreated(); // Start the third party application launcher and dump and save the // output of the application - List output = new Executor().saveOutput().dumpOutput() - .setExecutable(cmd.appLauncherPath().toAbsolutePath()) - .execute(0).getOutput(); + List output = configureAndExecute(0, new Executor().saveOutput().dumpOutput() + .setExecutable(cmd.appLauncherPath().toAbsolutePath())) + .getOutput(); String pidStr = output.get(0); // parse child PID @@ -76,10 +78,12 @@ public static void test() throws Throwable { Optional processHandle = ProcessHandle.of(childPid); boolean isAlive = processHandle.isPresent() && processHandle.get().isAlive(); - TKit.assertTrue(isAlive, "Check is child process is alive"); + TKit.assertTrue(isAlive, "Check child process is alive"); } finally { - // Kill only a specific child instance - Runtime.getRuntime().exec("taskkill /F /PID " + childPid); + if (childPid != 0) { + // Kill only a specific child instance + killProcess(childPid); + } } } }