Back to home page

OSCL-LXR

 
 

    


0001 # SPDX-License-Identifier: GPL-2.0
0002 #
0003 # Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com>
0004 #
0005 
0006 """
0007 Kconfig unit testing framework.
0008 
0009 This provides fixture functions commonly used from test files.
0010 """
0011 
0012 import os
0013 import pytest
0014 import shutil
0015 import subprocess
0016 import tempfile
0017 
0018 CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf'))
0019 
0020 
0021 class Conf:
0022     """Kconfig runner and result checker.
0023 
0024     This class provides methods to run text-based interface of Kconfig
0025     (scripts/kconfig/conf) and retrieve the resulted configuration,
0026     stdout, and stderr.  It also provides methods to compare those
0027     results with expectations.
0028     """
0029 
0030     def __init__(self, request):
0031         """Create a new Conf instance.
0032 
0033         request: object to introspect the requesting test module
0034         """
0035         # the directory of the test being run
0036         self._test_dir = os.path.dirname(str(request.fspath))
0037 
0038     # runners
0039     def _run_conf(self, mode, dot_config=None, out_file='.config',
0040                   interactive=False, in_keys=None, extra_env={}):
0041         """Run text-based Kconfig executable and save the result.
0042 
0043         mode: input mode option (--oldaskconfig, --defconfig=<file> etc.)
0044         dot_config: .config file to use for configuration base
0045         out_file: file name to contain the output config data
0046         interactive: flag to specify the interactive mode
0047         in_keys: key inputs for interactive modes
0048         extra_env: additional environments
0049         returncode: exit status of the Kconfig executable
0050         """
0051         command = [CONF_PATH, mode, 'Kconfig']
0052 
0053         # Override 'srctree' environment to make the test as the top directory
0054         extra_env['srctree'] = self._test_dir
0055 
0056         # Clear KCONFIG_DEFCONFIG_LIST to keep unit tests from being affected
0057         # by the user's environment.
0058         extra_env['KCONFIG_DEFCONFIG_LIST'] = ''
0059 
0060         # Run Kconfig in a temporary directory.
0061         # This directory is automatically removed when done.
0062         with tempfile.TemporaryDirectory() as temp_dir:
0063 
0064             # if .config is given, copy it to the working directory
0065             if dot_config:
0066                 shutil.copyfile(os.path.join(self._test_dir, dot_config),
0067                                 os.path.join(temp_dir, '.config'))
0068 
0069             ps = subprocess.Popen(command,
0070                                   stdin=subprocess.PIPE,
0071                                   stdout=subprocess.PIPE,
0072                                   stderr=subprocess.PIPE,
0073                                   cwd=temp_dir,
0074                                   env=dict(os.environ, **extra_env))
0075 
0076             # If input key sequence is given, feed it to stdin.
0077             if in_keys:
0078                 ps.stdin.write(in_keys.encode('utf-8'))
0079 
0080             while ps.poll() is None:
0081                 # For interactive modes such as oldaskconfig, oldconfig,
0082                 # send 'Enter' key until the program finishes.
0083                 if interactive:
0084                     ps.stdin.write(b'\n')
0085 
0086             self.retcode = ps.returncode
0087             self.stdout = ps.stdout.read().decode()
0088             self.stderr = ps.stderr.read().decode()
0089 
0090             # Retrieve the resulted config data only when .config is supposed
0091             # to exist.  If the command fails, the .config does not exist.
0092             # 'listnewconfig' does not produce .config in the first place.
0093             if self.retcode == 0 and out_file:
0094                 with open(os.path.join(temp_dir, out_file)) as f:
0095                     self.config = f.read()
0096             else:
0097                 self.config = None
0098 
0099         # Logging:
0100         # Pytest captures the following information by default.  In failure
0101         # of tests, the captured log will be displayed.  This will be useful to
0102         # figure out what has happened.
0103 
0104         print("[command]\n{}\n".format(' '.join(command)))
0105 
0106         print("[retcode]\n{}\n".format(self.retcode))
0107 
0108         print("[stdout]")
0109         print(self.stdout)
0110 
0111         print("[stderr]")
0112         print(self.stderr)
0113 
0114         if self.config is not None:
0115             print("[output for '{}']".format(out_file))
0116             print(self.config)
0117 
0118         return self.retcode
0119 
0120     def oldaskconfig(self, dot_config=None, in_keys=None):
0121         """Run oldaskconfig.
0122 
0123         dot_config: .config file to use for configuration base (optional)
0124         in_key: key inputs (optional)
0125         returncode: exit status of the Kconfig executable
0126         """
0127         return self._run_conf('--oldaskconfig', dot_config=dot_config,
0128                               interactive=True, in_keys=in_keys)
0129 
0130     def oldconfig(self, dot_config=None, in_keys=None):
0131         """Run oldconfig.
0132 
0133         dot_config: .config file to use for configuration base (optional)
0134         in_key: key inputs (optional)
0135         returncode: exit status of the Kconfig executable
0136         """
0137         return self._run_conf('--oldconfig', dot_config=dot_config,
0138                               interactive=True, in_keys=in_keys)
0139 
0140     def olddefconfig(self, dot_config=None):
0141         """Run olddefconfig.
0142 
0143         dot_config: .config file to use for configuration base (optional)
0144         returncode: exit status of the Kconfig executable
0145         """
0146         return self._run_conf('--olddefconfig', dot_config=dot_config)
0147 
0148     def defconfig(self, defconfig):
0149         """Run defconfig.
0150 
0151         defconfig: defconfig file for input
0152         returncode: exit status of the Kconfig executable
0153         """
0154         defconfig_path = os.path.join(self._test_dir, defconfig)
0155         return self._run_conf('--defconfig={}'.format(defconfig_path))
0156 
0157     def _allconfig(self, mode, all_config):
0158         if all_config:
0159             all_config_path = os.path.join(self._test_dir, all_config)
0160             extra_env = {'KCONFIG_ALLCONFIG': all_config_path}
0161         else:
0162             extra_env = {}
0163 
0164         return self._run_conf('--{}config'.format(mode), extra_env=extra_env)
0165 
0166     def allyesconfig(self, all_config=None):
0167         """Run allyesconfig.
0168 
0169         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0170         returncode: exit status of the Kconfig executable
0171         """
0172         return self._allconfig('allyes', all_config)
0173 
0174     def allmodconfig(self, all_config=None):
0175         """Run allmodconfig.
0176 
0177         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0178         returncode: exit status of the Kconfig executable
0179         """
0180         return self._allconfig('allmod', all_config)
0181 
0182     def allnoconfig(self, all_config=None):
0183         """Run allnoconfig.
0184 
0185         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0186         returncode: exit status of the Kconfig executable
0187         """
0188         return self._allconfig('allno', all_config)
0189 
0190     def alldefconfig(self, all_config=None):
0191         """Run alldefconfig.
0192 
0193         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0194         returncode: exit status of the Kconfig executable
0195         """
0196         return self._allconfig('alldef', all_config)
0197 
0198     def randconfig(self, all_config=None):
0199         """Run randconfig.
0200 
0201         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0202         returncode: exit status of the Kconfig executable
0203         """
0204         return self._allconfig('rand', all_config)
0205 
0206     def savedefconfig(self, dot_config):
0207         """Run savedefconfig.
0208 
0209         dot_config: .config file for input
0210         returncode: exit status of the Kconfig executable
0211         """
0212         return self._run_conf('--savedefconfig', out_file='defconfig')
0213 
0214     def listnewconfig(self, dot_config=None):
0215         """Run listnewconfig.
0216 
0217         dot_config: .config file to use for configuration base (optional)
0218         returncode: exit status of the Kconfig executable
0219         """
0220         return self._run_conf('--listnewconfig', dot_config=dot_config,
0221                               out_file=None)
0222 
0223     # checkers
0224     def _read_and_compare(self, compare, expected):
0225         """Compare the result with expectation.
0226 
0227         compare: function to compare the result with expectation
0228         expected: file that contains the expected data
0229         """
0230         with open(os.path.join(self._test_dir, expected)) as f:
0231             expected_data = f.read()
0232         return compare(self, expected_data)
0233 
0234     def _contains(self, attr, expected):
0235         return self._read_and_compare(
0236                                     lambda s, e: getattr(s, attr).find(e) >= 0,
0237                                     expected)
0238 
0239     def _matches(self, attr, expected):
0240         return self._read_and_compare(lambda s, e: getattr(s, attr) == e,
0241                                       expected)
0242 
0243     def config_contains(self, expected):
0244         """Check if resulted configuration contains expected data.
0245 
0246         expected: file that contains the expected data
0247         returncode: True if result contains the expected data, False otherwise
0248         """
0249         return self._contains('config', expected)
0250 
0251     def config_matches(self, expected):
0252         """Check if resulted configuration exactly matches expected data.
0253 
0254         expected: file that contains the expected data
0255         returncode: True if result matches the expected data, False otherwise
0256         """
0257         return self._matches('config', expected)
0258 
0259     def stdout_contains(self, expected):
0260         """Check if resulted stdout contains expected data.
0261 
0262         expected: file that contains the expected data
0263         returncode: True if result contains the expected data, False otherwise
0264         """
0265         return self._contains('stdout', expected)
0266 
0267     def stdout_matches(self, expected):
0268         """Check if resulted stdout exactly matches expected data.
0269 
0270         expected: file that contains the expected data
0271         returncode: True if result matches the expected data, False otherwise
0272         """
0273         return self._matches('stdout', expected)
0274 
0275     def stderr_contains(self, expected):
0276         """Check if resulted stderr contains expected data.
0277 
0278         expected: file that contains the expected data
0279         returncode: True if result contains the expected data, False otherwise
0280         """
0281         return self._contains('stderr', expected)
0282 
0283     def stderr_matches(self, expected):
0284         """Check if resulted stderr exactly matches expected data.
0285 
0286         expected: file that contains the expected data
0287         returncode: True if result matches the expected data, False otherwise
0288         """
0289         return self._matches('stderr', expected)
0290 
0291 
0292 @pytest.fixture(scope="module")
0293 def conf(request):
0294     """Create a Conf instance and provide it to test functions."""
0295     return Conf(request)