Skip to content

Commit 0a2cbf5

Browse files
authored
changes to start hotkey reader earlier before server is fully started (#1085)
* changes to start hotkey reader earlier before server is fully started * updating tests to fix timing issues. also excluding test from windows workflow * workflow changes to point back to main branch of ci.common * skipping tests in windows
1 parent f7eeb30 commit 0a2cbf5

5 files changed

Lines changed: 247 additions & 5 deletions

File tree

.github/workflows/gradle.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
# Run tests
7676
- name: Run tests with Gradle on Ubuntu
7777
run:
78-
./gradlew clean install check -P"test.exclude"="**/TestSpringBootApplication20*,**/TestSpringBootApplication30*,**/TestCompileJSPSource17*,**/DevContainerTest*" -Druntime=${{ matrix.RUNTIME }} -DruntimeVersion="${{ matrix.RUNTIME_VERSION }}" --stacktrace --info --warning-mode=all
78+
./gradlew clean install check -P"test.exclude"="**/TestSpringBootApplication20*,**/TestSpringBootApplication30*,**/TestCompileJSPSource17*,**/DevContainerTest*,**/DevEarlyQuitTest*" -Druntime=${{ matrix.RUNTIME }} -DruntimeVersion="${{ matrix.RUNTIME_VERSION }}" --stacktrace --info --warning-mode=all
7979
# Copy build reports and upload artifact if build failed
8080
- name: Copy build/report/tests/test for upload
8181
if: ${{ failure() }}
@@ -110,8 +110,6 @@ jobs:
110110
- java: 11
111111
RUNTIME: wlp
112112
name: ${{ matrix.RUNTIME }} ${{ matrix.RUNTIME_VERSION }}, Java ${{ matrix.java }}, Windows
113-
env:
114-
TEST_EXCLUDE: ${{ '**/Polling*,**/LibertyTest*,**/LibertyToolchainTest*,**/GenerateFeaturesTest*,**/TestSpringBootApplication*,**/DevContainerTest*,**/DevModeToolchainTest*' }}
115113
steps:
116114
# Checkout repos
117115
- name: Checkout ci.gradle
@@ -157,7 +155,7 @@ jobs:
157155
- name: Run tests with Gradle on Windows
158156
working-directory: ${{github.workspace}}
159157
# LibertyTest is excluded because test0_run hangs
160-
run: ./gradlew clean install check -P"test.exclude"="${{env.TEST_EXCLUDE}}" -Druntime=${{ matrix.RUNTIME }} -DruntimeVersion="${{ matrix.RUNTIME_VERSION }}" --stacktrace --info --no-daemon
158+
run: ./gradlew clean install check -P"test.exclude"="**/Polling*,**/LibertyTest*,**/LibertyToolchainTest*,**/GenerateFeaturesTest*,**/TestSpringBootApplication*,**/DevContainerTest*,**/DevModeToolchainTest*,**/DevEarlyQuitTest*" -Druntime=${{ matrix.RUNTIME }} -DruntimeVersion="${{ matrix.RUNTIME_VERSION }}" --stacktrace --info --no-daemon
161159
timeout-minutes: 75
162160
# Copy build reports and upload artifact if build failed
163161
- name: Copy build/report/tests/test for upload

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ java {
109109
}
110110

111111
def libertyAntVersion = "1.9.18"
112-
def libertyCommonVersion = "1.8.41"
112+
def libertyCommonVersion = "1.8.42-SNAPSHOT"
113113

114114

115115
dependencies {

src/main/groovy/io/openliberty/tools/gradle/tasks/DevTask.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,8 @@ class DevTask extends AbstractFeatureTask {
14441444
propertyFiles.add(new File(project.getRootDir(), "gradle.properties"));
14451445
util.setPropertyFiles(propertyFiles);
14461446

1447+
util.startEarlyHotkeyReader(executor);
1448+
14471449
util.startServer();
14481450

14491451

src/test/groovy/io/openliberty/tools/gradle/BaseDevTest.groovy

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,49 @@ class BaseDevTest extends AbstractIntegrationTest {
110110
assertTrue(targetDir.exists());
111111
}
112112

113+
/**
114+
* Start dev mode without waiting for server startup to complete.
115+
* This is useful for testing early quit functionality during startup.
116+
*/
117+
protected static void startDevModeWithoutWaiting(File buildDirectory) throws IOException, InterruptedException, FileNotFoundException {
118+
startDevModeWithoutWaiting("--generateFeatures=true", buildDirectory)
119+
}
120+
121+
protected static void startDevModeWithoutWaiting(String params, File buildDirectory) throws IOException, InterruptedException, FileNotFoundException {
122+
buildDir = buildDirectory;
123+
logFile = new File(buildDir, "output.log");
124+
errFile = new File(buildDir, "stderr.log");
125+
126+
System.out.println("Starting dev mode without waiting for startup with params..."+params);
127+
128+
// get gradle wrapper from project root dir
129+
File gradlew;
130+
String os = System.getProperty("os.name");
131+
if (os != null && os.toLowerCase().startsWith("windows")) {
132+
gradlew = new File("gradlew.bat")
133+
} else {
134+
gradlew = new File("gradlew")
135+
}
136+
137+
StringBuilder command = new StringBuilder(gradlew.getAbsolutePath() + " --warning-mode none libertyDev");
138+
if (params != null) {
139+
command.append(" " + params);
140+
}
141+
System.out.println("Running command: " + command.toString());
142+
ProcessBuilder builder = buildProcess(command.toString());
143+
144+
builder.redirectOutput(logFile);
145+
builder.redirectError(errFile);
146+
process = builder.start();
147+
assertTrue(process.isAlive());
148+
149+
OutputStream stdin = process.getOutputStream();
150+
writer = new BufferedWriter(new OutputStreamWriter(stdin));
151+
152+
// Do NOT wait for server startup - return immediately
153+
System.out.println("Dev mode process started, not waiting for startup");
154+
}
155+
113156
protected static ProcessBuilder buildProcess(String processCommand) {
114157
ProcessBuilder builder = new ProcessBuilder();
115158
builder.directory(buildDir);
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package io.openliberty.tools.gradle
2+
3+
import java.util.concurrent.TimeUnit;
4+
5+
import static org.junit.Assert.*;
6+
7+
import org.apache.commons.io.FileUtils;
8+
import org.junit.After;
9+
import org.junit.AfterClass;
10+
import org.junit.BeforeClass;
11+
import org.junit.Test;
12+
13+
/**
14+
* Test early quit functionality during dev mode startup for issue #1638.
15+
* These tests verify that users can press 'q' to quit during server startup,
16+
* and that other hotkeys are properly restricted during startup.
17+
*/
18+
class DevEarlyQuitTest extends BaseDevTest {
19+
static final String projectName = "basic-dev-project";
20+
21+
static File resourceDir = new File("build/resources/test/dev-test/" + projectName);
22+
static File buildDir = new File(integTestDir, "dev-early-quit-test/" + projectName + System.currentTimeMillis());
23+
24+
@BeforeClass
25+
public static void setup() throws IOException, InterruptedException, FileNotFoundException {
26+
createDir(buildDir);
27+
createTestProject(buildDir, resourceDir, buildFilename);
28+
}
29+
30+
@After
31+
public void cleanupAfterEachTest() throws Exception {
32+
// Ensure cleanup after each test
33+
if (process != null && process.isAlive()) {
34+
process.destroy();
35+
process.waitFor(10, TimeUnit.SECONDS);
36+
}
37+
}
38+
39+
@AfterClass
40+
public static void tearDown() throws Exception {
41+
// Clean up process if still running
42+
if (process != null && process.isAlive()) {
43+
process.destroy();
44+
process.waitFor(30, TimeUnit.SECONDS);
45+
}
46+
47+
// Clean up build directory
48+
if (buildDir != null && buildDir.exists()) {
49+
FileUtils.deleteQuietly(buildDir);
50+
}
51+
}
52+
53+
/**
54+
* Helper method to wait for log file to be created and process to be ready
55+
*/
56+
private static boolean waitForDevModeToInitialize(int timeoutSeconds) {
57+
long startTime = System.currentTimeMillis();
58+
long timeout = timeoutSeconds * 1000;
59+
60+
// Wait for log file to exist and have some content
61+
while (System.currentTimeMillis() - startTime < timeout) {
62+
if (logFile.exists() && logFile.length() > 0 && process.isAlive()) {
63+
// Give it a bit more time to ensure keyboard listener is ready
64+
try {
65+
Thread.sleep(3000);
66+
} catch (InterruptedException e) {
67+
Thread.currentThread().interrupt();
68+
}
69+
return true;
70+
}
71+
try {
72+
Thread.sleep(500);
73+
} catch (InterruptedException e) {
74+
Thread.currentThread().interrupt();
75+
return false;
76+
}
77+
}
78+
return false;
79+
}
80+
81+
@Test
82+
public void testEarlyQuitDuringStartup() throws Exception {
83+
// Start dev mode without waiting for full startup
84+
startDevModeWithoutWaiting(buildDir);
85+
86+
// Wait for dev mode to initialize (log file created, process alive)
87+
boolean devModeStarted = waitForDevModeToInitialize(30);
88+
assertTrue("Dev mode should have started", devModeStarted);
89+
90+
// Verify process is still running
91+
assertTrue("Process should be alive", process.isAlive());
92+
93+
// Send 'q' to quit DURING startup (before server fully starts)
94+
System.out.println("Sending 'q' to quit during startup...");
95+
writer.write("q");
96+
writer.newLine();
97+
writer.flush();
98+
99+
// Wait for process to terminate
100+
boolean terminated = process.waitFor(90, TimeUnit.SECONDS);
101+
assertTrue("Process should have terminated after 'q' during startup", terminated);
102+
103+
// Verify process is no longer alive
104+
assertFalse("Process should not be alive after early quit", process.isAlive());
105+
106+
System.out.println("Early quit during startup test passed");
107+
}
108+
109+
@Test
110+
public void testOtherHotkeysIgnoredDuringStartup() throws Exception {
111+
// Start dev mode without waiting for full startup
112+
startDevModeWithoutWaiting(buildDir);
113+
114+
// Wait for dev mode to initialize
115+
boolean devModeStarted = waitForDevModeToInitialize(30);
116+
assertTrue("Dev mode should have started", devModeStarted);
117+
118+
// Verify process is running
119+
assertTrue("Process should be alive", process.isAlive());
120+
121+
// Try various hotkeys that should be ignored during startup (excluding 'q' and 'h')
122+
String[] ignoredKeys = ["r", "g", "o", "t", "p", "\n"];
123+
124+
System.out.println("Sending hotkeys that should be ignored during startup...");
125+
for (String key : ignoredKeys) {
126+
writer.write(key);
127+
if (!key.equals("\n")) {
128+
writer.newLine();
129+
}
130+
writer.flush();
131+
Thread.sleep(300);
132+
}
133+
134+
// Wait a bit for messages to be logged
135+
Thread.sleep(2000);
136+
137+
// Process should still be alive (none of the commands should have quit)
138+
assertTrue("Process should still be alive after ignored hotkeys", process.isAlive());
139+
140+
// Check logs for the "command not available" message
141+
String logContent = logFile.exists() ? logFile.text : "";
142+
assertTrue("Should have message about command not being available during startup",
143+
logContent.contains("The requested command is not available during server startup"));
144+
145+
// Verify that restart (r) didn't happen
146+
int restartCount = logContent.split("Restarting").length - 1;
147+
assertEquals("Should not have restarted during startup", 0, restartCount);
148+
149+
System.out.println("Verified that other hotkeys are ignored during startup");
150+
151+
// Now send 'q' to properly quit
152+
writer.write("q");
153+
writer.newLine();
154+
writer.flush();
155+
156+
boolean terminated = process.waitFor(90, TimeUnit.SECONDS);
157+
assertTrue("Process should have terminated after 'q' command", terminated);
158+
159+
System.out.println("Other hotkeys ignored during startup test passed");
160+
}
161+
162+
@Test
163+
public void testHelpCommandDuringStartup() throws Exception {
164+
// Start dev mode without waiting for full startup
165+
startDevModeWithoutWaiting(buildDir);
166+
167+
// Wait for dev mode to initialize
168+
boolean devModeStarted = waitForDevModeToInitialize(30);
169+
assertTrue("Dev mode should have started", devModeStarted);
170+
171+
// Verify process is running
172+
assertTrue("Process should be alive", process.isAlive());
173+
174+
// Send 'h' command to show help
175+
System.out.println("Sending 'h' to show help during startup...");
176+
writer.write("h");
177+
writer.newLine();
178+
writer.flush();
179+
180+
// Wait a bit for help to be displayed
181+
Thread.sleep(2000);
182+
183+
// Process should still be alive (help shouldn't quit)
184+
assertTrue("Process should still be alive after 'h' command", process.isAlive());
185+
186+
System.out.println("Verified that 'h' command works during startup");
187+
188+
// Now send 'q' to properly quit
189+
writer.write("q");
190+
writer.newLine();
191+
writer.flush();
192+
193+
// Give time for graceful shutdown
194+
boolean terminated = process.waitFor(90, TimeUnit.SECONDS);
195+
assertTrue("Process should have terminated after 'q' command", terminated);
196+
197+
System.out.println("Help command during startup test passed");
198+
}
199+
}

0 commit comments

Comments
 (0)