Skip to content

Conversation

@vojtechtrefny
Copy link
Member

@vojtechtrefny vojtechtrefny commented Jan 26, 2026

Mostly prompted by our QE's coverage report. I am definitely not aiming for 100 % coverage, but there were some quite bad "holes" in our tests, sometimes even hiding some real issues.

Just for fun: LCOV report on master and with these changes. The difference isn't that huge (lines coverage from 62.6 % to 64.3 % and function coverage from 65.7 % to 67.5 %), but most of the "missing parts" are either error cases or the copy and free functions that are not really covered by the python tests.

Summary by CodeRabbit

  • Bug Fixes

    • Treat empty filesystem-type strings as invalid; multipath and loop DM operations no longer bail early for non-root and proceed with device queries.
  • Documentation

    • Added LCOV coverage report guide; clarified stdout/stderr logging behavior.
  • Public API

    • SMART tech API signature adjusted and a deprecated runtime shim added; LVM cache-statistics interface removed.
  • Tests

    • Expanded coverage across LVM (pvmove), crypto, filesystems, loop, multipath, partitioning, SMART, and utilities.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

Adds LCOV coverage docs; tightens fstype validation in FS checks; adds and removes LVM cache-stats implementations across backends; adjusts SMART API and plugin shims; drops root-only guards in multipath DM ops; and introduces numerous test additions and adjustments across plugins and utils.

Changes

Cohort / File(s) Summary
Docs & Logging
docs/libblockdev-docs.xml.in, src/utils/logging.c
Added LCOV coverage-generation instructions; documented that bd_utils_log_stdout uses GLib logging. (docs-only changes)
SMART plugin & API
src/lib/plugin_apis/smart.api, src/plugins/smart/libatasmart.c, src/plugins/smart/smartmontools.c, tests/smart_test.py, tests/smartmontools_test.py
Changed bd_smart_is_tech_avail first parameter type to BDSmartTech in API; added bd_smart_check_deps() shim; simplified smartmontools deps check to call bd_smart_is_tech_avail(0,0,NULL); added tech-availability tests and fake-path failure tests. Attention: public API signature changed in smart.api.
LVM cache-stats relocation
src/plugins/lvm/lvm-common.c, src/plugins/lvm/lvm-dbus.c, src/plugins/lvm/lvm.c, tests/_lvm_cases.py, tests/lvm_test.py, tests/lvm_dbus_tests.py
Added bd_lvm_cache_stats implementation in lvm-common.c; removed prior implementations from lvm-dbus.c and lvm.c; added PV-move and PV-tag tests and a LvmTestPVmove test class wiring. Attention: symbol moved/removed across LVM backends.
Filesystem validation & tests
src/plugins/fs/generic.c, tests/fs_tests/generic_test.py
bd_fs_check_label and bd_fs_check_uuid now treat empty-string fstype as invalid; tests updated to use fs_can_get_info, added set_uuid tests, expanded label/UUID and resize coverage (VFAT/XFS variants). Attention: callers passing empty fstype now error.
Multipath (mpath)
src/plugins/mpath.c, tests/mpath_test.py
Removed early non-root guards from DM map operations (map_is_multipath, get_map_deps, bd_mpath_is_mpath_member, bd_mpath_get_mpath_members); DM errors now propagate instead of early non-root failure; added test_flush_mpaths. Attention: runtime permission/error behavior changed for non-root.
SMART libatasmart minor changes
src/plugins/smart/libatasmart.c
Marked bd_smart_is_tech_avail tech parameter as used (removed G_GNUC_UNUSED) and added bd_smart_check_deps() returning TRUE (deprecated shim).
Crypto / LUKS tests
tests/crypto_test.py
Expanded LUKS tests: tech-availability checks, negative resize/open assertions, long-device-name validation, and extended keyfile/keyslot add/change/remove scenarios for LUKS1/LUKS2.
Loop, Partition, Library & Utils tests
tests/loop_test.py, tests/part_test.py, tests/library_test.py, tests/utils_test.py, tests/btrfs_test.py, tests/skip.yml
Added tech-availability tests for loop/partition; extended library init/reinit tests; added utils tests (logging redirect, kernel module load/unload); fixed duplicate mkdir in btrfs test; tightened skip condition for an LVM DBus test. Attention: many new/modified tests may affect CI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Various test fixes" is vague and generic, using non-descriptive terms that don't convey specific information about the changeset's primary modifications. Consider using a more specific title that highlights the main changes, such as "Add test coverage for error cases and improve filesystem/LVM/SMART test suites" or "Expand test coverage across multiple plugins and consolidate LVM cache stats implementation."
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/smart/libatasmart.c (1)

