package com.github.liblevenshtein;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import jdepend.framework.JDepend;
import jdepend.framework.JavaPackage;
import lombok.extern.slf4j.Slf4j;
import static com.github.liblevenshtein.assertion.IteratorAssertions.assertThat;
@Slf4j
public class JDependIntegTest {
private static final String BASE_PACKAGE = "com.github.liblevenshtein";
@SuppressWarnings("unchecked")
private static final Set<String> EMPTY_DEPS = (Set<String>) Collections.EMPTY_SET;
/** Valid, cyclic dependencies. */
private static final Map<String, Map<String, Set<String>>> VALID_DEPS = buildValidDeps();
@SuppressWarnings("checkstyle:multiplestringliterals")
private static Map<String, Map<String, Set<String>>> buildValidDeps() {
final Map<String, Set<String>> mainDeps = new ImmutableMap.Builder<String, Set<String>>()
.put("com.github.liblevenshtein.transducer", new ImmutableSet.Builder<String>()
.add("com.github.liblevenshtein.transducer.factory")
.add("com.github.liblevenshtein.transducer")
.build())
.put("com.github.liblevenshtein.transducer.factory", new ImmutableSet.Builder<String>()
.add("com.github.liblevenshtein.transducer")
.add("com.github.liblevenshtein.transducer.factory")
.build())
.build();
// In the tests, pretty much every package is cyclic to every other through
// their assertions, according to JDepend. In the future, it may be good to
// refactor the tests such that their packages are not so coupled.
final Set<String> assertionDeps = new ImmutableSet.Builder<String>()
.add("com.github.liblevenshtein.assertion")
.add("com.github.liblevenshtein.collection.dictionary")
.add("com.github.liblevenshtein.collection.dictionary.factory")
.add("com.github.liblevenshtein.distance")
.add("com.github.liblevenshtein.distance.factory")
.add("com.github.liblevenshtein.serialization")
.add("com.github.liblevenshtein.transducer")
.add("com.github.liblevenshtein.transducer.factory")
.build();
final Map<String, Set<String>> testDeps = assertionDeps.stream()
.reduce(new ImmutableMap.Builder<String, Set<String>>(),
(builder, dep) -> builder.put(dep, assertionDeps),
(lhs, rhs) -> lhs).build();
final Map<String, Set<String>> regrDeps = mainDeps;
final Map<String, Set<String>> integDeps = mainDeps;
return new ImmutableMap.Builder<String, Map<String, Set<String>>>()
.put("main", mainDeps)
.put("test", testDeps)
.put("regr", regrDeps)
.put("integ", integDeps)
.build();
}
@DataProvider(name = "jdependProvider")
@SuppressWarnings("checkstyle:multiplestringliterals")
public Object[][] jdependProvider() throws IOException {
final String[] configs = {"main", "test", "regr", "integ", "task"};
final Object[][] jdepends = new Object[configs.length][2];
for (int i = 0; i < configs.length; i += 1) {
final String config = configs[i];
try {
final JDepend jdepend = new JDepend();
addConfigDir(jdepend, config);
if (!"main".equals(config) && !"task".equals(config)) {
addConfigDir(jdepend, "main");
}
jdepend.analyze();
jdepends[i][0] = jdepend;
jdepends[i][1] = config;
}
catch (final IOException exception) {
log.error("Failed to initialize config [{}]", config, exception);
throw exception;
}
}
return jdepends;
}
private void addConfigDir(final JDepend jdepend, final String config) throws IOException {
final String projectDir = System.getProperty("user.dir");
final String classesDir =
String.format("%s/build/classes/%s", projectDir, config);
jdepend.addDirectory(classesDir);
}
private Set<String> depsFor(final JavaPackage pkg, final String config) {
if (!VALID_DEPS.containsKey(config)) {
return EMPTY_DEPS;
}
final Map<String, Set<String>> validDeps = VALID_DEPS.get(config);
if (!validDeps.containsKey(pkg.getName())) {
return EMPTY_DEPS;
}
return validDeps.get(pkg.getName());
}
@SuppressWarnings("unchecked")
@Test(dataProvider = "jdependProvider")
public void testNoUnexpectedCycles(final JDepend jdepend, final String config) {
final List<JavaPackage> cycles = new ArrayList<>();
for (final JavaPackage pkg : (Collection<JavaPackage>) jdepend.getPackages()) {
if (pkg.getName().startsWith(BASE_PACKAGE)
&& pkg.collectAllCycles(cycles)
&& isDirectCycle(cycles)) {
final Iterator<JavaPackage> iter = cycles.iterator();
assertThat(iter).hasNext();
assertThat(iter.next().getName()).isEqualTo(pkg.getName());
Set<String> expectedDeps = depsFor(pkg, config);
JavaPackage prev = pkg;
while (iter.hasNext()) {
try {
final JavaPackage dep = iter.next();
assertThat(expectedDeps).contains(dep.getName());
expectedDeps = depsFor(dep, config);
prev = dep;
}
catch (final AssertionError error) {
final String message =
String.format("Failed to analyze dependencies for package [%s], config [%s]",
prev.getName(), config);
throw new AssertionError(message, error);
}
}
cycles.clear();
}
}
}
private boolean isDirectCycle(final Collection<JavaPackage> cycles) {
final Iterator<JavaPackage> iter = cycles.iterator();
if (iter.hasNext()) {
final JavaPackage src = iter.next();
while (iter.hasNext()) {
final JavaPackage tgt = iter.next();
if (src.getName().equals(tgt.getName())) {
return true;
}
}
}
return false;
}
}