def test_eta_min_is_ignored_on_first_poll(self):
        "eta_min/earliest_estimated_completion should not be used anymore"

        with Client('endpoint', 'token') as client:
            now = datetime_in_future(0)
            eta_min, eta_max = datetime_in_future(10), datetime_in_future(30)
            client.session = mock.Mock()
            client.session.post = lambda path, _: choose_reply(path, {
                'endpoint/problems/':
                '[%s]' % continue_reply(
                    '1', 'abc123', eta_min=eta_min, eta_max=eta_max, now=now)
            },
                                                               date=now)
            client.session.get = lambda path: choose_reply(path, {
                'endpoint/problems/?id=1':
                '[%s]' % complete_no_answer_reply('1', 'abc123'),
                'endpoint/problems/1/':
                complete_reply('1', 'abc123')
            },
                                                           date=now)

            solver = Solver(client, solver_data('abc123'))

            def assert_no_delay(s):
                s and self.assertTrue(
                    abs(s - client._POLL_BACKOFF_MIN) <
                    client._POLL_BACKOFF_MIN / 10.0)

            with mock.patch('time.sleep', assert_no_delay):
                future = solver.sample_qubo({})
                future.result()
Beispiel #2
0
    def test_upload_failure(self):
        """Submit should gracefully fail if upload as part of submit fails."""

        # build a test problem
        bqm = dimod.BQM.from_ising({}, {'ab': 1})

        # use a global mocked session, so we can modify it on-fly
        session = mock.Mock()

        # upload is now part of submit, so we need to mock it
        mock_upload_exc = ValueError('error')
        def mock_upload(self, bqm):
            return Present(exception=mock_upload_exc)

        # construct a functional solver by mocking client and api response data
        with mock.patch.object(Client, 'create_session', lambda self: session):
            with Client('endpoint', 'token') as client:
                with mock.patch.object(UnstructuredSolver, 'upload_bqm', mock_upload):
                    solver = UnstructuredSolver(client, unstructured_solver_data())

                    # direct bqm sampling
                    ss = dimod.ExactSolver().sample(bqm)
                    session.post = lambda path, _: choose_reply(
                        path, {'problems/': complete_reply(ss)})

                    fut = solver.sample_bqm(bqm)

                    with self.assertRaises(type(mock_upload_exc)):
                        fut.result()
Beispiel #3
0
    def test_many_upload_failures(self):
        """Failure handling in high concurrency mode works correctly."""

        # build a test problem
        bqm = dimod.BQM.from_ising({}, {'ab': 1})

        # use a global mocked session, so we can modify it on-fly
        session = mock.Mock()

        # upload is now part of submit, so we need to mock it
        mock_upload_exc = ValueError('error')
        def mock_upload(self, bqm):
            return Present(exception=mock_upload_exc)

        # construct a functional solver by mocking client and api response data
        with mock.patch.object(Client, 'create_session', lambda self: session):
            with Client('endpoint', 'token') as client:
                with mock.patch.object(UnstructuredSolver, 'upload_bqm', mock_upload):
                    solver = UnstructuredSolver(client, unstructured_solver_data())

                    futs = [solver.sample_bqm(bqm) for _ in range(100)]

                    for fut in futs:
                        with self.assertRaises(type(mock_upload_exc)):
                            fut.result()
