initial commit

This commit is contained in:
2026-01-29 23:20:12 +05:00
parent fb1ce36970
commit 67357cf271
76 changed files with 7115 additions and 0 deletions

84
java/base/Asserts.java Normal file
View File

@@ -0,0 +1,84 @@
package base;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public final class Asserts {
static {
Locale.setDefault(Locale.US);
}
private Asserts() {
}
public static void assertEquals(final String message, final Object expected, final Object actual) {
final String reason = String.format("%s:%n expected `%s`,%n actual `%s`",
message, toString(expected), toString(actual));
assertTrue(reason, Objects.deepEquals(expected, actual));
}
public static String toString(final Object value) {
if (value != null && value.getClass().isArray()) {
final String result = Arrays.deepToString(new Object[]{value});
return result.substring(1, result.length() - 1);
} else {
return Objects.toString(value);
}
}
public static <T> void assertEquals(final String message, final List<T> expected, final List<T> actual) {
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
assertEquals(message + ":" + (i + 1), expected.get(i), actual.get(i));
}
assertEquals(message + ": Number of items", expected.size(), actual.size());
}
public static void assertTrue(final String message, final boolean value) {
if (!value) {
throw error("%s", message);
}
}
public static void assertEquals(final String message, final double expected, final double actual, final double precision) {
assertTrue(
String.format("%s: Expected %.12f, found %.12f", message, expected, actual),
isEqual(expected, actual, precision)
);
}
public static boolean isEqual(final double expected, final double actual, final double precision) {
final double error = Math.abs(actual - expected);
return error <= precision
|| error <= precision * Math.abs(expected)
|| !Double.isFinite(expected)
|| Math.abs(expected) > 1e100
|| Math.abs(expected) < precision && !Double.isFinite(actual);
}
public static void assertSame(final String message, final Object expected, final Object actual) {
assertTrue(String.format("%s: expected same objects: %s and %s", message, expected, actual), expected == actual);
}
public static void checkAssert(final Class<?> c) {
if (!c.desiredAssertionStatus()) {
throw error("You should enable assertions by running 'java -ea %s'", c.getName());
}
}
public static AssertionError error(final String format, final Object... args) {
final String message = String.format(format, args);
return args.length > 0 && args[args.length - 1] instanceof Throwable
? new AssertionError(message, (Throwable) args[args.length - 1])
: new AssertionError(message);
}
public static void printStackTrace(final String message) {
new Exception(message).printStackTrace(System.out);
}
}

View File

@@ -0,0 +1,20 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public abstract class BaseChecker {
protected final TestCounter counter;
protected BaseChecker(final TestCounter counter) {
this.counter = counter;
}
public ExtendedRandom random() {
return counter.random();
}
public int mode() {
return counter.mode();
}
}

95
java/base/Either.java Normal file
View File

@@ -0,0 +1,95 @@
package base;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface Either<L, R> {
<NR> Either<L, NR> mapRight(final Function<? super R, NR> f);
<NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f);
<T> T either(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
boolean isRight();
L getLeft();
R getRight();
static <L, R> Either<L, R> right(final R value) {
return new Either<>() {
@Override
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
return right(f.apply(value));
}
@Override
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
return f.apply(value);
}
@Override
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
return rf.apply(value);
}
@Override
public boolean isRight() {
return true;
}
@Override
public L getLeft() {
return null;
}
@Override
public R getRight() {
return value;
}
@Override
public String toString() {
return String.format("Right(%s)", value);
}
};
}
static <L, R> Either<L, R> left(final L value) {
return new Either<>() {
@Override
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
return left(value);
}
@Override
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
return left(value);
}
@Override
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
return lf.apply(value);
}
@Override
public boolean isRight() {
return false;
}
@Override
public L getLeft() {
return value;
}
@Override
public R getRight() {
return null;
}
@Override
public String toString() {
return String.format("Left(%s)", value);
}
};
}
}

View File

