From c865bfe9c845c30043853064ab9ed55550ebb4ff Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Tue, 7 Oct 2025 16:27:16 -0700 Subject: [PATCH 1/4] Fix Locator.xq to handle trailing quotes (backport) (#2733) --- src/org/labkey/test/Locator.java | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/Locator.java b/src/org/labkey/test/Locator.java index 8bcf8b532a..89d772df63 100644 --- a/src/org/labkey/test/Locator.java +++ b/src/org/labkey/test/Locator.java @@ -985,10 +985,39 @@ public static XPathLocator pageHeader(String headerText) * Direct port from attibuteValue function in selenium IDE locatorBuilders.js * @param value to be quoted * @return value with either ' or " around it or assembled from parts + * @implNote {@link Quotes#escape(String)} doesn't handle trailing quotes correctly */ public static String xq(String value) { - return Quotes.escape(value); + if (!value.contains("'")) { + return "'" + value + "'"; + } else if (!value.contains("\"")) { + return '"' + value + '"'; + } else { + StringBuilder result = new StringBuilder("concat("); + while (true) { + int apos = value.indexOf("'"); + int quot = value.indexOf('"'); + if (apos < 0) { + result.append("'").append(value).append("'"); + break; + } else if (quot < 0) { + result.append('"').append(value).append('"'); + break; + } else if (quot < apos) { + String part = value.substring(0, apos); + result.append("'").append(part).append("'"); + value = value.substring(part.length()); + } else { + String part = value.substring(0, quot); + result.append('"').append(part).append('"'); + value = value.substring(part.length()); + } + result.append(','); + } + result.append(')'); + return result.toString(); + } } /** From 436260908048d676c81ae672a5d285bf80d8ca41 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Thu, 9 Oct 2025 09:13:39 -0700 Subject: [PATCH 2/4] refactor testLongName to use FieldInfo (#2737) Co-authored-by: Trey Chadick --- src/org/labkey/test/tests/list/ListTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index ff624341b8..672612ba83 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -52,8 +52,8 @@ import org.labkey.test.pages.query.UpdateQueryRowPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.FieldDefinition.StringLookup; +import org.labkey.test.params.FieldInfo; import org.labkey.test.params.FieldKey; -import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.VarListDefinition; import org.labkey.test.tests.AuditLogTest; import org.labkey.test.util.AbstractDataRegionExportOrSignHelper.ColumnHeaderType; @@ -533,26 +533,27 @@ public void testNameTrimming() public void testLongName() { String listName = "A_+-:''.¡™£¢∞§¶•ªº–≠œ∑´®†¥¨ˆøπ“‘«æ…¬˚∆˙©√ƒ∂ßΩ≈ç√∫µ≤≥÷‹›fifl‡°·‚—±⁄€‹›‡‰Æ«»¢∫√∑∏∂"; - String fieldWithDefault = TestDataGenerator.randomFieldName("With Default"); + var fieldWithDefault = FieldInfo.random("With Default", ColumnType.String); EditListDefinitionPage listEditPage = _listHelper.beginCreateList(getProjectName(), listName); listEditPage.manuallyDefineFieldsWithAutoIncrementingKey("Key"); - listEditPage.addField(new FieldDefinition(fieldWithDefault, ColumnType.String)); + listEditPage.addField(fieldWithDefault.getFieldDefinition()); listEditPage.clickSave(); listEditPage = _listHelper.goToEditDesign(listName); var page = listEditPage.getFieldsPanel() .expand() - .getField(fieldWithDefault) + .getField(fieldWithDefault.getName()) .clickAdvancedSettings() .clickDefaultValuesLink(); - var input = Locator.tagContainingText("td", "With Default").followingSibling("td").descendant("input").findElement(page.getDriver()); + var input = Locator.tagContainingText("td", fieldWithDefault.getLabel()).followingSibling("td") + .descendant("input").findElement(page.getDriver()); setFormElement(input, "42"); clickButton("Save Defaults"); _listHelper.beginAtList(getProjectName(), listName); DataRegionTable list = new DataRegionTable("query", getDriver()); UpdateQueryRowPage updatePage = list.clickInsertNewRow(); - checker().verifyEquals("Default value not as expected ", "42", updatePage.getTextInputValue(fieldWithDefault)); + checker().verifyEquals("Default value not as expected ", "42", updatePage.getTextInputValue(fieldWithDefault.getName())); updatePage.submit(); } From 1e70153e5e67f71391d30f7054cb26e4ac537434 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Fri, 10 Oct 2025 10:57:05 -0700 Subject: [PATCH 3/4] Backport base classes for upgrade pipeline (#2728) --- src/org/labkey/test/BaseWebDriverTest.java | 7 +- src/org/labkey/test/TestProperties.java | 6 +- .../test/tests/upgrade/BaseUpgradeTest.java | 125 ++++++++++++++++++ .../test/tests/upgrade/BasicUpgradeTest.java | 92 +++++++++++++ src/org/labkey/test/util/APIUserHelper.java | 13 ++ .../labkey/test/util/PermissionsHelper.java | 6 + src/org/labkey/test/util/TestUser.java | 45 +++++-- src/org/labkey/test/util/Version.java | 114 ++++++++++++++++ src/org/labkey/test/util/VersionRange.java | 34 +++++ 9 files changed, 424 insertions(+), 18 deletions(-) create mode 100644 src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java create mode 100644 src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java create mode 100644 src/org/labkey/test/util/Version.java create mode 100644 src/org/labkey/test/util/VersionRange.java diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index 61a5167cb3..bfb20cb9d8 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -1333,11 +1333,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); diff --git a/src/org/labkey/test/TestProperties.java b/src/org/labkey/test/TestProperties.java index 83d56286fc..b6cc830ba6 100644 --- a/src/org/labkey/test/TestProperties.java +++ b/src/org/labkey/test/TestProperties.java @@ -373,7 +373,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)) @@ -393,7 +393,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)) @@ -417,7 +417,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)) diff --git a/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java new file mode 100644 index 0000000000..ffaeacacbc --- /dev/null +++ b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java @@ -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.
+ * 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).
+ * 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 getAssociatedModules() + { + return Arrays.asList(); + } + + /** + * Annotates test methods that should only run when upgrading from particular LabKey versions, as specified in + * {@code webtest.upgradePreviousVersion}.
+ * 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}.
+ * 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(); + } + }; + } + } + +} diff --git a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java new file mode 100644 index 0000000000..5cdb8ed7d2 --- /dev/null +++ b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java @@ -0,0 +1,92 @@ +package org.labkey.test.tests.upgrade; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.Connection; +import org.labkey.remoteapi.query.SelectRowsCommand; +import org.labkey.test.params.FieldInfo; +import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.TestUser; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.labkey.test.util.PermissionsHelper.READER_ROLE; + +@Category({}) +public class BasicUpgradeTest extends BaseUpgradeTest +{ + private static final TestUser USER = new TestUser("basic_upgrade_reader@basicupgradetest.test"); + private static final String SAMPLE_TYPE = "UpgradeTestSamples"; + + private static final FieldInfo STR_COL = new FieldInfo("String Col1"); + + @Override + protected void doCleanup(boolean afterTest) + { + _containerHelper.deleteProject(getProjectName(), afterTest); + _userHelper.deleteUsers(afterTest, USER); + } + + @Override + protected void doSetup() throws Exception + { + _containerHelper.createProject(getProjectName(), null); + USER.create(this); + USER.setInitialPassword(); + USER.addPermission(READER_ROLE, getProjectName()); + + new SampleTypeDefinition(SAMPLE_TYPE) + .setFields(List.of(STR_COL.getFieldDefinition())) + .create(createDefaultConnection(), getProjectName()) + .insertRows(createDefaultConnection(), List.of(Map.of( + "Name", "S-1", + STR_COL.getName(), "Test String Value" + )) ); + } + + @Before + public void preTest() + { + USER.load(this); + } + + @Test + public void testSampleRowsExist() throws Exception + { + // Use primary user to verify data + queryData(createDefaultConnection()); + } + + @Test + public void testUserPassword() throws Exception + { + // Use password authentication for USER + queryData(USER.getUserConnection()); + } + + @Test + public void testUserPermissions() throws Exception + { + // Impersonate to test USER's permission level + queryData(createDefaultConnection().impersonate(USER.getEmail())); + } + + private void queryData(Connection connection) throws IOException, CommandException + { + List> rows = new SelectRowsCommand("samples", SAMPLE_TYPE) + .execute(connection, getProjectName()) + .getRows(); + assertFalse("Expected at least one row", rows.isEmpty()); + } + + @Override + protected String getProjectName() + { + return getClass().getSimpleName() + " Project"; + } +} diff --git a/src/org/labkey/test/util/APIUserHelper.java b/src/org/labkey/test/util/APIUserHelper.java index 311b953b6c..f67c89bcde 100644 --- a/src/org/labkey/test/util/APIUserHelper.java +++ b/src/org/labkey/test/util/APIUserHelper.java @@ -217,6 +217,14 @@ public Integer getUserId(String userEmail) return getUserIds(Arrays.asList(userEmail)).get(userEmail); } + public int getUserIdStrict(String userEmail) + { + Integer userId = getUserId(userEmail); + if (userId == null) + throw new IllegalStateException("No user with email " + userEmail + " found."); + return userId; + } + @Override protected void _deleteUser(String userEmail) { @@ -255,6 +263,11 @@ protected void _deleteUsers(boolean failIfNotFound, String... userEmails) private static final Pattern regEmailVerification = Pattern.compile("verification=([A-Za-z0-9]+)"); + public String setInitialPassword(String email) + { + return setInitialPassword(getUserIdStrict(email)); + } + @Override public String setInitialPassword(int userId) { diff --git a/src/org/labkey/test/util/PermissionsHelper.java b/src/org/labkey/test/util/PermissionsHelper.java index e0fa06d52d..3d01274dd9 100644 --- a/src/org/labkey/test/util/PermissionsHelper.java +++ b/src/org/labkey/test/util/PermissionsHelper.java @@ -38,6 +38,12 @@ public abstract class PermissionsHelper public static final String APP_ADMIN_ROLE = "Application Admin"; public static final String DEVELOPER_ROLE = "Platform Developer"; public static final String IMP_TROUBLESHOOTER_ROLE = "Impersonating Troubleshooter"; + public static final String PROJECT_ADMIN_ROLE = "Project Administrator"; + public static final String FOLDER_ADMIN_ROLE = "Folder Administrator"; + public static final String READER_ROLE = "Reader"; + public static final String EDITOR_ROLE = "Editor"; + public static final String AUTHOR_ROLE = "Author"; + public static final String SUBMITTER_ROLE = "Submitter"; public static String toRole(final String name) { diff --git a/src/org/labkey/test/util/TestUser.java b/src/org/labkey/test/util/TestUser.java index bddbbfd837..8a0a2b24d4 100644 --- a/src/org/labkey/test/util/TestUser.java +++ b/src/org/labkey/test/util/TestUser.java @@ -2,8 +2,8 @@ import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.Connection; -import org.labkey.remoteapi.security.CreateUserResponse; import org.labkey.test.WebDriverWrapper; +import org.labkey.test.WebTestHelper; import java.io.IOException; @@ -13,7 +13,7 @@ public class TestUser { private WebDriverWrapper _test; private final String _email; - private CreateUserResponse _createUserResponse; + private Integer _userId; private String _password; private APIUserHelper _apiUserHelper; private Connection _impersonationConnection; @@ -31,12 +31,25 @@ public TestUser create(WebDriverWrapper test) { _test = test; _apiUserHelper = new APIUserHelper(_test); - _createUserResponse = _apiUserHelper.createUser(_email); + _userId = _apiUserHelper.createUser(_email).getUserId(); _impersonationConnection = null; _password = null; return this; } + /** + * Gets info for an already existing user + */ + public TestUser load(WebDriverWrapper test) + { + _test = test; + _apiUserHelper = new APIUserHelper(_test); + _userId = _apiUserHelper.getUserId(_email); + _impersonationConnection = null; + _password = PasswordUtil.getPassword(); + return this; + } + public void deleteUser() { getApiUserHelper().deleteUsers(false, _email); @@ -44,7 +57,11 @@ public void deleteUser() public Integer getUserId() { - return getCreateUserResponse().getUserId(); + if (_userId == null) + { + throw new IllegalStateException("User" + _email + " has not yet been created"); + } + return _userId; } public String getEmail() @@ -68,7 +85,7 @@ public TestUser setInitialPassword() { if (_password == null) // if null, this is the initial password - we can use the UI to set it now { - _password = _apiUserHelper.setInitialPassword(_createUserResponse.getUserId()); + _password = _apiUserHelper.setInitialPassword(_userId); } else { @@ -102,6 +119,15 @@ public String getPassword() return _password; } + /** + * Create a non-impersonating API connection for the user. + * @return new Connection + */ + public Connection getUserConnection() + { + return new Connection(WebTestHelper.getBaseURL(), getEmail(), getPassword()); + } + public TestUser addPermission(String role, String containerPath) { new ApiPermissionsHelper(getWrapper()).addMemberToRole(getEmail(), role, PermissionsHelper.MemberType.user, containerPath); @@ -152,15 +178,6 @@ public void stopImpersonating(boolean refresh) throws IOException, CommandExcept getWrapper().refresh(); } - private CreateUserResponse getCreateUserResponse() - { - if (_createUserResponse == null) - { - throw new IllegalStateException("User" + _email + " has not yet been created"); - } - return _createUserResponse; - } - private APIUserHelper getApiUserHelper() { if (_apiUserHelper == null) diff --git a/src/org/labkey/test/util/Version.java b/src/org/labkey/test/util/Version.java new file mode 100644 index 0000000000..cef81cd705 --- /dev/null +++ b/src/org/labkey/test/util/Version.java @@ -0,0 +1,114 @@ +package org.labkey.test.util; + +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A simple class for parsing and comparing version numbers + */ +public class Version implements Comparable +{ + private final List _version; + + public Version(Integer... version) + { + _version = validate(version); + } + + public Version(String version) + { + this(Arrays.stream(version.split("\\.")).map(Integer::parseInt).toArray(Integer[]::new)); + } + + public Version(Double version) + { + this(version.toString()); + } + + private static List validate(Integer... versionParts) + { + List partList = List.of(versionParts); + if (partList.isEmpty()) + { + throw new IllegalArgumentException("Version must have at least one part"); + } + for (Integer part : partList) + { + if (part < 0) + { + throw new IllegalArgumentException("Version parts must be non-negative"); + } + } + return partList; + } + + @Override + public int compareTo(@NotNull Version o) + { + int i = 0; + for (; i < _version.size() && i < o._version.size(); i++) + { + Integer versionPart = _version.get(i); + Integer otherVersionPart = o._version.get(i); + int result = versionPart.compareTo(otherVersionPart); + if (result != 0) + { + return result; + } + } + // Treat the less specific version as higher + if (i < _version.size()) + return -1; // this version is more specific + if (i < o._version.size()) + return 1; // the other version is more specific + return 0; + } + + @Override + public final boolean equals(Object o) + { + if (!(o instanceof Version version)) return false; + + return _version.equals(version._version); + } + + @Override + public int hashCode() + { + return _version.hashCode(); + } + + @Override + public String toString() + { + return _version.stream().map(Object::toString).collect(Collectors.joining(".")); + } + + + public static class VersionTest + { + @Test + public void testConstructors() + { + Assert.assertEquals("Integer constructor comparison", new Version("25.7"), new Version(25, 7)); + Assert.assertEquals("Integer constructor comparison", new Version("25.7.3"), new Version(25, 7, 3)); + Assert.assertEquals("Double constructor comparison", new Version("25.7"), new Version(25.7)); + } + + @Test + public void testCompareTo() + { + Assert.assertEquals("CompareTo equal version", 0, new Version("25.7").compareTo(new Version("25.7"))); + Assert.assertEquals("CompareTo earlier version", 1, new Version("25.7").compareTo(new Version("25.3"))); + Assert.assertEquals("CompareTo later version", -1, new Version("25.7").compareTo(new Version("25.11"))); + Assert.assertEquals("CompareTo less specific version", -1, new Version("25.7.0").compareTo(new Version("25.7"))); + Assert.assertEquals("CompareTo more specific version", 1, new Version("25.7.0").compareTo(new Version("25.7.0.0"))); + } + } + +} diff --git a/src/org/labkey/test/util/VersionRange.java b/src/org/labkey/test/util/VersionRange.java new file mode 100644 index 0000000000..123b9a987d --- /dev/null +++ b/src/org/labkey/test/util/VersionRange.java @@ -0,0 +1,34 @@ +package org.labkey.test.util; + +public class VersionRange +{ + private final Version eariestVersion; + private final Version latestVersion; + + public VersionRange(Version eariestVersion, Version latestVersion) + { + this.eariestVersion = eariestVersion; + this.latestVersion = latestVersion; + } + + public static VersionRange from(String version) + { + return new VersionRange(new Version(version), null); + } + + public static VersionRange until(String version) + { + return new VersionRange(null, new Version(version)); + } + + public static VersionRange versionRange(String earliestVersion, String latestVersion) + { + return new VersionRange(new Version(earliestVersion), new Version(latestVersion)); + } + + public boolean contains(Version version) + { + return (eariestVersion == null || eariestVersion.compareTo(version) <= 0) && + (latestVersion == null || latestVersion.compareTo(version) >= 0); + } +} From 40c9c7e48c7f5c071df145e13472ae6fa56adfca Mon Sep 17 00:00:00 2001 From: Nick Kerr Date: Tue, 14 Oct 2025 15:31:20 -0700 Subject: [PATCH 4/4] Issue 54094: escape form field names ending with "\" (#2743) --- src/org/labkey/test/tests/list/ListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index b9596d8e33..56d346f399 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -1055,7 +1055,7 @@ public void listSelfJoinTest() final String dummyCol = dummyBase + TRICKY_CHARACTERS; final String lookupField = "lookupField" + TRICKY_CHARACTERS; final String lookupSchema = "lists"; - final String keyCol = "Key &%<+"; + final String keyCol = "Key &%<+\\"; // Issue 54094: Verify key field ending with "\" log("Issue 6883: test list self join");