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 run(final TestCounter counter, final List input); default List run(final TestCounter counter, final String... input) { return run(counter, List.of(input)); } default void testEquals(final TestCounter counter, final List input, final List expected) { counter.test(() -> { final List 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 run(String comment, TestCounter counter, List input); } final class Packages { private final List packages; private Packages(final List 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 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 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 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 output = Files.readAllLines(ouf); Files.delete(inf); Files.delete(ouf); return output; }); } } }