curtin-dev team mailing list archive
-
curtin-dev team
-
Mailing list archive
-
Message #02907
[Merge] ~dbungert/curtin:py35-fixes into curtin:release/23.1
Dan Bungert has proposed merging ~dbungert/curtin:py35-fixes into curtin:release/23.1 with ~dbungert/curtin:series-fixes as a prerequisite.
Commit message:
Several fixes for compatibility with python 3.5
Requested reviews:
Server Team CI bot (server-team-bot): continuous-integration
curtin developers (curtin-dev)
For more details, see:
https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/443616
--
Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:py35-fixes into curtin:release/23.1.
diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py
index 570da83..e599397 100644
--- a/curtin/commands/block_meta_v2.py
+++ b/curtin/commands/block_meta_v2.py
@@ -1,10 +1,6 @@
# This file is part of curtin. See LICENSE file for copyright and license info.
import os
-from typing import (
- List,
- Optional,
- )
import attr
@@ -26,7 +22,7 @@ from curtin.storage_config import (
from curtin.udev import udevadm_settle
-def to_utf8_hex_notation(string: str) -> str:
+def to_utf8_hex_notation(string):
''' Convert a string into a valid ASCII string where all characters outside
the alphanumerical range (according to bytes.isalnum()) are translated to
their corresponding \\x notation. E.g.:
@@ -39,25 +35,26 @@ def to_utf8_hex_notation(string: str) -> str:
if bytes([c]).isalnum():
result += bytes([c]).decode()
else:
- result += f'\\x{c:02x}'
+ result += '\\x{:02x}'.format(c)
+
return result
-@attr.s(auto_attribs=True)
+@attr.s()
class PartTableEntry:
# The order listed here matches the order sfdisk represents these fields
# when using the --dump argument.
- number: int
- start: int
- size: int
- type: str
- uuid: Optional[str]
+ number = attr.ib(default=None)
+ start = attr.ib(default=None)
+ size = attr.ib(default=None)
+ type = attr.ib(default=None)
+ uuid = attr.ib(default=None)
# name here is the sfdisk term - quoted descriptive text of the partition -
# not to be confused with what make_dname() does.
# Offered in the partition command as 'partition_name'.
- name: Optional[str]
- attrs: Optional[List[str]]
- bootable: bool = False
+ name = attr.ib(default=None)
+ attrs = attr.ib(default=None)
+ bootable = attr.ib(default=False)
def render(self):
r = '{}: '.format(self.number)
@@ -149,8 +146,8 @@ class SFDiskPartTable:
self._sector_bytes = sector_bytes
if ONE_MIB_BYTES % sector_bytes != 0:
raise Exception(
- f"sector_bytes {sector_bytes} does not divide 1MiB, cannot "
- "continue!")
+ "sector_bytes {} does not divide 1MiB, cannot "
+ "continue!".format(sector_bytes))
self.one_mib_sectors = ONE_MIB_BYTES // sector_bytes
def bytes2sectors(self, amount):
diff --git a/curtin/commands/install.py b/curtin/commands/install.py
index 7ccad87..bd1016a 100644
--- a/curtin/commands/install.py
+++ b/curtin/commands/install.py
@@ -112,16 +112,16 @@ def writeline(fname, output):
pass
-@attr.s(auto_attribs=True)
+@attr.s()
class WorkingDir:
- target: str
- top: str
- scratch: str
- interfaces: str
- netconf: str
- netstate: str
- fstab: str
- config_file: str
+ target = attr.ib()
+ top = attr.ib()
+ scratch = attr.ib()
+ interfaces = attr.ib()
+ netconf = attr.ib()
+ netstate = attr.ib()
+ fstab = attr.ib()
+ config_file = attr.ib()
@classmethod
def import_existing(cls, config):
diff --git a/tests/integration/test_block_meta.py b/tests/integration/test_block_meta.py
index a2368e8..4a4ece4 100644
--- a/tests/integration/test_block_meta.py
+++ b/tests/integration/test_block_meta.py
@@ -1,17 +1,16 @@
# This file is part of curtin. See LICENSE file for copyright and license info.
-import dataclasses
-from dataclasses import dataclass
import contextlib
import json
import os
-from parameterized import parameterized
+import pprint
import re
import sys
-from typing import Optional
from unittest import skipIf
import yaml
+import attr
+
from curtin import block, compat, distro, log, udev, util
from curtin.commands.block_meta import _get_volume_fstype
from curtin.commands.block_meta_v2 import ONE_MIB_BYTES
@@ -38,22 +37,25 @@ def loop_dev(image, sector_size=512):
util.subp(['losetup', '--detach', dev])
-@dataclass(order=True)
+@attr.s(init=True, cmp=False)
class PartData:
- number: Optional[int] = None
- offset: Optional[int] = None
- size: Optional[int] = None
- boot: Optional[bool] = None
- partition_type: Optional[str] = None
+ number = attr.ib(default=None)
+ offset = attr.ib(default=None)
+ size = attr.ib(default=None)
+ boot = attr.ib(default=None)
+ partition_type = attr.ib(default=None)
# test cases may initialize the values they care about
# test utilities shall initialize all fields
def assertFieldsAreNotNone(self):
- for field in dataclasses.fields(self):
+ for field in attr.fields(self.__class__):
assert getattr(self, field.name) is not None
+ def __lt__(self, other):
+ return self.number < other.number
+
def __eq__(self, other):
- for field in dataclasses.fields(self):
+ for field in attr.fields(self.__class__):
myval = getattr(self, field.name)
otherval = getattr(other, field.name)
if myval is not None and otherval is not None \
@@ -64,7 +66,7 @@ class PartData:
def _get_ext_size(dev, part_action):
num = part_action['number']
- cmd = ['dumpe2fs', '-h', f'{dev}p{num}']
+ cmd = ['dumpe2fs', '-h', '{}p{}'.format(dev, num)]
out = util.subp(cmd, capture=True)[0]
for line in out.splitlines():
if line.startswith('Block count'):
@@ -79,7 +81,7 @@ def _get_ntfs_size(dev, part_action):
cmd = ['ntfsresize',
'--no-action',
'--force', # needed post-resize, which otherwise demands a CHKDSK
- '--info', f'{dev}p{num}']
+ '--info', '{}p{}'.format(dev, num)]
out = util.subp(cmd, capture=True)[0]
# Sample input:
# Current volume size: 41939456 bytes (42 MB)
@@ -101,7 +103,7 @@ _get_fs_sizers = {
def _get_filesystem_size(dev, part_action, fstype='ext4'):
if fstype not in _get_fs_sizers.keys():
- raise Exception(f'_get_filesystem_size: no support for {fstype}')
+ raise Exception('_get_filesystem_size: no support for %s' % fstype)
return _get_fs_sizers[fstype](dev, part_action)
@@ -124,7 +126,7 @@ def summarize_partitions(dev):
(unused, s_number, s_offset, s_size) = [
entry for entry in sysfs_data
if '/dev/' + entry[0] == node][0]
- assert node.startswith(f'{dev}p')
+ assert node.startswith(dev + 'p')
number = int(node[len(dev) + 1:])
ptype = part['type']
offset = part['start'] * sectorsize
@@ -206,13 +208,13 @@ class TestBlockMeta(IntegrationTestCase):
def mount(self, dev, partition_cfg):
mnt_point = self.tmp_dir()
num = partition_cfg['number']
- with util.mount(f'{dev}p{num}', mnt_point):
+ with util.mount('{}p{}'.format(dev, num), mnt_point):
yield mnt_point
@contextlib.contextmanager
def open_file_on_part(self, dev, part_action, mode):
with self.mount(dev, part_action) as mnt_point:
- with open(f'{mnt_point}/data.txt', mode) as fp:
+ with open(mnt_point + '/data.txt', mode) as fp:
yield fp
def create_data(self, dev, part_action):
@@ -232,7 +234,7 @@ class TestBlockMeta(IntegrationTestCase):
tolerance = 512 * 10
actual_fssize = _get_filesystem_size(dev, part_action, fstype)
diff = expected - actual_fssize
- self.assertTrue(0 <= diff <= tolerance, f'difference of {diff}')
+ self.assertTrue(0 <= diff <= tolerance, 'difference of ' + str(diff))
def run_bm(self, config, *args, **kwargs):
config_path = self.tmp_path('config.yaml')
@@ -602,7 +604,7 @@ class TestBlockMeta(IntegrationTestCase):
}
with loop_dev(img) as dev:
try:
- self.run_bm(curtin_cfg, f'--devices={dev}', env=cmd_env)
+ self.run_bm(curtin_cfg, '--devices=' + dev, env=cmd_env)
finally:
util.subp(['umount', mnt_point])
udev.udevadm_settle()
@@ -622,7 +624,7 @@ class TestBlockMeta(IntegrationTestCase):
fstype=fstype)
self.run_bm(config.render())
with loop_dev(img) as dev:
- self.assertEqual(fstype, _get_volume_fstype(f'{dev}p1'))
+ self.assertEqual(fstype, _get_volume_fstype(dev + 'p1'))
self.create_data(dev, p1)
self.assertEqual(
summarize_partitions(dev), [
@@ -651,7 +653,7 @@ class TestBlockMeta(IntegrationTestCase):
p1['size'] = size
self.run_bm(config.render())
with loop_dev(img) as dev:
- self.assertEqual('ntfs', _get_volume_fstype(f'{dev}p1'))
+ self.assertEqual('ntfs', _get_volume_fstype(dev + 'p1'))
self.create_data(dev, p1)
self.assertEqual(
summarize_partitions(dev), [
@@ -959,11 +961,11 @@ class TestBlockMeta(IntegrationTestCase):
with self.mount(dev, p1) as mnt_point:
# Attempt to create files across the partition with gaps
for i in range(1, 41):
- with open(f'{mnt_point}/{str(i)}', 'wb') as fp:
+ with open('{}/{}'.format(mnt_point, i), 'wb') as fp:
fp.write(bytes([i]) * (2 << 20))
for i in range(1, 41):
if i % 5 != 0:
- os.remove(f'{mnt_point}/{str(i)}')
+ os.remove('{}/{}'.format(mnt_point, i))
config = StorageConfigBuilder(version=2)
config.add_image(path=img, size='100M', ptable='gpt')
@@ -981,7 +983,7 @@ class TestBlockMeta(IntegrationTestCase):
])
with self.mount(dev, p1) as mnt_point:
for i in range(5, 41, 5):
- with open(f'{mnt_point}/{i}', 'rb') as fp:
+ with open('{}/{}'.format(mnt_point, i), 'rb') as fp:
self.assertEqual(bytes([i]) * (2 << 20), fp.read())
def test_parttype_dos(self):
@@ -1043,8 +1045,7 @@ class TestBlockMeta(IntegrationTestCase):
PartData(number=4, offset=80 << 20, size=19 << 20,
partition_type=winre))
- @parameterized.expand([('msdos',), ('gpt',)])
- def test_disk_label_id_persistent(self, ptable):
+ def _test_disk_label_id_persistent(self, ptable):
# when the disk is preserved, the disk label id shall also be preserved
self.img = self.tmp_path('image.img')
config = StorageConfigBuilder(version=2)
@@ -1063,6 +1064,12 @@ class TestBlockMeta(IntegrationTestCase):
with loop_dev(self.img) as dev:
self.assertEqual(orig_label_id, _get_disk_label_id(dev))
+ def test_disk_label_id_persistent_msdos(self):
+ self._test_disk_label_id_persistent('msdos')
+
+ def test_disk_label_id_persistent_gpt(self):
+ self._test_disk_label_id_persistent('gpt')
+
def test_gpt_uuid_persistent(self):
# A persistent partition with an unspecified uuid shall keep the uuid
self.img = self.tmp_path('image.img')
@@ -1099,12 +1106,7 @@ class TestBlockMeta(IntegrationTestCase):
actual_name = sfdisk_info['partitions'][0]['name']
self.assertEqual(name, actual_name)
- @parameterized.expand([
- ('random', CiTestCase.random_string(),),
- # "écrasé" means "overwritten"
- ('unicode', "'name' must not be écrasé/덮어쓴!"),
- ])
- def test_gpt_name_persistent(self, title, name):
+ def _test_gpt_name_persistent(self, title, name):
self.img = self.tmp_path('image.img')
config = StorageConfigBuilder(version=2)
config.add_image(path=self.img, size='20M', ptable='gpt')
@@ -1127,6 +1129,14 @@ class TestBlockMeta(IntegrationTestCase):
actual_name = sfdisk_info['partitions'][0]['name']
self.assertEqual(name, actual_name)
+ def test_gpt_name_persistent_random(self):
+ self._test_gpt_name_persistent('random', CiTestCase.random_string())
+
+ def test_gpt_name_persistent_unicode(self):
+ self._test_gpt_name_persistent('unicode',
+ "'name' must not be écrasé/덮어쓴!")
+
+
def test_gpt_set_single_attr(self):
self.img = self.tmp_path('image.img')
config = StorageConfigBuilder(version=2)
@@ -1271,8 +1281,7 @@ table-length: 256'''.encode()
self.assertPartitions(
PartData(number=1, offset=1 << 20, size=1 << 20))
- @parameterized.expand(((1,), (2,)))
- def test_swap(self, sv):
+ def _test_swap(self, sv):
self.img = self.tmp_path('image.img')
config = StorageConfigBuilder(version=sv)
config.add_image(path=self.img, create=True, size='20M',
@@ -1283,3 +1292,9 @@ table-length: 256'''.encode()
self.assertPartitions(
PartData(number=1, offset=1 << 20, size=1 << 20, boot=False,
partition_type='82'))
+
+ def test_swap_sv1(self):
+ self._test_swap(1)
+
+ def test_swap_sv2(self):
+ self._test_swap(2)
diff --git a/tests/integration/webserv.py b/tests/integration/webserv.py
index f4ce4e4..de30e04 100644
--- a/tests/integration/webserv.py
+++ b/tests/integration/webserv.py
@@ -1,8 +1,10 @@
# This file is part of curtin. See LICENSE file for copyright and license info.
-import threading
-import socketserver
from http.server import SimpleHTTPRequestHandler
+import socketserver
+import threading
+import os
+
from tests.vmtests.image_sync import IMAGE_DIR
@@ -12,7 +14,17 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
class ImageHTTPRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
- super().__init__(*args, directory=IMAGE_DIR, **kwargs)
+ try:
+ super().__init__(*args, directory=IMAGE_DIR, **kwargs)
+ except TypeError:
+ # SimpleHTTPRequestHandler in python < 3.7 doesn't take a directory
+ # arg, fake it.
+ curdir = os.getcwd()
+ os.chdir(IMAGE_DIR)
+ try:
+ super().__init__(*args, **kwargs)
+ finally:
+ os.chdir(curdir)
class ImageServer:
@@ -50,4 +62,4 @@ class ImageServer:
if self.server is not None:
ip, port = self.server.server_address
- return f"http://{ip}:{port}"
+ return "http://{}:{}".format(ip, port)
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index 5599886..14be419 100644
--- a/tests/unittests/test_commands_block_meta.py
+++ b/tests/unittests/test_commands_block_meta.py
@@ -3066,10 +3066,10 @@ label: gpt
table = block_meta_v2.GPTPartTable(512)
table.add(dict(number=1, offset=1 << 20, size=9 << 20,
flag='boot', partition_type=ptype))
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={ptype}'''
+1: start=2048 size=18432 type={}'''.format(ptype)
self.assertEqual(expected, table.render())
def test_gpt_name(self):
@@ -3079,10 +3079,10 @@ label: gpt
partition_name=name))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
to_hex = block_meta_v2.to_utf8_hex_notation
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id} name="{to_hex(name)}"'''
+1: start=2048 size=18432 type={} name="{}"'''.format(type_id, to_hex(name))
self.assertEqual(expected, table.render())
def test_gpt_name_free_text(self):
@@ -3096,10 +3096,10 @@ label: gpt
table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
partition_name=name))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id} name="{expected_name}"'''
+1: start=2048 size=18432 type={} name="{}"'''.format(type_id, expected_name)
self.assertEqual(expected, table.render())
def test_gpt_attrs_none(self):
@@ -3107,10 +3107,10 @@ label: gpt
table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
attrs=None))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id}'''
+1: start=2048 size=18432 type={}'''.format(type_id)
self.assertEqual(expected, table.render())
def test_gpt_attrs_empty(self):
@@ -3118,10 +3118,10 @@ label: gpt
table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
attrs=[]))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id}'''
+1: start=2048 size=18432 type={}'''.format(type_id)
self.assertEqual(expected, table.render())
def test_gpt_attrs_required(self):
@@ -3129,10 +3129,10 @@ label: gpt
table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
attrs=['RequiredPartition']))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id} attrs="RequiredPartition"'''
+1: start=2048 size=18432 type={} attrs="RequiredPartition"'''.format(type_id)
self.assertEqual(expected, table.render())
def test_gpt_attrs_bit(self):
@@ -3140,10 +3140,10 @@ label: gpt
table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
attrs=['GUID:51']))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
- expected = f'''\
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id} attrs="GUID:51"'''
+1: start=2048 size=18432 type={} attrs="GUID:51"'''.format(type_id)
self.assertEqual(expected, table.render())
def test_gpt_attrs_multi(self):
@@ -3151,10 +3151,11 @@ label: gpt
table.add(dict(number=1, offset=1 << 20, size=9 << 20, flag='boot',
attrs=['RequiredPartition', 'GUID:51']))
type_id = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
- expected = f'''\
+ attrs = 'RequiredPartition GUID:51'
+ expected = '''\
label: gpt
-1: start=2048 size=18432 type={type_id} attrs="RequiredPartition GUID:51"'''
+1: start=2048 size=18432 type={} attrs="{}"'''.format(type_id, attrs)
self.assertEqual(expected, table.render())
def test_dos_basic(self):
@@ -3178,10 +3179,10 @@ label: dos
table = block_meta_v2.DOSPartTable(512)
table.add(dict(number=1, offset=1 << 20, size=9 << 20,
flag='boot', partition_type=ptype))
- expected = f'''\
+ expected = '''\
label: dos
-1: start=2048 size=18432 type={ptype} bootable'''
+1: start=2048 size=18432 type={} bootable'''.format(ptype)
self.assertEqual(expected, table.render())
def test_preserve_labelid_gpt(self):
@@ -3277,21 +3278,21 @@ label: dos
number=1, start=2, size=3, type='04', bootable=True,
uuid=uuid, name='name',
attrs=['stuff', 'things'])
- expected = f'1: start=2 size=3 type=04 uuid={uuid} ' + \
+ expected = '1: start=2 size=3 type=04 uuid={} '.format(uuid) + \
'name="name" attrs="stuff things" bootable'
self.assertEqual(expected, pte.render())
def test_gpt_entry_preserve(self):
uuid = str(random_uuid())
name = self.random_string()
- attrs = f'{self.random_string()} {self.random_string()}'
+ attrs = '{} {}'.format(self.random_string(), self.random_string())
pte = block_meta_v2.PartTableEntry(
number=1, start=2, size=3, type='04', bootable=False,
uuid=None, name=None, attrs=None)
pte.preserve({'uuid': uuid, 'name': name, 'attrs': attrs})
to_hex = block_meta_v2.to_utf8_hex_notation
- expected = f'1: start=2 size=3 type=04 uuid={uuid} ' + \
- f'name="{to_hex(name)}" attrs="{attrs}"'
+ expected = '1: start=2 size=3 type=04 uuid={} '.format(uuid) + \
+ 'name="{}" attrs="{}"'.format(to_hex(name), attrs)
self.assertEqual(expected, pte.render())
def test_v2_dos_is_logical(self):
diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
index 9e4fa87..a96f5fd 100644
--- a/tests/unittests/test_curthooks.py
+++ b/tests/unittests/test_curthooks.py
@@ -2286,19 +2286,21 @@ class TestDoAptConfig(CiTestCase):
def test_apt_config_dict(self):
with patch(self.handle_apt_sym) as m_handle_apt:
curthooks.do_apt_config({"apt": {}}, target="/")
- m_handle_apt.assert_called()
+ m_handle_apt.assert_any_call({}, '/')
def test_with_apt_config(self):
with patch(self.handle_apt_sym) as m_handle_apt:
curthooks.do_apt_config(
{"apt": {"proxy": {"http_proxy": "http://proxy:3128"}}},
target="/")
- m_handle_apt.assert_called_once()
+ m_handle_apt.assert_any_call(
+ {'proxy': {'http_proxy': 'http://proxy:3128'}}, '/')
def test_with_debconf_selections(self):
# debconf_selections are translated to apt config
with patch(self.handle_apt_sym) as m_handle_apt:
curthooks.do_apt_config({"debconf_selections": "foo"}, target="/")
- m_handle_apt.assert_called_once()
+ m_handle_apt.assert_any_call({'debconf_selections': 'foo'}, '/')
+
# vi: ts=4 expandtab syntax=python
diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
index 5743475..a10ddbf 100644
--- a/tests/unittests/test_distro.py
+++ b/tests/unittests/test_distro.py
@@ -311,7 +311,7 @@ class TestAptInstall(CiTestCase):
]
distro.run_apt_command('install', ['foobar', 'wark'])
- m_apt_update.assert_called_once()
+ self.assertEqual(1, m_apt_update.call_count)
m_apt_install.assert_has_calls(expected_calls)
m_subp.assert_called_once_with(['apt-get', 'clean'], target='/')
@@ -321,7 +321,7 @@ class TestAptInstall(CiTestCase):
# no clean option
distro.run_apt_command('install', ['foobar', 'wark'], clean=False)
- m_apt_update.assert_called_once()
+ self.assertEqual(1, m_apt_update.call_count)
m_subp.assert_has_calls(expected_calls[:-1])
@mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py
index a538ece..516d56a 100644
--- a/tests/unittests/test_storage_config.py
+++ b/tests/unittests/test_storage_config.py
@@ -1170,7 +1170,10 @@ class TestSelectConfigs(CiTestCase):
id1 = {'a': 1, 'c': 3}
sc = {'id0': id0, 'id1': id1}
- self.assertEqual([id0, id1], select_configs(sc, a=1))
+ actual = select_configs(sc, a=1)
+ self.assertEqual(2, len(actual))
+ self.assertIn(id0, actual)
+ self.assertIn(id1, actual)
def test_not_found(self):
id0 = {'a': 1, 'b': 2}
Follow ups