56-66: Handle combined BDSmartTech flags explicitly.

BDSmartTech is currently used with combined flags (e.g., BD_SMART_TECH_ATA | BD_SMART_TECH_SCSI in smartmontools.c), but the enum values are sequential (0, 1) rather than powers of 2. The switch statement relies on the coincidence that 0 | 1 = 1, which happens to match BD_SMART_TECH_SCSI. This works by accident but is fragile and error-prone. Use bitmask checks to handle combined flags explicitly and correctly.

🔧 Suggested fix
-gboolean bd_smart_is_tech_avail (BDSmartTech tech, G_GNUC_UNUSED guint64 mode, GError **error) {
-    switch (tech) {
-        case BD_SMART_TECH_ATA:
-            return TRUE;
-        case BD_SMART_TECH_SCSI:
-            g_set_error (error, BD_SMART_ERROR, BD_SMART_ERROR_TECH_UNAVAIL, "SCSI SMART is unavailable with libatasmart");
-            return FALSE;
-        default:
-            g_set_error (error, BD_SMART_ERROR, BD_SMART_ERROR_TECH_UNAVAIL, "Unknown technology");
-            return FALSE;
-    }
-}
+gboolean bd_smart_is_tech_avail (BDSmartTech tech, G_GNUC_UNUSED guint64 mode, GError **error) {
+    if (tech & BD_SMART_TECH_SCSI) {
+        g_set_error (error, BD_SMART_ERROR, BD_SMART_ERROR_TECH_UNAVAIL,
+                     "SCSI SMART is unavailable with libatasmart");
+        return FALSE;
+    }
+    if (tech & BD_SMART_TECH_ATA)
+        return TRUE;
+
+    g_set_error (error, BD_SMART_ERROR, BD_SMART_ERROR_TECH_UNAVAIL, "Unknown technology");
+    return FALSE;
+}
🤖 Fix all issues with AI agents
In `@tests/crypto_test.py`:
- Around line 589-595: After calling
BlockDev.crypto_luks_add_key(self.loop_devs[0], ctx, nctx2) the test assigns the
result to succ but never asserts it; add a verification similar to the
subsequent call to ensure the operation succeeded. Update tests/crypto_test.py
to assert succ (e.g., self.assertTrue(succ)) immediately after the first
crypto_luks_add_key call that produces succ so failures adding the keyfile-based
key are caught; reference the existing BlockDev.CryptoKeyslotContext and
BlockDev.crypto_luks_add_key usage and the succ variable for placement.
🧹 Nitpick comments (6)
tests/btrfs_test.py (1)

382-382: Consider using os.makedirs() for consistency and safety.

While TEST_MNT is a hardcoded constant (so the shell injection risk flagged by static analysis is a false positive in practice), using os.makedirs() would be more Pythonic and consistent with line 388 which already uses this approach.

Suggested change
-        os.system("mkdir -p %s/one/two/one/two" % (TEST_MNT))
+        os.makedirs(os.path.join(TEST_MNT, "one/two/one/two"), exist_ok=True)
tests/mpath_test.py (1)

51-55: Avoid unnecessary storage setup for this no-op test.

This test doesn’t use the loop device from MpathTestCase.setUp, so it pays the LIO setup cost (and potential privilege failures) unnecessarily. Consider moving it to MpathNoDevTestCase and tagging it NOSTORAGE to keep it runnable in minimal environments.