Beispiel #4
0
    def test_cancel_with_id(self):
        """Make sure the cancel method submits to the right endpoint.

        When cancel is called after the submission is finished.
        """
        submission_id = 'test-id'
        reply_body = '[%s]' % continue_reply(submission_id, 'solver')

        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()

            client.session.get = lambda a: choose_reply(
                a,
                {'endpoint/problems/?id={}'.format(submission_id): reply_body})
            client.session.delete = DeleteEvent.handle

            solver = Solver(client, solver_data('abc123'))
            future = solver.retrieve_problem(submission_id)
            future.cancel()

            try:
                self.assertTrue(future.id is not None)
                future.samples
                self.fail()
            except DeleteEvent as event:
                if event.url == 'endpoint/problems/':
                    self.assertEqual(event.body,
                                     '["{}"]'.format(submission_id))
                else:
                    self.assertEqual(
                        event.url,
                        'endpoint/problems/{}/'.format(submission_id))
    def test_submit_continue_then_ok_reply(self):
        """Handle polling for a complete problem."""
        with Client('endpoint', 'token') as client:
            now = datetime_in_future(0)
            eta_min, eta_max = datetime_in_future(10), datetime_in_future(30)
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(a, {
                'endpoint/problems/':
                '[%s]' % continue_reply(
                    '123', 'abc123', eta_min=eta_min, eta_max=eta_max, now=now)
            },
                                                            date=now)
            client.session.get = lambda a: choose_reply(a, {
                'endpoint/problems/?id=123':
                '[%s]' % complete_no_answer_reply('123', 'abc123'),
                'endpoint/problems/123/':
                complete_reply('123', 'abc123')
            },
                                                        date=now)
            solver = Solver(client, solver_data('abc123'))

            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            results = solver.sample_ising(linear, quad, num_reads=100)

            self._check(results, linear, quad, 100)

            # test future has eta_min and eta_max parsed correctly
            self.assertEqual(results.eta_min, eta_min)
            self.assertEqual(results.eta_max, eta_max)
Beispiel #6
0
    def test_exponential_backoff_polling(self):
        "After each poll, back-off should double"

        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            # on submit, return status pending
            client.session.post = lambda path, _: choose_reply(path, {
                'endpoint/problems/': '[%s]' % continue_reply('123', 'abc123')
            })
            # on first and second status poll, return pending
            # on third status poll, return completed
            def continue_then_complete(path, state={'count': 0}):
                state['count'] += 1
                if state['count'] < 3:
                    return choose_reply(path, {
                        'endpoint/problems/?id=123': '[%s]' % continue_reply('123', 'abc123'),
                        'endpoint/problems/123/': continue_reply('123', 'abc123')
                    })
                else:
                    return choose_reply(path, {
                        'endpoint/problems/?id=123': '[%s]' % complete_no_answer_reply('123', 'abc123'),
                        'endpoint/problems/123/': complete_reply('123', 'abc123')
                    })

            client.session.get = continue_then_complete

            solver = Solver(client, solver_data('abc123'))

            future = solver.sample_qubo({})
            future.result()

            # after third poll, back-off interval should be 4 x initial back-off
            self.assertEqual(future._poll_backoff, Client._POLL_BACKOFF_MIN * 2**2)
Beispiel #7
0
        def global_mock_session():
            session = mock.Mock()

            # on submit, return status pending
            session.post = lambda path, _: choose_reply(
                path, {'problems/': '[%s]' % continue_reply('123', 'abc123')})

            # on first and second status poll, return pending
            # on third status poll, return completed
            def continue_then_complete(path, state={'count': 0}):
                state['count'] += 1
                if state['count'] < 3:
                    return choose_reply(
                        path, {
                            'problems/?id=123':
                            '[%s]' % continue_reply('123', 'abc123'),
                            'problems/123/':
                            continue_reply('123', 'abc123')
                        })
                else:
                    return choose_reply(
                        path, {
                            'problems/?id=123':
                            '[%s]' % complete_no_answer_reply('123', 'abc123'),
                            'problems/123/':
                            complete_reply('123', 'abc123')
                        })

            session.get = continue_then_complete

            return session
Beispiel #8
0
 def create_mock_session(client):
     session = mock.Mock()
     reply_body = '[%s]' % continue_reply(submission_id, 'solver')
     session.get = lambda a: choose_reply(
         a, {'problems/?id={}'.format(submission_id): reply_body})
     session.delete = DeleteEvent.handle
     return session
    def test_immediate_polling_with_local_clock_unsynced(self):
        """First poll happens with minimal delay if local clock is way off from
        the remote/server clock."""

        with Client('endpoint', 'token') as client:
            badnow = datetime_in_future(100)
            client.session = mock.Mock()
            client.session.post = lambda path, _: choose_reply(
                path,
                {'endpoint/problems/': '[%s]' % continue_reply('1', 'abc123')},
                date=badnow)
            client.session.get = lambda path: choose_reply(path, {
                'endpoint/problems/?id=1':
                '[%s]' % complete_no_answer_reply('1', 'abc123'),
                'endpoint/problems/1/':
                complete_reply('1', 'abc123')
            },
                                                           date=badnow)

            solver = Solver(client, solver_data('abc123'))

            def assert_no_delay(s):
                s and self.assertTrue(
                    abs(s - client._POLL_BACKOFF_MIN) <
                    client._POLL_BACKOFF_MIN / 10.0)

            with mock.patch('time.sleep', assert_no_delay):
                future = solver.sample_qubo({})
                future.result()
