Skip to content

Commit 39dc4c4

Browse files
author
Julien Letrouit
committed
Adds unit test system and more advanced objects koans
1 parent 2ab762c commit 39dc4c4

42 files changed

Lines changed: 1428 additions & 659 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/engine/Assertions.java

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package engine;
22

3-
import java.lang.reflect.InvocationTargetException;
43
import java.lang.reflect.Modifier;
54
import java.util.Arrays;
65
import java.util.Optional;
@@ -13,15 +12,15 @@
1312
* Library of various assertions which can be run about the result of a koan execution.
1413
*/
1514
public class Assertions {
16-
private static String resolveParam(KoanResult res, Object p) {
17-
if (p instanceof FormatParam) {
18-
return ((FormatParam)p).format(res);
15+
private static String resolveTemplateParam(KoanResult res, Object param) {
16+
if (param instanceof FormatParam) {
17+
return ((FormatParam)param).format(res);
1918
}
2019

21-
return Optional.ofNullable(p).map((v) -> v.toString()).orElse("");
20+
return Optional.ofNullable(param).map((v) -> v.toString()).orElse("");
2221
}
2322

24-
private static String whenCalling(KoanResult res) throws IllegalAccessException, ClassNotFoundException, InstantiationException, InvocationTargetException {
23+
private static String whenCalling(KoanResult res) {
2524
if (res.targetMethod.hasParameters()) {
2625
return String.format(" when calling %s", res.targetMethod);
2726
}
@@ -35,7 +34,7 @@ public static ResultAssertion assertIf(boolean condition, ResultAssertion inner)
3534
public static ResultAssertion assertNextStdOutLineEquals(Localizable<String> expectedTemplate, Object... params) {
3635
return (p, res) -> {
3736
final var realParams = Arrays.stream(params)
38-
.map((param) -> Assertions.resolveParam(res, param))
37+
.map((param) -> Assertions.resolveTemplateParam(res, param))
3938
.toArray();
4039
final var expected = String.format(expectedTemplate.get(res.locale), realParams);
4140
final var lineContent = res.nextStdOutLine();
@@ -245,7 +244,7 @@ public static ResultAssertion assertReturnValueWithRandomEquals(int fromOffset,
245244
private static ResultAssertion assertReturnValueWithRandomEquals(Function<KoanResult, double[]> randomNumbersFunc, ResToIntFunction buildExpected) {
246245
return (p, res) -> {
247246
var randomNumbers = randomNumbersFunc.apply(res);
248-
var formatRandomNumbers = Helpers.formatSequence(randomNumbers, AND.get(res.locale));
247+
var formatRandomNumbers = Helpers.formatSequence(res.locale, randomNumbers);
249248

250249
int expected = buildExpected.apply(res);
251250
if (res.methodReturnValue == null) {
@@ -270,9 +269,127 @@ private static ResultAssertion assertReturnValueWithRandomEquals(Function<KoanRe
270269
};
271270
}
272271

272+
public static KoanAssertion assertClassIsInstantiable() {
273+
return (p, locale, koan) -> {
274+
try {
275+
var clasz = koan.koanClass.get(locale).resolve();
276+
if (!Helpers.isInstantiable(clasz)) {
277+
p.println(Color.red(EXPECTED_CLASS_TO_BE_INSTANTIABLE), koan.koanClass.get(locale).className);
278+
return false;
279+
}
280+
} catch (ClassNotFoundException cnfe) {
281+
p.println(Color.red(EXPECTED_TO_FIND_A_CLASS_IN_THE_PACKAGE), koan.koanClass.get(locale).simpleClassName, koan.koanClass.get(locale).packageName);
282+
return false;
283+
}
284+
285+
return true;
286+
};
287+
}
288+
289+
public static KoanAssertion assertConstructorIsInvokable() {
290+
return (p, locale, koan) -> {
291+
var clasz = koan.koanClass.get(locale).unsafeResolve();
292+
293+
try {
294+
var constructor = clasz.getConstructor(Type.unsafeResolveTypes(koan.constructorParamTypes));
295+
if (!Modifier.isPublic(constructor.getModifiers())) {
296+
p.println(
297+
Color.red(EXPECTED_CONSTRUCTOR_TO_BE_PUBLIC),
298+
koan.koanClass.get(locale).simpleClassName
299+
);
300+
}
301+
}
302+
catch(NoSuchMethodException nsme) {
303+
if (koan.constructorParamTypes.length == 0) {
304+
p.println(
305+
Color.red(EXPECTED_TO_FIND_CONSTRUCTOR_NO_PARAMS),
306+
koan.koanClass.get(locale).simpleClassName
307+
);
308+
} else if (koan.constructorParamTypes.length == 1) {
309+
p.println(
310+
Color.red(EXPECTED_TO_FIND_CONSTRUCTOR_ONE_PARAM),
311+
koan.koanClass.get(locale).simpleClassName,
312+
koan.constructorParamTypes[0]
313+
);
314+
} else {
315+
final var expectedParams = Arrays
316+
.stream(koan.constructorParamTypes)
317+
.map(type -> "'" + type + "'")
318+
.toArray(String[]::new);
319+
p.println(
320+
Color.red(EXPECTED_TO_FIND_CONSTRUCTOR_MANY_PARAMS),
321+
koan.koanClass.get(locale).simpleClassName,
322+
Helpers.formatSequence(locale, expectedParams)
323+
);
324+
}
325+
return false;
326+
}
327+
328+
return true;
329+
};
330+
}
331+
332+
public static KoanAssertion assertMethodIsInvokable(String methodName, boolean isStatic, Type... paramTypes) {
333+
return assertMethodIsInvokable(methodName, isStatic, Type.unsafeResolveTypes(paramTypes));
334+
}
335+
336+
public static KoanAssertion assertMethodIsInvokable(String methodName, boolean isStatic, Class<?>... methodParamTypes) {
337+
return (p, locale, koan) -> {
338+
var clasz = koan.koanClass.get(locale).unsafeResolve();
339+
340+
try {
341+
var method = clasz.getMethod(methodName, methodParamTypes);
342+
if (isStatic && !Modifier.isStatic(method.getModifiers())) {
343+
p.println(Color.red(EXPECTED_METHOD_TO_NOT_BE_STATIC), methodName, clasz.getName().replace(".", "/"));
344+
return false;
345+
}
346+
if (!isStatic && Modifier.isStatic(method.getModifiers())) {
347+
p.println(Color.red(EXPECTED_METHOD_TO_BE_STATIC), methodName, clasz.getName().replace(".", "/"));
348+
return false;
349+
}
350+
if (!Modifier.isPublic(method.getModifiers())) {
351+
p.println(
352+
Color.red(EXPECTED_METHOD_TO_BE_PUBLIC),
353+
koan.koanClass.get(locale).simpleClassName
354+
);
355+
}
356+
}
357+
catch(NoSuchMethodException nsme) {
358+
if (methodParamTypes.length == 0) {
359+
p.println(
360+
Color.red(EXPECTED_TO_FIND_MEHOD_NO_PARAMS),
361+
methodName,
362+
clasz.getName().replace(".", "/")
363+
);
364+
} else if (methodParamTypes.length == 1) {
365+
p.println(
366+
Color.red(EXPECTED_TO_FIND_MEHOD_ONE_PARAM),
367+
methodName,
368+
clasz.getName().replace(".", "/"),
369+
methodParamTypes[0].getSimpleName()
370+
);
371+
} else {
372+
final var expectedParams = Arrays
373+
.stream(methodParamTypes)
374+
.map(type -> "'" + type.getSimpleName() + "'")
375+
.toArray(String[]::new);
376+
p.println(
377+
Color.red(EXPECTED_TO_FIND_MEHOD_MANY_PARAMS),
378+
methodName,
379+
clasz.getName().replace(".", "/"),
380+
Helpers.formatSequence(locale, expectedParams)
381+
);
382+
}
383+
return false;
384+
}
385+
386+
return true;
387+
};
388+
}
389+
273390
public static KoanAssertion assertFieldIsPrivate(String fieldName) {
274-
return (p, methodDetails) -> {
275-
var clasz = methodDetails.clasz;
391+
return (p, locale, koan) -> {
392+
var clasz = koan.koanClass.get(locale).unsafeResolve();
276393

277394
try {
278395
var field = clasz.getDeclaredField(fieldName);
@@ -291,8 +408,8 @@ public static KoanAssertion assertFieldIsPrivate(String fieldName) {
291408
}
292409

293410
public static KoanAssertion assertFieldIsFinal(String fieldName) {
294-
return (p, methodDetails) -> {
295-
var clasz = methodDetails.clasz;
411+
return (p, locale, koan) -> {
412+
var clasz = koan.koanClass.get(locale).unsafeResolve();
296413

297414
try {
298415
var field = clasz.getDeclaredField(fieldName);
@@ -311,12 +428,12 @@ public static KoanAssertion assertFieldIsFinal(String fieldName) {
311428
}
312429

313430
public static KoanAssertion assertFieldType(String fieldName, Type fieldType) {
314-
return (p, methodDetails) -> {
315-
var clasz = methodDetails.clasz;
431+
return (p, locale, koan) -> {
432+
var clasz = koan.koanClass.get(locale).unsafeResolve();
316433

317434
try {
318435
var field = clasz.getDeclaredField(fieldName);
319-
if (!field.getType().equals(fieldType.resolve())) {
436+
if (!field.getType().equals(fieldType.unsafeResolve())) {
320437
p.println(Color.red(EXPECTED_FIELD_TO_BE_OF_TYPE), fieldName, clasz.getName(), fieldType, field.getType().getSimpleName());
321438
return false;
322439
}

src/main/java/engine/Factories.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public static <T> Global<T> global(T item) {
2525
return new Global<T>(item);
2626
}
2727

28+
public static Global<Class<?>> globalClass(Class<?> enItem) {
29+
return new Global<>(enItem);
30+
}
31+
2832
public static <T> Local<T> local(T enItem) {
2933
return new Local<T>(enItem);
3034
}

src/main/java/engine/Global.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package engine;
22

3+
import java.util.function.Function;
4+
35
/**
46
* Implements Localizable for items which do NOT vary from one locale to the other, and are constants accross locales.
57
*/
@@ -13,4 +15,8 @@ public Global(T item) {
1315
public T get(Locale locale) {
1416
return item;
1517
}
18+
19+
public <U> Localizable<U> map(Function<T, U> transformFunction) {
20+
return new Global<U>(transformFunction.apply(item));
21+
}
1622
}

src/main/java/engine/Helpers.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package engine;
22

3+
import static engine.Texts.AND;
4+
5+
import java.lang.reflect.Modifier;
36
import java.util.Arrays;
47
import java.util.Random;
58
import java.util.Scanner;
@@ -30,14 +33,14 @@ public static double random() {
3033
return rng.nextDouble();
3134
}
3235

33-
static String formatSequence(double[] toFormat, String localizedAndTemplate) {
36+
static String formatSequence(Locale locale, double[] toFormat) {
3437
return formatSequence(
35-
Arrays.stream(toFormat).mapToObj(Double::toString).toArray(String[]::new),
36-
localizedAndTemplate
38+
locale,
39+
Arrays.stream(toFormat).mapToObj(Double::toString).toArray(String[]::new)
3740
);
3841
}
3942

40-
static String formatSequence(Object[] toFormat, String localizedAndTemplate) {
43+
static String formatSequence(Locale locale, Object[] toFormat) {
4144
if (toFormat == null || toFormat.length == 0) {
4245
return "";
4346
}
@@ -49,9 +52,20 @@ static String formatSequence(Object[] toFormat, String localizedAndTemplate) {
4952
result.append(", ");
5053
result.append(toFormat[i]);
5154
}
52-
result.append(String.format(localizedAndTemplate, toFormat[toFormat.length - 1]));
55+
result.append(String.format(AND.get(locale), toFormat[toFormat.length - 1]));
5356
}
5457

5558
return result.toString();
5659
}
60+
61+
static boolean isInstantiable(Class<?> clasz) {
62+
int modifiers = clasz.getModifiers();
63+
return
64+
Modifier.isPublic(modifiers) &&
65+
!clasz.isInterface() &&
66+
!Modifier.isAbstract(modifiers) &&
67+
!clasz.isArray() &&
68+
!clasz.isPrimitive() &&
69+
!clasz.equals(void.class);
70+
}
5771
}

0 commit comments

Comments
 (0)