♻️ Proposed refactor
-class MpathTestCase(MpathTest):
+class MpathTestCase(MpathTest):
@@
-    def test_flush_mpaths(self):
-        """ Verify that mpath_flush_mpaths can be called """
-        # we have no multipath devices, but flush should still work (and do nothing)
-        succ = BlockDev.mpath_flush_mpaths()
-        self.assertTrue(succ)
+    # (moved to MpathNoDevTestCase)
@@
-class MpathNoDevTestCase(MpathTest):
+class MpathNoDevTestCase(MpathTest):
+    `@tag_test`(TestTags.NOSTORAGE)
+    def test_flush_mpaths(self):
+        """ Verify that mpath_flush_mpaths can be called """
+        # we have no multipath devices, but flush should still work (and do nothing)
+        succ = BlockDev.mpath_flush_mpaths()
+        self.assertTrue(succ)
tests/utils_test.py (1)

538-554: Consider potential test flakiness.

The test assumes that if no loop devices exist (/dev/loop[0-9]*), the loop module can be safely loaded and unloaded. However:

  1. The module might already be loaded but with no active loop devices
  2. The module could be built into the kernel rather than loadable
  3. utils_unload_kernel_module may fail if the module is in use by other subsystems

The conditional guard helps, but this test might still fail in certain environments. Consider adding the TestTags.UNSTABLE tag if flakiness is observed in CI.

tests/fs_tests/generic_test.py (1)

959-967: Rename the UUID failure test for clarity.
The method name says “label” but it tests UUID behavior, which can confuse future readers.

♻️ Suggested rename for clarity
-    def test_fail_generic_check_label(self):
+    def test_fail_generic_check_uuid(self):
@@
-            BlockDev.fs_check_uuid("", "label")
+            BlockDev.fs_check_uuid("", "uuid")
src/plugins/smart/smartmontools.c (1)

54-58: Avoid bitwise OR on BDSmartTech.
BDSmartTech is an enum, not a flags type. Passing ATA | SCSI is non-idiomatic and could break if bd_smart_is_tech_avail starts using the tech parameter in this plugin.

♻️ Suggested refactor
-    return bd_smart_is_tech_avail (BD_SMART_TECH_ATA | BD_SMART_TECH_SCSI, 0, NULL);
+    return bd_smart_is_tech_avail (BD_SMART_TECH_ATA, 0, NULL) &&
+           bd_smart_is_tech_avail (BD_SMART_TECH_SCSI, 0, NULL);
tests/lvm_dbus_tests.py (1)

72-82: Add reinit after fake_path context to restore normal tool availability state.

Inside the fake_path context, BlockDev.reinit() reloads the plugin with a restricted PATH, marking tools (lvmconfig, lvmdevices) as unavailable. When the context exits, PATH is restored but the plugin's cached dependency state persists, potentially affecting subsequent tests that rely on tool availability. Add a reinit call after the context to reload with the normal PATH restored.

Suggested fix
         with fake_path(all_but=("lvmconfig", "lvmdevices")):
             self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
 
             # no lvmconfig tool available, should fail
             with self.assertRaises(GLib.GError):
                 BlockDev.lvm_is_tech_avail(BlockDev.LVMTech.CONFIG, 0)
 
             # no lvmdevices tool available, should fail
             with self.assertRaises(GLib.GError):
                 BlockDev.lvm_is_tech_avail(BlockDev.LVMTech.DEVICES, 0)
