Skip to content

Commit

Permalink
8316017: Refactor timeout handler in PassFailJFrame
Browse files Browse the repository at this point in the history
Reviewed-by: prr
  • Loading branch information
aivanov-jdk committed Oct 25, 2023
1 parent 202c013 commit 3abd772
Showing 1 changed file with 116 additions and 57 deletions.
173 changes: 116 additions & 57 deletions test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
Expand Down Expand Up @@ -155,14 +157,26 @@ public class PassFailJFrame {
* Prefix for the user-provided failure reason.
*/
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 Timer timer = new Timer(0, null);

private static final CountDownLatch latch = new CountDownLatch(1);

private static volatile boolean failed;
private static volatile boolean timeout;
private static volatile String testFailedReason;
private static TimeoutHandler timeoutHandler;

/**
* 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);

Expand Down Expand Up @@ -319,42 +333,27 @@ private static void createUI(String title, String instructions,
frame = new JFrame(title);
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>")
? configureHTML(instructions, rows, columns)
: configurePlainText(instructions, rows, columns);
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);

JButton btnPass = new JButton("Pass");
btnPass.addActionListener((e) -> {
latch.countDown();
timer.stop();
timeoutHandler.stop();
});

JButton btnFail = new JButton("Fail");
btnFail.addActionListener((e) -> {
getFailureReason();
timer.stop();
requestFailureReason();
timeoutHandler.stop();
});

JPanel buttonsPanel = new JPanel();
Expand Down Expand Up @@ -480,12 +479,58 @@ public interface InstructionUI {
}


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 {
@Override
public void windowClosing(WindowEvent e) {
testFailedReason = FAILURE_REASON
+ "User closed a window";
failed = true;
setFailureReason(FAILURE_REASON
+ "User closed a window");
latch.countDown();
}
}
Expand Down Expand Up @@ -579,14 +624,28 @@ private static void captureScreen(CaptureType type) {
JOptionPane.INFORMATION_MESSAGE);
}

private static String convertMillisToTimeStr(long millis) {
if (millis < 0) {
return "00:00:00";
/**
* Sets the failure reason which describes why the test fails.
* 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;
long minutes = (millis - hours * 3_600_000) / 60_000;
long seconds = (millis - hours * 3_600_000 - minutes * 60_000) / 1_000;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
if (failureReason == null) {
failureReason = reason;
}
}

/**
* {@return the description of why the test fails}
*/
private static synchronized String getFailureReason() {
return failureReason;
}

/**
Expand All @@ -607,38 +666,28 @@ public void awaitAndCheck() throws InterruptedException, InvocationTargetExcepti
latch.await();
invokeAndWait(PassFailJFrame::disposeWindows);

if (timeout) {
throw new RuntimeException(testFailedReason);
}

if (failed) {
throw new RuntimeException("Test failed! : " + testFailedReason);
String failure = getFailureReason();
if (failure != null) {
throw new RuntimeException(failure);
}

System.out.println("Test passed!");
}

/**
* 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);
}

/**
* Read the test failure reason and add the reason to the test result
* example in the jtreg .jtr file.
* Requests the description of the test failure reason from the tester.
*/
private static void getFailureReason() {
private static void requestFailureReason() {
final JDialog dialog = new JDialog(frame, "Test Failure ", true);
dialog.setTitle("Failure reason");
JPanel jPanel = new JPanel(new BorderLayout());
JTextArea jTextArea = new JTextArea(5, 20);

JButton okButton = new JButton("OK");
okButton.addActionListener((ae) -> {
testFailedReason = FAILURE_REASON + jTextArea.getText();
String text = jTextArea.getText();
setFailureReason(FAILURE_REASON
+ (!text.isEmpty() ? text : EMPTY_REASON));
dialog.setVisible(false);
});

Expand All @@ -653,11 +702,22 @@ private static void getFailureReason() {
dialog.pack();
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();
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) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

Expand Down Expand Up @@ -827,8 +887,7 @@ public static void forceFail() {
* @param reason the reason why the test is failed
*/
public static void forceFail(String reason) {
failed = true;
testFailedReason = FAILURE_REASON + reason;
setFailureReason(FAILURE_REASON + reason);
latch.countDown();
}

Expand Down

3 comments on commit 3abd772

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aivanov-jdk
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/backport jdk21u

@openjdk
Copy link

@openjdk openjdk bot commented on 3abd772 Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aivanov-jdk the backport was successfully created on the branch aivanov-jdk-backport-3abd7726 in my personal fork of openjdk/jdk21u. To create a pull request with this backport targeting openjdk/jdk21u:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 3abd7726 from the openjdk/jdk repository.

The commit being backported was authored by Alexey Ivanov on 25 Oct 2023 and was reviewed by Phil Race.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk21u:

$ git fetch https://github.com/openjdk-bots/jdk21u.git aivanov-jdk-backport-3abd7726:aivanov-jdk-backport-3abd7726
$ git checkout aivanov-jdk-backport-3abd7726
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk21u.git aivanov-jdk-backport-3abd7726

Please sign in to comment.