@@ -19,11 +19,13 @@ package za.co.absa.cobrix.cobol.utils
1919import org .scalatest .wordspec .AnyWordSpec
2020
2121class UsingUtilsSuite extends AnyWordSpec {
22+ import UsingUtils .Implicits ._
23+
2224 " using with a single resource" should {
2325 " properly close the resource" in {
2426 var resource : AutoCloseableSpy = null
2527
26- UsingUtils .using( new AutoCloseableSpy ()) { res =>
28+ for (res <- new AutoCloseableSpy ()) {
2729 resource = res
2830 res.dummyAction()
2931 }
@@ -179,6 +181,28 @@ class UsingUtilsSuite extends AnyWordSpec {
179181 assert(resource2.closeCallCount == 1 )
180182 }
181183
184+ " work with for comprehension" in {
185+ var resource1 : AutoCloseableSpy = null
186+ var resource2 : AutoCloseableSpy = null
187+
188+ val result = for {
189+ res1 <- new AutoCloseableSpy ()
190+ res2 <- new AutoCloseableSpy ()
191+ } yield {
192+ resource1 = res1
193+ resource2 = res2
194+ res1.dummyAction()
195+ res2.dummyAction()
196+ 100
197+ }
198+
199+ assert(result == 100 )
200+ assert(resource1.actionCallCount == 1 )
201+ assert(resource1.closeCallCount == 1 )
202+ assert(resource2.actionCallCount == 1 )
203+ assert(resource2.closeCallCount == 1 )
204+ }
205+
182206 " properly close both resources when an inner one throws an exception during action and close" in {
183207 var resource1 : AutoCloseableSpy = null
184208 var resource2 : AutoCloseableSpy = null
@@ -209,6 +233,37 @@ class UsingUtilsSuite extends AnyWordSpec {
209233 assert(resource2.closeCallCount == 1 )
210234 }
211235
236+ " properly close both resources when an inner one throws an exception during action and close (for comprehension)" in {
237+ var resource1 : AutoCloseableSpy = null
238+ var resource2 : AutoCloseableSpy = null
239+ var exceptionThrown = false
240+
241+ try {
242+ for {
243+ res1 <- new AutoCloseableSpy ()
244+ res2 <- new AutoCloseableSpy (failAction = true , failClose = true )
245+ } {
246+ resource1 = res1
247+ resource2 = res2
248+ res1.dummyAction()
249+ res2.dummyAction()
250+ }
251+ } catch {
252+ case ex : Throwable =>
253+ exceptionThrown = true
254+ assert(ex.getMessage.contains(" Failed during action" ))
255+ val suppressed = ex.getSuppressed
256+ assert(suppressed.length == 1 )
257+ assert(suppressed(0 ).getMessage.contains(" Failed to close resource" ))
258+ }
259+
260+ assert(exceptionThrown)
261+ assert(resource1.actionCallCount == 1 )
262+ assert(resource1.closeCallCount == 1 )
263+ assert(resource2.actionCallCount == 1 )
264+ assert(resource2.closeCallCount == 1 )
265+ }
266+
212267 " properly close both resources when an outer one throws an exception during action and close" in {
213268 var resource1 : AutoCloseableSpy = null
214269 var resource2 : AutoCloseableSpy = null
@@ -239,6 +294,37 @@ class UsingUtilsSuite extends AnyWordSpec {
239294 assert(resource2.closeCallCount == 1 )
240295 }
241296
297+ " properly close both resources when an outer one throws an exception during action and close (for comprehension)" in {
298+ var resource1 : AutoCloseableSpy = null
299+ var resource2 : AutoCloseableSpy = null
300+ var exceptionThrown = false
301+
302+ try {
303+ for {
304+ res1 <- new AutoCloseableSpy (failAction = true , failClose = true )
305+ res2 <- new AutoCloseableSpy ()
306+ } {
307+ resource1 = res1
308+ resource2 = res2
309+ res1.dummyAction()
310+ res2.dummyAction()
311+ }
312+ } catch {
313+ case ex : Throwable =>
314+ exceptionThrown = true
315+ assert(ex.getMessage.contains(" Failed during action" ))
316+ val suppressed = ex.getSuppressed
317+ assert(suppressed.length == 1 )
318+ assert(suppressed(0 ).getMessage.contains(" Failed to close resource" ))
319+ }
320+
321+ assert(exceptionThrown)
322+ assert(resource1.actionCallCount == 1 )
323+ assert(resource1.closeCallCount == 1 )
324+ assert(resource2.actionCallCount == 0 )
325+ assert(resource2.closeCallCount == 1 )
326+ }
327+
242328 " properly close the outer resource when the inner one fails on create" in {
243329 var resource1 : AutoCloseableSpy = null
244330 var resource2 : AutoCloseableSpy = null
@@ -264,5 +350,163 @@ class UsingUtilsSuite extends AnyWordSpec {
264350 assert(resource1.closeCallCount == 1 )
265351 assert(resource2 == null )
266352 }
353+
354+ " properly close the outer resource when the inner one fails on create (for comprehension)" in {
355+ val resource1 : AutoCloseableSpy = new AutoCloseableSpy ()
356+ var resource2 : AutoCloseableSpy = null
357+ var exceptionThrown = false
358+
359+ try {
360+ resource1
361+ .flatMap(res1 =>
362+ new AutoCloseableSpy (failCreate = true )
363+ .map(res2 => {
364+ resource2 = res2
365+ res1.dummyAction()
366+ res2.dummyAction()
367+ })
368+ )
369+ } catch {
370+ case ex : Throwable =>
371+ exceptionThrown = true
372+ assert(ex.getMessage.contains(" Failed to create resource" ))
373+ }
374+
375+ assert(exceptionThrown)
376+ assert(resource1.actionCallCount == 0 )
377+ assert(resource1.closeCallCount == 1 )
378+ assert(resource2 == null )
379+ }
380+ }
381+
382+ " withFilter (for-comprehension guards)" should {
383+ " skip the body when the guard is false (foreach form) and still close the resource" in {
384+ val res = new AutoCloseableSpy ()
385+
386+ var bodyRan = false
387+ for {
388+ r <- res if false
389+ } {
390+ bodyRan = true
391+ r.dummyAction()
392+ }
393+
394+ assert(! bodyRan)
395+ assert(res.actionCallCount == 0 )
396+ assert(res.closeCallCount == 1 )
397+ }
398+
399+ " run the body when the guard is true (foreach form) and close the resource" in {
400+ val res = new AutoCloseableSpy ()
401+
402+ var bodyRan = false
403+ for {
404+ r <- res if true
405+ } {
406+ bodyRan = true
407+ r.dummyAction()
408+ }
409+
410+ assert(bodyRan)
411+ assert(res.actionCallCount == 1 )
412+ assert(res.closeCallCount == 1 )
413+ }
414+
415+ " close both resources when an inner guard is false (nested generators)" in {
416+ val r1 = new AutoCloseableSpy ()
417+ val r2 = new AutoCloseableSpy ()
418+
419+ var bodyRan = false
420+ for {
421+ a <- r1
422+ b <- r2 if false
423+ } {
424+ bodyRan = true
425+ a.dummyAction()
426+ b.dummyAction()
427+ }
428+
429+ assert(! bodyRan)
430+ assert(r1.actionCallCount == 0 )
431+ assert(r2.actionCallCount == 0 )
432+ assert(r2.closeCallCount == 1 )
433+ assert(r1.closeCallCount == 1 )
434+ }
435+
436+ " compose multiple guards correctly (both must be true)" in {
437+ val res = new AutoCloseableSpy ()
438+
439+ var bodyRan = false
440+ for {
441+ r <- res if true if false
442+ } {
443+ bodyRan = true
444+ r.dummyAction()
445+ }
446+
447+ assert(! bodyRan)
448+ assert(res.actionCallCount == 0 )
449+ assert(res.closeCallCount == 1 )
450+ }
451+
452+ " not evaluate the body when the first guard is false (side-effect check)" in {
453+ val res = new AutoCloseableSpy ()
454+
455+ var sideEffect = 0
456+ for {
457+ r <- res if false if { sideEffect += 1 ; true }
458+ } {
459+ r.dummyAction()
460+ }
461+
462+ assert(sideEffect == 0 )
463+ assert(res.actionCallCount == 0 )
464+ assert(res.closeCallCount == 1 )
465+ }
466+
467+ " throw on yield when the guard is false (map path) and still close the resource" in {
468+ val res = new AutoCloseableSpy ()
469+
470+ var thrown = false
471+ try {
472+ val _ = for {
473+ r <- res if false
474+ } yield {
475+ r.dummyAction()
476+ 1
477+ }
478+ } catch {
479+ case _ : NoSuchElementException => thrown = true
480+ }
481+
482+ assert(thrown)
483+ assert(res.actionCallCount == 0 )
484+ assert(res.closeCallCount == 1 )
485+ }
486+
487+ " throw on a guarded middle generator in a yield (flatMap->map path) and close all opened resources" in {
488+ val r1 = new AutoCloseableSpy ()
489+ val r2 = new AutoCloseableSpy ()
490+
491+ var thrown = false
492+ try {
493+ val _ = for {
494+ a <- r1
495+ b <- r2 if false
496+ } yield {
497+ a.dummyAction()
498+ b.dummyAction()
499+ 1
500+ }
501+ } catch {
502+ case _ : NoSuchElementException => thrown = true
503+ }
504+
505+ assert(thrown)
506+ assert(r1.actionCallCount == 0 )
507+ assert(r2.actionCallCount == 0 )
508+ assert(r2.closeCallCount == 1 )
509+ assert(r1.closeCallCount == 1 )
510+ }
267511 }
268512}
0 commit comments