blob: bf9f60a14f2af3f4678dc25ccd213b9a63769771 [file] [log] [blame]
Amin Hassanifdc488d2018-07-19 19:28:51 -07001#!/usr/bin/python2
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
Amin Hassani50dd2f02018-07-19 19:21:29 -070017
Amin Hassanifdc488d2018-07-19 19:28:51 -070018"""Unit testing payload_info.py."""
Amin Hassani50dd2f02018-07-19 19:21:29 -070019
20from __future__ import print_function
21
Amin Hassanifdc488d2018-07-19 19:28:51 -070022import StringIO
Amin Hassani50dd2f02018-07-19 19:21:29 -070023import sys
Amin Hassanifdc488d2018-07-19 19:28:51 -070024import unittest
Amin Hassani50dd2f02018-07-19 19:21:29 -070025
Amin Hassani55c75412019-10-07 11:20:39 -070026from contextlib import contextmanager
27
28import mock # pylint: disable=import-error
29
Amin Hassanifdc488d2018-07-19 19:28:51 -070030import payload_info
Amin Hassani50dd2f02018-07-19 19:21:29 -070031import update_payload
Amin Hassanifdc488d2018-07-19 19:28:51 -070032
Amin Hassani50dd2f02018-07-19 19:21:29 -070033from update_payload import update_metadata_pb2
34
35class FakePayloadError(Exception):
36 """A generic error when using the FakePayload."""
37
38class FakeOption(object):
39 """Fake options object for testing."""
40
41 def __init__(self, **kwargs):
42 self.list_ops = False
43 self.stats = False
44 self.signatures = False
45 for key, val in kwargs.iteritems():
46 setattr(self, key, val)
47 if not hasattr(self, 'payload_file'):
48 self.payload_file = None
49
50class FakeOp(object):
51 """Fake manifest operation for testing."""
52
53 def __init__(self, src_extents, dst_extents, op_type, **kwargs):
54 self.src_extents = src_extents
55 self.dst_extents = dst_extents
56 self.type = op_type
57 for key, val in kwargs.iteritems():
58 setattr(self, key, val)
59
60 def HasField(self, field):
61 return hasattr(self, field)
62
Amin Hassani55c75412019-10-07 11:20:39 -070063class FakeExtent(object):
64 """Fake Extent for testing."""
65 def __init__(self, start_block, num_blocks):
66 self.start_block = start_block
67 self.num_blocks = num_blocks
68
69class FakePartitionInfo(object):
70 """Fake PartitionInfo for testing."""
71 def __init__(self, size):
72 self.size = size
73
Amin Hassani50dd2f02018-07-19 19:21:29 -070074class FakePartition(object):
75 """Fake PartitionUpdate field for testing."""
76
Amin Hassani55c75412019-10-07 11:20:39 -070077 def __init__(self, partition_name, operations, old_size, new_size):
Amin Hassani50dd2f02018-07-19 19:21:29 -070078 self.partition_name = partition_name
79 self.operations = operations
Amin Hassani55c75412019-10-07 11:20:39 -070080 self.old_partition_info = FakePartitionInfo(old_size)
81 self.new_partition_info = FakePartitionInfo(new_size)
Amin Hassani50dd2f02018-07-19 19:21:29 -070082
83class FakeManifest(object):
84 """Fake manifest for testing."""
85
Amin Hassani55c75412019-10-07 11:20:39 -070086 def __init__(self):
87 self.partitions = [
88 FakePartition(update_payload.common.ROOTFS,
89 [FakeOp([], [FakeExtent(1, 1), FakeExtent(2, 2)],
90 update_payload.common.OpType.REPLACE_BZ,
91 dst_length=3*4096,
92 data_offset=1,
93 data_length=1)
94 ], 1 * 4096, 3 * 4096),
95 FakePartition(update_payload.common.KERNEL,
96 [FakeOp([FakeExtent(1, 1)],
97 [FakeExtent(x, x) for x in xrange(20)],
98 update_payload.common.OpType.SOURCE_COPY,
99 src_length=4096)
100 ], 2 * 4096, 4 * 4096),
101 ]
Amin Hassani50dd2f02018-07-19 19:21:29 -0700102 self.block_size = 4096
103 self.minor_version = 4
Amin Hassani50dd2f02018-07-19 19:21:29 -0700104 self.signatures_offset = None
105 self.signatures_size = None
106
107 def HasField(self, field_name):
108 """Fake HasField method based on the python members."""
109 return hasattr(self, field_name) and getattr(self, field_name) is not None
110
111class FakeHeader(object):
112 """Fake payload header for testing."""
113
Amin Hassani55c75412019-10-07 11:20:39 -0700114 def __init__(self, manifest_len, metadata_signature_len):
115 self.version = payload_info.MAJOR_PAYLOAD_VERSION_BRILLO
Amin Hassani50dd2f02018-07-19 19:21:29 -0700116 self.manifest_len = manifest_len
117 self.metadata_signature_len = metadata_signature_len
118
119 @property
120 def size(self):
Amin Hassani55c75412019-10-07 11:20:39 -0700121 return 24
Amin Hassani50dd2f02018-07-19 19:21:29 -0700122
Amin Hassani50dd2f02018-07-19 19:21:29 -0700123class FakePayload(object):
124 """Fake payload for testing."""
125
Amin Hassani55c75412019-10-07 11:20:39 -0700126 def __init__(self):
127 self._header = FakeHeader(222, 0)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700128 self.header = None
Amin Hassani55c75412019-10-07 11:20:39 -0700129 self._manifest = FakeManifest()
Amin Hassani50dd2f02018-07-19 19:21:29 -0700130 self.manifest = None
131
132 self._blobs = {}
133 self._payload_signatures = update_metadata_pb2.Signatures()
134 self._metadata_signatures = update_metadata_pb2.Signatures()
135
136 def Init(self):
137 """Fake Init that sets header and manifest.
138
139 Failing to call Init() will not make header and manifest available to the
140 test.
141 """
142 self.header = self._header
143 self.manifest = self._manifest
144
145 def ReadDataBlob(self, offset, length):
146 """Return the blob that should be present at the offset location"""
147 if not offset in self._blobs:
148 raise FakePayloadError('Requested blob at unknown offset %d' % offset)
149 blob = self._blobs[offset]
150 if len(blob) != length:
151 raise FakePayloadError('Read blob with the wrong length (expect: %d, '
152 'actual: %d)' % (len(blob), length))
153 return blob
154
155 @staticmethod
156 def _AddSignatureToProto(proto, **kwargs):
157 """Add a new Signature element to the passed proto."""
158 new_signature = proto.signatures.add()
159 for key, val in kwargs.iteritems():
160 setattr(new_signature, key, val)
161
162 def AddPayloadSignature(self, **kwargs):
163 self._AddSignatureToProto(self._payload_signatures, **kwargs)
164 blob = self._payload_signatures.SerializeToString()
165 self._manifest.signatures_offset = 1234
166 self._manifest.signatures_size = len(blob)
167 self._blobs[self._manifest.signatures_offset] = blob
168
169 def AddMetadataSignature(self, **kwargs):
170 self._AddSignatureToProto(self._metadata_signatures, **kwargs)
171 if self._header.metadata_signature_len:
172 del self._blobs[-self._header.metadata_signature_len]
173 blob = self._metadata_signatures.SerializeToString()
174 self._header.metadata_signature_len = len(blob)
175 self._blobs[-len(blob)] = blob
176
Amin Hassanifdc488d2018-07-19 19:28:51 -0700177class PayloadCommandTest(unittest.TestCase):
Amin Hassani50dd2f02018-07-19 19:21:29 -0700178 """Test class for our PayloadCommand class."""
179
Amin Hassanifdc488d2018-07-19 19:28:51 -0700180 @contextmanager
181 def OutputCapturer(self):
182 """A tool for capturing the sys.stdout"""
183 stdout = sys.stdout
184 try:
185 sys.stdout = StringIO.StringIO()
186 yield sys.stdout
187 finally:
188 sys.stdout = stdout
189
190 def TestCommand(self, payload_cmd, payload, expected_out):
191 """A tool for testing a payload command.
192
193 It tests that a payload command which runs with a given payload produces a
194 correct output.
195 """
196 with mock.patch.object(update_payload, 'Payload', return_value=payload), \
197 self.OutputCapturer() as output:
198 payload_cmd.Run()
199 self.assertEquals(output.getvalue(), expected_out)
200
Amin Hassani50dd2f02018-07-19 19:21:29 -0700201 def testDisplayValue(self):
202 """Verify that DisplayValue prints what we expect."""
203 with self.OutputCapturer() as output:
Amin Hassanifdc488d2018-07-19 19:28:51 -0700204 payload_info.DisplayValue('key', 'value')
205 self.assertEquals(output.getvalue(), 'key: value\n')
Amin Hassani50dd2f02018-07-19 19:21:29 -0700206
207 def testRun(self):
208 """Verify that Run parses and displays the payload like we expect."""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700209 payload_cmd = payload_info.PayloadCommand(FakeOption(action='show'))
Amin Hassani55c75412019-10-07 11:20:39 -0700210 payload = FakePayload()
211 expected_out = """Payload version: 2
Amin Hassanifdc488d2018-07-19 19:28:51 -0700212Manifest length: 222
Amin Hassani55c75412019-10-07 11:20:39 -0700213Number of partitions: 2
214 Number of "root" ops: 1
215 Number of "kernel" ops: 1
Amin Hassanifdc488d2018-07-19 19:28:51 -0700216Block size: 4096
217Minor version: 4
Amin Hassani50dd2f02018-07-19 19:21:29 -0700218"""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700219 self.TestCommand(payload_cmd, payload, expected_out)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700220
Amin Hassani50dd2f02018-07-19 19:21:29 -0700221 def testListOpsOnVersion2(self):
222 """Verify that the --list_ops option gives the correct output."""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700223 payload_cmd = payload_info.PayloadCommand(
224 FakeOption(list_ops=True, action='show'))
Amin Hassani55c75412019-10-07 11:20:39 -0700225 payload = FakePayload()
Amin Hassanifdc488d2018-07-19 19:28:51 -0700226 expected_out = """Payload version: 2
227Manifest length: 222
228Number of partitions: 2
229 Number of "root" ops: 1
230 Number of "kernel" ops: 1
231Block size: 4096
232Minor version: 4
Amin Hassani50dd2f02018-07-19 19:21:29 -0700233
Amin Hassanifdc488d2018-07-19 19:28:51 -0700234root install operations:
Amin Hassani50dd2f02018-07-19 19:21:29 -0700235 0: REPLACE_BZ
236 Data offset: 1
237 Data length: 1
238 Destination: 2 extents (3 blocks)
239 (1,1) (2,2)
240kernel install operations:
241 0: SOURCE_COPY
242 Source: 1 extent (1 block)
243 (1,1)
244 Destination: 20 extents (190 blocks)
245 (0,0) (1,1) (2,2) (3,3) (4,4) (5,5) (6,6) (7,7) (8,8) (9,9) (10,10)
246 (11,11) (12,12) (13,13) (14,14) (15,15) (16,16) (17,17) (18,18) (19,19)
247"""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700248 self.TestCommand(payload_cmd, payload, expected_out)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700249
Amin Hassani50dd2f02018-07-19 19:21:29 -0700250 def testStatsOnVersion2(self):
251 """Verify that the --stats option works correctly on version 2."""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700252 payload_cmd = payload_info.PayloadCommand(
253 FakeOption(stats=True, action='show'))
Amin Hassani55c75412019-10-07 11:20:39 -0700254 payload = FakePayload()
Amin Hassanifdc488d2018-07-19 19:28:51 -0700255 expected_out = """Payload version: 2
256Manifest length: 222
257Number of partitions: 2
258 Number of "root" ops: 1
259 Number of "kernel" ops: 1
260Block size: 4096
261Minor version: 4
262Blocks read: 11
263Blocks written: 193
264Seeks when writing: 18
Amin Hassani50dd2f02018-07-19 19:21:29 -0700265"""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700266 self.TestCommand(payload_cmd, payload, expected_out)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700267
268 def testEmptySignatures(self):
269 """Verify that the --signatures option works with unsigned payloads."""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700270 payload_cmd = payload_info.PayloadCommand(
Amin Hassani50dd2f02018-07-19 19:21:29 -0700271 FakeOption(action='show', signatures=True))
Amin Hassani55c75412019-10-07 11:20:39 -0700272 payload = FakePayload()
273 expected_out = """Payload version: 2
Amin Hassanifdc488d2018-07-19 19:28:51 -0700274Manifest length: 222
Amin Hassani55c75412019-10-07 11:20:39 -0700275Number of partitions: 2
276 Number of "root" ops: 1
277 Number of "kernel" ops: 1
Amin Hassanifdc488d2018-07-19 19:28:51 -0700278Block size: 4096
279Minor version: 4
Amin Hassani50dd2f02018-07-19 19:21:29 -0700280No metadata signatures stored in the payload
281No payload signatures stored in the payload
282"""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700283 self.TestCommand(payload_cmd, payload, expected_out)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700284
285 def testSignatures(self):
286 """Verify that the --signatures option shows the present signatures."""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700287 payload_cmd = payload_info.PayloadCommand(
Amin Hassani50dd2f02018-07-19 19:21:29 -0700288 FakeOption(action='show', signatures=True))
Amin Hassani55c75412019-10-07 11:20:39 -0700289 payload = FakePayload()
Amin Hassani50dd2f02018-07-19 19:21:29 -0700290 payload.AddPayloadSignature(version=1,
291 data='12345678abcdefgh\x00\x01\x02\x03')
292 payload.AddPayloadSignature(data='I am a signature so access is yes.')
293 payload.AddMetadataSignature(data='\x00\x0a\x0c')
Amin Hassanifdc488d2018-07-19 19:28:51 -0700294 expected_out = """Payload version: 2
295Manifest length: 222
296Number of partitions: 2
297 Number of "root" ops: 1
298 Number of "kernel" ops: 1
299Block size: 4096
300Minor version: 4
301Metadata signatures blob: file_offset=246 (7 bytes)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700302Metadata signatures: (1 entries)
303 version=None, hex_data: (3 bytes)
304 00 0a 0c | ...
Amin Hassanifdc488d2018-07-19 19:28:51 -0700305Payload signatures blob: blob_offset=1234 (64 bytes)
Amin Hassani50dd2f02018-07-19 19:21:29 -0700306Payload signatures: (2 entries)
307 version=1, hex_data: (20 bytes)
308 31 32 33 34 35 36 37 38 61 62 63 64 65 66 67 68 | 12345678abcdefgh
309 00 01 02 03 | ....
310 version=None, hex_data: (34 bytes)
311 49 20 61 6d 20 61 20 73 69 67 6e 61 74 75 72 65 | I am a signature
312 20 73 6f 20 61 63 63 65 73 73 20 69 73 20 79 65 | so access is ye
313 73 2e | s.
314"""
Amin Hassanifdc488d2018-07-19 19:28:51 -0700315 self.TestCommand(payload_cmd, payload, expected_out)
316
317if __name__ == '__main__':
318 unittest.main()