xref: /aosp_15_r20/external/federated-compute/fcp/demo/server_test.py (revision 14675a029014e728ec732f129a32e299b2da0601)
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