-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Introduce BoundedDelimitedStringCollector
#124303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
elasticsearchmachine
merged 5 commits into
elastic:main
from
DaveCTurner:2025/03/07/limit-string-collection-length-util
Mar 7, 2025
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
b0f3c13
Introduce `BoundedDelimitedStringCollector`
DaveCTurner 02d661d
[CI] Auto commit changes from spotless
e37863f
Merge branch 'main' into 2025/03/07/limit-string-collection-length-util
DaveCTurner 1a0a020
Merge branch 'main' into 2025/03/07/limit-string-collection-length-util
DaveCTurner b8f1e06
Merge branch 'main' into 2025/03/07/limit-string-collection-length-util
DaveCTurner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
server/src/test/java/org/elasticsearch/common/BoundedDelimitedStringCollectorTests.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| package org.elasticsearch.common; | ||
|
|
||
| import com.carrotsearch.randomizedtesting.annotations.Name; | ||
| import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; | ||
|
|
||
| import org.elasticsearch.test.ESTestCase; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static org.elasticsearch.common.Strings.collectionToDelimitedString; | ||
| import static org.hamcrest.Matchers.allOf; | ||
| import static org.hamcrest.Matchers.containsString; | ||
| import static org.hamcrest.Matchers.endsWith; | ||
| import static org.hamcrest.Matchers.equalTo; | ||
| import static org.hamcrest.Matchers.lessThanOrEqualTo; | ||
|
|
||
| public class BoundedDelimitedStringCollectorTests extends ESTestCase { | ||
|
|
||
| private interface TestHarness { | ||
| String getResult(Iterable<?> collection, String prefix, String delimiter, String suffix, int appendLimit); | ||
|
|
||
| enum Type { | ||
| COLLECTING, | ||
| ITERATING | ||
| } | ||
| } | ||
|
|
||
| private final TestHarness testHarness; | ||
|
|
||
| @ParametersFactory | ||
| public static Iterable<Object[]> parameters() throws Exception { | ||
| return Stream.of(TestHarness.Type.values()).map(x -> new Object[] { x })::iterator; | ||
| } | ||
|
|
||
| public BoundedDelimitedStringCollectorTests(@Name("type") TestHarness.Type testHarnessType) { | ||
| testHarness = switch (testHarnessType) { | ||
| case COLLECTING -> (collection, prefix, delimiter, suffix, appendLimit) -> { | ||
| final var stringBuilder = new StringBuilder(); | ||
| final var collector = new Strings.BoundedDelimitedStringCollector(stringBuilder, prefix, delimiter, suffix, appendLimit); | ||
| collection.forEach(collector::appendItem); | ||
| collector.finish(); | ||
| return stringBuilder.toString(); | ||
| }; | ||
| case ITERATING -> (collection, prefix, delimiter, suffix, appendLimit) -> { | ||
| final var stringBuilder = new StringBuilder(); | ||
| Strings.collectionToDelimitedStringWithLimit(collection, delimiter, prefix, suffix, appendLimit, stringBuilder); | ||
| return stringBuilder.toString(); | ||
| }; | ||
| }; | ||
| } | ||
|
|
||
| public void testCollectionToDelimitedStringWithLimitZero() { | ||
| final String delimiter = randomFrom("", ",", ", ", "/"); | ||
| final String prefix = randomFrom("", "["); | ||
| final String suffix = randomFrom("", "]"); | ||
|
|
||
| final int count = between(0, 100); | ||
| final List<String> strings = new ArrayList<>(count); | ||
| while (strings.size() < count) { | ||
| // avoid starting with a sequence of empty appends, it makes the assertions much messier | ||
| final int minLength = strings.isEmpty() && delimiter.isEmpty() && prefix.isEmpty() && suffix.isEmpty() ? 1 : 0; | ||
| strings.add(randomAlphaOfLength(between(minLength, 10))); | ||
| } | ||
|
|
||
| final String completelyTruncatedDescription = testHarness.getResult(strings, prefix, delimiter, suffix, 0); | ||
|
|
||
| if (count == 0) { | ||
| assertThat(completelyTruncatedDescription, equalTo("")); | ||
| } else if (count == 1) { | ||
| assertThat(completelyTruncatedDescription, equalTo(prefix + strings.get(0) + suffix)); | ||
| } else { | ||
| assertThat( | ||
| completelyTruncatedDescription, | ||
| equalTo(prefix + strings.get(0) + suffix + delimiter + "... (" + count + " in total, " + (count - 1) + " omitted)") | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| public void testCollectionToDelimitedStringWithLimitTruncation() { | ||
| final String delimiter = randomFrom("", ",", ", ", "/"); | ||
| final String prefix = randomFrom("", "["); | ||
| final String suffix = randomFrom("", "]"); | ||
|
|
||
| final int count = between(2, 100); | ||
| final List<String> strings = new ArrayList<>(count); | ||
| while (strings.size() < count) { | ||
| // avoid empty appends, it makes the assertions much messier | ||
| final int minLength = delimiter.isEmpty() && prefix.isEmpty() && suffix.isEmpty() ? 1 : 0; | ||
| strings.add(randomAlphaOfLength(between(minLength, 10))); | ||
| } | ||
|
|
||
| final int fullDescriptionLength = collectionToDelimitedString(strings, delimiter, prefix, suffix).length(); | ||
| final int lastItemSize = prefix.length() + strings.get(count - 1).length() + suffix.length(); | ||
| final int truncatedLength = between(0, fullDescriptionLength - lastItemSize - 1); | ||
| final String truncatedDescription = testHarness.getResult(strings, prefix, delimiter, suffix, truncatedLength); | ||
|
|
||
| assertThat(truncatedDescription, allOf(containsString("... (" + count + " in total,"), endsWith(" omitted)"))); | ||
|
|
||
| assertThat( | ||
| truncatedDescription, | ||
| truncatedDescription.length(), | ||
| lessThanOrEqualTo(truncatedLength + (prefix + "0123456789" + suffix + delimiter + "... (999 in total, 999 omitted)").length()) | ||
| ); | ||
| } | ||
|
|
||
| public void testCollectionToDelimitedStringWithLimitNoTruncation() { | ||
| final String delimiter = randomFrom("", ",", ", ", "/"); | ||
| final String prefix = randomFrom("", "["); | ||
| final String suffix = randomFrom("", "]"); | ||
|
|
||
| final int count = between(1, 100); | ||
| final List<String> strings = new ArrayList<>(count); | ||
| while (strings.size() < count) { | ||
| strings.add(randomAlphaOfLength(between(0, 10))); | ||
| } | ||
|
|
||
| final String fullDescription = collectionToDelimitedString(strings, delimiter, prefix, suffix); | ||
| for (String string : strings) { | ||
| assertThat(fullDescription, containsString(prefix + string + suffix)); | ||
| } | ||
|
|
||
| final int lastItemSize = prefix.length() + strings.get(count - 1).length() + suffix.length(); | ||
| final int minLimit = fullDescription.length() - lastItemSize; | ||
| final int limit = randomFrom(between(minLimit, fullDescription.length()), between(minLimit, Integer.MAX_VALUE), Integer.MAX_VALUE); | ||
|
|
||
| assertThat(testHarness.getResult(strings, prefix, delimiter, suffix, limit), equalTo(fullDescription)); | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized the
prefixandsuffixparams don't work as I thought they would. They're added to every item, whereas I thought they would only be added once at the start and once at the end respectively. I see that the two usages in rollover and lazy rollover are the only usages that specify a (non-empty) prefix and suffix, and it looks to me like it doesn't really make sense to wrap every result with the string withlazy bulk rollover [...]; it would make more sense to wrap all the results with the stringlazy bulk rollover [result1, result2].Assuming that we'd fix those two use cases, there are no use cases of
Strings.collectionToDelimitedStringWithLimitthat pass non-empty strings for theprefixandsuffix, so I think we can just remove those two parameters. What do you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ha yeah that caught me out when re-implementing it here too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let me open a PR to do that first, it'll be noisy here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#124353