Michael Hudson-Doyle has proposed merging ~mwhudson/curtin:modernize-uefi-code 
into curtin:master.

Commit message:
modernize efi boot entry handling code

"modernize" means: type annotations and passing attr.s-based types
rather than dictionaries around.

I also made some changes to reduce indentation and removed a few
defaults for arguments that were always passed and a few other things I
couldn't restrain myself from.



Requested reviews:
  curtin developers (curtin-dev)

For more details, see:
https://code.launchpad.net/~mwhudson/curtin/+git/curtin/+merge/444527
-- 
Your team curtin developers is requested to review the proposed merge of 
~mwhudson/curtin:modernize-uefi-code into curtin:master.
diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
index 74a67e8..ee71f41 100644
--- a/curtin/commands/curthooks.py
+++ b/curtin/commands/curthooks.py
@@ -8,6 +8,7 @@ import re
 import sys
 import shutil
 import textwrap
+from typing import List, Tuple
 
 from curtin import config
 from curtin import block
@@ -424,32 +425,37 @@ def install_kernel(cfg, target):
 
 def uefi_remove_old_loaders(grubcfg, target):
     """Removes the old UEFI loaders from efibootmgr."""
-    efi_output = util.get_efibootmgr(target)
-    LOG.debug('UEFI remove old olders efi output:\n%s', efi_output)
-    current_uefi_boot = efi_output.get('current', None)
+    efi_state = util.get_efibootmgr(target)
+
+    LOG.debug('UEFI remove old olders efi state:\n%s', efi_state)
+
     old_efi_entries = {
-        entry: info
-        for entry, info in efi_output['entries'].items()
-        if re.match(r'^.*File\(\\EFI.*$', info['path'])
+        number: entry
+        for number, entry in efi_state.entries.items()
+        if 'File(\\EFI' in entry.path
     }
-    old_efi_entries.pop(current_uefi_boot, None)
-    remove_old_loaders = grubcfg.get('remove_old_uefi_loaders', True)
-    if old_efi_entries:
-        if remove_old_loaders:
-            with util.ChrootableTarget(target) as in_chroot:
-                for entry, info in old_efi_entries.items():
-                    LOG.debug("removing old UEFI entry: %s" % info['name'])
-                    in_chroot.subp(
-                        ['efibootmgr', '-B', '-b', entry], capture=True)
-        else:
+
+    if efi_state.current in old_efi_entries:
+        del old_efi_entries[efi_state.current]
+
+    if not old_efi_entries:
+        return
+
+    if grubcfg.get('remove_old_uefi_loaders', True):
+        with util.ChrootableTarget(target) as in_chroot:
+            for number, entry in old_efi_entries.items():
+                LOG.debug("removing old UEFI entry: %s", entry.name)
+                in_chroot.subp(
+                    ['efibootmgr', '-B', '-b', number], capture=True)
+    else:
+        LOG.debug(
+            "Skipped removing %d old UEFI entrie%s.",
+            len(old_efi_entries),
+            '' if len(old_efi_entries) == 1 else 's')
+        for entry in old_efi_entries.values():
             LOG.debug(
-                "Skipped removing %d old UEFI entrie%s.",
-                len(old_efi_entries),
-                '' if len(old_efi_entries) == 1 else 's')
-            for info in old_efi_entries.values():
-                LOG.debug(
-                    "UEFI entry '%s' might no longer exist and "
-                    "should be removed.", info['name'])
+                "UEFI entry '%s' might no longer exist and "
+                "should be removed.", entry.name)
 
 
 def uefi_boot_entry_is_network(boot_entry_name):
@@ -460,7 +466,11 @@ def uefi_boot_entry_is_network(boot_entry_name):
                     boot_entry_name, re.IGNORECASE) is not None
 
 
-def _reorder_new_entry(boot_order, efi_output, efi_orig=None, variant=None):
+def _reorder_new_entry(
+        efi_state: util.EFIBootState,
+        efi_orig: util.EFIBootState,
+        variant: str,
+        ) -> List[str]:
     """
     Reorder the EFI boot menu as follows
 
@@ -470,32 +480,22 @@ def _reorder_new_entry(boot_order, efi_output, efi_orig=None, variant=None):
 
     returns a list of bootnum strings
     """
-
-    if not boot_order:
-        raise RuntimeError('boot_order is not a list')
-
-    if efi_orig is None:
-        raise RuntimeError('Missing efi_orig boot dictionary')
-
-    if variant is None:
-        variant = ""
-
     net_boot = []
     other = []
     target = []
 
-    LOG.debug("UEFI previous boot order: %s", efi_orig['order'])
-    LOG.debug("UEFI current  boot order: %s", boot_order)
-    new_entries = list(set(boot_order).difference(set(efi_orig['order'])))
+    LOG.debug("UEFI previous boot order: %s", efi_orig.order)
+    LOG.debug("UEFI current  boot order: %s", efi_state.order)
+    new_entries = list(set(efi_state.order).difference(set(efi_orig.order)))
     if new_entries:
         LOG.debug("UEFI Found new boot entries: %s", new_entries)
     LOG.debug('UEFI Looking for installed entry variant=%s', variant.lower())
-    for bootnum in boot_order:
-        entry = efi_output['entries'][bootnum]
-        if uefi_boot_entry_is_network(entry['name']):
+    for bootnum in efi_state.order:
+        entry = efi_state.entries[bootnum]
+        if uefi_boot_entry_is_network(entry.name):
             net_boot.append(bootnum)
         else:
-            if entry['name'].lower() == variant.lower():
+            if entry.name.lower() == variant.lower():
                 target.append(bootnum)
             else:
                 other.append(bootnum)
@@ -505,17 +505,22 @@ def _reorder_new_entry(boot_order, efi_output, efi_orig=None, variant=None):
     if other:
         LOG.debug("UEFI found other entries: %s", other)
     if target:
-        LOG.debug("UEFI found target entry: %s", target)
+        LOG.debug("UEFI found target entries: %s", target)
     else:
         LOG.debug("UEFI Did not find an entry with variant=%s",
                   variant.lower())
     new_order = net_boot + target + other
-    if boot_order == new_order:
+    if efi_state.order == new_order:
         LOG.debug("UEFI Current and Previous bootorders match")
     return new_order
 
 
-def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None):
+def uefi_reorder_loaders(
+        grubcfg: dict,
+        target: str,
+        efi_orig_state: util.EFIBootState,
+        variant: str,
+        ) -> None:
     """Reorders the UEFI BootOrder to place BootCurrent first.
 
     The specifically doesn't try to do to much. The order in which grub places
@@ -527,85 +532,88 @@ def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None):
     is installed after the the previous first entry (before we installed grub).
 
     """
-    if grubcfg.get('reorder_uefi', True):
-        efi_output = util.get_efibootmgr(target=target)
-        LOG.debug('UEFI efibootmgr output after install:\n%s', efi_output)
-        currently_booted = efi_output.get('current', None)
-        boot_order = efi_output.get('order', [])
-        new_boot_order = None
-        force_fallback_reorder = config.value_as_boolean(
-            grubcfg.get('reorder_uefi_force_fallback', False))
-        if currently_booted and force_fallback_reorder is False:
-            if currently_booted in boot_order:
-                boot_order.remove(currently_booted)
-            boot_order = [currently_booted] + boot_order
-            new_boot_order = ','.join(boot_order)
-            LOG.debug(
-                "Setting currently booted %s as the first "
-                "UEFI loader.", currently_booted)
-        else:
-            reason = (
-                "config 'reorder_uefi_force_fallback' is True" if
-                force_fallback_reorder else "missing 'BootCurrent' value")
-            LOG.debug("Using fallback UEFI reordering: " + reason)
-            if len(boot_order) < 2:
-                LOG.debug(
-                    'UEFI BootOrder has less than 2 entries, cannot reorder')
-                return
-            # look at efi entries before we added one to find the new addition
-            new_order = _reorder_new_entry(
-                    copy.deepcopy(boot_order), efi_output, efi_orig, variant)
-            if new_order != boot_order:
-                new_boot_order = ','.join(new_order)
-            else:
-                LOG.debug("UEFI No changes to boot order.")
-        if new_boot_order:
-            LOG.debug(
-                "New UEFI boot order: %s", new_boot_order)
-            with util.ChrootableTarget(target) as in_chroot:
-                in_chroot.subp(['efibootmgr', '-o', new_boot_order])
-    else:
+    if not grubcfg.get('reorder_uefi', True):
         LOG.debug("Skipped reordering of UEFI boot methods.")
         LOG.debug("Currently booted UEFI loader might no longer boot.")
+        return
+
+    efi_state = util.get_efibootmgr(target=target)
+    LOG.debug('UEFI efibootmgr output after install:\n%s', efi_state)
+    new_boot_order = None
+
+    force_fallback_reorder = config.value_as_boolean(
+        grubcfg.get('reorder_uefi_force_fallback', False))
+
+    if efi_state.current and not force_fallback_reorder:
+        boot_order = list(efi_state.order)
+        if efi_state.current in boot_order:
+            boot_order.remove(efi_state.current)
+        new_boot_order = ','.join([efi_state.current] + boot_order)
+        LOG.debug(
+            "Setting currently booted %s as the first UEFI loader.",
+            efi_state.current)
+    else:
+        if force_fallback_reorder:
+            reason = "config 'reorder_uefi_force_fallback' is True"
+        else:
+            reason = "missing 'BootCurrent' value"
+        LOG.debug("Using fallback UEFI reordering: " + reason)
+
+        if len(efi_state.order) < 2:
+            LOG.debug(
+                'UEFI BootOrder has less than 2 entries, cannot reorder')
+            return
+
+        # look at efi entries before we added one to find the new addition
+        new_order = _reorder_new_entry(efi_state, efi_orig_state, variant)
+
+        if new_order != efi_state.order:
+            new_boot_order = ','.join(new_order)
+        else:
+            LOG.debug("UEFI No changes to boot order.")
+
+    if new_boot_order:
+        LOG.debug("New UEFI boot order: %s", new_boot_order)
+        with util.ChrootableTarget(target) as in_chroot:
+            in_chroot.subp(['efibootmgr', '-o', new_boot_order])
 
 
-def uefi_remove_duplicate_entries(grubcfg, target, to_remove=None):
+def uefi_remove_duplicate_entries(grubcfg: dict, target: str) -> None:
     if not grubcfg.get('remove_duplicate_entries', True):
         LOG.debug("Skipped removing duplicate UEFI boot entries per config.")
         return
