如何在java测试代码中使用多线程自动完成扫描仪类方法中的输入?

问题描述 投票:0回答:1

我正在尝试编写一个Java测试源。 如何在多线程环境中处理通过 Scanner 类方法接收输入的测试目标的自动输入?

class SystemInInterceptor extends InputStream {
    private String input;
    private int position = 0;

    public SystemInInterceptor(String input) {
        this.input = input + "\n";  // Add a newline at the end to simulate Enter key
    }

    @Override
    public int read() {
        if (position >= input.length()) {
            return -1;
        }
        return input.charAt(position++);
    }
}
Thread[] threads = new Thread[5];
        
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        String testInput = "o\n611019\n2\n748943\n8\n \nq\n";
                        InputStream inputStream = new SystemInInterceptor(testInput);
                        System.setIn(inputStream);
                        App.main(null);
                    } catch (SoldOutException e) {
                        throw new RuntimeException(e);
                    }
                }
            });

        }
        for(Thread thread: threads){
            thread.start();
        }
        for(Thread thread: threads){
            thread.join();
        }

当我运行上面的源代码时,第一个线程工作正常,但其他线程抛出

Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.util.NoSuchElementException: No line found

它在非多线程情况下工作得很好。

java multithreading inputstream
1个回答
0
投票

在我所知道的简单情况下,一个进程只能有一个输入流,以及一个输出流和一个错误流。因此,如果您无法控制

App.main
的功能,或者您不想修改它,那么您可以创建多个进程。

想象多次执行以下操作:

  1. 打开 CLI 实例。
  2. 执行您想要的可运行程序(据我从您的问题中了解到的,调用
    App.main
    的程序),但使用重定向的输入流。

您可以使用

java.lang.Process
以编程方式执行此操作。

进程比线程重(就资源消耗而言),我知道,但如果您的场景很简单(例如,您只需要并行的少数进程),那么您可以尝试以下操作:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class MultiSystemIn {
    
    private static class ExecuteProcessRunnable implements Runnable {
        
        private final String input;
        private final List<String> command;
        
        public ExecuteProcessRunnable(final List<String> command,
                                      final String input) {
            this.input = Objects.requireNonNull(input);
            if (command == null || command.isEmpty())
                throw new IllegalArgumentException("Empty command.");
            this.command = new ArrayList<>(command);
        }
        
        @Override
        public void run() {
            final ProcessBuilder builder = new ProcessBuilder(command);
            Process process = null;
            try {
                //Start the command:
                process = builder.start();
                
                //process.getOutputStream() is what the process will be reading as its own System.in...
                try (final OutputStreamWriter osw = new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8)) {
                    osw.write(input);
                }
                
                //process.getErrorStream() is what we can read as the corresponding System.err of the command.get(0) program...
                try (final InputStreamReader isr = new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8);
                     final BufferedReader br = new BufferedReader(isr)) {
                    for (String errline = br.readLine(); errline != null; errline = br.readLine())
                        System.out.println("Process " + command + " error stream says: " + errline);
                }
            }
            catch (final IOException ioe) {
                System.err.println("Process " + command + " failed with " + ioe);
            }
            finally {
                if (process != null) {
                    process.destroyForcibly();
                    try {
                        process.waitFor();
                    }
                    catch (final InterruptedException ie) {
                    }
                }
            }
        }
    }
    
    public static void main(final String[] args) {
        for (int i = 0; i < 10; ++i) {
            System.out.println("Starting process " + i + "...");
            final Thread t = new Thread(new ExecuteProcessRunnable(
                    Arrays.asList("java", "App"),
                    "o\n611019\n2\n748943\n8\n \nq\n"
            ));
            t.setDaemon(false);
            t.start();
        }
    }
}

上面的代码也使用了线程,其中每个线程对应管理一个进程(例如中继错误流、写入进程的标准输入以及等待进程终止)。

默认情况下,以这种方式创建的进程使用管道作为其标准输入流,但管道没有无限的缓冲区(您必须在某些时候从它们读取数据,以便能够在缓冲区的情况下再次写入它们)满了)。这就是我也使用线程的原因,因为如果在没有线程的情况下执行相同的操作(即仅在

main
创建和启动
Process
中运行一个循环,然后写入它们的流),那么在输入
 的情况下String
由于某种原因变得太大,那么管道可能会阻塞,而无法让其他进程启动。

您也可以通过使用文件重定向流来完成此操作,而不是手动写入每个进程的流。还有一些其他定制可以做(只需阅读

Process
ProcessBuilder
的文档)。

© www.soinside.com 2019 - 2024. All rights reserved.