Beispiel #10
0
 def create_mock_session(client):
     session = mock.Mock()
     session.post = lambda a, _: choose_reply(
         a, {
             'problems/':
             '[%s]' % error_reply('123', 'abc123', 'An error message')
         })
     return session
Beispiel #11
0
 def create_mock_session(client):
     session = mock.Mock()
     session.post = lambda a, _: choose_reply(a, {
         'problems/':
         '[%s]' % complete_no_answer_reply('123', 'abc123')
     })
     session.get = lambda a: choose_reply(
         a, {'problems/123/': complete_reply('123', 'abc123')})
     return session
Beispiel #12
0
 def create_mock_session(client):
     session = mock.Mock()
     session.post = lambda a, _: choose_reply(
         a, {
             'problems/':
             '[%s]' % immediate_error_reply(
                 400, "Missing parameter 'num_reads' in problem JSON")
         })
     return session
Beispiel #13
0
def choose_reply(path, replies):
    """Choose the right response based on the path and make a mock response."""
    if path in replies:
        response = mock.Mock(['json', 'raise_for_status'])
        response.status_code = 200
        response.json.side_effect = lambda: json.loads(replies[path])
        return response
    else:
        raise NotImplementedError(path)
Beispiel #14
0
 def create_mock_session(client):
     session = mock.Mock()
     session.post = lambda a, _: choose_reply(
         a, {'problems/': '[%s]' % continue_reply('123', 'abc123')})
     session.get = lambda a: choose_reply(
         a, {
             'problems/?id=123':
             '[%s]' % error_reply('123', 'abc123', "error message")
         })
     return session
Beispiel #15
0
    def test_submit_continue_then_ok_and_error_reply(self):
        """Handle polling for the status of multiple problems."""

        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()

            # on first status poll, return pending for both problems
            # on second status poll, return error for first problem and complete for second
            def continue_then_complete(path, state={'count': 0}):
                state['count'] += 1
                if state['count'] < 2:
                    return choose_reply(path, {
                        'endpoint/problems/?id=1': '[{}]'.format(continue_reply('1', 'abc123')),
                        'endpoint/problems/?id=2': '[{}]'.format(continue_reply('2', 'abc123')),
                        'endpoint/problems/1/': continue_reply('1', 'abc123'),
                        'endpoint/problems/2/': continue_reply('2', 'abc123'),
                        'endpoint/problems/?id=1,2': '[{},{}]'.format(continue_reply('1', 'abc123'),
                                                                      continue_reply('2', 'abc123')),
                        'endpoint/problems/?id=2,1': '[{},{}]'.format(continue_reply('2', 'abc123'),
                                                                      continue_reply('1', 'abc123'))
                    })
                else:
                    return choose_reply(path, {
                        'endpoint/problems/?id=1': '[{}]'.format(error_reply('1', 'abc123', 'error')),
                        'endpoint/problems/?id=2': '[{}]'.format(complete_no_answer_reply('2', 'abc123')),
                        'endpoint/problems/1/': error_reply('1', 'abc123', 'error'),
                        'endpoint/problems/2/': complete_reply('2', 'abc123'),
                        'endpoint/problems/?id=1,2': '[{},{}]'.format(error_reply('1', 'abc123', 'error'),
                                                                      complete_no_answer_reply('2', 'abc123')),
                        'endpoint/problems/?id=2,1': '[{},{}]'.format(complete_no_answer_reply('2', 'abc123'),
                                                                      error_reply('1', 'abc123', 'error'))
                    })

            client.session.get = continue_then_complete

            def accept_problems_with_continue_reply(path, body, ids=iter('12')):
                problems = json.loads(body)
                return choose_reply(path, {
                    'endpoint/problems/': json.dumps(
                        [json.loads(continue_reply(next(ids), 'abc123')) for _ in problems])
                })

            client.session.post = accept_problems_with_continue_reply

            solver = Solver(client, solver_data('abc123'))

            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}

            results1 = solver.sample_ising(linear, quad, num_reads=100)
            results2 = solver.sample_ising(linear, quad, num_reads=100)

            with self.assertRaises(SolverFailureError):
                self._check(results1, linear, quad, 100)
            self._check(results2, linear, quad, 100)
