• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2022 The Pigweed Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5# use this file except in compliance with the License. You may obtain a copy of
6# the License at
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations under
14# the License.
15"""Tests the tokenized string decode module."""
16
17from datetime import datetime
18import unittest
19
20import tokenized_string_decoding_test_data as tokenized_string
21import varint_test_data
22from pw_tokenizer import decode
23
24
25def error(msg, value=None) -> str:
26    """Formats msg as the message for an argument that failed to parse."""
27    if value is None:
28        return '<[{}]>'.format(msg)
29    return '<[{} ({})]>'.format(msg, value)
30
31
32class TestDecodeTokenized(unittest.TestCase):
33    """Tests decoding tokenized strings with various arguments."""
34    def test_decode_generated_data(self) -> None:
35        self.assertGreater(len(tokenized_string.TEST_DATA), 100)
36
37        for fmt, decoded, encoded in tokenized_string.TEST_DATA:
38            self.assertEqual(decode.decode(fmt, encoded, True), decoded)
39
40    def test_unicode_decode_errors(self) -> None:
41        """Tests unicode errors, which do not occur in the C++ decoding code."""
42        self.assertEqual(decode.decode('Why, %c', b'\x01', True),
43                         'Why, ' + error('%c ERROR', -1))
44
45        self.assertEqual(
46            decode.decode('%sXY%+ldxy%u', b'\x83N\x80!\x01\x02', True),
47            '{}XY{}xy{}'.format(error('%s ERROR', "'N\\x80!'"),
48                                error('%+ld SKIPPED', -1),
49                                error('%u SKIPPED', 1)))
50
51        self.assertEqual(
52            decode.decode('%s%lld%9u', b'\x82$\x80\x80', True),
53            '{0}{1}{2}'.format(error("%s ERROR ('$\\x80')"),
54                               error('%lld SKIPPED'), error('%9u SKIPPED')))
55
56        self.assertEqual(decode.decode('%c', b'\xff\xff\xff\xff\x0f', True),
57                         error('%c ERROR', -2147483648))
58
59    def test_ignore_errors(self) -> None:
60        self.assertEqual(decode.decode('Why, %c', b'\x01'), 'Why, %c')
61
62        self.assertEqual(decode.decode('%s %d', b'\x01!'), '! %d')
63
64    def test_pointer(self) -> None:
65        """Tests pointer args, which are not natively supported in Python."""
66        self.assertEqual(decode.decode('Hello: %p', b'\x00', True),
67                         'Hello: 0x00000000')
68        self.assertEqual(decode.decode('%p%d%d', b'\x02\x80', True),
69                         '0x00000001<[%d ERROR]><[%d SKIPPED]>')
70
71
72class TestIntegerDecoding(unittest.TestCase):
73    """Tests decoding variable-length integers."""
74    def test_decode_generated_data(self) -> None:
75        test_data = varint_test_data.TEST_DATA
76        self.assertGreater(len(test_data), 100)
77
78        for signed_spec, signed, unsigned_spec, unsigned, encoded in test_data:
79            self.assertEqual(
80                int(signed),
81                decode.FormatSpec.from_string(signed_spec).decode(
82                    bytearray(encoded)).value)
83
84            self.assertEqual(
85                int(unsigned),
86                decode.FormatSpec.from_string(unsigned_spec).decode(
87                    bytearray(encoded)).value)
88
89
90class TestFormattedString(unittest.TestCase):
91    """Tests scoring how successfully a formatted string decoded."""
92    def test_no_args(self) -> None:
93        result = decode.FormatString('string').format(b'')
94
95        self.assertTrue(result.ok())
96        self.assertEqual(result.score(), (True, True, 0, 0, datetime.max))
97
98    def test_one_arg(self) -> None:
99        result = decode.FormatString('%d').format(b'\0')
100
101        self.assertTrue(result.ok())
102        self.assertEqual(result.score(), (True, True, 0, 1, datetime.max))
103
104    def test_missing_args(self) -> None:
105        result = decode.FormatString('%p%d%d').format(b'\x02\x80')
106
107        self.assertFalse(result.ok())
108        self.assertEqual(result.score(), (False, True, -2, 3, datetime.max))
109        self.assertGreater(result.score(), result.score(datetime.now()))
110        self.assertGreater(result.score(datetime.now()),
111                           result.score(datetime.min))
112
113    def test_compare_score(self) -> None:
114        all_args_ok = decode.FormatString('%d%d%d').format(b'\0\0\0')
115        missing_one_arg = decode.FormatString('%d%d%d').format(b'\0\0')
116        missing_two_args = decode.FormatString('%d%d%d').format(b'\0')
117        all_args_extra_data = decode.FormatString('%d%d%d').format(b'\0\0\0\1')
118        missing_one_arg_extra_data = decode.FormatString('%d%d%d').format(
119            b'\0' + b'\x80' * 100)
120
121        self.assertGreater(all_args_ok.score(), missing_one_arg.score())
122        self.assertGreater(missing_one_arg.score(), missing_two_args.score())
123        self.assertGreater(missing_two_args.score(),
124                           all_args_extra_data.score())
125        self.assertGreater(all_args_extra_data.score(),
126                           missing_one_arg_extra_data.score())
127
128
129if __name__ == '__main__':
130    unittest.main()
131