Repository: Qiskit/qiskit

Issue:
Add support for new IBMQJob states VALIDATING and ERROR_VALIDATING_JOB
### What is the expected behaviour?
Jobs should not fail when the backend returns `VALIDATING` and `ERROR_VALIDATING_JOB`.





Discussion:

Resolving patch:
diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py
--- a/qiskit/backends/ibmq/ibmqjob.py
+++ b/qiskit/backends/ibmq/ibmqjob.py
@@ -204,7 +204,11 @@ def _update_status(self):
             self._status_msg = '{}'.format(err)
             return None
 
-        if api_job['status'] == 'RUNNING':
+        if api_job['status'] == 'VALIDATING':
+            self._status = JobStatus.VALIDATING
+            self._status_msg = self._status.value
+
+        elif api_job['status'] == 'RUNNING':
             self._status = JobStatus.RUNNING
             self._status_msg = self._status.value
             queued, queue_position = self._is_job_queued(api_job)
@@ -224,7 +228,7 @@ def _update_status(self):
             self._cancelled = True
 
         elif 'ERROR' in api_job['status']:
-            # ERROR_CREATING_JOB or ERROR_RUNNING_JOB
+            # Errored status are of the form "ERROR_*_JOB"
             self._status = JobStatus.ERROR
             self._status_msg = api_job['status']
 
@@ -278,6 +282,19 @@ def running(self):
         """
         return self.status['status'] == JobStatus.RUNNING
 
+    @property
+    def validating(self):
+        """
+        Returns whether job is being validated
+
+        Returns:
+            bool: True if job is under validation, else False.
+
+        Raises:
+            QISKitError: couldn't get job status from server
+        """
+        return self.status['status'] == JobStatus.VALIDATING
+
     @property
     def done(self):
         """
diff --git a/qiskit/backends/jobstatus.py b/qiskit/backends/jobstatus.py
--- a/qiskit/backends/jobstatus.py
+++ b/qiskit/backends/jobstatus.py
@@ -14,6 +14,7 @@ class JobStatus(enum.Enum):
     """Class for job status enumerated type."""
     INITIALIZING = 'job is being initialized'
     QUEUED = 'job is queued'
+    VALIDATING = 'job is being validated'
     RUNNING = 'job is actively running'
     CANCELLED = 'job has been cancelled'
     DONE = 'job has successfully run'


Test patch:
diff --git a/test/python/test_ibmqjob_states.py b/test/python/test_ibmqjob_states.py
--- a/test/python/test_ibmqjob_states.py
+++ b/test/python/test_ibmqjob_states.py
@@ -38,6 +38,42 @@ def test_unrecognized_status(self):
         self.assertIsInstance(job.exception, IBMQJobError)
         self.assertStatus(job, JobStatus.ERROR)
 
+    def test_validating_job(self):
+        job = self.run_with_api(ValidatingAPI())
+        self.assertStatus(job, JobStatus.INITIALIZING)
+
+        self.wait_for_initialization(job)
+        self.assertStatus(job, JobStatus.VALIDATING)
+
+        self._current_api.progress()
+
+    def test_error_while_creating_job(self):
+        job = self.run_with_api(ErrorWhileCreatingAPI())
+        self.assertStatus(job, JobStatus.INITIALIZING)
+
+        self.wait_for_initialization(job)
+        self.assertStatus(job, JobStatus.ERROR)
+
+    def test_error_while_running_job(self):
+        job = self.run_with_api(ErrorWhileRunningAPI())
+        self.assertStatus(job, JobStatus.INITIALIZING)
+
+        self.wait_for_initialization(job)
+        self.assertStatus(job, JobStatus.RUNNING)
+
+        self._current_api.progress()
+        self.assertStatus(job, JobStatus.ERROR)
+
+    def test_error_while_validating_job(self):
+        job = self.run_with_api(ErrorWhileValidatingAPI())
+        self.assertStatus(job, JobStatus.INITIALIZING)
+
+        self.wait_for_initialization(job)
+        self.assertStatus(job, JobStatus.VALIDATING)
+
+        self._current_api.progress()
+        self.assertStatus(job, JobStatus.ERROR)
+
     def test_status_flow_for_non_queued_job(self):
         job = self.run_with_api(NonQueuedAPI())
         self.assertStatus(job, JobStatus.INITIALIZING)
@@ -218,6 +254,8 @@ def assertStatus(self, job, status):
             self.assertTrue(job.cancelled)
         elif status == JobStatus.DONE:
             self.assertTrue(job.done)
+        elif status == JobStatus.VALIDATING:
+            self.assertTrue(job.validating)
         elif status == JobStatus.RUNNING:
             self.assertTrue(job.running)
         elif status == JobStatus.QUEUED:
@@ -291,6 +329,24 @@ class UnknownStatusAPI(BaseFakeAPI):
     ]
 
 
+class ValidatingAPI(BaseFakeAPI):
+    """Class for emulating an API with job validation."""
+
+    _job_status = [
+        {'status': 'VALIDATING'},
+        {'status': 'RUNNING'}
+    ]
+
+
+class ErrorWhileValidatingAPI(BaseFakeAPI):
+    """Class for emulating an API processing an invalid job."""
+
+    _job_status = [
+        {'status': 'VALIDATING'},
+        {'status': 'ERROR_VALIDATING_JOB'}
+    ]
+
+
 class NonQueuedAPI(BaseFakeAPI):
     """Class for emulating a successfully-completed non-queued API."""
 
@@ -300,6 +356,24 @@ class NonQueuedAPI(BaseFakeAPI):
     ]
 
 
+class ErrorWhileCreatingAPI(BaseFakeAPI):
+    """Class emulating an API processing a job that errors while creating
+    the job."""
+
+    _job_status = [
+        {'status': 'ERROR_CREATING_JOB'}
+    ]
+
+
+class ErrorWhileRunningAPI(BaseFakeAPI):
+    """Class emulating an API processing a job that errors while running."""
+
+    _job_status = [
+        {'status': 'RUNNING'},
+        {'status': 'ERROR_RUNNING_JOB'}
+    ]
+
+
 class QueuedAPI(BaseFakeAPI):
     """Class for emulating a successfully-completed queued API."""