Beispiel #16
0
    def test_submit_immediate_reply(self):
        """Construction of and sampling from an unstructured solver works."""

        # build a test problem
        bqm = dimod.BQM.from_ising({}, {'ab': 1})

        # use a global mocked session, so we can modify it on-fly
        session = mock.Mock()

        # construct a functional solver by mocking client and api response data
        with mock.patch.object(Client, 'create_session', lambda self: session):
            with Client('endpoint', 'token') as client:
                solver = UnstructuredSolver(client, unstructured_solver_data())

                # direct bqm sampling
                ss = dimod.ExactSolver().sample(bqm)
                session.post = lambda path, _: choose_reply(
                    path, {'problems/': complete_reply(ss)})

                fut = solver.sample_bqm(bqm)
                numpy.testing.assert_array_equal(fut.sampleset, ss)
                numpy.testing.assert_array_equal(fut.samples, ss.record.sample)
                numpy.testing.assert_array_equal(fut.energies,
                                                 ss.record.energy)
                numpy.testing.assert_array_equal(fut.occurrences,
                                                 ss.record.num_occurrences)

                # ising sampling
                lin, quad, _ = bqm.to_ising()
                ss = dimod.ExactSolver().sample_ising(lin, quad)
                session.post = lambda path, _: choose_reply(
                    path, {'problems/': complete_reply(ss)})

                fut = solver.sample_ising(lin, quad)
                numpy.testing.assert_array_equal(fut.sampleset, ss)
                numpy.testing.assert_array_equal(fut.samples, ss.record.sample)
                numpy.testing.assert_array_equal(fut.energies,
                                                 ss.record.energy)
                numpy.testing.assert_array_equal(fut.occurrences,
                                                 ss.record.num_occurrences)

                # qubo sampling
                qubo, _ = bqm.to_qubo()
                ss = dimod.ExactSolver().sample_qubo(qubo)
                session.post = lambda path, _: choose_reply(
                    path, {'problems/': complete_reply(ss)})

                fut = solver.sample_qubo(qubo)
                numpy.testing.assert_array_equal(fut.sampleset, ss)
                numpy.testing.assert_array_equal(fut.samples, ss.record.sample)
                numpy.testing.assert_array_equal(fut.energies,
                                                 ss.record.energy)
                numpy.testing.assert_array_equal(fut.occurrences,
                                                 ss.record.num_occurrences)
Beispiel #17
0
        def create_mock_session(client):
            session = mock.Mock()

            # delayed submit; emulates waiting in queue
            def post(path, _):
                release_reply.wait()
                reply_body = complete_reply(submission_id, solver_name)
                return choose_reply(path, {'problems/': '[%s]' % reply_body})

            session.post = post

            return session
def choose_reply(path, replies, date=None):
    """Choose the right response based on the path and make a mock response."""

    if date is None:
        date = datetime_in_future(0)

    if path in replies:
        response = mock.Mock(['json', 'raise_for_status', 'headers'])
        response.status_code = 200
        response.json.side_effect = lambda: json.loads(replies[path])
        response.headers = CaseInsensitiveDict({'Date': date.isoformat()})
        return response
    else:
        raise NotImplementedError(path)
