Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f797690
Base classes for upgrade pipeline
labkey-tchad Sep 26, 2025
8c70809
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Sep 26, 2025
1d83b91
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Sep 29, 2025
bf373b4
Cleanup imports
labkey-tchad Sep 29, 2025
abbac24
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Sep 29, 2025
e189bd8
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Sep 30, 2025
b9b8fa9
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Sep 30, 2025
81894aa
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Oct 1, 2025
dd289ae
Don't wait an hour for server to start
labkey-tchad Oct 1, 2025
73a32a7
Use Java remote API to connect to starting server
labkey-tchad Oct 2, 2025
8b04631
wrong file
labkey-tchad Oct 2, 2025
cf23760
Interpret response correctly
labkey-tchad Oct 2, 2025
9dfaf9b
Back to old HttpClient
labkey-tchad Oct 2, 2025
4cd57b2
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Oct 2, 2025
bf701cb
Try ensureLogin
labkey-tchad Oct 2, 2025
2f24b32
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Oct 6, 2025
3d0a1cb
Update project name
labkey-tchad Oct 6, 2025
59fbef0
Merge remote-tracking branch 'origin/develop' into fb_upgradeTestPipe…
labkey-tchad Oct 8, 2025
cdc53bb
Reuse code
labkey-tchad Oct 8, 2025
dfb3619
Update upgrade tests
labkey-tchad Oct 9, 2025
bbdbacd
Add comments and fix version range check
labkey-tchad Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions src/org/labkey/test/BaseWebDriverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.labkey.test;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.time.FastDateFormat;
Expand Down Expand Up @@ -109,7 +108,6 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.html5.WebStorage;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.UnreachableBrowserException;
import org.openqa.selenium.remote.service.DriverService;
Expand Down Expand Up @@ -780,12 +778,8 @@ private void clearLocalStorage()
{
// Clears browser localStorage. Needed in order to reset some state such as grid filters/sorts/etc.
// which are sticky, but can interfere with what tests expect.
WebDriver driver = getDriver();

if (driver instanceof WebStorage webStorage)
{
webStorage.getLocalStorage().clear();
}
executeScript("window.localStorage.clear();");
executeScript("window.sessionStorage.clear();");
Comment on lines +781 to +782
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an efficiency change, and not related to this story?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was an accidental inclusion. The WebStorage API for WebDriver has been deprecated for a while and I was removing it.

}

@Override
Expand Down Expand Up @@ -1337,11 +1331,16 @@ private int getPendingRequestCount(Connection connection)
}
}

protected boolean skipCleanup(boolean afterTest)
{
return false;
}

