Skip to content

Commit

Permalink
add negated version of name{startingWith/containing/endingWith}
Browse files Browse the repository at this point in the history
I have also added some tests for the failure details, since broken messages there would be undetected at the moment. The reason that some of these tests for members are missing for other methods is simply that we reuse those for classes, so they were not added in the past since those tests would have already caught it. If we have syntax methods explicitly for members (that are not also used for classes), then we should add a detailed test checking that the failure details indeed match.
when I wrote those tests I also noticed that messages like `Constructor<very.long.fqn.<init>()> does not start with 'foo'` does also read slightly weird (because there is no focus on the "name" the "starts with" refers to) so I added "name" to the failure details, i.e. `Constructor<..> name does not start with...`.

Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
  • Loading branch information
codecholeric committed May 24, 2020
1 parent b493518 commit 74dcc1b
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -563,20 +563,41 @@ ArchCondition<HAS_FULL_NAME> haveFullNameMatching(String regex) {
return new StartingCondition<>(haveNameStartingWith, prefix);
}

@PublicAPI(usage = ACCESS)
public static <HAS_NAME extends HasName & HasDescription & HasSourceCodeLocation> ArchCondition<HAS_NAME>
haveNameNotStartingWith(String prefix) {
final DescribedPredicate<HAS_NAME> haveNameStartingWith = have(nameStartingWith(prefix)).forSubType();
return not(new StartingCondition<>(haveNameStartingWith, prefix)).as("have name not starting with '%s'", prefix);
}

@PublicAPI(usage = ACCESS)
public static <HAS_NAME extends HasName & HasDescription & HasSourceCodeLocation> ArchCondition<HAS_NAME>
haveNameContaining(String infix) {
final DescribedPredicate<HAS_NAME> haveNameContaining = have(nameContaining(infix)).forSubType();
return new ContainingCondition<>(haveNameContaining, infix);
}

@PublicAPI(usage = ACCESS)
public static <HAS_NAME extends HasName & HasDescription & HasSourceCodeLocation> ArchCondition<HAS_NAME>
haveNameNotContaining(String infix) {
final DescribedPredicate<HAS_NAME> haveNameContaining = have(nameContaining(infix)).forSubType();
return not(new ContainingCondition<>(haveNameContaining, infix)).as("have name not containing '%s'", infix);
}

@PublicAPI(usage = ACCESS)
public static <HAS_NAME extends HasName & HasDescription & HasSourceCodeLocation> ArchCondition<HAS_NAME>
haveNameEndingWith(String suffix) {
final DescribedPredicate<HAS_NAME> haveNameEndingWith = have(nameEndingWith(suffix)).forSubType();
return new EndingCondition<>(haveNameEndingWith, suffix);
}

@PublicAPI(usage = ACCESS)
public static <HAS_NAME extends HasName & HasDescription & HasSourceCodeLocation> ArchCondition<HAS_NAME>
haveNameNotEndingWith(String suffix) {
final DescribedPredicate<HAS_NAME> haveNameEndingWith = have(nameEndingWith(suffix)).forSubType();
return not(new EndingCondition<>(haveNameEndingWith, suffix)).as("have name not ending with '%s'", suffix);
}

@PublicAPI(usage = ACCESS)
public static ArchCondition<JavaClass> resideInAPackage(final String packageIdentifier) {
return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier));
Expand Down Expand Up @@ -1281,7 +1302,7 @@ private static class StartingCondition<T extends HasDescription & HasSourceCodeL
public void check(T item, ConditionEvents events) {
boolean satisfied = startingWith.apply(item);
String message = createMessage(item,
String.format("%s '%s'", satisfied ? "starts with" : "does not start with", prefix));
String.format("name %s '%s'", satisfied ? "starts with" : "does not start with", prefix));
events.add(new SimpleConditionEvent(item, satisfied, message));
}
}
Expand All @@ -1300,7 +1321,7 @@ private static class ContainingCondition<T extends HasDescription & HasSourceCod
public void check(T item, ConditionEvents events) {
boolean satisfied = containing.apply(item);
String message = createMessage(item,
String.format("%s '%s'", satisfied ? "contains" : "does not contain", infix));
String.format("name %s '%s'", satisfied ? "contains" : "does not contain", infix));
events.add(new SimpleConditionEvent(item, satisfied, message));
}
}
Expand All @@ -1319,7 +1340,7 @@ private static class EndingCondition<T extends HasDescription & HasSourceCodeLoc
public void check(T item, ConditionEvents events) {
boolean satisfied = endingWith.apply(item);
String message = createMessage(item,
String.format("%s '%s'", satisfied ? "ends with" : "does not end with", suffix));
String.format("name %s '%s'", satisfied ? "ends with" : "does not end with", suffix));
events.add(new SimpleConditionEvent(item, satisfied, message));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,31 @@ public SELF haveNameStartingWith(String prefix) {
return addCondition(ArchConditions.haveNameStartingWith(prefix));
}

@Override
public SELF haveNameNotStartingWith(String prefix) {
return addCondition(ArchConditions.haveNameNotStartingWith(prefix));
}