-    if to_remove is None:
-        to_remove = uefi_find_duplicate_entries(grubcfg, target)
+
+    to_remove = uefi_find_duplicate_entries(grubcfg, target)
 
     # check so we don't run ChrootableTarget code unless we have things to do
-    if to_remove:
-        with util.ChrootableTarget(target) as in_chroot:
-            for bootnum, entry in to_remove:
-                LOG.debug('Removing duplicate EFI entry (%s, %s)',
-                          bootnum, entry)
-                in_chroot.subp(['efibootmgr', '--bootnum=%s' % bootnum,
-                                '--delete-bootnum'])
+    if not to_remove:
+        return
+
+    with util.ChrootableTarget(target) as in_chroot:
+        for bootnum, entry in to_remove:
+            LOG.debug('Removing duplicate EFI entry (%s, %s)', bootnum, entry)
+            in_chroot.subp(
+                ['efibootmgr', '--bootnum=%s' % bootnum, '--delete-bootnum'])
 
 
-def uefi_find_duplicate_entries(grubcfg, target, efi_output=None):
+def uefi_find_duplicate_entries(grubcfg: dict, target: str) \
+      -> List[Tuple[str, util.EFIBootEntry]]:
     seen = set()
     to_remove = []
-    if efi_output is None:
-        efi_output = util.get_efibootmgr(target=target)
-    entries = efi_output.get('entries', {})
-    current_bootnum = efi_output.get('current', None)
+    efi_state = util.get_efibootmgr(target=target)
     # adding BootCurrent to seen first allows us to remove any other duplicate
     # entry of BootCurrent.
-    if current_bootnum:
-        seen.add(tuple(entries[current_bootnum].items()))
-    for bootnum in sorted(entries):
-        if bootnum == current_bootnum:
+    if efi_state.current in efi_state.entries:
+        current = efi_state.entries[efi_state.current]
+        seen.add((current.name, current.path))
+    for bootnum, entry in sorted(efi_state.entries.items()):
+        if bootnum == efi_state.current:
             continue
-        entry = entries[bootnum]
-        t = tuple(entry.items())
-        if t not in seen:
-            seen.add(t)
-        else:
+        t = (entry.name, entry.path)
+        if t in seen:
             to_remove.append((bootnum, entry))
+        seen.add(t)
+
     return to_remove
 
 
@@ -715,7 +723,7 @@ def uefi_find_grub_device_ids(sconfig):
     return grub_device_ids
 
 
-def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None):
+def setup_grub(cfg, target, osfamily, variant):
     # target is the path to the mounted filesystem
 
     # FIXME: these methods need moving to curtin.block
@@ -815,13 +823,13 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None):
 
     update_nvram = grubcfg.get('update_nvram', True)
     if uefi_bootable and update_nvram:
-        efi_orig_output = util.get_efibootmgr(target)
+        efi_orig_state = util.get_efibootmgr(target)
         uefi_remove_old_loaders(grubcfg, target)
 
     install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg)
 
     if uefi_bootable and update_nvram:
-        uefi_reorder_loaders(grubcfg, target, efi_orig_output, variant)
+        uefi_reorder_loaders(grubcfg, target, efi_orig_state, variant)
         uefi_remove_duplicate_entries(grubcfg, target)
 
 
diff --git a/curtin/util.py b/curtin/util.py
index 8a9e8f4..08c1fd5 100644
--- a/curtin/util.py
+++ b/curtin/util.py
@@ -16,6 +16,9 @@ import stat
 import sys
 import tempfile
 import time
+from typing import Dict, List, Optional
+
+import attr
 
 # avoid the dependency to python3-six as used in cloud-init
 try:
@@ -907,63 +910,62 @@ def is_uefi_bootable():
     return os.path.exists('/sys/firmware/efi') is True
 
 
