View Javadoc
1   /*
2    * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.merge;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static org.junit.Assert.assertEquals;
15  
16  import java.io.ByteArrayOutputStream;
17  import java.io.IOException;
18  
19  import org.eclipse.jgit.diff.RawText;
20  import org.eclipse.jgit.diff.RawTextComparator;
21  import org.eclipse.jgit.lib.Constants;
22  import org.junit.Assume;
23  import org.junit.Test;
24  import org.junit.experimental.theories.DataPoints;
25  import org.junit.experimental.theories.Theories;
26  import org.junit.runner.RunWith;
27  
28  @RunWith(Theories.class)
29  public class MergeAlgorithmTest {
30  	MergeFormatter fmt=new MergeFormatter();
31  
32  	private final boolean newlineAtEnd;
33  
34  	@DataPoints
35  	public static boolean[] newlineAtEndDataPoints = { false, true };
36  
37  	public MergeAlgorithmTest(boolean newlineAtEnd) {
38  		this.newlineAtEnd = newlineAtEnd;
39  	}
40  
41  	/**
42  	 * Check for a conflict where the second text was changed similar to the
43  	 * first one, but the second texts modification covers one more line.
44  	 *
45  	 * @throws IOException
46  	 */
47  	@Test
48  	public void testTwoConflictingModifications() throws IOException {
49  		assertEquals(t("a<b=Z>Zdefghij"),
50  				merge("abcdefghij", "abZdefghij", "aZZdefghij"));
51  	}
52  
53  	/**
54  	 * Test a case where we have three consecutive chunks. The first text
55  	 * modifies all three chunks. The second text modifies the first and the
56  	 * last chunk. This should be reported as one conflicting region.
57  	 *
58  	 * @throws IOException
59  	 */
60  	@Test
61  	public void testOneAgainstTwoConflictingModifications() throws IOException {
62  		assertEquals(t("aZ<Z=c>Zefghij"),
63  				merge("abcdefghij", "aZZZefghij", "aZcZefghij"));
64  	}
65  
66  	/**
67  	 * Test a merge where only the second text contains modifications. Expect as
68  	 * merge result the second text.
69  	 *
70  	 * @throws IOException
71  	 */
72  	@Test
73  	public void testNoAgainstOneModification() throws IOException {
74  		assertEquals(t("aZcZefghij"),
75  				merge("abcdefghij", "abcdefghij", "aZcZefghij"));
76  	}
77  
78  	/**
79  	 * Both texts contain modifications but not on the same chunks. Expect a
80  	 * non-conflict merge result.
81  	 *
82  	 * @throws IOException
83  	 */
84  	@Test
85  	public void testTwoNonConflictingModifications() throws IOException {
86  		assertEquals(t("YbZdefghij"),
87  				merge("abcdefghij", "abZdefghij", "Ybcdefghij"));
88  	}
89  
90  	/**
91  	 * Merge two complicated modifications. The merge algorithm has to extend
92  	 * and combine conflicting regions to get to the expected merge result.
93  	 *
94  	 * @throws IOException
95  	 */
96  	@Test
97  	public void testTwoComplicatedModifications() throws IOException {
98  		assertEquals(t("a<ZZZZfZhZj=bYdYYYYiY>"),
99  				merge("abcdefghij", "aZZZZfZhZj", "abYdYYYYiY"));
100 	}
101 
102 	/**
103 	 * Merge two modifications with a shared delete at the end. The underlying
104 	 * diff algorithm has to provide consistent edit results to get the expected
105 	 * merge result.
106 	 *
107 	 * @throws IOException
108 	 */
109 	@Test
110 	public void testTwoModificationsWithSharedDelete() throws IOException {
111 		assertEquals(t("Cb}n}"),
112 				merge("ab}n}n}", "ab}n}", "Cb}n}"));
113 	}
114 
115 	/**
116 	 * Merge modifications with a shared insert in the middle. The
117 	 * underlying diff algorithm has to provide consistent edit
118 	 * results to get the expected merge result.
119 	 *
120 	 * @throws IOException
121 	 */
122 	@Test
123 	public void testModificationsWithMiddleInsert() throws IOException {
124 		assertEquals(t("aBcd123123uvwxPq"),
125 				merge("abcd123uvwxpq", "aBcd123123uvwxPq", "abcd123123uvwxpq"));
126 	}
127 
128 	/**
129 	 * Merge modifications with a shared delete in the middle. The
130 	 * underlying diff algorithm has to provide consistent edit
131 	 * results to get the expected merge result.
132 	 *
133 	 * @throws IOException
134 	 */
135 	@Test
136 	public void testModificationsWithMiddleDelete() throws IOException {
137 		assertEquals(t("Abz}z123Q"),
138 				merge("abz}z}z123q", "Abz}z123Q", "abz}z123q"));
139 	}
140 
141 	/**
142 	 * Test a conflicting region at the very start of the text.
143 	 *
144 	 * @throws IOException
145 	 */
146 	@Test
147 	public void testConflictAtStart() throws IOException {
148 		assertEquals(t("<Z=Y>bcdefghij"),
149 				merge("abcdefghij", "Zbcdefghij", "Ybcdefghij"));
150 	}
151 
152 	/**
153 	 * Test a conflicting region at the very end of the text.
154 	 *
155 	 * @throws IOException
156 	 */
157 	@Test
158 	public void testConflictAtEnd() throws IOException {
159 		assertEquals(t("abcdefghi<Z=Y>"),
160 				merge("abcdefghij", "abcdefghiZ", "abcdefghiY"));
161 	}
162 
163 	/**
164 	 * Check for a conflict where the second text was changed similar to the
165 	 * first one, but the second texts modification covers one more line.
166 	 *
167 	 * @throws IOException
168 	 */
169 	@Test
170 	public void testSameModification() throws IOException {
171 		assertEquals(t("abZdefghij"),
172 				merge("abcdefghij", "abZdefghij", "abZdefghij"));
173 	}
174 
175 	/**
176 	 * Check that a deleted vs. a modified line shows up as conflict (see Bug
177 	 * 328551)
178 	 *
179 	 * @throws IOException
180 	 */
181 	@Test
182 	public void testDeleteVsModify() throws IOException {
183 		assertEquals(t("ab<=Z>defghij"),
184 				merge("abcdefghij", "abdefghij", "abZdefghij"));
185 	}
186 
187 	@Test
188 	public void testInsertVsModify() throws IOException {
189 		assertEquals(t("a<bZ=XY>"), merge("ab", "abZ", "aXY"));
190 	}
191 
192 	@Test
193 	public void testAdjacentModifications() throws IOException {
194 		assertEquals(t("a<Zc=bY>d"), merge("abcd", "aZcd", "abYd"));
195 	}
196 
197 	@Test
198 	public void testSeparateModifications() throws IOException {
199 		assertEquals(t("aZcYe"), merge("abcde", "aZcde", "abcYe"));
200 	}
201 
202 	@Test
203 	public void testBlankLines() throws IOException {
204 		assertEquals(t("aZc\nYe"), merge("abc\nde", "aZc\nde", "abc\nYe"));
205 	}
206 
207 	/**
208 	 * Test merging two contents which do one similar modification and one
209 	 * insertion is only done by one side, in the middle. Between modification
210 	 * and insertion is a block which is common between the two contents and the
211 	 * common base
212 	 *
213 	 * @throws IOException
214 	 */
215 	@Test
216 	public void testTwoSimilarModsAndOneInsert() throws IOException {
217 		assertEquals(t("aBcDde"), merge("abcde", "aBcde", "aBcDde"));
218 		assertEquals(t("IAAAJCAB"), merge("iACAB", "IACAB", "IAAAJCAB"));
219 		assertEquals(t("HIAAAJCAB"), merge("HiACAB", "HIACAB", "HIAAAJCAB"));
220 		assertEquals(t("AGADEFHIAAAJCAB"),
221 				merge("AGADEFHiACAB", "AGADEFHIACAB", "AGADEFHIAAAJCAB"));
222 	}
223 
224 	/**
225 	 * Test merging two contents which do one similar modification and one
226 	 * insertion is only done by one side, at the end. Between modification and
227 	 * insertion is a block which is common between the two contents and the
228 	 * common base
229 	 *
230 	 * @throws IOException
231 	 */
232 	@Test
233 	public void testTwoSimilarModsAndOneInsertAtEnd() throws IOException {
234 		Assume.assumeTrue(newlineAtEnd);
235 		assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ"));
236 		assertEquals(t("IAJ"), merge("iA", "IA", "IAJ"));
237 		assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ"));
238 	}
239 
240 	@Test
241 	public void testTwoSimilarModsAndOneInsertAtEndNoNewlineAtEnd()
242 			throws IOException {
243 		Assume.assumeFalse(newlineAtEnd);
244 		assertEquals(t("I<A=AAJ>"), merge("iA", "IA", "IAAJ"));
245 		assertEquals(t("I<A=AJ>"), merge("iA", "IA", "IAJ"));
246 		assertEquals(t("I<A=AAAJ>"), merge("iA", "IA", "IAAAJ"));
247 	}
248 
249 	/**
250 	 * Test situations where (at least) one input value is the empty text
251 	 *
252 	 * @throws IOException
253 	 */
254 	@Test
255 	public void testEmptyTexts() throws IOException {
256 		// test modification against deletion
257 		assertEquals(t("<AB=>"), merge("A", "AB", ""));
258 		assertEquals(t("<=AB>"), merge("A", "", "AB"));
259 
260 		// test unmodified against deletion
261 		assertEquals(t(""), merge("AB", "AB", ""));
262 		assertEquals(t(""), merge("AB", "", "AB"));
263 
264 		// test deletion against deletion
265 		assertEquals(t(""), merge("AB", "", ""));
266 	}
267 
268 	private String merge(String commonBase, String ours, String theirs) throws IOException {
269 		MergeResult r = new MergeAlgorithm().merge(RawTextComparator.DEFAULT,
270 				T(commonBase), T(ours), T(theirs));
271 		ByteArrayOutputStream bo=new ByteArrayOutputStream(50);
272 		fmt.formatMerge(bo, r, "B", "O", "T", UTF_8);
273 		return new String(bo.toByteArray(), UTF_8);
274 	}
275 
276 	public String t(String text) {
277 		StringBuilder r = new StringBuilder();
278 		for (int i = 0; i < text.length(); i++) {
279 			char c = text.charAt(i);
280 			switch (c) {
281 			case '<':
282 				r.append("<<<<<<< O\n");
283 				break;
284 			case '=':
285 				r.append("=======\n");
286 				break;
287 			case '>':
288 				r.append(">>>>>>> T\n");
289 				break;
290 			default:
291 				r.append(c);
292 				if (newlineAtEnd || i < text.length() - 1)
293 					r.append('\n');
294 			}
295 		}
296 		return r.toString();
297 	}
298 
299 	public RawText T(String text) {
300 		return new RawText(Constants.encode(t(text)));
301 	}
302 }