// Path: src/test/java/org/terasology/launcher/StringIteratorInputStream.java
// public class StringIteratorInputStream extends InputStream {
//     static final int COOLDOWN_LENGTH = 1;
// 
//     int cooldown;
//     private final Iterator<String> source;
//     private byte[] currentLine;
//     private int byteIndex;
// 
//     public StringIteratorInputStream(Iterator<String> source) {
//         this.source = source;
//         resetCooldown();
//     }
// 
//     @Override
//     public int available() {
//         if (currentLine == null) {
//             // Playing hard-to-get with StreamDecoder.read. If it finishes a read
//             // and still has room in its buffer, it'll check if we're ready again
//             // right away. When we say we still aren't ready yet, it backs off.
//             if (cooldown > 0) {
//                 cooldown -= 1;
//                 return 0;
//             }
//             if (!loadNextLine()) {
//                 // there's no more to be had. for real this time!
//                 return 0;
//             }
//         }
//         return availableInCurrentLine();
//     }
// 
//     @Override
//     public int read() {
//         if (currentLine == null) {
//             var gotNext = loadNextLine();
//             if (!gotNext) {
//                 return -1;
//             }
//         }
//         var c = currentLine[byteIndex];
//         byteIndex++;
//         if (byteIndex >= currentLine.length) {
//             currentLine = null;
//             resetCooldown();
//         }
//         return c;
//     }
// 
//     @Override
//     public int read(final byte[] b, final int off, final int len) throws IOException {
//         if (len == 0) {
//             return 0;
//         }
//         // Even if available() says we're empty, our superclass wants us to try
//         // to come up with at least one byte, blocking if necessary. Otherwise
//         // StreamDecoder.readBytes says "Underlying input stream returned zero bytes"
//         // and implodes.
//         @SuppressWarnings("UnstableApiUsage") var availableLength =
//                 Ints.constrainToRange(availableInCurrentLine(), 1, len);
//         return super.read(b, off, availableLength);
//     }
// 
//     protected int availableInCurrentLine() {
//         if (currentLine == null) {
//             return 0;
//         } else {
//             return currentLine.length - byteIndex;
//         }
//     }
// 
//     private void resetCooldown() {
//         cooldown = COOLDOWN_LENGTH;
//     }
// 
//     /**
//      * @return true when it succeeds in getting the next line, false when the input source has no more
//      */
//     private boolean loadNextLine() {
//         try {
//             final String nextString = source.next();
//             currentLine = nextString.getBytes();
//         } catch (NoSuchElementException e) {
//             return false;
//         } finally {
//             byteIndex = 0;
//         }
//         return true;
//     }
// }


import org.apache.commons.io.input.NullInputStream;
import org.terasology.launcher.StringIteratorInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.Callable;
            if (this.pid == 0) {
                this.pid = new Random().nextLong();
            }
            return this.pid;
        }

        @Override
        public int exitValue() {
            return 0;
        }

        @Override
        public void destroy() {
            throw new UnsupportedOperationException();
        }
    }

    static class OneLineAtATimeProcess extends HappyGameProcess {
        private final Iterator<String> lines;

        OneLineAtATimeProcess(Iterator<String> lines) {
            this.lines = lines;
        }

        @Override
        public InputStream getInputStream() {
            // 🤔 If RunGameTask had a way to do
            //        Iterator<String> lines = adapt(process)
            //    and we could provide a different adapter for our mock process than a system
            //    process, we could probably do without StringIteratorInputStream. ?
// Path: src/test/java/org/terasology/launcher/StringIteratorInputStream.java
// public class StringIteratorInputStream extends InputStream {
//     static final int COOLDOWN_LENGTH = 1;
// 
//     int cooldown;
//     private final Iterator<String> source;
//     private byte[] currentLine;
//     private int byteIndex;
// 
//     public StringIteratorInputStream(Iterator<String> source) {
//         this.source = source;
//         resetCooldown();
//     }
// 
//     @Override
//     public int available() {
//         if (currentLine == null) {
//             // Playing hard-to-get with StreamDecoder.read. If it finishes a read
//             // and still has room in its buffer, it'll check if we're ready again
//             // right away. When we say we still aren't ready yet, it backs off.
//             if (cooldown > 0) {
//                 cooldown -= 1;
//                 return 0;
//             }
//             if (!loadNextLine()) {
//                 // there's no more to be had. for real this time!
//                 return 0;
//             }
//         }
//         return availableInCurrentLine();
//     }
// 
//     @Override
//     public int read() {
//         if (currentLine == null) {
//             var gotNext = loadNextLine();
//             if (!gotNext) {
//                 return -1;
//             }
//         }
//         var c = currentLine[byteIndex];
//         byteIndex++;
//         if (byteIndex >= currentLine.length) {
//             currentLine = null;
//             resetCooldown();
//         }
//         return c;
//     }
// 
//     @Override
//     public int read(final byte[] b, final int off, final int len) throws IOException {
//         if (len == 0) {
//             return 0;
//         }
//         // Even if available() says we're empty, our superclass wants us to try
//         // to come up with at least one byte, blocking if necessary. Otherwise
//         // StreamDecoder.readBytes says "Underlying input stream returned zero bytes"
//         // and implodes.
//         @SuppressWarnings("UnstableApiUsage") var availableLength =
//                 Ints.constrainToRange(availableInCurrentLine(), 1, len);
//         return super.read(b, off, availableLength);
//     }
// 
//     protected int availableInCurrentLine() {
//         if (currentLine == null) {
//             return 0;
//         } else {
//             return currentLine.length - byteIndex;
//         }
//     }
// 
//     private void resetCooldown() {
//         cooldown = COOLDOWN_LENGTH;
//     }
// 
//     /**
//      * @return true when it succeeds in getting the next line, false when the input source has no more
//      */
//     private boolean loadNextLine() {
//         try {
//             final String nextString = source.next();
//             currentLine = nextString.getBytes();
//         } catch (NoSuchElementException e) {
//             return false;
//         } finally {
//             byteIndex = 0;
//         }
//         return true;
//     }
// }


// Path: src/test/java/org/terasology/launcher/game/MockProcesses.java
import org.apache.commons.io.input.NullInputStream;
import org.terasology.launcher.StringIteratorInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.Callable;

            if (this.pid == 0) {
                this.pid = new Random().nextLong();
            }
            return this.pid;
        }

        @Override
        public int exitValue() {
            return 0;
        }

        @Override
        public void destroy() {
            throw new UnsupportedOperationException();
        }
    }

    static class OneLineAtATimeProcess extends HappyGameProcess {
        private final Iterator<String> lines;

        OneLineAtATimeProcess(Iterator<String> lines) {
            this.lines = lines;
        }

        @Override
        public InputStream getInputStream() {
            // 🤔 If RunGameTask had a way to do
            //        Iterator<String> lines = adapt(process)
            //    and we could provide a different adapter for our mock process than a system
            //    process, we could probably do without StringIteratorInputStream. ?
            return new StringIteratorInputStream(lines);