Skip to content

Commit 89e0125

Browse files
committed
Execute before/after methods in session tests in correct instance #903
The SessionTestExtension intercepts test execution to deploy it to a remote executor starting a dedicated Eclipse application / session. In both the host and the remote-execution environment, a test runner executes the method and the interceptor decides how it is processed. Currently, the before/after methods of the test class are not properly considered, thus they are executed on both the host and the remote system, which can lead to unintended behavior This change improves consistency and comprehensibility of the session test execution. To this end, it converts the SessionTestExtension into an interface and splits it up into two realizations, one for the host and one for the remote execution, each ensuring that test methods are properly processed in their environment. It also ensures that before/after methods are, by default, only executed remotely, i.e., where the actual test method is executed, as these methods usually do some recurring preparation/cleanup work that needs to be done on the same test class instances state than on which the test is executed. In addition, the `ExecuteInHost` annotation is introduced, which can be attached to any before/after and ordinary test method to execute it in the host instance instead of the remote session. This can be used for general setup of the test instance at the host, but also for adding some reconfiguration or validation work done on the host between multiple sessions. Contributes to #903
1 parent 390d8aa commit 89e0125

File tree

11 files changed

+268
-70
lines changed

11 files changed

+268
-70
lines changed

resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/session/TestBug294854.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.eclipse.core.runtime.CoreException;
3333
import org.eclipse.core.runtime.IPath;
3434
import org.eclipse.core.tests.harness.session.CustomSessionWorkspace;
35+
import org.eclipse.core.tests.harness.session.ExecuteInHost;
3536
import org.eclipse.core.tests.harness.session.SessionShouldError;
3637
import org.eclipse.core.tests.harness.session.SessionTestExtension;
3738
import org.junit.jupiter.api.BeforeEach;
@@ -83,6 +84,7 @@ private static boolean checkProjectIsOpen(String name) {
8384
}
8485

