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