// This product is provided under the terms of EPL (Eclipse Public License)
// version 1.0.
//
// The full license text can be read from: http://www.eclipse.org/org/documents/epl-v10.php
package org.dtangler.core.acceptancetests.validation;
import static com.agical.bumblebee.junit4.Storage.store;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.dtangler.core.analysis.ChildViolation;
import org.dtangler.core.analysis.configurableanalyzer.ConfigurableDependencyAnalyzer;
import org.dtangler.core.analysisresult.AnalysisResult;
import org.dtangler.core.analysisresult.Violation;
import org.dtangler.core.configuration.Arguments;
import org.dtangler.core.configuration.ParserConstants;
import org.dtangler.core.cycleanalysis.DependencyCycle;
import org.dtangler.core.cycleanalysis.TestDependencyCycle;
import org.dtangler.core.dependencies.Dependable;
import org.dtangler.core.dependencies.Dependencies;
import org.dtangler.core.dependencies.TestDependable;
import org.dtangler.core.input.ArgumentBuilder;
import org.dtangler.core.input.CommandLineParser;
import org.dtangler.core.ruleanalysis.Rule;
import org.dtangler.core.testutil.dependenciesbuilder.DependenciesBuilder;
import org.dtangler.core.testutil.dependenciesbuilder.DependencyGraphBuilder;
import org.dtangler.core.testutil.ruleanalysis.MockRule;
import org.dtangler.core.testutil.ruleanalysis.MockRuleViolation;
import org.dtangler.javaengine.types.JavaScope;
import org.junit.Test;
public class ReturnValuesAcceptanceTest {
/*!!
#{set_header 'Return values and error messages'}
If any of the specified rules are broken in the analysed code,
dtangler will produce a rule violation message and exit with a non-zero value.
Cycles are always reported. They cause a non-zero exit value by default.
*/
private final String rulesKey = CommandLineParser
.getKeyString(ParserConstants.RULES_KEY);
private final String groupsKey = CommandLineParser
.getKeyString(ParserConstants.GROUPS_KEY);
@Test
public void cyclesFoundWhenCyclesAreAllowed() {
/*!
If cycles are allowed, cycles are reported but they do not cause an error.
For example, a possible output could be:
>>>>
#{example}
<<<<
*/
String item1 = "Dependable1";
String item2 = "Dependable2";
Dependencies cyclic = createWithCycles(item1, item2);
DependencyCycle expected = new TestDependencyCycle(Arrays.asList(item1,
item2, item1));
store("example", expected.asText());
Arguments arguments = new Arguments();
arguments.setCyclesAllowed(true);
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(cyclic);
assertTrue(analysisResult.isValid());
assertTrue(analysisResult.getAllViolations().contains(expected));
}
@Test
public void cyclesFoundWhenCyclesAreDisallowed() {
/*!
Cycles are reported, and dtangler's exits with a non-zero value.
*/
String item1 = "Part1";
String item2 = "Part2";
Dependencies cyclic = createWithCycles(item1, item2);
DependencyCycle expected = new TestDependencyCycle(Arrays.asList(item1,
item2, item1));
Arguments arguments = new Arguments();
arguments.setCyclesAllowed(false);
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(cyclic);
assertFalse(analysisResult.isValid());
assertTrue(analysisResult.getAllViolations().contains(expected));
}
private Dependencies createWithCycles(String item1, String item2) {
DependencyGraphBuilder builder = new DependencyGraphBuilder();
builder.add(item1).dependsOn(item2).dependsOn(item1);
return new DependenciesBuilder().addDependencies(builder
.getDependencies());
}
@Test
public void noCyclesFound() {
/*!
If cycles are not found, no cycle violations are reported and the exit
value is not affected.
*/
Dependencies noCycles = createSimpleDependency("basic1", "basic2");
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
new Arguments()).analyze(noCycles);
assertTrue(analysisResult.isValid());
assertTrue(analysisResult.getAllViolations().isEmpty());
}
@Test
public void noRuleViolations() {
/*!
If no rule violations are found, the exit value is not affected.
*/
Dependencies simple = createSimpleDependency("A", "B");
Arguments arguments = new Arguments();
arguments.setForbiddenDependencies(createMap("B", "A"));
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(simple);
assertTrue(analysisResult.isValid());
assertTrue(analysisResult.getAllViolations().isEmpty());
}
@Test
public void ruleViolationsBetweenSingleItems() {
/*!
When a forbidden dependency has been found, the broken rule is
reported in the output.
For example, if the rule
>>>>
#{simpleRule}
<<<<
is broken, the following violation is produced:
>>>>
#{ruleViolation1}
<<<<
*/
Dependencies simple = createSimpleDependency("A", "B");
String[] rule = { rulesKey + "A " + ParserConstants.CANNOT_DEPEND
+ " B" };
Arguments arguments = new ArgumentBuilder().build(rule);
arguments.setForbiddenDependencies(createMap("A", "B"));
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(simple);
assertFalse(analysisResult.isValid());
assertFalse(analysisResult.getAllViolations().isEmpty());
store("simpleRule", rule[0]);
store("ruleViolation1", analysisResult.getAllViolations().iterator()
.next().asText());
}
@Test
public void ruleViolationsBetweenGroups() {
/*!
Rules constructed with groups are handled in the same way as
rules with single items.
Consider the following arguments:
>>>>
#{groupDefs}
#{ruleWith2Groups}
<<<<
If there is a dependency between any members of the groups, the output will be:
>>>>
#{ruleViolation2}
<<<<
*/
Dependencies simple = createSimpleDependency("org.util",
"org.application");
String[] args = {
groupsKey + "Util " + ParserConstants.CONTAINS + " *util*"
+ ParserConstants.BIG_SEPARATOR + "App "
+ ParserConstants.CONTAINS + " *app*",
rulesKey + ParserConstants.GROUP_IDENTIFIER + "Util "
+ ParserConstants.CANNOT_DEPEND + " "
+ ParserConstants.GROUP_IDENTIFIER + "App" };
Arguments arguments = new ArgumentBuilder().build(args);
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(simple);
MockRuleViolation expectedViolation = new MockRuleViolation("org.util",
"org.application", new MockRule(Rule.Type.cannotDepend,
arguments.getGroups().get("Util"), arguments
.getGroups().get("App")));
assertFalse(analysisResult.isValid());
assertTrue(analysisResult.getAllViolations()
.contains(expectedViolation));
store("ruleViolation2", expectedViolation.asText());
store("groupDefs", args[0]);
store("ruleWith2Groups", args[1]);
}
private Dependencies createSimpleDependency(String dependant,
String dependee) {
DependencyGraphBuilder builder = new DependencyGraphBuilder();
builder.add(dependant).dependsOn(dependee);
return new DependenciesBuilder().addDependencies(builder
.getDependencies());
}
@Test
public void ruleViolationsAndExcludedItems() {
/*!
Dependencies to and from items that were *excluded* from groups
do not affect the outcome of group rules.
For example, with dependencies =A->C->B=, the following arguments
won't generate a violation because C has been excluded from the group:
>>>>
#{groupWithExcluded}
#{ruleForGroupWithExcluded}
<<<<
*/
Dependencies deps = createTransitiveDeps("A", "C", "B");
String[] args = {
groupsKey + "All " + ParserConstants.CONTAINS + " * "
+ ParserConstants.DOES_NOT_CONTAIN + " C",
rulesKey + ParserConstants.GROUP_IDENTIFIER + "All "
+ ParserConstants.CANNOT_DEPEND + " "
+ ParserConstants.GROUP_IDENTIFIER + "All" };
store("groupWithExcluded", args[0]);
store("ruleForGroupWithExcluded", args[1]);
Arguments arguments = new ArgumentBuilder().build(args);
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(deps);
assertTrue(analysisResult.isValid());
assertTrue(analysisResult.getAllViolations().isEmpty());
}
@Test
public void usingGroupsAndSingleItemsTogether() {
/*!
You can combine single items and group items freely in rule definitions:
>>>>
#{groupsForCombinations}
#{combinationRule1}
<<<<
With dependencies =eg.foo->eg.bar->eg.bay=, the resulting violation output
would look like this:
>>>>
#{combinationResult}
<<<<
*/
String group = groupsKey + "Foo " + ParserConstants.CONTAINS
+ " eg.foo*" + ParserConstants.BIG_SEPARATOR + " Bay "
+ ParserConstants.CONTAINS + " eg.bay*";
String rules = rulesKey + ParserConstants.GROUP_IDENTIFIER + "Foo "
+ ParserConstants.CANNOT_DEPEND + " eg.bar"
+ ParserConstants.BIG_SEPARATOR + " eg.bar "
+ ParserConstants.CANNOT_DEPEND + " "
+ ParserConstants.GROUP_IDENTIFIER + "Bay";
Arguments arguments = new ArgumentBuilder().build(new String[] { group,
rules });
Dependencies deps = createTransitiveDeps("eg.foo", "eg.bar", "eg.bay");
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
arguments).analyze(deps);
Set<Violation> actualViolations = analysisResult.getAllViolations();
MockRuleViolation fooExpected = new MockRuleViolation("eg.foo",
"eg.bar", new MockRule(Rule.Type.cannotDepend, arguments
.getGroups().get("Foo"), "eg.bar"));
MockRuleViolation barExpected = new MockRuleViolation("eg.bar",
"eg.bay", new MockRule(Rule.Type.cannotDepend, "eg.bar",
arguments.getGroups().get("Bay")));
assertFalse(analysisResult.isValid());
assertEquals(2, actualViolations.size());
assertTrue(actualViolations.contains(fooExpected));
assertTrue(actualViolations.contains(barExpected));
Iterator<Violation> iterator = analysisResult.getAllViolations()
.iterator();
store("combinationRule1", rules);
store("groupsForCombinations", group);
store("combinationResult", iterator.next().asText() + "\n"
+ iterator.next().asText());
}
private Dependencies createTransitiveDeps(String item1, String item2,
String item3) {
DependencyGraphBuilder builder = new DependencyGraphBuilder();
builder.add(item1).dependsOn(item2).dependsOn(item3);
return new DependenciesBuilder().addDependencies(builder
.getDependencies());
}
@Test
public void violationsOnLowerScope() {
/*!
A notification is printed if an item contains violations
on any of its more detailed scopes.
For example, a cycle between two classes, *#{class1}* and *#{class2}*,
both inside the same Java package *#{package1}*, will produce a
notification of this type on package scope:
>>>>
#{violationOnLowerScope}
<<<<
*/
Dependencies dependencies = createCycleOnLowerScope("animals", "Cat",
"Dog");
ChildViolation expected = new ChildViolation(new TestDependable(
"animals", JavaScope.packages), new TestDependencyCycle(Arrays
.asList("Cat", "Dog", "Cat")));
AnalysisResult analysisResult = new ConfigurableDependencyAnalyzer(
new Arguments()).analyze(dependencies);
Set<Violation> childViolations = analysisResult.getAllChildViolations();
assertFalse(analysisResult.isValid());
assertFalse(childViolations.isEmpty());
assertTrue(childViolations.contains(expected));
store("violationOnLowerScope", expected.toString());
store("class1", "Cat");
store("class2", "Dog");
store("package1", "animals");
}
private Dependencies createCycleOnLowerScope(String parentName,
String cycleParticipant1Name, String cycleParticipant2Name) {
Dependable parent = new TestDependable(parentName, JavaScope.packages);
Dependable part1 = new TestDependable(cycleParticipant1Name,
JavaScope.classes);
Dependable part2 = new TestDependable(cycleParticipant2Name,
JavaScope.classes);
Dependencies dependencies = new Dependencies();
dependencies.addDependencies(part1, createMap(part2));
dependencies.addDependencies(part2, createMap(part1));
dependencies.addChild(parent, part1);
dependencies.addChild(parent, part2);
return dependencies;
}
private Map<String, Set<String>> createMap(String key, String... values) {
Map<String, Set<String>> result = new HashMap();
result.put(key, new HashSet(Arrays.asList(values)));
return result;
}
private Map<Dependable, Integer> createMap(Dependable... items) {
Map<Dependable, Integer> result = new HashMap();
for (Dependable item : items) {
result.put(item, 1);
}
return result;
}
}