Skip to content

Commit 772cfe8

Browse files
core: cleanup registration to be part of teardown (#824)
1 parent 24016cd commit 772cfe8

14 files changed

Lines changed: 199 additions & 52 deletions

File tree

webtau-browser/src/main/java/org/testingisdocumenting/webtau/browser/driver/WebDriverCreator.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.openqa.selenium.firefox.FirefoxDriver;
2121
import org.openqa.selenium.firefox.FirefoxOptions;
2222
import org.testingisdocumenting.webtau.browser.BrowserConfig;
23+
import org.testingisdocumenting.webtau.cleanup.CleanupRegistration;
2324
import org.testingisdocumenting.webtau.console.ConsoleOutputs;
2425
import org.testingisdocumenting.webtau.console.ansi.Color;
2526
import io.github.bonigarcia.wdm.WebDriverManager;
@@ -38,6 +39,8 @@ public class WebDriverCreator {
3839

3940
private static final List<WebDriver> drivers = Collections.synchronizedList(new ArrayList<>());
4041

42+
private static final ThreadLocal<Boolean> disableBrowserClose = ThreadLocal.withInitial(() -> false);
43+
4144
static {
4245
registerCleanup();
4346
}
@@ -51,6 +54,20 @@ public static WebDriver create() {
5154
return register(driver);
5255
}
5356

57+
public static void quitAll() {
58+
drivers.forEach(WebDriverCreator::quitWithoutRemove);
59+
drivers.clear();
60+
}
61+
62+
public static void withDisabledBrowserAutoClose(Runnable code) {
63+
try {
64+
disableBrowserClose.set(true);
65+
code.run();
66+
} finally {
67+
disableBrowserClose.set(false);
68+
}
69+
}
70+
5471
static void quit(WebDriver driver) {
5572
quitWithoutRemove(driver);
5673
drivers.remove(driver);
@@ -147,11 +164,6 @@ private static void setupDriverManagerConfig() {
147164
System.setProperty("wdm.forceCache", "true");
148165
}
149166

150-
public static void quitAll() {
151-
drivers.forEach(WebDriverCreator::quitWithoutRemove);
152-
drivers.clear();
153-
}
154-
155167
private static WebDriver register(WebDriver driver) {
156168
drivers.add(driver);
157169
WebDriverCreatorListeners.afterDriverCreation(driver);
@@ -170,6 +182,8 @@ private static void initState(WebDriver driver) {
170182
}
171183

172184
private static void registerCleanup() {
173-
Runtime.getRuntime().addShutdownHook(new Thread(WebDriverCreator::quitAll));
185+
CleanupRegistration.registerForCleanup("closing", "closed", "browsers",
186+
() -> !disableBrowserClose.get() && !drivers.isEmpty(),
187+
WebDriverCreator::quitAll);
174188
}
175189
}

webtau-cli/src/main/java/org/testingisdocumenting/webtau/cli/CliBackgroundCommandManager.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.testingisdocumenting.webtau.cli;
1818

19+
import org.testingisdocumenting.webtau.cleanup.CleanupRegistration;
1920
import org.testingisdocumenting.webtau.reporter.TestListener;
2021
import org.testingisdocumenting.webtau.reporter.TestResultPayload;
2122
import org.testingisdocumenting.webtau.reporter.WebTauTest;
@@ -51,12 +52,6 @@ static void remove(CliBackgroundCommand backgroundCommand) {
5152
runningCommands.remove(backgroundCommand.getBackgroundProcess().getPid());
5253
}
5354

54-
@Override
55-
public void afterAllTests() {
56-
destroyActiveProcesses();
57-
runningCommands.clear();
58-
}
59-
6055
@Override
6156
public void beforeTestRun(WebTauTest test) {
6257
localRunningCommands.get().clear();
@@ -78,10 +73,11 @@ public void afterTestRun(WebTauTest test) {
7873
test.addTestResultPayload(new TestResultPayload("cliBackground", backgroundCommands));
7974
}
8075

81-
static void destroyActiveProcesses() {
76+
static synchronized void destroyActiveProcesses() {
8277
runningCommands.values().stream()
8378
.filter(CliBackgroundCommand::isActive)
8479
.forEach(CliBackgroundCommand::stop);
80+
runningCommands.clear();
8581
}
8682

8783
private static void validateProcessActive(CliBackgroundCommand backgroundCommand) {
@@ -94,10 +90,9 @@ private static class LazyShutdownHook {
9490
private static final LazyShutdownHook INSTANCE = new LazyShutdownHook();
9591

9692
private LazyShutdownHook() {
97-
// afterAllTests may not be called if cli module is used outside of a test runner
98-
// that's why we register clearing of background processed as hook
99-
Runtime.getRuntime().addShutdownHook(
100-
new Thread(CliBackgroundCommandManager::destroyActiveProcesses));
93+
CleanupRegistration.registerForCleanup("shutting down", "shut down", "cli background processes",
94+
() -> runningCommands.values().stream().anyMatch(CliBackgroundCommand::isActive),
95+
CliBackgroundCommandManager::destroyActiveProcesses);
10196
}
10297

10398
// to trigger class loading and shutdown hook registration
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2021 webtau maintainers
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.testingisdocumenting.webtau.cleanup;
18+
19+
import org.testingisdocumenting.webtau.reporter.TestListener;
20+
import org.testingisdocumenting.webtau.reporter.WebTauStep;
21+
22+
import java.util.ArrayList;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.function.Supplier;
26+
27+
import static org.testingisdocumenting.webtau.reporter.IntegrationTestsMessageBuilder.action;
28+
import static org.testingisdocumenting.webtau.reporter.IntegrationTestsMessageBuilder.id;
29+
import static org.testingisdocumenting.webtau.reporter.TokenizedMessage.tokenizedMessage;
30+
31+
/**
32+
* centralized place to register cleanup actions at the end of all test runs
33+
* as a catch-all cleans not cleaned things on exit
34+
*/
35+
public class CleanupRegistration implements TestListener {
36+
private static final List<CleanupEntry> registered = Collections.synchronizedList(new ArrayList<>());
37+
38+
@Override
39+
public void afterAllTests() {
40+
cleanup();
41+
}
42+
43+
public static void registerForCleanup(String action,
44+
String actionCompleted,
45+
String id,
46+
Supplier<Boolean> isValid,
47+
Runnable cleanupCode) {
48+
registered.add(new CleanupEntry(action, actionCompleted, id, isValid, cleanupCode));
49+
50+
// lazy shutdown hook init to avoid nested shutdowns in case of
51+
// JUnit4 runner that calls afterAllTests in shutdown hook
52+
ShutdownHook.INSTANCE.noOp();
53+
}
54+
55+
private static void cleanup() {
56+
registered.stream()
57+
.filter(CleanupEntry::isValid)
58+
.forEach(CleanupRegistration::cleanup);
59+
registered.removeIf(CleanupEntry::isValid);
60+
}
61+
62+
private static void cleanup(CleanupEntry entry) {
63+
WebTauStep.createAndExecuteStep(
64+
tokenizedMessage(action(entry.action), id(entry.id)),
65+
() -> tokenizedMessage(action(entry.actionCompleted), id(entry.id)),
66+
entry.cleanupCode);
67+
}
68+
69+
private static void registerShutdownHook() {
70+
Runtime.getRuntime().addShutdownHook(new Thread(CleanupRegistration::cleanup));
71+
}
72+
73+
private static class CleanupEntry {
74+
String action;
75+
String actionCompleted;
76+
String id;
77+
Supplier<Boolean> isValid;
78+
Runnable cleanupCode;
79+
80+
public CleanupEntry(String action,
81+
String actionCompleted,
82+
String id,
83+
Supplier<Boolean> isValid,
84+
Runnable cleanupCode) {
85+
this.action = action;
86+
this.actionCompleted = actionCompleted;
87+
this.id = id;
88+
this.isValid = isValid;
89+
this.cleanupCode = cleanupCode;
90+
}
91+
92+
boolean isValid() {
93+
return this.isValid.get();
94+
}
95+
}
96+
97+
private static class ShutdownHook {
98+
final static ShutdownHook INSTANCE = new ShutdownHook();
99+
100+
private ShutdownHook() {
101+
registerShutdownHook();
102+
}
103+
104+
public void noOp() {
105+
}
106+
}
107+
}
Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,18 @@
1-
org.testingisdocumenting.webtau.reporter.TestResultPayloadExtractorTestListener
1+
#
2+
# Copyright 2021 webtau maintainers
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
org.testingisdocumenting.webtau.reporter.TestResultPayloadExtractorTestListener
18+
org.testingisdocumenting.webtau.cleanup.CleanupRegistration

webtau-feature-testing/examples/listeners/ServerAutoStartListener.groovy

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,13 @@ import org.testingisdocumenting.webtau.version.WebtauVersion
2424
import static org.testingisdocumenting.webtau.WebTauGroovyDsl.*
2525

2626
class ServerAutoStartListener implements TestListener {
27-
CliBackgroundCommand server
28-
2927
@Override
3028
void beforeFirstTest() {
3129
def jarName = "webtau-testapp-${WebtauVersion.version}-exec.jar"
32-
server = cli.runInBackground("java -jar ../webtau-testapp/target/${jarName} --server.port=0 --spring.profiles.active=qa")
30+
CliBackgroundCommand server = cli.runInBackground("java -jar ../webtau-testapp/target/${jarName} --server.port=0 --spring.profiles.active=qa")
3331
server.output.waitTo(contain("Tomcat started on port(s)"), 40_000)
3432

3533
def port = RegexpUtils.extractByRegexp(server.output.get(), /Tomcat started on port\(s\): (\d+)/)
3634
cfg.baseUrl = "http://localhost:${port}"
3735
}
38-
39-
@Override
40-
void afterAllTests() {
41-
if (server) {
42-
server.stop()
43-
}
44-
}
4536
}

webtau-feature-testing/src/main/groovy/org/testingisdocumenting/webtau/featuretesting/WebTauEndToEndTestRunner.groovy

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
package org.testingisdocumenting.webtau.featuretesting
1919

2020
import org.eclipse.jetty.server.Handler
21+
import org.testingisdocumenting.webtau.browser.driver.WebDriverCreator
2122
import org.testingisdocumenting.webtau.cfg.WebTauConfig
2223
import org.testingisdocumenting.webtau.cli.WebTauCliApp
23-
import org.testingisdocumenting.webtau.documentation.DocumentationArtifactsLocation
2424
import org.testingisdocumenting.webtau.http.testserver.TestServer
2525
import org.testingisdocumenting.webtau.reporter.*
2626

@@ -80,8 +80,11 @@ class WebTauEndToEndTestRunner {
8080

8181
capturedStepsSummary = [:].withDefault { 0 }
8282

83-
cliApp.start(WebTauCliApp.WebDriverBehavior.KeepWebDriversOpen) { exitCode ->
84-
testDetails.exitCode = exitCode
83+
// this is for optimization so browsers stay open in between feature test runs
84+
WebDriverCreator.withDisabledBrowserAutoClose {
85+
cliApp.start { exitCode ->
86+
testDetails.exitCode = exitCode
87+
}
8588
}
8689

8790
testDetails.scenarioDetails = buildScenarioDetails(cliApp.runner.report)

webtau-feature-testing/test-expectations/scenarios/cli/backgroundCommandAutoKill/run-details.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"scenario" : "after all tests",
1010
"shortContainerId" : "Teardown",
1111
"stepsSummary" : {
12-
"numberOfSuccessful" : 1
12+
"numberOfSuccessful" : 2
1313
}
1414
}, {
1515
"scenario" : "dummy scenario",

webtau-feature-testing/test-expectations/scenarios/cli/backgroundExamples/run-details.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"scenario" : "after all tests",
44
"shortContainerId" : "Teardown",
55
"stepsSummary" : {
6-
"numberOfSuccessful" : 1
6+
"numberOfSuccessful" : 2
77
}
88
}, {
99
"scenario" : "run in background",

webtau-feature-testing/test-expectations/scenarios/fs/archive/run-details.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
{
22
"scenarioDetails" : [ {
3+
"scenario" : "after all tests",
4+
"shortContainerId" : "Teardown",
5+
"stepsSummary" : {
6+
"numberOfSuccessful" : 1
7+
}
8+
}, {
39
"scenario" : "unzip",
410
"shortContainerId" : "archive.groovy",
511
"stepsSummary" : {

webtau-feature-testing/test-expectations/scenarios/fs/copy/run-details.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
{
22
"scenarioDetails" : [ {
3+
"scenario" : "after all tests",
4+
"shortContainerId" : "Teardown",
5+
"stepsSummary" : {
6+
"numberOfSuccessful" : 1
7+
}
8+
}, {
39
"scenario" : "copy file to temp dir",
410
"shortContainerId" : "copy.groovy",
511
"stepsSummary" : {

0 commit comments

Comments
 (0)