2015-12-06 11:45:16 -08:00
|
|
|
// onjava/atunit/AtUnit.java
|
2021-01-31 15:42:31 -07:00
|
|
|
// (c)2021 MindView LLC: see Copyright.txt
|
2015-11-15 15:51:35 -08:00
|
|
|
// We make no guarantees that this code is fit for any purpose.
|
2016-09-23 13:23:35 -06:00
|
|
|
// Visit http://OnJava8.com for more book information.
|
2016-01-25 18:05:55 -08:00
|
|
|
// An annotation-based unit-test framework
|
2016-07-28 12:48:23 -06:00
|
|
|
// {java onjava.atunit.AtUnit}
|
2015-12-06 11:45:16 -08:00
|
|
|
package onjava.atunit;
|
2015-06-15 17:47:35 -07:00
|
|
|
import java.lang.reflect.*;
|
|
|
|
import java.io.*;
|
|
|
|
import java.util.*;
|
2015-12-06 11:45:16 -08:00
|
|
|
import java.nio.file.*;
|
|
|
|
import java.util.stream.*;
|
2015-11-11 20:20:04 -08:00
|
|
|
import onjava.*;
|
2015-06-15 17:47:35 -07:00
|
|
|
|
|
|
|
public class AtUnit implements ProcessFiles.Strategy {
|
|
|
|
static Class<?> testClass;
|
|
|
|
static List<String> failedTests= new ArrayList<>();
|
|
|
|
static long testsRun = 0;
|
|
|
|
static long failures = 0;
|
2016-01-25 18:05:55 -08:00
|
|
|
public static void
|
|
|
|
main(String[] args) throws Exception {
|
2015-06-15 17:47:35 -07:00
|
|
|
ClassLoader.getSystemClassLoader()
|
2017-05-10 11:45:39 -06:00
|
|
|
.setDefaultAssertionStatus(true); // Enable assert
|
2015-06-15 17:47:35 -07:00
|
|
|
new ProcessFiles(new AtUnit(), "class").start(args);
|
|
|
|
if(failures == 0)
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println("OK (" + testsRun + " tests)");
|
2015-06-15 17:47:35 -07:00
|
|
|
else {
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println("(" + testsRun + " tests)");
|
2016-01-25 18:05:55 -08:00
|
|
|
System.out.println(
|
|
|
|
"\n>>> " + failures + " FAILURE" +
|
2015-06-15 17:47:35 -07:00
|
|
|
(failures > 1 ? "S" : "") + " <<<");
|
|
|
|
for(String failed : failedTests)
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println(" " + failed);
|
2015-06-15 17:47:35 -07:00
|
|
|
}
|
|
|
|
}
|
2021-01-31 15:42:31 -07:00
|
|
|
@Override public void process(File cFile) {
|
2015-06-15 17:47:35 -07:00
|
|
|
try {
|
|
|
|
String cName = ClassNameFinder.thisClass(
|
2015-12-06 11:45:16 -08:00
|
|
|
Files.readAllBytes(cFile.toPath()));
|
|
|
|
if(!cName.startsWith("public:"))
|
|
|
|
return;
|
|
|
|
cName = cName.split(":")[1];
|
2015-06-15 17:47:35 -07:00
|
|
|
if(!cName.contains("."))
|
|
|
|
return; // Ignore unpackaged classes
|
|
|
|
testClass = Class.forName(cName);
|
|
|
|
} catch(IOException | ClassNotFoundException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
TestMethods testMethods = new TestMethods();
|
|
|
|
Method creator = null;
|
|
|
|
Method cleanup = null;
|
|
|
|
for(Method m : testClass.getDeclaredMethods()) {
|
|
|
|
testMethods.addIfTestMethod(m);
|
|
|
|
if(creator == null)
|
|
|
|
creator = checkForCreatorMethod(m);
|
|
|
|
if(cleanup == null)
|
|
|
|
cleanup = checkForCleanupMethod(m);
|
|
|
|
}
|
|
|
|
if(testMethods.size() > 0) {
|
|
|
|
if(creator == null)
|
|
|
|
try {
|
|
|
|
if(!Modifier.isPublic(testClass
|
2017-05-10 11:45:39 -06:00
|
|
|
.getDeclaredConstructor()
|
|
|
|
.getModifiers())) {
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println("Error: " + testClass +
|
|
|
|
" no-arg constructor must be public");
|
2015-06-15 17:47:35 -07:00
|
|
|
System.exit(1);
|
|
|
|
}
|
|
|
|
} catch(NoSuchMethodException e) {
|
2015-11-03 12:00:44 -08:00
|
|
|
// Synthesized no-arg constructor; OK
|
2015-06-15 17:47:35 -07:00
|
|
|
}
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println(testClass.getName());
|
2015-06-15 17:47:35 -07:00
|
|
|
}
|
|
|
|
for(Method m : testMethods) {
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.print(" . " + m.getName() + " ");
|
2015-06-15 17:47:35 -07:00
|
|
|
try {
|
|
|
|
Object testObject = createTestObject(creator);
|
|
|
|
boolean success = false;
|
|
|
|
try {
|
|
|
|
if(m.getReturnType().equals(boolean.class))
|
|
|
|
success = (Boolean)m.invoke(testObject);
|
|
|
|
else {
|
|
|
|
m.invoke(testObject);
|
|
|
|
success = true; // If no assert fails
|
|
|
|
}
|
|
|
|
} catch(InvocationTargetException e) {
|
|
|
|
// Actual exception is inside e:
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println(e.getCause());
|
2015-06-15 17:47:35 -07:00
|
|
|
}
|
2015-11-03 12:00:44 -08:00
|
|
|
System.out.println(success ? "" : "(failed)");
|
2015-06-15 17:47:35 -07:00
|
|
|
testsRun++;
|
|
|
|
if(!success) {
|
|
|
|
failures++;
|
|
|
|
failedTests.add(testClass.getName() +
|
|
|
|
": " + m.getName());
|
|
|
|
}
|
|
|
|
if(cleanup != null)
|
|
|
|
cleanup.invoke(testObject, testObject);
|
|
|
|
} catch(IllegalAccessException |
|
|
|
|
IllegalArgumentException |
|
|
|
|
InvocationTargetException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-06 11:45:16 -08:00
|
|
|
public static
|
|
|
|
class TestMethods extends ArrayList<Method> {
|
2015-06-15 17:47:35 -07:00
|
|
|
void addIfTestMethod(Method m) {
|
|
|
|
if(m.getAnnotation(Test.class) == null)
|
|
|
|
return;
|
|
|
|
if(!(m.getReturnType().equals(boolean.class) ||
|
|
|
|
m.getReturnType().equals(void.class)))
|
|
|
|
throw new RuntimeException("@Test method" +
|
|
|
|
" must return boolean or void");
|
2017-05-10 11:45:39 -06:00
|
|
|
m.setAccessible(true); // If it's private, etc.
|
2015-06-15 17:47:35 -07:00
|
|
|
add(m);
|
|
|
|
}
|
|
|
|
}
|
2017-05-10 11:45:39 -06:00
|
|
|
private static
|
|
|
|
Method checkForCreatorMethod(Method m) {
|
2015-06-15 17:47:35 -07:00
|
|
|
if(m.getAnnotation(TestObjectCreate.class) == null)
|
|
|
|
return null;
|
|
|
|
if(!m.getReturnType().equals(testClass))
|
|
|
|
throw new RuntimeException("@TestObjectCreate " +
|
|
|
|
"must return instance of Class to be tested");
|
|
|
|
if((m.getModifiers() &
|
|
|
|
java.lang.reflect.Modifier.STATIC) < 1)
|
|
|
|
throw new RuntimeException("@TestObjectCreate " +
|
|
|
|
"must be static.");
|
|
|
|
m.setAccessible(true);
|
|
|
|
return m;
|
|
|
|
}
|
2017-05-10 11:45:39 -06:00
|
|
|
private static
|
|
|
|
Method checkForCleanupMethod(Method m) {
|
2015-06-15 17:47:35 -07:00
|
|
|
if(m.getAnnotation(TestObjectCleanup.class) == null)
|
|
|
|
return null;
|
|
|
|
if(!m.getReturnType().equals(void.class))
|
|
|
|
throw new RuntimeException("@TestObjectCleanup " +
|
|
|
|
"must return void");
|
|
|
|
if((m.getModifiers() &
|
|
|
|
java.lang.reflect.Modifier.STATIC) < 1)
|
|
|
|
throw new RuntimeException("@TestObjectCleanup " +
|
|
|
|
"must be static.");
|
|
|
|
if(m.getParameterTypes().length == 0 ||
|
|
|
|
m.getParameterTypes()[0] != testClass)
|
|
|
|
throw new RuntimeException("@TestObjectCleanup " +
|
|
|
|
"must take an argument of the tested type.");
|
|
|
|
m.setAccessible(true);
|
|
|
|
return m;
|
|
|
|
}
|
2017-01-22 16:48:11 -08:00
|
|
|
private static Object
|
|
|
|
createTestObject(Method creator) {
|
2015-06-15 17:47:35 -07:00
|
|
|
if(creator != null) {
|
|
|
|
try {
|
|
|
|
return creator.invoke(testClass);
|
|
|
|
} catch(IllegalAccessException |
|
|
|
|
IllegalArgumentException |
|
|
|
|
InvocationTargetException e) {
|
|
|
|
throw new RuntimeException("Couldn't run " +
|
|
|
|
"@TestObject (creator) method.");
|
|
|
|
}
|
2015-11-03 12:00:44 -08:00
|
|
|
} else { // Use the no-arg constructor:
|
2015-06-15 17:47:35 -07:00
|
|
|
try {
|
2020-10-07 17:06:42 -06:00
|
|
|
return testClass
|
|
|
|
.getConstructor().newInstance();
|
2015-06-15 17:47:35 -07:00
|
|
|
} catch(InstantiationException |
|
2020-10-07 17:06:42 -06:00
|
|
|
NoSuchMethodException |
|
|
|
|
InvocationTargetException |
|
2015-06-15 17:47:35 -07:00
|
|
|
IllegalAccessException e) {
|
2017-01-22 16:48:11 -08:00
|
|
|
throw new RuntimeException(
|
|
|
|
"Couldn't create a test object. " +
|
|
|
|
"Try using a @TestObject method.");
|
2015-06-15 17:47:35 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-07 11:44:36 -06:00
|
|
|
}
|