Repository: cloudslang/cloud-slang
Issue: iterating over empty arrays problem

I ran over this usecase where a loop is supposed to iterate over an empty array and I get an unclear message

Command failed java.lang.RuntimeException: Slang Error : Error running: 'get_parent_image'

```
null
```

As @orius123 we should either show a warning about not being allowed to iterate over empty arrays or just skip the loop


Changed files:
cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBindingTest.java
cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/LoopsBindingTest.java
cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBinding.java
cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/LoopsBinding.java

diff --git a/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBinding.java b/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBinding.java
index 504ffaa27..6e8134ef7 100644
--- a/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBinding.java
+++ b/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBinding.java
@@ -27,24 +27,31 @@ import java.util.List;
 @Component
 public class AsyncLoopBinding {
 
+    public static final String ASYNC_LOOP_EXPRESSION_ERROR_MESSAGE = "Error evaluating async loop expression in task";
+
     @Autowired
     private ScriptEvaluator scriptEvaluator;
 
+    public static String generateAsyncLoopExpressionMessage(String nodeName, String message) {
+        return ASYNC_LOOP_EXPRESSION_ERROR_MESSAGE + " '" + nodeName + "', error is: \\n" + message;
+    }
+
     public List<Serializable> bindAsyncLoopList(AsyncLoopStatement asyncLoopStatement, Context flowContext, String nodeName) {
-        Validate.notNull(asyncLoopStatement, "async task statement cannot be null");
+        Validate.notNull(asyncLoopStatement, "async loop statement cannot be null");
         Validate.notNull(flowContext, "flow context cannot be null");
         Validate.notNull(nodeName, "node name cannot be null");
 
+        List<Serializable> evalResult;
         try {
-            @SuppressWarnings("unchecked") List<Serializable> evalResult = (List<Serializable>) scriptEvaluator.evalExpr(
+            evalResult = (List<Serializable>) scriptEvaluator.evalExpr(
                     asyncLoopStatement.getExpression(),
                     flowContext.getImmutableViewOfVariables());
-            if (CollectionUtils.isEmpty(evalResult)) {
-                throw new RuntimeException("Expression cannot be empty");
-            }
-            return evalResult;
         } catch (Throwable t) {
-            throw new RuntimeException("Error evaluating async loop expression in task '" + nodeName + "', error is: \\n" + t.getMessage(), t);
+            throw new RuntimeException(generateAsyncLoopExpressionMessage(nodeName, t.getMessage()), t);
+        }
+        if (CollectionUtils.isEmpty(evalResult)) {
+            throw new RuntimeException(generateAsyncLoopExpressionMessage(nodeName, "expression is empty"));
         }
+        return evalResult;
     }
 }
diff --git a/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/LoopsBinding.java b/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/LoopsBinding.java
index 2914640ae..d1c047018 100644
--- a/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/LoopsBinding.java
+++ b/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/bindings/LoopsBinding.java
@@ -34,6 +34,9 @@ import static io.cloudslang.lang.runtime.env.LoopCondition.LOOP_CONDITION_KEY;
 @Component
 public class LoopsBinding {
 
+    public static final String FOR_LOOP_EXPRESSION_ERROR_MESSAGE = "Error evaluating for loop expression in task";
+    public static final String INVALID_MAP_EXPRESSION_MESSAGE = "Invalid expression for iterating maps";
+
     private final Logger logger = Logger.getLogger(getClass());
 
     @Autowired
@@ -85,7 +88,7 @@ public class LoopsBinding {
         try {
             evalResult = scriptEvaluator.evalExpr(collectionExpression, variables);
         } catch (Throwable t) {
-            throw new RuntimeException("Error evaluating for loop expression in task '" + nodeName + "',\\n\\tError is: " + t.getMessage(), t);
+            throw new RuntimeException(FOR_LOOP_EXPRESSION_ERROR_MESSAGE + " '" + nodeName + "',\\n\\tError is: " + t.getMessage(), t);
         }
 
         if (forLoopStatement instanceof MapForLoopStatement) {
@@ -97,7 +100,7 @@ public class LoopsBinding {
                 }
                 evalResult = (Serializable) entriesAsSerializable;
             } else {
-                throw new RuntimeException("Invalid expression for iterating maps: " + collectionExpression);
+                throw new RuntimeException(INVALID_MAP_EXPRESSION_MESSAGE + ": " + collectionExpression);
             }
         }
 
@@ -107,6 +110,9 @@ public class LoopsBinding {
                     "in task: '" + nodeName + "' " +
                     "doesn't return an iterable, other types are not supported");
         }
+        if (!forLoopCondition.hasMore()) {
+            throw new RuntimeException(FOR_LOOP_EXPRESSION_ERROR_MESSAGE + " '" + nodeName + "',\\n\\tError is: expression is empty");
+        }
         return forLoopCondition;
     }
 
