package hudson;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeFalse;

import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
import hudson.Launcher.RemoteLauncher.ProcImpl;
import hudson.model.TaskListener;
import hudson.remoting.Pipe;
import hudson.remoting.VirtualChannel;
import hudson.slaves.DumbSlave;
import hudson.util.IOUtils;
import hudson.util.StreamTaskListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import jenkins.security.MasterToSlaveCallable;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

/**
 * @author Kohsuke Kawaguchi
 */
@Issue("JENKINS-7809")
public class ProcTest {

    @Rule
    public JenkinsRule j = new JenkinsRule();

    /**
     * Makes sure that the output flushing and {@link ProcImpl#join()} is synced.
     */
    @Test
    public void remoteProcOutputSync() throws Exception {
        assumeFalse("TODO: Implement this test for Windows", Functions.isWindows());
        VirtualChannel ch = createSlaveChannel();

        // keep the pipe fairly busy
        final Pipe p = Pipe.createRemoteToLocal();
        for (int i = 0; i < 10; i++)
            ch.callAsync(new ChannelFiller(p.getOut()));
        new Thread() {
            @Override
            public void run() {
                try {
                    IOUtils.drain(p.getIn());
                } catch (IOException e) {
                }
            }
        }.start();

        RemoteLauncher launcher = new RemoteLauncher(TaskListener.NULL, ch, true);

        String str = "";
        for (int i = 0; i < 256; i++)
            str += "oxox";

        for (int i = 0; i < 1000; i++) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            launcher.launch().cmds("echo", str).stdout(baos).join();
            assertEquals(str, baos.toString(Charset.defaultCharset()).trim());
        }

        ch.close();
    }

    private VirtualChannel createSlaveChannel() throws Exception {
        DumbSlave s = j.createSlave();
        s.toComputer().connect(false).get();
        VirtualChannel ch = null;
        while (ch == null) {
            ch = s.toComputer().getChannel();
            Thread.sleep(100);
        }
        return ch;
    }

    private static class ChannelFiller extends MasterToSlaveCallable<Void, IOException> {
        private final OutputStream o;

        private ChannelFiller(OutputStream o) {
            this.o = o;
        }

        @Override
        public Void call() throws IOException {
            while (!Thread.interrupted()) {
                o.write(new byte[256]);
            }
            return null;
        }
    }

    @Test
    public void ioPumpingWithLocalLaunch() throws Exception {
        assumeFalse("TODO: Implement this test for Windows", Functions.isWindows());
        doIoPumpingTest(new LocalLauncher(new StreamTaskListener(System.out, Charset.defaultCharset())));
    }

    @Test
    public void ioPumpingWithRemoteLaunch() throws Exception {
        assumeFalse("TODO: Implement this test for Windows", Functions.isWindows());
        doIoPumpingTest(new RemoteLauncher(
                new StreamTaskListener(System.out, Charset.defaultCharset()),
                createSlaveChannel(), true));
    }

    private void doIoPumpingTest(Launcher l) throws IOException, InterruptedException {
        String[] ECHO_BACK_CMD = {"cat"};   // TODO: what is the echo back command for Windows? "cmd /C copy CON CON"?

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        l.launch().cmds(ECHO_BACK_CMD).stdin(new ByteArrayInputStream("Hello".getBytes(Charset.defaultCharset()))).stdout(out).join();
        assertEquals("Hello", out.toString(Charset.defaultCharset()));

        Proc p = l.launch().cmds(ECHO_BACK_CMD).stdin(new ByteArrayInputStream("Hello".getBytes(Charset.defaultCharset()))).readStdout().start();
        p.join();
        assertEquals("Hello", org.apache.commons.io.IOUtils.toString(p.getStdout(), Charset.defaultCharset()));
        assertNull(p.getStderr());
        assertNull(p.getStdin());


        p = l.launch().cmds(ECHO_BACK_CMD).writeStdin().readStdout().start();
        p.getStdin().write("Hello".getBytes(Charset.defaultCharset()));
        p.getStdin().close();
        p.join();
        assertEquals("Hello", org.apache.commons.io.IOUtils.toString(p.getStdout(), Charset.defaultCharset()));
        assertNull(p.getStderr());
    }
}

@@ -1118,93 +1118,8 @@
 n %7B%0A
-        assumeFalse(%22TODO: Implement this test for Windows%22, Functions.isWindows());%0A
     
@@ -1370,70 +1370,16 @@
 ead(
-) %7B%0A            @Override%0A            public void run() %7B%0A    
+() -%3E %7B%0A
     
@@ -1392,20 +1392,16 @@
   try %7B%0A
-    
         
@@ -1446,20 +1446,16 @@
         
-   
- 
 %7D catch 
@@ -1484,34 +1484,16 @@
         
-    %7D%0A            
 %7D%0A      
@@ -1495,16 +1495,17 @@
        %7D
+)
 .start()
@@ -1587,104 +1587,121 @@
 ch, 
-true);%0A%0A        String str = %22%22;%0A        for (int i = 0; i %3C 256; i++)%0A            str += %22oxox%22
+!Functions.isWindows());%0A%0A        StringBuilder str = new StringBuilder();%0A        str.append(%22oxox%22.repeat(256))
 ;%0A%0A 
@@ -1826,42 +1826,127 @@
     
-launcher.launch().cmds(
+if (Functions.isWindows()) %7B%0A                launcher.launch().cmds(new String%5B%5D %7B%22cmd%22, %22/c%22, 
 %22echo
+ 
 %22, str
+.toString()%7D
 ).st
@@ -1956,32 +1956,163 @@
 t(baos).join();%0A
+            %7D%0A            else %7B%0A                launcher.launch().cmds(%22echo%22, str.toString()).stdout(baos).join();%0A            %7D%0A
             asse
@@ -2123,16 +2123,27 @@
 uals(str
+.toString()
 , baos.t

Implement test in `ProcTest` on Windows (#7363)