#!/usr/bin/env python # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from __future__ import division, print_function import csv import io import os import struct import subprocess import sys import tempfile import unittest from test_utils import Py23TestCase try: import gen_esp32part except ImportError: sys.path.append('..') import gen_esp32part SIMPLE_CSV = """ # Name,Type,SubType,Offset,Size,Flags factory,0,2,65536,1048576, """ LONGER_BINARY_TABLE = b'' # type 0x00, subtype 0x00, # offset 64KB, size 1MB LONGER_BINARY_TABLE += b'\xAA\x50\x00\x00' + \ b'\x00\x00\x01\x00' + \ b'\x00\x00\x10\x00' + \ b'factory\0' + (b'\0' * 8) + \ b'\x00\x00\x00\x00' # type 0x01, subtype 0x20, # offset 0x110000, size 128KB LONGER_BINARY_TABLE += b'\xAA\x50\x01\x20' + \ b'\x00\x00\x11\x00' + \ b'\x00\x02\x00\x00' + \ b'data' + (b'\0' * 12) + \ b'\x00\x00\x00\x00' # type 0x10, subtype 0x00, # offset 0x150000, size 1MB LONGER_BINARY_TABLE += b'\xAA\x50\x10\x00' + \ b'\x00\x00\x15\x00' + \ b'\x00\x10\x00\x00' + \ b'second' + (b'\0' * 10) + \ b'\x00\x00\x00\x00' # MD5 checksum LONGER_BINARY_TABLE += b'\xEB\xEB' + b'\xFF' * 14 LONGER_BINARY_TABLE += b'\xf9\xbd\x06\x1b\x45\x68\x6f\x86\x57\x1a\x2c\xd5\x2a\x1d\xa6\x5b' # empty partition LONGER_BINARY_TABLE += b'\xFF' * 32 def _strip_trailing_ffs(binary_table): """ Strip all FFs down to the last 32 bytes (terminating entry) """ while binary_table.endswith(b'\xFF' * 64): binary_table = binary_table[0:len(binary_table) - 32] return binary_table class CSVParserTests(Py23TestCase): def test_simple_partition(self): table = gen_esp32part.PartitionTable.from_csv(SIMPLE_CSV) self.assertEqual(len(table), 1) self.assertEqual(table[0].name, 'factory') self.assertEqual(table[0].type, 0) self.assertEqual(table[0].subtype, 2) self.assertEqual(table[0].offset, 65536) self.assertEqual(table[0].size, 1048576) def test_require_type(self): csv = """ # Name,Type, SubType,Offset,Size ihavenotype, """ with self.assertRaisesRegex(gen_esp32part.InputError, 'type'): gen_esp32part.PartitionTable.from_csv(csv) def test_type_subtype_names(self): csv_magicnumbers = """ # Name, Type, SubType, Offset, Size myapp, 0, 0,, 0x100000 myota_0, 0, 0x10,, 0x100000 myota_1, 0, 0x11,, 0x100000 myota_15, 0, 0x1f,, 0x100000 mytest, 0, 0x20,, 0x100000 myota_status, 1, 0,, 0x2000 """ csv_nomagicnumbers = """ # Name, Type, SubType, Offset, Size myapp, app, factory,, 0x100000 myota_0, app, ota_0,, 0x100000 myota_1, app, ota_1,, 0x100000 myota_15, app, ota_15,, 0x100000 mytest, app, test,, 0x100000 myota_status, data, ota,, 0x2000 """ # make two equivalent partition tables, one using # magic numbers and one using shortcuts. Ensure they match magic = gen_esp32part.PartitionTable.from_csv(csv_magicnumbers) magic.verify() nomagic = gen_esp32part.PartitionTable.from_csv(csv_nomagicnumbers) nomagic.verify() self.assertEqual(nomagic['myapp'].type, 0) self.assertEqual(nomagic['myapp'].subtype, 0) self.assertEqual(nomagic['myapp'], magic['myapp']) self.assertEqual(nomagic['myota_0'].type, 0) self.assertEqual(nomagic['myota_0'].subtype, 0x10) self.assertEqual(nomagic['myota_0'], magic['myota_0']) self.assertEqual(nomagic['myota_15'], magic['myota_15']) self.assertEqual(nomagic['mytest'], magic['mytest']) self.assertEqual(nomagic['myota_status'], magic['myota_status']) # self.assertEqual(nomagic.to_binary(), magic.to_binary()) def test_unit_suffixes(self): csv = """ # Name, Type, Subtype, Offset, Size one_megabyte, app, factory, 64k, 1M """ t = gen_esp32part.PartitionTable.from_csv(csv) t.verify() self.assertEqual(t[0].offset, 64 * 1024) self.assertEqual(t[0].size, 1 * 1024 * 1024) def test_default_offsets(self): csv = """ # Name, Type, Subtype, Offset, Size first, app, factory,, 1M second, data, 0x15,, 1M minidata, data, 0x40,, 32K otherapp, app, factory,, 1M """ t = gen_esp32part.PartitionTable.from_csv(csv) # 'first' self.assertEqual(t[0].offset, 0x010000) # 64KB boundary as it's an app image self.assertEqual(t[0].size, 0x100000) # Size specified in CSV # 'second' self.assertEqual(t[1].offset, 0x110000) # prev offset+size self.assertEqual(t[1].size, 0x100000) # Size specified in CSV # 'minidata' self.assertEqual(t[2].offset, 0x210000) # 'otherapp' self.assertEqual(t[3].offset, 0x220000) # 64KB boundary as it's an app image def test_negative_size_to_offset(self): csv = """ # Name, Type, Subtype, Offset, Size first, app, factory, 0x10000, -2M second, data, 0x15, , 1M """ t = gen_esp32part.PartitionTable.from_csv(csv) t.verify() # 'first' self.assertEqual(t[0].offset, 0x10000) # in CSV self.assertEqual(t[0].size, 0x200000 - t[0].offset) # Up to 2M # 'second' self.assertEqual(t[1].offset, 0x200000) # prev offset+size def test_overlapping_offsets_fail(self): csv = """ first, app, factory, 0x100000, 2M second, app, ota_0, 0x200000, 1M """ with self.assertRaisesRegex(gen_esp32part.InputError, 'overlap'): t = gen_esp32part.PartitionTable.from_csv(csv) t.verify() def test_unique_name_fail(self): csv = """ first, app, factory, 0x100000, 1M first, app, ota_0, 0x200000, 1M """ with self.assertRaisesRegex(gen_esp32part.InputError, 'Partition names must be unique'): t = gen_esp32part.PartitionTable.from_csv(csv) t.verify() class BinaryOutputTests(Py23TestCase): def test_binary_entry(self): csv = """ first, 0x30, 0xEE, 0x100400, 0x300000 """ t = gen_esp32part.PartitionTable.from_csv(csv) tb = _strip_trailing_ffs(t.to_binary()) self.assertEqual(len(tb), 64 + 32) self.assertEqual(b'\xAA\x50', tb[0:2]) # magic self.assertEqual(b'\x30\xee', tb[2:4]) # type, subtype eo, es = struct.unpack('