Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExpectedSystemExit and SystemErrRule or SystemOutRule cause Gradle to throw MessageIOException when used in the same test #38

Closed
ghost opened this issue Jan 3, 2016 · 4 comments

Comments

@ghost
Copy link

ghost commented Jan 3, 2016

While creating unit tests to handle some command line output and exceptions thrown in an application, I decided to use SystemErrRule and SystemOutRule to assist with this.

As this particular test is for the main entry point of the application, it is parsing the command line and calling System.exit(1) when invalid command line arguments have been provided.

However, including both of these causes Gradle to throw an exception when trying to run the task gradlew test:

:test
Unexpected exception thrown.
org.gradle.messaging.remote.internal.MessageIOException: Could not read message from '/127.0.0.1:50239'.
        at org.gradle.messaging.remote.internal.inet.SocketConnection.receive(SocketConnection.java:79)
        at org.gradle.messaging.remote.internal.hub.MessageHub$ConnectionReceive.run(MessageHub.java:235)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
        at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
Caused by: com.esotericsoftware.kryo.KryoException: java.io.IOException: An existing connection was forcibly closed by the remote host
        at com.esotericsoftware.kryo.io.Input.fill(Input.java:141)
        at com.esotericsoftware.kryo.io.Input.require(Input.java:159)
        at com.esotericsoftware.kryo.io.Input.readByte(Input.java:255)
        at org.gradle.internal.serialize.kryo.KryoBackedDecoder.readByte(KryoBackedDecoder.java:80)
        at org.gradle.messaging.remote.internal.hub.InterHubMessageSerializer$MessageReader.read(InterHubMessageSerializer.java:69)
        at org.gradle.messaging.remote.internal.hub.InterHubMessageSerializer$MessageReader.read(InterHubMessageSerializer.java:58)
        at org.gradle.messaging.remote.internal.inet.SocketConnection.receive(SocketConnection.java:74)
        ... 6 more
Caused by: java.io.IOException: An existing connection was forcibly closed by the remote host
        at sun.nio.ch.SocketDispatcher.read0(Native Method)
        at sun.nio.ch.SocketDispatcher.read(Unknown Source)
        at sun.nio.ch.IOUtil.readIntoNativeBuffer(Unknown Source)
        at sun.nio.ch.IOUtil.read(Unknown Source)
        at sun.nio.ch.SocketChannelImpl.read(Unknown Source)
        at org.gradle.messaging.remote.internal.inet.SocketConnection$SocketInputStream.read(SocketConnection.java:158)
        at com.esotericsoftware.kryo.io.Input.fill(Input.java:139)
        ... 12 more
Unexpected exception thrown.
org.gradle.messaging.remote.internal.MessageIOException: Could not write message [EndOfStream] to '/127.0.0.1:50239'.
        at org.gradle.messaging.remote.internal.inet.SocketConnection.dispatch(SocketConnection.java:106)
        at org.gradle.messaging.remote.internal.hub.MessageHub$ConnectionDispatch.run(MessageHub.java:284)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
        at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.io.IOException: An existing connection was forcibly closed by the remote host
        at sun.nio.ch.SocketDispatcher.write0(Native Method)
        at sun.nio.ch.SocketDispatcher.write(Unknown Source)
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(Unknown Source)
        at sun.nio.ch.IOUtil.write(Unknown Source)
        at sun.nio.ch.SocketChannelImpl.write(Unknown Source)
        at org.gradle.messaging.remote.internal.inet.SocketConnection$SocketOutputStream.flush(SocketConnection.java:221)
        at org.gradle.messaging.remote.internal.inet.SocketConnection.dispatch(SocketConnection.java:104)
        ... 6 more
:test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> Process 'Gradle Test Executor 1' finished with non-zero exit value 1

In IntelliJ, trying to run this test results in the error response "Failed to start: 0 passed, 1 not started" - so it isn't isolated to Gradle.

Here is an example setup that can reproduce this issue:

Program.java:

package org.test.example;

public final class Program
{
   public static void main(String[] args)
   {
      if (args.length != 2)
      {
         System.out.println("Proper usage: -port <number>");

         System.exit(1);
      }
   }
}

ProgramTest.java:

package org.test.example;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
import org.junit.contrib.java.lang.system.SystemErrRule;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Program.class)
public class ProgramTest
{
   /** Handles System.exit() calls. */
   @Rule
   private final ExpectedSystemExit exit = ExpectedSystemExit.none();

   /** Handles System.err calls. */
   @Rule
   private final SystemErrRule err = new SystemErrRule().enableLog().muteForSuccessfulTests();

   /** Handles System.out calls. */
   @Rule
   private final SystemOutRule out = new SystemOutRule().enableLog().muteForSuccessfulTests();

   /**
    * Verify server does not run when only a single valid command line
    * parameter is provided.
    *
    * @throws Exception On error.
    */
   @Test
   public void testMainOneParameterPort() throws Exception
   {
      expectExit();

      Program.main(new String[] { "-port" });
   }

   /**
    * Helper function to setup System.exit() expectations
    */
   private void expectExit()
   {
      exit.expectSystemExitWithStatus(1);

      exit.checkAssertionAfterwards(() ->
      {
         assertFalse(out.getLog().isEmpty());
         assertTrue(err.getLog().isEmpty());
      });
   }
}

Environment Configuration:

Windows 10 Pro x64 10.0.10586

Oracle JDK/JRE x64 1.8.0_66

Gradle 2.5

JUnit 4.12
System Rules 1.15.1
Mockito 1.10.19
PowerMock 1.6.4
@ghost ghost changed the title ExpectedSystemExit and SystemErrRule or SystemOutRule cause Gradle to throw org.gradle.messaging.remote.internal.MessageIOException when used in the same test ExpectedSystemExit and SystemErrRule or SystemOutRule cause Gradle to throw MessageIOException when used in the same test Jan 3, 2016
@stefanbirkner
Copy link
Owner

Does the problem still exists if you run the test without the PowerMockRunner?

@stefanbirkner
Copy link
Owner

The problem seems to be the PowerMockRunner. The test is successful if I don't run it with the PowerMockRunner.

@stefanbirkner
Copy link
Owner

This is a PowerMock issue: powermock/powermock#427. I created a pull request for PowerMock that solves the problem. I found a workaround, too. The issue does not apply if there is only one rule for a test. This can be achieved by using org.junit.rules.RuleChain:

@RunWith(PowerMockRunner.class)
@PrepareForTest(Program.class)
public class ProgramTest
{
    private final ExpectedSystemExit exit = ExpectedSystemExit.none();
    private final SystemErrRule err = new SystemErrRule().enableLog().muteForSuccessfulTests();
    private final SystemOutRule out = new SystemOutRule().enableLog().muteForSuccessfulTests();

    @Rule
    public final TestRule rule = RuleChain.outerRule(exit).around(err).around(out);

    ...

@ghost
Copy link
Author

ghost commented Jan 8, 2016

Thank you for the deep look into this, as well as the provided workaround.

I'll have a chance to test that it works as you've suggested this weekend and will watch for implementation on the PowerMock side.

Since this isn't an issue with the System Rules implementation, this seems like it can be closed.

@ghost ghost closed this as completed Jan 8, 2016
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant