View Javadoc
1   /*
2    * Copyright (C) 2022, Simeon Andreev <simeon.danailov.andreev@gmail.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  package org.eclipse.jgit.pgm;
11  
12  import static java.nio.charset.StandardCharsets.UTF_8;
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertTrue;
15  
16  import java.io.ByteArrayInputStream;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.nio.file.Path;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  import java.util.stream.Collectors;
27  
28  import org.eclipse.jgit.api.Git;
29  import org.eclipse.jgit.diff.DiffEntry;
30  import org.eclipse.jgit.lib.CLIRepositoryTestCase;
31  import org.eclipse.jgit.pgm.opt.CmdLineParser;
32  import org.eclipse.jgit.pgm.opt.SubcommandHandler;
33  import org.eclipse.jgit.revwalk.RevCommit;
34  import org.eclipse.jgit.treewalk.FileTreeIterator;
35  import org.eclipse.jgit.treewalk.TreeWalk;
36  import org.eclipse.jgit.util.SystemReader;
37  import org.junit.Assume;
38  import org.junit.Before;
39  import org.kohsuke.args4j.Argument;
40  import org.kohsuke.args4j.CmdLineException;
41  
42  /**
43   * Base test case for the {@code difftool} and {@code mergetool} commands.
44   */
45  public abstract class ToolTestCase extends CLIRepositoryTestCase {
46  
47  	public static class GitCliJGitWrapperParser {
48  		@Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
49  		TextBuiltin subcommand;
50  
51  		@Argument(index = 1, metaVar = "metaVar_arg")
52  		List<String> arguments = new ArrayList<>();
53  	}
54  
55  	protected static final String TOOL_NAME = "some_tool";
56  
57  	private static final String TEST_BRANCH_NAME = "test_branch";
58  
59  	private Git git;
60  
61  	@Override
62  	@Before
63  	public void setUp() throws Exception {
64  		super.setUp();
65  		git = new Git(db);
66  		git.commit().setMessage("initial commit").call();
67  		git.branchCreate().setName(TEST_BRANCH_NAME).call();
68  	}
69  
70  	protected String[] runAndCaptureUsingInitRaw(String... args)
71  			throws Exception {
72  		InputStream inputStream = null; // no input stream
73  		return runAndCaptureUsingInitRaw(inputStream, args);
74  	}
75  
76  	protected String[] runAndCaptureUsingInitRaw(
77  			List<String> expectedErrorOutput, String... args) throws Exception {
78  		InputStream inputStream = null; // no input stream
79  		return runAndCaptureUsingInitRaw(inputStream, expectedErrorOutput,
80  				args);
81  	}
82  
83  	protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
84  			String... args) throws Exception {
85  		List<String> expectedErrorOutput = Collections.emptyList();
86  		return runAndCaptureUsingInitRaw(inputStream, expectedErrorOutput,
87  				args);
88  	}
89  
90  	protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
91  			List<String> expectedErrorOutput, String... args)
92  			throws CmdLineException, Exception, IOException {
93  		CLIGitCommand.Result result = new CLIGitCommand.Result();
94  
95  		GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
96  		CmdLineParser clp = new CmdLineParser(bean);
97  		clp.parseArgument(args);
98  
99  		TextBuiltin cmd = bean.subcommand;
100 		cmd.initRaw(db, null, inputStream, result.out, result.err);
101 		cmd.execute(bean.arguments.toArray(new String[bean.arguments.size()]));
102 		if (cmd.getOutputWriter() != null) {
103 			cmd.getOutputWriter().flush();
104 		}
105 		if (cmd.getErrorWriter() != null) {
106 			cmd.getErrorWriter().flush();
107 		}
108 
109 		List<String> errLines = result.errLines().stream()
110 				.filter(l -> !l.isBlank()) // we care only about error messages
111 				.collect(Collectors.toList());
112 		assertEquals("Expected no standard error output from tool",
113 				expectedErrorOutput.toString(), errLines.toString());
114 
115 		return result.outLines().toArray(new String[0]);
116 	}
117 
118 	protected String[] createMergeConflict() throws Exception {
119 		// create files on initial branch
120 		git.checkout().setName(TEST_BRANCH_NAME).call();
121 		writeTrashFile("dir1/a", "Hello world a");
122 		writeTrashFile("dir2/b", "Hello world b");
123 		git.add().addFilepattern(".").call();
124 		git.commit().setMessage("files a & b added").call();
125 		// create another branch and change files
126 		git.branchCreate().setName("branch_1").call();
127 		git.checkout().setName("branch_1").call();
128 		writeTrashFile("dir1/a", "Hello world a 1");
129 		writeTrashFile("dir2/b", "Hello world b 1");
130 		git.add().addFilepattern(".").call();
131 		RevCommit commit1 = git.commit()
132 				.setMessage("files a & b modified commit 1").call();
133 		// checkout initial branch
134 		git.checkout().setName(TEST_BRANCH_NAME).call();
135 		// create another branch and change files
136 		git.branchCreate().setName("branch_2").call();
137 		git.checkout().setName("branch_2").call();
138 		writeTrashFile("dir1/a", "Hello world a 2");
139 		writeTrashFile("dir2/b", "Hello world b 2");
140 		git.add().addFilepattern(".").call();
141 		git.commit().setMessage("files a & b modified commit 2").call();
142 		// cherry-pick conflicting changes
143 		git.cherryPick().include(commit1).call();
144 		String[] conflictingFilenames = { "dir1/a", "dir2/b" };
145 		return conflictingFilenames;
146 	}
147 
148 	protected String[] createDeletedConflict() throws Exception {
149 		// create files on initial branch
150 		git.checkout().setName(TEST_BRANCH_NAME).call();
151 		writeTrashFile("dir1/a", "Hello world a");
152 		writeTrashFile("dir2/b", "Hello world b");
153 		git.add().addFilepattern(".").call();
154 		git.commit().setMessage("files a & b added").call();
155 		// create another branch and change files
156 		git.branchCreate().setName("branch_1").call();
157 		git.checkout().setName("branch_1").call();
158 		writeTrashFile("dir1/a", "Hello world a 1");
159 		writeTrashFile("dir2/b", "Hello world b 1");
160 		git.add().addFilepattern(".").call();
161 		RevCommit commit1 = git.commit()
162 				.setMessage("files a & b modified commit 1").call();
163 		// checkout initial branch
164 		git.checkout().setName(TEST_BRANCH_NAME).call();
165 		// create another branch and change files
166 		git.branchCreate().setName("branch_2").call();
167 		git.checkout().setName("branch_2").call();
168 		git.rm().addFilepattern("dir1/a").call();
169 		git.rm().addFilepattern("dir2/b").call();
170 		git.commit().setMessage("files a & b deleted commit 2").call();
171 		// cherry-pick conflicting changes
172 		git.cherryPick().include(commit1).call();
173 		String[] conflictingFilenames = { "dir1/a", "dir2/b" };
174 		return conflictingFilenames;
175 	}
176 
177 	protected String[] createUnstagedChanges() throws Exception {
178 		writeTrashFile("dir1/a", "Hello world a");
179 		writeTrashFile("dir2/b", "Hello world b");
180 		git.add().addFilepattern(".").call();
181 		git.commit().setMessage("files a & b").call();
182 		writeTrashFile("dir1/a", "New Hello world a");
183 		writeTrashFile("dir2/b", "New Hello world b");
184 		String[] conflictingFilenames = { "dir1/a", "dir2/b" };
185 		return conflictingFilenames;
186 	}
187 
188 	protected String[] createStagedChanges() throws Exception {
189 		String[] conflictingFilenames = createUnstagedChanges();
190 		git.add().addFilepattern(".").call();
191 		return conflictingFilenames;
192 	}
193 
194 	protected List<DiffEntry> getRepositoryChanges(RevCommit commit)
195 			throws Exception {
196 		TreeWalk tw = new TreeWalk(db);
197 		tw.addTree(commit.getTree());
198 		FileTreeIterator modifiedTree = new FileTreeIterator(db);
199 		tw.addTree(modifiedTree);
200 		List<DiffEntry> changes = DiffEntry.scan(tw);
201 		return changes;
202 	}
203 
204 	protected Path getFullPath(String repositoryFilename) {
205 		Path dotGitPath = db.getDirectory().toPath();
206 		Path repositoryRoot = dotGitPath.getParent();
207 		Path repositoryFilePath = repositoryRoot.resolve(repositoryFilename);
208 		return repositoryFilePath;
209 	}
210 
211 	protected static InputStream createInputStream(String[] inputLines) {
212 		return createInputStream(Arrays.asList(inputLines));
213 	}
214 
215 	protected static InputStream createInputStream(List<String> inputLines) {
216 		String input = String.join(System.lineSeparator(), inputLines);
217 		InputStream inputStream = new ByteArrayInputStream(input.getBytes(UTF_8));
218 		return inputStream;
219 	}
220 
221 	protected static void assertArrayOfLinesEquals(String failMessage,
222 			String[] expected, String[] actual) {
223 		assertEquals(failMessage, toString(expected), toString(actual));
224 	}
225 
226 	protected static void assertArrayOfMatchingLines(String failMessage,
227 			Pattern[] expected, String[] actual) {
228 		assertEquals(failMessage + System.lineSeparator()
229 				+ "Expected and actual lines count don't match. Expected: "
230 				+ Arrays.asList(expected) + ", actual: "
231 				+ Arrays.asList(actual), expected.length, actual.length);
232 		int n = expected.length;
233 		for (int i = 0; i < n; ++i) {
234 			Pattern expectedPattern = expected[i];
235 			String actualLine = actual[i];
236 			Matcher matcher = expectedPattern.matcher(actualLine);
237 			boolean matches = matcher.matches();
238 			assertTrue(failMessage + System.lineSeparator() + "Line " + i + " '"
239 					+ actualLine + "' doesn't match expected pattern: "
240 					+ expectedPattern + System.lineSeparator() + "Expected: "
241 					+ Arrays.asList(expected) + ", actual: "
242 					+ Arrays.asList(actual),
243 					matches);
244 		}
245 	}
246 
247 	protected static void assumeLinuxPlatform() {
248 		Assume.assumeTrue("This test can run only in Linux tests",
249 				SystemReader.getInstance().isLinux());
250 	}
251 }