Repository: easybuilders/easybuild-framework

Issue:
self.iter_idx is bumped early
While working on https://github.com/easybuilders/easybuild-easyblocks/pull/1718 I noticed that `self.iter_idx` is bumped before the iteration is actually fully complete (see https://github.com/easybuilders/easybuild-framework/blob/706a40b77087952f3a61a200ca6f7ddf16b1525d/easybuild/framework/easyblock.py#L1556).

Can we add another variable that gets set at the beginning of the same function? This would make leveraging the value more consistent in easyblocks. For example:
```
self.curr_idx = self.iter_idx
```
done anywhere before the bump.


Discussion:
I'll open a PR and we can debate there I guess

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -1526,6 +1526,11 @@ def guess_start_dir(self):
     def handle_iterate_opts(self):
         """Handle options relevant during iterated part of build/install procedure."""
 
+        # if we were iterating already, bump iteration index
+        if self.cfg.iterating:
+            self.log.info("Done with iteration #%d!", self.iter_idx)
+            self.iter_idx += 1
+
         # disable templating in this function, since we're messing about with values in self.cfg
         prev_enable_templating = self.cfg.enable_templating
         self.cfg.enable_templating = False
@@ -1561,9 +1566,6 @@ def handle_iterate_opts(self):
         # re-enable templating before self.cfg values are used
         self.cfg.enable_templating = prev_enable_templating
 
-        # prepare for next iteration (if any)
-        self.iter_idx += 1
-
     def post_iter_step(self):
         """Restore options that were iterated over"""
         # disable templating, since we're messing about with values in self.cfg


Test patch:
diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py
--- a/test/framework/easyblock.py
+++ b/test/framework/easyblock.py
@@ -665,6 +665,65 @@ def test_det_iter_cnt(self):
         error_pattern = "lists for iterated build should have same length"
         self.assertErrorRegex(EasyBuildError, error_pattern, EasyConfig, self.eb_file)
 
+    def test_handle_iterate_opts(self):
+        """Test for handle_iterate_opts method."""
+        testdir = os.path.abspath(os.path.dirname(__file__))
+        toy_ec = os.path.join(testdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')
+
+        test_ec = os.path.join(self.test_prefix, 'test.eb')
+        write_file(test_ec, read_file(toy_ec) + "\nconfigopts = ['--opt1 --anotheropt', '--opt2', '--opt3 --optbis']")
+
+        ec = process_easyconfig(test_ec)[0]
+        eb = get_easyblock_instance(ec)
+
+        # check initial state
+        self.assertEqual(eb.iter_idx, 0)
+        self.assertEqual(eb.iter_opts, {})
+        self.assertEqual(eb.cfg.iterating, False)
+        self.assertEqual(eb.cfg.iterate_options, [])
+        self.assertEqual(eb.cfg['configopts'], ["--opt1 --anotheropt", "--opt2", "--opt3 --optbis"])
+
+        expected_iter_opts = {'configopts': ["--opt1 --anotheropt", "--opt2", "--opt3 --optbis"]}
+
+        # once iteration mode is set, we're still in iteration #0
+        self.mock_stdout(True)
+        eb.handle_iterate_opts()
+        stdout = self.get_stdout()
+        self.mock_stdout(False)
+        self.assertEqual(eb.iter_idx, 0)
+        self.assertEqual(stdout, "== starting iteration #0 ...\n")
+        self.assertEqual(eb.cfg.iterating, True)
+        self.assertEqual(eb.cfg.iterate_options, ['configopts'])
+        self.assertEqual(eb.cfg['configopts'], "--opt1 --anotheropt")
+        self.assertEqual(eb.iter_opts, expected_iter_opts)
+
+        # when next iteration is start, iteration index gets bumped
+        self.mock_stdout(True)
+        eb.handle_iterate_opts()
+        stdout = self.get_stdout()
+        self.mock_stdout(False)
+        self.assertEqual(eb.iter_idx, 1)
+        self.assertEqual(stdout, "== starting iteration #1 ...\n")
+        self.assertEqual(eb.cfg.iterating, True)
+        self.assertEqual(eb.cfg.iterate_options, ['configopts'])
+        self.assertEqual(eb.cfg['configopts'], "--opt2")
+        self.assertEqual(eb.iter_opts, expected_iter_opts)
+
+        self.mock_stdout(True)
+        eb.handle_iterate_opts()
+        stdout = self.get_stdout()
+        self.mock_stdout(False)
+        self.assertEqual(eb.iter_idx, 2)
+        self.assertEqual(stdout, "== starting iteration #2 ...\n")
+        self.assertEqual(eb.cfg.iterating, True)
+        self.assertEqual(eb.cfg.iterate_options, ['configopts'])
+        self.assertEqual(eb.cfg['configopts'], "--opt3 --optbis")
+        self.assertEqual(eb.iter_opts, expected_iter_opts)
+
+        eb.post_iter_step()
+        self.assertEqual(eb.cfg.iterating, False)
+        self.assertEqual(eb.cfg['configopts'], ["--opt1 --anotheropt", "--opt2", "--opt3 --optbis"])
+
     def test_extensions_step(self):
         """Test the extensions_step"""
         init_config(build_options={'silent': True})