| /* |
| * Copyright (C) 2021-2022, Simeon Andreev <simeon.danailov.andreev@gmail.com> 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 org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION; |
| import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION; |
| import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD; |
| import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT; |
| import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL; |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.io.InputStream; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.jgit.internal.diffmergetool.DiffTools; |
| import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool; |
| import org.eclipse.jgit.lib.StoredConfig; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * Testing the {@code difftool} command. |
| */ |
| public class DiffToolTest extends ToolTestCase { |
| |
| private static final String DIFF_TOOL = CONFIG_DIFFTOOL_SECTION; |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| configureEchoTool(TOOL_NAME); |
| } |
| |
| @Test(expected = Die.class) |
| public void testUndefinedTool() throws Exception { |
| String toolName = "undefined"; |
| String[] conflictingFilenames = createUnstagedChanges(); |
| |
| List<String> expectedErrors = new ArrayList<>(); |
| for (String changedFilename : conflictingFilenames) { |
| expectedErrors.add("External diff tool is not defined: " + toolName); |
| expectedErrors.add("compare of " + changedFilename + " failed"); |
| } |
| |
| runAndCaptureUsingInitRaw(expectedErrors, DIFF_TOOL, "--no-prompt", |
| "--tool", toolName); |
| fail("Expected exception to be thrown due to undefined external tool"); |
| } |
| |
| @Test(expected = Die.class) |
| public void testUserToolWithCommandNotFoundError() throws Exception { |
| String toolName = "customTool"; |
| |
| int errorReturnCode = 127; // command not found |
| String command = "exit " + errorReturnCode; |
| |
| StoredConfig config = db.getConfig(); |
| config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD, |
| command); |
| |
| createMergeConflict(); |
| runAndCaptureUsingInitRaw(DIFF_TOOL, "--no-prompt", "--tool", toolName); |
| |
| fail("Expected exception to be thrown due to external tool exiting with error code: " |
| + errorReturnCode); |
| } |
| |
| @Test(expected = Die.class) |
| public void testEmptyToolName() throws Exception { |
| assumeLinuxPlatform(); |
| |
| String emptyToolName = ""; |
| |
| StoredConfig config = db.getConfig(); |
| // the default diff tool is configured without a subsection |
| String subsection = null; |
| config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL, |
| emptyToolName); |
| |
| createUnstagedChanges(); |
| |
| String araxisErrorLine = "compare: unrecognized option `-wait' @ error/compare.c/CompareImageCommand/1123."; |
| String[] expectedErrorOutput = { araxisErrorLine, araxisErrorLine, }; |
| runAndCaptureUsingInitRaw(Arrays.asList(expectedErrorOutput), DIFF_TOOL, |
| "--no-prompt"); |
| fail("Expected exception to be thrown due to external tool exiting with an error"); |
| } |
| |
| @Test |
| public void testToolWithPrompt() throws Exception { |
| String[] inputLines = { |
| "y", // accept launching diff tool |
| "y", // accept launching diff tool |
| }; |
| |
| String[] conflictingFilenames = createUnstagedChanges(); |
| String[] expectedOutput = getExpectedCompareOutput(conflictingFilenames); |
| |
| String option = "--tool"; |
| |
| InputStream inputStream = createInputStream(inputLines); |
| assertArrayOfLinesEquals("Incorrect output for option: " + option, |
| expectedOutput, runAndCaptureUsingInitRaw(inputStream, |
| DIFF_TOOL, "--prompt", option, TOOL_NAME)); |
| } |
| |
| @Test |
| public void testToolAbortLaunch() throws Exception { |
| String[] inputLines = { |
| "y", // accept launching diff tool |
| "n", // don't launch diff tool |
| }; |
| |
| String[] conflictingFilenames = createUnstagedChanges(); |
| int abortIndex = 1; |
| String[] expectedOutput = getExpectedAbortOutput(conflictingFilenames, abortIndex); |
| |
| String option = "--tool"; |
| |
| InputStream inputStream = createInputStream(inputLines); |
| assertArrayOfLinesEquals("Incorrect output for option: " + option, |
| expectedOutput, |
| runAndCaptureUsingInitRaw(inputStream, DIFF_TOOL, "--prompt", option, |
| TOOL_NAME)); |
| } |
| |
| @Test(expected = Die.class) |
| public void testNotDefinedTool() throws Exception { |
| createUnstagedChanges(); |
| |
| runAndCaptureUsingInitRaw(DIFF_TOOL, "--tool", "undefined"); |
| fail("Expected exception when trying to run undefined tool"); |
| } |
| |
| @Test |
| public void testTool() throws Exception { |
| String[] conflictFilenames = createUnstagedChanges(); |
| String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictFilenames); |
| |
| String[] options = { |
| "--tool", |
| "-t", |
| }; |
| |
| for (String option : options) { |
| assertArrayOfLinesEquals("Incorrect output for option: " + option, |
| expectedOutput, |
| runAndCaptureUsingInitRaw(DIFF_TOOL, option, |
| TOOL_NAME)); |
| } |
| } |
| |
| @Test |
| public void testToolTrustExitCode() throws Exception { |
| String[] conflictingFilenames = createUnstagedChanges(); |
| String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictingFilenames); |
| |
| String[] options = { "--tool", "-t", }; |
| |
| for (String option : options) { |
| assertArrayOfLinesEquals("Incorrect output for option: " + option, |
| expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL, |
| "--trust-exit-code", option, TOOL_NAME)); |
| } |
| } |
| |
| @Test |
| public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception { |
| String[] conflictingFilenames = createUnstagedChanges(); |
| String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictingFilenames); |
| |
| String[] options = { "--tool", "-t", }; |
| |
| for (String option : options) { |
| assertArrayOfLinesEquals("Incorrect output for option: " + option, |
| expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL, |
| "--no-gui", "--no-prompt", "--no-trust-exit-code", |
| option, TOOL_NAME)); |
| } |
| } |
| |
| @Test |
| public void testToolCached() throws Exception { |
| String[] conflictingFilenames = createStagedChanges(); |
| Pattern[] expectedOutput = getExpectedCachedToolOutputNoPrompt(conflictingFilenames); |
| |
| String[] options = { "--cached", "--staged", }; |
| |
| for (String option : options) { |
| assertArrayOfMatchingLines("Incorrect output for option: " + option, |
| expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL, |
| option, "--tool", TOOL_NAME)); |
| } |
| } |
| |
| @Test |
| public void testToolHelp() throws Exception { |
| List<String> expectedOutput = new ArrayList<>(); |
| |
| DiffTools diffTools = new DiffTools(db); |
| Map<String, ExternalDiffTool> predefinedTools = diffTools |
| .getPredefinedTools(true); |
| List<ExternalDiffTool> availableTools = new ArrayList<>(); |
| List<ExternalDiffTool> notAvailableTools = new ArrayList<>(); |
| for (ExternalDiffTool tool : predefinedTools.values()) { |
| if (tool.isAvailable()) { |
| availableTools.add(tool); |
| } else { |
| notAvailableTools.add(tool); |
| } |
| } |
| |
| expectedOutput.add( |
| "'git difftool --tool=<tool>' may be set to one of the following:"); |
| for (ExternalDiffTool tool : availableTools) { |
| String toolName = tool.getName(); |
| expectedOutput.add(toolName); |
| } |
| String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " " |
| + getEchoCommand(); |
| expectedOutput.add("user-defined:"); |
| expectedOutput.add(customToolHelpLine); |
| expectedOutput.add( |
| "The following tools are valid, but not currently available:"); |
| for (ExternalDiffTool tool : notAvailableTools) { |
| String toolName = tool.getName(); |
| expectedOutput.add(toolName); |
| } |
| String[] userDefinedToolsHelp = { |
| "Some of the tools listed above only work in a windowed", |
| "environment. If run in a terminal-only session, they will fail.", |
| }; |
| expectedOutput.addAll(Arrays.asList(userDefinedToolsHelp)); |
| |
| String option = "--tool-help"; |
| assertArrayOfLinesEquals("Incorrect output for option: " + option, |
| expectedOutput.toArray(new String[0]), |
| runAndCaptureUsingInitRaw(DIFF_TOOL, option)); |
| } |
| |
| private void configureEchoTool(String toolName) { |
| StoredConfig config = db.getConfig(); |
| // the default diff tool is configured without a subsection |
| String subsection = null; |
| config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL, |
| toolName); |
| |
| String command = getEchoCommand(); |
| |
| config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD, |
| command); |
| /* |
| * prevent prompts as we are running in tests and there is no user to |
| * interact with on the command line |
| */ |
| config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_PROMPT, |
| String.valueOf(false)); |
| } |
| |
| private String[] getExpectedToolOutputNoPrompt(String[] conflictingFilenames) { |
| String[] expectedToolOutput = new String[conflictingFilenames.length]; |
| for (int i = 0; i < conflictingFilenames.length; ++i) { |
| String newPath = conflictingFilenames[i]; |
| Path fullPath = getFullPath(newPath); |
| expectedToolOutput[i] = fullPath.toString(); |
| } |
| return expectedToolOutput; |
| } |
| |
| private Pattern[] getExpectedCachedToolOutputNoPrompt(String[] conflictingFilenames) { |
| String tmpDir = System.getProperty("java.io.tmpdir"); |
| if (tmpDir.endsWith(File.separator)) { |
| tmpDir = tmpDir.substring(0, tmpDir.length() - 1); |
| } |
| Pattern emptyPattern = Pattern.compile(""); |
| List<Pattern> expectedToolOutput = new ArrayList<>(); |
| for (int i = 0; i < conflictingFilenames.length; ++i) { |
| String changedFilename = conflictingFilenames[i]; |
| Path fullPath = getFullPath(changedFilename); |
| String filename = fullPath.getFileName().toString(); |
| String regexp = tmpDir + File.separatorChar + filename |
| + "_REMOTE_.*"; |
| Pattern pattern = Pattern.compile(regexp); |
| expectedToolOutput.add(pattern); |
| expectedToolOutput.add(emptyPattern); |
| } |
| expectedToolOutput.add(emptyPattern); |
| return expectedToolOutput.toArray(new Pattern[0]); |
| } |
| |
| private String[] getExpectedCompareOutput(String[] conflictingFilenames) { |
| List<String> expected = new ArrayList<>(); |
| int n = conflictingFilenames.length; |
| for (int i = 0; i < n; ++i) { |
| String changedFilename = conflictingFilenames[i]; |
| expected.add( |
| "Viewing (" + (i + 1) + "/" + n + "): '" + changedFilename |
| + "'"); |
| expected.add("Launch '" + TOOL_NAME + "' [Y/n]?"); |
| Path fullPath = getFullPath(changedFilename); |
| expected.add(fullPath.toString()); |
| } |
| return expected.toArray(new String[0]); |
| } |
| |
| private String[] getExpectedAbortOutput(String[] conflictingFilenames, |
| int abortIndex) { |
| List<String> expected = new ArrayList<>(); |
| int n = conflictingFilenames.length; |
| for (int i = 0; i < n; ++i) { |
| String changedFilename = conflictingFilenames[i]; |
| expected.add( |
| "Viewing (" + (i + 1) + "/" + n + "): '" + changedFilename |
| + "'"); |
| expected.add("Launch '" + TOOL_NAME + "' [Y/n]?"); |
| if (i == abortIndex) { |
| break; |
| } |
| Path fullPath = getFullPath(changedFilename); |
| expected.add(fullPath.toString()); |
| } |
| return expected.toArray(new String[0]); |
| } |
| |
| private static String getEchoCommand() { |
| /* |
| * use 'REMOTE' placeholder, as it will be replaced by a file path |
| * within the repository. |
| */ |
| return "(echo \"$REMOTE\")"; |
| } |
| } |