295 lines · 10.4 KB · Python
RawDownload
# SPDX-License-Identifier: GPL-2.0## Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com># """Kconfig unit testing framework. This provides fixture functions commonly used from test files.""" import osimport pytestimport shutilimport subprocessimport tempfile CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf'))  class Conf:    """Kconfig runner and result checker.     This class provides methods to run text-based interface of Kconfig    (scripts/kconfig/conf) and retrieve the resulted configuration,    stdout, and stderr.  It also provides methods to compare those    results with expectations.    """     def __init__(self, request):        """Create a new Conf instance.         request: object to introspect the requesting test module        """        # the directory of the test being run        self._test_dir = os.path.dirname(str(request.fspath))     # runners    def _run_conf(self, mode, dot_config=None, out_file='.config',                  interactive=False, in_keys=None, extra_env={}):        """Run text-based Kconfig executable and save the result.         mode: input mode option (--oldaskconfig, --defconfig=<file> etc.)        dot_config: .config file to use for configuration base        out_file: file name to contain the output config data        interactive: flag to specify the interactive mode        in_keys: key inputs for interactive modes        extra_env: additional environments        returncode: exit status of the Kconfig executable        """        command = [CONF_PATH, mode, 'Kconfig']         # Override 'srctree' environment to make the test as the top directory        extra_env['srctree'] = self._test_dir         # Clear KCONFIG_DEFCONFIG_LIST to keep unit tests from being affected        # by the user's environment.        extra_env['KCONFIG_DEFCONFIG_LIST'] = ''         # Run Kconfig in a temporary directory.        # This directory is automatically removed when done.        with tempfile.TemporaryDirectory() as temp_dir:             # if .config is given, copy it to the working directory            if dot_config:                shutil.copyfile(os.path.join(self._test_dir, dot_config),                                os.path.join(temp_dir, '.config'))             ps = subprocess.Popen(command,                                  stdin=subprocess.PIPE,                                  stdout=subprocess.PIPE,                                  stderr=subprocess.PIPE,                                  cwd=temp_dir,                                  env=dict(os.environ, **extra_env))             # If input key sequence is given, feed it to stdin.            if in_keys:                ps.stdin.write(in_keys.encode('utf-8'))             while ps.poll() is None:                # For interactive modes such as oldaskconfig, oldconfig,                # send 'Enter' key until the program finishes.                if interactive:                    ps.stdin.write(b'\n')             self.retcode = ps.returncode            self.stdout = ps.stdout.read().decode()            self.stderr = ps.stderr.read().decode()             # Retrieve the resulted config data only when .config is supposed            # to exist.  If the command fails, the .config does not exist.            # 'listnewconfig' does not produce .config in the first place.            if self.retcode == 0 and out_file:                with open(os.path.join(temp_dir, out_file)) as f:                    self.config = f.read()            else:                self.config = None         # Logging:        # Pytest captures the following information by default.  In failure        # of tests, the captured log will be displayed.  This will be useful to        # figure out what has happened.         print("[command]\n{}\n".format(' '.join(command)))         print("[retcode]\n{}\n".format(self.retcode))         print("[stdout]")        print(self.stdout)         print("[stderr]")        print(self.stderr)         if self.config is not None:            print("[output for '{}']".format(out_file))            print(self.config)         return self.retcode     def oldaskconfig(self, dot_config=None, in_keys=None):        """Run oldaskconfig.         dot_config: .config file to use for configuration base (optional)        in_key: key inputs (optional)        returncode: exit status of the Kconfig executable        """        return self._run_conf('--oldaskconfig', dot_config=dot_config,                              interactive=True, in_keys=in_keys)     def oldconfig(self, dot_config=None, in_keys=None):        """Run oldconfig.         dot_config: .config file to use for configuration base (optional)        in_key: key inputs (optional)        returncode: exit status of the Kconfig executable        """        return self._run_conf('--oldconfig', dot_config=dot_config,                              interactive=True, in_keys=in_keys)     def olddefconfig(self, dot_config=None):        """Run olddefconfig.         dot_config: .config file to use for configuration base (optional)        returncode: exit status of the Kconfig executable        """        return self._run_conf('--olddefconfig', dot_config=dot_config)     def defconfig(self, defconfig):        """Run defconfig.         defconfig: defconfig file for input        returncode: exit status of the Kconfig executable        """        defconfig_path = os.path.join(self._test_dir, defconfig)        return self._run_conf('--defconfig={}'.format(defconfig_path))     def _allconfig(self, mode, all_config):        if all_config:            all_config_path = os.path.join(self._test_dir, all_config)            extra_env = {'KCONFIG_ALLCONFIG': all_config_path}        else:            extra_env = {}         return self._run_conf('--{}config'.format(mode), extra_env=extra_env)     def allyesconfig(self, all_config=None):        """Run allyesconfig.         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)        returncode: exit status of the Kconfig executable        """        return self._allconfig('allyes', all_config)     def allmodconfig(self, all_config=None):        """Run allmodconfig.         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)        returncode: exit status of the Kconfig executable        """        return self._allconfig('allmod', all_config)     def allnoconfig(self, all_config=None):        """Run allnoconfig.         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)        returncode: exit status of the Kconfig executable        """        return self._allconfig('allno', all_config)     def alldefconfig(self, all_config=None):        """Run alldefconfig.         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)        returncode: exit status of the Kconfig executable        """        return self._allconfig('alldef', all_config)     def randconfig(self, all_config=None):        """Run randconfig.         all_config: fragment config file for KCONFIG_ALLCONFIG (optional)        returncode: exit status of the Kconfig executable        """        return self._allconfig('rand', all_config)     def savedefconfig(self, dot_config):        """Run savedefconfig.         dot_config: .config file for input        returncode: exit status of the Kconfig executable        """        return self._run_conf('--savedefconfig', out_file='defconfig')     def listnewconfig(self, dot_config=None):        """Run listnewconfig.         dot_config: .config file to use for configuration base (optional)        returncode: exit status of the Kconfig executable        """        return self._run_conf('--listnewconfig', dot_config=dot_config,                              out_file=None)     # checkers    def _read_and_compare(self, compare, expected):        """Compare the result with expectation.         compare: function to compare the result with expectation        expected: file that contains the expected data        """        with open(os.path.join(self._test_dir, expected)) as f:            expected_data = f.read()        return compare(self, expected_data)     def _contains(self, attr, expected):        return self._read_and_compare(                                    lambda s, e: getattr(s, attr).find(e) >= 0,                                    expected)     def _matches(self, attr, expected):        return self._read_and_compare(lambda s, e: getattr(s, attr) == e,                                      expected)     def config_contains(self, expected):        """Check if resulted configuration contains expected data.         expected: file that contains the expected data        returncode: True if result contains the expected data, False otherwise        """        return self._contains('config', expected)     def config_matches(self, expected):        """Check if resulted configuration exactly matches expected data.         expected: file that contains the expected data        returncode: True if result matches the expected data, False otherwise        """        return self._matches('config', expected)     def stdout_contains(self, expected):        """Check if resulted stdout contains expected data.         expected: file that contains the expected data        returncode: True if result contains the expected data, False otherwise        """        return self._contains('stdout', expected)     def stdout_matches(self, expected):        """Check if resulted stdout exactly matches expected data.         expected: file that contains the expected data        returncode: True if result matches the expected data, False otherwise        """        return self._matches('stdout', expected)     def stderr_contains(self, expected):        """Check if resulted stderr contains expected data.         expected: file that contains the expected data        returncode: True if result contains the expected data, False otherwise        """        return self._contains('stderr', expected)     def stderr_matches(self, expected):        """Check if resulted stderr exactly matches expected data.         expected: file that contains the expected data        returncode: True if result matches the expected data, False otherwise        """        return self._matches('stderr', expected)  @pytest.fixture(scope="module")def conf(request):    """Create a Conf instance and provide it to test functions."""    return Conf(request)