1*14675a02SAndroid Build Coastguard Worker# Copyright 2022 Google LLC 2*14675a02SAndroid Build Coastguard Worker# 3*14675a02SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*14675a02SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*14675a02SAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*14675a02SAndroid Build Coastguard Worker# 7*14675a02SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*14675a02SAndroid Build Coastguard Worker# 9*14675a02SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*14675a02SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*14675a02SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*14675a02SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*14675a02SAndroid Build Coastguard Worker# limitations under the License. 14*14675a02SAndroid Build Coastguard Worker"""Tests for server.""" 15*14675a02SAndroid Build Coastguard Worker 16*14675a02SAndroid Build Coastguard Workerimport asyncio 17*14675a02SAndroid Build Coastguard Workerimport gzip 18*14675a02SAndroid Build Coastguard Workerimport http 19*14675a02SAndroid Build Coastguard Workerimport http.client 20*14675a02SAndroid Build Coastguard Workerimport os 21*14675a02SAndroid Build Coastguard Workerimport threading 22*14675a02SAndroid Build Coastguard Workerimport unittest 23*14675a02SAndroid Build Coastguard Workerfrom unittest import mock 24*14675a02SAndroid Build Coastguard Workerimport urllib.parse 25*14675a02SAndroid Build Coastguard Workerimport urllib.request 26*14675a02SAndroid Build Coastguard Worker 27*14675a02SAndroid Build Coastguard Workerfrom absl import flags 28*14675a02SAndroid Build Coastguard Workerfrom absl import logging 29*14675a02SAndroid Build Coastguard Workerfrom absl.testing import absltest 30*14675a02SAndroid Build Coastguard Workerimport tensorflow as tf 31*14675a02SAndroid Build Coastguard Worker 32*14675a02SAndroid Build Coastguard Workerfrom google.longrunning import operations_pb2 33*14675a02SAndroid Build Coastguard Workerfrom fcp.demo import plan_utils 34*14675a02SAndroid Build Coastguard Workerfrom fcp.demo import server 35*14675a02SAndroid Build Coastguard Workerfrom fcp.demo import test_utils 36*14675a02SAndroid Build Coastguard Workerfrom fcp.protos import plan_pb2 37*14675a02SAndroid Build Coastguard Workerfrom fcp.protos.federatedcompute import eligibility_eval_tasks_pb2 38*14675a02SAndroid Build Coastguard Workerfrom fcp.protos.federatedcompute import task_assignments_pb2 39*14675a02SAndroid Build Coastguard Workerfrom fcp.tensorflow import external_dataset 40*14675a02SAndroid Build Coastguard Worker 41*14675a02SAndroid Build Coastguard Worker_TaskAssignmentMode = ( 42*14675a02SAndroid Build Coastguard Worker eligibility_eval_tasks_pb2.PopulationEligibilitySpec.TaskInfo.TaskAssignmentMode 43*14675a02SAndroid Build Coastguard Worker) 44*14675a02SAndroid Build Coastguard Worker 45*14675a02SAndroid Build Coastguard WorkerPOPULATION_NAME = 'test/population' 46*14675a02SAndroid Build Coastguard WorkerCAP_TENSOR_NAME = 'cap' 47*14675a02SAndroid Build Coastguard WorkerCOUNT_TENSOR_NAME = 'count' 48*14675a02SAndroid Build Coastguard WorkerTEST_SLICES = { 49*14675a02SAndroid Build Coastguard Worker 'id1': [b'1-1', b'1-2', b'1-3'], 50*14675a02SAndroid Build Coastguard Worker 'id2': [b'2-1', b'2-2'], 51*14675a02SAndroid Build Coastguard Worker} 52*14675a02SAndroid Build Coastguard Worker 53*14675a02SAndroid Build Coastguard Worker 54*14675a02SAndroid Build Coastguard Workerdef create_plan() -> plan_pb2.Plan: 55*14675a02SAndroid Build Coastguard Worker """Creates a test plan that counts examples, with a per-client cap.""" 56*14675a02SAndroid Build Coastguard Worker 57*14675a02SAndroid Build Coastguard Worker with tf.compat.v1.Graph().as_default() as client_graph: 58*14675a02SAndroid Build Coastguard Worker dataset_token = tf.compat.v1.placeholder(tf.string, shape=()) 59*14675a02SAndroid Build Coastguard Worker input_filepath = tf.compat.v1.placeholder(tf.string, shape=()) 60*14675a02SAndroid Build Coastguard Worker output_filepath = tf.compat.v1.placeholder(tf.string, shape=()) 61*14675a02SAndroid Build Coastguard Worker ds = external_dataset.ExternalDataset(token=dataset_token, selector=b'') 62*14675a02SAndroid Build Coastguard Worker cap = tf.raw_ops.Restore( 63*14675a02SAndroid Build Coastguard Worker file_pattern=input_filepath, tensor_name=CAP_TENSOR_NAME, dt=tf.int32) 64*14675a02SAndroid Build Coastguard Worker count = ds.take(tf.cast(cap, dtype=tf.int64)).reduce(0, lambda x, _: x + 1) 65*14675a02SAndroid Build Coastguard Worker target_node = tf.raw_ops.Save( 66*14675a02SAndroid Build Coastguard Worker filename=output_filepath, 67*14675a02SAndroid Build Coastguard Worker tensor_names=[COUNT_TENSOR_NAME], 68*14675a02SAndroid Build Coastguard Worker data=[count]) 69*14675a02SAndroid Build Coastguard Worker 70*14675a02SAndroid Build Coastguard Worker with tf.compat.v1.Graph().as_default() as server_graph: 71*14675a02SAndroid Build Coastguard Worker filename = tf.compat.v1.placeholder(tf.string, shape=()) 72*14675a02SAndroid Build Coastguard Worker contribution_cap = tf.Variable(0, dtype=tf.int32) 73*14675a02SAndroid Build Coastguard Worker count = tf.Variable(0, dtype=tf.int32) 74*14675a02SAndroid Build Coastguard Worker load_initial_count = count.assign( 75*14675a02SAndroid Build Coastguard Worker tf.raw_ops.Restore( 76*14675a02SAndroid Build Coastguard Worker file_pattern=filename, tensor_name=COUNT_TENSOR_NAME, dt=tf.int32), 77*14675a02SAndroid Build Coastguard Worker read_value=False) 78*14675a02SAndroid Build Coastguard Worker load_contribution_cap = contribution_cap.assign( 79*14675a02SAndroid Build Coastguard Worker tf.raw_ops.Restore( 80*14675a02SAndroid Build Coastguard Worker file_pattern=filename, tensor_name=CAP_TENSOR_NAME, dt=tf.int32), 81*14675a02SAndroid Build Coastguard Worker read_value=False) 82*14675a02SAndroid Build Coastguard Worker with tf.control_dependencies([load_initial_count, load_contribution_cap]): 83*14675a02SAndroid Build Coastguard Worker restore_server_savepoint = tf.no_op() 84*14675a02SAndroid Build Coastguard Worker write_client_init = tf.raw_ops.Save( 85*14675a02SAndroid Build Coastguard Worker filename=filename, 86*14675a02SAndroid Build Coastguard Worker tensor_names=[CAP_TENSOR_NAME], 87*14675a02SAndroid Build Coastguard Worker data=[contribution_cap]) 88*14675a02SAndroid Build Coastguard Worker 89*14675a02SAndroid Build Coastguard Worker read_intermediate_update = count.assign_add( 90*14675a02SAndroid Build Coastguard Worker tf.raw_ops.Restore( 91*14675a02SAndroid Build Coastguard Worker file_pattern=filename, tensor_name=COUNT_TENSOR_NAME, dt=tf.int32)) 92*14675a02SAndroid Build Coastguard Worker save_count = tf.raw_ops.Save( 93*14675a02SAndroid Build Coastguard Worker filename=filename, tensor_names=[COUNT_TENSOR_NAME], data=[count]) 94*14675a02SAndroid Build Coastguard Worker 95*14675a02SAndroid Build Coastguard Worker plan = plan_pb2.Plan( 96*14675a02SAndroid Build Coastguard Worker phase=[ 97*14675a02SAndroid Build Coastguard Worker plan_pb2.Plan.Phase( 98*14675a02SAndroid Build Coastguard Worker client_phase=plan_pb2.ClientPhase( 99*14675a02SAndroid Build Coastguard Worker tensorflow_spec=plan_pb2.TensorflowSpec( 100*14675a02SAndroid Build Coastguard Worker dataset_token_tensor_name=dataset_token.op.name, 101*14675a02SAndroid Build Coastguard Worker input_tensor_specs=[ 102*14675a02SAndroid Build Coastguard Worker tf.TensorSpec.from_tensor( 103*14675a02SAndroid Build Coastguard Worker input_filepath).experimental_as_proto(), 104*14675a02SAndroid Build Coastguard Worker tf.TensorSpec.from_tensor( 105*14675a02SAndroid Build Coastguard Worker output_filepath).experimental_as_proto(), 106*14675a02SAndroid Build Coastguard Worker ], 107*14675a02SAndroid Build Coastguard Worker target_node_names=[target_node.name]), 108*14675a02SAndroid Build Coastguard Worker federated_compute=plan_pb2.FederatedComputeIORouter( 109*14675a02SAndroid Build Coastguard Worker input_filepath_tensor_name=input_filepath.op.name, 110*14675a02SAndroid Build Coastguard Worker output_filepath_tensor_name=output_filepath.op.name)), 111*14675a02SAndroid Build Coastguard Worker server_phase=plan_pb2.ServerPhase( 112*14675a02SAndroid Build Coastguard Worker write_client_init=plan_pb2.CheckpointOp( 113*14675a02SAndroid Build Coastguard Worker saver_def=tf.compat.v1.train.SaverDef( 114*14675a02SAndroid Build Coastguard Worker filename_tensor_name=filename.name, 115*14675a02SAndroid Build Coastguard Worker save_tensor_name=write_client_init.name)), 116*14675a02SAndroid Build Coastguard Worker read_intermediate_update=plan_pb2.CheckpointOp( 117*14675a02SAndroid Build Coastguard Worker saver_def=tf.compat.v1.train.SaverDef( 118*14675a02SAndroid Build Coastguard Worker filename_tensor_name=filename.name, 119*14675a02SAndroid Build Coastguard Worker restore_op_name=read_intermediate_update.name))), 120*14675a02SAndroid Build Coastguard Worker server_phase_v2=plan_pb2.ServerPhaseV2(aggregations=[ 121*14675a02SAndroid Build Coastguard Worker plan_pb2.ServerAggregationConfig( 122*14675a02SAndroid Build Coastguard Worker intrinsic_uri='federated_sum', 123*14675a02SAndroid Build Coastguard Worker intrinsic_args=[ 124*14675a02SAndroid Build Coastguard Worker plan_pb2.ServerAggregationConfig.IntrinsicArg( 125*14675a02SAndroid Build Coastguard Worker input_tensor=tf.TensorSpec( 126*14675a02SAndroid Build Coastguard Worker (), tf.int32, 127*14675a02SAndroid Build Coastguard Worker COUNT_TENSOR_NAME).experimental_as_proto()) 128*14675a02SAndroid Build Coastguard Worker ], 129*14675a02SAndroid Build Coastguard Worker output_tensors=[ 130*14675a02SAndroid Build Coastguard Worker tf.TensorSpec((), tf.int32, COUNT_TENSOR_NAME) 131*14675a02SAndroid Build Coastguard Worker .experimental_as_proto() 132*14675a02SAndroid Build Coastguard Worker ]) 133*14675a02SAndroid Build Coastguard Worker ])) 134*14675a02SAndroid Build Coastguard Worker ], 135*14675a02SAndroid Build Coastguard Worker server_savepoint=plan_pb2.CheckpointOp( 136*14675a02SAndroid Build Coastguard Worker saver_def=tf.compat.v1.train.SaverDef( 137*14675a02SAndroid Build Coastguard Worker filename_tensor_name=filename.name, 138*14675a02SAndroid Build Coastguard Worker save_tensor_name=save_count.name, 139*14675a02SAndroid Build Coastguard Worker restore_op_name=restore_server_savepoint.name)), 140*14675a02SAndroid Build Coastguard Worker version=1) 141*14675a02SAndroid Build Coastguard Worker plan.client_graph_bytes.Pack(client_graph.as_graph_def()) 142*14675a02SAndroid Build Coastguard Worker plan.server_graph_bytes.Pack(server_graph.as_graph_def()) 143*14675a02SAndroid Build Coastguard Worker return plan 144*14675a02SAndroid Build Coastguard Worker 145*14675a02SAndroid Build Coastguard Worker 146*14675a02SAndroid Build Coastguard Workerclass ServerTest(absltest.TestCase, unittest.IsolatedAsyncioTestCase): 147*14675a02SAndroid Build Coastguard Worker 148*14675a02SAndroid Build Coastguard Worker def setUp(self): 149*14675a02SAndroid Build Coastguard Worker super().setUp() 150*14675a02SAndroid Build Coastguard Worker self.server = server.InProcessServer( # pytype: disable=wrong-arg-types 151*14675a02SAndroid Build Coastguard Worker population_name=POPULATION_NAME, 152*14675a02SAndroid Build Coastguard Worker host='localhost', 153*14675a02SAndroid Build Coastguard Worker port=0) 154*14675a02SAndroid Build Coastguard Worker self._server_thread = threading.Thread(target=self.server.serve_forever) 155*14675a02SAndroid Build Coastguard Worker self._server_thread.start() 156*14675a02SAndroid Build Coastguard Worker self.conn = http.client.HTTPConnection( 157*14675a02SAndroid Build Coastguard Worker self.server.server_name, port=self.server.server_port) 158*14675a02SAndroid Build Coastguard Worker 159*14675a02SAndroid Build Coastguard Worker def tearDown(self): 160*14675a02SAndroid Build Coastguard Worker self.server.shutdown() 161*14675a02SAndroid Build Coastguard Worker self._server_thread.join() 162*14675a02SAndroid Build Coastguard Worker self.server.server_close() 163*14675a02SAndroid Build Coastguard Worker super().tearDown() 164*14675a02SAndroid Build Coastguard Worker 165*14675a02SAndroid Build Coastguard Worker async def wait_for_task(self) -> task_assignments_pb2.TaskAssignment: 166*14675a02SAndroid Build Coastguard Worker """Polls the server until a task is being served.""" 167*14675a02SAndroid Build Coastguard Worker pop = urllib.parse.quote(POPULATION_NAME, safe='') 168*14675a02SAndroid Build Coastguard Worker url = f'/v1/populations/{pop}/taskassignments/test:start?%24alt=proto' 169*14675a02SAndroid Build Coastguard Worker request = task_assignments_pb2.StartTaskAssignmentRequest() 170*14675a02SAndroid Build Coastguard Worker while True: 171*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', url, request.SerializeToString()) 172*14675a02SAndroid Build Coastguard Worker http_response = self.conn.getresponse() 173*14675a02SAndroid Build Coastguard Worker if http_response.status == http.HTTPStatus.OK: 174*14675a02SAndroid Build Coastguard Worker op = operations_pb2.Operation.FromString(http_response.read()) 175*14675a02SAndroid Build Coastguard Worker response = task_assignments_pb2.StartTaskAssignmentResponse() 176*14675a02SAndroid Build Coastguard Worker op.response.Unpack(response) 177*14675a02SAndroid Build Coastguard Worker if response.HasField('task_assignment'): 178*14675a02SAndroid Build Coastguard Worker logging.info('wait_for_task received assignment to %s', 179*14675a02SAndroid Build Coastguard Worker response.task_assignment.task_name) 180*14675a02SAndroid Build Coastguard Worker return response.task_assignment 181*14675a02SAndroid Build Coastguard Worker await asyncio.sleep(0.5) 182*14675a02SAndroid Build Coastguard Worker 183*14675a02SAndroid Build Coastguard Worker async def test_run_computation(self): 184*14675a02SAndroid Build Coastguard Worker initial_count = 100 185*14675a02SAndroid Build Coastguard Worker cap = 10 186*14675a02SAndroid Build Coastguard Worker examples_per_client = [1, 5, 15] 187*14675a02SAndroid Build Coastguard Worker checkpoint = test_utils.create_checkpoint({ 188*14675a02SAndroid Build Coastguard Worker CAP_TENSOR_NAME: cap, 189*14675a02SAndroid Build Coastguard Worker COUNT_TENSOR_NAME: initial_count, 190*14675a02SAndroid Build Coastguard Worker }) 191*14675a02SAndroid Build Coastguard Worker run_computation_task = asyncio.create_task( 192*14675a02SAndroid Build Coastguard Worker self.server.run_computation( 193*14675a02SAndroid Build Coastguard Worker 'task/name', 194*14675a02SAndroid Build Coastguard Worker create_plan(), 195*14675a02SAndroid Build Coastguard Worker checkpoint, 196*14675a02SAndroid Build Coastguard Worker _TaskAssignmentMode.TASK_ASSIGNMENT_MODE_SINGLE, 197*14675a02SAndroid Build Coastguard Worker len(examples_per_client), 198*14675a02SAndroid Build Coastguard Worker ) 199*14675a02SAndroid Build Coastguard Worker ) 200*14675a02SAndroid Build Coastguard Worker 201*14675a02SAndroid Build Coastguard Worker # Wait for task assignment to return a task. 202*14675a02SAndroid Build Coastguard Worker wait_task = asyncio.create_task(self.wait_for_task()) 203*14675a02SAndroid Build Coastguard Worker await asyncio.wait([run_computation_task, wait_task], 204*14675a02SAndroid Build Coastguard Worker timeout=10, 205*14675a02SAndroid Build Coastguard Worker return_when=asyncio.FIRST_COMPLETED) 206*14675a02SAndroid Build Coastguard Worker self.assertTrue(wait_task.done()) 207*14675a02SAndroid Build Coastguard Worker # `run_computation` should not be done since no clients have reported. 208*14675a02SAndroid Build Coastguard Worker self.assertFalse(run_computation_task.done()) 209*14675a02SAndroid Build Coastguard Worker 210*14675a02SAndroid Build Coastguard Worker client_runner = os.path.join( 211*14675a02SAndroid Build Coastguard Worker flags.FLAGS.test_srcdir, 212*14675a02SAndroid Build Coastguard Worker 'com_google_fcp', 213*14675a02SAndroid Build Coastguard Worker 'fcp', 214*14675a02SAndroid Build Coastguard Worker 'client', 215*14675a02SAndroid Build Coastguard Worker 'client_runner_main') 216*14675a02SAndroid Build Coastguard Worker server_url = f'http://{self.server.server_name}:{self.server.server_port}/' 217*14675a02SAndroid Build Coastguard Worker clients = [] 218*14675a02SAndroid Build Coastguard Worker for num_examples in examples_per_client: 219*14675a02SAndroid Build Coastguard Worker subprocess = asyncio.create_subprocess_exec( 220*14675a02SAndroid Build Coastguard Worker client_runner, f'--server={server_url}', 221*14675a02SAndroid Build Coastguard Worker f'--population={POPULATION_NAME}', 222*14675a02SAndroid Build Coastguard Worker f'--num_empty_examples={num_examples}', '--sleep_after_round_secs=0', 223*14675a02SAndroid Build Coastguard Worker '--use_http_federated_compute_protocol') 224*14675a02SAndroid Build Coastguard Worker clients.append(asyncio.create_task((await subprocess).wait())) 225*14675a02SAndroid Build Coastguard Worker 226*14675a02SAndroid Build Coastguard Worker # Wait for the computation to complete. 227*14675a02SAndroid Build Coastguard Worker await asyncio.wait([run_computation_task] + clients, timeout=10) 228*14675a02SAndroid Build Coastguard Worker self.assertTrue(run_computation_task.done()) 229*14675a02SAndroid Build Coastguard Worker for client in clients: 230*14675a02SAndroid Build Coastguard Worker self.assertTrue(client.done()) 231*14675a02SAndroid Build Coastguard Worker self.assertEqual(client.result(), 0) 232*14675a02SAndroid Build Coastguard Worker 233*14675a02SAndroid Build Coastguard Worker # Verify the sum in the checkpoint. 234*14675a02SAndroid Build Coastguard Worker result = test_utils.read_tensor_from_checkpoint( 235*14675a02SAndroid Build Coastguard Worker run_computation_task.result(), COUNT_TENSOR_NAME, tf.int32) 236*14675a02SAndroid Build Coastguard Worker self.assertEqual( 237*14675a02SAndroid Build Coastguard Worker result, initial_count + sum([min(n, cap) for n in examples_per_client])) 238*14675a02SAndroid Build Coastguard Worker 239*14675a02SAndroid Build Coastguard Worker @mock.patch.object( 240*14675a02SAndroid Build Coastguard Worker plan_utils.Session, 241*14675a02SAndroid Build Coastguard Worker 'slices', 242*14675a02SAndroid Build Coastguard Worker new=property(lambda unused_self: TEST_SLICES), 243*14675a02SAndroid Build Coastguard Worker ) 244*14675a02SAndroid Build Coastguard Worker async def test_federated_select(self): 245*14675a02SAndroid Build Coastguard Worker checkpoint = test_utils.create_checkpoint({ 246*14675a02SAndroid Build Coastguard Worker CAP_TENSOR_NAME: 100, 247*14675a02SAndroid Build Coastguard Worker COUNT_TENSOR_NAME: 0, 248*14675a02SAndroid Build Coastguard Worker }) 249*14675a02SAndroid Build Coastguard Worker run_computation_task = asyncio.create_task( 250*14675a02SAndroid Build Coastguard Worker self.server.run_computation( 251*14675a02SAndroid Build Coastguard Worker 'task/name', 252*14675a02SAndroid Build Coastguard Worker create_plan(), 253*14675a02SAndroid Build Coastguard Worker checkpoint, 254*14675a02SAndroid Build Coastguard Worker _TaskAssignmentMode.TASK_ASSIGNMENT_MODE_SINGLE, 255*14675a02SAndroid Build Coastguard Worker 1, 256*14675a02SAndroid Build Coastguard Worker ) 257*14675a02SAndroid Build Coastguard Worker ) 258*14675a02SAndroid Build Coastguard Worker 259*14675a02SAndroid Build Coastguard Worker # Wait for task assignment to return a task. 260*14675a02SAndroid Build Coastguard Worker wait_task = asyncio.create_task(self.wait_for_task()) 261*14675a02SAndroid Build Coastguard Worker await asyncio.wait( 262*14675a02SAndroid Build Coastguard Worker [run_computation_task, wait_task], 263*14675a02SAndroid Build Coastguard Worker timeout=10, 264*14675a02SAndroid Build Coastguard Worker return_when=asyncio.FIRST_COMPLETED, 265*14675a02SAndroid Build Coastguard Worker ) 266*14675a02SAndroid Build Coastguard Worker self.assertTrue(wait_task.done()) 267*14675a02SAndroid Build Coastguard Worker uri_template = wait_task.result().federated_select_uri_info.uri_template 268*14675a02SAndroid Build Coastguard Worker self.assertNotEmpty(uri_template) 269*14675a02SAndroid Build Coastguard Worker 270*14675a02SAndroid Build Coastguard Worker # Check the contents of the slices. 271*14675a02SAndroid Build Coastguard Worker for served_at_id, slices in TEST_SLICES.items(): 272*14675a02SAndroid Build Coastguard Worker for i, slice_data in enumerate(slices): 273*14675a02SAndroid Build Coastguard Worker with urllib.request.urlopen( 274*14675a02SAndroid Build Coastguard Worker uri_template.format(served_at_id=served_at_id, key_base10=str(i)) 275*14675a02SAndroid Build Coastguard Worker ) as response: 276*14675a02SAndroid Build Coastguard Worker self.assertEqual( 277*14675a02SAndroid Build Coastguard Worker response.getheader('Content-Type'), 278*14675a02SAndroid Build Coastguard Worker 'application/octet-stream+gzip', 279*14675a02SAndroid Build Coastguard Worker ) 280*14675a02SAndroid Build Coastguard Worker self.assertEqual(gzip.decompress(response.read()), slice_data) 281*14675a02SAndroid Build Coastguard Worker 282*14675a02SAndroid Build Coastguard Worker 283*14675a02SAndroid Build Coastguard Workerif __name__ == '__main__': 284*14675a02SAndroid Build Coastguard Worker absltest.main() 285