-def parse_efibootmgr(content):
-    efikey_to_dict_key = {
+@attr.s(auto_attribs=True)
+class EFIBootEntry:
+    name: str
+    path: str
+
+
+@attr.s(auto_attribs=True)
+class EFIBootState:
+    current: str
+    timeout: str
+    order: List[str]
+    entries: Dict[str, EFIBootEntry] = attr.Factory(dict)
+
+
+EFI_BOOT_ENTRY_LINE_REGEXP = \
+  r"^Boot(?P<entry>[0-9a-fA-F]{4})\*?\s(?P<name>.+)\t(?P<path>.*)$"
+
+
+def parse_efibootmgr(content: str) -> EFIBootState:
+    efikey_to_attr = {
         'BootCurrent': 'current',
         'Timeout': 'timeout',
         'BootOrder': 'order',
     }
 
-    output = {}
+    args = {'current': '', 'timeout': '', 'order': ''}
+
     for line in content.splitlines():
         split = line.split(':')
-        if len(split) == 2:
-            key = split[0].strip()
-            output_key = efikey_to_dict_key.get(key, None)
-            if output_key:
-                output[output_key] = split[1].strip()
-                if output_key == 'order':
-                    output[output_key] = output[output_key].split(',')
-    output['entries'] = {
-        entry: {
-            'name': name.strip(),
-            'path': path.strip(),
-        }
-        for entry, name, path in re.findall(
-            r"^Boot(?P<entry>[0-9a-fA-F]{4})\*?\s(?P<name>.+)\t"
-            r"(?P<path>.*)$",
-            content, re.MULTILINE)
-    }
-    if 'order' in output:
-        new_order = [item for item in output['order']
-                     if item in output['entries']]
-        output['order'] = new_order
-    return output
-
-
-def get_efibootmgr(target=None):
-    """Return mapping of EFI information.
-
-    Calls `efibootmgr` inside the `target`.
-
-    Example output:
-        {
-            'current': '0000',
-            'timeout': '1 seconds',
-            'order': ['0000', '0001'],
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': (
-                        'HD(1,GPT,0,0x8,0x1)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-                },
-                '0001': {
-                    'name': 'UEFI:Network Device',
-                    'path': 'BBS(131,,0x0)',
-                }
-            }
-        }
-    """
+        if len(split) != 2:
+            continue
+        key, val = split
+        attr = efikey_to_attr.get(key.strip())
+        if not attr:
+            continue
+        args[attr] = val.strip()
+
+    print(args)
+    args['order'] = args['order'].split(',')
+
+    state = EFIBootState(**args)
+
+    for line in content.splitlines():
+        match = re.match(EFI_BOOT_ENTRY_LINE_REGEXP, line)
+        if match is None:
+            continue
+        state.entries[match['entry']] = EFIBootEntry(
+            name=match['name'].strip(), path=match['path'].strip())
+
+    state.order = [item for item in state.order if item in state.entries]
+
+    return state
+
+
+def get_efibootmgr(target: Optional[str] = None) -> EFIBootState:
+    """Return information about current EFI boot state."""
     with ChrootableTarget(target=target) as in_chroot:
         stdout, _ = in_chroot.subp(['efibootmgr', '-v'], capture=True)
         output = parse_efibootmgr(stdout)
diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
index 9e4fa87..86e588e 100644
--- a/tests/unittests/test_curthooks.py
+++ b/tests/unittests/test_curthooks.py
@@ -1,9 +1,11 @@
 # This file is part of curtin. See LICENSE file for copyright and license info.
 
-import copy
 import os
 from mock import call, patch
 import textwrap
+from typing import Optional
+
+import attr
 
 from curtin.commands import curthooks
 from curtin.commands.block_meta import extract_storage_ordered_dict
@@ -576,44 +578,37 @@ class TestSetupZipl(CiTestCase):
         self.assertIn('root={}'.format(root_dev), content)
 
 
-class EfiOutput(object):
-
-    def __init__(self, current=None, order=None, entries=None):
-        self.entries = {}
-        if entries:
-            for entry in entries:
-                self.entries.update(entry)
-        self.current = current
-        self.order = order
-        if not order and self.entries:
-            self.order = sorted(self.entries.keys())
-
-    def add_entry(self, bootnum=None, name=None, path=None, current=False):
-        if not bootnum:
-            bootnum = "%04x" % random.randint(0, 1000)
-        if not name:
-            name = CiTestCase.random_string()
-        if not path:
-            path = ''
-        if bootnum not in self.entries:
-            self.entries[bootnum] = {'name': name, 'path': path}
-            if not self.order:
-                self.order = []
-            self.order.append(bootnum)
-        if current:
-            self.current = bootnum
-
-    def set_order(self, new_order):
-        self.order = new_order
-
-    def as_dict(self):
-        output = {}
-        if self.current:
-            output['current'] = self.current
-        if self.order:
-            output['order'] = self.order
-        output['entries'] = self.entries
-        return output
+def make_efi_state() -> util.EFIBootState:
+    return util.EFIBootState(current='', timeout='', order=[])
+
+
+def copy_efi_state(orig: util.EFIBootState) -> util.EFIBootState:
+    kw = attr.asdict(orig)
+    kw['entries'] = {
+        bootnum: util.EFIBootEntry(**d)
+        for bootnum, d in kw['entries'].items()
+        }
+    return util.EFIBootState(**kw)
+
+
+def add_efi_entry(
+        state: util.EFIBootState,
+        bootnum: Optional[str] = None,
+        name: Optional[str] = None,
+        path: Optional[str] = None,
+        current: bool = False) -> None:
+    if not bootnum:
+        bootnum = "%04x" % random.randint(0, 1000)
+    if not name:
+        name = CiTestCase.random_string()
+    if not path:
+        path = ''
+    if bootnum not in state.entries:
+        state.entries[bootnum] = util.EFIBootEntry(
+            name=name, path=path)
+        state.order.append(bootnum)
+    if current:
+        state.current = bootnum
 
 
 class TestSetupGrub(CiTestCase):
@@ -655,7 +650,9 @@ class TestSetupGrub(CiTestCase):
                 'install_devices': ['/dev/vdb'],
             },
         }
-        curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
+        curthooks.setup_grub(
+            cfg, self.target,
+            osfamily=self.distro_family, variant=self.variant)
         self.m_install_grub.assert_called_with(
             ['/dev/vdb'], self.target, uefi=False, grubcfg=cfg.get('grub'))
 
@@ -763,16 +760,15 @@ class TestSetupGrub(CiTestCase):
             },
         }
         self.mock_haspkg.return_value = False
-        self.mock_efibootmgr.return_value = {
-            'current': '0000',
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-                }
-            }
-        }
+        self.mock_efibootmgr.return_value = util.EFIBootState(
+            current='0000',
+            timeout='',
+            order=[],
+            entries={
+                '0000': util.EFIBootEntry(
+                    name='ubuntu',
+                    path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
+                })
         curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
                              variant=self.variant)
         self.m_install_grub.assert_called_with(
@@ -793,26 +789,21 @@ class TestSetupGrub(CiTestCase):
                 'reorder_uefi': False,
             },
         }
