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