Skip to content

Commit

Permalink
kunit: tool: reconfigure when the used kunitconfig changes
Browse files Browse the repository at this point in the history
Problem: currently, if you remove something from your kunitconfig,
kunit.py will not regenerate the .config file.
The same thing happens if you did --kunitconfig_add=CONFIG_KASAN=y [1]
and then ran again without it. Your new run will still have KASAN.

The reason is that kunit.py won't regenerate the .config file if it's a
superset of the kunitconfig. This speeds it up a bit for iterating.

This patch adds an additional check that forces kunit.py to regenerate
the .config file if the current kunitconfig doesn't match the previous
one.

What this means:
* deleting entries from .kunitconfig works as one would expect
* dropping  a --kunitconfig_add also triggers a rebuild
* you can still edit .config directly to turn on new options

We implement this by creating a `last_used_kunitconfig` file in the
build directory (so .kunit, by default) after we generate the .config.
When comparing the kconfigs, we compare python sets, so duplicates and
permutations don't trip us up.

The majority of this patch is adding unit tests for the existing logic
and for the new case where `last_used_kunitconfig` differs.

[1] https://lore.kernel.org/linux-kselftest/20211106013058.2621799-2-dlatypov@google.com/

Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: David Gow <davidgow@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
  • Loading branch information
Daniel Latypov authored and Shuah Khan committed Dec 13, 2021
1 parent c44895b commit 4c2911f
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 15 deletions.
8 changes: 4 additions & 4 deletions Documentation/dev-tools/kunit/start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ It'll warn you if you haven't included the dependencies of the options you're
using.

.. note::
Note that removing something from the ``.kunitconfig`` will not trigger a
rebuild of the ``.config`` file: the configuration is only updated if the
``.kunitconfig`` is not a subset of ``.config``. This means that you can use
other tools (such as make menuconfig) to adjust other config options.
If you change the ``.kunitconfig``, kunit.py will trigger a rebuild of the
``.config`` file. But you can edit the ``.config`` file directly or with
tools like ``make menuconfig O=.kunit``. As long as its a superset of
``.kunitconfig``, kunit.py won't overwrite your changes.


Running the tests (KUnit Wrapper)
Expand Down
40 changes: 29 additions & 11 deletions tools/testing/kunit/kunit_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

KCONFIG_PATH = '.config'
KUNITCONFIG_PATH = '.kunitconfig'
OLD_KUNITCONFIG_PATH = 'last_used_kunitconfig'
DEFAULT_KUNITCONFIG_PATH = 'tools/testing/kunit/configs/default.config'
BROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
OUTFILE_PATH = 'test.log'
Expand Down Expand Up @@ -179,6 +180,9 @@ def get_kconfig_path(build_dir) -> str:
def get_kunitconfig_path(build_dir) -> str:
return get_file_path(build_dir, KUNITCONFIG_PATH)

def get_old_kunitconfig_path(build_dir) -> str:
return get_file_path(build_dir, OLD_KUNITCONFIG_PATH)

def get_outfile_path(build_dir) -> str:
return get_file_path(build_dir, OUTFILE_PATH)

Expand Down Expand Up @@ -289,24 +293,38 @@ def build_config(self, build_dir, make_options) -> bool:
except ConfigError as e:
logging.error(e)
return False
return self.validate_config(build_dir)
if not self.validate_config(build_dir):
return False

old_path = get_old_kunitconfig_path(build_dir)
if os.path.exists(old_path):
os.remove(old_path) # write_to_file appends to the file
self._kconfig.write_to_file(old_path)
return True

def _kunitconfig_changed(self, build_dir: str) -> bool:
old_path = get_old_kunitconfig_path(build_dir)
if not os.path.exists(old_path):
return True

old_kconfig = kunit_config.parse_file(old_path)
return old_kconfig.entries() != self._kconfig.entries()

def build_reconfig(self, build_dir, make_options) -> bool:
"""Creates a new .config if it is not a subset of the .kunitconfig."""
kconfig_path = get_kconfig_path(build_dir)
if os.path.exists(kconfig_path):
existing_kconfig = kunit_config.parse_file(kconfig_path)
self._ops.make_arch_qemuconfig(self._kconfig)
if not self._kconfig.is_subset_of(existing_kconfig):
print('Regenerating .config ...')
os.remove(kconfig_path)
return self.build_config(build_dir, make_options)
else:
return True
else:
if not os.path.exists(kconfig_path):
print('Generating .config ...')
return self.build_config(build_dir, make_options)

existing_kconfig = kunit_config.parse_file(kconfig_path)
self._ops.make_arch_qemuconfig(self._kconfig)
if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir):
return True
print('Regenerating .config ...')
os.remove(kconfig_path)
return self.build_config(build_dir, make_options)

def build_kernel(self, alltests, jobs, build_dir, make_options) -> bool:
try:
if alltests:
Expand Down
45 changes: 45 additions & 0 deletions tools/testing/kunit/kunit_tool_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,51 @@ def fake_start(unused_args, unused_build_dir):
with open(kunit_kernel.get_outfile_path(build_dir), 'rt') as outfile:
self.assertEqual(outfile.read(), 'hi\nbye\n', msg='Missing some output')

def test_build_reconfig_no_config(self):
with tempfile.TemporaryDirectory('') as build_dir:
with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y')

tree = kunit_kernel.LinuxSourceTree(build_dir)
mock_build_config = mock.patch.object(tree, 'build_config').start()

# Should generate the .config
self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
mock_build_config.assert_called_once_with(build_dir, [])

def test_build_reconfig_existing_config(self):
with tempfile.TemporaryDirectory('') as build_dir:
# Existing .config is a superset, should not touch it
with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y')
with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y')
with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')

tree = kunit_kernel.LinuxSourceTree(build_dir)
mock_build_config = mock.patch.object(tree, 'build_config').start()

self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
self.assertEqual(mock_build_config.call_count, 0)

def test_build_reconfig_remove_option(self):
with tempfile.TemporaryDirectory('') as build_dir:
# We removed CONFIG_KUNIT_TEST=y from our .kunitconfig...
with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y')
with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')

tree = kunit_kernel.LinuxSourceTree(build_dir)
mock_build_config = mock.patch.object(tree, 'build_config').start()

# ... so we should trigger a call to build_config()
self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
mock_build_config.assert_called_once_with(build_dir, [])

# TODO: add more test cases.


Expand Down

0 comments on commit 4c2911f

Please sign in to comment.