diff --git a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/OneOnEach.scala b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/OneOnEach.scala index 62cf288263b..282e5209147 100644 --- a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/OneOnEach.scala +++ b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/OneOnEach.scala @@ -36,7 +36,7 @@ class OneOnEach extends DeployStrategy { override def next(): Address = { val i = index if (i >= available.length) { - throw new IndexOutOfBoundsException() + throw new NoSuchElementException("no available addresses") } index += 1 available(i) diff --git a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RandomDeployment.scala b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RandomDeployment.scala index aebab32fca9..5e3ebfa972c 100644 --- a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RandomDeployment.scala +++ b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RandomDeployment.scala @@ -33,6 +33,9 @@ class RandomDeployment extends DeployStrategy { } override def next(): Address = { + if (available.isEmpty) { + throw new NoSuchElementException("no available addresses") + } available(util.Random.nextInt(available.length)) } } diff --git a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RoundRobinDeployment.scala b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RoundRobinDeployment.scala index 3fee912d7d9..70472974937 100644 --- a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RoundRobinDeployment.scala +++ b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/RoundRobinDeployment.scala @@ -34,6 +34,9 @@ class RoundRobinDeployment extends DeployStrategy { } override def next(): Address = { + if (available.isEmpty) { + throw new NoSuchElementException("no available addresses") + } val i = index index = (index + 1) % available.length available(i) diff --git a/amber/src/test/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/DeployStrategiesSpec.scala b/amber/src/test/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/DeployStrategiesSpec.scala index 3d35752c408..73306cb9cf9 100644 --- a/amber/src/test/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/DeployStrategiesSpec.scala +++ b/amber/src/test/scala/org/apache/texera/amber/engine/architecture/deploysemantics/deploystrategy/DeployStrategiesSpec.scala @@ -42,17 +42,17 @@ class DeployStrategiesSpec extends AnyFlatSpec with Matchers { strategy.next() shouldBe nodeC } - it should "raise IndexOutOfBoundsException once the array is exhausted" in { + it should "raise NoSuchElementException once the array is exhausted" in { val strategy = OneOnEach() strategy.initialize(Array(nodeA)) strategy.next() shouldBe nodeA - assertThrows[IndexOutOfBoundsException](strategy.next()) + assertThrows[NoSuchElementException](strategy.next()) } - it should "raise IndexOutOfBoundsException immediately when initialized with an empty array" in { + it should "raise NoSuchElementException immediately when initialized with an empty array" in { val strategy = OneOnEach() strategy.initialize(Array.empty[Address]) - assertThrows[IndexOutOfBoundsException](strategy.next()) + assertThrows[NoSuchElementException](strategy.next()) } it should "preserve its iteration cursor across re-initialization (current behavior)" in { @@ -66,7 +66,7 @@ class DeployStrategiesSpec extends AnyFlatSpec with Matchers { strategy.initialize(Array(nodeC)) // index is still 1 from the previous run; the new single-element array // is therefore reported as exhausted. - assertThrows[IndexOutOfBoundsException](strategy.next()) + assertThrows[NoSuchElementException](strategy.next()) } "OneOnEach.apply" should "produce a fresh, independent instance" in { @@ -93,16 +93,10 @@ class DeployStrategiesSpec extends AnyFlatSpec with Matchers { for (_ <- 1 to 5) strategy.next() shouldBe nodeA } - it should "raise ArithmeticException on next() with an empty array (current behavior)" in { - // Pin: RoundRobinDeployment.next does `index = (index + 1) % length`, - // which divides by zero when length == 0 and crashes with - // ArithmeticException before any address is returned. Other strategies - // raise IndexOutOfBoundsException for the same situation, so this is a - // contract divergence — pinning the current behavior so a future fix - // that aligns the empty-array error type will need to update this spec. + it should "raise NoSuchElementException on next() with an empty array" in { val strategy = RoundRobinDeployment() strategy.initialize(Array.empty[Address]) - assertThrows[ArithmeticException](strategy.next()) + assertThrows[NoSuchElementException](strategy.next()) } "RoundRobinDeployment.apply" should "produce a fresh, independent instance" in { @@ -129,15 +123,10 @@ class DeployStrategiesSpec extends AnyFlatSpec with Matchers { for (_ <- 1 to 5) strategy.next() shouldBe nodeA } - it should "raise IllegalArgumentException on next() with an empty array (current behavior)" in { - // Pin: RandomDeployment.next() calls Random.nextInt(0), which throws - // IllegalArgumentException with bound must be positive. Same root issue - // as the empty-array case for RoundRobinDeployment: each strategy reports - // the empty-array fault with a different exception type. Pinning this - // separately so a unification fix shows up here. + it should "raise NoSuchElementException on next() with an empty array" in { val strategy = RandomDeployment() strategy.initialize(Array.empty[Address]) - assertThrows[IllegalArgumentException](strategy.next()) + assertThrows[NoSuchElementException](strategy.next()) } "RandomDeployment.apply" should "produce a fresh, independent instance" in {