Beispiel #19
0
    def test_func_called_only_until_succeeds(self):
        """Wrapped function is called no more times then it takes to succeed."""

        err = ValueError
        val = mock.sentinel
        attrs = dict(__name__='f')

        # f succeeds on 3rd try
        f = mock.Mock(side_effect=[err, err, val.a, val.b], **attrs)
        ret = retried(3)(f)()
        self.assertEqual(ret, val.a)
        self.assertEqual(f.call_count, 3)

        # fail with only on retry
        f = mock.Mock(side_effect=[err, err, val.a, val.b], **attrs)
        with self.assertRaises(err):
            ret = retried(1)(f)()

        # no errors, return without retries
        f = mock.Mock(side_effect=[val.a, val.b, val.c], **attrs)
        ret = retried(3)(f)()
        self.assertEqual(ret, val.a)
        self.assertEqual(f.call_count, 1)
Beispiel #20
0
 def create_mock_session(client):
     badnow = datetime_in_future(100)
     session = mock.Mock()
     session.post = lambda path, _: choose_reply(
         path, {'problems/': '[%s]' % continue_reply('1', 'abc123')},
         date=badnow)
     session.get = lambda path: choose_reply(path, {
         'problems/?id=1':
         '[%s]' % complete_no_answer_reply('1', 'abc123'),
         'problems/1/':
         complete_reply('1', 'abc123')
     },
                                             date=badnow)
     return session
Beispiel #21
0
    def test_submit_cancel_reply(self):
        """Handle a response for a canceled job."""
        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(a, {'endpoint/problems/': '[%s]' % cancel_reply('123', 'abc123')})
            solver = Solver(client, solver_data('abc123'))

            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            results = solver.sample_ising(linear, quad, num_reads=100)

            with self.assertRaises(CanceledFutureError):
                results.samples
Beispiel #22
0
    def test_submit_null_reply(self):
        """Get an error when the server's response is incomplete."""
        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(a, {'endpoint/problems/': ''})
            solver = Solver(client, solver_data('abc123'))

            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            results = solver.sample_ising(linear, quad, num_reads=100)

            with self.assertRaises(ValueError):
                results.samples
    def test_polling_recovery_after_5xx(self):
        "Polling shouldn't be aborted on 5xx responses."

        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            # on submit, return status pending
            client.session.post = lambda path, _: choose_reply(
                path, {
                    'endpoint/problems/': '[%s]' % continue_reply(
                        '123', 'abc123')
                })
            # on first and second status poll, fail with 503 and 504
            # on third status poll, return completed
            statuses = iter([503, 504])

            def continue_then_complete(path, state={'count': 0}):
                state['count'] += 1
                if state['count'] < 3:
                    return choose_reply(
                        path,
                        replies={
                            'endpoint/problems/?id=123':
                            '[%s]' % continue_reply('123', 'abc123'),
                            'endpoint/problems/123/':
                            continue_reply('123', 'abc123')
                        },
                        statuses={
                            'endpoint/problems/?id=123': statuses,
                            'endpoint/problems/123/': statuses
                        })
                else:
                    return choose_reply(
                        path, {
                            'endpoint/problems/?id=123':
                            '[%s]' % complete_no_answer_reply('123', 'abc123'),
                            'endpoint/problems/123/':
                            complete_reply('123', 'abc123')
                        })

            client.session.get = continue_then_complete

            solver = Solver(client, solver_data('abc123'))

            future = solver.sample_qubo({})
            future.result()

            # after third poll, back-off interval should be 4 x initial back-off
            self.assertEqual(future._poll_backoff,
                             Client._POLL_BACKOFF_MIN * 2**2)
Beispiel #24
0
    def test_submit_ok_reply(self):
        """Handle a normal query and response."""
        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(a, {
                'endpoint/problems/': '[%s]' % complete_no_answer_reply('123', 'abc123')})
            client.session.get = lambda a: choose_reply(a, {'endpoint/problems/123/': complete_reply('123', 'abc123')})
            solver = Solver(client, solver_data('abc123'))

            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            results = solver.sample_ising(linear, quad, num_reads=100)

            self._check(results, linear, quad, 100)