-        self.mock_efibootmgr.return_value = {
-            'current': '0000',
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-                },
-                '0001': {
-                    'name': 'centos',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\centos\\shimx64.efi)'),
-                },
-                '0002': {
-                    'name': 'sles',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\sles\\shimx64.efi)'),
-                },
-            }
-        }
+        self.mock_efibootmgr.return_value = util.EFIBootState(
+            current='0000',
+            timeout='',
+            order=[],
+            entries={
+                '0000': util.EFIBootEntry(
+                    name='ubuntu',
+                    path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
+                '0001': util.EFIBootEntry(
+                    name='centos',
+                    path='HD(1,GPT)/File(\\EFI\\centos\\shimx64.efi)'),
+                '0002': util.EFIBootEntry(
+                    name='sles',
+                    path='HD(1,GPT)/File(\\EFI\\sles\\shimx64.efi)'),
+                })
         self.mock_haspkg.return_value = False
         curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
                              variant=self.variant)
@@ -840,21 +831,21 @@ class TestSetupGrub(CiTestCase):
                 'reorder_uefi': True,
             },
         }
-        self.mock_efibootmgr.return_value = {
-            'current': '0001',
-            'order': ['0000', '0001'],
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-                },
-                '0001': {
-                    'name': 'UEFI:Network Device',
-                    'path': 'BBS(131,,0x0)',
-                },
-            }
-        }
+        self.mock_efibootmgr.return_value = util.EFIBootState(
+            current='0001',
+            timeout='',
+            order=['0000', '0001'],
+            entries={
+                '0000': util.EFIBootEntry(
+                    name='ubuntu',
+                    path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
+                '0001': util.EFIBootEntry(
+                    name='UEFI:Network Device',
+                    path='BBS(131,,0x0)'),
+                '0002': util.EFIBootEntry(
+                    name='sles',
+                    path='HD(1,GPT)/File(\\EFI\\sles\\shimx64.efi)'),
+                })
         self.mock_haspkg.return_value = False
         curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
                              variant=self.variant)
@@ -878,29 +869,29 @@ class TestSetupGrub(CiTestCase):
         }
 
         # Single existing entry 0001
-        efi_orig = EfiOutput()
-        efi_orig.add_entry(bootnum='0001', name='centos')
+        orig_state = make_efi_state()
+        add_efi_entry(orig_state, bootnum='0001', name='centos')
 
         # After install add a second entry, 0000 to the front of order
-        efi_post = copy.deepcopy(efi_orig)
-        efi_post.add_entry(bootnum='0000', name='ubuntu')
-        efi_post.set_order(['0000', '0001'])
+        post_state = copy_efi_state(orig_state)
+        add_efi_entry(post_state, bootnum='0000', name='ubuntu')
+        post_state.order = ['0000', '0001']
 
-        # After reorder we should have the target install first
-        efi_final = copy.deepcopy(efi_post)
+        final_state = copy_efi_state(post_state)
 
         self.mock_efibootmgr.side_effect = iter([
-            efi_orig.as_dict(),   # collect original order before install
-            efi_orig.as_dict(),   # remove_old_loaders query (no change)
-            efi_post.as_dict(),   # efi table after grub install, (changed)
-            efi_final.as_dict(),  # remove duplicates checks and finds reorder
-                                  # has changed
+            orig_state,   # collect original order before install
+            orig_state,   # remove_old_loaders query (no change)
+            post_state,   # efi table after grub install, (changed)
+            final_state,  # remove duplicates checks and finds reorder has
+                          # changed
         ])
         self.mock_haspkg.return_value = False
         curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
                              variant=self.variant)
         logs = self.logs.getvalue()
         print(logs)
+        print(self.mock_subp.call_args_list)
         self.assertEquals([], self.mock_subp.call_args_list)
         self.assertIn("Using fallback UEFI reordering:", logs)
         self.assertIn("missing 'BootCurrent' value", logs)
@@ -924,25 +915,26 @@ class TestSetupGrub(CiTestCase):
         }
 
         # Existing Custom Ubuntu, usb and cd/dvd entry, booting Ubuntu
-        efi_orig = EfiOutput()
-        efi_orig.add_entry(bootnum='0001', name='Ubuntu Deluxe Edition')
-        efi_orig.add_entry(bootnum='0002', name='USB Device')
-        efi_orig.add_entry(bootnum='0000', name='CD/DVD')
-        efi_orig.set_order(['0001', '0002', '0000'])
+        orig_state = make_efi_state()
+        add_efi_entry(orig_state, bootnum='0001', name='Ubuntu Deluxe Edition')
+        add_efi_entry(orig_state, bootnum='0002', name='USB Device')
+        add_efi_entry(orig_state, bootnum='0000', name='CD/DVD')
+        orig_state.order = ['0001', '0002', '0000']
 
         # after install existing ubuntu entry is reused, no change in order
-        efi_post = efi_orig
+        post_state = copy_efi_state(orig_state)
 
         # after reorder, no change is made due to the installed distro variant
         # string 'ubuntu' is not found in the boot entries so we retain the
         # original efi order.