8586
@BeforeEach
87+
@ExecuteInHost
8688
public void resetWorkspace(TestInfo testInfo) throws IOException {
8789
if (testInfo.getTags().contains(RESET_WORKSPACE_BEFORE_TAG)) {
8890
Path newWorkspace = Files.createTempDirectory(null);

resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/session/TestBug323833.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.eclipse.core.runtime.CoreException;
3131
import org.eclipse.core.runtime.Platform.OS;
3232
import org.eclipse.core.tests.harness.session.CustomSessionWorkspace;
33+
import org.eclipse.core.tests.harness.session.ExecuteInHost;
3334
import org.eclipse.core.tests.harness.session.SessionTestExtension;
3435
import org.junit.jupiter.api.AfterAll;
3536
import org.junit.jupiter.api.MethodOrderer;
@@ -52,6 +53,7 @@ public class TestBug323833 {
5253
.withCustomization(sessionWorkspace).create();
5354

5455
@AfterAll
56+
@ExecuteInHost
5557
public static void restoreFileWriabilityForCleanup() throws CoreException, IOException {
5658
sessionWorkspace.getWorkspaceDirectory().resolve(READONLY_FILE_NAME).toFile().setWritable(true, false);
5759
}

resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/session/TestWorkspaceEncodingExistingWorkspace.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.core.resources.IWorkspace;
2424
import org.eclipse.core.resources.ResourcesPlugin;
2525
import org.eclipse.core.tests.harness.session.CustomSessionWorkspace;
26+
import org.eclipse.core.tests.harness.session.ExecuteInHost;
2627
import org.eclipse.core.tests.harness.session.SessionTestExtension;
2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
@@ -40,6 +41,7 @@ public class TestWorkspaceEncodingExistingWorkspace {
4041
.withCustomization(sessionWorkspace).create();
4142

4243
@BeforeEach
44+
@ExecuteInHost
4345
public void setUpWorkspace() throws IOException {
4446
Path projectsTree = sessionWorkspace.getWorkspaceDirectory().resolve(".metadata/.plugins/org.eclipse.core.resources/.projects");
4547
Files.createDirectories(projectsTree);

resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/session/TestWorkspaceEncodingWithJvmArgs.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.eclipse.core.resources.IWorkspace;
2020
import org.eclipse.core.resources.ResourcesPlugin;
21+
import org.eclipse.core.tests.harness.session.ExecuteInHost;
2122
import org.eclipse.core.tests.harness.session.SessionTestExtension;
2223
import org.junit.jupiter.api.BeforeEach;
2324
import org.junit.jupiter.api.Test;
@@ -35,6 +36,7 @@ public class TestWorkspaceEncodingWithJvmArgs {
3536
.withCustomization(SessionTestExtension.createCustomWorkspace()).create();
3637

3738
@BeforeEach
39+
@ExecuteInHost
3840
public void setUpSession() {
3941
sessionTestExtension.setSystemProperty("file.encoding", "UTF-16");
4042
}

resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/session/TestWorkspaceEncodingWithPluginCustomization.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.core.resources.IWorkspace;
2424
import org.eclipse.core.resources.ResourcesPlugin;
2525
import org.eclipse.core.tests.harness.FileSystemHelper;
26+
import org.eclipse.core.tests.harness.session.ExecuteInHost;
2627
import org.eclipse.core.tests.harness.session.SessionTestExtension;
2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
@@ -41,6 +42,7 @@ public class TestWorkspaceEncodingWithPluginCustomization {
4142
.withCustomization(SessionTestExtension.createCustomWorkspace()).create();
4243

4344
@BeforeEach
45+
@ExecuteInHost
4446
public void setUpSession() throws IOException {
4547
// create plugin_customization.ini file
4648
File file = new File(FILE_NAME);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License v2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.core.tests.harness.session;
12+
13+
import java.lang.annotation.ElementType;
14+
import java.lang.annotation.Retention;
15+
import java.lang.annotation.RetentionPolicy;
16+
import java.lang.annotation.Target;
17+
18+
/**
19+
* Can be attached to any test method, before method or after method in a
20+
* session test class (i.e., one using a {@link SessionTestExtension}). It
21+
* defines that the annotated method is to be executed in the host Eclipse and
22+
* not in the remote Eclipse application started to run a test in a separate
23+
* session.
24+
*/
25+
@Retention(RetentionPolicy.RUNTIME)
26+
@Target(ElementType.METHOD)
27+
public @interface ExecuteInHost {
28+
// Marker annotation
29+
}

runtime/tests/org.eclipse.core.tests.harness/src/org/eclipse/core/tests/harness/session/SessionTestExtension.java

Lines changed: 7 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
*******************************************************************************/
1111
package org.eclipse.core.tests.harness.session;
1212

13-
import java.lang.reflect.Method;
1413
import java.util.HashSet;
1514
import java.util.Objects;
1615
import java.util.Set;
@@ -19,14 +18,9 @@
1918
import org.eclipse.core.tests.harness.session.customization.CustomSessionWorkspaceImpl;
2019
import org.eclipse.core.tests.harness.session.customization.SessionCustomization;
2120
import org.eclipse.core.tests.harness.session.samples.SampleSessionTests;
22-
import org.eclipse.core.tests.session.Setup;
23-
import org.eclipse.core.tests.session.SetupManager;
24-
import org.eclipse.core.tests.session.SetupManager.SetupException;
2521
import org.junit.jupiter.api.TestInstance.Lifecycle;
2622
import org.junit.jupiter.api.TestMethodOrder;
27-
import org.junit.jupiter.api.extension.ExtensionContext;
2823
import org.junit.jupiter.api.extension.InvocationInterceptor;
29-
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
3024

3125
/**
3226
* A JUnit 5 extension that will execute every test method in a class in its own
@@ -57,25 +51,9 @@
5751
* @see SessionShouldError
5852
*
5953
*/
60-
public class SessionTestExtension implements InvocationInterceptor {
54+
public interface SessionTestExtension extends InvocationInterceptor {
6155
public static final String CORE_TEST_APPLICATION = "org.eclipse.pde.junit.runtime.coretestapplication"; //$NON-NLS-1$
6256

63-
private final RemoteTestExecutor testExecutor;
64-
65-
private final Setup setup;
66-
67-
private final Set<SessionCustomization> sessionCustomizations = new HashSet<>();
68-
69-
private SessionTestExtension(String pluginId, String applicationId) {
70-
try {
71-
this.setup = SetupManager.getInstance().getDefaultSetup();
72-
setup.setSystemProperty("org.eclipse.update.reconcile", "false");
73-
testExecutor = new RemoteTestExecutor(setup, applicationId, pluginId);
74-
} catch (SetupException e) {
75-
throw new IllegalStateException("unable to create setup", e);
76-
}
77-
}
78-
7957
/**
8058
* Creates a builder for the session test extension. Make sure to finally call
8159
* {@link SessionTestExtensionBuilder#create()} to create a
@@ -194,16 +172,15 @@ public SessionTestExtensionBuilder withCustomization(CustomSessionConfiguration
194172
* this builder}
195173
*/
196174
public SessionTestExtension create() {
197-
SessionTestExtension extension = new SessionTestExtension(storedPluginId, storedApplicationId);
175+
if (RemoteTestExecutor.isRemoteExecution()) {
176+
return new SessionTestExtensionRemote();
177+
}
178+
SessionTestExtensionHost extension = new SessionTestExtensionHost(storedPluginId, storedApplicationId);
198179
storedSessionCustomizations.forEach(customization -> extension.addSessionCustomization(customization));
199180
return extension;
200181
}
201182
}
202183

203-
private void addSessionCustomization(SessionCustomization sessionCustomization) {
204-
this.sessionCustomizations.add(sessionCustomization);
205-
}
206-
207184
/**
208185
* {@return a custom workspace configuration that, by default, uses a temporary
209186
* folder to store the workspace files}
@@ -228,9 +205,7 @@ public static CustomSessionConfiguration createCustomConfiguration() {
228205
* @param value the Eclipse argument value to set, may be {@code null} to remove
229206
* the key
230207
*/
231-
public void setEclipseArgument(String key, String value) {
232-
setup.setEclipseArgument(key, value);
233-
}
208+
public void setEclipseArgument(String key, String value);
234209

235210
/**
236211
* Sets the given system property to the given value for sessions executed with
@@ -240,44 +215,6 @@ public void setEclipseArgument(String key, String value) {
240215
* @param value the system property value to set, may be {@code null} to remove
241216
* the key
242217
*/
243-
public void setSystemProperty(String key, String value) {
244-
setup.setSystemProperty(key, value);
245-
}
246-
247-
@Override
248-
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext,
249-
ExtensionContext extensionContext) throws Throwable {
250-
/**
251-
* Ensure that we do not recursively make a remote call if we are already in
252-
* remote execution
253-
*/
254-
if (RemoteTestExecutor.isRemoteExecution()) {
255-
invocation.proceed();
256-
return;
257-
}
258-
String testClass = extensionContext.getTestClass().get().getName();
259-
String testMethod = extensionContext.getTestMethod().get().getName();
260-
261-
boolean shouldFail = extensionContext.getTestMethod().get().getAnnotation(SessionShouldError.class) != null;
262-
invocation.skip();
263-
try {
264-
prepareSession();
265-
testExecutor.executeRemotely(testClass, testMethod, shouldFail);
266-
} finally {
267-
cleanupSession();
268-
}
269-
}
270-
271-
private void prepareSession() throws Exception {
272-
for (SessionCustomization customization : sessionCustomizations) {
273-
customization.prepareSession(setup);
274-
}
275-
}
276-
277-
private void cleanupSession() throws Exception {
278-
for (SessionCustomization customization : sessionCustomizations) {
279-
customization.cleanupSession(setup);
280-
}
281-
}
218+
public void setSystemProperty(String key, String value);
282219

283220
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License v2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.core.tests.harness.session;
12+
13+
import java.lang.reflect.Method;
14+
import java.util.HashSet;
15+
import java.util.Set;
16+
import org.eclipse.core.tests.harness.session.customization.SessionCustomization;
17+
import org.eclipse.core.tests.session.Setup;
18+
import org.eclipse.core.tests.session.SetupManager;
19+
import org.eclipse.core.tests.session.SetupManager.SetupException;
20+
import org.junit.jupiter.api.extension.ExtensionContext;
21+
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
22+
23+
/**
24+
* The implementation of the {@link SessionTestExtension} to be instantiated on
25+
* the host that is executing the session tests. It executes the test methods
26+
* remotely in a dedicated session.
27+
*/
28+
class SessionTestExtensionHost implements SessionTestExtension {
29+
public static final String CORE_TEST_APPLICATION = "org.eclipse.pde.junit.runtime.coretestapplication"; //$NON-NLS-1$
30+
31+
private final RemoteTestExecutor testExecutor;
32+
33+
private final Setup setup;
34+
35+
private final Set<SessionCustomization> sessionCustomizations = new HashSet<>();
36+
37+
SessionTestExtensionHost(String pluginId, String applicationId) {
38+
try {
39+
this.setup = SetupManager.getInstance().getDefaultSetup();
40+
setup.setSystemProperty("org.eclipse.update.reconcile", "false");
41+
testExecutor = new RemoteTestExecutor(setup, applicationId, pluginId);
42+
} catch (SetupException e) {
43+
throw new IllegalStateException("unable to create setup", e);
44+
}
45+
}
46+
47+
void addSessionCustomization(SessionCustomization sessionCustomization) {
48+
this.sessionCustomizations.add(sessionCustomization);
49+
}
50+
51+
/**
52+
* Sets the given Eclipse program argument to the given value for sessions
53+
* executed with this extension.
54+
*
55+
* @param key the Eclipse argument key, must not be {@code null}
56+
* @param value the Eclipse argument value to set, may be {@code null} to remove
57+
* the key
58+
*/
59+
@Override
60+
public void setEclipseArgument(String key, String value) {
61+
setup.setEclipseArgument(key, value);
62+
}
63+
64+
/**
65+
* Sets the given system property to the given value for sessions executed with
66+
* this extension.
67+
*
68+
* @param key the system property key, must not be {@code null}
69+
* @param value the system property value to set, may be {@code null} to remove
70+
* the key
71+
*/
72+
@Override
73+
public void setSystemProperty(String key, String value) {
74+
setup.setSystemProperty(key, value);
75+
}
76+
77+
@Override
78+
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext,
79+
ExtensionContext extensionContext) throws Throwable {
80+
if (!skipIfNotExecuteInHost(invocation, invocationContext)) {
81+
return;
82+
}
83+
84+
Class<?> testClass = extensionContext.getTestClass().get();
85+
Method testMethod = extensionContext.getTestMethod().get();
86+
87+
boolean shouldFail = extensionContext.getTestMethod().get().getAnnotation(SessionShouldError.class) != null;
88+
try {
89+
prepareSession();
90+
testExecutor.executeRemotely(testClass.getName(), testMethod.getName(), shouldFail);
91+
} finally {
92+
cleanupSession();
93+
}
94+
}
95+
96+
private boolean skipIfNotExecuteInHost(Invocation<Void> invocation,
97+
ReflectiveInvocationContext<Method> invocationContext) throws Throwable {
98+
boolean shouldExecuteInHost = invocationContext.getExecutable().getAnnotation(ExecuteInHost.class) != null;
99+
if (!shouldExecuteInHost) {
100+
invocation.skip();
101+
return true;
102+
}
103+
invocation.proceed();
104+
return false;
105+
}
106+
107+
private void prepareSession() throws Exception {
108+
for (SessionCustomization customization : sessionCustomizations) {
109+
customization.prepareSession(setup);
110+
}
111+
}
112+
113+
private void cleanupSession() throws Exception {
114+
for (SessionCustomization customization : sessionCustomizations) {
115+
customization.cleanupSession(setup);
116+
}
117+
}
118+
119+
@Override
120+
public void interceptAfterAllMethod(Invocation<Void> invocation,
121+
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
122+
skipIfNotExecuteInHost(invocation, invocationContext);
123+
}
124+
125+
@Override
126+
public void interceptAfterEachMethod(Invocation<Void> invocation,
127+
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
128+
skipIfNotExecuteInHost(invocation, invocationContext);
129+
}
130+
131+
@Override
132+
public void interceptBeforeAllMethod(Invocation<Void> invocation,
133+
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
134+
skipIfNotExecuteInHost(invocation, invocationContext);
135+
}
136+
137+
@Override
138+
public void interceptBeforeEachMethod(Invocation<Void> invocation,
139+
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
140+
skipIfNotExecuteInHost(invocation, invocationContext);
141+
}
142+
143+
}

0 commit comments

Comments
 (0)