/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.uberfire.java.nio.fs.jgit;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.jboss.byteman.contrib.bmunit.BMScript;
import org.jboss.byteman.contrib.bmunit.BMUnitConfig;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.java.nio.base.options.SquashOption;
import org.uberfire.java.nio.base.version.VersionRecord;
import org.uberfire.java.nio.file.Path;
import static org.junit.Assert.*;
import static org.uberfire.java.nio.fs.jgit.util.JGitUtil.resolveObjectId;
@RunWith(org.jboss.byteman.contrib.bmunit.BMUnitRunner.class)
@BMUnitConfig(loadDirectory = "target/test-classes", debug = true) // set "debug=true to see debug output
public class JGitFileSystemProviderBytemanTest extends AbstractTestInfra {
private static Logger logger = LoggerFactory.getLogger(JGitFileSystemProviderBytemanTest.class);
private static void printLog(final Git git) {
try {
for (final RevCommit revCommit : git.log().call()) {
logger.info("[LOG]: " + revCommit.getName() + " --- " + revCommit.getFullMessage());
}
} catch (GitAPIException e) {
e.printStackTrace();
}
}
protected static void waitFor(CyclicBarrier barrier) {
String threadName = Thread.currentThread().getName();
try {
logger.info(threadName + " request for await");
barrier.await();
logger.info(threadName + " await finished");
} catch (InterruptedException e) {
fail("Thread '" + threadName + "' was interrupted while waiting for the other threads!");
} catch (BrokenBarrierException e) {
fail("Thread '" + threadName + "' barrier was broken while waiting for the other threads!");
}
}
@Ignore("This test produces a strange behaviour that locks the other test. Is ignored until a solution is found.")
@Test()
@BMScript(value = "byteman/squash_lock.btm")
public void testConcurrentLocking() throws IOException, GitAPIException {
final URI newRepo = URI.create("git://byteman-lock-squash-repo");
final JGitFileSystem fs = (JGitFileSystem) provider.newFileSystem(newRepo,
EMPTY_ENV);
final CyclicBarrier threadsFinishedBarrier = new CyclicBarrier(3);
final Thread t = new Thread(() -> {
final Path master = provider.getPath(URI.create("git://master@byteman-lock-squash-repo"));
final RevCommit commit = commitThreeTimesAndGetReference(fs,
"byteman-lock-squash-repo",
"master",
"t1");
Thread t1 = new Thread(() -> {
logger.info("<<<<<<<<<<<<< " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing a!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-1: Squashing");
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
printLog(fs.gitRepo());
waitFor(threadsFinishedBarrier);
});
Thread t2 = new Thread(() -> {
logger.info("<<<<<<<<<<<<< " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing a!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-2: Squashing");
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
printLog(fs.gitRepo());
waitFor(threadsFinishedBarrier);
});
t1.setName("LOCK-COMMITTER-1");
t2.setName("LOCK-COMMITTER-2");
t2.start();
t1.start();
waitFor(threadsFinishedBarrier);
});
try {
t.start();
t.join(10000);
t.interrupt();
} catch (InterruptedException e) {
}
assertEquals(3,
this.getCommitsFromBranch(fs.gitRepo(),
"master").size());
}
@Test
@BMScript(value = "byteman/squash.btm")
public void testConcurrentSquashWithThreeCommit() throws IOException, GitAPIException {
final URI newRepo = URI.create("git://three-squash-repo");
final JGitFileSystem fs = (JGitFileSystem) provider.newFileSystem(newRepo,
EMPTY_ENV);
final CyclicBarrier threadsFinishedBarrier = new CyclicBarrier(3);
final Path master = provider.getPath(URI.create("git://three-squash-repo"));
final RevCommit commit = commitThreeTimesAndGetReference(fs,
"three-squash-repo",
"master",
"t1");
Thread t1 = new Thread(() -> {
logger.info("<<<<<<<<<<<<< COMMIT TO SQUASH " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing a!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-1: Squashing");
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
printLog(fs.gitRepo());
waitFor(threadsFinishedBarrier);
});
Thread t2 = new Thread(() -> {
logger.info("<<<<<<<<<<<<< COMMIT TO SQUASH " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing b!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-2: Squashing");
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
printLog(fs.gitRepo());
waitFor(threadsFinishedBarrier);
});
t1.setName("COMMITTER-1");
t2.setName("COMMITTER-2");
t2.start();
t1.start();
waitFor(threadsFinishedBarrier);
assertEquals(2,
getCommitsFromBranch(fs.gitRepo(),
"master").size());
}
@Test
@BMScript(value = "byteman/squash.btm")
public void testConcurrentSquashWithSixCommit() throws IOException, GitAPIException {
final URI newRepo = URI.create("git://byteman-six-squash-repo");
final JGitFileSystem fs = (JGitFileSystem) provider.newFileSystem(newRepo,
EMPTY_ENV);
final CyclicBarrier threadsFinishedBarrier = new CyclicBarrier(3);
final Path master = provider.getPath(URI.create("git://master@byteman-six-squash-repo"));
final RevCommit commit = commitSixTimesAndGetReference(fs,
"byteman-six-squash-repo",
"master",
"t1");
Thread t1 = new Thread(() -> {
logger.info("<<<<<<<<<<<<< COMMIT TO SQUASH " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing a!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-1: Squashing");
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
printLog(fs.gitRepo());
waitFor(threadsFinishedBarrier);
});
Thread t2 = new Thread(() -> {
logger.info("<<<<<<<<<<<<< COMMIT TO SQUASH " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing b!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-2: Squashing");
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
printLog(fs.gitRepo());
waitFor(threadsFinishedBarrier);
});
t1.setName("COMMITTER-1");
t2.setName("COMMITTER-2");
t2.start();
t1.start();
waitFor(threadsFinishedBarrier);
assertEquals(2,
getCommitsFromBranch(fs.gitRepo(),
"master").size());
}
@Test
@BMScript(value = "byteman/squash_exception.btm")
public void testForceExceptionWhenTryingToSquash() throws IOException, GitAPIException {
final URI newRepo = URI.create("git://byteman-exception-squash-repo");
final JGitFileSystem fs = (JGitFileSystem) provider.newFileSystem(newRepo,
EMPTY_ENV);
final Path master = provider.getPath(URI.create("git://master@byteman-exception-squash-repo"));
final RevCommit commit = commitThreeTimesAndGetReference(fs,
"byteman-exception-squash-repo",
"master",
"t1");
logger.info("<<<<<<<<<<<<< COMMIT TO SQUASH " + commit.getName() + " --- " + commit.getFullMessage());
printLog(fs.gitRepo());
VersionRecord record = makeVersionRecord("aparedes",
"aparedes@redhat.com",
"squashing a!",
new Date(),
commit.getName());
SquashOption squashOption = new SquashOption(record);
logger.info("COMMITTER-1: Squashing");
try {
provider.setAttribute(master,
SquashOption.SQUASH_ATTR,
squashOption);
} catch (Exception e) {
fs.lock();
fs.unlock();
}
assertEquals(3,
getCommitsFromBranch(fs.gitRepo(),
"master").size());
}
@Test
@BMScript(value = "byteman/commit_exception.btm")
public void testFileSystemLockOnException() throws IOException, GitAPIException {
final URI newRepo = URI.create("git://byteman-exception-commit-repo");
final JGitFileSystem fs = (JGitFileSystem) provider.newFileSystem(newRepo,
EMPTY_ENV);
final Path path = provider.getPath(URI.create("git://master@byteman-exception-commit-repo/myfile.txt"));
try {
writeFile(fs,
path,
"master");
} catch (RuntimeException e) {
// intentional Exception. Ignore
}
// fs must be unlocked
Object lock = null;
try {
Field field = JGitFileSystem.class.getDeclaredField("lock");
field.setAccessible(true);
lock = field.get(fs);
} catch (Exception e) {
fail(e.getMessage());
}
Object isLocked = null;
try {
Field field = lock.getClass().getDeclaredField("isLocked");
field.setAccessible(true);
isLocked = field.get(lock);
} catch (Exception e) {
fail(e.getMessage());
}
assertFalse(((AtomicBoolean) isLocked).get());
}
private VersionRecord makeVersionRecord(final String author,
final String email,
final String comment,
final Date date,
final String commit) {
return new VersionRecord() {
@Override
public String id() {
return commit;
}
@Override
public String author() {
return author;
}
@Override
public String email() {
return email;
}
@Override
public String comment() {
return comment;
}
@Override
public Date date() {
return date;
}
@Override
public String uri() {
return null;
}
};
}
private RevCommit commitThreeTimesAndGetReference(JGitFileSystem fs,
String repo,
String branch,
String thread) {
try {
final Path path = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile1.txt"));
final Path path2 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile2.txt"));
final Path path3 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile3.txt"));
final RevCommit commit = writeFile(fs,
path,
branch);
writeFile(fs,
path2,
branch);
writeFile(fs,
path3,
branch);
return commit;
} catch (IOException | GitAPIException e) {
throw new RuntimeException(e);
}
}
private RevCommit commitSixTimesAndGetReference(JGitFileSystem fs,
String repo,
String branch,
String thread) {
try {
final Path path = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile1.txt"));
final Path path2 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile2.txt"));
final Path path3 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile3.txt"));
final Path path4 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile4.txt"));
final Path path5 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile5.txt"));
final Path path6 = provider.getPath(URI.create("git://" + branch + "@" + repo + "/" + thread + "-myfile6.txt"));
final RevCommit commit = writeFile(fs,
path,
branch);
writeFile(fs,
path2,
branch);
writeFile(fs,
path3,
branch);
writeFile(fs,
path4,
branch);
writeFile(fs,
path5,
branch);
writeFile(fs,
path6,
branch);
return commit;
} catch (IOException | GitAPIException e) {
throw new RuntimeException(e);
}
}
private RevCommit writeFile(final JGitFileSystem fs,
final Path path,
String branch) throws IOException, GitAPIException {
final OutputStream stream = provider.newOutputStream(path);
logger.info("Writing file: " + path.getFileName().toString());
stream.write("my cool content".getBytes());
stream.close();
return this.getCommitsFromBranch(fs.gitRepo(),
branch).get(0);
}
private List<RevCommit> getCommitsFromBranch(final Git origin,
String branch) throws GitAPIException, MissingObjectException, IncorrectObjectTypeException {
List<RevCommit> commits = new ArrayList<>();
final ObjectId id = resolveObjectId(origin,
branch);
for (RevCommit commit : origin.log().add(id).call()) {
// logger.info( ">>> " + branch + " Commits: " + commit.getFullMessage() + " - " + commit.toString() );
commits.add(commit);
}
return commits;
}
}