Repository: microsoft/Qcodes

Issue:
Parameter sets value when value is out of range
### Steps to reproduce
```
from qcodes import Parameter
from qcodes.utils.validators import Numbers

the_value = -10

def set_function(value):
    global the_value
    the_value = value
    print('set {0}'.format(the_value))

def get_function():
    print('get {0}'.format(the_value))
    return the_value

a = Parameter('test', set_cmd=set_function, get_cmd=get_function, vals=Numbers(0, 100), step=5)
a.set(10)
a.get()
```

### Expected behaviour
```
a.set(10) --> ValueError('-10 is invalid')
a.get() --> -10
```
### Actual behavior
```
a.set(10) --> ValueError('-5 is invalid')
a.get() --> -5
```
### System
**operating system**
Windows 10 x64, miniconda v 4.5.4 (venv)

**qcodes branch**
master

**qcodes commit**
8970a083c2b582e4d966da082f039fef49f2089a


@dpfranke, @eendebakpt 


Discussion:
The problem is that the wrapper validated the end point but does not take into account that the points in between generated by step may not be valid because the parameter is out of bounds for the validator before starting to step. 



Resolving patch:
diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py
--- a/qcodes/instrument/parameter.py
+++ b/qcodes/instrument/parameter.py
@@ -415,6 +415,9 @@ def set_wrapper(value, **kwargs):
                 steps = self.get_ramp_values(value, step=self.step)
 
                 for step_index, val_step in enumerate(steps):
+                    # even if the final value is valid we may be generating
+                    # steps that are not so validate them too
+                    self.validate(val_step)
                     if self.val_mapping is not None:
                         # Convert set values using val_mapping dictionary
                         raw_value = self.val_mapping[val_step]
@@ -460,10 +463,7 @@ def set_wrapper(value, **kwargs):
                     set_function(raw_value, **kwargs)
                     self.raw_value = raw_value
                     self._save_val(val_step,
-                                   validate=(self.val_mapping is None and
-                                             self.set_parser is None and
-                                             not(step_index == len(steps)-1 or
-                                                 len(steps) == 1)))
+                                   validate=False)
 
                     # Update last set time (used for calculating delays)
                     self._t_last_set = time.perf_counter()


Test patch:
diff --git a/qcodes/tests/test_parameter.py b/qcodes/tests/test_parameter.py
--- a/qcodes/tests/test_parameter.py
+++ b/qcodes/tests/test_parameter.py
@@ -4,6 +4,7 @@
 from collections import namedtuple, Iterable
 from unittest import TestCase
 from typing import Tuple
+import pytest
 
 import numpy as np
 from hypothesis import given, event, settings
@@ -14,7 +15,7 @@
     InstrumentRefParameter)
 import qcodes.utils.validators as vals
 from qcodes.tests.instrument_mocks import DummyInstrument
-
+from qcodes.utils.validators import Numbers
 
 
 class GettableParam(Parameter):
@@ -156,14 +157,16 @@ def test_number_of_validations(self):
 
         p = Parameter('p', set_cmd=None, initial_value=0,
                       vals=BookkeepingValidator())
-
-        self.assertEqual(p.vals.values_validated, [0])
+        # in the set wrapper the final value is validated
+        # and then subsequently each step is validated.
+        # in this case there is one step so the final value
+        # is validated twice.
+        self.assertEqual(p.vals.values_validated, [0, 0])
 
         p.step = 1
         p.set(10)
-
         self.assertEqual(p.vals.values_validated,
-                         [0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+                         [0, 0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
 
     def test_snapshot_value(self):
         p_snapshot = Parameter('no_snapshot', set_cmd=None, get_cmd=None,
@@ -436,6 +439,31 @@ def test_ramp_parsed_scaled(self, scale, value):
         np.testing.assert_allclose(p.get(), value)
         assert p.raw_value == -scale * value
 
+    def test_steppeing_from_invalid_starting_point(self):
+
+        the_value = -10
+
+        def set_function(value):
+            nonlocal the_value
+            the_value = value
+
+        def get_function():
+            return the_value
+
+        a = Parameter('test', set_cmd=set_function, get_cmd=get_function,
+                      vals=Numbers(0, 100), step=5)
+        # We start out by setting the parameter to an
+        # invalid value. This is not possible using initial_value
+        # as the validator will catch that but perhaps this may happen
+        # if the instrument can return out of range values.
+        assert a.get() == -10
+        with pytest.raises(ValueError):
+            # trying to set to 10 should raise even with 10 valid
+            # as the steps demand that we first step to -5 which is not
+            a.set(10)
+        # afterwards the value should still be the same
+        assert a.get() == -10
+
 
 class TestValsandParseParameter(TestCase):