-        efi_final = efi_post
+        final_state = copy_efi_state(post_state)
 
         self.mock_efibootmgr.side_effect = iter([
-            efi_orig.as_dict(),   # collect original order before install
-            efi_orig.as_dict(),   # remove_old_loaders query
-            efi_post.as_dict(),   # reorder entries queries post install
-            efi_final.as_dict(),  # remove duplicates checks and finds reorder
+            orig_state,   # collect original order before install
+            post_state,   # remove_old_loaders query (no change)
+            post_state,   # efi table after grub install, (changed)
+            final_state,  # remove duplicates checks and finds reorder has
+                          # changed
         ])
 
         self.mock_haspkg.return_value = False
@@ -976,26 +968,30 @@ class TestSetupGrub(CiTestCase):
         }
         # Single existing entry 0001 and set as current, which should avoid
         # any fallback logic, but we're forcing fallback pack via config
-        efi_orig = EfiOutput()
-        efi_orig.add_entry(bootnum='0001', name='PXE', current=True)
-        print(efi_orig.as_dict())
+        orig_state = make_efi_state()
+        add_efi_entry(orig_state, bootnum='0001', name='PXE', current=True)
 
         # After install add a second entry, 0000 to the front of order
-        efi_post = copy.deepcopy(efi_orig)
-        efi_post.add_entry(bootnum='0000', name='ubuntu')
-        efi_post.set_order(['0000', '0001'])
-        print(efi_orig.as_dict())
+        post_state = copy_efi_state(orig_state)
+        add_efi_entry(post_state, bootnum='0000', name='ubuntu')
+
+        final_state = copy_efi_state(post_state)
+
+        # After install add a second entry, 0000 to the front of order
+        post_state = copy_efi_state(orig_state)
+        add_efi_entry(post_state, bootnum='0000', name='ubuntu')
+        post_state.order = ['0000', '0001']
 
         # After reorder we should have the original boot entry 0001 as first
-        efi_final = copy.deepcopy(efi_post)
-        efi_final.set_order(['0001', '0000'])
+        final_state = copy_efi_state(post_state)
+        final_state.order = ['0001', '0000']
 
         self.mock_efibootmgr.side_effect = iter([
-            efi_orig.as_dict(),   # collect original order before install
-            efi_orig.as_dict(),   # remove_old_loaders query (no change)
-            efi_post.as_dict(),   # efi table after grub install, (changed)
-            efi_final.as_dict(),  # remove duplicates checks and finds reorder
-                                  # has changed
+            orig_state,   # collect original order before install
+            post_state,   # remove_old_loaders query (no change)
+            post_state,   # efi table after grub install, (changed)
+            final_state,  # remove duplicates checks and finds reorder has
+                          # changed
         ])
 
         self.mock_haspkg.return_value = False
@@ -1003,7 +999,8 @@ class TestSetupGrub(CiTestCase):
                              variant=self.variant)
         logs = self.logs.getvalue()
         print(logs)
