282 lines
11 KiB
Java
282 lines
11 KiB
Java
package base;
|
|
|
|
import java.io.*;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
/**
|
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
@FunctionalInterface
|
|
public interface Runner {
|
|
List<String> run(final TestCounter counter, final List<String> input);
|
|
|
|
default List<String> run(final TestCounter counter, final String... input) {
|
|
return run(counter, List.of(input));
|
|
}
|
|
|
|
default void testEquals(
|
|
final TestCounter counter,
|
|
final List<String> input,
|
|
final List<String> expected
|
|
) {
|
|
counter.test(() -> {
|
|
final List<String> actual = run(counter, input);
|
|
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
|
final String exp = expected.get(i);
|
|
final String act = actual.get(i);
|
|
if (!exp.equalsIgnoreCase(act)) {
|
|
Asserts.assertEquals("Line " + (i + 1), exp, act);
|
|
return;
|
|
}
|
|
}
|
|
Asserts.assertEquals(
|
|
"Number of lines",
|
|
expected.size(),
|
|
actual.size()
|
|
);
|
|
});
|
|
}
|
|
|
|
static Packages packages(final String... packages) {
|
|
return new Packages(List.of(packages));
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface CommentRunner {
|
|
List<String> run(
|
|
String comment,
|
|
TestCounter counter,
|
|
List<String> input
|
|
);
|
|
}
|
|
|
|
final class Packages {
|
|
|
|
private final List<String> packages;
|
|
|
|
private Packages(final List<String> packages) {
|
|
this.packages = packages;
|
|
}
|
|
|
|
public Runner std(final String className) {
|
|
final CommentRunner main = main(className);
|
|
return (counter, input) ->
|
|
counter.call("io", () -> {
|
|
final ByteArrayOutputStream baos =
|
|
new ByteArrayOutputStream();
|
|
try (
|
|
final PrintWriter writer = new PrintWriter(
|
|
baos,
|
|
false,
|
|
StandardCharsets.UTF_8
|
|
)
|
|
) {
|
|
input.forEach(writer::println);
|
|
}
|
|
|
|
final InputStream oldIn = System.in;
|
|
try {
|
|
System.setIn(
|
|
new ByteArrayInputStream(baos.toByteArray())
|
|
);
|
|
return main.run(
|
|
String.format("[%d input lines]", input.size()),
|
|
counter,
|
|
List.of()
|
|
);
|
|
} finally {
|
|
System.setIn(oldIn);
|
|
}
|
|
});
|
|
}
|
|
|
|
@SuppressWarnings("ConfusingMainMethod")
|
|
public CommentRunner main(final String className) {
|
|
final Method method = findMain(className);
|
|
|
|
return (comment, counter, input) -> {
|
|
counter.format(
|
|
"Running test %02d: java %s %s%n",
|
|
counter.getTestNo(),
|
|
method.getDeclaringClass().getName(),
|
|
comment
|
|
);
|
|
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
|
final PrintStream oldOut = System.out;
|
|
try {
|
|
System.setOut(
|
|
new PrintStream(out, false, StandardCharsets.UTF_8)
|
|
);
|
|
method.invoke(
|
|
null,
|
|
new Object[] { input.toArray(String[]::new) }
|
|
);
|
|
final BufferedReader reader = new BufferedReader(
|
|
new InputStreamReader(
|
|
new ByteArrayInputStream(out.toByteArray()),
|
|
StandardCharsets.UTF_8
|
|
)
|
|
);
|
|
final List<String> result = new ArrayList<>();
|
|
while (true) {
|
|
final String line = reader.readLine();
|
|
if (line == null) {
|
|
if (result.isEmpty()) {
|
|
result.add("");
|
|
}
|
|
return result;
|
|
}
|
|
result.add(line.trim());
|
|
}
|
|
} catch (final InvocationTargetException e) {
|
|
final Throwable cause = e.getCause();
|
|
throw Asserts.error(
|
|
"main thrown exception %s: %s",
|
|
cause.getClass().getSimpleName(),
|
|
cause
|
|
);
|
|
} catch (final Exception e) {
|
|
throw Asserts.error(
|
|
"Cannot invoke main: %s: %s",
|
|
e.getClass().getSimpleName(),
|
|
e.getMessage()
|
|
);
|
|
} finally {
|
|
System.setOut(oldOut);
|
|
}
|
|
};
|
|
}
|
|
|
|
private Method findMain(final String className) {
|
|
try {
|
|
final URL url = new File(".").toURI().toURL();
|
|
final List<String> candidates = packages
|
|
.stream()
|
|
.flatMap(pkg -> {
|
|
final String prefix = pkg.isEmpty() ? pkg : pkg + ".";
|
|
return Stream.of(
|
|
prefix + className,
|
|
prefix + "$" + className
|
|
);
|
|
})
|
|
.toList();
|
|
|
|
//noinspection ClassLoaderInstantiation,resource,IOResourceOpenedButNotSafelyClosed
|
|
final URLClassLoader classLoader = new URLClassLoader(
|
|
new URL[] { url }
|
|
);
|
|
for (final String candidate : candidates) {
|
|
try {
|
|
final Class<?> loaded = classLoader.loadClass(
|
|
candidate
|
|
);
|
|
if (!Modifier.isPublic(loaded.getModifiers())) {
|
|
throw Asserts.error(
|
|
"Class %s is not public",
|
|
candidate
|
|
);
|
|
}
|
|
final Method main = loaded.getMethod(
|
|
"main",
|
|
String[].class
|
|
);
|
|
if (
|
|
!Modifier.isPublic(main.getModifiers()) ||
|
|
!Modifier.isStatic(main.getModifiers())
|
|
) {
|
|
throw Asserts.error(
|
|
"Method main of class %s should be public and static",
|
|
candidate
|
|
);
|
|
}
|
|
return main;
|
|
} catch (final ClassNotFoundException e) {
|
|
// Ignore
|
|
} catch (final NoSuchMethodException e) {
|
|
throw Asserts.error(
|
|
"Could not find method main(String[]) in class %s",
|
|
candidate,
|
|
e
|
|
);
|
|
}
|
|
}
|
|
throw Asserts.error(
|
|
"Could not find neither of classes %s",
|
|
candidates
|
|
);
|
|
} catch (final MalformedURLException e) {
|
|
throw Asserts.error("Invalid path", e);
|
|
}
|
|
}
|
|
|
|
private static String getClassName(
|
|
final String pkg,
|
|
final String className
|
|
) {
|
|
return pkg.isEmpty() ? className : pkg + "." + className;
|
|
}
|
|
|
|
public Runner args(final String className) {
|
|
final CommentRunner main = main(className);
|
|
// final AtomicReference<String> prev = new AtomicReference<>("");
|
|
return (counter, input) -> {
|
|
final int total =
|
|
input.stream().mapToInt(String::length).sum() +
|
|
input.size() * 3;
|
|
final String comment =
|
|
total <= 300
|
|
? input
|
|
.stream()
|
|
.collect(Collectors.joining("\" \"", "\"", "\""))
|
|
: input.size() <= 100
|
|
? String.format(
|
|
"[%d arguments, sizes: %s]",
|
|
input.size(),
|
|
input
|
|
.stream()
|
|
.mapToInt(String::length)
|
|
.mapToObj(String::valueOf)
|
|
.collect(Collectors.joining(", "))
|
|
)
|
|
: String.format(
|
|
"[%d arguments, total size: %d]",
|
|
input.size(),
|
|
total
|
|
);
|
|
// assert comment.length() <= 5 || !prev.get().equals(comment) : "Duplicate tests " + comment;
|
|
// prev.set(comment);
|
|
return main.run(comment, counter, input);
|
|
};
|
|
}
|
|
|
|
public Runner files(final String className) {
|
|
final Runner args = args(className);
|
|
return (counter, input) ->
|
|
counter.call("io", () -> {
|
|
final Path inf = counter.getFile("in");
|
|
final Path ouf = counter.getFile("out");
|
|
Files.write(inf, input);
|
|
args.run(counter, List.of(inf.toString(), ouf.toString()));
|
|
final List<String> output = Files.readAllLines(ouf);
|
|
Files.delete(inf);
|
|
Files.delete(ouf);
|
|
return output;
|
|
});
|
|
}
|
|
}
|
|
}
|