@@ -0,0 +1,89 @@
package base;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExtendedRandom {
public static final String ENGLISH = "abcdefghijklmnopqrstuvwxyz";
public static final String RUSSIAN = "абвгдеежзийклмнопрстуфхцчшщъыьэюя";
public static final String GREEK = "αβγŋδεζηθικλμνξοπρτυφχψω";
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public static final String SPACES = " \t\n\u000B\u2029\f";
private final Random random;
public ExtendedRandom(final Random random) {
this.random = random;
}
public ExtendedRandom(final Class<?> owner) {
this(new Random(7912736473497634913L + owner.getName().hashCode()));
}
public String randomString(final String chars) {
return randomChar(chars) + (random.nextBoolean() ? "" : randomString(chars));
}
public char randomChar(final String chars) {
return chars.charAt(nextInt(chars.length()));
}
public String randomString(final String chars, final int length) {
final StringBuilder string = new StringBuilder();
for (int i = 0; i < length; i++) {
string.append(randomChar(chars));
}
return string.toString();
}
public String randomString(final String chars, final int minLength, final int maxLength) {
return randomString(chars, nextInt(minLength, maxLength));
}
public boolean nextBoolean() {
return random.nextBoolean();
}
public int nextInt() {
return random.nextInt();
}
public int nextInt(final int min, final int max) {
return nextInt(max - min + 1) + min;
}
public int nextInt(final int n) {
return random.nextInt(n);
}
@SafeVarargs
public final <T> T randomItem(final T... items) {
return items[nextInt(items.length)];
}
public <T> T randomItem(final List<T> items) {
return items.get(nextInt(items.size()));
}
public Random getRandom() {
return random;
}
public <T> List<T> random(final int list, final Function<ExtendedRandom, T> generator) {
return Stream.generate(() -> generator.apply(this)).limit(list).toList();
}
public double nextDouble() {
return random.nextDouble();
}
public <E> void shuffle(final List<E> all) {
Collections.shuffle(all, random);
}
}

92
java/base/Functional.java Normal file
View File

@@ -0,0 +1,92 @@
package base;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Functional {
private Functional() {}
public static <T, R> List<R> map(final Collection<T> items, final Function<? super T, ? extends R> f) {
return items.stream().map(f).collect(Collectors.toUnmodifiableList());
}
public static <T, R> List<R> map(final List<T> items, final BiFunction<? super Integer, ? super T, ? extends R> f) {
return IntStream.range(0, items.size())
.mapToObj(i -> f.apply(i, items.get(i)))
.collect(Collectors.toUnmodifiableList());
}
public static <K, T, R> Map<K, R> mapValues(final Map<K, T> map, final Function<T, R> f) {
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue())));
}
@SafeVarargs
public static <K, T> Map<K, T> mergeMaps(final Map<K, T>... maps) {
return Stream.of(maps).flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
}
@SafeVarargs
public static <T> List<T> concat(final Collection<? extends T>... items) {
final List<T> result = new ArrayList<>();
for (final Collection<? extends T> item : items) {
result.addAll(item);
}
return result;
}
public static <T> List<T> append(final Collection<T> collection, final T item) {
final List<T> list = new ArrayList<>(collection);
list.add(item);
return list;
}
public static <T> List<List<T>> allValues(final List<T> vals, final int length) {
return Stream.generate(() -> vals)
.limit(length)
.reduce(
List.of(List.of()),
(prev, next) -> next.stream()
.flatMap(value -> prev.stream().map(list -> append(list, value)))
.toList(),
(prev, next) -> next.stream()
.flatMap(suffix -> prev.stream().map(prefix -> concat(prefix, suffix)))
.toList()
);
}
public static <K, V> V get(final Map<K, V> map, final K key) {
final V result = map.get(key);
if (result == null) {
throw new NullPointerException(key.toString() + " in " + map(map.keySet(), Objects::toString));
}
return result;
}
public static void addRange(final List<Integer> values, final int d, final int c) {
for (int i = -d; i <= d; i++) {
values.add(c + i);
}
}
public static <T> void forEachPair(final T[] items, final BiConsumer<? super T, ? super T> consumer) {
assert items.length % 2 == 0;
IntStream.range(0, items.length / 2).forEach(i -> consumer.accept(items[i * 2], items[i * 2 + 1]));
}
public static <T> List<Pair<T, T>> toPairs(final T[] items) {
assert items.length % 2 == 0;
return IntStream.range(0, items.length / 2)
.mapToObj(i -> Pair.of(items[i * 2], items[i * 2 + 1]))
.toList();
}
}

