[PATCH] Add new storage test cases that reuse results of earlier autopart runs.

Chris Lumens clumens at redhat.com
Thu Nov 20 15:48:23 UTC 2014


This required adding some new storage component classes to make that reuse
work, and passing components around as objects instead of class names.

Along the way I also changed disk image names in existing tests to be more
distinctive, changed output to go to stderr for better reporting, and
started passing ksdata to the blivet object.

Note that run_storage_tests.py still has an obnoxious list of all test
cases to run - it's not yet discovering them for itself.  I'll be fixing
that next.
---
 tests/storage/cases/__init__.py    | 112 ++++++++++++++++++----
 tests/storage/cases/bz1014545.py   |  34 ++++---
 tests/storage/cases/bz1067707.py   |  10 +-
 tests/storage/cases/reuse.py       | 187 +++++++++++++++++++++++++++++++++++++
 tests/storage/run_storage_tests.py |  11 ++-
 5 files changed, 318 insertions(+), 36 deletions(-)
 create mode 100644 tests/storage/cases/reuse.py

diff --git a/tests/storage/cases/__init__.py b/tests/storage/cases/__init__.py
index 87882a9..c07a1ea 100644
--- a/tests/storage/cases/__init__.py
+++ b/tests/storage/cases/__init__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 #
-# Copyright (C) 2013  Red Hat, Inc.
+# Copyright (C) 2013-2014  Red Hat, Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published
@@ -17,6 +17,8 @@
 #
 # Author: Chris Lumens <clumens at redhat.com>
 
+from __future__ import print_function
+
 import logging
 import os, sys
 import re
@@ -27,6 +29,7 @@ blivet.util.set_up_logging()
 blivet_log = logging.getLogger("blivet")
 blivet_log.info(sys.argv[0])
 
+from pyanaconda.bootloader import BootLoaderError
 from pyanaconda.installclass import DefaultInstall
 from pyanaconda.kickstart import AnacondaKSHandler, AnacondaKSParser, doKickstartStorage
 from pykickstart.errors import KickstartError
@@ -45,7 +48,6 @@ class TestCase(object):
 
        Class attributes:
 
-       components   -- A list of TestCaseComponent classes.
        desc         -- A description of what this test is supposed to be
                        testing.
        name         -- An identifying string given to this TestCase.
@@ -54,13 +56,12 @@ class TestCase(object):
                        matching platforms.  If the list is empty, it is assumed
                        to be valid for all platforms.
     """
-    components  = []
     desc        = ""
     name        = ""
     platforms   = []
 
     def __init__(self):
-        pass
+        self.components = []
 
     def run(self):
         """Iterate over all components, running each, and collecting the
@@ -70,25 +71,23 @@ class TestCase(object):
         failures = 0
 
         if self.platforms and blivet.platform.getPlatform().__class__.__name__ not in self.platforms:
-            print("Test %s skipped:  not valid for this platform" % self.name)
+            print("Test %s skipped:  not valid for this platform" % self.name, file=sys.stderr)
             return
 
-        for c in self.components:
-            obj = c()
-
+        for obj in self.components:
             try:
                 obj._run()
             except FailedTest as e:
-                print("Test %s-%s failed:\n\tExpected: %s\n\tGot:      %s" % (self.name, obj.name, e.expected, e.got))
+                print("Test %s-%s failed:\n\tExpected: %s\n\tGot:      %s" % (self.name, obj.name, e.expected, e.got), file=sys.stderr)
                 failures += 1
                 continue
 
-            print("Test %s-%s succeeded" % (self.name, obj.name))
+            print("Test %s-%s succeeded" % (self.name, obj.name), file=sys.stderr)
             successes += 1
 
-        print("Test %s summary:" % self.name)
-        print("\tSuccesses: %s" % successes)
-        print("\tFailures:  %s" % failures)
+        print("Test %s summary:" % self.name, file=sys.stderr)
+        print("\tSuccesses: %s" % successes, file=sys.stderr)
+        print("\tFailures:  %s\n" % failures, file=sys.stderr)
         return failures
 
 class TestCaseComponent(object):
