8316017: Refactor timeout handler in PassFailJFrame

Reviewed-by: prr
This commit is contained in:
Alexey Ivanov 2023-10-25 13:25:34 +00:00
parent 202c0137b8
commit 3abd772672

View File

@ -34,6 +34,8 @@ import java.awt.Rectangle;
import java.awt.Robot; import java.awt.Robot;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.awt.event.WindowListener; import java.awt.event.WindowListener;
@ -155,14 +157,26 @@ public class PassFailJFrame {
* Prefix for the user-provided failure reason. * Prefix for the user-provided failure reason.
*/ */
private static final String FAILURE_REASON = "Failure Reason:\n"; private static final String FAILURE_REASON = "Failure Reason:\n";
/**
* The failure reason message when the user didn't provide one.
*/
private static final String EMPTY_REASON = "(no reason provided)";
private static final List<Window> windowList = new ArrayList<>(); private static final List<Window> windowList = new ArrayList<>();
private static final Timer timer = new Timer(0, null);
private static final CountDownLatch latch = new CountDownLatch(1); private static final CountDownLatch latch = new CountDownLatch(1);
private static volatile boolean failed; private static TimeoutHandler timeoutHandler;
private static volatile boolean timeout;
private static volatile String testFailedReason; /**
* The description of why the test fails.
* <p>
* Note: <strong>do not use</strong> this field directly,
* use the {@link #setFailureReason(String) setFailureReason} and
* {@link #getFailureReason() getFailureReason} methods to modify and
* to read its value.
*/
private static String failureReason;
private static final AtomicInteger imgCounter = new AtomicInteger(0); private static final AtomicInteger imgCounter = new AtomicInteger(0);
@ -319,42 +333,27 @@ public class PassFailJFrame {
frame = new JFrame(title); frame = new JFrame(title);
frame.setLayout(new BorderLayout()); frame.setLayout(new BorderLayout());
JLabel testTimeoutLabel = new JLabel("", JLabel.CENTER);
timeoutHandler = new TimeoutHandler(testTimeoutLabel, testTimeOut);
frame.add(testTimeoutLabel, BorderLayout.NORTH);
JTextComponent text = instructions.startsWith("<html>") JTextComponent text = instructions.startsWith("<html>")
? configureHTML(instructions, rows, columns) ? configureHTML(instructions, rows, columns)
: configurePlainText(instructions, rows, columns); : configurePlainText(instructions, rows, columns);
text.setEditable(false); text.setEditable(false);
long tTimeout = TimeUnit.MINUTES.toMillis(testTimeOut);
final JLabel testTimeoutLabel = new JLabel(String.format("Test " +
"timeout: %s", convertMillisToTimeStr(tTimeout)), JLabel.CENTER);
final long startTime = System.currentTimeMillis();
timer.setDelay(1000);
timer.addActionListener((e) -> {
long leftTime = tTimeout - (System.currentTimeMillis() - startTime);
if ((leftTime < 0) || failed) {
timer.stop();
testFailedReason = FAILURE_REASON
+ "Timeout User did not perform testing.";
timeout = true;
latch.countDown();
}
testTimeoutLabel.setText(String.format("Test timeout: %s", convertMillisToTimeStr(leftTime)));
});
timer.start();
frame.add(testTimeoutLabel, BorderLayout.NORTH);
frame.add(new JScrollPane(text), BorderLayout.CENTER); frame.add(new JScrollPane(text), BorderLayout.CENTER);
JButton btnPass = new JButton("Pass"); JButton btnPass = new JButton("Pass");
btnPass.addActionListener((e) -> { btnPass.addActionListener((e) -> {
latch.countDown(); latch.countDown();
timer.stop(); timeoutHandler.stop();
}); });
JButton btnFail = new JButton("Fail"); JButton btnFail = new JButton("Fail");
btnFail.addActionListener((e) -> { btnFail.addActionListener((e) -> {
getFailureReason(); requestFailureReason();
timer.stop(); timeoutHandler.stop();
}); });
JPanel buttonsPanel = new JPanel(); JPanel buttonsPanel = new JPanel();
@ -480,12 +479,58 @@ public class PassFailJFrame {
} }
private static final class TimeoutHandler implements ActionListener {
private final long endTime;
private final Timer timer;
private final JLabel label;
public TimeoutHandler(final JLabel label, final long testTimeOut) {
endTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(testTimeOut);
this.label = label;
timer = new Timer(1000, this);
timer.start();
updateTime(testTimeOut);
}
@Override
public void actionPerformed(ActionEvent e) {
long leftTime = endTime - System.currentTimeMillis();
if (leftTime < 0) {
timer.stop();
setFailureReason(FAILURE_REASON
+ "Timeout - User did not perform testing.");
latch.countDown();
}
updateTime(leftTime);
}
private void updateTime(final long leftTime) {
if (leftTime < 0) {
label.setText("Test timeout: 00:00:00");
return;
}
long hours = leftTime / 3_600_000;
long minutes = (leftTime - hours * 3_600_000) / 60_000;
long seconds = (leftTime - hours * 3_600_000 - minutes * 60_000) / 1_000;
label.setText(String.format("Test timeout: %02d:%02d:%02d",
hours, minutes, seconds));
}
public void stop() {
timer.stop();
}
}
private static final class WindowClosingHandler extends WindowAdapter { private static final class WindowClosingHandler extends WindowAdapter {
@Override @Override
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
testFailedReason = FAILURE_REASON setFailureReason(FAILURE_REASON
+ "User closed a window"; + "User closed a window");
failed = true;
latch.countDown(); latch.countDown();
} }
} }
@ -579,14 +624,28 @@ public class PassFailJFrame {
JOptionPane.INFORMATION_MESSAGE); JOptionPane.INFORMATION_MESSAGE);
} }
private static String convertMillisToTimeStr(long millis) { /**
if (millis < 0) { * Sets the failure reason which describes why the test fails.
return "00:00:00"; * This method ensures the {@code failureReason} field does not change
* after it's set to a non-{@code null} value.
* @param reason the description of why the test fails
* @throws IllegalArgumentException if the {@code reason} parameter
* is {@code null}
*/
private static synchronized void setFailureReason(final String reason) {
if (reason == null) {
throw new IllegalArgumentException("The failure reason must not be null");
} }
long hours = millis / 3_600_000; if (failureReason == null) {
long minutes = (millis - hours * 3_600_000) / 60_000; failureReason = reason;
long seconds = (millis - hours * 3_600_000 - minutes * 60_000) / 1_000; }
return String.format("%02d:%02d:%02d", hours, minutes, seconds); }
/**
* {@return the description of why the test fails}
*/
private static synchronized String getFailureReason() {
return failureReason;
} }
/** /**
@ -607,30 +666,18 @@ public class PassFailJFrame {
latch.await(); latch.await();
invokeAndWait(PassFailJFrame::disposeWindows); invokeAndWait(PassFailJFrame::disposeWindows);
if (timeout) { String failure = getFailureReason();
throw new RuntimeException(testFailedReason); if (failure != null) {
} throw new RuntimeException(failure);
if (failed) {
throw new RuntimeException("Test failed! : " + testFailedReason);
} }
System.out.println("Test passed!"); System.out.println("Test passed!");
} }
/** /**
* Disposes of all the windows. It disposes of the test instruction frame * Requests the description of the test failure reason from the tester.
* and all other windows added via {@link #addTestWindow(Window)}.
*/ */
private static synchronized void disposeWindows() { private static void requestFailureReason() {
windowList.forEach(Window::dispose);
}
/**
* Read the test failure reason and add the reason to the test result
* example in the jtreg .jtr file.
*/
private static void getFailureReason() {
final JDialog dialog = new JDialog(frame, "Test Failure ", true); final JDialog dialog = new JDialog(frame, "Test Failure ", true);
dialog.setTitle("Failure reason"); dialog.setTitle("Failure reason");
JPanel jPanel = new JPanel(new BorderLayout()); JPanel jPanel = new JPanel(new BorderLayout());
@ -638,7 +685,9 @@ public class PassFailJFrame {
JButton okButton = new JButton("OK"); JButton okButton = new JButton("OK");
okButton.addActionListener((ae) -> { okButton.addActionListener((ae) -> {
testFailedReason = FAILURE_REASON + jTextArea.getText(); String text = jTextArea.getText();
setFailureReason(FAILURE_REASON
+ (!text.isEmpty() ? text : EMPTY_REASON));
dialog.setVisible(false); dialog.setVisible(false);
}); });
@ -653,11 +702,22 @@ public class PassFailJFrame {
dialog.pack(); dialog.pack();
dialog.setVisible(true); dialog.setVisible(true);
failed = true; // Ensure the test fails even if the dialog is closed
// without clicking the OK button
setFailureReason(FAILURE_REASON + EMPTY_REASON);
dialog.dispose(); dialog.dispose();
latch.countDown(); latch.countDown();
} }
/**
* Disposes of all the windows. It disposes of the test instruction frame
* and all other windows added via {@link #addTestWindow(Window)}.
*/
private static synchronized void disposeWindows() {
windowList.forEach(Window::dispose);
}
private static void positionInstructionFrame(final Position position) { private static void positionInstructionFrame(final Position position) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
@ -827,8 +887,7 @@ public class PassFailJFrame {
* @param reason the reason why the test is failed * @param reason the reason why the test is failed
*/ */
public static void forceFail(String reason) { public static void forceFail(String reason) {
failed = true; setFailureReason(FAILURE_REASON + reason);
testFailedReason = FAILURE_REASON + reason;
latch.countDown(); latch.countDown();
} }