56
java/base/Log.java Normal file
View File

@@ -0,0 +1,56 @@
package base;
import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class Log {
private final Pattern LINES = Pattern.compile("\n");
private int indent;
public static Supplier<Void> action(final Runnable action) {
return () -> {
action.run();
return null;
};
}
public void scope(final String name, final Runnable action) {
scope(name, action(action));
}
public <T> T scope(final String name, final Supplier<T> action) {
println(name);
indent++;
try {
return silentScope(name, action);
} finally {
indent--;
}
}
public <T> T silentScope(final String ignoredName, final Supplier<T> action) {
return action.get();
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public void println(final Object value) {
for (final String line : LINES.split(String.valueOf(value))) {
System.out.println(indent() + line);
}
}
public void format(final String format, final Object... args) {
println(String.format(format,args));
}
private String indent() {
return " ".repeat(indent);
}
protected int getIndent() {
return indent;
}
}

View File

@@ -0,0 +1,28 @@
package base;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public final class MainChecker {
private final Runner runner;
public MainChecker(final Runner runner) {
this.runner = runner;
}
public List<String> run(final TestCounter counter, final String... input) {
return runner.run(counter, input);
}
public List<String> run(final TestCounter counter, final List<String> input) {
return runner.run(counter, input);
}
public void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
runner.testEquals(counter, input, expected);
}
}

15
java/base/Named.java Normal file
View File

@@ -0,0 +1,15 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record Named<T>(String name, T value) {
public static <T> Named<T> of(final String name, final T f) {
return new Named<>(name, f);
}
@Override
public String toString() {
return name;
}
}

44
java/base/Pair.java Normal file
View File

@@ -0,0 +1,44 @@
package base;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused"})
public record Pair<F, S>(F first, S second) {
public static <F, S> Pair<F, S> of(final F first, final S second) {
return new Pair<>(first, second);
}
public static <F, S> Pair<F, S> of(final Map.Entry<F, S> e) {
return of(e.getKey(), e.getValue());
}
public static <F, S> UnaryOperator<Pair<F, S>> lift(final UnaryOperator<F> f, final UnaryOperator<S> s) {
return p -> of(f.apply(p.first), s.apply(p.second));
}
public static <F, S> BinaryOperator<Pair<F, S>> lift(final BinaryOperator<F> f, final BinaryOperator<S> s) {
return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second));
}
public static <T, F, S> Function<T, Pair<F, S>> tee(
final Function<? super T, ? extends F> f,
final Function<? super T, ? extends S> s
) {
return t -> of(f.apply(t), s.apply(t));
}
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
public <R> Pair<F, R> second(final R second) {
return new Pair<>(first, second);
}
}

185
java/base/Runner.java Normal file
View File

@@ -0,0 +1,185 @@
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;
});
}
}
}

143
java/base/Selector.java Normal file
View File

