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

Unmask failures hidden by afterEach exceptions #41

Merged
merged 3 commits into from
Aug 10, 2016

Conversation

stuart-pollock
Copy link
Contributor

Signed-off-by: Stu Pollock spollock@pivotal.io

Signed-off-by: Stu Pollock <spollock@pivotal.io>
@stuart-pollock stuart-pollock changed the title Unmask failures hidden by afterEach exceptions Unmask failures hidden by afterEach exceptions (#40) Aug 9, 2016
@stuart-pollock stuart-pollock changed the title Unmask failures hidden by afterEach exceptions (#40) Unmask failures hidden by afterEach exceptions Aug 9, 2016
@stuart-pollock
Copy link
Contributor Author

This is intended to address #40

Signed-off-by: Philip Kuryloski <pkuryloski@pivotal.io>
@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling b05cc61 on pivotal-stuart-pollock:unmask_exceptions into e343a4b on greghaskins:master.

@greghaskins
Copy link
Owner

Good catch on this one, @pivotal-stuart-pollock !

I absolutely agree that the exception in beforeEach should not get swallowed. In your PR, the afterEach would get swallowed instead, which I'm not sure is a great tradeoff. I think Spectrum should report all the exceptions that occur, regardless of where they are triggered.

Prior Art

It's an odd scenario, so I did some quick research into how other tools handle this scenario. I've been trying to keep Spectrum's behavior mostly in line with things that already exist whenever it is possible and makes sense.

Normal JUnit 4

Standard JUnit runs only the first failing @Before method, and all @After methods, even if they fail, and reports all of them:

public class FooTest {

  @Before
  public void before1() {
    throw new RuntimeException("boom in before1");
  }

  @Before
  public void before2() {
    throw new RuntimeException("boom in before2");
  }

  @After
  public void after1() {
    throw new RuntimeException("boom in after 1");
  }

  @After
  public void after2() {
    throw new RuntimeException("boom in after 2");
  }

  @Test
  public void doesSomething() throws Exception {
    System.out.println("running doesSomething()");
  }
}

Trace:

java.lang.RuntimeException: boom in before2
    at given.a.spec.with.exception.in.beforeeach.block.and.aftereach.block.FooTest.before2(FooTest.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ...

java.lang.RuntimeException: boom in after 1
    at given.a.spec.with.exception.in.beforeeach.block.and.aftereach.block.FooTest.after1(FooTest.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ...

java.lang.RuntimeException: boom in after 2
    at given.a.spec.with.exception.in.beforeeach.block.and.aftereach.block.FooTest.after2(FooTest.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  ...

RSpec

RSpec seems to behave similar to JUnit: run the before blocks until an exception is hit, then run all after, even if they throw exceptions. It doesn't run specs if a before block blows up, and reports all exceptions.

panda = "happy"

RSpec.describe "the panda" do

  before do
    raise Exception.new("boom before 1")
  end

  before do
    raise Exception.new("boom before 2")
  end

  after do
    raise Exception.new("boom after 1")
  end

  after do
    raise Exception.new("boom after 2")
  end

  it "should be happy" do
    puts "running the spec"
    expect(panda).to eq('happy')
  end

end

Result:

F

Failures:

  1) the panda should be happy
     Got 0 failures and 3 other errors:

     1.1) Failure/Error: raise Exception.new("boom before 1")

          Exception:
            boom before 1
          # ./spec/sample_spec.rb:10:in `block (2 levels) in <top (required)>'

     1.2) Failure/Error: raise Exception.new("boom after 2")

          Exception:
            boom after 2
          # ./spec/sample_spec.rb:22:in `block (2 levels) in <top (required)>'

     1.3) Failure/Error: raise Exception.new("boom after 1")

          Exception:
            boom after 1
          # ./spec/sample_spec.rb:18:in `block (2 levels) in <top (required)>'

Finished in 0.00098 seconds (files took 0.12791 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample_spec.rb:25 # the panda should be happy

Jasmine

See my fiddle at https://jsfiddle.net/greghaskins/tq4jxh0p/ . Jasmine throws all errors in both exploding beforeEach and both afterEach blocks and still runs the spec despite those errors. This is the weirdest behavior of the three, and probably not what Spectrum should follow. It seems very un-Java to keep running everything even in the face of exceptions. JavaScript is a whole other world...

Suggested Behavior

I think RSpec gets it right:

  • Stop running the beforeEach blocks at the first exception
  • Run all afterEach blocks, even if some of them explode
  • Do not run any specs in a suite with an exploding beforeEach (mark them as failures but don't actually run the spec body)
  • Report all exceptions so the developer can see everything that might have gone wrong

It's also basically the same behavior as JUnit, with the main difference being the explicit order of setup/teardown in a BDD-style runner.

So in this example:

@RunWith(Spectrum.class)
public class PandaSpec {
  {
    final String panda = "happy";

    describe("the panda", () -> {

      beforeEach(() -> {
        throw new RuntimeException("boom beforeEach 1");
      });

      beforeEach(() -> {
        throw new RuntimeException("boom beforeEach 2");
      });

      afterEach(() -> {
        throw new RuntimeException("boom afterEach 1");
      });

      afterEach(() -> {
        throw new RuntimeException("boom afterEach 2");
      });

      it("should be happy", () -> {
        System.out.println("running the spec");
        assertThat(panda, is("happy"));
      });


    });
  }
}

I'd think that Spectrum should report something like

java.lang.RuntimeException: boom beforeEach 1
    at PandaSpec.lambda$3(PandaSpec.java:22)
    at ..
java.lang.RuntimeException: boom afterEach 2
    at PandaSpec.lambda$3(PandaSpec.java:34)
    at ..
java.lang.RuntimeException: boom afterEach 1
    at PandaSpec.lambda$3(PandaSpec.java:30)
    at ..

What do you think?

@HoloRin
Copy link
Contributor

HoloRin commented Aug 10, 2016

Hi @greghaskins,

Thanks for providing a very thoughtful and thorough response. What would you think about merging and adding the rspec style behavior on top as an additional enhancement? We came across the issue when performing some Spring Bean injection in a beforeEach (where our configuration had errors), and attempting to reset that configuration in an afterEach, and being really confused by the stacktrace. We imagine this is actually a common scenario, since afterEach's are often used for teardown, and may often fail if their corresponding setup failed. Our initial thought with the pull request was that by preferring to show the beforeEach, a developer could fix that error, run their tests again, and see the afterEach failure. We agree the Rspec behavior seems nicer from a developer perspective, but this change would be a nice incremental step towards a better developer experience. Also, a release with the PR included would allow us to easily share the enhanced behavior with the rest of our team.

One additional thing we noticed that surprised us with the examples above is how the "boom afterEach 2" is logged before the "boom afterEach 1" in everything but JUnit. We were actually expecting the BDD frameworks to log in the JUnit order. Interesting, although we agree Spectrum should probably have the BDD behavior instead of JUnit's.

@greghaskins
Copy link
Owner

I think that sounds reasonable.

The only thing I feel weird about in the interim is just completely swallowing that afterEach exception. Perhaps we could at least dump its stack trace to the console with printStackTrace so it isn't completely silent? If you update the PR with that, I'll merge and push a release. Then we'll create a separate issue for the full behavior.

The ordering of afterEach was an interesting bit. I discovered that RSpec and Jasmine treat those blocks like a stack, executed in reverse order of scope and declaration. When you think about it, it makes sense, but can be surprising coming from vanilla JUnit. In normal JUnit, ordering of everything is undefined (sometimes alphabetical, sometimes not) since the Java reflection API does not have a way for JUnit to know about the declaration order (last I checked). It can sometimes be a different order across runs and certainly dependent on the specific tool--Eclipse, Maven, Gradle all have slight differences.

exceptions thrown in an afterEach when a beforeEach has already thrown

Signed-off-by: Nitya Dhanushkodi <ndhanushkodi@pivotal.io>
@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling cdd1470 on pivotal-stuart-pollock:unmask_exceptions into e343a4b on greghaskins:master.

@HoloRin
Copy link
Contributor

HoloRin commented Aug 10, 2016

Excellent, printStackTrace added. Thanks!

We didn't realize that the JUnit ordering is undefined, but that makes sense given reflection.

@greghaskins greghaskins merged commit 4d6f6ba into greghaskins:master Aug 10, 2016
@HoloRin HoloRin deleted the unmask_exceptions branch August 10, 2016 18:26
@greghaskins
Copy link
Owner

@pjk25 @pivotal-stuart-pollock merged and released. Thanks for the help!

I left a note in #40 about our goal of matching RSpec, for tracking purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants