From 7784574810968797699e2b7c7f5603873e05bcc6 Mon Sep 17 00:00:00 2001 From: Trishun Date: Sun, 23 Oct 2022 11:57:46 +0200 Subject: [PATCH 1/2] Creates test environment --- pom.xml | 31 +++++ .../training/hackerrank/EnvironmentTest.java | 125 ++++++++++++++++++ .../training/hackerrank/HackerRankTest.java | 68 ++++++++++ .../training/hackerrank/ReflectionHelper.java | 40 ++++++ .../hackerrank/SolutionClassDescription.java | 19 +++ 5 files changed, 283 insertions(+) create mode 100644 pom.xml create mode 100644 src/test/java/eu/ztsh/training/hackerrank/EnvironmentTest.java create mode 100644 src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java create mode 100644 src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java create mode 100644 src/test/java/eu/ztsh/training/hackerrank/SolutionClassDescription.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3f1e4dc --- /dev/null +++ b/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + eu.ztsh.training + hackerrank + 1.0-SNAPSHOT + + + 17 + 17 + + + + + org.junit.jupiter + junit-jupiter + 5.9.0 + test + + + org.assertj + assertj-core + 3.23.1 + + + + \ No newline at end of file diff --git a/src/test/java/eu/ztsh/training/hackerrank/EnvironmentTest.java b/src/test/java/eu/ztsh/training/hackerrank/EnvironmentTest.java new file mode 100644 index 0000000..5fe0e22 --- /dev/null +++ b/src/test/java/eu/ztsh/training/hackerrank/EnvironmentTest.java @@ -0,0 +1,125 @@ +package eu.ztsh.training.hackerrank; + +import java.util.List; +import java.util.Scanner; +import eu.ztsh.training.hackerrank.SolutionClassDescription.FieldModifier; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EnvironmentTest { + + abstract static class HackerRankEnvironmentTest extends HackerRankTest { + + @Test + public void passTwoLinesTest() { + var input = List.of( + "Line 1", + "Line 2" + ); + var result = invoke(input); + assertThat(result).containsExactlyElementsOf(input); + } + + @Test + public void multiInvokeTest() { + var input = List.of( + "Line 1", + "Line 2" + ); + invoke(input); + invoke(input); + var result = invoke(input); + assertThat(result).containsExactlyElementsOf(input); + } + + @Test + public void passTwoAnotherLinesTest() { + var input = List.of( + "Line 3", + "Line 4" + ); + var result = invoke(input); + assertThat(result).containsExactlyElementsOf(input); + } + + } + + @Nested + @DisplayName("Test with Scanner created as private static field") + class EnvironmentPrivateStaticTest extends HackerRankEnvironmentTest { + + @Override + protected SolutionClassDescription getSolutionClassDescription() { + return new SolutionClassDescription(SampleSolutionWithPrivateStaticScanner.class, + "scan", + new FieldModifier[]{FieldModifier.PRIVATE, FieldModifier.STATIC}); + } + + } + + @Disabled("Disabled: run with --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED") + @Nested + @DisplayName("Test with Scanner created as private static final field") + class EnvironmentPrivateStaticFinalTest extends HackerRankEnvironmentTest { + + @Override + protected SolutionClassDescription getSolutionClassDescription() { + return new SolutionClassDescription(SampleSolutionWithPrivateStaticScanner.class, + "scan", + FieldModifier.values()); + } + + } + + @Nested + @DisplayName("Test with Scanner created in main(String[]) method") + class EnvironmentInlineTest extends HackerRankEnvironmentTest { + + @Override + protected SolutionClassDescription getSolutionClassDescription() { + return new SolutionClassDescription(SampleSolutionWithInlineScanner.class); + } + + } + +} + +class SampleSolutionWithPrivateStaticScanner { + + public static void main(String... args) { + while (scan.hasNext()) { + System.out.println(scan.nextLine()); + } + } + + @SuppressWarnings("FieldMayBeFinal") + private static Scanner scan = new Scanner(System.in); + +} + +class SampleSolutionWithPrivateStaticFinalScanner { + + public static void main(String... args) { + while (scan.hasNext()) { + System.out.println(scan.nextLine()); + } + } + + private static final Scanner scan = new Scanner(System.in); + +} + +class SampleSolutionWithInlineScanner { + + public static void main(String... args) { + var scan = new Scanner(System.in); + while (scan.hasNext()) { + System.out.println(scan.nextLine()); + } + } + +} diff --git a/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java b/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java new file mode 100644 index 0000000..512566c --- /dev/null +++ b/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java @@ -0,0 +1,68 @@ +package eu.ztsh.training.hackerrank; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +public abstract class HackerRankTest { + + @BeforeAll + public static void setUpStreams() { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + } + + @AfterAll + public static void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + System.setIn(originalIn); + } + + protected List invoke(List input) { + try { + outContent.reset(); + writeLines(input); + if (getSolutionClassDescription().fieldName() != null) { + ReflectionHelper.reloadScanner(getSolutionClassDescription()); + } + getSolutionClassDescription().targetClass() + .getMethod("main", String[].class) + .invoke(null, (Object) new String[]{}); + return readLines(); + } catch (final NoSuchMethodException | InvocationTargetException | IllegalAccessException | + NoSuchFieldException e) { + throw new IllegalStateException(e); + } + } + + protected abstract SolutionClassDescription getSolutionClassDescription(); + + private void writeLines(List lines) { + // https://stackoverflow.com/a/31635737 + System.setIn(new ByteArrayInputStream((String.join(System.lineSeparator(), lines) + System.lineSeparator()).getBytes())); + } + + private List readLines() { + // https://stackoverflow.com/a/1119559 + return List.copyOf(Arrays.stream(outContent.toString().split(System.lineSeparator())).toList()); + } + + private final static ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final static ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private final static PrintStream originalOut = System.out; + private final static PrintStream originalErr = System.err; + private final static InputStream originalIn = System.in; + +} diff --git a/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java b/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java new file mode 100644 index 0000000..a32f901 --- /dev/null +++ b/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java @@ -0,0 +1,40 @@ +package eu.ztsh.training.hackerrank; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Scanner; +import eu.ztsh.training.hackerrank.SolutionClassDescription.FieldModifier; + + +public class ReflectionHelper { + + static { + try { + var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup()); + MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class); + CAN_MODIFY_FINALS = true; + } catch (IllegalAccessException | NoSuchFieldException ex) { + CAN_MODIFY_FINALS = false; + } + } + + static void reloadScanner(SolutionClassDescription description) throws NoSuchFieldException, IllegalAccessException { + // https://stackoverflow.com/a/3301720 + var scannerField = description.hasModifier(FieldModifier.STATIC) + ? description.targetClass().getDeclaredField(description.fieldName()) + : description.targetClass().getField(description.fieldName()); + if (description.hasModifier(FieldModifier.PRIVATE)) { + scannerField.setAccessible(true); + } + if (description.hasModifier(FieldModifier.FINAL) && CAN_MODIFY_FINALS) { + MODIFIERS.set(scannerField, scannerField.getModifiers() & ~Modifier.FINAL); + } + scannerField.set(null, new Scanner(System.in)); + } + + private static VarHandle MODIFIERS; + private static boolean CAN_MODIFY_FINALS; + +} diff --git a/src/test/java/eu/ztsh/training/hackerrank/SolutionClassDescription.java b/src/test/java/eu/ztsh/training/hackerrank/SolutionClassDescription.java new file mode 100644 index 0000000..b141262 --- /dev/null +++ b/src/test/java/eu/ztsh/training/hackerrank/SolutionClassDescription.java @@ -0,0 +1,19 @@ +package eu.ztsh.training.hackerrank; + +import java.util.Arrays; + +public record SolutionClassDescription(Class targetClass, String fieldName, FieldModifier[] modifiers) { + + SolutionClassDescription(Class targetClass) { + this(targetClass, null, null); + } + + public boolean hasModifier(FieldModifier modifier) { + return Arrays.asList(modifiers).contains(modifier); + } + + public enum FieldModifier { + PRIVATE, FINAL, STATIC + } + +} From b863add55a6224b1eec3331906d4436356e814ff Mon Sep 17 00:00:00 2001 From: Trishun Date: Sun, 23 Oct 2022 22:01:33 +0200 Subject: [PATCH 2/2] Provides comment and some code cleanup --- .../java/eu/ztsh/training/hackerrank/HackerRankTest.java | 5 ----- .../java/eu/ztsh/training/hackerrank/ReflectionHelper.java | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java b/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java index 512566c..cf59987 100644 --- a/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java +++ b/src/test/java/eu/ztsh/training/hackerrank/HackerRankTest.java @@ -4,14 +4,9 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.PrintStream; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; -import java.util.Scanner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; diff --git a/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java b/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java index a32f901..498becf 100644 --- a/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java +++ b/src/test/java/eu/ztsh/training/hackerrank/ReflectionHelper.java @@ -10,6 +10,7 @@ import eu.ztsh.training.hackerrank.SolutionClassDescription.FieldModifier; public class ReflectionHelper { + // https://stackoverflow.com/a/56043252 static { try { var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());