@@ -132,12 +131,12 @@ class TestCaseComponent(object):
         """
         return ""
 
-    def setupDisks(self):
+    def setupDisks(self, ksdata):
         """Create all disk images given by self.disksToCreate and initialize
            the storage module.  Subclasses may override this method, but they
            should be sure to call the base method as well.
         """
-        self._blivet = blivet.Blivet()
+        self._blivet = blivet.Blivet(ksdata=ksdata)
 
         # blivet only sets up the bootloader in installer_mode.  We don't
         # want installer_mode, though, because that involves running lots
@@ -197,8 +196,6 @@ class TestCaseComponent(object):
 
         # Set up disks/blivet.
         try:
-            self.setupDisks()
-
             # Parse the kickstart using anaconda's parser, since it has more
             # advanced error detection.  This also requires having storage set
             # up first.
@@ -207,10 +204,13 @@ class TestCaseComponent(object):
 
             instClass = DefaultInstall()
 
+            self.setupDisks(parser.handler)
+
             doKickstartStorage(self._blivet, parser.handler, instClass)
             self._blivet.updateKSData()
+            self._blivet.devicetree.teardownAll()
             self._blivet.doIt()
-        except (KickstartError, StorageError) as e:
+        except (BootLoaderError, KickstartError, StorageError) as e:
             # anaconda handles expected kickstart errors (like parsing busted
             # input files) by printing the error and quitting.  For testing, an
             # error might be expected so we should compare the result here with
@@ -234,4 +234,78 @@ class TestCaseComponent(object):
         if self.expectedExceptionType:
             raise FailedTest(None, self.expectedExceptionType)
 
-        return
+class ReusableTestCaseComponent(TestCaseComponent):
+    """A version of TestCaseComponent that does not remove its disk images
+       after use.  In this way, a later TestCaseComponent can reuse them.
+       This is handy for test cases that need pre-existing partitioning.
+
+       See further comments in ReusingTestCaseComponent.
+    """
+
+    def tearDownDisks(self):
+        # Don't destroy disks here, since a later component will want to
+        # use them.
+        self._blivet.devicetree.teardownDiskImages()
+
+class ReusingTestCaseComponent(TestCaseComponent):
+    """A version of TestCaseComponent that reuses existing disk images
+       rather than create its own.  It will, however, delete these disk images
+       after use.
+
+       This class knows which disk images to reuse by the reusedComponents
+       parameter passed in at object instantiation.  This is a list of other
+       TestCaseComponent instances.  This class will reuse all disk images
+       from each instance in that list, in the order given.
+
+       A typical pipeline of components would thus look like this:
+
+           class ComponentA(ReusableTestCaseComponent):
+               ...
+
+           class ComponentB(ReusingTestCaseComponent):
+               ...
+
+           ComponentA -> ComponentB
+
+       A component may also derive from both, if it's in the middle of the
+       pipeline:
+
+           class ComponentA(ReusableTestCaseComponent):
+               ...
+
+           class ComponentB(ReusableTestCaseComponent, ReusingTestCaseComponent):
+               ...
+
+           class ComponentC(ReusingTestCaseComponent):
+               ...
+
+           ComponentA -> ComponentB -> ComponentC
+    """
+
+    def __init__(self, reusedComponents=None):
+        """Create a new ReusingTestCaseComponent.  reusedComponents is a list
+           of other TestCaseComponent objects that this instance should make
+           use of.  All disk images in that list will be used by this instance,
+           and all will be cleaned up at the end of the test.
+        """
+        TestCaseComponent.__init__(self)
+
+        if reusedComponents is None:
+            self._reusedComponents = []
+        else:
+            self._reusedComponents = reusedComponents
+
+    def setupDisks(self, ksdata):
+        self._blivet = blivet.Blivet(ksdata=ksdata)
+
+        # See comment in super class's method.
+        from pyanaconda.bootloader import get_bootloader
+        self._blivet._bootloader = get_bootloader()
+
+        for component in self._reusedComponents:
+            self._disks.update(component._disks)
+
+        for (name, image) in self._disks.items():
+            self._blivet.config.diskImages[name] = image
+
+        self._blivet.reset()
diff --git a/tests/storage/cases/bz1014545.py b/tests/storage/cases/bz1014545.py
index 6aa8ba1..1e652f5 100644
--- a/tests/storage/cases/bz1014545.py
+++ b/tests/storage/cases/bz1014545.py
@@ -17,6 +17,8 @@
 #
 # Author: Chris Lumens <clumens at redhat.com>
 
+__all__ = ["BZ1014545_TestCase"]
+
 from . import TestCase, TestCaseComponent
 
 from blivet.size import Size
@@ -27,14 +29,15 @@ class BTRFSOnNonBTRFSComponent(TestCaseComponent):
 
     def __init__(self, *args, **kwargs):
         TestCaseComponent.__init__(self, *args, **kwargs)
-        self.disksToCreate = [("anatest-disk1", Size("1GiB"))]
+        self.disksToCreate = [("btrfs-on-non-btrfs-disk1", Size("1GiB"))]
 
     @property
     def ks(self):
         return """
