diff --git a/bin/jmeter.properties b/bin/jmeter.properties index 447b3649f0e..8996e4f185d 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -1180,6 +1180,13 @@ cookies=cookies # Set to 0 to disable the size check and display the whole response #view.results.tree.max_size=10485760 +# UI gets unresponsive when response contains very long lines, +# So we break lines by adding artificial line breaks +# The break is introduced somewhere in between soft_wrap_line_size..max_line_size +# We try to break on word boundaries first +#view.results.tree.max_line_size=110000 +#view.results.tree.soft_wrap_line_size=100000 + # Order of Renderers in View Results Tree # Note full class names should be used for non JMeter core renderers # For JMeter core renderers, class names start with '.' and are automatically @@ -1194,6 +1201,9 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar # Set to 0 to disable the size check #document.max_size=0 +# Configures the maximum document length for rendering with kerning enabled +#text.kerning.max_document_size=10000 + #JMS options # Enable the following property to stop JMS Point-to-Point Sampler from using # the properties java.naming.security.[principal|credentials] when creating the queue connection diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java index 0af0cdacf17..e48002e246f 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsBoundaryExtractor.java @@ -212,6 +212,7 @@ public void setSamplerResult(Object userObject) { public void renderResult(SampleResult sampleResult) { clearData(); String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + response = ViewResultsFullVisualizer.wrapLongLines(response); boundaryExtractorDataField.setText(response); boundaryExtractorDataField.setCaretPosition(0); } diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java index eff86a9be55..e802246feab 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsCssJQuery.java @@ -145,6 +145,7 @@ private String process(String textToParse) { public void renderResult(SampleResult sampleResult) { clearData(); String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + response = ViewResultsFullVisualizer.wrapLongLines(response); cssJqueryDataField.setText(response); cssJqueryDataField.setCaretPosition(0); } diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java index 66d74fb0021..f598e0a1efa 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsRegexp.java @@ -143,6 +143,7 @@ private String process(String textToParse) { public void renderResult(SampleResult sampleResult) { clearData(); String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + response = ViewResultsFullVisualizer.wrapLongLines(response); regexpDataField.setText(response); regexpDataField.setCaretPosition(0); } diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java index 2399f56db2c..f8e6ea51e8e 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath.java @@ -180,6 +180,7 @@ private Document parseResponse(String unicodeData, XPathExtractor extractor) @Override public void renderResult(SampleResult sampleResult) { String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + response = ViewResultsFullVisualizer.wrapLongLines(response); try { xmlDataField.setText(response == null ? "" : response); xmlDataField.setCaretPosition(0); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java index b55ffcbac01..4dd8acf151a 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/RenderAsXPath2.java @@ -164,6 +164,7 @@ private String getDocumentNamespaces(String textToParse) { @Override public void renderResult(SampleResult sampleResult) { String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult); + response = ViewResultsFullVisualizer.wrapLongLines(response); try { xmlDataField.setText(response == null ? "" : response); xmlDataField.setCaretPosition(0); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java b/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java index 3828c2a571d..5a9afe4fad2 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java @@ -61,6 +61,7 @@ import org.apache.jorphan.gui.GuiUtils; import org.apache.jorphan.gui.ObjectTableModel; import org.apache.jorphan.gui.RendererUtils; +import org.apache.jorphan.gui.ui.KerningOptimizer; import org.apache.jorphan.reflect.Functor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -688,10 +689,12 @@ protected void setTextOptimized(String data) { Document blank = new DefaultStyledDocument(); results.setDocument(blank); try { + data = ViewResultsFullVisualizer.wrapLongLines(data); document.insertString(0, data == null ? "" : data, null); } catch (BadLocationException ex) { LOGGER.error("Error inserting text", ex); } + KerningOptimizer.INSTANCE.configureKerning(results, document.getLength()); results.setDocument(document); } } diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java index d4520d55820..3a549ba9c1e 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java @@ -74,6 +74,8 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.gui.AbstractVisualizer; import org.apache.jorphan.gui.JMeterUIDefaults; +import org.apache.jorphan.util.StringWrap; +import org.apiguardian.api.API; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -103,6 +105,14 @@ public class ViewResultsFullVisualizer extends AbstractVisualizer private static final int MAX_DISPLAY_SIZE = JMeterUtils.getPropDefault("view.results.tree.max_size", 10485760); // $NON-NLS-1$ + // Default limited to 110K + private static final int MAX_LINE_SIZE = + JMeterUtils.getPropDefault("view.results.tree.max_line_size", 110000); // $NON-NLS-1$ + + // Limit the soft wrap to 100K (hard limit divided by 1.1) + private static final int SOFT_WRAP_LINE_SIZE = + JMeterUtils.getPropDefault("view.results.tree.soft_wrap_line_size", (int) (MAX_LINE_SIZE / 1.1f)); // $NON-NLS-1$ + // default display order private static final String VIEWERS_ORDER = JMeterUtils.getPropDefault("view.results.tree.renderers_order", ""); // $NON-NLS-1$ //$NON-NLS-2$ @@ -563,6 +573,19 @@ public static String getResponseAsString(SampleResult res) { return response; } + @API(status = API.Status.INTERNAL, since = "5.5") + public static String wrapLongLines(String input) { + if (input == null || input.isEmpty()) { + return input; + } + if (SOFT_WRAP_LINE_SIZE > 0 && MAX_LINE_SIZE > 0) { + StringWrap stringWrap = new StringWrap(SOFT_WRAP_LINE_SIZE, MAX_LINE_SIZE); + return stringWrap.wrap(input, "\n"); + } + return input; + } + + private static class ResultsNodeRenderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = 4159626601097711565L; diff --git a/src/core/src/main/java/org/apache/jmeter/JMeter.java b/src/core/src/main/java/org/apache/jmeter/JMeter.java index 83a62020036..e5dffc61a99 100644 --- a/src/core/src/main/java/org/apache/jmeter/JMeter.java +++ b/src/core/src/main/java/org/apache/jmeter/JMeter.java @@ -105,6 +105,7 @@ import org.apache.jorphan.collections.SearchByClass; import org.apache.jorphan.gui.ComponentUtil; import org.apache.jorphan.gui.JMeterUIDefaults; +import org.apache.jorphan.gui.ui.KerningOptimizer; import org.apache.jorphan.reflect.ClassTools; import org.apache.jorphan.util.HeapDumper; import org.apache.jorphan.util.JMeterException; @@ -374,6 +375,9 @@ private void startGui(String testFile) { System.out.println("Check : https://jmeter.apache.org/usermanual/best-practices.html");//NOSONAR System.out.println("================================================================================");//NOSONAR + KerningOptimizer.INSTANCE.setMaxTextLengthWithKerning( + JMeterUtils.getPropDefault("text.kerning.max_document_size", 10000) + ); JMeterUIDefaults.INSTANCE.install(); String jMeterLaf = LookAndFeelCommand.getPreferredLafCommand(); diff --git a/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/KerningOptimizer.java b/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/KerningOptimizer.java new file mode 100644 index 00000000000..a414cae47d5 --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/KerningOptimizer.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.gui.ui; + +import java.awt.Font; +import java.awt.font.TextAttribute; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; +import java.util.Collections; + +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; + +import org.apiguardian.api.API; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Text rendering might be slow for long lines when kerning is enabled, so it is worth disabling kerning for long + * texts. + */ +@API(since = "5.5", status = API.Status.INTERNAL) +public class KerningOptimizer { + private final static Logger log = LoggerFactory.getLogger(KerningOptimizer.class); + + public static final KerningOptimizer INSTANCE = new KerningOptimizer(); + + private volatile int maxLengthWithKerning = 10000; + + /** + * Cache to avoid repeated calls to {@link Font#getAttributes()} since it copies the map every time. + */ + static class FontKerningCache { + private static final String CLIENT_PROPERTY_KEY = "[jmeter]FontKerningCache"; + WeakReference font; + boolean kerning; + + static Boolean kerningOf(JComponent component) { + Font font = component.getFont(); + if (font == null) { + return null; + } + if (!font.hasLayoutAttributes()) { + return false; + } + FontKerningCache cache = (FontKerningCache) component.getClientProperty(CLIENT_PROPERTY_KEY); + if (cache == null) { + cache = new FontKerningCache(); + component.putClientProperty(CLIENT_PROPERTY_KEY, cache); + } + if (cache.font == null || !font.equals(cache.font.get())) { + cache.font = new WeakReference<>(font); + cache.kerning = TextAttribute.KERNING_ON.equals(font.getAttributes().get(TextAttribute.KERNING)); + } + return cache.kerning; + } + } + + /** + * Configures the maximum document length for rendering with kerning enabled. + * + * @param length maximum document length for rendering with kerning enabled + */ + public void setMaxTextLengthWithKerning(int length) { + maxLengthWithKerning = length; + } + + public int getMaxTextLengthWithKerning() { + return maxLengthWithKerning; + } + + /** + * Configures text kerning according to the expected document length. This might be useful before setting the + * document so the kerning is disabled before updating the document. + * + * @param component text component for kerning configuration + * @param documentLength expected document length + */ + public void configureKerning(JComponent component, int documentLength) { + Boolean kerning = FontKerningCache.kerningOf(component); + if (kerning == null) { + return; + } + boolean desiredKerning = documentLength <= maxLengthWithKerning; + if (kerning != desiredKerning) { + if (log.isDebugEnabled()) { + log.info("Updating kerning (old: {}, new: {}), documentLength={}, component {}, ", kerning, desiredKerning, documentLength, component); + } + Font font = component.getFont(); + Font newFont = font.deriveFont(Collections.singletonMap(TextAttribute.KERNING, desiredKerning ? TextAttribute.KERNING_ON : 0)); + SwingUtilities.invokeLater(() -> component.setFont(newFont)); + } + } + + /** + * Adds a listener that disables kerning if text length reaches a certain threshold. + * + * @param textComponent text component for kerning configuration + */ + public void installKerningListener(JTextComponent textComponent) { + log.debug("Installing KerningOptimizer {} to {}", this, textComponent); + textComponent.addPropertyChangeListener("document", new DisableKerningForLargeTexts(textComponent)); + } + + /** + * Removes the listener that disables kerning if text length reaches a certain threshold. + * + * @param textComponent text component for kerning configuration + */ + public void uninstallKerningListener(JTextComponent textComponent) { + DisableKerningForLargeTexts kerningListener = null; + for (PropertyChangeListener listener : textComponent.getPropertyChangeListeners("document")) { + if (listener instanceof DisableKerningForLargeTexts) { + kerningListener = (DisableKerningForLargeTexts) listener; + } + } + if (kerningListener == null) { + return; + } + log.debug("Uninstalling KerningOptimizer {} from {}", this, textComponent); + Document document = textComponent.getDocument(); + if (document != null) { + document.removeDocumentListener(kerningListener); + } + textComponent.removePropertyChangeListener("document", kerningListener); + } + + static class DisableKerningForLargeTexts implements PropertyChangeListener, DocumentListener { + final JTextComponent component; + + DisableKerningForLargeTexts(JTextComponent component) { + this.component = component; + } + + private void configureKerning(Document e) { + // RSyntaxTextArea and other implementations do not expect setFont called from document change listeners + // It looks like invokeLater fixes that + Boolean kerning = FontKerningCache.kerningOf(component); + if (kerning == null) { + return; + } + boolean desiredKerning = e.getLength() <= INSTANCE.getMaxTextLengthWithKerning(); + if (kerning != desiredKerning) { + SwingUtilities.invokeLater(() -> INSTANCE.configureKerning(component, e.getLength())); + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (!"document".equals(evt.getPropertyName())) { + return; + } + Document oldDocument = (Document) evt.getOldValue(); + if (oldDocument != null) { + oldDocument.removeDocumentListener(this); + } + Document newDocument = (Document) evt.getNewValue(); + if (newDocument != null) { + newDocument.addDocumentListener(this); + configureKerning(newDocument); + } + } + + + @Override + public void insertUpdate(DocumentEvent e) { + configureKerning(e.getDocument()); + } + + @Override + public void removeUpdate(DocumentEvent e) { + configureKerning(e.getDocument()); + } + + @Override + public void changedUpdate(DocumentEvent e) { + configureKerning(e.getDocument()); + } + } +} diff --git a/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/TextAreaUIWithUndo.java b/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/TextAreaUIWithUndo.java index 75dd190a058..8cc94e061f6 100644 --- a/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/TextAreaUIWithUndo.java +++ b/src/jorphan/src/main/java/org/apache/jorphan/gui/ui/TextAreaUIWithUndo.java @@ -60,6 +60,7 @@ public static void install(UIDefaults defaults) { */ @SuppressWarnings("unused") public static ComponentUI createUI(JComponent component) { + KerningOptimizer.INSTANCE.installKerningListener((JTextComponent) component); TextComponentUI.INSTANCE.installUndo((JTextComponent) component); if (component.getClass() == JTextArea.class) { component.addPropertyChangeListener("UI", diff --git a/src/jorphan/src/main/java/org/apache/jorphan/util/StringWrap.java b/src/jorphan/src/main/java/org/apache/jorphan/util/StringWrap.java new file mode 100644 index 00000000000..b36f66ad490 --- /dev/null +++ b/src/jorphan/src/main/java/org/apache/jorphan/util/StringWrap.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.util; + +import java.text.BreakIterator; + +import org.apiguardian.api.API; + +/** + * Wraps text in such a way so the lines do not exceed given maximum length. + */ +@API(since = "5.5", status = API.Status.EXPERIMENTAL) +public class StringWrap { + private final int minWrap; + private final int maxWrap; + + private final BreakCursor wordCursor = new BreakCursor(BreakIterator.getLineInstance()); + private final BreakCursor charCursor = new BreakCursor(BreakIterator.getCharacterInstance()); + + /** + * Stores the current and the next position for a given {@link BreakIterator}. + * It allows reducing the number of calls to {@link BreakIterator}. + */ + private static class BreakCursor { + private static final int UNINITIALIZED = -2; + + private final BreakIterator iterator; + private int pos; + private int next; + + BreakCursor(BreakIterator iterator) { + this.iterator = iterator; + } + + void setText(String text) { + iterator.setText(text); + pos = 0; + next = UNINITIALIZED; + } + + public int getPos() { + return pos; + } + + /** + * Advances the cursor if possible. + * @param startWrap the start index of the wrap to consider + * @param endWrap the end index of the wrap to consider + * @return true if the next break is detected within startWrap..endWrap boundaries + */ + public boolean advance(int startWrap, int endWrap) { + if (pos == BreakIterator.DONE || pos > endWrap) { + return false; + } + pos = next != UNINITIALIZED ? next : iterator.following(startWrap); + if (pos == BreakIterator.DONE || pos > endWrap) { + return false; + } + // Try adding more items up to endWrap + while (true) { + next = iterator.next(); + if (next == BreakIterator.DONE || next > endWrap) { + break; + } + pos = next; + } + return true; + } + } + + /** + * Creates string wrapper instance. + * + * @param minWrap minimal word length for the wrap + * @param maxWrap maximum word length for the wrap + */ + public StringWrap(int minWrap, int maxWrap) { + this.minWrap = minWrap; + this.maxWrap = maxWrap; + } + + public int getMinWrap() { + return minWrap; + } + + public int getMaxWrap() { + return maxWrap; + } + + /** + * Wraps given {@code input} text accoding to + * + * @param input input text + * @param delimiter delimiter when inserting soft wraps + * @return modified text with added soft wraps, or input if wraps are not needed + */ + public String wrap(String input, String delimiter) { + if (input.length() <= minWrap) { + return input; + } + wordCursor.setText(input); + charCursor.setText(input); + int pos = 0; + StringBuilder sb = new StringBuilder(input.length() + input.length() / minWrap * delimiter.length()); + boolean hasChanges = false; + int nextLineSeparator = BreakCursor.UNINITIALIZED; + // Wrap long lines + while (input.length() - pos > maxWrap) { + if (nextLineSeparator != BreakIterator.DONE && nextLineSeparator < pos) { + nextLineSeparator = input.indexOf('\n', pos); + } + // Try adding the next line if it does not exceed maxWrap + int next = nextLineSeparator; + if (next != -1 && pos - next <= maxWrap) { + // The existing lines do not exceed maxWrap, just reuse them + next++; // include newline + sb.append(input, pos, next); + pos = next; + continue; + } + int startWrap = pos + minWrap - 1; + int endWrap = pos + maxWrap; + // Try breaking on word boundaries first + if (wordCursor.advance(startWrap, endWrap)) { + next = wordCursor.getPos(); + } else { + // If char advances at least once, add it with the break even if it exceeds maxWrap + // Note: single "char break" might consume multiple Java chars in case like emojis. + charCursor.advance(startWrap, endWrap); + next = charCursor.getPos(); + if (next == BreakIterator.DONE || next == input.length()) { + break; + } + } + sb.append(input, pos, next); + sb.append(delimiter); + hasChanges = true; + pos = next; + } + // Free up the memory + wordCursor.setText(""); + charCursor.setText(""); + if (!hasChanges) { + return input; + } + if (pos != input.length()) { + sb.append(input, pos, input.length()); + } + return sb.toString(); + } +} diff --git a/src/jorphan/src/test/java/org/apache/jorphan/util/StringWrapTest.java b/src/jorphan/src/test/java/org/apache/jorphan/util/StringWrapTest.java new file mode 100644 index 00000000000..12b19e3809b --- /dev/null +++ b/src/jorphan/src/test/java/org/apache/jorphan/util/StringWrapTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jorphan.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class StringWrapTest { + static Stream data() { + return Stream.of( + arguments(2, 2, "0123456789", "01|23|45|67|89"), + arguments(2, 5, "0123456789", "01234|56789"), + arguments(3, 3, "0123456789", "012|345|678|9"), + arguments(2, 5, "01-2-3-4-56-789-", "01-2-|3-4-|56-|789-"), + arguments(2, 5, "012\n345\n6\n7\n8\n9", null), + arguments(2, 5, "012\n3456789", "012\n34567|89"), + // Single-char symbols + arguments(2, 5, "丈丈丈丈丈a丈丈a 丈丈丈 b 丈丈", "丈丈丈丈丈|a丈丈a |丈丈丈 |b 丈丈"), + // Two-char symbols + arguments(2, 5, "निनिनिनिनिनिनdनिनिनिनि3निनिनिनिनिनि1 नि", "निनि|निनि|निनिन|dनिनि|निनि3|निनि|निनि|निनि1| नि"), + // Two-char symbols + arguments(2, 5, "😃😃😃😃😃😃😃😃a😃aa😃😃 😃😃 b 😃", "😃😃|😃😃|😃😃|😃😃a|😃aa|😃😃 |😃😃 |b 😃"), + arguments(1, 1, "😃", null), + // Multi-char symbols + arguments(2, 5, "rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆rè̑ͧ", "rè̑ͧ̌|aͨ|l̘̝̙̃ͤ͂̾̆|r|è̑ͧ̌|aͨ|l̘̝̙̃ͤ͂̾̆|rè̑ͧ"), + arguments(2, 9, "rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆rè̑ͧ", "rè̑ͧ̌aͨ|l̘̝̙̃ͤ͂̾̆|rè̑ͧ̌aͨ|l̘̝̙̃ͤ͂̾̆|rè̑ͧ") + ); + } + + @ParameterizedTest + @MethodSource("data") + void wrap(int minWrap, int maxWrap, String input, String expected) { + if (expected == null) { + expected = input; + } + StringWrap stringWrap = new StringWrap(minWrap, maxWrap); + String output = stringWrap.wrap(input, "|"); + assertEquals(expected, output, () -> "minWrap=" + minWrap + ", maxWrap=" + maxWrap + ", input=" + input); + } +} diff --git a/xdocs/changes.xml b/xdocs/changes.xml index 2f86ec86bde..1666936dc13 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -143,6 +143,7 @@ however, the profile can't be updated while the test is running.
  • 61805663Add simple HTTP request template. Contributed by Ori Marko (orimarko at gmail.com)
  • 65611673Add support for IPv6 addresses when specifying a remote worker node. Based on a patch by Peter Wong (peter.wong at csexperts.com)
  • Reduce memory consumption by the logging panel (disable undo events for it)
  • +
  • 63620694Fix GUI freeze when viewing response body with long line breaks
  • Non-functional changes diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index e373627838d..fb53cd2e1e2 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1510,6 +1510,18 @@ JMETER-SERVER Set to zero to disable the size check and display the whole response.
    Defaults to: 10485760 + + Maximum size (in characters) of the line in the displayed.
    + This property works around Bug 63620 since Swing hangs when displaying very long lines.
    + Set to zero to disable line wrapping.
    + Defaults to: 110000 +
    + + Line size (in characters) to consider wrapping to make UI faster.
    + This property works around Bug 63620 since Swing hangs when displaying very long lines.
    + Set to zero to disable line wrapping.
    + Defaults to: view.results.tree.max_line_size / 1.1f +
    Order of Renderers in View Results Tree.
    Note full class names should be used for non JMeter core renderers @@ -1523,6 +1535,10 @@ JMETER-SERVER Set to zero to disable the size check.
    Defaults to: 10485760
    + + Configures the maximum document length for rendering with kerning enabled.
    + Defaults to: 10000 +
    JMS options.
    Enable the following property to stop JMS Point-to-Point Sampler from using