| import os |
| import pytest |
| |
| from buildstream import _yaml |
| from buildstream.plugintestutils import cli_integration as cli |
| from tests.testutils.site import HAVE_SANDBOX |
| |
| |
| pytestmark = pytest.mark.integration |
| |
| |
| DATA_DIR = os.path.join( |
| os.path.dirname(os.path.realpath(__file__)), |
| "project" |
| ) |
| |
| |
| # execute_shell() |
| # |
| # Helper to run `bst shell` and first ensure that the element is built |
| # |
| # Args: |
| # cli (Cli): The cli runner fixture |
| # project (str): The project directory |
| # command (list): The command argv list |
| # config (dict): A project.conf dictionary to composite over the default |
| # mount (tuple): A (host, target) tuple for the `--mount` option |
| # element (str): The element to build and run a shell with |
| # isolate (bool): Whether to pass --isolate to `bst shell` |
| # |
| def execute_shell(cli, project, command, *, config=None, mount=None, element='base.bst', isolate=False): |
| # Ensure the element is built |
| result = cli.run(project=project, project_config=config, args=['build', element]) |
| assert result.exit_code == 0 |
| |
| args = ['shell'] |
| if isolate: |
| args += ['--isolate'] |
| if mount is not None: |
| host_path, target_path = mount |
| args += ['--mount', host_path, target_path] |
| args += [element, '--', *command] |
| |
| return cli.run(project=project, project_config=config, args=args) |
| |
| |
| # Test running something through a shell, allowing it to find the |
| # executable |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_shell(cli, datafiles): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| |
| result = execute_shell(cli, project, ["echo", "Ponies!"]) |
| assert result.exit_code == 0 |
| assert result.output == "Ponies!\n" |
| |
| |
| # Test running an executable directly |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_executable(cli, datafiles): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| |
| result = execute_shell(cli, project, ["/bin/echo", "Horseys!"]) |
| assert result.exit_code == 0 |
| assert result.output == "Horseys!\n" |
| |
| |
| # Test shell environment variable explicit assignments |
| @pytest.mark.parametrize("animal", [("Horse"), ("Pony")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_env_assign(cli, datafiles, animal): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| expected = animal + '\n' |
| |
| result = execute_shell(cli, project, ['/bin/sh', '-c', 'echo ${ANIMAL}'], config={ |
| 'shell': { |
| 'environment': { |
| 'ANIMAL': animal |
| } |
| } |
| }) |
| |
| assert result.exit_code == 0 |
| assert result.output == expected |
| |
| |
| # Test shell environment variable explicit assignments with host env var expansion |
| @pytest.mark.parametrize("animal", [("Horse"), ("Pony")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_env_assign_expand_host_environ(cli, datafiles, animal): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| expected = 'The animal is: {}\n'.format(animal) |
| |
| os.environ['BEAST'] = animal |
| |
| result = execute_shell(cli, project, ['/bin/sh', '-c', 'echo ${ANIMAL}'], config={ |
| 'shell': { |
| 'environment': { |
| 'ANIMAL': 'The animal is: ${BEAST}' |
| } |
| } |
| }) |
| |
| assert result.exit_code == 0 |
| assert result.output == expected |
| |
| |
| # Test that shell environment variable explicit assignments are discarded |
| # when running an isolated shell |
| @pytest.mark.parametrize("animal", [("Horse"), ("Pony")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_env_assign_isolated(cli, datafiles, animal): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| result = execute_shell(cli, project, ['/bin/sh', '-c', 'echo ${ANIMAL}'], isolate=True, config={ |
| 'shell': { |
| 'environment': { |
| 'ANIMAL': animal |
| } |
| } |
| }) |
| |
| assert result.exit_code == 0 |
| assert result.output == '\n' |
| |
| |
| # Test running an executable in a runtime with no shell (i.e., no |
| # /bin/sh) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_no_shell(cli, datafiles): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| element_path = os.path.join(project, 'elements') |
| element_name = 'shell/no-shell.bst' |
| |
| # Create an element that removes /bin/sh from the base runtime |
| element = { |
| 'kind': 'script', |
| 'depends': [{ |
| 'filename': 'base.bst', |
| 'type': 'build' |
| }], |
| 'variables': { |
| 'install-root': '/' |
| }, |
| 'config': { |
| 'commands': [ |
| 'rm /bin/sh' |
| ] |
| } |
| } |
| os.makedirs(os.path.dirname(os.path.join(element_path, element_name)), exist_ok=True) |
| _yaml.dump(element, os.path.join(element_path, element_name)) |
| |
| result = execute_shell(cli, project, ['/bin/echo', 'Pegasissies!'], element=element_name) |
| assert result.exit_code == 0 |
| assert result.output == "Pegasissies!\n" |
| |
| |
| # Test that bind mounts defined in project.conf work |
| @pytest.mark.parametrize("path", [("/etc/pony.conf"), ("/usr/share/pony/pony.txt")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_host_files(cli, datafiles, path): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| ponyfile = os.path.join(project, 'files', 'shell-mount', 'pony.txt') |
| result = execute_shell(cli, project, ['cat', path], config={ |
| 'shell': { |
| 'host-files': [ |
| { |
| 'host_path': ponyfile, |
| 'path': path |
| } |
| ] |
| } |
| }) |
| assert result.exit_code == 0 |
| assert result.output == 'pony\n' |
| |
| |
| # Test that bind mounts defined in project.conf work |
| @pytest.mark.parametrize("path", [("/etc"), ("/usr/share/pony")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_host_files_expand_environ(cli, datafiles, path): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| hostpath = os.path.join(project, 'files', 'shell-mount') |
| fullpath = os.path.join(path, 'pony.txt') |
| |
| os.environ['BASE_PONY'] = path |
| os.environ['HOST_PONY_PATH'] = hostpath |
| |
| result = execute_shell(cli, project, ['cat', fullpath], config={ |
| 'shell': { |
| 'host-files': [ |
| { |
| 'host_path': '${HOST_PONY_PATH}/pony.txt', |
| 'path': '${BASE_PONY}/pony.txt' |
| } |
| ] |
| } |
| }) |
| assert result.exit_code == 0 |
| assert result.output == 'pony\n' |
| |
| |
| # Test that bind mounts defined in project.conf dont mount in isolation |
| @pytest.mark.parametrize("path", [("/etc/pony.conf"), ("/usr/share/pony/pony.txt")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_isolated_no_mount(cli, datafiles, path): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| ponyfile = os.path.join(project, 'files', 'shell-mount', 'pony.txt') |
| result = execute_shell(cli, project, ['cat', path], isolate=True, config={ |
| 'shell': { |
| 'host-files': [ |
| { |
| 'host_path': ponyfile, |
| 'path': path |
| } |
| ] |
| } |
| }) |
| assert result.exit_code != 0 |
| assert path in result.stderr |
| assert 'No such file or directory' in result.stderr |
| |
| |
| # Test that we warn about non-existing files on the host if the mount is not |
| # declared as optional, and that there is no warning if it is optional |
| @pytest.mark.parametrize("optional", [("mandatory"), ("optional")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_host_files_missing(cli, datafiles, optional): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| ponyfile = os.path.join(project, 'files', 'shell-mount', 'horsy.txt') |
| |
| if optional == "optional": |
| option = True |
| else: |
| option = False |
| |
| # Assert that we did successfully run something in the shell anyway |
| result = execute_shell(cli, project, ['echo', 'Hello'], config={ |
| 'shell': { |
| 'host-files': [ |
| { |
| 'host_path': ponyfile, |
| 'path': '/etc/pony.conf', |
| 'optional': option |
| } |
| ] |
| } |
| }) |
| assert result.exit_code == 0 |
| assert result.output == 'Hello\n' |
| |
| if option: |
| # Assert that there was no warning about the mount |
| assert ponyfile not in result.stderr |
| else: |
| # Assert that there was a warning about the mount |
| assert ponyfile in result.stderr |
| |
| |
| # Test that bind mounts defined in project.conf work |
| @pytest.mark.parametrize("path", [("/etc/pony.conf"), ("/usr/share/pony/pony.txt")]) |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_cli_mount(cli, datafiles, path): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| ponyfile = os.path.join(project, 'files', 'shell-mount', 'pony.txt') |
| |
| result = execute_shell(cli, project, ['cat', path], mount=(ponyfile, path)) |
| assert result.exit_code == 0 |
| assert result.output == 'pony\n' |
| |
| |
| # Test that we can see the workspace files in a shell |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_workspace_visible(cli, datafiles): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| workspace = os.path.join(cli.directory, 'workspace') |
| element_name = 'workspace/workspace-mount-fail.bst' |
| |
| # Open a workspace on our build failing element |
| # |
| res = cli.run(project=project, args=['workspace', 'open', '--directory', workspace, element_name]) |
| assert res.exit_code == 0 |
| |
| # Ensure the dependencies of our build failing element are built |
| result = cli.run(project=project, args=['build', 'base.bst']) |
| assert result.exit_code == 0 |
| |
| # Obtain a copy of the hello.c content from the workspace |
| # |
| workspace_hello_path = os.path.join(cli.directory, 'workspace', 'hello.c') |
| assert os.path.exists(workspace_hello_path) |
| with open(workspace_hello_path, 'r') as f: |
| workspace_hello = f.read() |
| |
| # Cat the hello.c file from a bst shell command, and assert |
| # that we got the same content here |
| # |
| result = cli.run(project=project, args=[ |
| 'shell', '--build', element_name, '--', 'cat', 'hello.c' |
| ]) |
| assert result.exit_code == 0 |
| assert result.output == workspace_hello |
| |
| |
| # Test that '--sysroot' works |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_sysroot(cli, tmpdir, datafiles): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| base_element = "base/base-alpine.bst" |
| # test element only needs to be something lightweight for this test |
| test_element = "script/script.bst" |
| checkout_dir = os.path.join(str(tmpdir), 'alpine-sysroot') |
| test_file = 'hello' |
| |
| # Build and check out a sysroot |
| res = cli.run(project=project, args=['build', base_element]) |
| res.assert_success() |
| res = cli.run(project=project, args=['artifact', 'checkout', base_element, '--directory', checkout_dir]) |
| res.assert_success() |
| |
| # Mutate the sysroot |
| test_path = os.path.join(checkout_dir, test_file) |
| with open(test_path, 'w') as f: |
| f.write('hello\n') |
| |
| # Shell into the sysroot and check the test file exists |
| res = cli.run(project=project, args=[ |
| 'shell', '--build', '--sysroot', checkout_dir, test_element, '--', |
| 'grep', '-q', 'hello', '/' + test_file |
| ]) |
| res.assert_success() |
| |
| |
| # Test system integration commands can access devices in /dev |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_integration_devices(cli, datafiles): |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| element_name = 'integration.bst' |
| |
| result = execute_shell(cli, project, ["true"], element=element_name) |
| assert result.exit_code == 0 |
| |
| |
| # Test that a shell can be opened from an external workspace |
| @pytest.mark.datafiles(DATA_DIR) |
| @pytest.mark.parametrize("build_shell", [("build"), ("nobuild")]) |
| @pytest.mark.parametrize("guess_element", [True, False], ids=["guess", "no-guess"]) |
| @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') |
| def test_integration_external_workspace(cli, tmpdir_factory, datafiles, build_shell, guess_element): |
| tmpdir = tmpdir_factory.mktemp("") |
| project = os.path.join(datafiles.dirname, datafiles.basename) |
| element_name = 'autotools/amhello.bst' |
| workspace_dir = os.path.join(str(tmpdir), 'workspace') |
| |
| if guess_element: |
| # Mutate the project.conf to use a default shell command |
| project_file = os.path.join(project, 'project.conf') |
| config_text = "shell:\n"\ |
| " command: ['true']\n" |
| with open(project_file, 'a') as f: |
| f.write(config_text) |
| |
| result = cli.run(project=project, args=[ |
| 'workspace', 'open', '--directory', workspace_dir, element_name |
| ]) |
| result.assert_success() |
| |
| result = cli.run(project=project, args=['-C', workspace_dir, 'build', element_name]) |
| result.assert_success() |
| |
| command = ['-C', workspace_dir, 'shell'] |
| if build_shell == 'build': |
| command.append('--build') |
| if not guess_element: |
| command.extend([element_name, '--', 'true']) |
| result = cli.run(project=project, cwd=workspace_dir, args=command) |
| result.assert_success() |