Beispiel #25
0
    def test_submit_error_reply(self):
        """Handle an error on problem submission."""
        error_body = 'An error message'
        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(a, {
                'endpoint/problems/': '[%s]' % error_reply('123', 'abc123', error_body)})
            solver = Solver(client, solver_data('abc123'))

            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            results = solver.sample_ising(linear, quad, num_reads=100)

            with self.assertRaises(SolverFailureError):
                results.samples
Beispiel #26
0
 def create_mock_session(client):
     session = mock.Mock()
     session.post = lambda a, _: choose_reply(a, {
         'problems/':
         '[%s]' % continue_reply(
             '123', 'abc123', eta_min=eta_min, eta_max=eta_max, now=now)
     },
                                              date=now)
     session.get = lambda a: choose_reply(a, {
         'problems/?id=123':
         '[%s]' % complete_no_answer_reply('123', 'abc123'),
         'problems/123/':
         complete_reply('123', 'abc123')
     },
                                          date=now)
     return session
Beispiel #27
0
    def test_submit_continue_then_error_reply(self):
        """Handle polling for an error message."""
        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(a, {'endpoint/problems/': '[%s]' % continue_reply('123', 'abc123')})
            client.session.get = lambda a: choose_reply(a, {
                'endpoint/problems/?id=123': '[%s]' % error_reply('123', 'abc123', "error message")})
            solver = Solver(client, solver_data('abc123'))

            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            results = solver.sample_ising(linear, quad, num_reads=100)

            with self.assertRaises(SolverFailureError):
                self._check(results, linear, quad, 100)
    def test_submit_immediate_error_reply(self):
        """Handle an (obvious) error on problem submission."""
        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda a, _: choose_reply(
                a, {
                    'endpoint/problems/':
                    '[%s]' % immediate_error_reply(
                        400, "Missing parameter 'num_reads' in problem JSON")
                })
            solver = Solver(client, solver_data('abc123'))

            linear, quad = generate_random_ising_problem(solver)
            results = solver.sample_ising(linear, quad)

            with self.assertRaises(SolverFailureError):
                results.samples
Beispiel #29
0
    def test_cancel_without_id(self):
        """Make sure the cancel method submits to the right endpoint.

        When cancel is called before the submission has returned the problem id.
        """
        submission_id = 'test-id'
        reply_body = '[%s]' % continue_reply(submission_id, 'solver')

        release_reply = threading.Event()

        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.get = lambda a: choose_reply(
                a,
                {'endpoint/problems/?id={}'.format(submission_id): reply_body})

            def post(a, _):
                release_reply.wait()
                return choose_reply(a, {'endpoint/problems/': reply_body})

            client.session.post = post
            client.session.delete = DeleteEvent.handle

            solver = Solver(client, solver_data('abc123'))
            # Build a problem
            linear = {index: 1 for index in solver.nodes}
            quad = {key: -1 for key in solver.undirected_edges}
            future = solver.sample_ising(linear, quad)
            future.cancel()

            try:
                release_reply.set()
                future.samples
                self.fail()
            except DeleteEvent as event:
                if event.url == 'endpoint/problems/':
                    self.assertEqual(event.body,
                                     '["{}"]'.format(submission_id))
                else:
                    self.assertEqual(
                        event.url,
                        'endpoint/problems/{}/'.format(submission_id))
Beispiel #30
0
    def test_immediate_polling_without_eta_min(self):
        "First poll happens with minimal delay if eta_min missing"

        with Client('endpoint', 'token') as client:
            client.session = mock.Mock()
            client.session.post = lambda path, _: choose_reply(path, {
                'endpoint/problems/': '[%s]' % continue_reply('1', 'abc123')
            })
            client.session.get = lambda path: choose_reply(path, {
                'endpoint/problems/?id=1': '[%s]' % complete_no_answer_reply('1', 'abc123'),
                'endpoint/problems/1/': complete_reply('1', 'abc123')
            })

            solver = Solver(client, solver_data('abc123'))

            def assert_no_delay(s):
                s and self.assertTrue(
                    abs(s - client._POLL_BACKOFF_MIN) < client._POLL_BACKOFF_MIN / 10.0)

            with mock.patch('time.sleep', assert_no_delay):
                future = solver.sample_qubo({})
                future.result()