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