diff --git a/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBindingTest.java b/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBindingTest.java
index a3a416d75..023f816c3 100644
--- a/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBindingTest.java
+++ b/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/AsyncLoopBindingTest.java
@@ -67,6 +67,21 @@ public class AsyncLoopBindingTest {
         assertEquals("returned async loop list not as expected", expectedList, actualList);
     }
 
+    @Test
+    public void testEmptyExpressionThrowsException() throws Exception {
+        Map<String, Serializable> variables = new HashMap<>();
+        variables.put("key1", "value1");
+        variables.put("key2", "value2");
+        Context context = new Context(variables);
+
+        when(scriptEvaluator.evalExpr(eq("expression"), eq(variables))).thenReturn(Lists.newArrayList());
+
+        exception.expectMessage("expression is empty");
+        exception.expect(RuntimeException.class);
+
+        asyncLoopBinding.bindAsyncLoopList(createBasicSyncLoopStatement(), context, "nodeName");
+    }
+
     @Test
     public void testExceptionIsPropagated() throws Exception {
         Map<String, Serializable> variables = new HashMap<>();
diff --git a/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/LoopsBindingTest.java b/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/LoopsBindingTest.java
index f1fdf3558..854a6aa1f 100644
--- a/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/LoopsBindingTest.java
+++ b/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/bindings/LoopsBindingTest.java
@@ -14,6 +14,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 import io.cloudslang.lang.entities.LoopStatement;
 import io.cloudslang.lang.runtime.env.Context;
 import io.cloudslang.lang.runtime.env.ForLoopCondition;
+import org.python.google.common.collect.Lists;
 
 import javax.script.ScriptEngine;
 import java.io.Serializable;
@@ -49,13 +50,27 @@ public class LoopsBindingTest {
     public void whenValueIsNotThereItWillBeCreated() throws Exception {
         Context context = mock(Context.class);
         when(scriptEvaluator.evalExpr(anyString(), anyMapOf(String.class, Serializable.class)))
-                .thenReturn(new ArrayList<>());
+                .thenReturn(Lists.newArrayList(1));
         HashMap<String, Serializable> langVars = new HashMap<>();
         when(context.getLangVariables()).thenReturn(langVars);
         loopsBinding.getOrCreateLoopCondition(createBasicForStatement(), context, "node");
         Assert.assertEquals(true, context.getLangVariables().containsKey(LoopCondition.LOOP_CONDITION_KEY));
     }
 
+    @Test
+    public void whenExpressionIsEmptyThrowsException() throws Exception {
+        Context context = mock(Context.class);
+        when(scriptEvaluator.evalExpr(anyString(), anyMapOf(String.class, Serializable.class)))
+                .thenReturn(Lists.newArrayList());
+        HashMap<String, Serializable> langVars = new HashMap<>();
+        when(context.getLangVariables()).thenReturn(langVars);
+
+        exception.expectMessage("expression is empty");
+        exception.expect(RuntimeException.class);
+
+        loopsBinding.getOrCreateLoopCondition(createBasicForStatement(), context, "node");
+    }
+
     @Test(expected = RuntimeException.class)
     public void passingNullLoopStatementThrowsException() throws Exception {
         loopsBinding.getOrCreateLoopCondition(null, mock(Context.class), "aa");
@@ -109,4 +124,5 @@ public class LoopsBindingTest {
         verify(context).putVariable("k", "john");
         verify(context).putVariable("v", 1);
     }
+
 }
\\ No newline at end of file