-        self.assertEquals([
+        print(self.mock_subp.call_args_list)
+        self.assertEqual([
             call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
             self.mock_subp.call_args_list)
         self.assertIn("Using fallback UEFI reordering:", logs)
@@ -1025,31 +1022,29 @@ class TestSetupGrub(CiTestCase):
         }
 
         # Existing ubuntu, usb and cd/dvd entry, booting ubuntu
-        efi_orig = EfiOutput()
-        efi_orig.add_entry(bootnum='0001', name='centos')
-        efi_orig.add_entry(bootnum='0002', name='Network')
-        efi_orig.add_entry(bootnum='0003', name='PXE')
-        efi_orig.add_entry(bootnum='0004', name='LAN')
-        efi_orig.add_entry(bootnum='0000', name='CD/DVD')
-        efi_orig.set_order(['0001', '0002', '0003', '0004', '0000'])
-        print(efi_orig.as_dict())
+        orig_state = make_efi_state()
+        add_efi_entry(orig_state, bootnum='0001', name='centos')
+        add_efi_entry(orig_state, bootnum='0002', name='Network')
+        add_efi_entry(orig_state, bootnum='0003', name='PXE')
+        add_efi_entry(orig_state, bootnum='0004', name='LAN')
+        add_efi_entry(orig_state, bootnum='0000', name='CD/DVD')
+        orig_state.order = ['0001', '0002', '0003', '0004', '0000']
 
         # after install we add an ubuntu entry, and grub puts it first
-        efi_post = copy.deepcopy(efi_orig)
-        efi_post.add_entry(bootnum='0007', name='ubuntu')
-        efi_post.set_order(['0007'] + efi_orig.order)
-        print(efi_post.as_dict())
+        post_state = copy_efi_state(orig_state)
+        add_efi_entry(post_state, bootnum='0007', name='ubuntu')
+        post_state.order = ['0007'] + orig_state.order
 
         # reorder must place all network devices first, then ubuntu, and others
-        efi_final = copy.deepcopy(efi_post)
-        expected_order = ['0002', '0003', '0004', '0007', '0001', '0000']
-        efi_final.set_order(expected_order)
+        final_state = copy_efi_state(post_state)
+        final_state.order = ['0002', '0003', '0004', '0007', '0001', '0000']
 
         self.mock_efibootmgr.side_effect = iter([
-            efi_orig.as_dict(),   # collect original order before install
-            efi_orig.as_dict(),   # remove_old_loaders query
-            efi_post.as_dict(),   # reorder entries queries post install
-            efi_final.as_dict(),  # remove duplicates checks and finds reorder
+            orig_state,   # collect original order before install
+            post_state,   # remove_old_loaders query (no change)
+            post_state,   # efi table after grub install, (changed)
+            final_state,  # remove duplicates checks and finds reorder has
+                          # changed
         ])
         self.mock_haspkg.return_value = False
         curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
@@ -1058,7 +1053,7 @@ class TestSetupGrub(CiTestCase):
         print(logs)
         print('Number of bootmgr calls: %s' % self.mock_efibootmgr.call_count)
         self.assertEquals([
-            call(['efibootmgr', '-o', '%s' % (",".join(expected_order))],
+            call(['efibootmgr', '-o', '%s' % (",".join(final_state.order))],
                  target=self.target)],
             self.mock_subp.call_args_list)
         self.assertIn("Using fallback UEFI reordering:", logs)
@@ -1066,43 +1061,43 @@ class TestSetupGrub(CiTestCase):
         self.assertIn("Looking for installed entry variant=", logs)
         self.assertIn("found netboot entries: ['0002', '0003', '0004']", logs)
         self.assertIn("found other entries: ['0001', '0000']", logs)
-        self.assertIn("found target entry: ['0007']", logs)
+        self.assertIn("found target entries: ['0007']", logs)
 
 
 class TestUefiRemoveDuplicateEntries(CiTestCase):
 
-    efibootmgr_output = {
-        'current': '0000',
-        'entries': {
-            '0000': {
-                'name': 'ubuntu',
-                'path': (
-                    'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-            },
-            '0001': {  # Is duplicate of 0000
-                'name': 'ubuntu',
-                'path': (
-                    'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-            },
-            '0002': {  # Is not a duplicate because of unique path
-                'name': 'ubuntu',
-                'path': (
-                    'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-            },
-            '0003': {  # Is duplicate of 0000
-                'name': 'ubuntu',
-                'path': (
-                    'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-            },
-        }
-    }
+    efibootmgr_output = util.EFIBootState(
+        current='0000',
+        order='',
+        timeout='',
+        entries={
+            '0000': util.EFIBootEntry(
+                name='ubuntu',
+                path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+            ),
+            '0001': util.EFIBootEntry(
+                # Is duplicate of 0000
+                name='ubuntu',
+                path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+            ),
+            '0002': util.EFIBootEntry(
+                # Is not a duplicate because of unique path
+                name='ubuntu',
+                path='HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+            ),
+            '0003': util.EFIBootEntry(
+                # Is duplicate of 0000
+                name='ubuntu',
+                path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+            ),
+        })
 
     def setUp(self):
         super(TestUefiRemoveDuplicateEntries, self).setUp()
         self.target = self.tmp_dir()
         self.add_patch('curtin.util.get_efibootmgr', 'm_efibootmgr')
         self.add_patch('curtin.util.subp', 'm_subp')
-        self.m_efibootmgr.return_value = copy.deepcopy(self.efibootmgr_output)
+        self.m_efibootmgr.return_value = copy_efi_state(self.efibootmgr_output)
 
     @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
     def test_uefi_remove_duplicate_entries(self):
@@ -1118,8 +1113,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
     @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
     def test_uefi_remove_duplicate_entries_no_bootcurrent(self):
         grubcfg = {}
-        efiout = copy.deepcopy(self.efibootmgr_output)
-        del efiout['current']
+        efiout = copy_efi_state(self.efibootmgr_output)
+        efiout.current = ''
         self.m_efibootmgr.return_value = efiout
         curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
         self.assertEquals([
@@ -1140,8 +1135,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
     @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
     def test_uefi_remove_duplicate_entries_skip_bootcurrent(self):
         grubcfg = {}
-        efiout = copy.deepcopy(self.efibootmgr_output)
-        efiout['current'] = '0003'
+        efiout = copy_efi_state(self.efibootmgr_output)
+        efiout.current = '0003'
         self.m_efibootmgr.return_value = efiout
         curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
         self.assertEquals([
@@ -1154,26 +1149,24 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
     @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
     def test_uefi_remove_duplicate_entries_no_change(self):
         grubcfg = {}
-        self.m_efibootmgr.return_value = {
-            'current': '0000',
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
-                },
-                '0001': {
-                    'name': 'centos',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\centos\\shimx64.efi)'),
-                },
-                '0002': {
-                    'name': 'sles',
-                    'path': (
-                        'HD(1,GPT)/File(\\EFI\\sles\\shimx64.efi)'),
-                },
-            }
-        }
+        self.m_efibootmgr.return_value = util.EFIBootState(
+            order=[],
+            timeout='',
+            current='0000',
+            entries={
+                '0000': util.EFIBootEntry(
+                    name='ubuntu',
+                    path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+                ),
+                '0001': util.EFIBootEntry(
+                    name='centos',
+                    path='HD(1,GPT)/File(\\EFI\\centos\\shimx64.efi)',
+                ),
+                '0002': util.EFIBootEntry(
+                    name='sles',
+                    path='HD(1,GPT)/File(\\EFI\\sles\\shimx64.efi)',
+                ),
+            })
         curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
         self.assertEquals([], self.m_subp.call_args_list)
 
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index 6a0c951..f259da4 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -893,7 +893,7 @@ class TestGetEFIBootMGR(CiTestCase):
     def test_calls_efibootmgr_verbose(self):
         self.in_chroot_subp_output.append(('', ''))
         util.get_efibootmgr('target')
-        self.assertEquals(
+        self.assertEqual(
             (['efibootmgr', '-v'],),
             self.mock_in_chroot_subp.call_args_list[0][0])
 
@@ -911,37 +911,38 @@ class TestGetEFIBootMGR(CiTestCase):
             Boot0005* UEFI:Network Device	BBS(131,,0x0)
             """), ''))
         observed = util.get_efibootmgr('target')
-        self.assertEquals({
-            'current': '0000',
-            'timeout': '1 seconds',
-            'order': ['0000', '0002', '0001', '0003', '0004', '0005'],
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
-                },
-                '0001': {
-                    'name': 'CD/DVD Drive',
-                    'path': 'BBS(CDROM,,0x0)',
-                },
-                '0002': {
-                    'name': 'Hard Drive',
-                    'path': 'BBS(HD,,0x0)',
-                },
-                '0003': {
-                    'name': 'UEFI:CD/DVD Drive',
-                    'path': 'BBS(129,,0x0)',
-                },
-                '0004': {
-                    'name': 'UEFI:Removable Device',
-                    'path': 'BBS(130,,0x0)',
-                },
-                '0005': {
-                    'name': 'UEFI:Network Device',
-                    'path': 'BBS(131,,0x0)',
-                },
-            }
-        }, observed)
+        expected = util.EFIBootState(
+            current='0000',
+            timeout='1 seconds',
+            order=['0000', '0002', '0001', '0003', '0004', '0005'],
+            entries={
+                '0000': util.EFIBootEntry(
+                    name='ubuntu',
+                    path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+                    ),
+                '0001': util.EFIBootEntry(
+                    name='CD/DVD Drive',
+                    path='BBS(CDROM,,0x0)',
+                    ),
+                '0002': util.EFIBootEntry(
+                    name='Hard Drive',
+                    path='BBS(HD,,0x0)',
+                    ),
+                '0003': util.EFIBootEntry(
+                    name='UEFI:CD/DVD Drive',
+                    path='BBS(129,,0x0)',
+                    ),
+                '0004': util.EFIBootEntry(
+                    name='UEFI:Removable Device',
+                    path='BBS(130,,0x0)',
+                    ),
+                '0005': util.EFIBootEntry(
+                    name='UEFI:Network Device',
+                    path='BBS(131,,0x0)',
+                    ),
+                })
+
+        self.assertEqual(expected, observed)
 
     def test_parses_output_filter_missing(self):
         """ensure parsing ignores items in order that don't have entries"""
@@ -958,37 +959,37 @@ class TestGetEFIBootMGR(CiTestCase):
             Boot0005* UEFI:Network Device	BBS(131,,0x0)
             """), ''))
         observed = util.get_efibootmgr('target')
-        self.assertEquals({
-            'current': '0000',
-            'timeout': '1 seconds',
-            'order': ['0000', '0002', '0001', '0003', '0004', '0005'],
-            'entries': {
-                '0000': {
-                    'name': 'ubuntu',
-                    'path': 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
-                },
-                '0001': {
-                    'name': 'CD/DVD Drive',
-                    'path': 'BBS(CDROM,,0x0)',
-                },
-                '0002': {
-                    'name': 'Hard Drive',
-                    'path': 'BBS(HD,,0x0)',
-                },
-                '0003': {
-                    'name': 'UEFI:CD/DVD Drive',
-                    'path': 'BBS(129,,0x0)',
-                },
-                '0004': {
-                    'name': 'UEFI:Removable Device',
-                    'path': 'BBS(130,,0x0)',
-                },
-                '0005': {
-                    'name': 'UEFI:Network Device',
-                    'path': 'BBS(131,,0x0)',
-                },
-            }
-        }, observed)
+        expected = util.EFIBootState(
+            current='0000',
+            timeout='1 seconds',
+            order=['0000', '0002', '0001', '0003', '0004', '0005'],
+            entries={
+                '0000': util.EFIBootEntry(
+                    name='ubuntu',
+                    path='HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)',
+                    ),
+                '0001': util.EFIBootEntry(
+                    name='CD/DVD Drive',
+                    path='BBS(CDROM,,0x0)',
+                    ),
+                '0002': util.EFIBootEntry(
+                    name='Hard Drive',
+                    path='BBS(HD,,0x0)',
+                    ),
+                '0003': util.EFIBootEntry(
+                    name='UEFI:CD/DVD Drive',
+                    path='BBS(129,,0x0)',
+                    ),
+                '0004': util.EFIBootEntry(
+                    name='UEFI:Removable Device',
+                    path='BBS(130,,0x0)',
+                    ),
+                '0005': util.EFIBootEntry(
+                    name='UEFI:Network Device',
+                    path='BBS(131,,0x0)',
+                    ),
+                })
+        self.assertEqual(expected, observed)
 
 
 class TestUsesSystemd(CiTestCase):
-- 
Mailing list: https://launchpad.net/~curtin-dev
Post to     : curtin-dev@lists.launchpad.net
Unsubscribe : https://launchpad.net/~curtin-dev
More help   : https://help.launchpad.net/ListHelp

Reply via email to