@Override
public SELF haveNameContaining(String infix) {
return addCondition(ArchConditions.haveNameContaining(infix));
}

@Override
public SELF haveNameNotContaining(String infix) {
return addCondition(ArchConditions.haveNameNotContaining(infix));
}

@Override
public SELF haveNameEndingWith(String suffix) {
return addCondition(ArchConditions.haveNameEndingWith(suffix));
}

@Override
public SELF haveNameNotEndingWith(String suffix) {
return addCondition(ArchConditions.haveNameNotEndingWith(suffix));
}

@Override
public SELF bePublic() {
return addCondition(ArchConditions.bePublic());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,31 @@ public CONJUNCTION haveNameStartingWith(String prefix) {
return givenWith(have(nameStartingWith(prefix)));
}

@Override
public CONJUNCTION haveNameNotStartingWith(String prefix) {
return givenWith(have(not(nameStartingWith(prefix)).as("name not starting with '%s'", prefix)));
}

@Override
public CONJUNCTION haveNameContaining(String infix) {
return givenWith(have(nameContaining(infix)));
}

@Override
public CONJUNCTION haveNameNotContaining(String infix) {
return givenWith(have(not(nameContaining(infix)).as("name not containing '%s'", infix)));
}

@Override
public CONJUNCTION haveNameEndingWith(String suffix) {
return givenWith(have(nameEndingWith(suffix)));
}

@Override
public CONJUNCTION haveNameNotEndingWith(String suffix) {
return givenWith(have(not(nameEndingWith(suffix)).as("name not ending with '%s'", suffix)));
}

@Override
public CONJUNCTION arePublic() {
return givenWith(SyntaxPredicates.arePublic());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ public interface MembersShould<CONJUNCTION extends MembersShouldConjunction<?>>
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameStartingWith(String prefix);

/**
* Asserts that members have a name not starting with the specified prefix.
*
* @param prefix A prefix the member name should not start with
* @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameNotStartingWith(String prefix);

/**
* Asserts that members have a name containing the specified infix.
*
Expand All @@ -125,6 +134,15 @@ public interface MembersShould<CONJUNCTION extends MembersShouldConjunction<?>>
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameContaining(String infix);

/**
* Asserts that members have a name not containing the specified infix.
*
* @param infix An infix the member name should not contain
* @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameNotContaining(String infix);

/**
* Asserts that members have a name ending with the specified suffix.
*
Expand All @@ -134,6 +152,15 @@ public interface MembersShould<CONJUNCTION extends MembersShouldConjunction<?>>
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameEndingWith(String suffix);

/**
* Asserts that members have a name not ending with the specified suffix.
*
* @param suffix A suffix the member name should not end with
* @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameNotEndingWith(String suffix);

/**
* Asserts that members are public.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ public interface MembersThat<CONJUNCTION extends GivenMembersConjunction<?>> {
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameStartingWith(String prefix);

/**
* Matches members with a name not starting with the specified prefix.
*
* @param prefix A prefix the member name should not start with
* @return A syntax conjunction element, which can be completed to form a full rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameNotStartingWith(String prefix);

/**
* Matches members with a name containing the specified infix.
*
Expand All @@ -121,6 +130,15 @@ public interface MembersThat<CONJUNCTION extends GivenMembersConjunction<?>> {
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameContaining(String infix);

/**
* Matches members with a name not containing the specified infix.
*
* @param infix An infix the member name should not contain
* @return A syntax conjunction element, which can be completed to form a full rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameNotContaining(String infix);

/**
* Matches members with a name ending with the specified suffix.
*
Expand All @@ -130,6 +148,15 @@ public interface MembersThat<CONJUNCTION extends GivenMembersConjunction<?>> {
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameEndingWith(String suffix);

/**
* Matches members with a name not ending with the specified suffix.
*
* @param suffix A suffix the member name should not end with
* @return A syntax conjunction element, which can be completed to form a full rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION haveNameNotEndingWith(String suffix);

/**
* Matches public members.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,31 @@ public void haveSimpleNameStartingWith(ArchRule rule, String prefix) {
.doesNotContain(SomeClass.class.getName());
}

@DataProvider
public static Object[][] haveSimpleNameNotStartingWith_rules() {
String simpleName = WrongNamedClass.class.getSimpleName();
String prefix = simpleName.substring(0, simpleName.length() - 1);
return $$(
$(classes().should().haveSimpleNameNotStartingWith(prefix), prefix),
$(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix)
);
}

@Test
@UseDataProvider("haveSimpleNameNotStartingWith_rules")
public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) {
EvaluationResult result = rule.evaluate(importClasses(
SomeClass.class, WrongNamedClass.class));

assertThat(singleLineFailureReportOf(result))
.contains(String.format("classes should have simple name not starting with '%s'", prefix))
.containsPattern(String.format("simple name of %s starts with '%s' in %s",
quote(WrongNamedClass.class.getName()),
quote(prefix),
locationPattern(WrongNamedClass.class)))
.doesNotContain(SomeClass.class.getName());
}

@DataProvider
public static Object[][] haveSimpleNameContaining_rules() {
String simpleName = SomeClass.class.getSimpleName();
Expand Down Expand Up @@ -298,31 +323,6 @@ public void haveSimpleNameNotContaining(ArchRule rule, String infix) {
.doesNotContain(SomeClass.class.getName());
}

@DataProvider
public static Object[][] haveSimpleNameNotStartingWith_rules() {
String simpleName = WrongNamedClass.class.getSimpleName();
String prefix = simpleName.substring(0, simpleName.length() - 1);
return $$(
$(classes().should().haveSimpleNameNotStartingWith(prefix), prefix),
$(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix)
);
}

@Test
@UseDataProvider("haveSimpleNameNotStartingWith_rules")
public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) {
EvaluationResult result = rule.evaluate(importClasses(
SomeClass.class, WrongNamedClass.class));

assertThat(singleLineFailureReportOf(result))
.contains(String.format("classes should have simple name not starting with '%s'", prefix))
.containsPattern(String.format("simple name of %s starts with '%s' in %s",
quote(WrongNamedClass.class.getName()),
quote(prefix),
locationPattern(WrongNamedClass.class)))
.doesNotContain(SomeClass.class.getName());
}

@DataProvider
public static Object[][] haveSimpleNameEndingWith_rules() {
String simpleName = SomeClass.class.getSimpleName();
Expand Down Expand Up @@ -1722,7 +1722,7 @@ public void onlyCall_should_report_success_if_targets_are_non_resolvable(ArchRul
}

static String locationPattern(Class<?> clazz) {
return String.format("\\(%s.java:0\\)", quote(clazz.getSimpleName()));
return String.format("\\(%s.java:\\d+\\)", quote(clazz.getSimpleName()));
}

static String singleLineFailureReportOf(EvaluationResult result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,33 @@ public static Object[][] restricted_property_rule_starts() {
$(described(methods().that().haveNameStartingWith("m")), ALL_METHOD_DESCRIPTIONS),
$(described(constructors().that().haveNameStartingWith("<")), ALL_CONSTRUCTOR_DESCRIPTIONS),
$(described(fields().that().haveNameStartingWith("f")), ALL_FIELD_DESCRIPTIONS),
$(described(members().that().haveNameNotStartingWith("fie")), ALL_CODE_UNIT_DESCRIPTIONS),
$(described(codeUnits().that().haveNameNotStartingWith("me")), ALL_CONSTRUCTOR_DESCRIPTIONS),
$(described(methods().that().haveNameNotStartingWith("m")), emptySet()),
$(described(constructors().that().haveNameNotStartingWith("<")), emptySet()),
$(described(fields().that().haveNameNotStartingWith("f")), emptySet()),

$(described(members().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS),
$(described(codeUnits().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS),
$(described(methods().that().haveNameContaining("dA")), ImmutableSet.of(METHOD_A)),
$(described(constructors().that().haveNameContaining("init")), ALL_CONSTRUCTOR_DESCRIPTIONS),
$(described(fields().that().haveNameContaining("dA")), ImmutableSet.of(FIELD_A)),
$(described(members().that().haveNameNotContaining("et")), union(ALL_FIELD_DESCRIPTIONS, ALL_CONSTRUCTOR_DESCRIPTIONS)),
$(described(codeUnits().that().haveNameNotContaining("et")), ALL_CONSTRUCTOR_DESCRIPTIONS),
$(described(methods().that().haveNameNotContaining("dA")), allMethodsExcept(METHOD_A)),
$(described(constructors().that().haveNameNotContaining("init")), emptySet()),
$(described(fields().that().haveNameNotContaining("dA")), allFieldsExcept(FIELD_A)),

$(described(members().that().haveNameEndingWith("D")), ImmutableSet.of(FIELD_D, METHOD_D)),
$(described(codeUnits().that().haveNameEndingWith("A")), ImmutableSet.of(METHOD_A)),
$(described(methods().that().haveNameEndingWith("C")), ImmutableSet.of(METHOD_C)),
$(described(constructors().that().haveNameEndingWith("it>")), ALL_CONSTRUCTOR_DESCRIPTIONS),
$(described(fields().that().haveNameEndingWith("B")), ImmutableSet.of(FIELD_B)),
$(described(members().that().haveNameNotEndingWith("D")), allMembersExcept(FIELD_D, METHOD_D)),
$(described(codeUnits().that().haveNameNotEndingWith("A")), allCodeUnitsExcept(METHOD_A)),
$(described(methods().that().haveNameNotEndingWith("C")), allMethodsExcept(METHOD_C)),
$(described(constructors().that().haveNameNotEndingWith("it>")), emptySet()),
$(described(fields().that().haveNameNotEndingWith("B")), allFieldsExcept(FIELD_B)),

$(described(members().that().arePublic()), ImmutableSet.of(
FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)),
Expand Down
Loading

0 comments on commit 74dcc1b

Please sign in to comment.