1

I am Currently writing Junit tests for automated testing of student tasks. The topic is console input in java. I have multiple tests that need to take simulated user input and check for correct behaviour of tested methods (method is working fine).

My problem at the moment: the ByteArrayInputStream set as System.in won't release when needed but one test later (see pic below).

I tried to reset the stream, make it static, make it non-static, have standard System.in set before and after, tried read method, switched java-versions (from SE13 to 1.8). All attempts at different times, ofc.

enter image description here

Explanation of output in the picture:

It's from our automated JUnit-Framework. Those are two tests reliant on the tested method that takes a user input. The "?" at the start of a line indicate the tested method is active and waiting. First two words after the STARTING TEST line are what is supposed to get injected.

JUnit test code:

final  static InputStream STDIN = System.in;
static ByteArrayInputStream bais;
final static String[] INJECTIONS = { "abc", "def", "ghi", "jkl", "mmmm", "klopp", "urgl", "gurle", "ding", "dong" };

private static void inject(String injection, Object o, MethodSignature ms) {
    System.out.println(injection);

    try {
        ByteArrayInputStream bais = new ByteArrayInputStream(injection.getBytes("UTF-8"));

        System.setIn(bais);
        System.out.println("Injection Bytes: "+injection.getBytes().length);
        System.out.println("Available for injection: " + System.in.available());

        ms.getMethod().invoke(o);

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        System.setIn(STDIN);
    }
}

private static String getStringInject(String injection, Object o, MethodSignature ms) {
    String out = null;
    System.out.println(injection);
    try {

        ByteArrayInputStream bais = new ByteArrayInputStream(injection.getBytes("UTF-8"));

        System.setIn(bais);
        System.out.println("Injection Bytes: "+injection.getBytes().length);
        System.out.println("Available for injection: " + System.in.available());
        System.in.reset();
        out = (String) ms.getMethod().invoke(o);

    } catch (Exception e) {
        e.printStackTrace();
    }
    finally {
        System.setIn(STDIN);
    }
    return out;
}

@Test
public void checkReadFromConsoleCorrectReturnTest() {

    Object o = createObject(csMain.getC());
    if (o == null) {
        addFail(csMain.getObjectFail());
        return;
    }
    String injection = INJECTIONS[ThreadLocalRandom.current().nextInt(0, INJECTIONS.length)];

    String output = getStringInject(injection + System.lineSeparator() + "END" + System.lineSeparator(), o,
            msReadFromConsole);

    if (output == null || output.isEmpty()) {
        addFail(msReadFromConsole.getMethodNoReturnString());
        return;
    }

}

@Test
public void checkReadCorrectEndingTriggerTest() {

    Object o = createObject(csMain.getC());
    if (o == null) {
        addFail(csMain.getObjectFail());
        return;
    }
    String injection = INJECTIONS[ThreadLocalRandom.current().nextInt(0, INJECTIONS.length)]
            + System.lineSeparator() + "END" + System.lineSeparator();


    inject(injection, o, msRead);
}

Tested java methods:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class ConsoleReader implements IConsoleReader {

    ArrayList<String> reads;
    BufferedReader br;

    public ConsoleReader() {
        reads = new ArrayList<String>();
        br = new BufferedReader(new InputStreamReader(System.in));
    }

    public void read() {
        String input = readFromConsole();
        while (true) {
            if (input == null || input.isEmpty()) {
                System.out.println("ERROR: null");
                return;
            }
            input = input.trim().toUpperCase();

            if (input.equals("END")) {
                System.out.println("Done.");
                return;
            }

            reads.add(input);
            input = readFromConsole();
        }
    }

    public String readFromConsole() {
        System.out.print("? ");
        String out = null;
        try {
            out = br.readLine();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        System.out.println(out);
        return out;
    }
}

Help is much appreciated. Can't change the Junit library used or add other external libraries atm.

TreffnonX
  • 2,924
  • 15
  • 23
MeepMania
  • 103
  • 2
  • 13
  • I have a (probably dumb) counter question: Why are you building a framework around JUnit 4 that covers available JUnit 5 features? – TreffnonX May 06 '20 at 09:09
  • We use an automated testing environment not developed by us. They provide Junit 3 and 4. It's free and it allows for good student interaction. – MeepMania May 06 '20 at 09:20
  • Would you attempt to use `ByteArrayInputStream` as an `AutoCloseable` in the try blocks? It should be closed or flushed. I am not sure this is enough to explain your issue, but I think it might be: `try (ByteArrayInputStream bais = new ByteArrayInputStream(injection.getBytes("UTF-8"))) { ...` – TreffnonX May 06 '20 at 10:12
  • Hi, did you manage to fix this? –  Sep 24 '21 at 07:34

1 Answers1

0

I wrote the following snippet for you to run:

@Test
public void test_byteArrayInputStream() {
    InputStream STDIN = System.in;
    String message = "test" + System.lineSeparator();
    try (ByteArrayInputStream in = new ByteArrayInputStream(message.getBytes("UTF-8"))) {
        System.setIn(in);

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        assertEquals("test", br.readLine()); // this succeeds
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        System.setIn(STDIN);
    }
}

As you can see, the BufferedReader correctly ready "test" from the in stream. I used the AutoClosable aspect of ByteArrayInputStream to close it after the code block.

Since this does not solve your problem, I am uncertain whether the problem does not originate in the surrounding framework. More specifically, the only explanation I can think of for the leap over of values from Testcase 1 to Testcase 2 is, that the object is not reinstantiated, but reused somehow. Since this code is missing, it cannot be said for sure.

TreffnonX
  • 2,924
  • 15
  • 23
  • Try-with-ressources doesn't solve the problem which is "ms.getMethod().invoke(o);" needs the input from System.in but it doesn't get delivered. It holds onto its data, until the next ByteArrayInputStream is created, which is odd to say the least. If only there was some kind of flush() because close() doesn't affect the stream at all. – MeepMania May 06 '20 at 11:17
  • Is the object `o` actually the `ConsoleReader`? And if so, is it *a different `ConsoleReader` in Test2 than it is in Test1? It appears this is the intent, but can you make sure? The `ConsoleReader` object instance should not survive from one test to another (obviously) but since you use a completely separate `BufferedArrayInputStream`, there should not be leap over... – TreffnonX May 06 '20 at 11:25
  • No, o is an object of the tested class. Tried also shoving the creation of the BufferedArrayInputStream into the tests - same effect. Frustrating but thank you for trying to help. – MeepMania May 06 '20 at 14:45