@@ -0,0 +1,143 @@
package base;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Selector {
private final Class<?> owner;
private final List<String> modes;
private final Set<String> variantNames = new LinkedHashSet<>();
private final Map<String, Consumer<TestCounter>> variants = new LinkedHashMap<>();
public Selector(final Class<?> owner, final String... modes) {
this.owner = owner;
this.modes = List.of(modes);
}
public Selector variant(final String name, final Consumer<TestCounter> operations) {
Asserts.assertTrue("Duplicate variant " + name, variants.put(name.toLowerCase(), operations) == null);
variantNames.add(name);
return this;
}
private static void check(final boolean condition, final String format, final Object... args) {
if (!condition) {
throw new IllegalArgumentException(String.format(format, args));
}
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public void main(final String... args) {
try {
final String mode;
if (modes.isEmpty()) {
check(args.length >= 1, "At least one argument expected, found %s", args.length);
mode = "";
} else {
check(args.length >= 2, "At least two arguments expected, found %s", args.length);
mode = args[0];
}
final List<String> vars = Arrays.stream(args).skip(modes.isEmpty() ? 0 : 1)
.flatMap(arg -> Arrays.stream(arg.split("[ +]+")))
.toList();
test(mode, vars);
} catch (final IllegalArgumentException e) {
System.err.println("ERROR: " + e.getMessage());
if (modes.isEmpty()) {
System.err.println("Usage: " + owner.getName() + " VARIANT...");
} else {
System.err.println("Usage: " + owner.getName() + " MODE VARIANT...");
System.err.println("Modes: " + String.join(", ", modes));
}
System.err.println("Variants: " + String.join(", ", variantNames));
System.exit(1);
}
}
public void test(final String mode, List<String> vars) {
final int modeNo = modes.isEmpty() ? -1 : modes.indexOf(mode) ;
check(modes.isEmpty() || modeNo >= 0, "Unknown mode '%s'", mode);
if (variantNames.contains("Base") && !vars.contains("Base")) {
vars = new ArrayList<>(vars);
vars.add(0, "Base");
}
vars.forEach(variant -> check(variants.containsKey(variant.toLowerCase()), "Unknown variant '%s'", variant));
final Map<String, String> properties = modes.isEmpty()
? Map.of("variant", String.join("+", vars))
: Map.of("variant", String.join("+", vars), "mode", mode);
final TestCounter counter = new TestCounter(owner, modeNo, properties);
counter.printHead();
vars.forEach(variant -> counter.scope("Testing " + variant, () -> variants.get(variant.toLowerCase()).accept(counter)));
counter.printStatus();
}
public static <V extends Tester> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final String... modes) {
return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes);
}
public static <V> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
return new Composite<>(owner, factory, tester, modes);
}
public List<String> getModes() {
return modes.isEmpty() ? List.of("~") : modes;
}
public List<String> getVariants() {
return List.copyOf(variants.keySet());
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public static final class Composite<V> {
private final Selector selector;
private final Function<TestCounter, V> factory;
private final BiConsumer<V, TestCounter> tester;
private List<Consumer<? super V>> base;
private Composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
selector = new Selector(owner, modes);
this.factory = factory;
this.tester = tester;
}
@SafeVarargs
public final Composite<V> variant(final String name, final Consumer<? super V>... parts) {
if ("Base".equalsIgnoreCase(name)) {
base = List.of(parts);
return v(name.toLowerCase());
} else {
return v(name, parts);
}
}
@SafeVarargs
private Composite<V> v(final String name, final Consumer<? super V>... parts) {
selector.variant(name, counter -> {
final V variant = factory.apply(counter);
for (final Consumer<? super V> part : base) {
part.accept(variant);
}
for (final Consumer<? super V> part : parts) {
part.accept(variant);
}
tester.accept(variant, counter);
});
return this;
}
public Selector selector() {
return selector;
}
}
}

184
java/base/TestCounter.java Normal file
View File

@@ -0,0 +1,184 @@
package base;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class TestCounter extends Log {
public static final int DENOMINATOR = Integer.getInteger("base.denominator", 1);
public static final int DENOMINATOR2 = (int) Math.round(Math.sqrt(DENOMINATOR));
private static final String JAR_EXT = ".jar";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
private final Class<?> owner;
private final int mode;
private final Map<String, ?> properties;
private final ExtendedRandom random;
private final long start = System.currentTimeMillis();
private int passed;
public TestCounter(final Class<?> owner, final int mode, final Map<String, ?> properties) {
Locale.setDefault(Locale.US);
Asserts.checkAssert(getClass());
this.owner = owner;
this.mode = mode;
this.properties = properties;
random = new ExtendedRandom(owner);
}
public int mode() {
return mode;
}
public int getTestNo() {
return passed + 1;
}
public void test(final Runnable action) {
testV(() -> {
action.run();
return null;
});
}
public <T> void testForEach(final Iterable<? extends T> items, final Consumer<? super T> action) {
for (final T item : items) {
test(() -> action.accept(item));
}
}
public <T> T testV(final Supplier<T> action) {
return silentScope("Test " + getTestNo(), () -> {
final T result = action.get();
passed++;
return result;
});
}
private String getLine() {
return getIndent() == 0 ? "=" : "-";
}
public void printHead() {
println("=== " + getTitle());
}
public void printStatus() {
format("%s%n%s%n", getLine().repeat(30), getTitle());
format("%d tests passed in %dms%n", passed, System.currentTimeMillis() - start);
println("Version: " + getVersion(owner));
println("");
}
private String getTitle() {
return String.format("%s %s", owner.getSimpleName(), properties.isEmpty() ? "" : properties);
}
private static String getVersion(final Class<?> clazz) {
try {
final ClassLoader cl = clazz.getClassLoader();
final URL url = cl.getResource(clazz.getName().replace('.', '/') + ".class");
if (url == null) {
return "(no manifest)";
}
final String path = url.getPath();
final int index = path.indexOf(JAR_EXT);
if (index == -1) {
return DATE_FORMAT.format(new Date(new File(path).lastModified()));
}
final String jarPath = path.substring(0, index + JAR_EXT.length());
try (final JarFile jarFile = new JarFile(new File(new URI(jarPath)))) {
final JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
return DATE_FORMAT.format(new Date(entry.getTime()));
}
} catch (final IOException | URISyntaxException e) {
return "error: " + e;
}
}
public <T> T call(final String message, final SupplierE<T> supplier) {
return get(supplier).either(e -> fail(e, "%s", message), Function.identity());
}
public void shouldFail(final String message, @SuppressWarnings("TypeMayBeWeakened") final RunnableE action) {
test(() -> get(action).either(e -> null, v -> fail("%s", message)));
}
public <T> T fail(final String format, final Object... args) {
return fail(Asserts.error(format, args));
}
public <T> T fail(final Throwable throwable) {
return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage());
}
public <T> T fail(final Throwable throwable, final String format, final Object... args) {
final String message = String.format(format, args);
println("ERROR: " + message);
throw throwable instanceof Error ? (Error) throwable : new AssertionError(throwable);
}
public void checkTrue(final boolean condition, final String message, final Object... args) {
if (!condition) {
fail(message, args);
}
}
public static <T> Either<Exception, T> get(final SupplierE<T> supplier) {
return supplier.get();
}
public Path getFile(final String suffix) {
return Paths.get(String.format("test%d.%s", getTestNo(), suffix));
}
public ExtendedRandom random() {
return random;
}
@FunctionalInterface
public interface SupplierE<T> extends Supplier<Either<Exception, T>> {
T getE() throws Exception;
@Override
default Either<Exception, T> get() {
try {
return Either.right(getE());
} catch (final Exception e) {
return Either.left(e);
}
}
}
@FunctionalInterface
public interface RunnableE extends SupplierE<Void> {
void run() throws Exception;
@Override
default Void getE() throws Exception {
run();
return null;
}
}
}

18
java/base/Tester.java Normal file
View File

@@ -0,0 +1,18 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public abstract class Tester extends BaseChecker {
protected Tester(final TestCounter counter) {
super(counter);
}
public abstract void test();
public void run(final Class<?> test, final String... args) {
System.out.println("=== Testing " + test.getSimpleName() + " " + String.join(" ", args));
test();
counter.printStatus();
}
}

15
java/base/Unit.java Normal file
View File

@@ -0,0 +1,15 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Unit {
public static final Unit INSTANCE = new Unit();
private Unit() { }
@Override
public String toString() {
return "unit";
}
}

View File

@@ -0,0 +1,7 @@
/**
* Common homeworks test classes
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package base;