Skip to content

Commit 4c2911f

Browse files
dlatypovshuahkh
authored andcommitted
kunit: tool: reconfigure when the used kunitconfig changes
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/[email protected]/ Signed-off-by: Daniel Latypov <[email protected]> Reviewed-by: David Gow <[email protected]> Reviewed-by: Brendan Higgins <[email protected]> Signed-off-by: Shuah Khan <[email protected]>
1 parent c44895b commit 4c2911f

File tree

3 files changed

+78
-15
lines changed

3 files changed

+78
-15
lines changed

Documentation/dev-tools/kunit/start.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ It'll warn you if you haven't included the dependencies of the options you're
5050
using.
5151

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

5858

5959
Running the tests (KUnit Wrapper)

tools/testing/kunit/kunit_kernel.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
KCONFIG_PATH = '.config'
2323
KUNITCONFIG_PATH = '.kunitconfig'
24+
OLD_KUNITCONFIG_PATH = 'last_used_kunitconfig'
2425
DEFAULT_KUNITCONFIG_PATH = 'tools/testing/kunit/configs/default.config'
2526
BROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
2627
OUTFILE_PATH = 'test.log'
@@ -179,6 +180,9 @@ def get_kconfig_path(build_dir) -> str:
179180
def get_kunitconfig_path(build_dir) -> str:
180181
return get_file_path(build_dir, KUNITCONFIG_PATH)
181182

183+
def get_old_kunitconfig_path(build_dir) -> str:
184+
return get_file_path(build_dir, OLD_KUNITCONFIG_PATH)
185+
182186
def get_outfile_path(build_dir) -> str:
183187
return get_file_path(build_dir, OUTFILE_PATH)
184188

@@ -289,24 +293,38 @@ def build_config(self, build_dir, make_options) -> bool:
289293
except ConfigError as e:
290294
logging.error(e)
291295
return False
292-
return self.validate_config(build_dir)
296+
if not self.validate_config(build_dir):
297+
return False
298+
299+
old_path = get_old_kunitconfig_path(build_dir)
300+
if os.path.exists(old_path):
301+
os.remove(old_path) # write_to_file appends to the file
302+
self._kconfig.write_to_file(old_path)
303+
return True
304+
305+
def _kunitconfig_changed(self, build_dir: str) -> bool:
306+
old_path = get_old_kunitconfig_path(build_dir)
307+
if not os.path.exists(old_path):
308+
return True
309+
310+
old_kconfig = kunit_config.parse_file(old_path)
311+
return old_kconfig.entries() != self._kconfig.entries()
293312

294313
def build_reconfig(self, build_dir, make_options) -> bool:
295314
"""Creates a new .config if it is not a subset of the .kunitconfig."""
296315
kconfig_path = get_kconfig_path(build_dir)
297-
if os.path.exists(kconfig_path):
298-
existing_kconfig = kunit_config.parse_file(kconfig_path)
299-
self._ops.make_arch_qemuconfig(self._kconfig)
300-
if not self._kconfig.is_subset_of(existing_kconfig):
301-
print('Regenerating .config ...')
302-
os.remove(kconfig_path)
303-
return self.build_config(build_dir, make_options)
304-
else:
305-
return True
306-
else:
316+
if not os.path.exists(kconfig_path):
307317
print('Generating .config ...')
308318
return self.build_config(build_dir, make_options)
309319

320+
existing_kconfig = kunit_config.parse_file(kconfig_path)
321+
self._ops.make_arch_qemuconfig(self._kconfig)
322+
if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir):
323+
return True
324+
print('Regenerating .config ...')
325+
os.remove(kconfig_path)
326+
return self.build_config(build_dir, make_options)
327+
310328
def build_kernel(self, alltests, jobs, build_dir, make_options) -> bool:
311329
try:
312330
if alltests:

tools/testing/kunit/kunit_tool_test.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,51 @@ def fake_start(unused_args, unused_build_dir):
413413
with open(kunit_kernel.get_outfile_path(build_dir), 'rt') as outfile:
414414
self.assertEqual(outfile.read(), 'hi\nbye\n', msg='Missing some output')
415415

416+
def test_build_reconfig_no_config(self):
417+
with tempfile.TemporaryDirectory('') as build_dir:
418+
with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
419+
f.write('CONFIG_KUNIT=y')
420+
421+
tree = kunit_kernel.LinuxSourceTree(build_dir)
422+
mock_build_config = mock.patch.object(tree, 'build_config').start()
423+
424+
# Should generate the .config
425+
self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
426+
mock_build_config.assert_called_once_with(build_dir, [])
427+
428+
def test_build_reconfig_existing_config(self):
429+
with tempfile.TemporaryDirectory('') as build_dir:
430+
# Existing .config is a superset, should not touch it
431+
with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
432+
f.write('CONFIG_KUNIT=y')
433+
with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
434+
f.write('CONFIG_KUNIT=y')
435+
with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
436+
f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
437+
438+
tree = kunit_kernel.LinuxSourceTree(build_dir)
439+
mock_build_config = mock.patch.object(tree, 'build_config').start()
440+
441+
self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
442+
self.assertEqual(mock_build_config.call_count, 0)
443+
444+
def test_build_reconfig_remove_option(self):
445+
with tempfile.TemporaryDirectory('') as build_dir:
446+
# We removed CONFIG_KUNIT_TEST=y from our .kunitconfig...
447+
with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
448+
f.write('CONFIG_KUNIT=y')
449+
with open(kunit_kernel.get_old_kunitconfig_path(build_dir), 'w') as f:
450+
f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
451+
with open(kunit_kernel.get_kconfig_path(build_dir), 'w') as f:
452+
f.write('CONFIG_KUNIT=y\nCONFIG_KUNIT_TEST=y')
453+
454+
tree = kunit_kernel.LinuxSourceTree(build_dir)
455+
mock_build_config = mock.patch.object(tree, 'build_config').start()
456+
457+
# ... so we should trigger a call to build_config()
458+
self.assertTrue(tree.build_reconfig(build_dir, make_options=[]))
459+
mock_build_config.assert_called_once_with(build_dir, [])
460+
416461
# TODO: add more test cases.
417462

418463

0 commit comments

Comments
 (0)