import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

class Should {

    static void beEqual(Object expected, Object given) {
        if (!expected.equals(given)) {
            throw new RuntimeException("equality failed. expected " + expected + ", but given " + given);
        }
    }
}

// ----------------------------------------------------------------

class A {

    String description(int x) {
        return getClass().getSimpleName();
    }
}

class ASpec<T extends A> {

    final void runAll(Collection<Supplier<? extends T>> implementations) {
        implementations.forEach(this::run);
    }

    void run(Supplier<? extends T> a) {
        should_be_A_object_description_when_x_is_42(a.get());
    }

    String descriptionOf(A a) {
        return a.getClass().getSimpleName();
    }

    void should_be_A_object_description_when_x_is_42(A a) {
        final var expected = descriptionOf(a);
        Should.beEqual(expected, a.description(42));
    }
}

class B extends A {

    String description() {
        return "B";
    }
}

class BSpec<T extends B> extends ASpec<B> {

    void run(Supplier<? extends B> b) {
        super.run(b);
        should_be_B_object_description(b.get());
    }

    String bDescriptionOf(B b) {
        return "B";
    }

    void should_be_B_object_description(B b) {
        final var expected = bDescriptionOf(b);
        Should.beEqual(expected, b.description());
    }
}

final class Ideone {

    public static void main(String[] args) {
        new ASpec<>().runAll(List.of(A::new, B::new));
        new BSpec<>().runAll(List.of(B::new));
    }
}