+bootloader --location=none
 zerombr
 clearpart --all --initlabel
-btrfs none --data=0 --metadata=1 anatest-disk1
+btrfs none --data=0 --metadata=1 btrfs-on-non-btrfs-disk1
 """
 
     @property
@@ -43,21 +46,22 @@ btrfs none --data=0 --metadata=1 anatest-disk1
 
     @property
     def expectedExceptionText(self):
-        return "BTRFS partition .* has incorrect format"
+        return "Btrfs partition .* has a format of \"disklabel\", but should have a format of \"btrfs\""
 
 class VolGroupOnNonPVsComponent(TestCaseComponent):
     name = "VolGroupOnNonPVs"
 
     def __init__(self, *args, **kwargs):
         TestCaseComponent.__init__(self, *args, **kwargs)
-        self.disksToCreate = [("anatest-disk1", Size("1GiB"))]
+        self.disksToCreate = [("volgroup-on-non-pv-disk1", Size("1GiB"))]
 
     @property
     def ks(self):
         return """
+bootloader --location=none
 zerombr
 clearpart --all --initlabel
-volgroup myvg anatest-disk1
+volgroup myvg volgroup-on-non-pv-disk1
 """
 
     @property
@@ -66,22 +70,23 @@ volgroup myvg anatest-disk1
 
     @property
     def expectedExceptionText(self):
-        return "Physical Volume .* has incorrect format"
+        return "Physical volume .* has a format of \"disklabel\", but should have a format of \"lvmpv\""
 
 class RaidOnNonRaidMembersComponent(TestCaseComponent):
     name = "RaidOnNonRaidMembers"
 
     def __init__(self, *args, **kwargs):
         TestCaseComponent.__init__(self, *args, **kwargs)
-        self.disksToCreate = [("anatest-disk1", Size("1GiB")),
-                              ("anatest-disk2", Size("1GiB"))]
+        self.disksToCreate = [("raid-on-non-raid-disk1", Size("1GiB")),
+                              ("raid-on-non-raid-disk2", Size("1GiB"))]
 
     @property
     def ks(self):
         return """
+bootloader --location=none
 zerombr
 clearpart --all --initlabel
-raid / --level=1 --device=md0 anatest-disk1 anatest-disk2
+raid / --level=1 --device=md0 raid-on-non-raid-disk1 raid-on-non-raid-disk2
 """
 
     @property
@@ -90,12 +95,9 @@ raid / --level=1 --device=md0 anatest-disk1 anatest-disk2
 
     @property
     def expectedExceptionText(self):
-        return "RAID member .* has incorrect format"
+        return "RAID device .* has a format of \"disklabel\", but should have a format of \"mdmember\""
 
 class BZ1014545_TestCase(TestCase):
-    components = [BTRFSOnNonBTRFSComponent,
-                  RaidOnNonRaidMembersComponent,
-                  VolGroupOnNonPVsComponent]
     name = "1014545"
     desc = """The members of various commands must have the correct format.
 For instance, raid members must have mdmember, and volgroup members must have
