xref: /aosp_15_r20/external/pigweed/pw_tokenizer/py/detokenize_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*61c4878aSAndroid Build Coastguard Worker# Copyright 2020 The Pigweed Authors
3*61c4878aSAndroid Build Coastguard Worker#
4*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
6*61c4878aSAndroid Build Coastguard Worker# the License at
7*61c4878aSAndroid Build Coastguard Worker#
8*61c4878aSAndroid Build Coastguard Worker#     https://www.apache.org/licenses/LICENSE-2.0
9*61c4878aSAndroid Build Coastguard Worker#
10*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
14*61c4878aSAndroid Build Coastguard Worker# the License.
15*61c4878aSAndroid Build Coastguard Worker"""Tests for detokenize."""
16*61c4878aSAndroid Build Coastguard Worker
17*61c4878aSAndroid Build Coastguard Workerimport base64
18*61c4878aSAndroid Build Coastguard Workerimport concurrent
19*61c4878aSAndroid Build Coastguard Workerimport datetime as dt
20*61c4878aSAndroid Build Coastguard Workerimport functools
21*61c4878aSAndroid Build Coastguard Workerimport io
22*61c4878aSAndroid Build Coastguard Workerimport os
23*61c4878aSAndroid Build Coastguard Workerfrom pathlib import Path
24*61c4878aSAndroid Build Coastguard Workerimport struct
25*61c4878aSAndroid Build Coastguard Workerimport tempfile
26*61c4878aSAndroid Build Coastguard Workerfrom typing import Any, Callable, NamedTuple
27*61c4878aSAndroid Build Coastguard Workerimport unittest
28*61c4878aSAndroid Build Coastguard Workerfrom unittest import mock
29*61c4878aSAndroid Build Coastguard Worker
30*61c4878aSAndroid Build Coastguard Workerfrom pw_tokenizer import database
31*61c4878aSAndroid Build Coastguard Workerfrom pw_tokenizer import detokenize
32*61c4878aSAndroid Build Coastguard Workerfrom pw_tokenizer import elf_reader
33*61c4878aSAndroid Build Coastguard Workerfrom pw_tokenizer import tokens
34*61c4878aSAndroid Build Coastguard Worker
35*61c4878aSAndroid Build Coastguard Worker
36*61c4878aSAndroid Build Coastguard Worker# This function is not part of this test. It was used to generate the binary
37*61c4878aSAndroid Build Coastguard Worker# strings for EMPTY_ELF and ELF_WITH_TOKENIZER_SECTIONS. It takes a path and
38*61c4878aSAndroid Build Coastguard Worker# returns a Python byte string suitable for copying into Python source code.
39*61c4878aSAndroid Build Coastguard Workerdef path_to_byte_string(path: str) -> str:
40*61c4878aSAndroid Build Coastguard Worker    with open(path, 'rb') as fd:
41*61c4878aSAndroid Build Coastguard Worker        data = fd.read()
42*61c4878aSAndroid Build Coastguard Worker
43*61c4878aSAndroid Build Coastguard Worker    output: list[str] = []
44*61c4878aSAndroid Build Coastguard Worker    indices = iter(range(len(data)))
45*61c4878aSAndroid Build Coastguard Worker
46*61c4878aSAndroid Build Coastguard Worker    while True:
47*61c4878aSAndroid Build Coastguard Worker        line = ''
48*61c4878aSAndroid Build Coastguard Worker
49*61c4878aSAndroid Build Coastguard Worker        while len(line) < 70:
50*61c4878aSAndroid Build Coastguard Worker            try:
51*61c4878aSAndroid Build Coastguard Worker                i = next(indices)
52*61c4878aSAndroid Build Coastguard Worker            except StopIteration:
53*61c4878aSAndroid Build Coastguard Worker                break
54*61c4878aSAndroid Build Coastguard Worker
55*61c4878aSAndroid Build Coastguard Worker            line += repr(data[i : i + 1])[2:-1].replace("'", r'\'')
56*61c4878aSAndroid Build Coastguard Worker
57*61c4878aSAndroid Build Coastguard Worker        if not line:
58*61c4878aSAndroid Build Coastguard Worker            return ''.join(output)
59*61c4878aSAndroid Build Coastguard Worker
60*61c4878aSAndroid Build Coastguard Worker        output.append("    b'{}'\n".format(''.join(line)))
61*61c4878aSAndroid Build Coastguard Worker
62*61c4878aSAndroid Build Coastguard Worker
63*61c4878aSAndroid Build Coastguard Worker# This is an empty ELF file. It was created from the ELF file for
64*61c4878aSAndroid Build Coastguard Worker# tokenize_test.cc with the command:
65*61c4878aSAndroid Build Coastguard Worker#
66*61c4878aSAndroid Build Coastguard Worker#   arm-none-eabi-objcopy -S --only-section NO_SECTIONS_PLEASE <ELF> <OUTPUT>
67*61c4878aSAndroid Build Coastguard Worker#
68*61c4878aSAndroid Build Coastguard Worker# The resulting ELF was converted to a Python binary string using
69*61c4878aSAndroid Build Coastguard Worker# path_to_byte_string function above.
70*61c4878aSAndroid Build Coastguard WorkerEMPTY_ELF = (
71*61c4878aSAndroid Build Coastguard Worker    b'\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00(\x00\x01'
72*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\xd1\x83\x00\x084\x00\x00\x00\xe0\x00\x00\x00\x00\x04\x00\x05'
73*61c4878aSAndroid Build Coastguard Worker    b'4\x00 \x00\x05\x00(\x00\x02\x00\x01\x00\x01\x00\x00\x00\xd4\x00\x00\x00'
74*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00'
75*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x00\x00'
76*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00'
77*61c4878aSAndroid Build Coastguard Worker    b'\x01\x00\x01\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
78*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x01\x00\x01\x00'
79*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
80*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\xd4\x00'
81*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
82*61c4878aSAndroid Build Coastguard Worker    b'\x06\x00\x00\x00\x00\x00\x01\x00\x00.shstrtab\x00\x00\x00\x00\x00\x00\x00'
83*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
84*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
85*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\x00\x00'
86*61c4878aSAndroid Build Coastguard Worker    b'\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'
87*61c4878aSAndroid Build Coastguard Worker    b'\x00\x00\x00'
88*61c4878aSAndroid Build Coastguard Worker)
89*61c4878aSAndroid Build Coastguard Worker
90*61c4878aSAndroid Build Coastguard Worker# This is an ELF file with only the pw_tokenizer sections. It was created
91*61c4878aSAndroid Build Coastguard Worker# from a tokenize_test binary built for the STM32F429i Discovery board. The
92*61c4878aSAndroid Build Coastguard Worker# pw_tokenizer sections were extracted with this command:
93*61c4878aSAndroid Build Coastguard Worker#
94*61c4878aSAndroid Build Coastguard Worker#   arm-none-eabi-objcopy -S --only-section ".pw_tokenizer*" <ELF> <OUTPUT>
95*61c4878aSAndroid Build Coastguard Worker#
96*61c4878aSAndroid Build Coastguard WorkerELF_WITH_TOKENIZER_SECTIONS_PATH = Path(__file__).parent.joinpath(
97*61c4878aSAndroid Build Coastguard Worker    'example_binary_with_tokenized_strings.elf'
98*61c4878aSAndroid Build Coastguard Worker)
99*61c4878aSAndroid Build Coastguard WorkerELF_WITH_TOKENIZER_SECTIONS = ELF_WITH_TOKENIZER_SECTIONS_PATH.read_bytes()
100*61c4878aSAndroid Build Coastguard Worker
101*61c4878aSAndroid Build Coastguard WorkerDEFAULT_DOMAIN_TOKENS = 22
102*61c4878aSAndroid Build Coastguard Worker
103*61c4878aSAndroid Build Coastguard Worker# 27 total since token 881436a0="The answer is: %s" is in two domains.
104*61c4878aSAndroid Build Coastguard WorkerALL_DOMAIN_TOKENS = 26 + 1
105*61c4878aSAndroid Build Coastguard Worker
106*61c4878aSAndroid Build Coastguard Worker# 0x2e668cd6 is 'Jello, world!' (which is also used in database_test.py).
107*61c4878aSAndroid Build Coastguard WorkerJELLO_WORLD_TOKEN = b'\xd6\x8c\x66\x2e'
108*61c4878aSAndroid Build Coastguard Worker
109*61c4878aSAndroid Build Coastguard Worker
110*61c4878aSAndroid Build Coastguard Workerclass DetokenizeTest(unittest.TestCase):
111*61c4878aSAndroid Build Coastguard Worker    """Tests the detokenize.Detokenizer."""
112*61c4878aSAndroid Build Coastguard Worker
113*61c4878aSAndroid Build Coastguard Worker    def test_simple(self) -> None:
114*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
115*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
116*61c4878aSAndroid Build Coastguard Worker                [
117*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
118*61c4878aSAndroid Build Coastguard Worker                        0xCDAB, '%02d %s %c%%', date_removed=dt.datetime.now()
119*61c4878aSAndroid Build Coastguard Worker                    )
120*61c4878aSAndroid Build Coastguard Worker                ]
121*61c4878aSAndroid Build Coastguard Worker            )
122*61c4878aSAndroid Build Coastguard Worker        )
123*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
124*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\xab\xcd\0\0\x02\x03Two\x66')), '01 Two 3%'
125*61c4878aSAndroid Build Coastguard Worker        )
126*61c4878aSAndroid Build Coastguard Worker
127*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_extra_data_is_unsuccessful(self) -> None:
128*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
129*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
130*61c4878aSAndroid Build Coastguard Worker                [
131*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
132*61c4878aSAndroid Build Coastguard Worker                        1, 'no args', date_removed=dt.datetime(1, 1, 1)
133*61c4878aSAndroid Build Coastguard Worker                    )
134*61c4878aSAndroid Build Coastguard Worker                ]
135*61c4878aSAndroid Build Coastguard Worker            )
136*61c4878aSAndroid Build Coastguard Worker        )
137*61c4878aSAndroid Build Coastguard Worker
138*61c4878aSAndroid Build Coastguard Worker        result = detok.detokenize(b'\x01\0\0\0\x04args')
139*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(result.failures), 1)
140*61c4878aSAndroid Build Coastguard Worker        string, args, remaining = result.failures[0]
141*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('no args', string)
142*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(args)
143*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(b'\x04args', remaining)
144*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('no args', string)
145*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('no args', str(result))
146*61c4878aSAndroid Build Coastguard Worker
147*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_zero_extend_short_token_with_no_args(self) -> None:
148*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
149*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
150*61c4878aSAndroid Build Coastguard Worker                [tokens.TokenizedStringEntry(0xCDAB, 'This token is 16 bits')]
151*61c4878aSAndroid Build Coastguard Worker            )
152*61c4878aSAndroid Build Coastguard Worker        )
153*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
154*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\xab\xcd')), 'This token is 16 bits'
155*61c4878aSAndroid Build Coastguard Worker        )
156*61c4878aSAndroid Build Coastguard Worker
157*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_missing_data_is_unsuccessful(self) -> None:
158*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
159*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
160*61c4878aSAndroid Build Coastguard Worker                [
161*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
162*61c4878aSAndroid Build Coastguard Worker                        2, '%s', date_removed=dt.datetime(1, 1, 1)
163*61c4878aSAndroid Build Coastguard Worker                    )
164*61c4878aSAndroid Build Coastguard Worker                ]
165*61c4878aSAndroid Build Coastguard Worker            )
166*61c4878aSAndroid Build Coastguard Worker        )
167*61c4878aSAndroid Build Coastguard Worker
168*61c4878aSAndroid Build Coastguard Worker        result = detok.detokenize(b'\x02\0\0\0')
169*61c4878aSAndroid Build Coastguard Worker        string, args, remaining = result.failures[0]
170*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('%s', string)
171*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(args), 1)
172*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(b'', remaining)
173*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(result.failures), 1)
174*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('%s', str(result))
175*61c4878aSAndroid Build Coastguard Worker
176*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_missing_data_with_errors_is_unsuccessful(self) -> None:
177*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
178*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
179*61c4878aSAndroid Build Coastguard Worker                [
180*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
181*61c4878aSAndroid Build Coastguard Worker                        2, '%s', date_removed=dt.datetime(1, 1, 1)
182*61c4878aSAndroid Build Coastguard Worker                    )
183*61c4878aSAndroid Build Coastguard Worker                ]
184*61c4878aSAndroid Build Coastguard Worker            ),
185*61c4878aSAndroid Build Coastguard Worker            show_errors=True,
186*61c4878aSAndroid Build Coastguard Worker        )
187*61c4878aSAndroid Build Coastguard Worker
188*61c4878aSAndroid Build Coastguard Worker        result = detok.detokenize(b'\x02\0\0\0')
189*61c4878aSAndroid Build Coastguard Worker        string, args, remaining = result.failures[0]
190*61c4878aSAndroid Build Coastguard Worker        self.assertIn('%s MISSING', string)
191*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(args), 1)
192*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(b'', remaining)
193*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(result.failures), 1)
194*61c4878aSAndroid Build Coastguard Worker        self.assertIn('%s MISSING', str(result))
195*61c4878aSAndroid Build Coastguard Worker
196*61c4878aSAndroid Build Coastguard Worker    def test_unparsed_data(self) -> None:
197*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
198*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
199*61c4878aSAndroid Build Coastguard Worker                [
200*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
201*61c4878aSAndroid Build Coastguard Worker                        1, 'no args', date_removed=dt.datetime(100, 1, 1)
202*61c4878aSAndroid Build Coastguard Worker                    ),
203*61c4878aSAndroid Build Coastguard Worker                ]
204*61c4878aSAndroid Build Coastguard Worker            )
205*61c4878aSAndroid Build Coastguard Worker        )
206*61c4878aSAndroid Build Coastguard Worker        result = detok.detokenize(b'\x01\0\0\0o_o')
207*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(result.ok())
208*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('no args', str(result))
209*61c4878aSAndroid Build Coastguard Worker        self.assertIn('o_o', repr(result))
210*61c4878aSAndroid Build Coastguard Worker        self.assertIn('decoding failed', result.error_message())
211*61c4878aSAndroid Build Coastguard Worker
212*61c4878aSAndroid Build Coastguard Worker    def test_empty_db(self) -> None:
213*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(EMPTY_ELF))
214*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(detok.detokenize(b'\x12\x34\0\0').ok())
215*61c4878aSAndroid Build Coastguard Worker        self.assertIn(
216*61c4878aSAndroid Build Coastguard Worker            'unknown token', detok.detokenize(b'1234').error_message()
217*61c4878aSAndroid Build Coastguard Worker        )
218*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', repr(detok.detokenize(b'1234')))
219*61c4878aSAndroid Build Coastguard Worker
220*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
221*61c4878aSAndroid Build Coastguard Worker            '$' + base64.b64encode(b'1234').decode(),
222*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'1234')),
223*61c4878aSAndroid Build Coastguard Worker        )
224*61c4878aSAndroid Build Coastguard Worker
225*61c4878aSAndroid Build Coastguard Worker        self.assertIsNone(detok.detokenize(b'').token)
226*61c4878aSAndroid Build Coastguard Worker
227*61c4878aSAndroid Build Coastguard Worker    def test_empty_db_show_errors(self) -> None:
228*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(EMPTY_ELF), show_errors=True)
229*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(detok.detokenize(b'\x12\x34\0\0').ok())
230*61c4878aSAndroid Build Coastguard Worker        self.assertIn(
231*61c4878aSAndroid Build Coastguard Worker            'unknown token', detok.detokenize(b'1234').error_message()
232*61c4878aSAndroid Build Coastguard Worker        )
233*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', repr(detok.detokenize(b'1234')))
234*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', str(detok.detokenize(b'1234')))
235*61c4878aSAndroid Build Coastguard Worker
236*61c4878aSAndroid Build Coastguard Worker        self.assertIsNone(detok.detokenize(b'').token)
237*61c4878aSAndroid Build Coastguard Worker
238*61c4878aSAndroid Build Coastguard Worker    def test_missing_token_show_errors(self) -> None:
239*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(EMPTY_ELF), show_errors=True)
240*61c4878aSAndroid Build Coastguard Worker        self.assertIn('missing token', detok.detokenize(b'').error_message())
241*61c4878aSAndroid Build Coastguard Worker        self.assertIn('missing token', str(detok.detokenize(b'')))
242*61c4878aSAndroid Build Coastguard Worker
243*61c4878aSAndroid Build Coastguard Worker    def test_missing_token(self) -> None:
244*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(EMPTY_ELF))
245*61c4878aSAndroid Build Coastguard Worker        self.assertIn('missing token', detok.detokenize(b'').error_message())
246*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('$', str(detok.detokenize(b'')))
247*61c4878aSAndroid Build Coastguard Worker
248*61c4878aSAndroid Build Coastguard Worker    def test_unknown_shorter_token_show_error(self) -> None:
249*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(EMPTY_ELF), show_errors=True)
250*61c4878aSAndroid Build Coastguard Worker
251*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', detok.detokenize(b'1').error_message())
252*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', str(detok.detokenize(b'1')))
253*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', repr(detok.detokenize(b'1')))
254*61c4878aSAndroid Build Coastguard Worker
255*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', detok.detokenize(b'123').error_message())
256*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', str(detok.detokenize(b'123')))
257*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token', repr(detok.detokenize(b'123')))
258*61c4878aSAndroid Build Coastguard Worker
259*61c4878aSAndroid Build Coastguard Worker    def test_unknown_shorter_token(self) -> None:
260*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(EMPTY_ELF))
261*61c4878aSAndroid Build Coastguard Worker
262*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
263*61c4878aSAndroid Build Coastguard Worker            'unknown token 00000001', detok.detokenize(b'\1').error_message()
264*61c4878aSAndroid Build Coastguard Worker        )
265*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
266*61c4878aSAndroid Build Coastguard Worker            '$' + base64.b64encode(b'\1\0\0\0').decode(),
267*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\1')),
268*61c4878aSAndroid Build Coastguard Worker        )
269*61c4878aSAndroid Build Coastguard Worker        self.assertIn('unknown token 00000001', repr(detok.detokenize(b'\1')))
270*61c4878aSAndroid Build Coastguard Worker
271*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
272*61c4878aSAndroid Build Coastguard Worker            'unknown token 00030201',
273*61c4878aSAndroid Build Coastguard Worker            detok.detokenize(b'\1\2\3').error_message(),
274*61c4878aSAndroid Build Coastguard Worker        )
275*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
276*61c4878aSAndroid Build Coastguard Worker            '$' + base64.b64encode(b'\1\2\3\0').decode(),
277*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\1\2\3')),
278*61c4878aSAndroid Build Coastguard Worker        )
279*61c4878aSAndroid Build Coastguard Worker        self.assertIn(
280*61c4878aSAndroid Build Coastguard Worker            'unknown token 00030201', repr(detok.detokenize(b'\1\2\3'))
281*61c4878aSAndroid Build Coastguard Worker        )
282*61c4878aSAndroid Build Coastguard Worker
283*61c4878aSAndroid Build Coastguard Worker    def test_decode_from_elf_data(self) -> None:
284*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
285*61c4878aSAndroid Build Coastguard Worker
286*61c4878aSAndroid Build Coastguard Worker        self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
287*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
288*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(JELLO_WORLD_TOKEN)), 'Jello, world!'
289*61c4878aSAndroid Build Coastguard Worker        )
290*61c4878aSAndroid Build Coastguard Worker
291*61c4878aSAndroid Build Coastguard Worker        undecoded_args = detok.detokenize(JELLO_WORLD_TOKEN + b'some junk')
292*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(undecoded_args.ok())
293*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(undecoded_args), 'Jello, world!')
294*61c4878aSAndroid Build Coastguard Worker
295*61c4878aSAndroid Build Coastguard Worker        self.assertTrue(detok.detokenize(b'\0\0\0\0').ok())
296*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(detok.detokenize(b'\0\0\0\0')), '')
297*61c4878aSAndroid Build Coastguard Worker
298*61c4878aSAndroid Build Coastguard Worker    def test_decode_from_elf_file(self) -> None:
299*61c4878aSAndroid Build Coastguard Worker        """Test decoding from an elf file."""
300*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
301*61c4878aSAndroid Build Coastguard Worker        expected_tokens = frozenset(detok.database.token_to_entries.keys())
302*61c4878aSAndroid Build Coastguard Worker
303*61c4878aSAndroid Build Coastguard Worker        with tempfile.NamedTemporaryFile('wb', delete=False) as elf:
304*61c4878aSAndroid Build Coastguard Worker            try:
305*61c4878aSAndroid Build Coastguard Worker                elf.write(ELF_WITH_TOKENIZER_SECTIONS)
306*61c4878aSAndroid Build Coastguard Worker                elf.close()
307*61c4878aSAndroid Build Coastguard Worker
308*61c4878aSAndroid Build Coastguard Worker                # Open ELF by file object
309*61c4878aSAndroid Build Coastguard Worker                with open(elf.name, 'rb') as fd:
310*61c4878aSAndroid Build Coastguard Worker                    detok = detokenize.Detokenizer(fd)
311*61c4878aSAndroid Build Coastguard Worker
312*61c4878aSAndroid Build Coastguard Worker                self.assertEqual(
313*61c4878aSAndroid Build Coastguard Worker                    expected_tokens,
314*61c4878aSAndroid Build Coastguard Worker                    frozenset(detok.database.token_to_entries.keys()),
315*61c4878aSAndroid Build Coastguard Worker                )
316*61c4878aSAndroid Build Coastguard Worker
317*61c4878aSAndroid Build Coastguard Worker                # Open ELF by path
318*61c4878aSAndroid Build Coastguard Worker                detok = detokenize.Detokenizer(elf.name)
319*61c4878aSAndroid Build Coastguard Worker                self.assertEqual(
320*61c4878aSAndroid Build Coastguard Worker                    expected_tokens,
321*61c4878aSAndroid Build Coastguard Worker                    frozenset(detok.database.token_to_entries.keys()),
322*61c4878aSAndroid Build Coastguard Worker                )
323*61c4878aSAndroid Build Coastguard Worker
324*61c4878aSAndroid Build Coastguard Worker                # Open ELF by elf_reader.Elf
325*61c4878aSAndroid Build Coastguard Worker                with open(elf.name, 'rb') as fd:
326*61c4878aSAndroid Build Coastguard Worker                    detok = detokenize.Detokenizer(elf_reader.Elf(fd))
327*61c4878aSAndroid Build Coastguard Worker
328*61c4878aSAndroid Build Coastguard Worker                self.assertEqual(
329*61c4878aSAndroid Build Coastguard Worker                    expected_tokens,
330*61c4878aSAndroid Build Coastguard Worker                    frozenset(detok.database.token_to_entries.keys()),
331*61c4878aSAndroid Build Coastguard Worker                )
332*61c4878aSAndroid Build Coastguard Worker            finally:
333*61c4878aSAndroid Build Coastguard Worker                os.unlink(elf.name)
334*61c4878aSAndroid Build Coastguard Worker
335*61c4878aSAndroid Build Coastguard Worker    def test_decode_from_csv_file(self) -> None:
336*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
337*61c4878aSAndroid Build Coastguard Worker        expected_tokens = frozenset(detok.database.token_to_entries.keys())
338*61c4878aSAndroid Build Coastguard Worker
339*61c4878aSAndroid Build Coastguard Worker        csv_database = str(detok.database)
340*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(csv_database.splitlines()), ALL_DOMAIN_TOKENS)
341*61c4878aSAndroid Build Coastguard Worker
342*61c4878aSAndroid Build Coastguard Worker        with tempfile.NamedTemporaryFile('w', delete=False) as csv_file:
343*61c4878aSAndroid Build Coastguard Worker            try:
344*61c4878aSAndroid Build Coastguard Worker                csv_file.write(csv_database)
345*61c4878aSAndroid Build Coastguard Worker                csv_file.close()
346*61c4878aSAndroid Build Coastguard Worker
347*61c4878aSAndroid Build Coastguard Worker                # Open CSV by path
348*61c4878aSAndroid Build Coastguard Worker                detok = detokenize.Detokenizer(csv_file.name)
349*61c4878aSAndroid Build Coastguard Worker                self.assertEqual(
350*61c4878aSAndroid Build Coastguard Worker                    expected_tokens,
351*61c4878aSAndroid Build Coastguard Worker                    frozenset(detok.database.token_to_entries.keys()),
352*61c4878aSAndroid Build Coastguard Worker                )
353*61c4878aSAndroid Build Coastguard Worker
354*61c4878aSAndroid Build Coastguard Worker                # Open CSV by file object
355*61c4878aSAndroid Build Coastguard Worker                with open(csv_file.name) as fd:
356*61c4878aSAndroid Build Coastguard Worker                    detok = detokenize.Detokenizer(fd)
357*61c4878aSAndroid Build Coastguard Worker
358*61c4878aSAndroid Build Coastguard Worker                self.assertEqual(
359*61c4878aSAndroid Build Coastguard Worker                    expected_tokens,
360*61c4878aSAndroid Build Coastguard Worker                    frozenset(detok.database.token_to_entries.keys()),
361*61c4878aSAndroid Build Coastguard Worker                )
362*61c4878aSAndroid Build Coastguard Worker            finally:
363*61c4878aSAndroid Build Coastguard Worker                os.unlink(csv_file.name)
364*61c4878aSAndroid Build Coastguard Worker
365*61c4878aSAndroid Build Coastguard Worker    def test_create_detokenizer_with_token_database(self) -> None:
366*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
367*61c4878aSAndroid Build Coastguard Worker        expected_tokens = frozenset(detok.database.token_to_entries.keys())
368*61c4878aSAndroid Build Coastguard Worker
369*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(detok.database)
370*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
371*61c4878aSAndroid Build Coastguard Worker            expected_tokens, frozenset(detok.database.token_to_entries.keys())
372*61c4878aSAndroid Build Coastguard Worker        )
373*61c4878aSAndroid Build Coastguard Worker
374*61c4878aSAndroid Build Coastguard Worker
375*61c4878aSAndroid Build Coastguard Workerclass DetokenizeWithCollisions(unittest.TestCase):
376*61c4878aSAndroid Build Coastguard Worker    """Tests collision resolution."""
377*61c4878aSAndroid Build Coastguard Worker
378*61c4878aSAndroid Build Coastguard Worker    def setUp(self) -> None:
379*61c4878aSAndroid Build Coastguard Worker        super().setUp()
380*61c4878aSAndroid Build Coastguard Worker        token = 0xBAAD
381*61c4878aSAndroid Build Coastguard Worker
382*61c4878aSAndroid Build Coastguard Worker        # Database with several conflicting tokens.
383*61c4878aSAndroid Build Coastguard Worker        self.detok = detokenize.Detokenizer(
384*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
385*61c4878aSAndroid Build Coastguard Worker                [
386*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
387*61c4878aSAndroid Build Coastguard Worker                        token, 'REMOVED', date_removed=dt.datetime(9, 1, 1)
388*61c4878aSAndroid Build Coastguard Worker                    ),
389*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(token, 'newer'),
390*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
391*61c4878aSAndroid Build Coastguard Worker                        token, 'A: %d', date_removed=dt.datetime(30, 5, 9)
392*61c4878aSAndroid Build Coastguard Worker                    ),
393*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
394*61c4878aSAndroid Build Coastguard Worker                        token, 'B: %c', date_removed=dt.datetime(30, 5, 10)
395*61c4878aSAndroid Build Coastguard Worker                    ),
396*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(token, 'C: %s'),
397*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(token, '%d%u'),
398*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(token, '%s%u %d'),
399*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, '%s'),
400*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, '%d'),
401*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, 'Three %s %s %s'),
402*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, 'Five %d %d %d %d %s'),
403*61c4878aSAndroid Build Coastguard Worker                ]
404*61c4878aSAndroid Build Coastguard Worker            )
405*61c4878aSAndroid Build Coastguard Worker        )
406*61c4878aSAndroid Build Coastguard Worker
407*61c4878aSAndroid Build Coastguard Worker    def test_collision_no_args_favors_most_recently_present(self) -> None:
408*61c4878aSAndroid Build Coastguard Worker        no_args = self.detok.detokenize(b'\xad\xba\0\0')
409*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(no_args.ok())
410*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(no_args.successes), 2)
411*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(no_args.failures), 5)
412*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(no_args.matches()), 7)
413*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(no_args), 'newer')
414*61c4878aSAndroid Build Coastguard Worker        best_result = no_args.best_result()
415*61c4878aSAndroid Build Coastguard Worker        assert best_result is not None
416*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(best_result.args), 0)
417*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(best_result[0], 'newer')
418*61c4878aSAndroid Build Coastguard Worker
419*61c4878aSAndroid Build Coastguard Worker    def test_collision_one_integer_arg_favors_most_recently_present(
420*61c4878aSAndroid Build Coastguard Worker        self,
421*61c4878aSAndroid Build Coastguard Worker    ) -> None:
422*61c4878aSAndroid Build Coastguard Worker        multiple_correct = self.detok.detokenize(b'\xad\xba\0\0\x7a')
423*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(multiple_correct.ok())
424*61c4878aSAndroid Build Coastguard Worker        self.assertIn('ERROR', repr(multiple_correct))
425*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(multiple_correct.successes), 2)
426*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(multiple_correct.failures), 5)
427*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(multiple_correct.matches()), 7)
428*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(multiple_correct), 'B: =')
429*61c4878aSAndroid Build Coastguard Worker
430*61c4878aSAndroid Build Coastguard Worker    def test_collision_one_integer_arg_favor_successful_decode(self) -> None:
431*61c4878aSAndroid Build Coastguard Worker        # One string decodes successfully, since the arg is out of range for %c.
432*61c4878aSAndroid Build Coastguard Worker        int_arg = self.detok.detokenize(b'\xad\xba\0\0\xfe\xff\xff\xff\x0f')
433*61c4878aSAndroid Build Coastguard Worker        self.assertTrue(int_arg.ok())
434*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(int_arg), 'A: 2147483647')
435*61c4878aSAndroid Build Coastguard Worker
436*61c4878aSAndroid Build Coastguard Worker    def test_collision_one_string_arg_favors_successful_decode(self) -> None:
437*61c4878aSAndroid Build Coastguard Worker        # One string decodes successfully, since decoding the argument as an
438*61c4878aSAndroid Build Coastguard Worker        # integer does not decode all the data.
439*61c4878aSAndroid Build Coastguard Worker        string_arg = self.detok.detokenize(b'\xad\xba\0\0\x02Hi')
440*61c4878aSAndroid Build Coastguard Worker        self.assertTrue(string_arg.ok())
441*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(string_arg), 'C: Hi')
442*61c4878aSAndroid Build Coastguard Worker
443*61c4878aSAndroid Build Coastguard Worker    def test_collision_one_string_arg_favors_decoding_all_data(self) -> None:
444*61c4878aSAndroid Build Coastguard Worker        result = self.detok.detokenize(b'\1\0\0\0\x83hi')
445*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(result.failures), 2)
446*61c4878aSAndroid Build Coastguard Worker        # Should resolve to the string since %d would leave one byte behind.
447*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(str(result), '%s')
448*61c4878aSAndroid Build Coastguard Worker
449*61c4878aSAndroid Build Coastguard Worker    def test_collision_multiple_args_favors_decoding_more_arguments(
450*61c4878aSAndroid Build Coastguard Worker        self,
451*61c4878aSAndroid Build Coastguard Worker    ) -> None:
452*61c4878aSAndroid Build Coastguard Worker        result = self.detok.detokenize(b'\2\0\0\0\1\2\1\4\5')
453*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(result.matches()), 2)
454*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(result.matches()[0][0], 'Five -1 1 -1 2 %s')
455*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(result.matches()[1][0], 'Three \2 \4 %s')
456*61c4878aSAndroid Build Coastguard Worker
457*61c4878aSAndroid Build Coastguard Worker    def test_collision_multiple_args_favors_decoding_all_arguments(
458*61c4878aSAndroid Build Coastguard Worker        self,
459*61c4878aSAndroid Build Coastguard Worker    ) -> None:
460*61c4878aSAndroid Build Coastguard Worker        unambiguous = self.detok.detokenize(b'\xad\xba\0\0\x01#\x00\x01')
461*61c4878aSAndroid Build Coastguard Worker        self.assertTrue(unambiguous.ok())
462*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(unambiguous.matches()), 7)
463*61c4878aSAndroid Build Coastguard Worker        self.assertEqual('#0 -1', str(unambiguous))
464*61c4878aSAndroid Build Coastguard Worker        self.assertIn('#0 -1', repr(unambiguous))
465*61c4878aSAndroid Build Coastguard Worker
466*61c4878aSAndroid Build Coastguard Worker
467*61c4878aSAndroid Build Coastguard Workerclass ManualPoolExecutor(concurrent.futures.Executor):
468*61c4878aSAndroid Build Coastguard Worker    """A stubbed pool executor that captures the most recent work request
469*61c4878aSAndroid Build Coastguard Worker    and holds it until the public process method is manually called."""
470*61c4878aSAndroid Build Coastguard Worker
471*61c4878aSAndroid Build Coastguard Worker    def __init__(self) -> None:
472*61c4878aSAndroid Build Coastguard Worker        super().__init__()
473*61c4878aSAndroid Build Coastguard Worker        self._func = None
474*61c4878aSAndroid Build Coastguard Worker
475*61c4878aSAndroid Build Coastguard Worker    # pylint: disable=arguments-differ
476*61c4878aSAndroid Build Coastguard Worker    def submit(self, func, *args, **kwargs):
477*61c4878aSAndroid Build Coastguard Worker        """Submits work to the pool, stashing the partial for later use."""
478*61c4878aSAndroid Build Coastguard Worker        self._func = functools.partial(func, *args, **kwargs)
479*61c4878aSAndroid Build Coastguard Worker
480*61c4878aSAndroid Build Coastguard Worker    def process(self) -> None:
481*61c4878aSAndroid Build Coastguard Worker        """Processes the latest func submitted to the pool."""
482*61c4878aSAndroid Build Coastguard Worker        if self._func is not None:
483*61c4878aSAndroid Build Coastguard Worker            self._func()
484*61c4878aSAndroid Build Coastguard Worker            self._func = None
485*61c4878aSAndroid Build Coastguard Worker
486*61c4878aSAndroid Build Coastguard Worker
487*61c4878aSAndroid Build Coastguard Workerclass InlinePoolExecutor(concurrent.futures.Executor):
488*61c4878aSAndroid Build Coastguard Worker    """A stubbed pool executor that runs work immediately, inline."""
489*61c4878aSAndroid Build Coastguard Worker
490*61c4878aSAndroid Build Coastguard Worker    # pylint: disable=arguments-differ
491*61c4878aSAndroid Build Coastguard Worker    def submit(self, func, *args, **kwargs):
492*61c4878aSAndroid Build Coastguard Worker        """Submits work to the pool, stashing the partial for later use."""
493*61c4878aSAndroid Build Coastguard Worker        func(*args, **kwargs)
494*61c4878aSAndroid Build Coastguard Worker
495*61c4878aSAndroid Build Coastguard Worker
496*61c4878aSAndroid Build Coastguard Worker@mock.patch('os.path.getmtime')
497*61c4878aSAndroid Build Coastguard Workerclass AutoUpdatingDetokenizerTest(unittest.TestCase):
498*61c4878aSAndroid Build Coastguard Worker    """Tests the AutoUpdatingDetokenizer class."""
499*61c4878aSAndroid Build Coastguard Worker
500*61c4878aSAndroid Build Coastguard Worker    def test_update(self, mock_getmtime) -> None:
501*61c4878aSAndroid Build Coastguard Worker        """Tests the update command."""
502*61c4878aSAndroid Build Coastguard Worker
503*61c4878aSAndroid Build Coastguard Worker        db = database.load_token_database(
504*61c4878aSAndroid Build Coastguard Worker            io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS)
505*61c4878aSAndroid Build Coastguard Worker        )
506*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(db), ALL_DOMAIN_TOKENS)
507*61c4878aSAndroid Build Coastguard Worker
508*61c4878aSAndroid Build Coastguard Worker        the_time = [100]
509*61c4878aSAndroid Build Coastguard Worker
510*61c4878aSAndroid Build Coastguard Worker        def move_back_time_if_file_exists(path) -> int:
511*61c4878aSAndroid Build Coastguard Worker            if os.path.exists(path):
512*61c4878aSAndroid Build Coastguard Worker                the_time[0] -= 1
513*61c4878aSAndroid Build Coastguard Worker                return the_time[0]
514*61c4878aSAndroid Build Coastguard Worker
515*61c4878aSAndroid Build Coastguard Worker            raise FileNotFoundError
516*61c4878aSAndroid Build Coastguard Worker
517*61c4878aSAndroid Build Coastguard Worker        mock_getmtime.side_effect = move_back_time_if_file_exists
518*61c4878aSAndroid Build Coastguard Worker
519*61c4878aSAndroid Build Coastguard Worker        with tempfile.NamedTemporaryFile('wb', delete=False) as file:
520*61c4878aSAndroid Build Coastguard Worker            try:
521*61c4878aSAndroid Build Coastguard Worker                file.close()
522*61c4878aSAndroid Build Coastguard Worker
523*61c4878aSAndroid Build Coastguard Worker                pool = ManualPoolExecutor()
524*61c4878aSAndroid Build Coastguard Worker                detok = detokenize.AutoUpdatingDetokenizer(
525*61c4878aSAndroid Build Coastguard Worker                    file.name, min_poll_period_s=0, pool=pool
526*61c4878aSAndroid Build Coastguard Worker                )
527*61c4878aSAndroid Build Coastguard Worker                self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
528*61c4878aSAndroid Build Coastguard Worker
529*61c4878aSAndroid Build Coastguard Worker                with open(file.name, 'wb') as fd:
530*61c4878aSAndroid Build Coastguard Worker                    tokens.write_binary(db, fd)
531*61c4878aSAndroid Build Coastguard Worker
532*61c4878aSAndroid Build Coastguard Worker                # After the change but before the pool runs in another thread,
533*61c4878aSAndroid Build Coastguard Worker                # the token should not exist.
534*61c4878aSAndroid Build Coastguard Worker                self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
535*61c4878aSAndroid Build Coastguard Worker
536*61c4878aSAndroid Build Coastguard Worker                # After the pool is allowed to process, it should.
537*61c4878aSAndroid Build Coastguard Worker                pool.process()
538*61c4878aSAndroid Build Coastguard Worker                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
539*61c4878aSAndroid Build Coastguard Worker            finally:
540*61c4878aSAndroid Build Coastguard Worker                os.unlink(file.name)
541*61c4878aSAndroid Build Coastguard Worker
542*61c4878aSAndroid Build Coastguard Worker    def test_update_with_directory(self, mock_getmtime) -> None:
543*61c4878aSAndroid Build Coastguard Worker        """Tests the update command with a directory format database."""
544*61c4878aSAndroid Build Coastguard Worker        db = database.load_token_database(
545*61c4878aSAndroid Build Coastguard Worker            io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS)
546*61c4878aSAndroid Build Coastguard Worker        )
547*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(db), ALL_DOMAIN_TOKENS)
548*61c4878aSAndroid Build Coastguard Worker
549*61c4878aSAndroid Build Coastguard Worker        the_time = [100]
550*61c4878aSAndroid Build Coastguard Worker
551*61c4878aSAndroid Build Coastguard Worker        def move_back_time_if_file_exists(path: str) -> int:
552*61c4878aSAndroid Build Coastguard Worker            if os.path.exists(path):
553*61c4878aSAndroid Build Coastguard Worker                the_time[0] -= 1
554*61c4878aSAndroid Build Coastguard Worker                return the_time[0]
555*61c4878aSAndroid Build Coastguard Worker
556*61c4878aSAndroid Build Coastguard Worker            raise FileNotFoundError
557*61c4878aSAndroid Build Coastguard Worker
558*61c4878aSAndroid Build Coastguard Worker        mock_getmtime.side_effect = move_back_time_if_file_exists
559*61c4878aSAndroid Build Coastguard Worker
560*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as dbdir:
561*61c4878aSAndroid Build Coastguard Worker            with tempfile.NamedTemporaryFile(
562*61c4878aSAndroid Build Coastguard Worker                'wb', delete=False, suffix='.pw_tokenizer.csv', dir=dbdir
563*61c4878aSAndroid Build Coastguard Worker            ) as matching_suffix_file, tempfile.NamedTemporaryFile(
564*61c4878aSAndroid Build Coastguard Worker                'wb', delete=False, suffix='.not.right', dir=dbdir
565*61c4878aSAndroid Build Coastguard Worker            ) as mismatched_suffix_file:
566*61c4878aSAndroid Build Coastguard Worker                try:
567*61c4878aSAndroid Build Coastguard Worker                    matching_suffix_file.close()
568*61c4878aSAndroid Build Coastguard Worker                    mismatched_suffix_file.close()
569*61c4878aSAndroid Build Coastguard Worker
570*61c4878aSAndroid Build Coastguard Worker                    pool = ManualPoolExecutor()
571*61c4878aSAndroid Build Coastguard Worker                    detok = detokenize.AutoUpdatingDetokenizer(
572*61c4878aSAndroid Build Coastguard Worker                        dbdir, min_poll_period_s=0, pool=pool
573*61c4878aSAndroid Build Coastguard Worker                    )
574*61c4878aSAndroid Build Coastguard Worker                    self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
575*61c4878aSAndroid Build Coastguard Worker
576*61c4878aSAndroid Build Coastguard Worker                    with open(mismatched_suffix_file.name, 'wb') as fd:
577*61c4878aSAndroid Build Coastguard Worker                        tokens.write_csv(db, fd)
578*61c4878aSAndroid Build Coastguard Worker                    pool.process()
579*61c4878aSAndroid Build Coastguard Worker                    self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
580*61c4878aSAndroid Build Coastguard Worker
581*61c4878aSAndroid Build Coastguard Worker                    with open(matching_suffix_file.name, 'wb') as fd:
582*61c4878aSAndroid Build Coastguard Worker                        tokens.write_csv(db, fd)
583*61c4878aSAndroid Build Coastguard Worker
584*61c4878aSAndroid Build Coastguard Worker                    # After the change but before the pool runs in another
585*61c4878aSAndroid Build Coastguard Worker                    # thread, the token should not exist.
586*61c4878aSAndroid Build Coastguard Worker                    self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
587*61c4878aSAndroid Build Coastguard Worker                    pool.process()
588*61c4878aSAndroid Build Coastguard Worker
589*61c4878aSAndroid Build Coastguard Worker                    # After the pool is allowed to process, it should.
590*61c4878aSAndroid Build Coastguard Worker                    self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
591*61c4878aSAndroid Build Coastguard Worker                finally:
592*61c4878aSAndroid Build Coastguard Worker                    os.unlink(mismatched_suffix_file.name)
593*61c4878aSAndroid Build Coastguard Worker                    os.unlink(matching_suffix_file.name)
594*61c4878aSAndroid Build Coastguard Worker                    os.rmdir(dbdir)
595*61c4878aSAndroid Build Coastguard Worker
596*61c4878aSAndroid Build Coastguard Worker        # The database stays around if the file is deleted.
597*61c4878aSAndroid Build Coastguard Worker        self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
598*61c4878aSAndroid Build Coastguard Worker
599*61c4878aSAndroid Build Coastguard Worker    def test_no_update_if_time_is_same(self, mock_getmtime) -> None:
600*61c4878aSAndroid Build Coastguard Worker        mock_getmtime.return_value = 100
601*61c4878aSAndroid Build Coastguard Worker
602*61c4878aSAndroid Build Coastguard Worker        with tempfile.NamedTemporaryFile('wb', delete=False) as file:
603*61c4878aSAndroid Build Coastguard Worker            try:
604*61c4878aSAndroid Build Coastguard Worker                tokens.write_csv(
605*61c4878aSAndroid Build Coastguard Worker                    database.load_token_database(
606*61c4878aSAndroid Build Coastguard Worker                        io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS)
607*61c4878aSAndroid Build Coastguard Worker                    ),
608*61c4878aSAndroid Build Coastguard Worker                    file,
609*61c4878aSAndroid Build Coastguard Worker                )
610*61c4878aSAndroid Build Coastguard Worker                file.close()
611*61c4878aSAndroid Build Coastguard Worker
612*61c4878aSAndroid Build Coastguard Worker                detok = detokenize.AutoUpdatingDetokenizer(
613*61c4878aSAndroid Build Coastguard Worker                    file.name, min_poll_period_s=0, pool=InlinePoolExecutor()
614*61c4878aSAndroid Build Coastguard Worker                )
615*61c4878aSAndroid Build Coastguard Worker                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
616*61c4878aSAndroid Build Coastguard Worker
617*61c4878aSAndroid Build Coastguard Worker                # Empty the database, but keep the mock modified time the same.
618*61c4878aSAndroid Build Coastguard Worker                with open(file.name, 'wb'):
619*61c4878aSAndroid Build Coastguard Worker                    pass
620*61c4878aSAndroid Build Coastguard Worker
621*61c4878aSAndroid Build Coastguard Worker                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
622*61c4878aSAndroid Build Coastguard Worker                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
623*61c4878aSAndroid Build Coastguard Worker
624*61c4878aSAndroid Build Coastguard Worker                # Move back time so the now-empty file is reloaded.
625*61c4878aSAndroid Build Coastguard Worker                mock_getmtime.return_value = 50
626*61c4878aSAndroid Build Coastguard Worker                self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
627*61c4878aSAndroid Build Coastguard Worker            finally:
628*61c4878aSAndroid Build Coastguard Worker                os.unlink(file.name)
629*61c4878aSAndroid Build Coastguard Worker
630*61c4878aSAndroid Build Coastguard Worker    def test_token_domain_in_str(self, _) -> None:
631*61c4878aSAndroid Build Coastguard Worker        """Tests a str containing a domain."""
632*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.AutoUpdatingDetokenizer(
633*61c4878aSAndroid Build Coastguard Worker            f'{ELF_WITH_TOKENIZER_SECTIONS_PATH}#',  # Default domain
634*61c4878aSAndroid Build Coastguard Worker            min_poll_period_s=0,
635*61c4878aSAndroid Build Coastguard Worker            pool=InlinePoolExecutor(),
636*61c4878aSAndroid Build Coastguard Worker        )
637*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(detok.database), DEFAULT_DOMAIN_TOKENS)
638*61c4878aSAndroid Build Coastguard Worker
639*61c4878aSAndroid Build Coastguard Worker    def test_token_domain_in_path(self, _) -> None:
640*61c4878aSAndroid Build Coastguard Worker        """Tests a Path() containing a domain."""
641*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.AutoUpdatingDetokenizer(
642*61c4878aSAndroid Build Coastguard Worker            Path(f'{ELF_WITH_TOKENIZER_SECTIONS_PATH}#'),
643*61c4878aSAndroid Build Coastguard Worker            min_poll_period_s=0,
644*61c4878aSAndroid Build Coastguard Worker            pool=InlinePoolExecutor(),
645*61c4878aSAndroid Build Coastguard Worker        )
646*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(detok.database), DEFAULT_DOMAIN_TOKENS)
647*61c4878aSAndroid Build Coastguard Worker
648*61c4878aSAndroid Build Coastguard Worker    def test_token_no_domain_in_str(self, _) -> None:
649*61c4878aSAndroid Build Coastguard Worker        """Tests a str without a domain, which loads all domains."""
650*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.AutoUpdatingDetokenizer(
651*61c4878aSAndroid Build Coastguard Worker            str(ELF_WITH_TOKENIZER_SECTIONS_PATH),
652*61c4878aSAndroid Build Coastguard Worker            min_poll_period_s=0,
653*61c4878aSAndroid Build Coastguard Worker            pool=InlinePoolExecutor(),
654*61c4878aSAndroid Build Coastguard Worker        )
655*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(detok.database), ALL_DOMAIN_TOKENS)
656*61c4878aSAndroid Build Coastguard Worker
657*61c4878aSAndroid Build Coastguard Worker    def test_token_no_domain_in_path(self, _) -> None:
658*61c4878aSAndroid Build Coastguard Worker        """Tests a Path() without a domain, which loads all domains."""
659*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.AutoUpdatingDetokenizer(
660*61c4878aSAndroid Build Coastguard Worker            ELF_WITH_TOKENIZER_SECTIONS_PATH,
661*61c4878aSAndroid Build Coastguard Worker            min_poll_period_s=0,
662*61c4878aSAndroid Build Coastguard Worker            pool=InlinePoolExecutor(),
663*61c4878aSAndroid Build Coastguard Worker        )
664*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(len(detok.database), ALL_DOMAIN_TOKENS)
665*61c4878aSAndroid Build Coastguard Worker
666*61c4878aSAndroid Build Coastguard Worker    def test_invalid_domain_specification(self, _) -> None:
667*61c4878aSAndroid Build Coastguard Worker        with self.assertRaises(ValueError, msg='Too many # delimiters'):
668*61c4878aSAndroid Build Coastguard Worker            detokenize.AutoUpdatingDetokenizer(
669*61c4878aSAndroid Build Coastguard Worker                f'{ELF_WITH_TOKENIZER_SECTIONS_PATH}##',
670*61c4878aSAndroid Build Coastguard Worker                min_poll_period_s=0,
671*61c4878aSAndroid Build Coastguard Worker                pool=InlinePoolExecutor(),
672*61c4878aSAndroid Build Coastguard Worker            )
673*61c4878aSAndroid Build Coastguard Worker
674*61c4878aSAndroid Build Coastguard Worker
675*61c4878aSAndroid Build Coastguard Workerdef _next_char(message: bytes) -> bytes:
676*61c4878aSAndroid Build Coastguard Worker    return bytes(b + 1 for b in message)
677*61c4878aSAndroid Build Coastguard Worker
678*61c4878aSAndroid Build Coastguard Worker
679*61c4878aSAndroid Build Coastguard Workerclass NestedMessageParserTest(unittest.TestCase):
680*61c4878aSAndroid Build Coastguard Worker    """Tests parsing prefixed messages."""
681*61c4878aSAndroid Build Coastguard Worker
682*61c4878aSAndroid Build Coastguard Worker    class _Case(NamedTuple):
683*61c4878aSAndroid Build Coastguard Worker        data: bytes
684*61c4878aSAndroid Build Coastguard Worker        expected: bytes
685*61c4878aSAndroid Build Coastguard Worker        title: str
686*61c4878aSAndroid Build Coastguard Worker        transform: Callable[[bytes], bytes] = _next_char
687*61c4878aSAndroid Build Coastguard Worker
688*61c4878aSAndroid Build Coastguard Worker    TRANSFORM_TEST_CASES = (
689*61c4878aSAndroid Build Coastguard Worker        _Case(b'$abcd', b'%bcde', 'single message'),
690*61c4878aSAndroid Build Coastguard Worker        _Case(
691*61c4878aSAndroid Build Coastguard Worker            b'$$WHAT?$abc$WHY? is this $ok $',
692*61c4878aSAndroid Build Coastguard Worker            b'%%WHAT?%bcd%WHY? is this %ok %',
693*61c4878aSAndroid Build Coastguard Worker            'message and non-message',
694*61c4878aSAndroid Build Coastguard Worker        ),
695*61c4878aSAndroid Build Coastguard Worker        _Case(b'$1$', b'%1%', 'empty message'),
696*61c4878aSAndroid Build Coastguard Worker        _Case(b'$abc$defgh', b'%bcd%efghh', 'sequential message'),
697*61c4878aSAndroid Build Coastguard Worker        _Case(
698*61c4878aSAndroid Build Coastguard Worker            b'w$abcx$defygh$$abz',
699*61c4878aSAndroid Build Coastguard Worker            b'w$ABCx$DEFygh$$ABz',
700*61c4878aSAndroid Build Coastguard Worker            'interspersed start/end non-message',
701*61c4878aSAndroid Build Coastguard Worker            bytes.upper,
702*61c4878aSAndroid Build Coastguard Worker        ),
703*61c4878aSAndroid Build Coastguard Worker        _Case(
704*61c4878aSAndroid Build Coastguard Worker            b'$abcx$defygh$$ab',
705*61c4878aSAndroid Build Coastguard Worker            b'$ABCx$DEFygh$$AB',
706*61c4878aSAndroid Build Coastguard Worker            'interspersed start/end message ',
707*61c4878aSAndroid Build Coastguard Worker            bytes.upper,
708*61c4878aSAndroid Build Coastguard Worker        ),
709*61c4878aSAndroid Build Coastguard Worker    )
710*61c4878aSAndroid Build Coastguard Worker
711*61c4878aSAndroid Build Coastguard Worker    def setUp(self) -> None:
712*61c4878aSAndroid Build Coastguard Worker        self.decoder = detokenize.NestedMessageParser('$', 'abcdefg')
713*61c4878aSAndroid Build Coastguard Worker
714*61c4878aSAndroid Build Coastguard Worker    def test_transform_io(self) -> None:
715*61c4878aSAndroid Build Coastguard Worker        for data, expected, title, transform in self.TRANSFORM_TEST_CASES:
716*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(
717*61c4878aSAndroid Build Coastguard Worker                expected,
718*61c4878aSAndroid Build Coastguard Worker                b''.join(
719*61c4878aSAndroid Build Coastguard Worker                    self.decoder.transform_io(io.BytesIO(data), transform)
720*61c4878aSAndroid Build Coastguard Worker                ),
721*61c4878aSAndroid Build Coastguard Worker                f'{title}: {data!r}',
722*61c4878aSAndroid Build Coastguard Worker            )
723*61c4878aSAndroid Build Coastguard Worker
724*61c4878aSAndroid Build Coastguard Worker    def test_transform_bytes_with_flush(self) -> None:
725*61c4878aSAndroid Build Coastguard Worker        for data, expected, title, transform in self.TRANSFORM_TEST_CASES:
726*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(
727*61c4878aSAndroid Build Coastguard Worker                expected,
728*61c4878aSAndroid Build Coastguard Worker                self.decoder.transform(data, transform, flush=True),
729*61c4878aSAndroid Build Coastguard Worker                f'{title}: {data!r}',
730*61c4878aSAndroid Build Coastguard Worker            )
731*61c4878aSAndroid Build Coastguard Worker
732*61c4878aSAndroid Build Coastguard Worker    def test_transform_bytes_sequential(self) -> None:
733*61c4878aSAndroid Build Coastguard Worker        def transform(message):
734*61c4878aSAndroid Build Coastguard Worker            return message.upper().replace(b'$', b'*')
735*61c4878aSAndroid Build Coastguard Worker
736*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b'abc$abcd', transform), b'abc')
737*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b'$', transform), b'*ABCD')
738*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b'$b', transform), b'*')
739*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b'', transform), b'')
740*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b' ', transform), b'*B ')
741*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b'hello', transform), b'hello')
742*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(self.decoder.transform(b'?? $ab', transform), b'?? ')
743*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
744*61c4878aSAndroid Build Coastguard Worker            self.decoder.transform(b'123$ab4$56$a', transform), b'*AB123*AB4*56'
745*61c4878aSAndroid Build Coastguard Worker        )
746*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
747*61c4878aSAndroid Build Coastguard Worker            self.decoder.transform(b'bc', transform, flush=True), b'*ABC'
748*61c4878aSAndroid Build Coastguard Worker        )
749*61c4878aSAndroid Build Coastguard Worker
750*61c4878aSAndroid Build Coastguard Worker    MESSAGES_TEST: Any = (
751*61c4878aSAndroid Build Coastguard Worker        (b'123$abc456$a', (False, b'123'), (True, b'$abc'), (False, b'456')),
752*61c4878aSAndroid Build Coastguard Worker        (b'7$abcd', (True, b'$a'), (False, b'7')),
753*61c4878aSAndroid Build Coastguard Worker        (b'e',),
754*61c4878aSAndroid Build Coastguard Worker        (b'',),
755*61c4878aSAndroid Build Coastguard Worker        (b'$', (True, b'$abcde')),
756*61c4878aSAndroid Build Coastguard Worker        (b'$', (True, b'$')),
757*61c4878aSAndroid Build Coastguard Worker        (b'$a$b$c', (True, b'$'), (True, b'$a'), (True, b'$b')),
758*61c4878aSAndroid Build Coastguard Worker        (b'1', (True, b'$c'), (False, b'1')),
759*61c4878aSAndroid Build Coastguard Worker        (b'',),
760*61c4878aSAndroid Build Coastguard Worker        (b'?', (False, b'?')),
761*61c4878aSAndroid Build Coastguard Worker        (b'!@', (False, b'!@')),
762*61c4878aSAndroid Build Coastguard Worker        (b'%^&', (False, b'%^&')),
763*61c4878aSAndroid Build Coastguard Worker    )
764*61c4878aSAndroid Build Coastguard Worker
765*61c4878aSAndroid Build Coastguard Worker    def test_read_messages(self) -> None:
766*61c4878aSAndroid Build Coastguard Worker        for step in self.MESSAGES_TEST:
767*61c4878aSAndroid Build Coastguard Worker            data: bytes = step[0]
768*61c4878aSAndroid Build Coastguard Worker            pieces: tuple[tuple[bool, bytes], ...] = step[1:]
769*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(tuple(self.decoder.read_messages(data)), pieces)
770*61c4878aSAndroid Build Coastguard Worker
771*61c4878aSAndroid Build Coastguard Worker    def test_read_messages_flush(self) -> None:
772*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
773*61c4878aSAndroid Build Coastguard Worker            list(self.decoder.read_messages(b'123$a')), [(False, b'123')]
774*61c4878aSAndroid Build Coastguard Worker        )
775*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(list(self.decoder.read_messages(b'b')), [])
776*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
777*61c4878aSAndroid Build Coastguard Worker            list(self.decoder.read_messages(b'', flush=True)), [(True, b'$ab')]
778*61c4878aSAndroid Build Coastguard Worker        )
779*61c4878aSAndroid Build Coastguard Worker
780*61c4878aSAndroid Build Coastguard Worker    def test_read_messages_io(self) -> None:
781*61c4878aSAndroid Build Coastguard Worker        # Rework the read_messages test data for stream input.
782*61c4878aSAndroid Build Coastguard Worker        data = io.BytesIO(b''.join(step[0] for step in self.MESSAGES_TEST))
783*61c4878aSAndroid Build Coastguard Worker        expected_pieces = sum((step[1:] for step in self.MESSAGES_TEST), ())
784*61c4878aSAndroid Build Coastguard Worker
785*61c4878aSAndroid Build Coastguard Worker        result = self.decoder.read_messages_io(data)
786*61c4878aSAndroid Build Coastguard Worker        for expected_is_message, expected_data in expected_pieces:
787*61c4878aSAndroid Build Coastguard Worker            if expected_is_message:
788*61c4878aSAndroid Build Coastguard Worker                is_message, piece = next(result)
789*61c4878aSAndroid Build Coastguard Worker                self.assertTrue(is_message)
790*61c4878aSAndroid Build Coastguard Worker                self.assertEqual(expected_data, piece)
791*61c4878aSAndroid Build Coastguard Worker            else:  # the IO version yields non-messages byte by byte
792*61c4878aSAndroid Build Coastguard Worker                for byte in expected_data:
793*61c4878aSAndroid Build Coastguard Worker                    is_message, piece = next(result)
794*61c4878aSAndroid Build Coastguard Worker                    self.assertFalse(is_message)
795*61c4878aSAndroid Build Coastguard Worker                    self.assertEqual(bytes([byte]), piece)
796*61c4878aSAndroid Build Coastguard Worker
797*61c4878aSAndroid Build Coastguard Worker
798*61c4878aSAndroid Build Coastguard Workerclass DetokenizeNested(unittest.TestCase):
799*61c4878aSAndroid Build Coastguard Worker    """Tests detokenizing nested tokens"""
800*61c4878aSAndroid Build Coastguard Worker
801*61c4878aSAndroid Build Coastguard Worker    def test_nested_hashed_arg(self) -> None:
802*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
803*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
804*61c4878aSAndroid Build Coastguard Worker                [
805*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(0xA, 'tokenized argument'),
806*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
807*61c4878aSAndroid Build Coastguard Worker                        2,
808*61c4878aSAndroid Build Coastguard Worker                        'This is a ' + '$#%08x',
809*61c4878aSAndroid Build Coastguard Worker                    ),
810*61c4878aSAndroid Build Coastguard Worker                ]
811*61c4878aSAndroid Build Coastguard Worker            )
812*61c4878aSAndroid Build Coastguard Worker        )
813*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
814*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x14')),
815*61c4878aSAndroid Build Coastguard Worker            'This is a tokenized argument',
816*61c4878aSAndroid Build Coastguard Worker        )
817*61c4878aSAndroid Build Coastguard Worker
818*61c4878aSAndroid Build Coastguard Worker    def test_nested_base64_arg(self) -> None:
819*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
820*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
821*61c4878aSAndroid Build Coastguard Worker                [
822*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, 'base64 argument'),
823*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, 'This is a %s'),
824*61c4878aSAndroid Build Coastguard Worker                ]
825*61c4878aSAndroid Build Coastguard Worker            )
826*61c4878aSAndroid Build Coastguard Worker        )
827*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
828*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x09$AQAAAA==')),  # token for 1
829*61c4878aSAndroid Build Coastguard Worker            'This is a base64 argument',
830*61c4878aSAndroid Build Coastguard Worker        )
831*61c4878aSAndroid Build Coastguard Worker
832*61c4878aSAndroid Build Coastguard Worker    def test_deeply_nested_arg(self) -> None:
833*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
834*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
835*61c4878aSAndroid Build Coastguard Worker                [
836*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, '$10#0000000005'),
837*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, 'This is a $#%08x'),
838*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(3, 'deeply nested argument'),
839*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(4, '$AQAAAA=='),
840*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(5, '$AwAAAA=='),
841*61c4878aSAndroid Build Coastguard Worker                ]
842*61c4878aSAndroid Build Coastguard Worker            )
843*61c4878aSAndroid Build Coastguard Worker        )
844*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
845*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x08')),  # token for 4
846*61c4878aSAndroid Build Coastguard Worker            'This is a deeply nested argument',
847*61c4878aSAndroid Build Coastguard Worker        )
848*61c4878aSAndroid Build Coastguard Worker
849*61c4878aSAndroid Build Coastguard Worker
850*61c4878aSAndroid Build Coastguard Workerclass DetokenizeBase64(unittest.TestCase):
851*61c4878aSAndroid Build Coastguard Worker    """Tests detokenizing Base64 messages."""
852*61c4878aSAndroid Build Coastguard Worker
853*61c4878aSAndroid Build Coastguard Worker    JELLO = b'$' + base64.b64encode(JELLO_WORLD_TOKEN)
854*61c4878aSAndroid Build Coastguard Worker
855*61c4878aSAndroid Build Coastguard Worker    RECURSION_STRING = f'The secret message is "{JELLO.decode()}"'
856*61c4878aSAndroid Build Coastguard Worker    RECURSION = b'$' + base64.b64encode(
857*61c4878aSAndroid Build Coastguard Worker        struct.pack('I', tokens.c_hash(RECURSION_STRING))
858*61c4878aSAndroid Build Coastguard Worker    )
859*61c4878aSAndroid Build Coastguard Worker
860*61c4878aSAndroid Build Coastguard Worker    RECURSION_STRING_2 = f"'{RECURSION.decode()}', said the spy."
861*61c4878aSAndroid Build Coastguard Worker    RECURSION_2 = b'$' + base64.b64encode(
862*61c4878aSAndroid Build Coastguard Worker        struct.pack('I', tokens.c_hash(RECURSION_STRING_2))
863*61c4878aSAndroid Build Coastguard Worker    )
864*61c4878aSAndroid Build Coastguard Worker
865*61c4878aSAndroid Build Coastguard Worker    TEST_CASES = (
866*61c4878aSAndroid Build Coastguard Worker        (b'', b''),
867*61c4878aSAndroid Build Coastguard Worker        (b'nothing here', b'nothing here'),
868*61c4878aSAndroid Build Coastguard Worker        (JELLO, b'Jello, world!'),
869*61c4878aSAndroid Build Coastguard Worker        (JELLO + b'a', b'Jello, world!a'),
870*61c4878aSAndroid Build Coastguard Worker        (JELLO + b'abc', b'Jello, world!abc'),
871*61c4878aSAndroid Build Coastguard Worker        (JELLO + b'abc=', b'Jello, world!abc='),
872*61c4878aSAndroid Build Coastguard Worker        (b'$a' + JELLO + b'a', b'$aJello, world!a'),
873*61c4878aSAndroid Build Coastguard Worker        (b'Hello ' + JELLO + b'?', b'Hello Jello, world!?'),
874*61c4878aSAndroid Build Coastguard Worker        (b'$' + JELLO, b'$Jello, world!'),
875*61c4878aSAndroid Build Coastguard Worker        (JELLO + JELLO, b'Jello, world!Jello, world!'),
876*61c4878aSAndroid Build Coastguard Worker        (JELLO + b'$' + JELLO, b'Jello, world!$Jello, world!'),
877*61c4878aSAndroid Build Coastguard Worker        (JELLO + b'$a' + JELLO + b'bcd', b'Jello, world!$aJello, world!bcd'),
878*61c4878aSAndroid Build Coastguard Worker        (b'$3141', b'$3141'),
879*61c4878aSAndroid Build Coastguard Worker        (JELLO + b'$3141', b'Jello, world!$3141'),
880*61c4878aSAndroid Build Coastguard Worker        (
881*61c4878aSAndroid Build Coastguard Worker            JELLO + b'$a' + JELLO + b'b' + JELLO + b'c',
882*61c4878aSAndroid Build Coastguard Worker            b'Jello, world!$aJello, world!bJello, world!c',
883*61c4878aSAndroid Build Coastguard Worker        ),
884*61c4878aSAndroid Build Coastguard Worker        (RECURSION, b'The secret message is "Jello, world!"'),
885*61c4878aSAndroid Build Coastguard Worker        (
886*61c4878aSAndroid Build Coastguard Worker            RECURSION_2,
887*61c4878aSAndroid Build Coastguard Worker            b'\'The secret message is "Jello, world!"\', said the spy.',
888*61c4878aSAndroid Build Coastguard Worker        ),
889*61c4878aSAndroid Build Coastguard Worker    )
890*61c4878aSAndroid Build Coastguard Worker
891*61c4878aSAndroid Build Coastguard Worker    def setUp(self) -> None:
892*61c4878aSAndroid Build Coastguard Worker        super().setUp()
893*61c4878aSAndroid Build Coastguard Worker        db = database.load_token_database(
894*61c4878aSAndroid Build Coastguard Worker            io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS)
895*61c4878aSAndroid Build Coastguard Worker        )
896*61c4878aSAndroid Build Coastguard Worker        db.add(
897*61c4878aSAndroid Build Coastguard Worker            tokens.TokenizedStringEntry(tokens.c_hash(s), s)
898*61c4878aSAndroid Build Coastguard Worker            for s in [self.RECURSION_STRING, self.RECURSION_STRING_2]
899*61c4878aSAndroid Build Coastguard Worker        )
900*61c4878aSAndroid Build Coastguard Worker        self.detok = detokenize.Detokenizer(db)
901*61c4878aSAndroid Build Coastguard Worker
902*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_base64_live(self) -> None:
903*61c4878aSAndroid Build Coastguard Worker        for data, expected in self.TEST_CASES:
904*61c4878aSAndroid Build Coastguard Worker            output = io.BytesIO()
905*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_base64_live(io.BytesIO(data), output)
906*61c4878aSAndroid Build Coastguard Worker
907*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(expected, output.getvalue(), f'Input: {data!r}')
908*61c4878aSAndroid Build Coastguard Worker
909*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_base64_to_file(self) -> None:
910*61c4878aSAndroid Build Coastguard Worker        for data, expected in self.TEST_CASES:
911*61c4878aSAndroid Build Coastguard Worker            output = io.BytesIO()
912*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_base64_to_file(data, output)
913*61c4878aSAndroid Build Coastguard Worker
914*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(expected, output.getvalue())
915*61c4878aSAndroid Build Coastguard Worker
916*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_base64(self) -> None:
917*61c4878aSAndroid Build Coastguard Worker        for data, expected in self.TEST_CASES:
918*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(expected, self.detok.detokenize_base64(data))
919*61c4878aSAndroid Build Coastguard Worker
920*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_base64_str(self) -> None:
921*61c4878aSAndroid Build Coastguard Worker        for data, expected in self.TEST_CASES:
922*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(
923*61c4878aSAndroid Build Coastguard Worker                expected.decode(), self.detok.detokenize_base64(data.decode())
924*61c4878aSAndroid Build Coastguard Worker            )
925*61c4878aSAndroid Build Coastguard Worker
926*61c4878aSAndroid Build Coastguard Worker
927*61c4878aSAndroid Build Coastguard Workerclass DetokenizeInfiniteRecursion(unittest.TestCase):
928*61c4878aSAndroid Build Coastguard Worker    """Tests that infinite Base64 token recursion resolves."""
929*61c4878aSAndroid Build Coastguard Worker
930*61c4878aSAndroid Build Coastguard Worker    def setUp(self) -> None:
931*61c4878aSAndroid Build Coastguard Worker        super().setUp()
932*61c4878aSAndroid Build Coastguard Worker        self.detok = detokenize.Detokenizer(
933*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
934*61c4878aSAndroid Build Coastguard Worker                [
935*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(0, '$AAAAAA=='),  # token for 0
936*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, '$AgAAAA=='),  # token for 2
937*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, '$#00000003'),  # token for 3
938*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(3, '$AgAAAA=='),  # token for 2
939*61c4878aSAndroid Build Coastguard Worker                ]
940*61c4878aSAndroid Build Coastguard Worker            )
941*61c4878aSAndroid Build Coastguard Worker        )
942*61c4878aSAndroid Build Coastguard Worker
943*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_self_recursion(self) -> None:
944*61c4878aSAndroid Build Coastguard Worker        for depth in range(5):
945*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(
946*61c4878aSAndroid Build Coastguard Worker                self.detok.detokenize_text(
947*61c4878aSAndroid Build Coastguard Worker                    b'This one is deep: $AAAAAA==', recursion=depth
948*61c4878aSAndroid Build Coastguard Worker                ),
949*61c4878aSAndroid Build Coastguard Worker                b'This one is deep: $AAAAAA==',
950*61c4878aSAndroid Build Coastguard Worker            )
951*61c4878aSAndroid Build Coastguard Worker
952*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_self_recursion_default(self) -> None:
953*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
954*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_text(
955*61c4878aSAndroid Build Coastguard Worker                b'This one is deep: $AAAAAA==',
956*61c4878aSAndroid Build Coastguard Worker            ),
957*61c4878aSAndroid Build Coastguard Worker            b'This one is deep: $AAAAAA==',
958*61c4878aSAndroid Build Coastguard Worker        )
959*61c4878aSAndroid Build Coastguard Worker
960*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_cyclic_recursion_even(self) -> None:
961*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
962*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_text(b'I said "$AQAAAA=="', recursion=6),
963*61c4878aSAndroid Build Coastguard Worker            b'I said "$AgAAAA=="',
964*61c4878aSAndroid Build Coastguard Worker        )
965*61c4878aSAndroid Build Coastguard Worker
966*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_cyclic_recursion_odd(self) -> None:
967*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
968*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_text(b'I said "$AQAAAA=="', recursion=7),
969*61c4878aSAndroid Build Coastguard Worker            b'I said "$#00000003"',
970*61c4878aSAndroid Build Coastguard Worker        )
971*61c4878aSAndroid Build Coastguard Worker
972*61c4878aSAndroid Build Coastguard Worker
973*61c4878aSAndroid Build Coastguard Workerclass DetokenizeBase64InfiniteRecursion(unittest.TestCase):
974*61c4878aSAndroid Build Coastguard Worker    """Tests that infinite Bas64 token recursion resolves."""
975*61c4878aSAndroid Build Coastguard Worker
976*61c4878aSAndroid Build Coastguard Worker    def setUp(self) -> None:
977*61c4878aSAndroid Build Coastguard Worker        super().setUp()
978*61c4878aSAndroid Build Coastguard Worker        self.detok = detokenize.Detokenizer(
979*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
980*61c4878aSAndroid Build Coastguard Worker                [
981*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(0, '$AAAAAA=='),  # token for 0
982*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, '$AgAAAA=='),  # token for 2
983*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, '$AwAAAA=='),  # token for 3
984*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(3, '$AgAAAA=='),  # token for 2
985*61c4878aSAndroid Build Coastguard Worker                ]
986*61c4878aSAndroid Build Coastguard Worker            )
987*61c4878aSAndroid Build Coastguard Worker        )
988*61c4878aSAndroid Build Coastguard Worker
989*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_self_recursion(self) -> None:
990*61c4878aSAndroid Build Coastguard Worker        for depth in range(5):
991*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(
992*61c4878aSAndroid Build Coastguard Worker                self.detok.detokenize_base64(
993*61c4878aSAndroid Build Coastguard Worker                    b'This one is deep: $AAAAAA==', recursion=depth
994*61c4878aSAndroid Build Coastguard Worker                ),
995*61c4878aSAndroid Build Coastguard Worker                b'This one is deep: $AAAAAA==',
996*61c4878aSAndroid Build Coastguard Worker            )
997*61c4878aSAndroid Build Coastguard Worker
998*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_self_recursion_default(self) -> None:
999*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1000*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_base64(b'This one is deep: $64#AAAAAA=='),
1001*61c4878aSAndroid Build Coastguard Worker            b'This one is deep: $AAAAAA==',
1002*61c4878aSAndroid Build Coastguard Worker        )
1003*61c4878aSAndroid Build Coastguard Worker
1004*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_cyclic_recursion_even(self) -> None:
1005*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1006*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_base64(b'I said "$AQAAAA=="', recursion=2),
1007*61c4878aSAndroid Build Coastguard Worker            b'I said "$AgAAAA=="',
1008*61c4878aSAndroid Build Coastguard Worker        )
1009*61c4878aSAndroid Build Coastguard Worker
1010*61c4878aSAndroid Build Coastguard Worker    def test_detokenize_cyclic_recursion_odd(self) -> None:
1011*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1012*61c4878aSAndroid Build Coastguard Worker            self.detok.detokenize_base64(b'I said "$AQAAAA=="', recursion=3),
1013*61c4878aSAndroid Build Coastguard Worker            b'I said "$AwAAAA=="',
1014*61c4878aSAndroid Build Coastguard Worker        )
1015*61c4878aSAndroid Build Coastguard Worker
1016*61c4878aSAndroid Build Coastguard Worker
1017*61c4878aSAndroid Build Coastguard Workerclass DetokenizeNestedDomains(unittest.TestCase):
1018*61c4878aSAndroid Build Coastguard Worker    """Tests detokenizing nested tokens with specified domains"""
1019*61c4878aSAndroid Build Coastguard Worker
1020*61c4878aSAndroid Build Coastguard Worker    def test_nested_hashed_arg_with_one_domain_match(self) -> None:
1021*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
1022*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
1023*61c4878aSAndroid Build Coastguard Worker                [
1024*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(0xA, 'domain1', 'D1'),
1025*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1026*61c4878aSAndroid Build Coastguard Worker                        2, 'This is all in ' + '${D1}#%08x', 'D1'
1027*61c4878aSAndroid Build Coastguard Worker                    ),
1028*61c4878aSAndroid Build Coastguard Worker                ]
1029*61c4878aSAndroid Build Coastguard Worker            )
1030*61c4878aSAndroid Build Coastguard Worker        )
1031*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1032*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x14')),
1033*61c4878aSAndroid Build Coastguard Worker            'This is all in domain1',
1034*61c4878aSAndroid Build Coastguard Worker        )
1035*61c4878aSAndroid Build Coastguard Worker
1036*61c4878aSAndroid Build Coastguard Worker    def test_multiple_nested_args_in_one_sentence(self) -> None:
1037*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
1038*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
1039*61c4878aSAndroid Build Coastguard Worker                [
1040*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(0xA, 'nested token 1', 'D1'),
1041*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1042*61c4878aSAndroid Build Coastguard Worker                        2,
1043*61c4878aSAndroid Build Coastguard Worker                        'This is '
1044*61c4878aSAndroid Build Coastguard Worker                        + '${D1}#%08x'
1045*61c4878aSAndroid Build Coastguard Worker                        + ' and this is '
1046*61c4878aSAndroid Build Coastguard Worker                        + '${D1}#00000003',
1047*61c4878aSAndroid Build Coastguard Worker                        'D1',
1048*61c4878aSAndroid Build Coastguard Worker                    ),
1049*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(3, 'nested token 2', 'D1'),
1050*61c4878aSAndroid Build Coastguard Worker                ]
1051*61c4878aSAndroid Build Coastguard Worker            )
1052*61c4878aSAndroid Build Coastguard Worker        )
1053*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1054*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x14')),
1055*61c4878aSAndroid Build Coastguard Worker            'This is nested token 1 and this is nested token 2',
1056*61c4878aSAndroid Build Coastguard Worker        )
1057*61c4878aSAndroid Build Coastguard Worker
1058*61c4878aSAndroid Build Coastguard Worker    def test_nested_hashed_arg_with_two_domain_match(self) -> None:
1059*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
1060*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
1061*61c4878aSAndroid Build Coastguard Worker                [
1062*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1063*61c4878aSAndroid Build Coastguard Worker                        0xA, 'and this is domain1', 'D1'
1064*61c4878aSAndroid Build Coastguard Worker                    ),
1065*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1066*61c4878aSAndroid Build Coastguard Worker                        2, 'This is domain2 ' + '${D1}#%08x', 'D2'
1067*61c4878aSAndroid Build Coastguard Worker                    ),
1068*61c4878aSAndroid Build Coastguard Worker                ]
1069*61c4878aSAndroid Build Coastguard Worker            )
1070*61c4878aSAndroid Build Coastguard Worker        )
1071*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1072*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x14')),
1073*61c4878aSAndroid Build Coastguard Worker            'This is domain2 and this is domain1',
1074*61c4878aSAndroid Build Coastguard Worker        )
1075*61c4878aSAndroid Build Coastguard Worker
1076*61c4878aSAndroid Build Coastguard Worker    def test_nested_hashed_arg_with_different_domains(self) -> None:
1077*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
1078*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
1079*61c4878aSAndroid Build Coastguard Worker                [
1080*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(0xA, 'domain1', 'D1'),
1081*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1082*61c4878aSAndroid Build Coastguard Worker                        2, 'This is all in ' + '${D2}#%08x', 'D1'
1083*61c4878aSAndroid Build Coastguard Worker                    ),
1084*61c4878aSAndroid Build Coastguard Worker                ]
1085*61c4878aSAndroid Build Coastguard Worker            )
1086*61c4878aSAndroid Build Coastguard Worker        )
1087*61c4878aSAndroid Build Coastguard Worker        result = detok.detokenize(b'\x02\0\0\0\x14')
1088*61c4878aSAndroid Build Coastguard Worker        self.assertFalse(result == 'This is all in domain1')
1089*61c4878aSAndroid Build Coastguard Worker
1090*61c4878aSAndroid Build Coastguard Worker    def test_nested_base64_arg_multiple_domains(self) -> None:
1091*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
1092*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
1093*61c4878aSAndroid Build Coastguard Worker                [
1094*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(1, '${D5}#00000004', 'D1'),
1095*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(2, 'This is a %s', 'D1'),
1096*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(3, 'base64 argument', 'D2'),
1097*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1098*61c4878aSAndroid Build Coastguard Worker                        4, 'nested ' + '${D2}#00000003', 'D5'
1099*61c4878aSAndroid Build Coastguard Worker                    ),
1100*61c4878aSAndroid Build Coastguard Worker                ]
1101*61c4878aSAndroid Build Coastguard Worker            )
1102*61c4878aSAndroid Build Coastguard Worker        )
1103*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1104*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x09$AQAAAA==')),  # token for 1
1105*61c4878aSAndroid Build Coastguard Worker            'This is a nested base64 argument',
1106*61c4878aSAndroid Build Coastguard Worker        )
1107*61c4878aSAndroid Build Coastguard Worker
1108*61c4878aSAndroid Build Coastguard Worker    def test_nested_hashed_arg_with_domain_whitespace(self) -> None:
1109*61c4878aSAndroid Build Coastguard Worker        detok = detokenize.Detokenizer(
1110*61c4878aSAndroid Build Coastguard Worker            tokens.Database(
1111*61c4878aSAndroid Build Coastguard Worker                [
1112*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1113*61c4878aSAndroid Build Coastguard Worker                        0xA, 'and this is domain1', 'Domain1'
1114*61c4878aSAndroid Build Coastguard Worker                    ),
1115*61c4878aSAndroid Build Coastguard Worker                    tokens.TokenizedStringEntry(
1116*61c4878aSAndroid Build Coastguard Worker                        2, 'This is domain2 ' + '${Domain 1}#%08x', 'D2'
1117*61c4878aSAndroid Build Coastguard Worker                    ),
1118*61c4878aSAndroid Build Coastguard Worker                ]
1119*61c4878aSAndroid Build Coastguard Worker            )
1120*61c4878aSAndroid Build Coastguard Worker        )
1121*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
1122*61c4878aSAndroid Build Coastguard Worker            str(detok.detokenize(b'\x02\0\0\0\x14')),
1123*61c4878aSAndroid Build Coastguard Worker            'This is domain2 and this is domain1',
1124*61c4878aSAndroid Build Coastguard Worker        )
1125*61c4878aSAndroid Build Coastguard Worker
1126*61c4878aSAndroid Build Coastguard Worker
1127*61c4878aSAndroid Build Coastguard Workerif __name__ == '__main__':
1128*61c4878aSAndroid Build Coastguard Worker    unittest.main()
1129