diff --git a/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF b/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF
index c057f945887..1f8e3634d9f 100644
--- a/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF
+++ b/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF
@@ -7,8 +7,9 @@ Require-Bundle: org.eclipse.compare,
org.eclipse.jface.text,
org.eclipse.jface,
org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)",
- org.eclipse.test.performance,
org.eclipse.ui;bundle-version="3.208.0",
+ org.eclipse.ui.ide,
+ org.eclipse.ui.workbench.texteditor,
org.eclipse.core.resources,
org.eclipse.core.tests.resources,
org.eclipse.core.tests.harness,
diff --git a/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/CompareOpenPerformanceTest.java b/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/CompareOpenPerformanceTest.java
new file mode 100644
index 00000000000..f8265ed9d1f
--- /dev/null
+++ b/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/CompareOpenPerformanceTest.java
@@ -0,0 +1,513 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Lars Vogel and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eclipse contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.tests.performance;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BooleanSupplier;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.compare.CompareViewerSwitchingPane;
+import org.eclipse.compare.IEncodedStreamContentAccessor;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.contentmergeviewer.IIgnoreWhitespaceContributor;
+import org.eclipse.compare.contentmergeviewer.ITokenComparator;
+import org.eclipse.compare.contentmergeviewer.TokenComparator;
+import org.eclipse.compare.internal.ComparePreferencePage;
+import org.eclipse.compare.internal.CompareUIPlugin;
+import org.eclipse.compare.internal.IMergeViewerTestAdapter;
+import org.eclipse.compare.internal.merge.DocumentMerger;
+import org.eclipse.compare.internal.merge.DocumentMerger.IDocumentMergerInput;
+import org.eclipse.compare.structuremergeviewer.DiffNode;
+import org.eclipse.compare.tests.ReflectionUtils;
+import org.eclipse.compare.unifieddiff.UnifiedDiff;
+import org.eclipse.compare.unifieddiff.UnifiedDiffMode;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Adapters;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.RepetitionInfo;
+
+/**
+ * Timing measurements for the compare editor open path. These are not correctness
+ * tests: each scenario is repeated, the first repetitions are discarded as warmup
+ * and the median is printed as a stable greppable {@code PERF ...} line plus a CSV
+ * under the bundle {@code target/} directory. Only a generous sanity bound is
+ * asserted, so unstable shared-CI timings do not produce spurious failures.
+ *
+ * This class is intentionally not part of {@code AllCompareTests}; it is wired into
+ * {@link PerformanceTestSuite} instead.
+ */
+public class CompareOpenPerformanceTest {
+
+ private static final int REPETITIONS = 20;
+ private static final int WARMUP = 5;
+ private static final long TIMEOUT_MILLIS = 60_000;
+ private static final long SANITY_BOUND_MILLIS = 10_000;
+
+ private static final String UNIFIED_DIFF_ANNOTATION_PREFIX = "org.eclipse.compare.unifieddiff"; //$NON-NLS-1$
+
+ private enum Scenario {
+ CLASSIC_5K_SPARSE("compare-open", 5000, 100), //$NON-NLS-1$
+ CLASSIC_5K_DENSE("compare-open", 5000, 1250), //$NON-NLS-1$
+ CLASSIC_50K_SPARSE("compare-open", 50000, 1000), //$NON-NLS-1$
+ UNIFIED_5K_SPARSE("compare-open-unified", 5000, 100), //$NON-NLS-1$
+ DOC_MERGER_5K_DENSE("document-merger", 5000, 1250); //$NON-NLS-1$
+
+ final String label;
+ final int lines;
+ final int changes;
+ final List samples = new ArrayList<>();
+
+ Scenario(String label, int lines, int changes) {
+ this.label = label;
+ this.lines = lines;
+ this.changes = changes;
+ }
+ }
+
+ private boolean originalUnifiedDiff;
+ private static IProject unifiedProject;
+
+ @BeforeEach
+ public void setUp() {
+ originalUnifiedDiff = store().getBoolean(ComparePreferencePage.UNIFIED_DIFF);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ store().setValue(ComparePreferencePage.UNIFIED_DIFF, originalUnifiedDiff);
+ closeAllEditors();
+ }
+
+ @RepeatedTest(REPETITIONS)
+ public void compareOpen5kSparse(RepetitionInfo info) {
+ measureCompareOpen(Scenario.CLASSIC_5K_SPARSE, info);
+ }
+
+ @RepeatedTest(REPETITIONS)
+ public void compareOpen5kDense(RepetitionInfo info) {
+ measureCompareOpen(Scenario.CLASSIC_5K_DENSE, info);
+ }
+
+ @RepeatedTest(REPETITIONS)
+ public void compareOpen50kSparse(RepetitionInfo info) {
+ measureCompareOpen(Scenario.CLASSIC_50K_SPARSE, info);
+ }
+
+ @RepeatedTest(REPETITIONS)
+ public void compareOpenUnified5kSparse(RepetitionInfo info) throws Exception {
+ measureUnifiedOpen(Scenario.UNIFIED_5K_SPARSE, info);
+ }
+
+ @RepeatedTest(REPETITIONS)
+ public void documentMergerDoDiff5kDense(RepetitionInfo info) throws Exception {
+ measureDocumentMerger(Scenario.DOC_MERGER_5K_DENSE, info);
+ }
+
+ private void measureCompareOpen(Scenario scenario, RepetitionInfo info) {
+ store().setValue(ComparePreferencePage.UNIFIED_DIFF, false);
+ String[] content = generate(scenario.lines, scenario.changes);
+ TextCompareEditorInput input = new TextCompareEditorInput(content[0], content[1]);
+
+ long start = System.nanoTime();
+ long[] textReadyAt = { -1 };
+ CompareUI.openCompareEditor(input);
+ pumpUntil(() -> {
+ CompareViewerSwitchingPane pane = contentPane(input);
+ Viewer viewer = pane == null ? null : pane.getViewer();
+ if (viewer != null && textReadyAt[0] < 0) {
+ textReadyAt[0] = System.nanoTime();
+ }
+ IMergeViewerTestAdapter adapter = viewer == null ? null
+ : Adapters.adapt(viewer, IMergeViewerTestAdapter.class);
+ return adapter != null && adapter.getChangesCount() > 0;
+ }, "compare editor did not report a diff"); //$NON-NLS-1$
+ long diffReadyAt = System.nanoTime();
+
+ record(scenario, info, millis(start, textReadyAt[0]), millis(start, diffReadyAt));
+ closeAllEditors();
+ }
+
+ private void measureUnifiedOpen(Scenario scenario, RepetitionInfo info) throws Exception {
+ store().setValue(ComparePreferencePage.UNIFIED_DIFF, true);
+ String[] content = generate(scenario.lines, scenario.changes);
+ IFile file = unifiedFile(content[0]);
+
+ IWorkbenchPage page = activePage();
+ long start = System.nanoTime();
+ ITextEditor editor = (ITextEditor) IDE.openEditor(page, file);
+ pumpUntil(() -> editor.getAdapter(ITextViewer.class) != null,
+ "text editor did not open"); //$NON-NLS-1$
+ long textReadyAt = System.nanoTime();
+
+ IAnnotationModel model = editor.getDocumentProvider().getAnnotationModel(editor.getEditorInput());
+ assertNotNull(model, "annotation model must not be null"); //$NON-NLS-1$
+ UnifiedDiff.create(editor, content[1], UnifiedDiffMode.REVERT_MODE).open();
+ pumpUntil(() -> hasUnifiedDiffAnnotation(model), "unified diff annotations did not appear"); //$NON-NLS-1$
+ long diffReadyAt = System.nanoTime();
+
+ record(scenario, info, millis(start, textReadyAt), millis(start, diffReadyAt));
+ page.closeAllEditors(false);
+ processQueuedEvents();
+ }
+
+ private void measureDocumentMerger(Scenario scenario, RepetitionInfo info) throws Exception {
+ String[] content = generate(scenario.lines, scenario.changes);
+ IDocument left = new Document(content[0]);
+ IDocument right = new Document(content[1]);
+ DocumentMerger merger = new DocumentMerger(headlessInput(left, right));
+
+ long start = System.nanoTime();
+ merger.doDiff();
+ long doneAt = System.nanoTime();
+
+ if (info.getCurrentRepetition() > WARMUP) {
+ scenario.samples.add(new long[] { millis(start, doneAt), millis(start, doneAt) });
+ }
+ assertTrue(millis(start, doneAt) < SANITY_BOUND_MILLIS,
+ "DocumentMerger.doDiff() exceeded the sanity bound"); //$NON-NLS-1$
+ assertTrue(merger.changesCount() > 0, "expected at least one change"); //$NON-NLS-1$
+ }
+
+ private void record(Scenario scenario, RepetitionInfo info, long timeToText, long timeToDiff) {
+ if (info.getCurrentRepetition() > WARMUP) {
+ scenario.samples.add(new long[] { timeToText, timeToDiff });
+ }
+ assertTrue(timeToDiff < SANITY_BOUND_MILLIS,
+ scenario.name() + " time to diff exceeded the sanity bound: " + timeToDiff + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @AfterAll
+ public static void reportResults() throws IOException {
+ StringBuilder csv = new StringBuilder("label,lines,changes,timeToTextMedianMs,timeToDiffMedianMs,n\n"); //$NON-NLS-1$
+ for (Scenario scenario : Scenario.values()) {
+ if (scenario.samples.isEmpty()) {
+ continue;
+ }
+ long textMedian = median(scenario.samples, 0);
+ long diffMedian = median(scenario.samples, 1);
+ int n = scenario.samples.size();
+ if (scenario.label.equals("document-merger")) { //$NON-NLS-1$
+ System.out.println("PERF document-merger lines=" + scenario.lines + " changes=" + scenario.changes //$NON-NLS-1$ //$NON-NLS-2$
+ + " doDiff=" + diffMedian + "ms (median, n=" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ } else {
+ System.out.println("PERF " + scenario.label + " lines=" + scenario.lines + " changes=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ + scenario.changes + " timeToText=" + textMedian + "ms timeToDiff=" + diffMedian //$NON-NLS-1$ //$NON-NLS-2$
+ + "ms (median, n=" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ csv.append(scenario.label).append(',').append(scenario.lines).append(',').append(scenario.changes)
+ .append(',').append(textMedian).append(',').append(diffMedian).append(',').append(n)
+ .append('\n');
+ }
+ writeCsv(csv.toString());
+ if (unifiedProject != null && unifiedProject.exists()) {
+ try {
+ unifiedProject.delete(true, null);
+ } catch (CoreException e) {
+ // ignore cleanup failure
+ }
+ }
+ }
+
+ private static void writeCsv(String content) throws IOException {
+ File targetDir = new File("target"); //$NON-NLS-1$
+ if (!targetDir.isDirectory() && !targetDir.mkdirs()) {
+ targetDir = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+ }
+ File csvFile = new File(targetDir, "compare-open-performance.csv"); //$NON-NLS-1$
+ Files.writeString(csvFile.toPath(), content);
+ System.out.println("PERF csv written to " + csvFile.getAbsolutePath()); //$NON-NLS-1$
+ }
+
+ // Synthetic content generator modeled on RangeDifferencerTest.createDocument.
+ // The right side modifies roughly "changes" evenly spaced lines of the left side.
+ private static String[] generate(int lines, int changes) {
+ int interval = changes > 0 ? Math.max(1, lines / changes) : lines + 1;
+ StringBuilder left = new StringBuilder(lines * 10);
+ StringBuilder right = new StringBuilder(lines * 12);
+ for (int i = 0; i < lines; i++) {
+ String base = "line " + i; //$NON-NLS-1$
+ left.append(base).append('\n');
+ if (i > 0 && i % interval == 0) {
+ right.append(base).append(" CHANGED").append('\n'); //$NON-NLS-1$
+ } else {
+ right.append(base).append('\n');
+ }
+ }
+ return new String[] { left.toString(), right.toString() };
+ }
+
+ private IFile unifiedFile(String content) throws CoreException {
+ if (unifiedProject == null) {
+ unifiedProject = ResourcesPlugin.getWorkspace().getRoot().getProject("CompareOpenPerformance"); //$NON-NLS-1$
+ }
+ if (!unifiedProject.exists()) {
+ unifiedProject.create(null);
+ }
+ if (!unifiedProject.isOpen()) {
+ unifiedProject.open(null);
+ }
+ IFile file = unifiedProject.getFile("unified.txt"); //$NON-NLS-1$
+ byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
+ if (file.exists()) {
+ file.setContents(new ByteArrayInputStream(bytes), true, false, null);
+ } else {
+ file.create(new ByteArrayInputStream(bytes), true, null);
+ }
+ return file;
+ }
+
+ private static boolean hasUnifiedDiffAnnotation(IAnnotationModel model) {
+ Iterator it = model.getAnnotationIterator();
+ while (it.hasNext()) {
+ Annotation annotation = it.next();
+ String type = annotation.getType();
+ if (type != null && type.startsWith(UNIFIED_DIFF_ANNOTATION_PREFIX)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static IDocumentMergerInput headlessInput(IDocument left, IDocument right) {
+ return new IDocumentMergerInput() {
+ @Override
+ public IDocument getDocument(char contributor) {
+ return contributor == 'L' ? left : contributor == 'R' ? right : null;
+ }
+
+ @Override
+ public Position getRegion(char contributor) {
+ return null;
+ }
+
+ @Override
+ public boolean isIgnoreAncestor() {
+ return true;
+ }
+
+ @Override
+ public boolean isThreeWay() {
+ return false;
+ }
+
+ @Override
+ public CompareConfiguration getCompareConfiguration() {
+ return new CompareConfiguration();
+ }
+
+ @Override
+ public ITokenComparator createTokenComparator(String line) {
+ return new TokenComparator(line);
+ }
+
+ @Override
+ public Optional createIgnoreWhitespaceContributor(IDocument document) {
+ return Optional.empty();
+ }
+
+ @Override
+ public boolean isHunkOnLeft() {
+ return false;
+ }
+
+ @Override
+ public int getHunkStart() {
+ return 0;
+ }
+
+ @Override
+ public boolean isPatchHunk() {
+ return false;
+ }
+
+ @Override
+ public boolean isShowPseudoConflicts() {
+ return false;
+ }
+
+ @Override
+ public boolean isPatchHunkOk() {
+ return false;
+ }
+ };
+ }
+
+ private static long median(List samples, int index) {
+ List values = new ArrayList<>(samples.size());
+ for (long[] sample : samples) {
+ values.add(sample[index]);
+ }
+ Collections.sort(values);
+ int n = values.size();
+ if (n == 0) {
+ return -1;
+ }
+ if (n % 2 == 1) {
+ return values.get(n / 2);
+ }
+ return (values.get(n / 2 - 1) + values.get(n / 2)) / 2;
+ }
+
+ private static long millis(long startNanos, long endNanos) {
+ if (endNanos < 0) {
+ return -1;
+ }
+ return (endNanos - startNanos) / 1_000_000;
+ }
+
+ private static class TextElement implements ITypedElement, IEncodedStreamContentAccessor {
+ private final String name;
+ private final byte[] bytes;
+
+ TextElement(String name, String content) {
+ this.name = name;
+ this.bytes = content.getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public InputStream getContents() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public String getCharset() {
+ return "UTF-8"; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getType() {
+ return TEXT_TYPE;
+ }
+
+ @Override
+ public Image getImage() {
+ return null;
+ }
+ }
+
+ private static class TextCompareEditorInput extends CompareEditorInput {
+ private final ITypedElement left;
+ private final ITypedElement right;
+
+ TextCompareEditorInput(String leftContent, String rightContent) {
+ super(new CompareConfiguration());
+ this.left = new TextElement("left.txt", leftContent); //$NON-NLS-1$
+ this.right = new TextElement("right.txt", rightContent); //$NON-NLS-1$
+ setTitle("Performance compare"); //$NON-NLS-1$
+ }
+
+ @Override
+ protected Object prepareInput(IProgressMonitor monitor) {
+ return new DiffNode(left, right);
+ }
+
+ @Override
+ public boolean canRunAsJob() {
+ return true;
+ }
+ }
+
+ private static CompareViewerSwitchingPane contentPane(CompareEditorInput input) {
+ try {
+ return (CompareViewerSwitchingPane) ReflectionUtils.getField(input, "fContentInputPane", true); //$NON-NLS-1$
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void pumpUntil(BooleanSupplier condition, String failMessage) {
+ Display display = Display.getCurrent();
+ long deadline = System.currentTimeMillis() + TIMEOUT_MILLIS;
+ Runnable[] wake = new Runnable[1];
+ wake[0] = () -> display.timerExec(50, wake[0]);
+ display.timerExec(50, wake[0]);
+ try {
+ while (!condition.getAsBoolean()) {
+ if (System.currentTimeMillis() > deadline) {
+ fail(failMessage + " within " + TIMEOUT_MILLIS + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ } finally {
+ display.timerExec(-1, wake[0]);
+ }
+ }
+
+ private void closeAllEditors() {
+ IWorkbenchPage page = activePage();
+ if (page != null) {
+ page.closeAllEditors(false);
+ }
+ processQueuedEvents();
+ }
+
+ private void processQueuedEvents() {
+ Display display = Display.getCurrent();
+ while (display.readAndDispatch()) {
+ // drain the event queue
+ }
+ }
+
+ private static IWorkbenchPage activePage() {
+ return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ }
+
+ private static IPreferenceStore store() {
+ return CompareUIPlugin.getDefault().getPreferenceStore();
+ }
+}
diff --git a/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/PerformanceTestSuite.java b/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/PerformanceTestSuite.java
index ef3e05925fe..80223dce2e3 100644
--- a/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/PerformanceTestSuite.java
+++ b/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/PerformanceTestSuite.java
@@ -20,7 +20,7 @@
* @since 3.1
*/
@Suite
-@SelectClasses({RangeDifferencerTest.class})
+@SelectClasses({ RangeDifferencerTest.class, CompareOpenPerformanceTest.class })
public class PerformanceTestSuite {
//
}
diff --git a/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/RangeDifferencerTest.java b/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/RangeDifferencerTest.java
index 21a0b755549..eb618119d25 100644
--- a/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/RangeDifferencerTest.java
+++ b/team/tests/org.eclipse.compare.tests/src/org/eclipse/compare/tests/performance/RangeDifferencerTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2005, 2009 IBM Corporation and others.
+ * Copyright (c) 2005, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,10 +10,16 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Eclipse contributors - migrated off org.eclipse.test.performance
*******************************************************************************/
package org.eclipse.compare.tests.performance;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import org.eclipse.compare.contentmergeviewer.ITokenComparator;
import org.eclipse.compare.internal.DocLineComparator;
@@ -22,78 +28,96 @@
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
-import org.eclipse.test.performance.Dimension;
-import org.eclipse.test.performance.PerformanceTestCaseJunit5;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.RepetitionInfo;
-public class RangeDifferencerTest extends PerformanceTestCaseJunit5 {
+/**
+ * Timing measurement for a three way {@link RangeDifferencer} run on 5000 line
+ * documents. Correctness of the produced difference pattern is asserted on every
+ * repetition; the median wall time of the non-warmup repetitions is printed as a
+ * greppable {@code PERF ...} line. Only a generous sanity bound is asserted so the
+ * test stays useful on shared CI machines.
+ */
+public class RangeDifferencerTest {
+ private static final int REPETITIONS = 20;
+ private static final int WARMUP = 5;
+ private static final long SANITY_BOUND_MILLIS = 10_000;
- // static private final String EXPLANATION = "Performance decrease caused by changes in the compare framework, see bug 210688";
+ private static final List durations = new ArrayList<>();
/*
- * Creates document with 5000 lines.
- * Parameter code determines where additional lines are added.
+ * Creates a document with 5000 lines. Parameter code determines where
+ * additional lines are added.
*/
private IDocument createDocument(int code) {
- StringBuilder sb= new StringBuilder();
- for (int i= 0; i < 5000; i++) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 5000; i++) {
sb.append("line "); //$NON-NLS-1$
sb.append(Integer.toString(i));
sb.append('\n');
- int mod= i % 10;
+ int mod = i % 10;
switch (code) {
case 1:
- if (mod == 1)
+ if (mod == 1) {
sb.append("outgoing\n"); //$NON-NLS-1$
- if (mod == 4)
- sb.append("conflict1\n"); //$NON-NLS-1$
+ }
+ if (mod == 4) {
+ sb.append("conflict1\n"); //$NON-NLS-1$
+ }
break;
case 2:
- if (mod == 7)
+ if (mod == 7) {
sb.append("incoming\n"); //$NON-NLS-1$
- if (mod == 4)
- sb.append("conflict2\n"); //$NON-NLS-1$
+ }
+ if (mod == 4) {
+ sb.append("conflict2\n"); //$NON-NLS-1$
+ }
break;
}
}
return new Document(sb.toString());
}
- @Test
- public void testLargeDocument() {
-
- tagAsGlobalSummary("3-way compare, 5000 lines", Dimension.ELAPSED_PROCESS); //$NON-NLS-1$
- // setComment(Performance.EXPLAINS_DEGRADATION_COMMENT, EXPLANATION);
-
- ITokenComparator ancestor= new DocLineComparator(createDocument(0), null, false);
- ITokenComparator left= new DocLineComparator(createDocument(1), null, false);
- ITokenComparator right= new DocLineComparator(createDocument(2), null, false);
+ @RepeatedTest(REPETITIONS)
+ public void testLargeDocument(RepetitionInfo info) {
+ ITokenComparator ancestor = new DocLineComparator(createDocument(0), null, false);
+ ITokenComparator left = new DocLineComparator(createDocument(1), null, false);
+ ITokenComparator right = new DocLineComparator(createDocument(2), null, false);
- RangeDifference[] diffs= null;
+ long start = System.nanoTime();
+ RangeDifference[] diffs = RangeDifferencer.findRanges(new NullProgressMonitor(), ancestor, left, right);
+ long durationMillis = (System.nanoTime() - start) / 1_000_000;
- // a warm up run
- diffs= RangeDifferencer.findRanges(new NullProgressMonitor(), ancestor, left, right);
-
- // assert that result correct
- for (int i= 0; i < diffs.length-6; i+= 6) {
- assertEquals(diffs[i+0].kind(), RangeDifference.NOCHANGE);
- assertEquals(diffs[i+1].kind(), RangeDifference.LEFT);
- assertEquals(diffs[i+2].kind(), RangeDifference.NOCHANGE);
- assertEquals(diffs[i+3].kind(), RangeDifference.CONFLICT);
- assertEquals(diffs[i+4].kind(), RangeDifference.NOCHANGE);
- assertEquals(diffs[i+5].kind(), RangeDifference.RIGHT);
+ // Assert the result is correct on every repetition.
+ for (int i = 0; i < diffs.length - 6; i += 6) {
+ assertEquals(RangeDifference.NOCHANGE, diffs[i + 0].kind());
+ assertEquals(RangeDifference.LEFT, diffs[i + 1].kind());
+ assertEquals(RangeDifference.NOCHANGE, diffs[i + 2].kind());
+ assertEquals(RangeDifference.CONFLICT, diffs[i + 3].kind());
+ assertEquals(RangeDifference.NOCHANGE, diffs[i + 4].kind());
+ assertEquals(RangeDifference.RIGHT, diffs[i + 5].kind());
}
- // now do 3 performance runs
- for (int count= 0; count < 3; count++) {
- startMeasuring();
- RangeDifferencer.findRanges(new NullProgressMonitor(), ancestor, left, right);
- stopMeasuring();
+ if (info.getCurrentRepetition() > WARMUP) {
+ durations.add(durationMillis);
}
+ assertTrue(durationMillis < SANITY_BOUND_MILLIS,
+ "findRanges exceeded the sanity bound: " + durationMillis + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
- commitMeasurements();
- assertPerformance();
+ @AfterAll
+ public static void reportResults() {
+ if (durations.isEmpty()) {
+ return;
+ }
+ List sorted = new ArrayList<>(durations);
+ Collections.sort(sorted);
+ int n = sorted.size();
+ long median = n % 2 == 1 ? sorted.get(n / 2) : (sorted.get(n / 2 - 1) + sorted.get(n / 2)) / 2;
+ System.out.println("PERF range-differencer lines=5000 threeWay findRanges=" + median //$NON-NLS-1$
+ + "ms (median, n=" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
}