Skip to content

Commit

Permalink
Merge branch 'master' into fix/tests-2
Browse files Browse the repository at this point in the history
  • Loading branch information
zentox authored Jan 9, 2024
2 parents 2a0597b + 743e7b3 commit 13198f9
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,4 @@ gradle-app.setting

### Jagr ###
jagr.conf
*.hprof
46 changes: 30 additions & 16 deletions src/graderPrivate/java/h06/H3_MazeSolverRecursiveTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junitpioneer.jupiter.json.JsonClasspathSource;
import org.junitpioneer.jupiter.json.Property;
import org.opentest4j.AssertionFailedError;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.tutor.general.annotation.SkipAfterFirstFailedTest;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import org.tudalgo.algoutils.tutor.general.match.BasicStringMatchers;
import org.tudalgo.algoutils.tutor.general.reflections.BasicMethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.MethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.TypeLink;
Expand All @@ -36,7 +36,6 @@
import java.awt.Point;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

Expand Down Expand Up @@ -218,10 +217,12 @@ public void testLeftRightComplex(
@DisplayName("13 | Verbindliche Anforderungen")
@Test
public void testRequirements() {
BasicMethodLink method = ((BasicMethodLink) getMethod("nextStep"));
Context context = contextBuilder().subject(method).build();
BasicMethodLink method = ((BasicMethodLink) getMethod("nextStep"));
Context.Builder<?> context = contextBuilder().subject(method);
int conds = method.getCtElement().filterChildren(it -> it instanceof CtConditional<?>).list().size();
assertEquals(1, conds, context,


assertEquals(1, conds, context.build(),
result -> "MazeSolverRecursive#nextStep(World, Point, DirectionVector) should contain exactly one "
+ "conditional statement, but found %s".formatted(conds));
List<CtReturn<?>> returns = method.getCtElement().filterChildren(it -> it instanceof CtReturn<?>)
Expand All @@ -233,11 +234,11 @@ public void testRequirements() {
&& !method.getCtElement().filterChildren(it -> it instanceof CtConditional<?>).list().isEmpty();
boolean condAndAssign = expression instanceof CtAssignment<?, ?> assignment
&& assignment.getAssignment() instanceof CtConditional<?>;
assertTrue(condRet || condAndVarRead || condAndAssign, context,

assertTrue(condRet || condAndVarRead || condAndAssign, context.build(),
result -> "MazeSolverRecursive#nextStep(World, Point, DirectionVector) should contain exactly one "
+ "conditional statement, but found %s"
.formatted(returns.stream().map(CtReturn::getReturnedExpression).toList()));

List<? extends CtExecutableReference<?>> calls = method.getCtElement()
.filterChildren(it -> it instanceof CtInvocation<?>)
.list()
Expand All @@ -252,10 +253,14 @@ public void testRequirements() {
&& !name.equals("rotate270");
})
.toList();
assertTrue(calls.isEmpty(), context,
assertTrue(calls.isEmpty(), context.build(),
result -> "MazeSolverRecursive#nextStep(World, Point, DirectionVector) should not contain any "
+ "invocations, but found %s".formatted(calls));
assertRecursive(method.getCtElement(), "MazeSolverRecursive#nextStep(World, Point, DirectionVector)", context);
assertRecursive(method, "MazeSolverRecursive#nextStep(World, Point, DirectionVector)", context,
BasicStringMatchers.identical("rotate270"),
BasicStringMatchers.identical("rotate90"),
BasicStringMatchers.identical("isBlocked")
);
}
}

Expand Down Expand Up @@ -451,9 +456,13 @@ public void testComplexPath(
@Test
public void testRequirements() {
BasicMethodLink method = ((BasicMethodLink) H3_MazeSolverRecursiveTest.this.getMethod("numberOfSteps"));
Context context = contextBuilder().subject(method)
.build();
assertRecursive(method.getCtElement(), "MazeSolverRecursive#numberOfSteps(World, Point, Point)", context);
Context.Builder<?> context = contextBuilder().subject(method);
assertRecursive(method, "MazeSolverRecursive#numberOfSteps(World, Point, Point)", context,
BasicStringMatchers.identical("equals"),
BasicStringMatchers.identical("nextStep"),
BasicStringMatchers.identical("getMovement"),
BasicStringMatchers.identical("from")
);
}
}

Expand Down Expand Up @@ -701,10 +710,15 @@ public void testContainsAll(
@DisplayName("24 | Verbindliche Anforderungen")
@Test
public void testRequirements() {
BasicMethodLink method = ((BasicMethodLink) H3_MazeSolverRecursiveTest.this.getMethod("solve"));
Context context = contextBuilder().subject(method)
.build();
assertRecursive(method.getCtElement(), "MazeSolverRecursive#solve(World, Point, Point, Direction))", context);
BasicMethodLink method = getMethod();
Context.Builder<?> context = contextBuilder().subject(method);
assertRecursive(method, "MazeSolverRecursive#solve(World, Point, Point, Direction))", context,
BasicStringMatchers.identical("equals"),
BasicStringMatchers.identical("numberOfSteps"),
BasicStringMatchers.identical("nextStep"),
BasicStringMatchers.identical("getMovement"),
BasicStringMatchers.identical("from")
);
}
}

Expand Down
30 changes: 23 additions & 7 deletions src/graderPrivate/java/h06/H4_MazeSolverIterativeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junitpioneer.jupiter.json.JsonClasspathSource;
import org.junitpioneer.jupiter.json.Property;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.tutor.general.annotation.SkipAfterFirstFailedTest;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import org.tudalgo.algoutils.tutor.general.match.BasicStringMatchers;
import org.tudalgo.algoutils.tutor.general.reflections.BasicMethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.MethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.TypeLink;
Expand All @@ -35,6 +35,7 @@
import java.util.stream.Stream;

import static h06.TutorUtils.assertIterative;
import static h06.TutorUtils.assertRecursive;
import static h06.TutorUtils.buildWorldContext;
import static h06.TutorUtils.getMethodLink;
import static h06.TutorUtils.getTypeLink;
Expand Down Expand Up @@ -210,8 +211,12 @@ public void testLeftRightComplex(
@Test
public void testRequirements() {
BasicMethodLink method = ((BasicMethodLink) getMethod("nextStep"));
Context context = contextBuilder().subject(method).build();
assertIterative(method.getCtElement(), "MazeSolverIterative#nextStep(World, Point, DirectionVector)", context);
Context.Builder<?> context = contextBuilder().subject(method);
assertIterative(method, "MazeSolverIterative#nextStep(World, Point, DirectionVector)", context,
BasicStringMatchers.identical("rotate270"),
BasicStringMatchers.identical("rotate90"),
BasicStringMatchers.identical("isBlocked")
);
}
}

Expand Down Expand Up @@ -380,8 +385,13 @@ public void testComplexPath(
@Test
public void testRequirements() {
BasicMethodLink method = getMethod();
Context context = contextBuilder().subject(method).build();
assertIterative(method.getCtElement(), "MazeSolverIterative#numberOfSteps(World, Point, Point))", context);
Context.Builder<?> context = contextBuilder().subject(method);
assertIterative(method, "MazeSolverIterative#numberOfSteps(World, Point, Point)", context,
BasicStringMatchers.identical("equals"),
BasicStringMatchers.identical("nextStep"),
BasicStringMatchers.identical("getMovement"),
BasicStringMatchers.identical("from")
);
}
}

Expand Down Expand Up @@ -588,8 +598,14 @@ public void testContainsAll(
@Test
public void testRequirements() {
BasicMethodLink method = getMethod();
Context context = contextBuilder().subject(method).build();
assertIterative(method.getCtElement(), "MazeSolverIterative#solve(World, Point, Point, Direction)", context);
Context.Builder<?> context = contextBuilder().subject(method);
assertIterative(method, "MazeSolverIterative#solve(World, Point, Point, Direction))", context,
BasicStringMatchers.identical("equals"),
BasicStringMatchers.identical("numberOfSteps"),
BasicStringMatchers.identical("nextStep"),
BasicStringMatchers.identical("getMovement"),
BasicStringMatchers.identical("from")
);
}
}

Expand Down
150 changes: 143 additions & 7 deletions src/graderPrivate/java/h06/TutorUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.tudalgo.algoutils.tutor.general.match.BasicStringMatchers;
import org.tudalgo.algoutils.tutor.general.match.Matcher;
import org.tudalgo.algoutils.tutor.general.match.MatcherFactories;
import org.tudalgo.algoutils.tutor.general.reflections.BasicMethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.BasicPackageLink;
import org.tudalgo.algoutils.tutor.general.reflections.MethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.TypeLink;
Expand All @@ -22,10 +23,10 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertFalse;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertTrue;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.contextBuilder;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions3.assertMethodExists;
Expand Down Expand Up @@ -206,10 +207,19 @@ public static Criterion criterionNested(Class<?> source) {
* @param method the method to test
* @param methodName the name of the method
* @param context the context
* @param skips defines the methods to skip
*/
public static void assertRecursive(CtMethod<?> method, String methodName, Context context) {
assertTrue(isRecursive(method), context,
result -> "The %s should be recursive, but found a loop".formatted(methodName)
@SafeVarargs
public static void assertRecursive(
MethodLink method,
String methodName,
Context.Builder<?> context,
Matcher<MethodLink>... skips
) {
var calls = getIterativeCalls(method, skips);
var callsName = calls.stream().map(MethodLink::name).collect(Collectors.toSet());
assertTrue(calls.isEmpty(), context.add("Method calls (iterative)", callsName).build(),
result -> "The %s should be recursive, but found a loop(s) in %s".formatted(methodName, callsName)
);
}

Expand All @@ -220,12 +230,138 @@ public static void assertRecursive(CtMethod<?> method, String methodName, Contex
* @param methodName the name of the method
* @param context the context
*/
public static void assertIterative(CtMethod<?> method, String methodName, Context context) {
assertFalse(isRecursive(method), context,
result -> "The %s should be iterative, but found a recursion".formatted(methodName)
@SafeVarargs
public static void assertIterative(
MethodLink method, String methodName,
Context.Builder<?> context,
Matcher<MethodLink>... skips
) {
var calls = getRecursiveCalls(method, skips);
var callsName = calls.stream().map(MethodLink::name).collect(Collectors.toSet());
assertTrue(calls.isEmpty(), context.add("Method calls (iterative)", callsName).build(),
result -> "The %s should be iterative, but found a recursion in %s".formatted(methodName, callsName)
);
}

/**
* Returns the method calls in the given method.
*
* @param method the method to get the method calls for
* @param visited the visited methods so far (to prevent infinite recursion)
* @param skips defines the methods to skip
* @return the method calls in the given method
*/
private static Set<MethodLink> getMethodCalls(
MethodLink method,
Set<MethodLink> visited,
Matcher<MethodLink> skips
) {
CtMethod<?> ctMethod;
try {
ctMethod = ((BasicMethodLink) method).getCtElement();
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
// java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
// java.lang.NullPointerException: Cannot invoke "String.toCharArray()" because "this.content" is null
// Occurs if we read src code from stdlib - skip them
return Set.of();
}
return ctMethod.filterChildren(it -> it instanceof CtInvocation<?>)
.list()
.stream()
.filter(element -> element instanceof CtInvocation<?> invocation)
.map(element -> (CtInvocation<?>) element)
.map(CtInvocation::getExecutable)
.map(CtExecutableReference::getActualMethod)
.filter(Objects::nonNull)
.map(BasicMethodLink::of)
.filter(methodLink -> !visited.contains(methodLink))
.filter(methodLink -> skips.match(methodLink).negative())
.collect(Collectors.toSet());
}

/**
* Returns the recursive calls in the given method (including all method-calls in the method).
*
* @param method the method to get the recursive calls for
* @param skips defines the methods to skip
* @return the recursive calls in the given method
*/
@SafeVarargs
public static Set<MethodLink> getRecursiveCalls(MethodLink method, Matcher<MethodLink>... skips) {
Set<MethodLink> recursion = new HashSet<>();
computeRecursiveCalls(method, recursion, new HashSet<>(), Arrays.stream(skips)
.reduce(Matcher::or)
.orElse(Matcher.never()));
return recursion;
}

/**
* Computes the recursive calls in the given method (including all method-calls in the method).
*
* @param method the method to get the recursive calls for
* @param found the so far found recursive calls
* @param visited the visited methods so far (to prevent infinite recursion)
* @param skips defines the methods to skip
*/
private static void computeRecursiveCalls(
MethodLink method, Set<MethodLink> found,
Set<MethodLink> visited,
Matcher<MethodLink> skips
) {
var methodCalls = getMethodCalls(method, visited, skips);
if (methodCalls.stream().anyMatch(m -> m.equals(method))) {
found.add(method);
}
visited.addAll(methodCalls);
for (MethodLink methodLink : methodCalls) {
computeRecursiveCalls(methodLink, found, visited, skips);
}
}

/**
* Returns the iterative calls in the given method (including all method-calls in the method).
*
* @param method the method to get the iterative calls for
* @param skips defines the methods to skip
* @return the iterative calls in the given method
*/
@SafeVarargs
public static Set<MethodLink> getIterativeCalls(MethodLink method, Matcher<MethodLink>... skips) {
Set<MethodLink> recursion = new HashSet<>();
computeIterativeCalls(method, recursion, new HashSet<>(), Arrays.stream(skips)
.reduce(Matcher::or)
.orElse(Matcher.never()));
return recursion;
}

/**
* Computes the iterative calls in the given method (including all method-calls in the method).
*
* @param method the method to get the iterative calls for
* @param found the so far found iterative calls
* @param visited the visited methods so far (to prevent infinite recursion)
* @param skips defines the methods to skip
*/
private static void computeIterativeCalls(MethodLink method, Set<MethodLink> found, Set<MethodLink> visited, Matcher<MethodLink> skips) {
CtMethod<?> ctMethod;
try {
ctMethod = ((BasicMethodLink) method).getCtElement();
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
// java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
// java.lang.NullPointerException: Cannot invoke "String.toCharArray()" because "this.content" is null
// Occurs if we read src code from stdlib - skip them
return;
}
if (!ctMethod.filterChildren(it -> it instanceof CtLoop).list().isEmpty()) {
found.add(method);
}
var methodCalls = getMethodCalls(method, visited, skips);
visited.addAll(methodCalls);
for (MethodLink methodLink : methodCalls) {
computeIterativeCalls(methodLink, found, visited, skips);
}
}

/**
* Returns {@code true} if the given method is recursive.
*
Expand Down

0 comments on commit 13198f9

Please sign in to comment.