/* * Copyright (C) 2022, Simeon Andreev and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.pgm; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.SystemReader; import org.junit.Assume; import org.junit.Before; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; /** * Base test case for the {@code difftool} and {@code mergetool} commands. */ public abstract class ToolTestCase extends CLIRepositoryTestCase { public static class GitCliJGitWrapperParser { @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class) TextBuiltin subcommand; @Argument(index = 1, metaVar = "metaVar_arg") List arguments = new ArrayList<>(); } protected static final String TOOL_NAME = "some_tool"; private static final String TEST_BRANCH_NAME = "test_branch"; private Git git; @Override @Before public void setUp() throws Exception { super.setUp(); git = new Git(db); git.commit().setMessage("initial commit").call(); git.branchCreate().setName(TEST_BRANCH_NAME).call(); } protected String[] runAndCaptureUsingInitRaw(String... args) throws Exception { InputStream inputStream = null; // no input stream return runAndCaptureUsingInitRaw(inputStream, args); } protected String[] runAndCaptureUsingInitRaw( List expectedErrorOutput, String... args) throws Exception { InputStream inputStream = null; // no input stream return runAndCaptureUsingInitRaw(inputStream, expectedErrorOutput, args); } protected String[] runAndCaptureUsingInitRaw(InputStream inputStream, String... args) throws Exception { List expectedErrorOutput = Collections.emptyList(); return runAndCaptureUsingInitRaw(inputStream, expectedErrorOutput, args); } protected String[] runAndCaptureUsingInitRaw(InputStream inputStream, List expectedErrorOutput, String... args) throws CmdLineException, Exception, IOException { CLIGitCommand.Result result = new CLIGitCommand.Result(); GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser(); CmdLineParser clp = new CmdLineParser(bean); clp.parseArgument(args); TextBuiltin cmd = bean.subcommand; cmd.initRaw(db, null, inputStream, result.out, result.err); cmd.execute(bean.arguments.toArray(new String[bean.arguments.size()])); if (cmd.getOutputWriter() != null) { cmd.getOutputWriter().flush(); } if (cmd.getErrorWriter() != null) { cmd.getErrorWriter().flush(); } List errLines = result.errLines().stream() .filter(l -> !l.isBlank()) // we care only about error messages .collect(Collectors.toList()); assertEquals("Expected no standard error output from tool", expectedErrorOutput.toString(), errLines.toString()); return result.outLines().toArray(new String[0]); } protected String[] createMergeConflict() throws Exception { // create files on initial branch git.checkout().setName(TEST_BRANCH_NAME).call(); writeTrashFile("dir1/a", "Hello world a"); writeTrashFile("dir2/b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("files a & b added").call(); // create another branch and change files git.branchCreate().setName("branch_1").call(); git.checkout().setName("branch_1").call(); writeTrashFile("dir1/a", "Hello world a 1"); writeTrashFile("dir2/b", "Hello world b 1"); git.add().addFilepattern(".").call(); RevCommit commit1 = git.commit() .setMessage("files a & b modified commit 1").call(); // checkout initial branch git.checkout().setName(TEST_BRANCH_NAME).call(); // create another branch and change files git.branchCreate().setName("branch_2").call(); git.checkout().setName("branch_2").call(); writeTrashFile("dir1/a", "Hello world a 2"); writeTrashFile("dir2/b", "Hello world b 2"); git.add().addFilepattern(".").call(); git.commit().setMessage("files a & b modified commit 2").call(); // cherry-pick conflicting changes git.cherryPick().include(commit1).call(); String[] conflictingFilenames = { "dir1/a", "dir2/b" }; return conflictingFilenames; } protected String[] createDeletedConflict() throws Exception { // create files on initial branch git.checkout().setName(TEST_BRANCH_NAME).call(); writeTrashFile("dir1/a", "Hello world a"); writeTrashFile("dir2/b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("files a & b added").call(); // create another branch and change files git.branchCreate().setName("branch_1").call(); git.checkout().setName("branch_1").call(); writeTrashFile("dir1/a", "Hello world a 1"); writeTrashFile("dir2/b", "Hello world b 1"); git.add().addFilepattern(".").call(); RevCommit commit1 = git.commit() .setMessage("files a & b modified commit 1").call(); // checkout initial branch git.checkout().setName(TEST_BRANCH_NAME).call(); // create another branch and change files git.branchCreate().setName("branch_2").call(); git.checkout().setName("branch_2").call(); git.rm().addFilepattern("dir1/a").call(); git.rm().addFilepattern("dir2/b").call(); git.commit().setMessage("files a & b deleted commit 2").call(); // cherry-pick conflicting changes git.cherryPick().include(commit1).call(); String[] conflictingFilenames = { "dir1/a", "dir2/b" }; return conflictingFilenames; } protected String[] createUnstagedChanges() throws Exception { writeTrashFile("dir1/a", "Hello world a"); writeTrashFile("dir2/b", "Hello world b"); git.add().addFilepattern(".").call(); git.commit().setMessage("files a & b").call(); writeTrashFile("dir1/a", "New Hello world a"); writeTrashFile("dir2/b", "New Hello world b"); String[] conflictingFilenames = { "dir1/a", "dir2/b" }; return conflictingFilenames; } protected String[] createStagedChanges() throws Exception { String[] conflictingFilenames = createUnstagedChanges(); git.add().addFilepattern(".").call(); return conflictingFilenames; } protected List getRepositoryChanges(RevCommit commit) throws Exception { TreeWalk tw = new TreeWalk(db); tw.addTree(commit.getTree()); FileTreeIterator modifiedTree = new FileTreeIterator(db); tw.addTree(modifiedTree); List changes = DiffEntry.scan(tw); return changes; } protected Path getFullPath(String repositoryFilename) { Path dotGitPath = db.getDirectory().toPath(); Path repositoryRoot = dotGitPath.getParent(); Path repositoryFilePath = repositoryRoot.resolve(repositoryFilename); return repositoryFilePath; } protected static InputStream createInputStream(String[] inputLines) { return createInputStream(Arrays.asList(inputLines)); } protected static InputStream createInputStream(List inputLines) { String input = String.join(System.lineSeparator(), inputLines); InputStream inputStream = new ByteArrayInputStream(input.getBytes(UTF_8)); return inputStream; } protected static void assertArrayOfLinesEquals(String failMessage, String[] expected, String[] actual) { assertEquals(failMessage, toString(expected), toString(actual)); } protected static void assertArrayOfMatchingLines(String failMessage, Pattern[] expected, String[] actual) { assertEquals(failMessage + System.lineSeparator() + "Expected and actual lines count don't match. Expected: " + Arrays.asList(expected) + ", actual: " + Arrays.asList(actual), expected.length, actual.length); int n = expected.length; for (int i = 0; i < n; ++i) { Pattern expectedPattern = expected[i]; String actualLine = actual[i]; Matcher matcher = expectedPattern.matcher(actualLine); boolean matches = matcher.matches(); assertTrue(failMessage + System.lineSeparator() + "Line " + i + " '" + actualLine + "' doesn't match expected pattern: " + expectedPattern + System.lineSeparator() + "Expected: " + Arrays.asList(expected) + ", actual: " + Arrays.asList(actual), matches); } } protected static void assumeLinuxPlatform() { Assume.assumeTrue("This test can run only in Linux tests", SystemReader.getInstance().isLinux()); } }