Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java275
1 files changed, 275 insertions, 0 deletions
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
new file mode 100644
index 0000000000..db5f1b2eb6
--- /dev/null
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2015, Sebastien Arod <sebastien.arod@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.ignore;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import org.eclipse.jgit.api.Git;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * This test generates random ignore patterns and random path and compares the
+ * output of Cgit check-ignore to the output of {@link FastIgnoreRule}.
+ */
+public class CGitVsJGitRandomIgnorePatternTest {
+
+ private static class PseudoRandomPatternGenerator {
+
+ private static final int DEFAULT_MAX_FRAGMENTS_PER_PATTERN = 15;
+
+ /**
+ * Generates 75% Special fragments and 25% "standard" characters
+ */
+ private static final double DEFAULT_SPECIAL_FRAGMENTS_FREQUENCY = 0.75d;
+
+ private static final List<String> SPECIAL_FRAGMENTS = Arrays.asList(
+ "\\", "!", "#", "[", "]", "|", "/", "*", "?", "{", "}", "(",
+ ")", "\\d", "(", "**", "[a\\]]", "\\ ", "+", "-", "^", "$", ".",
+ ":", "=", "[[:", ":]]"
+
+ );
+
+ private static final String STANDARD_CHARACTERS = new String(
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+
+ private final Random random = new Random();
+
+ private final int maxFragmentsPerPattern;
+
+ private final double specialFragmentsFrequency;
+
+ public PseudoRandomPatternGenerator() {
+ this(DEFAULT_MAX_FRAGMENTS_PER_PATTERN,
+ DEFAULT_SPECIAL_FRAGMENTS_FREQUENCY);
+ }
+
+ public PseudoRandomPatternGenerator(int maxFragmentsPerPattern,
+ double specialFragmentsFrequency) {
+ this.maxFragmentsPerPattern = maxFragmentsPerPattern;
+ this.specialFragmentsFrequency = specialFragmentsFrequency;
+ }
+
+ public String nextRandomString() {
+ StringBuilder builder = new StringBuilder();
+ int length = randomFragmentCount();
+ for (int i = 0; i < length; i++) {
+ if (useSpecialFragment()) {
+ builder.append(randomSpecialFragment());
+ } else {
+ builder.append(randomStandardCharacters());
+ }
+
+ }
+ return builder.toString();
+ }
+
+ private int randomFragmentCount() {
+ // We want at least one fragment
+ return 1 + random.nextInt(maxFragmentsPerPattern - 1);
+ }
+
+ private char randomStandardCharacters() {
+ return STANDARD_CHARACTERS
+ .charAt(random.nextInt(STANDARD_CHARACTERS.length()));
+ }
+
+ private boolean useSpecialFragment() {
+ return random.nextDouble() < specialFragmentsFrequency;
+ }
+
+ private String randomSpecialFragment() {
+ return SPECIAL_FRAGMENTS
+ .get(random.nextInt(SPECIAL_FRAGMENTS.size()));
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class CgitFatalException extends Exception {
+
+ public CgitFatalException(int cgitExitCode, String pattern, String path,
+ String cgitStdError) {
+ super("CgitFatalException (" + cgitExitCode + ") for pattern:["
+ + pattern + "] and path:[" + path + "]\n" + cgitStdError);
+ }
+
+ }
+
+ public static class CGitIgnoreRule {
+
+ private File gitDir;
+
+ private String pattern;
+
+ public CGitIgnoreRule(File gitDir, String pattern)
+ throws UnsupportedEncodingException, IOException {
+ this.gitDir = gitDir;
+ this.pattern = pattern;
+ Files.write(new File(gitDir, ".gitignore").toPath(),
+ (pattern + "\n").getBytes("UTF-8"),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING,
+ StandardOpenOption.WRITE);
+ }
+
+ public boolean isMatch(String path)
+ throws IOException, InterruptedException, CgitFatalException {
+ Process proc = startCgitCheckIgnore(path);
+
+ String cgitStdOutput = readProcessStream(proc.getInputStream());
+ String cgitStdError = readProcessStream(proc.getErrorStream());
+
+ int cgitExitCode = proc.waitFor();
+
+ if (cgitExitCode == 128) {
+ throw new CgitFatalException(cgitExitCode, pattern, path,
+ cgitStdError);
+ }
+ return !cgitStdOutput.startsWith("::");
+ }
+
+ private Process startCgitCheckIgnore(String path) throws IOException {
+ // Use --stdin instead of using argument otherwise paths starting
+ // with "-" were interpreted as
+ // options by git check-ignore
+ String[] command = new String[] { "git", "check-ignore",
+ "--no-index", "-v", "-n", "--stdin" };
+ Process proc = Runtime.getRuntime().exec(command, new String[0],
+ gitDir);
+ OutputStream out = proc.getOutputStream();
+ out.write((path + "\n").getBytes("UTF-8"));
+ out.flush();
+ out.close();
+ return proc;
+ }
+
+ private String readProcessStream(InputStream processStream)
+ throws IOException {
+ try (BufferedReader stdOut = new BufferedReader(
+ new InputStreamReader(processStream))) {
+
+ StringBuilder out = new StringBuilder();
+ String s;
+ while ((s = stdOut.readLine()) != null) {
+ out.append(s);
+ }
+ return out.toString();
+ }
+ }
+ }
+
+ private static final int NB_PATTERN = 1000;
+
+ private static final int PATH_PER_PATTERN = 1000;
+
+ @Test
+ public void testRandomPatterns() throws Exception {
+ // Initialize new git repo
+ File gitDir = Files.createTempDirectory("jgit").toFile();
+ Git.init().setDirectory(gitDir).call();
+ PseudoRandomPatternGenerator generator = new PseudoRandomPatternGenerator();
+
+ // Generate random patterns and paths
+ for (int i = 0; i < NB_PATTERN; i++) {
+ String pattern = generator.nextRandomString();
+
+ FastIgnoreRule jgitIgnoreRule = new FastIgnoreRule(pattern);
+ CGitIgnoreRule cgitIgnoreRule = new CGitIgnoreRule(gitDir, pattern);
+
+ // Test path with pattern as path
+ assertCgitAndJgitMatch(pattern, jgitIgnoreRule, cgitIgnoreRule,
+ pattern);
+
+ for (int p = 0; p < PATH_PER_PATTERN; p++) {
+ String path = generator.nextRandomString();
+ assertCgitAndJgitMatch(pattern, jgitIgnoreRule, cgitIgnoreRule,
+ path);
+ }
+ }
+ }
+
+ @SuppressWarnings({ "boxing" })
+ private void assertCgitAndJgitMatch(String pattern,
+ FastIgnoreRule jgitIgnoreRule, CGitIgnoreRule cgitIgnoreRule,
+ String pathToTest) throws IOException, InterruptedException {
+
+ try {
+ boolean cgitMatch = cgitIgnoreRule.isMatch(pathToTest);
+ boolean jgitMatch = jgitIgnoreRule.isMatch(pathToTest,
+ pathToTest.endsWith("/"));
+ if (cgitMatch != jgitMatch) {
+ System.err.println(
+ buildAssertionToAdd(pattern, pathToTest, cgitMatch));
+ }
+ Assert.assertEquals("jgit:" + jgitMatch + " <> cgit:" + cgitMatch
+ + " for pattern:[" + pattern + "] and path:[" + pathToTest
+ + "]", cgitMatch, jgitMatch);
+ } catch (CgitFatalException e) {
+ // Lots of generated patterns or path are rejected by Cgit with a
+ // fatal error. We want to ignore them.
+ }
+ }
+
+ private String buildAssertionToAdd(String pattern, String pathToTest,
+ boolean cgitMatch) {
+ return "assertMatch(" + toJavaString(pattern) + ", "
+ + toJavaString(pathToTest) + ", " + cgitMatch
+ + " /*cgit result*/);";
+ }
+
+ private String toJavaString(String pattern2) {
+ return "\"" + pattern2.replace("\\", "\\\\").replace("\"", "\\\"")
+ + "\"";
+ }
+}

Back to the top