1# Copyright 2020 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from unittest import mock
16import datetime
17
18import pytest
19import freezegun
20from google.protobuf.duration_pb2 import Duration
21from google.protobuf.text_format import Merge
22from google.protobuf.timestamp_pb2 import Timestamp
23import cirq
24import cirq_google as cg
25from cirq_google.api import v2
26from cirq_google.engine.engine import EngineContext
27from cirq_google.engine.client.quantum_v1alpha1 import enums as qenums
28from cirq_google.engine.client.quantum_v1alpha1 import types as qtypes
29
30
31def _to_any(proto):
32    any_proto = qtypes.any_pb2.Any()
33    any_proto.Pack(proto)
34    return any_proto
35
36
37def _to_timestamp(json_string):
38    timestamp_proto = qtypes.timestamp_pb2.Timestamp()
39    timestamp_proto.FromJsonString(json_string)
40    return timestamp_proto
41
42
43_CALIBRATION = qtypes.QuantumCalibration(
44    name='projects/a/processors/p/calibrations/1562715599',
45    timestamp=_to_timestamp('2019-07-09T23:39:59Z'),
46    data=_to_any(
47        Merge(
48            """
49    timestamp_ms: 1562544000021,
50    metrics: [{
51        name: 'xeb',
52        targets: ['0_0', '0_1'],
53        values: [{
54            double_val: .9999
55        }]
56    }, {
57        name: 'xeb',
58        targets: ['0_0', '1_0'],
59        values: [{
60            double_val: .9998
61        }]
62    }, {
63        name: 't1',
64        targets: ['0_0'],
65        values: [{
66            double_val: 321
67        }]
68    }, {
69        name: 't1',
70        targets: ['0_1'],
71        values: [{
72            double_val: 911
73        }]
74    }, {
75        name: 't1',
76        targets: ['1_0'],
77        values: [{
78            double_val: 505
79        }]
80    }, {
81        name: 'globalMetric',
82        values: [{
83            int32_val: 12300
84        }]
85    }]
86""",
87            v2.metrics_pb2.MetricsSnapshot(),
88        )
89    ),
90)
91
92_DEVICE_SPEC = _to_any(
93    Merge(
94        """
95valid_gate_sets: [{
96    name: 'test_set',
97    valid_gates: [{
98        id: 'x',
99        number_of_qubits: 1,
100        gate_duration_picos: 1000,
101        valid_targets: ['1q_targets']
102    }]
103}],
104valid_qubits: ['0_0', '1_1'],
105valid_targets: [{
106    name: '1q_targets',
107    target_ordering: SYMMETRIC,
108    targets: [{
109        ids: ['0_0']
110    }]
111}]
112""",
113        v2.device_pb2.DeviceSpecification(),
114    )
115)
116
117
118_GATE_SET = cg.SerializableGateSet(
119    gate_set_name='x_gate_set',
120    serializers=[cg.GateOpSerializer(gate_type=cirq.XPowGate, serialized_gate_id='x', args=[])],
121    deserializers=[
122        cg.GateOpDeserializer(serialized_gate_id='x', gate_constructor=cirq.XPowGate, args=[])
123    ],
124)
125
126
127@pytest.fixture(scope='session', autouse=True)
128def mock_grpc_client():
129    with mock.patch(
130        'cirq_google.engine.engine_client.quantum.QuantumEngineServiceClient'
131    ) as _fixture:
132        yield _fixture
133
134
135def test_engine():
136    processor = cg.EngineProcessor('a', 'p', EngineContext())
137    assert processor.engine().project_id == 'a'
138
139
140@mock.patch('cirq_google.engine.engine_client.EngineClient.get_processor')
141def test_health(get_processor):
142    get_processor.return_value = qtypes.QuantumProcessor(health=qtypes.QuantumProcessor.Health.OK)
143    processor = cg.EngineProcessor(
144        'a',
145        'p',
146        EngineContext(),
147        _processor=qtypes.QuantumProcessor(health=qtypes.QuantumProcessor.Health.DOWN),
148    )
149    assert processor.health() == 'OK'
150
151
152@mock.patch('cirq_google.engine.engine_client.EngineClient.get_processor')
153def test_expected_down_time(get_processor):
154    processor = cg.EngineProcessor('a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor())
155    assert not processor.expected_down_time()
156
157    get_processor.return_value = qtypes.QuantumProcessor(
158        expected_down_time=qtypes.timestamp_pb2.Timestamp(seconds=1581515101)
159    )
160
161    assert cg.EngineProcessor('a', 'p', EngineContext()).expected_down_time() == datetime.datetime(
162        2020, 2, 12, 13, 45, 1
163    )
164    get_processor.assert_called_once()
165
166
167def test_expected_recovery_time():
168    processor = cg.EngineProcessor('a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor())
169    assert not processor.expected_recovery_time()
170    processor = cg.EngineProcessor(
171        'a',
172        'p',
173        EngineContext(),
174        _processor=qtypes.QuantumProcessor(
175            expected_recovery_time=qtypes.timestamp_pb2.Timestamp(seconds=1581515101)
176        ),
177    )
178    assert processor.expected_recovery_time() == datetime.datetime(2020, 2, 12, 13, 45, 1)
179
180
181def test_supported_languages():
182    processor = cg.EngineProcessor('a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor())
183    assert processor.supported_languages() == []
184    processor = cg.EngineProcessor(
185        'a',
186        'p',
187        EngineContext(),
188        _processor=qtypes.QuantumProcessor(supported_languages=['lang1', 'lang2']),
189    )
190    assert processor.supported_languages() == ['lang1', 'lang2']
191
192
193def test_get_device_specification():
194    processor = cg.EngineProcessor('a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor())
195    assert not processor.get_device_specification()
196
197    # Construct expected device proto based on example
198    expected = v2.device_pb2.DeviceSpecification()
199    gs = expected.valid_gate_sets.add()
200    gs.name = 'test_set'
201    gates = gs.valid_gates.add()
202    gates.id = 'x'
203    gates.number_of_qubits = 1
204    gates.gate_duration_picos = 1000
205    gates.valid_targets.extend(['1q_targets'])
206    expected.valid_qubits.extend(['0_0', '1_1'])
207    target = expected.valid_targets.add()
208    target.name = '1q_targets'
209    target.target_ordering = v2.device_pb2.TargetSet.SYMMETRIC
210    new_target = target.targets.add()
211    new_target.ids.extend(['0_0'])
212
213    processor = cg.EngineProcessor(
214        'a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor(device_spec=_DEVICE_SPEC)
215    )
216    assert processor.get_device_specification() == expected
217
218
219def test_get_device():
220    processor = cg.EngineProcessor(
221        'a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor(device_spec=_DEVICE_SPEC)
222    )
223    device = processor.get_device(gate_sets=[_GATE_SET])
224    assert device.qubits == [cirq.GridQubit(0, 0), cirq.GridQubit(1, 1)]
225    device.validate_operation(cirq.X(cirq.GridQubit(0, 0)))
226    with pytest.raises(ValueError):
227        device.validate_operation(cirq.X(cirq.GridQubit(1, 2)))
228    with pytest.raises(ValueError):
229        device.validate_operation(cirq.Y(cirq.GridQubit(0, 0)))
230
231
232def test_get_missing_device():
233    processor = cg.EngineProcessor('a', 'p', EngineContext(), _processor=qtypes.QuantumProcessor())
234    with pytest.raises(ValueError, match='device specification'):
235        _ = processor.get_device(gate_sets=[_GATE_SET])
236
237
238@mock.patch('cirq_google.engine.engine_client.EngineClient.list_calibrations')
239def test_list_calibrations(list_calibrations):
240    list_calibrations.return_value = [_CALIBRATION]
241    processor = cg.EngineProcessor('a', 'p', EngineContext())
242    assert [c.timestamp for c in processor.list_calibrations()] == [1562544000021]
243    list_calibrations.assert_called_with('a', 'p', '')
244    assert [c.timestamp for c in processor.list_calibrations(1562500000000)] == [1562544000021]
245    list_calibrations.assert_called_with('a', 'p', 'timestamp >= 1562500000000')
246    assert [
247        c.timestamp for c in processor.list_calibrations(latest_timestamp_seconds=1562600000000)
248    ] == [1562544000021]
249    list_calibrations.assert_called_with('a', 'p', 'timestamp <= 1562600000000')
250    assert [c.timestamp for c in processor.list_calibrations(1562500000000, 1562600000000)] == [
251        1562544000021
252    ]
253    list_calibrations.assert_called_with(
254        'a', 'p', 'timestamp >= 1562500000000 AND timestamp <= 1562600000000'
255    )
256
257
258@mock.patch('cirq_google.engine.engine_client.EngineClient.get_calibration')
259def test_get_calibration(get_calibration):
260    get_calibration.return_value = _CALIBRATION
261    processor = cg.EngineProcessor('a', 'p', EngineContext())
262    calibration = processor.get_calibration(1562544000021)
263    assert calibration.timestamp == 1562544000021
264    assert set(calibration.keys()) == {'xeb', 't1', 'globalMetric'}
265    get_calibration.assert_called_once_with('a', 'p', 1562544000021)
266
267
268@mock.patch('cirq_google.engine.engine_client.EngineClient.get_current_calibration')
269def test_current_calibration(get_current_calibration):
270    get_current_calibration.return_value = _CALIBRATION
271    processor = cg.EngineProcessor('a', 'p', EngineContext())
272    calibration = processor.get_current_calibration()
273    assert calibration.timestamp == 1562544000021
274    assert set(calibration.keys()) == {'xeb', 't1', 'globalMetric'}
275    get_current_calibration.assert_called_once_with('a', 'p')
276
277
278@mock.patch('cirq_google.engine.engine_client.EngineClient.get_current_calibration')
279def test_missing_latest_calibration(get_current_calibration):
280    get_current_calibration.return_value = None
281    processor = cg.EngineProcessor('a', 'p', EngineContext())
282    assert not processor.get_current_calibration()
283    get_current_calibration.assert_called_once_with('a', 'p')
284
285
286@mock.patch('cirq_google.engine.engine_client.EngineClient.create_reservation')
287def test_create_reservation(create_reservation):
288    name = 'projects/proj/processors/p0/reservations/psherman-wallaby-way'
289    result = qtypes.QuantumReservation(
290        name=name,
291        start_time=Timestamp(seconds=1000000000),
292        end_time=Timestamp(seconds=1000003600),
293        whitelisted_users=['dstrain@google.com'],
294    )
295    create_reservation.return_value = result
296    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
297    assert processor.create_reservation(
298        datetime.datetime.fromtimestamp(1000000000),
299        datetime.datetime.fromtimestamp(1000003600),
300        ['dstrain@google.com'],
301    )
302    create_reservation.assert_called_once_with(
303        'proj',
304        'p0',
305        datetime.datetime.fromtimestamp(1000000000),
306        datetime.datetime.fromtimestamp(1000003600),
307        ['dstrain@google.com'],
308    )
309
310
311@mock.patch('cirq_google.engine.engine_client.EngineClient.delete_reservation')
312def test_delete_reservation(delete_reservation):
313    name = 'projects/proj/processors/p0/reservations/rid'
314    result = qtypes.QuantumReservation(
315        name=name,
316        start_time=Timestamp(seconds=1000000000),
317        end_time=Timestamp(seconds=1000003600),
318        whitelisted_users=['dstrain@google.com'],
319    )
320    delete_reservation.return_value = result
321    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
322    assert processor._delete_reservation('rid') == result
323    delete_reservation.assert_called_once_with('proj', 'p0', 'rid')
324
325
326@mock.patch('cirq_google.engine.engine_client.EngineClient.cancel_reservation')
327def test_cancel_reservation(cancel_reservation):
328    name = 'projects/proj/processors/p0/reservations/rid'
329    result = qtypes.QuantumReservation(
330        name=name,
331        start_time=Timestamp(seconds=1000000000),
332        end_time=Timestamp(seconds=1000003600),
333        whitelisted_users=['dstrain@google.com'],
334    )
335    cancel_reservation.return_value = result
336    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
337    assert processor._cancel_reservation('rid') == result
338    cancel_reservation.assert_called_once_with('proj', 'p0', 'rid')
339
340
341@mock.patch('cirq_google.engine.engine_client.EngineClient.get_reservation')
342@mock.patch('cirq_google.engine.engine_client.EngineClient.delete_reservation')
343def test_remove_reservation_delete(delete_reservation, get_reservation):
344    name = 'projects/proj/processors/p0/reservations/rid'
345    now = int(datetime.datetime.now().timestamp())
346    result = qtypes.QuantumReservation(
347        name=name,
348        start_time=Timestamp(seconds=now + 20000),
349        end_time=Timestamp(seconds=now + 23610),
350        whitelisted_users=['dstrain@google.com'],
351    )
352    get_reservation.return_value = result
353    delete_reservation.return_value = result
354    processor = cg.EngineProcessor(
355        'proj',
356        'p0',
357        EngineContext(),
358        qtypes.QuantumProcessor(schedule_frozen_period=Duration(seconds=10000)),
359    )
360    assert processor.remove_reservation('rid') == result
361    delete_reservation.assert_called_once_with('proj', 'p0', 'rid')
362
363
364@mock.patch('cirq_google.engine.engine_client.EngineClient.get_reservation')
365@mock.patch('cirq_google.engine.engine_client.EngineClient.cancel_reservation')
366def test_remove_reservation_cancel(cancel_reservation, get_reservation):
367    name = 'projects/proj/processors/p0/reservations/rid'
368    now = int(datetime.datetime.now().timestamp())
369    result = qtypes.QuantumReservation(
370        name=name,
371        start_time=Timestamp(seconds=now + 10),
372        end_time=Timestamp(seconds=now + 3610),
373        whitelisted_users=['dstrain@google.com'],
374    )
375    get_reservation.return_value = result
376    cancel_reservation.return_value = result
377    processor = cg.EngineProcessor(
378        'proj',
379        'p0',
380        EngineContext(),
381        qtypes.QuantumProcessor(schedule_frozen_period=Duration(seconds=10000)),
382    )
383    assert processor.remove_reservation('rid') == result
384    cancel_reservation.assert_called_once_with('proj', 'p0', 'rid')
385
386
387@mock.patch('cirq_google.engine.engine_client.EngineClient.get_reservation')
388def test_remove_reservation_not_found(get_reservation):
389    get_reservation.return_value = None
390    processor = cg.EngineProcessor(
391        'proj',
392        'p0',
393        EngineContext(),
394        qtypes.QuantumProcessor(schedule_frozen_period=Duration(seconds=10000)),
395    )
396    with pytest.raises(ValueError):
397        processor.remove_reservation('rid')
398
399
400@mock.patch('cirq_google.engine.engine_client.EngineClient.get_processor')
401@mock.patch('cirq_google.engine.engine_client.EngineClient.get_reservation')
402def test_remove_reservation_failures(get_reservation, get_processor):
403    name = 'projects/proj/processors/p0/reservations/rid'
404    now = int(datetime.datetime.now().timestamp())
405    result = qtypes.QuantumReservation(
406        name=name,
407        start_time=Timestamp(seconds=now + 10),
408        end_time=Timestamp(seconds=now + 3610),
409        whitelisted_users=['dstrain@google.com'],
410    )
411    get_reservation.return_value = result
412    get_processor.return_value = None
413
414    # no processor
415    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
416    with pytest.raises(ValueError):
417        processor.remove_reservation('rid')
418
419    # No freeze period defined
420    processor = cg.EngineProcessor('proj', 'p0', EngineContext(), qtypes.QuantumProcessor())
421    with pytest.raises(ValueError):
422        processor.remove_reservation('rid')
423
424
425@mock.patch('cirq_google.engine.engine_client.EngineClient.get_reservation')
426def test_get_reservation(get_reservation):
427    name = 'projects/proj/processors/p0/reservations/rid'
428    result = qtypes.QuantumReservation(
429        name=name,
430        start_time=Timestamp(seconds=1000000000),
431        end_time=Timestamp(seconds=1000003600),
432        whitelisted_users=['dstrain@google.com'],
433    )
434    get_reservation.return_value = result
435    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
436    assert processor.get_reservation('rid') == result
437    get_reservation.assert_called_once_with('proj', 'p0', 'rid')
438
439
440@mock.patch('cirq_google.engine.engine_client.EngineClient.update_reservation')
441def test_update_reservation(update_reservation):
442    name = 'projects/proj/processors/p0/reservations/rid'
443    result = qtypes.QuantumReservation(
444        name=name,
445        start_time=Timestamp(seconds=1000000000),
446        end_time=Timestamp(seconds=1000003600),
447        whitelisted_users=['dstrain@google.com'],
448    )
449    start = datetime.datetime.fromtimestamp(1000000000)
450    end = datetime.datetime.fromtimestamp(1000003600)
451    update_reservation.return_value = result
452    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
453    assert processor.update_reservation('rid', start, end, ['dstrain@google.com']) == result
454    update_reservation.assert_called_once_with(
455        'proj', 'p0', 'rid', start=start, end=end, whitelisted_users=['dstrain@google.com']
456    )
457
458
459@mock.patch('cirq_google.engine.engine_client.EngineClient.list_reservations')
460def test_list_reservation(list_reservations):
461    name = 'projects/proj/processors/p0/reservations/rid'
462    results = [
463        qtypes.QuantumReservation(
464            name=name,
465            start_time=Timestamp(seconds=1000000000),
466            end_time=Timestamp(seconds=1000003600),
467            whitelisted_users=['dstrain@google.com'],
468        ),
469        qtypes.QuantumReservation(
470            name=name + '2',
471            start_time=Timestamp(seconds=1000003600),
472            end_time=Timestamp(seconds=1000007200),
473            whitelisted_users=['wcourtney@google.com'],
474        ),
475    ]
476    list_reservations.return_value = results
477    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
478    assert (
479        processor.list_reservations(
480            datetime.datetime.fromtimestamp(1000000000), datetime.datetime.fromtimestamp(1000010000)
481        )
482        == results
483    )
484    list_reservations.assert_called_once_with(
485        'proj', 'p0', 'start_time < 1000010000 AND end_time > 1000000000'
486    )
487
488
489@mock.patch('cirq_google.engine.engine_client.EngineClient.list_time_slots')
490def test_get_schedule(list_time_slots):
491    results = [
492        qtypes.QuantumTimeSlot(
493            processor_name='potofgold',
494            start_time=Timestamp(seconds=1000020000),
495            end_time=Timestamp(seconds=1000040000),
496            slot_type=qenums.QuantumTimeSlot.TimeSlotType.MAINTENANCE,
497            maintenance_config=qtypes.QuantumTimeSlot.MaintenanceConfig(
498                title='Testing',
499                description='Testing some new configuration.',
500            ),
501        ),
502        qtypes.QuantumTimeSlot(
503            processor_name='potofgold',
504            start_time=Timestamp(seconds=1000010000),
505            end_time=Timestamp(seconds=1000020000),
506            slot_type=qenums.QuantumTimeSlot.TimeSlotType.RESERVATION,
507            reservation_config=qtypes.QuantumTimeSlot.ReservationConfig(
508                project_id='super_secret_quantum'
509            ),
510        ),
511    ]
512    list_time_slots.return_value = results
513    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
514    assert (
515        processor.get_schedule(
516            datetime.datetime.fromtimestamp(1000000000), datetime.datetime.fromtimestamp(1000050000)
517        )
518        == results
519    )
520    list_time_slots.assert_called_once_with(
521        'proj', 'p0', 'start_time < 1000050000 AND end_time > 1000000000'
522    )
523
524
525@mock.patch('cirq_google.engine.engine_client.EngineClient.list_time_slots')
526def test_get_schedule_filter_by_time_slot(list_time_slots):
527    results = [
528        qtypes.QuantumTimeSlot(
529            processor_name='potofgold',
530            start_time=Timestamp(seconds=1000020000),
531            end_time=Timestamp(seconds=1000040000),
532            slot_type=qenums.QuantumTimeSlot.TimeSlotType.MAINTENANCE,
533            maintenance_config=qtypes.QuantumTimeSlot.MaintenanceConfig(
534                title='Testing',
535                description='Testing some new configuration.',
536            ),
537        )
538    ]
539    list_time_slots.return_value = results
540    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
541
542    assert (
543        processor.get_schedule(
544            datetime.datetime.fromtimestamp(1000000000),
545            datetime.datetime.fromtimestamp(1000050000),
546            qenums.QuantumTimeSlot.TimeSlotType.MAINTENANCE,
547        )
548        == results
549    )
550    list_time_slots.assert_called_once_with(
551        'proj',
552        'p0',
553        'start_time < 1000050000 AND end_time > 1000000000 AND ' + 'time_slot_type = MAINTENANCE',
554    )
555
556
557def _allow_deprecated_freezegun(func):
558    # a local hack, as freeze_time walks through all the sys.modules, and retrieves all the
559    # attributes for all modules when it reaches deprecated module attributes, we throw an error
560    # as the deprecation module thinks Cirq is using something deprecated. This hack SHOULD NOT be
561    # used elsewhere, it is specific to freezegun functionality.
562    def wrapper(*args, **kwargs):
563        import os
564        from cirq.testing.deprecation import ALLOW_DEPRECATION_IN_TEST
565
566        orig_exist, orig_value = (
567            ALLOW_DEPRECATION_IN_TEST in os.environ,
568            os.environ.get(ALLOW_DEPRECATION_IN_TEST, None),
569        )
570
571        os.environ[ALLOW_DEPRECATION_IN_TEST] = 'True'
572        try:
573            return func(*args, **kwargs)
574        finally:
575            if orig_exist:
576                # mypy can't resolve that orig_exist ensures that orig_value
577                # of type Optional[str] can't be None
578                # coverage: ignore
579                os.environ[ALLOW_DEPRECATION_IN_TEST] = orig_value  # type: ignore
580            else:
581                del os.environ[ALLOW_DEPRECATION_IN_TEST]
582
583    return wrapper
584
585
586@_allow_deprecated_freezegun
587@freezegun.freeze_time()
588@mock.patch('cirq_google.engine.engine_client.EngineClient.list_time_slots')
589def test_get_schedule_time_filter_behavior(list_time_slots):
590    list_time_slots.return_value = []
591    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
592
593    now = int(datetime.datetime.now().timestamp())
594    in_two_weeks = int((datetime.datetime.now() + datetime.timedelta(weeks=2)).timestamp())
595    processor.get_schedule()
596    list_time_slots.assert_called_with(
597        'proj', 'p0', f'start_time < {in_two_weeks} AND end_time > {now}'
598    )
599
600    with pytest.raises(ValueError, match='from_time of type'):
601        processor.get_schedule(from_time=object())
602
603    with pytest.raises(ValueError, match='to_time of type'):
604        processor.get_schedule(to_time=object())
605
606    processor.get_schedule(from_time=None, to_time=None)
607    list_time_slots.assert_called_with('proj', 'p0', '')
608
609    processor.get_schedule(from_time=datetime.timedelta(0), to_time=None)
610    list_time_slots.assert_called_with('proj', 'p0', f'end_time > {now}')
611
612    processor.get_schedule(from_time=datetime.timedelta(seconds=200), to_time=None)
613    list_time_slots.assert_called_with('proj', 'p0', f'end_time > {now + 200}')
614
615    test_timestamp = datetime.datetime.utcfromtimestamp(52)
616    utc_ts = int(test_timestamp.timestamp())
617    processor.get_schedule(from_time=test_timestamp, to_time=None)
618    list_time_slots.assert_called_with('proj', 'p0', f'end_time > {utc_ts}')
619
620    processor.get_schedule(from_time=None, to_time=datetime.timedelta(0))
621    list_time_slots.assert_called_with('proj', 'p0', f'start_time < {now}')
622
623    processor.get_schedule(from_time=None, to_time=datetime.timedelta(seconds=200))
624    list_time_slots.assert_called_with('proj', 'p0', f'start_time < {now + 200}')
625
626    processor.get_schedule(from_time=None, to_time=test_timestamp)
627    list_time_slots.assert_called_with('proj', 'p0', f'start_time < {utc_ts}')
628
629
630@_allow_deprecated_freezegun
631@freezegun.freeze_time()
632@mock.patch('cirq_google.engine.engine_client.EngineClient.list_reservations')
633def test_list_reservations_time_filter_behavior(list_reservations):
634    list_reservations.return_value = []
635    processor = cg.EngineProcessor('proj', 'p0', EngineContext())
636
637    now = int(datetime.datetime.now().timestamp())
638    in_two_weeks = int((datetime.datetime.now() + datetime.timedelta(weeks=2)).timestamp())
639    processor.list_reservations()
640    list_reservations.assert_called_with(
641        'proj', 'p0', f'start_time < {in_two_weeks} AND end_time > {now}'
642    )
643
644    with pytest.raises(ValueError, match='from_time of type'):
645        processor.list_reservations(from_time=object())
646
647    with pytest.raises(ValueError, match='to_time of type'):
648        processor.list_reservations(to_time=object())
649
650    processor.list_reservations(from_time=None, to_time=None)
651    list_reservations.assert_called_with('proj', 'p0', '')
652
653    processor.list_reservations(from_time=datetime.timedelta(0), to_time=None)
654    list_reservations.assert_called_with('proj', 'p0', f'end_time > {now}')
655
656    processor.list_reservations(from_time=datetime.timedelta(seconds=200), to_time=None)
657    list_reservations.assert_called_with('proj', 'p0', f'end_time > {now + 200}')
658
659    test_timestamp = datetime.datetime.utcfromtimestamp(52)
660    utc_ts = int(test_timestamp.timestamp())
661    processor.list_reservations(from_time=test_timestamp, to_time=None)
662    list_reservations.assert_called_with('proj', 'p0', f'end_time > {utc_ts}')
663
664    processor.list_reservations(from_time=None, to_time=datetime.timedelta(0))
665    list_reservations.assert_called_with('proj', 'p0', f'start_time < {now}')
666
667    processor.list_reservations(from_time=None, to_time=datetime.timedelta(seconds=200))
668    list_reservations.assert_called_with('proj', 'p0', f'start_time < {now + 200}')
669
670    processor.list_reservations(from_time=None, to_time=test_timestamp)
671    list_reservations.assert_called_with('proj', 'p0', f'start_time < {utc_ts}')
672
673
674def test_str():
675    processor = cg.EngineProcessor('a', 'p', EngineContext())
676    assert str(processor) == 'EngineProcessor(project_id=\'a\', processor_id=\'p\')'
677