+
+        # Restore normal PATH and refresh plugin state
+        self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@tests/fs_tests/generic_test.py`:
- Around line 849-860: The skip message in test_vfat_generic_set_label is
incorrect: change the text passed to self.skipTest from "dosfstools >= 4.2
needed to set UUID" to a message that references labels (e.g. "dosfstools >= 4.2
needed to set labels" or "dosfstools >= 4.2 needed to set vfat labels") so the
skip message matches the test's purpose; update the string in the
test_vfat_generic_set_label function where mkfs_vfat and _test_generic_set_label
are used.

In `@tests/utils_test.py`:
- Around line 561-563: The tearDown method is removing the GLib log handler with
an empty string domain which doesn't match how the handler was registered
(registered with None); update the removal call in tearDown to use the same
domain value (None) as used when setting the handler so
GLib.log_remove_handler(None, self.handler) matches the original
GLib.log_set_handler/GLib.log_add handler registration (refer to the tearDown
method and the code that registers the handler).
🧹 Nitpick comments (3)
tests/mpath_test.py (1)

51-56: Consider moving this test to MpathNoDevTestCase.

This test doesn't use the loop device created in MpathTestCase.setUp(). Since it only verifies that mpath_flush_mpaths() can be called when no multipath devices exist, it could be moved to MpathNoDevTestCase and tagged with @tag_test(TestTags.NOSTORAGE) to avoid unnecessary device setup/teardown overhead.

♻️ Suggested refactor
 class MpathNoDevTestCase(MpathTest):
     `@tag_test`(TestTags.NOSTORAGE)
     def test_plugin_version(self):
         self.assertEqual(BlockDev.get_plugin_soname(BlockDev.Plugin.MPATH), "libbd_mpath.so.3")

+    `@tag_test`(TestTags.NOSTORAGE)
+    def test_flush_mpaths(self):
+        """ Verify that mpath_flush_mpaths can be called """
+        # we have no multipath devices, but flush should still work (and do nothing)
+        succ = BlockDev.mpath_flush_mpaths()
+        self.assertTrue(succ)
+
     `@tag_test`(TestTags.NOSTORAGE)
     def test_get_mpath_members(self):
tests/utils_test.py (1)

565-566: Unused callback parameters are required by GLib signature.

The static analysis flags domain, level, and usrptr as unused, but these parameters are required by the GLib log handler callback signature. This is a false positive.

Consider adding a brief comment or using underscore prefixes to document the intent:

Optional: clarify unused parameters
-    def log_func(self, domain, level, msg, usrptr):
+    def log_func(self, _domain, _level, msg, _usrptr):
         self.log += msg
tests/fs_tests/generic_test.py (1)

1062-1175: Consider a small helper to reduce repeated mount branching.
The repeated if mount / with mounted(...) blocks are easy to drift; a tiny wrapper would keep this concise.

Copy link
Member

@tbzatek tbzatek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

This function should've never been added -- the check_deps
functions were removed in 3.0 and are no longer needed.
bd_smart_check_deps now calls bd_smart_is_tech_avail which is also
the recommended replacement.
This function was also not previously implemented in the
libatasmart plugin, which is also incorrect, all implementations
must implement all functions.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/libblockdev-docs.xml.in`:
- Around line 165-186: The LCOV workflow documents creating coverage-base.info
and coverage-test.info but never merges them, so update the sequence to combine
the initial and test tracefiles using lcov's --add-tracefile option (e.g., add a
step to run lcov --add-tracefile coverage-base.info --add-tracefile
coverage-test.info --output-file coverage-merged.info) before running the filter
(coverage-filtered.info) and genhtml; reference the existing filenames
coverage-base.info, coverage-test.info, coverage-filtered.info and the genhtml
step so the new merge step is inserted between capture of coverage-test.info and
the lcov --remove call.
🧹 Nitpick comments (1)
tests/mpath_test.py (1)

51-60: Avoid leaving dm-multipath loaded after the test.

This test loads a kernel module but never unloads it, which can leak state across the suite. Consider tracking whether the module was already loaded and add a cleanup to unload it only if the test loaded it. Also consider tagging this test as UNSAFE or EXTRADEPS if your test filters treat kernel-module changes as unsafe.

♻️ Suggested cleanup
-        ret, _out, _err = run_command("modprobe dm-multipath")
+        was_loaded, _out, _err = run_command("grep -q '^dm_multipath ' /proc/modules")
+        ret, _out, _err = run_command("modprobe dm-multipath")
         if ret != 0:
             self.skipTest("cannot load dm-multipath, skipping")
+        if was_loaded != 0:
+            self.addCleanup(lambda: run_command("modprobe -r dm-multipath"))

@vojtechtrefny vojtechtrefny merged commit cd289f8 into storaged-project:master Jan 27, 2026
42 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants