@@ -2,6 +2,9 @@ package com.dimafeng.testcontainers
22
33import java .util .function .Consumer
44
5+ import com .dimafeng .testcontainers .TestContainers .TestContainersSuite
6+ import com .dimafeng .testcontainers .lifecycle .TestLifecycleAware
7+ import org .junit .runner .{Description => JunitDescription }
58import com .github .dockerjava .api .DockerClient
69import com .github .dockerjava .api .command .{CreateContainerCmd , InspectContainerResponse }
710import com .github .dockerjava .api .model .{Bind , Info , VolumesFrom }
@@ -11,60 +14,113 @@ import org.testcontainers.containers.output.OutputFrame
1114import org .testcontainers .containers .startupcheck .StartupCheckStrategy
1215import org .testcontainers .containers .traits .LinkableContainer
1316import org .testcontainers .containers .{FailureDetectingExternalResource , Network , TestContainerAccessor , GenericContainer => OTCGenericContainer }
17+ import org .testcontainers .lifecycle .{Startable , TestDescription }
1418
1519import scala .collection .JavaConverters ._
1620import scala .concurrent .{Future , blocking }
1721
18- trait ForEachTestContainer extends SuiteMixin {
19- self : Suite =>
22+ private [testcontainers] object TestContainers {
23+
24+ implicit def junit2testContainersDescription (junit : JunitDescription ): TestDescription = {
25+ new TestDescription {
26+ override def getTestId : String = junit.getDisplayName
27+ override def getFilesystemFriendlyName : String = s " ${junit.getClassName}- ${junit.getMethodName}"
28+ }
29+ }
30+
31+ // Copy-pasted from `org.scalatest.junit.JUnitRunner.createDescription`
32+ def createDescription (suite : Suite ): JunitDescription = {
33+ val description = JunitDescription .createSuiteDescription(suite.getClass)
34+ // If we don't add the testNames and nested suites in, we get
35+ // Unrooted Tests show up in Eclipse
36+ for (name <- suite.testNames) {
37+ description.addChild(JunitDescription .createTestDescription(suite.getClass, name))
38+ }
39+ for (nestedSuite <- suite.nestedSuites) {
40+ description.addChild(createDescription(nestedSuite))
41+ }
42+ description
43+ }
44+
45+ trait TestContainersSuite extends SuiteMixin { self : Suite =>
46+
47+ val container : Container
48+
49+ def afterStart (): Unit = {}
50+
51+ def beforeStop (): Unit = {}
52+
53+ private val suiteDescription = createDescription(self)
2054
21- val container : Container
55+ private [testcontainers] def beforeTest (): Unit = {
56+ container match {
57+ case container : TestLifecycleAware => container.beforeTest(suiteDescription)
58+ case _ => // do nothing
59+ }
60+ }
2261
23- implicit private val suiteDescription = Description .createSuiteDescription(self.getClass)
62+ private [testcontainers] def afterTest (throwable : Option [Throwable ]): Unit = {
63+ container match {
64+ case container : TestLifecycleAware => container.afterTest(suiteDescription, throwable)
65+ case _ => // do nothing
66+ }
67+ }
68+ }
69+ }
70+
71+ trait ForEachTestContainer extends TestContainersSuite {
72+ self : Suite =>
2473
2574 abstract protected override def runTest (testName : String , args : Args ): Status = {
26- container.starting()
75+ container.start()
76+
77+ @ volatile var testCalled = false
78+ @ volatile var afterTestCalled = false
79+
2780 try {
2881 afterStart()
82+ beforeTest()
83+
84+ testCalled = true
2985 val status = super .runTest(testName, args)
30- status match {
31- case FailedStatus => container.failed(new RuntimeException (status.toString))
32- case _ => container.succeeded()
86+
87+ afterTestCalled = true
88+ if (! status.succeeds()) {
89+ afterTest(Some (new RuntimeException (" Test failed" )))
90+ } else {
91+ afterTest(None )
3392 }
93+
3494 status
3595 }
3696 catch {
3797 case e : Throwable =>
38- container.failed(e)
98+ if (testCalled && ! afterTestCalled) {
99+ afterTestCalled = true
100+ afterTest(Some (e))
101+ }
102+
39103 throw e
40104 }
41105 finally {
42106 try {
43107 beforeStop()
44108 }
45109 finally {
46- container.finished ()
110+ container.stop ()
47111 }
48112 }
49113 }
50-
51- def afterStart (): Unit = {}
52-
53- def beforeStop (): Unit = {}
54114}
55115
56- trait ForAllTestContainer extends SuiteMixin {
116+ trait ForAllTestContainer extends TestContainersSuite {
57117 self : Suite =>
58118
59- val container : Container
60-
61- implicit private val suiteDescription = Description .createSuiteDescription(self.getClass)
62-
63119 abstract override def run (testName : Option [String ], args : Args ): Status = {
64120 if (expectedTestCount(args.filter) == 0 ) {
65121 new CompositeStatus (Set .empty)
66122 } else {
67- container.starting ()
123+ container.start ()
68124 try {
69125 afterStart()
70126 super .run(testName, args)
@@ -73,43 +129,82 @@ trait ForAllTestContainer extends SuiteMixin {
73129 beforeStop()
74130 }
75131 finally {
76- container.finished ()
132+ container.stop ()
77133 }
78134 }
79135 }
80136 }
81137
82- def afterStart (): Unit = {}
138+ abstract protected override def runTest (testName : String , args : Args ): Status = {
139+ @ volatile var testCalled = false
140+ @ volatile var afterTestCalled = false
141+
142+ try {
143+ beforeTest()
144+
145+ testCalled = true
146+ val status = super .runTest(testName, args)
147+
148+ afterTestCalled = true
149+ if (! status.succeeds()) {
150+ afterTest(Some (new RuntimeException (" Test failed" )))
151+ } else {
152+ afterTest(None )
153+ }
83154
84- def beforeStop (): Unit = {}
155+ status
156+ }
157+ catch {
158+ case e : Throwable =>
159+ if (testCalled && ! afterTestCalled) {
160+ afterTestCalled = true
161+ afterTest(Some (e))
162+ }
163+
164+ throw e
165+ }
166+ }
85167}
86168
87- trait Container {
88- def finished ()(implicit description : Description ): Unit
169+ trait Container extends Startable {
89170
90- def failed (e : Throwable )(implicit description : Description ): Unit
171+ @ deprecated(" Use `stop` instead" )
172+ def finished ()(implicit description : Description ): Unit = stop()
91173
92- def starting ()(implicit description : Description ): Unit
174+ @ deprecated(" Use `stop` and/or `TestLifecycleAware.afterTest` instead" )
175+ def failed (e : Throwable )(implicit description : Description ): Unit = {}
93176
94- def succeeded ()(implicit description : Description ): Unit
177+ @ deprecated(" Use `start` instead" )
178+ def starting ()(implicit description : Description ): Unit = start()
179+
180+ @ deprecated(" Use `stop` and/or `TestLifecycleAware.afterTest` instead" )
181+ def succeeded ()(implicit description : Description ): Unit = {}
95182}
96183
97184trait TestContainerProxy [T <: FailureDetectingExternalResource ] extends Container {
98185
99186 @ deprecated(" Please use reflective methods from the wrapper and `configure` method for creation" )
100- implicit val container : T
187+ implicit def container : T
101188
189+ @ deprecated(" Use `stop` instead" )
102190 override def finished ()(implicit description : Description ): Unit = TestContainerAccessor .finished(description)
103191
192+ @ deprecated(" Use `stop` and/or `TestLifecycleAware.afterTest` instead" )
104193 override def succeeded ()(implicit description : Description ): Unit = TestContainerAccessor .succeeded(description)
105194
195+ @ deprecated(" Use `start` instead" )
106196 override def starting ()(implicit description : Description ): Unit = TestContainerAccessor .starting(description)
107197
198+ @ deprecated(" Use `stop` and/or `TestLifecycleAware.afterTest` instead" )
108199 override def failed (e : Throwable )(implicit description : Description ): Unit = TestContainerAccessor .failed(e, description)
109200}
110201
111202abstract class SingleContainer [T <: OTCGenericContainer [_]] extends TestContainerProxy [T ] {
112203
204+ override def start (): Unit = container.start()
205+
206+ override def stop (): Unit = container.stop()
207+
113208 def binds : Seq [Bind ] = container.getBinds.asScala.toSeq
114209
115210 def command : Seq [String ] = container.getCommandParts
0 commit comments