def test_active_faults():
    # Check that active faults are managed correctly.

    visual_src = VisualImageSource("source1", FakeCamera(capture_with_error, decode_with_error))
    fault_client = MockFaultClient()
    visual_src.initialize_faults(fault_client, "service1")
    # Initialize function calls the clear_service_fault RPC.
    assert fault_client.clear_service_fault_called_count == 1
    assert len(visual_src.active_fault_id_names) == 0

    fake_fault_id = service_fault_pb2.ServiceFaultId(fault_name="Fake Capture Failure")
    fake_fault = service_fault_pb2.ServiceFault(
        fault_id=fake_fault_id, severity=service_fault_pb2.ServiceFault.SEVERITY_WARN)

    # Make sure we don't call clear_service_fault RPC because the fake fault is not active yet.
    visual_src.clear_fault(fake_fault)
    assert len(visual_src.active_fault_id_names) == 0
    assert fault_client.clear_service_fault_called_count == 1

    # Make the fake fault active.
    visual_src.trigger_fault("error", fake_fault)
    assert len(visual_src.active_fault_id_names) == 1
    assert fault_client.service_fault_counts[fake_fault.fault_id.fault_name] == 1

    # Clear the fake fault, but make sure RPC is only called once.
    visual_src.clear_fault(fake_fault)
    assert fault_client.clear_service_fault_called_count == 2
    visual_src.clear_fault(fake_fault)
    # Make sure clear_service_fault is not called for a cleared fault.
    assert fault_client.clear_service_fault_called_count == 2
    assert len(visual_src.active_fault_id_names) == 0
def test_clear_not_active_faults():
    # Check that clearing faults works with non active faults.

    class MockFaultClientFaultNotActive:
        def clear_service_fault(self, service_fault_id, clear_all_service_faults=False,
                                clear_all_payload_faults=False, **kwargs):
            raise ServiceFaultDoesNotExistError(None, "error")

    visual_src = VisualImageSource("source1", FakeCamera(capture_with_error, decode_with_error))
    fault_client = MockFaultClientFaultNotActive()
    visual_src.initialize_faults(fault_client, "service1")
    assert len(visual_src.active_fault_id_names) == 0
def test_faults_in_visual_source():
    # Create the fault client
    fault_client = MockFaultClient()

    # Populate fault client with at least one "old" fault.
    fault_client.trigger_service_fault_async(
        service_fault_pb2.ServiceFault(
            fault_id=service_fault_pb2.ServiceFaultId(fault_name="fault1")))
    init_fault_amount = fault_client.get_total_fault_count()

    visual_src = VisualImageSource(
        "source1", FakeCamera(capture_with_error, decode_with_error))

    # Attempt to get an image with no fault client enabled. Make sure no error is raised, and
    # values are returned as none.
    image, timestamp = visual_src.get_image_and_timestamp()
    assert image is None
    assert timestamp is None

    # attempt to decode an image with no fault client enabled. Make sure no error is raised.
    im_proto = image_pb2.Image(rows=10)
    success = visual_src.image_decode_with_error_checking(
        None, im_proto, None, None)
    assert im_proto.rows == 15
    assert not success

    # Setup faults
    visual_src.initialize_faults(fault_client, "service1")
    assert fault_client.get_total_fault_count() == 0
    assert visual_src.camera_capture_fault is not None
    assert visual_src.camera_capture_fault.fault_id.service_name == "service1"
    assert visual_src.decode_data_fault is not None
    assert visual_src.decode_data_fault.fault_id.service_name == "service1"

    # With fault client setup, check that faults are thrown when the bad functions get called.
    image, timestamp = visual_src.get_image_and_timestamp()
    assert image is None
    assert timestamp is None
    assert fault_client.service_fault_counts[
        visual_src.camera_capture_fault.fault_id.fault_name] == 1
    im_proto = image_pb2.Image(rows=21)
    success = visual_src.image_decode_with_error_checking(
        None, im_proto, None, None)
    assert im_proto.rows == 15
    assert fault_client.service_fault_counts[
        visual_src.decode_data_fault.fault_id.fault_name] == 1
    assert not success
def test_faults_are_cleared_on_success():
    # Check that captures/decodes that fail and then later succeed will cause the faults to get cleared.
    class FailsThenSucceeds(CameraInterface):
        def __init__(self):
            self.capture_count = 0
            self.decode_count = 0

        def blocking_capture(self):
            self.capture_count += 1
            if self.capture_count == 1:
                raise Exception("Fake bad capture.")
            return "image", 1

        def image_decode(self, image_data, image_proto, image_format,
                         quality_percent):
            self.decode_count += 1
            if self.decode_count == 1:
                raise Exception("Fake bad decode.")

    visual_src = VisualImageSource("source1", FailsThenSucceeds())
    fault_client = MockFaultClient()
    visual_src.initialize_faults(fault_client, "service1")
    # The first call to the capture and decode functions cause a fault to be thrown.
    image, timestamp = visual_src.get_image_and_timestamp()
    assert fault_client.service_fault_counts[
        visual_src.camera_capture_fault.fault_id.fault_name] == 1
    success = visual_src.image_decode_with_error_checking(
        None, image_pb2.Image(rows=21), None, None)
    assert fault_client.service_fault_counts[
        visual_src.decode_data_fault.fault_id.fault_name] == 1

    # The second calls will succeed, and now cause the faults to be cleared.
    image, timestamp = visual_src.get_image_and_timestamp()
    assert fault_client.service_fault_counts[
        visual_src.camera_capture_fault.fault_id.fault_name] == 0
    success = visual_src.image_decode_with_error_checking(
        None, image_pb2.Image(rows=21), None, None)
    assert fault_client.service_fault_counts[
        visual_src.decode_data_fault.fault_id.fault_name] == 0