private void cleanup(boolean afterTest)
{
ensureSignedInAsPrimaryTestUser();

if (!ClassUtils.getAllInterfaces(getClass()).contains(ReadOnlyTest.class) || ((ReadOnlyTest) this).needsSetup())
if (!skipCleanup(afterTest) && (!(this instanceof ReadOnlyTest readOnlyTest) || readOnlyTest.needsSetup()))
{
if (afterTest)
waitForPendingRequests(WAIT_FOR_PAGE);
Expand Down
6 changes: 3 additions & 3 deletions src/org/labkey/test/TestProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ public static File getDumpDir()
* @param def Default value
* @return value of the specified property
*/
private static boolean getBooleanProperty(String key, boolean def)
public static boolean getBooleanProperty(String key, boolean def)
{
String prop = System.getProperty(key);
if (!StringUtils.isBlank(prop))
Expand All @@ -403,7 +403,7 @@ private static boolean getBooleanProperty(String key, boolean def)
* @param def Default value
* @return value of the specified property
*/
private static int getIntegerProperty(String key, int def)
public static int getIntegerProperty(String key, int def)
{
String prop = System.getProperty(key);
if (!StringUtils.isBlank(prop))
Expand All @@ -427,7 +427,7 @@ private static int getIntegerProperty(String key, int def)
* @param def Default value
* @return value of the specified property
*/
private static double getDoubleProperty(String key, double def)
public static double getDoubleProperty(String key, double def)
{
String prop = System.getProperty(key);
if (!StringUtils.isBlank(prop))
Expand Down
3 changes: 2 additions & 1 deletion src/org/labkey/test/WebTestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.CharCodingConfig;
import org.apache.hc.core5.ssl.SSLContextBuilder;
Expand Down Expand Up @@ -808,7 +809,7 @@ public static HttpClientContext getBasicHttpContext()
return localcontext;
}

public static String getHttpResponseBody(CloseableHttpResponse response)
public static String getHttpResponseBody(ClassicHttpResponse response)
{
StringBuilder builder = new StringBuilder();
try
Expand Down
39 changes: 29 additions & 10 deletions src/org/labkey/test/tests/JUnitTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@
import org.labkey.test.util.JUnitFooter;
import org.labkey.test.util.JUnitHeader;
import org.labkey.test.util.LogMethod;
import org.labkey.test.util.TestDateUtils;
import org.labkey.test.util.TestLogger;
import org.labkey.test.util.Timer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -181,10 +184,10 @@ public static TestSuite dynamicSuite(Collection<String> categories, Collection<S

public static TestSuite _suite(Predicate<Map<String,Object>> accept, boolean skipInitialUserChecks) throws Exception
{
return _suite(accept, skipInitialUserChecks, 0, 0);
return _suite(accept, skipInitialUserChecks, new Timer(Duration.ofMinutes(5)), 0);
}

private static TestSuite _suite(Predicate<Map<String,Object>> accept, boolean skipInitialUserChecks, final int startupAttempts, final int upgradeAttempts) throws Exception
private static TestSuite _suite(Predicate<Map<String,Object>> accept, boolean skipInitialUserChecks, final Timer startupTimer, final int upgradeAttempts) throws Exception
{
if (TestProperties.isPrimaryUserAppAdmin())
{
Expand All @@ -196,17 +199,31 @@ private static TestSuite _suite(Predicate<Map<String,Object>> accept, boolean sk
try (CloseableHttpClient client = WebTestHelper.getHttpClient())
{
final String url = WebTestHelper.getBaseURL() + "/junit-testlist.view";
HttpGet method = new HttpGet(url);
HttpGet ensureLogin = new HttpGet(WebTestHelper.buildURL("security", "ensureLogin.api"));
HttpGet getTestList = new HttpGet(url);
try
{
response = client.execute(method, context);
client.execute(ensureLogin, context, r -> {
String ensureLoginResponse = WebTestHelper.getHttpResponseBody(r);
try
{
JSONObject jsonObject = new JSONObject(ensureLoginResponse);
LOG.info("ensureLogin: {}", jsonObject.getJSONObject("currentUser").getString("email"));
}
catch (JSONException e)
{
LOG.info("ensureLogin: {}", ensureLoginResponse);
}
return null;
});
response = client.execute(getTestList, context);
}
catch (IOException ex)
{
if (startupAttempts < 60 && upgradeAttempts == 0)
if (!startupTimer.isTimedOut() && upgradeAttempts == 0)
{
Thread.sleep(1000);
return _suite(accept, skipInitialUserChecks, startupAttempts + 1, upgradeAttempts);
return _suite(accept, skipInitialUserChecks, startupTimer, upgradeAttempts);
}
else
{
Expand Down Expand Up @@ -235,12 +252,13 @@ private static TestSuite _suite(Predicate<Map<String,Object>> accept, boolean sk
if (responseBody.contains("<title>Start Modules</title>"))
{
// Server still starting up. We don't need to use the upgradeHelper to sign in.
LOG.info("Remote JUnitTest: Server modules starting up (attempt " + startupAttempts + ") ...");
LOG.info("Remote JUnitTest: Server modules starting up (remaining " + TestDateUtils.durationString(startupTimer.timeRemaining()) + ") ...");

if (startupAttempts < 60)
EntityUtils.consumeQuietly(response.getEntity()); // Consume before possible recursion
if (!startupTimer.isTimedOut())
{
Thread.sleep(1000);
return _suite(accept, skipInitialUserChecks, startupAttempts + 1, upgradeAttempts);
return _suite(accept, skipInitialUserChecks, startupTimer, upgradeAttempts);
}
else
{
Expand All @@ -255,6 +273,7 @@ else if (responseBody.contains("<title>Upgrade Status</title>") ||
responseBody.contains("<title>Account Setup</title>") ||
responseBody.contains("This server is being upgraded to a new version of LabKey Server."))
{
EntityUtils.consumeQuietly(response.getEntity()); // Consume before possible recursion
LOG.info("Remote JUnitTest: Server needs install or upgrade ...");
if (upgradeAttempts > 3)
throw new AssertionFailedError("Failed to update or bootstrap on second attempt: " + responseBody);
Expand All @@ -273,7 +292,7 @@ else if (responseBody.contains("<title>Upgrade Status</title>") ||
TestSuite testSuite;
try
{
testSuite = _suite(accept, skipInitialUserChecks, startupAttempts + 1, upgradeAttempts + 1);
testSuite = _suite(accept, skipInitialUserChecks, startupTimer, upgradeAttempts + 1);
}
catch (Exception retryException)
{
Expand Down
125 changes: 125 additions & 0 deletions src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.labkey.test.tests.upgrade;

import org.jetbrains.annotations.NotNull;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.labkey.test.BaseWebDriverTest;
import org.labkey.test.TestProperties;
import org.labkey.test.util.TestLogger;
import org.labkey.test.util.Version;
import org.labkey.test.util.VersionRange;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.apache.commons.lang3.StringUtils.trimToNull;

/**
* Base test class for tests that setup data and configure a server then verify the persistence or modification of those
* data and configurations after upgrading to a newer version of LabKey.<br>
* The {@code EariestVersion} and {@code LatestVersion} annotations can be used to skip particular tests when they are
* not relevant to the version of LabKey being upgraded from (specified in the {@code webtest.upgradePreviousVersion}
* system property).<br>
* The setup steps will be skipped if the {@code webtest.upgradeSetup} system property is set to {@code false}.
*/
public abstract class BaseUpgradeTest extends BaseWebDriverTest
{

protected static final boolean isUpgradeSetupPhase = TestProperties.getBooleanProperty("webtest.upgradeSetup", true);
protected static final Version previousVersion = Optional.ofNullable(trimToNull(System.getProperty("webtest.upgradePreviousVersion")))
.map(Version::new).orElse(null);

@Override
protected boolean skipCleanup(boolean afterTest)
{
return afterTest || !isUpgradeSetupPhase;
}

@BeforeClass
public static void setupProject() throws Exception
{
BaseUpgradeTest currentTest = BaseWebDriverTest.getCurrentTest();

if (isUpgradeSetupPhase)
{
currentTest.doSetup();
}
else
{
TestLogger.info("Skipping setup for %s. Verifying upgrade.". formatted(currentTest.getClass().getSimpleName()));
}
}

protected abstract void doSetup() throws Exception;

@Rule
public final TestRule upgradeVersionCheck = new UpgradeVersionCheck();

@Override
public List<String> getAssociatedModules()
{
return Arrays.asList();
}

/**
* Annotates test methods that should only run when upgrading from particular LabKey versions, as specified in
* {@code webtest.upgradePreviousVersion}.<br>
* Specifies the earliest version of the test class that performed the required setup for the annotated method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
protected @interface EariestVersion {
String value();
}

/**
* Annotates test methods that should only run when upgrading from particular LabKey versions, as specified in
* {@code webtest.upgradePreviousVersion}.<br>
* Specifies the latest version of the test class that performed the required setup for the annotated method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
protected @interface LatestVersion {
String value();
}

private static class UpgradeVersionCheck implements TestRule
{

@Override
public @NotNull Statement apply(Statement base, Description description)
{
String eariestVersion = Optional.ofNullable(description.getAnnotation(EariestVersion.class))
.map(EariestVersion::value).orElse(null);
String latestVersion = Optional.ofNullable(description.getAnnotation(LatestVersion.class))
.map(LatestVersion::value).orElse(null);

if (isUpgradeSetupPhase || previousVersion == null || (eariestVersion == null && latestVersion == null))
{
return base; // Run the test normally
}

return new Statement()
{
@Override
public void evaluate() throws Throwable
{
Assume.assumeTrue("Test doesn't support upgrading from version: " + previousVersion,
VersionRange.versionRange(eariestVersion, latestVersion).contains(previousVersion)
);
base.evaluate();
}
};
}
}

}
Loading