/* * Copyright 2015-2017 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution and is available at * * http://www.eclipse.org/legal/epl-v10.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.opentest4j.TestAbortedException; /** * Micro-tests that verify behavior of {@link HierarchicalTestExecutor}. * * @since 1.0 */ class HierarchicalTestExecutorTests { MyContainer root; EngineExecutionListener listener; MyEngineExecutionContext rootContext; HierarchicalTestExecutor<MyEngineExecutionContext> executor; @BeforeEach public void init() { root = spy(new MyContainer(UniqueId.root("container", "root"))); listener = mock(EngineExecutionListener.class); rootContext = new MyEngineExecutionContext(); ExecutionRequest request = new ExecutionRequest(root, listener, null); executor = new MyExecutor(request, rootContext); } @Test void emptyRootDescriptor() throws Exception { InOrder inOrder = inOrder(listener, root); executor.execute(); ArgumentCaptor<TestExecutionResult> rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertTrue(rootExecutionResult.getValue().getStatus() == TestExecutionResult.Status.SUCCESSFUL, "Execution of root should be successful."); } @Test void rootDescriptorWithOneChildContainer() throws Exception { MyContainer child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).before(rootContext); inOrder.verify(child).after(rootContext); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertTrue(childExecutionResult.getValue().getStatus() == TestExecutionResult.Status.SUCCESSFUL, "Execution of child container should be successful."); } @Test void rootDescriptorWithOneChildLeaf() throws Exception { MyLeaf child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); root.addChild(child); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertTrue(aTestExecutionResult.getValue().getStatus() == TestExecutionResult.Status.SUCCESSFUL, "Execution of child leaf be successful."); } @Test void skippingAContainer() throws Exception { MyContainer child = spy(new MyContainer(UniqueId.root("container", "child container"))); when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); root.addChild(child); InOrder inOrder = inOrder(listener, root, child); executor.execute(); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); verify(listener, never()).executionStarted(child); verifyNoMoreInteractions(child); verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); } @Test void skippingALeaf() throws Exception { MyLeaf child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); root.addChild(child); InOrder inOrder = inOrder(listener, root, child); executor.execute(); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); verify(listener, never()).executionStarted(child); verifyNoMoreInteractions(child); verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); } @Test void exceptionInShouldBeSkipped() throws Exception { MyContainer child = spy(new MyContainer(UniqueId.root("container", "child container"))); RuntimeException anException = new RuntimeException("in skip"); when(child.shouldBeSkipped(rootContext)).thenThrow(anException); root.addChild(child); InOrder inOrder = inOrder(listener, child); executor.execute(); ArgumentCaptor<TestExecutionResult> childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); verifyNoMoreInteractions(child); assertTrue(childExecutionResult.getValue().getStatus() == TestExecutionResult.Status.FAILED, "Execution of child should fail."); assertSame(childExecutionResult.getValue().getThrowable().get(), anException); } @Test void exceptionInContainerBeforeAll() throws Exception { MyContainer child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); RuntimeException anException = new RuntimeException("in test"); when(root.before(rootContext)).thenThrow(anException); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertTrue(rootExecutionResult.getValue().getStatus() == TestExecutionResult.Status.FAILED, "Execution of root should fail."); assertSame(rootExecutionResult.getValue().getThrowable().get(), anException); verifyNoMoreInteractions(child); } @Test void exceptionInContainerAfterAll() throws Exception { MyLeaf child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); root.addChild(child); RuntimeException anException = new RuntimeException("in test"); doThrow(anException).when(root).after(rootContext); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), any(TestExecutionResult.class)); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertTrue(rootExecutionResult.getValue().getStatus() == TestExecutionResult.Status.FAILED, "Execution of root should fail."); assertSame(rootExecutionResult.getValue().getThrowable().get(), anException); } @Test void exceptionInLeafExecute() throws Exception { MyLeaf child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); RuntimeException anException = new RuntimeException("in test"); when(child.execute(eq(rootContext), any())).thenThrow(anException); root.addChild(child); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertTrue(childExecutionResult.getValue().getStatus() == TestExecutionResult.Status.FAILED, "Execution of child should fail."); assertSame(childExecutionResult.getValue().getThrowable().get(), anException); } @Test void abortInContainerBeforeAll() throws Exception { MyContainer child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); TestAbortedException anAbortedException = new TestAbortedException("in BeforeAll"); when(root.before(rootContext)).thenThrow(anAbortedException); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertTrue(rootExecutionResult.getValue().getStatus() == TestExecutionResult.Status.ABORTED, "Execution of root should abort."); assertSame(rootExecutionResult.getValue().getThrowable().get(), anAbortedException); verifyNoMoreInteractions(child); } @Test void abortInLeafExecute() throws Exception { MyLeaf child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); TestAbortedException anAbortedException = new TestAbortedException("in test"); when(child.execute(eq(rootContext), any())).thenThrow(anAbortedException); root.addChild(child); InOrder inOrder = inOrder(listener, root, child); executor.execute(); ArgumentCaptor<TestExecutionResult> childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertTrue(childExecutionResult.getValue().getStatus() == TestExecutionResult.Status.ABORTED, "Execution of child should abort."); assertSame(childExecutionResult.getValue().getThrowable().get(), anAbortedException); } @Test void executesDynamicTestDescriptors() throws Exception { UniqueId leafUniqueId = UniqueId.root("leaf", "child leaf"); MyLeaf child = spy(new MyLeaf(leafUniqueId)); MyLeaf dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); when(child.execute(any(), any())).thenAnswer(invocation -> { DynamicTestExecutor dynamicTestExecutor = invocation.getArgument(1); dynamicTestExecutor.execute(dynamicTestDescriptor); return invocation.getArgument(0); }); root.addChild(child); InOrder inOrder = inOrder(listener, root, child, dynamicTestDescriptor); executor.execute(); ArgumentCaptor<TestExecutionResult> aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).dynamicTestRegistered(dynamicTestDescriptor); inOrder.verify(dynamicTestDescriptor).prepare(rootContext); inOrder.verify(dynamicTestDescriptor).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(dynamicTestDescriptor); inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( TestExecutionResult.Status.SUCCESSFUL, TestExecutionResult.Status.SUCCESSFUL); } /** * Verifies support for blacklisted exceptions. */ @Test void outOfMemoryErrorInShouldBeSkipped() throws Exception { MyContainer child = spy(new MyContainer(UniqueId.root("container", "child container"))); OutOfMemoryError outOfMemoryError = new OutOfMemoryError("in skip"); when(child.shouldBeSkipped(rootContext)).thenThrow(outOfMemoryError); root.addChild(child); Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); assertSame(outOfMemoryError, actualException); } /** * Verifies support for blacklisted exceptions. */ @Test void outOfMemoryErrorInLeafExecution() throws Exception { MyLeaf child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); OutOfMemoryError outOfMemoryError = new OutOfMemoryError("in test"); when(child.execute(eq(rootContext), any())).thenThrow(outOfMemoryError); root.addChild(child); Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); assertSame(outOfMemoryError, actualException); } // ------------------------------------------------------------------- private static class MyEngineExecutionContext implements EngineExecutionContext { } private static class MyContainer extends AbstractTestDescriptor implements Node<MyEngineExecutionContext> { protected MyContainer(UniqueId uniqueId) { super(uniqueId, uniqueId.toString()); } @Override public Type getType() { return Type.CONTAINER; } } private static class MyLeaf extends AbstractTestDescriptor implements Node<MyEngineExecutionContext> { protected MyLeaf(UniqueId uniqueId) { super(uniqueId, uniqueId.toString()); } @Override public MyEngineExecutionContext execute(MyEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { return context; } @Override public Type getType() { return Type.TEST; } } private static class MyExecutor extends HierarchicalTestExecutor<MyEngineExecutionContext> { MyExecutor(ExecutionRequest request, MyEngineExecutionContext rootContext) { super(request, rootContext); } } }