@@ -108,3 +110,9 @@ much further in installation - during bootloader installation.  The real
 bug is that this condition should be detected when the kickstart storage
 commands are being converted to actions.
 """
+
+    def __init__(self):
+        TestCase.__init__(self)
+        self.components = [BTRFSOnNonBTRFSComponent(),
+                           RaidOnNonRaidMembersComponent(),
+                           VolGroupOnNonPVsComponent()]
diff --git a/tests/storage/cases/bz1067707.py b/tests/storage/cases/bz1067707.py
index e49e4fc..63b5de8 100644
--- a/tests/storage/cases/bz1067707.py
+++ b/tests/storage/cases/bz1067707.py
@@ -17,6 +17,8 @@
 #
 # Author: Chris Lumens <clumens at redhat.com>
 
+__all__ = ["BZ1067707_TestCase"]
+
 from . import TestCase, TestCaseComponent
 from blivet.size import Size
 
@@ -25,21 +27,25 @@ class SwapWithRecommendedSizeComponent(TestCaseComponent):
 
     def __init__(self, *args, **kwargs):
         TestCaseComponent.__init__(self, *args, **kwargs)
-        self.disksToCreate = [("anatest-disk1", Size("1GiB"))]
+        self.disksToCreate = [("recommended-swap-disk1", Size("1GiB"))]
 
     @property
     def ks(self):
         return """
+bootloader --location=none
 zerombr
 clearpart --all --initlabel
 part swap --recommended
 """
 
 class BZ1067707_TestCase(TestCase):
-    components = [SwapWithRecommendedSizeComponent]
     name = "1067707"
     desc = """Partition lines do not necessarily require a size to be
 specified.  There are plenty of reasons why a user might not do this:  putting
 a filesystem on a given pre-existing partition, giving a resize or maxsize
 option, or (in this case) asking for the recommended swap size.
 """
+
+    def __init__(self):
+        TestCase.__init__(self)
+        self.components = [SwapWithRecommendedSizeComponent()]
diff --git a/tests/storage/cases/reuse.py b/tests/storage/cases/reuse.py
new file mode 100644
index 0000000..ac02d58
--- /dev/null
+++ b/tests/storage/cases/reuse.py
@@ -0,0 +1,187 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014  Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Chris Lumens <clumens at redhat.com>
+
+__all__ = ["PartitionReuse_TestCase", "LVMReuse_TestCase", "BTRFSReuse_TestCase", "ThinpReuse_TestCase"]
+
+from . import TestCase, ReusableTestCaseComponent, ReusingTestCaseComponent
+from blivet.size import Size
+
+class FirstPartitionAutopartComponent(ReusableTestCaseComponent):
+    name = "FirstPartitionAutopart"
+
+    def __init__(self, *args, **kwargs):
+        ReusableTestCaseComponent.__init__(self, *args, **kwargs)
+        self.disksToCreate = [("part-autopart-disk1", Size("8GiB"))]
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=plain
+"""
+
+class SecondPartitionAutopartComponent(ReusingTestCaseComponent):
+    name = "SecondPartitionAutopart"
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=plain
+"""
+
+class PartitionReuse_TestCase(TestCase):
+    name = "PartitionReuse"
+    desc = """Test that a disk with pre-existing partitioning as a
+result of a previous installation with partition-based autopart works.
+"""
+
+    def __init__(self):
+        TestCase.__init__(self)
+        first = FirstPartitionAutopartComponent()
+        second = SecondPartitionAutopartComponent(reusedComponents=[first])
+
+        self.components = [first, second]
+
+class FirstLVMAutopartComponent(ReusableTestCaseComponent):
+    name = "FirstLVMAutopart"
+
+    def __init__(self, *args, **kwargs):
+        ReusableTestCaseComponent.__init__(self, *args, **kwargs)
+        self.disksToCreate = [("lvm-autopart-disk1", Size("8GiB"))]
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=lvm
+"""
+
+class SecondLVMAutopartComponent(ReusingTestCaseComponent):
+    name = "SecondLVMAutopart"
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=lvm
+"""
+
+class LVMReuse_TestCase(TestCase):
+    name = "LVMReuse"
+    desc = """Test that a disk with pre-existing LVM partitioning as a
+result of a previous installation with LVM-based autopart works.
+"""
+
+    def __init__(self):
+        TestCase.__init__(self)
+        first = FirstLVMAutopartComponent()
+        second = SecondLVMAutopartComponent(reusedComponents=[first])
+
+        self.components = [first, second]
+
+class FirstBTRFSAutopartComponent(ReusableTestCaseComponent):
+    name = "FirstBTRFSAutopart"
+
+    def __init__(self, *args, **kwargs):
+        ReusableTestCaseComponent.__init__(self, *args, **kwargs)
+        self.disksToCreate = [("btrfs-autopart-disk1", Size("8GiB"))]
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=btrfs
+"""
+
+class SecondBTRFSAutopartComponent(ReusingTestCaseComponent):
+    name = "SecondBTRFSAutopart"
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=btrfs
+"""
+
+class BTRFSReuse_TestCase(TestCase):
+    name = "BTRFSReuse"
+    desc = """Test that a disk with pre-existing BTRFS partitioning as a
+result of a previous installation with BTRFS-based autopart works.
+"""
+
+    def __init__(self):
+        TestCase.__init__(self)
+        first = FirstBTRFSAutopartComponent()
+        second = SecondBTRFSAutopartComponent(reusedComponents=[first])
+
+        self.components = [first, second]
+
+class FirstThinpAutopartComponent(ReusableTestCaseComponent):
+    name = "FirstThinpAutopart"
+
+    def __init__(self, *args, **kwargs):
+        ReusableTestCaseComponent.__init__(self, *args, **kwargs)
+        self.disksToCreate = [("thinp-autopart-disk1", Size("8GiB"))]
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=thinp
+"""
+
+class SecondThinpAutopartComponent(ReusingTestCaseComponent):
+    name = "SecondThinpAutopart"
+
+    @property
+    def ks(self):
+        return """
+bootloader --location=none
+zerombr
+clearpart --all --initlabel
+autopart --type=thinp
+"""
+
+class ThinpReuse_TestCase(TestCase):
+    name = "ThinpReuse"
+    desc = """Test that a disk with pre-existing thinp partitioning as a
+result of a previous installation with thinp autopart works.
+"""
+
+    def __init__(self):
+        TestCase.__init__(self)
+        first = FirstThinpAutopartComponent()
+        second = SecondThinpAutopartComponent(reusedComponents=[first])
+
+        self.components = [first, second]
diff --git a/tests/storage/run_storage_tests.py b/tests/storage/run_storage_tests.py
index ef155e0..8a52730 100755
--- a/tests/storage/run_storage_tests.py
+++ b/tests/storage/run_storage_tests.py
@@ -9,9 +9,16 @@ if os.geteuid() != 0:
 
 from cases.bz1014545 import BZ1014545_TestCase
 from cases.bz1067707 import BZ1067707_TestCase
+from cases.reuse import PartitionReuse_TestCase, LVMReuse_TestCase, BTRFSReuse_TestCase, ThinpReuse_TestCase
 
-for tc in [BZ1014545_TestCase(),
+failures = 0
+
+for tc in [PartitionReuse_TestCase(),
+           LVMReuse_TestCase(),
+           BTRFSReuse_TestCase(),
+           ThinpReuse_TestCase(),
+           BZ1014545_TestCase(),
            BZ1067707_TestCase()]:
-    failures = tc.run()
+    failures += tc.run()
 
 os._exit(failures)
-- 
1.9.3



More information about the anaconda-patches mailing list