blob: a0af521a2581fbf197fa3867e369cd060223a351 [file] [log] [blame]
# Pylint doesn't play well with fixtures and dependency injection from pytest
# pylint: disable=redefined-outer-name
import os
import shutil
import pytest
from buildstream import _yaml
from buildstream._exceptions import ErrorDomain, LoadErrorReason
from buildstream.testing import cli # pylint: disable=unused-import
from buildstream.testing import create_repo
from buildstream.testing._utils.site import HAVE_GIT
DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'junctions',
)
def copy_subprojects(project, datafiles, subprojects):
for subproject in subprojects:
shutil.copytree(os.path.join(str(datafiles), subproject), os.path.join(str(project), subproject))
@pytest.mark.datafiles(DATA_DIR)
def test_simple_pipeline(cli, datafiles):
project = os.path.join(str(datafiles), 'foo')
copy_subprojects(project, datafiles, ['base'])
# Check that the pipeline includes the subproject element
element_list = cli.get_pipeline(project, ['target.bst'])
assert 'base.bst:target.bst' in element_list
@pytest.mark.datafiles(DATA_DIR)
def test_simple_build(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'foo')
copy_subprojects(project, datafiles, ['base'])
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from both projects
assert os.path.exists(os.path.join(checkoutdir, 'base.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'foo.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_junction_missing_project_conf(cli, datafiles):
project = datafiles / 'foo'
copy_subprojects(project, datafiles, ['base'])
# TODO: see if datafiles can tidy this concat up
# py3.5 requires this str conversion.
os.remove(str(project / 'base' / 'project.conf'))
# Note that both 'foo' and 'base' projects have a 'target.bst'. The
# 'app.bst' in 'foo' depends on the 'target.bst' in 'base', i.e.:
#
# foo/base/target.bst
# foo/app.bst -> foo/base/target.bst
# foo/target.bst -> foo/app.bst, foor/base/target.bst
#
# In a previous bug (issue #960) if the 'project.conf' was not found in the
# junction's dir then we were continuing the search in the parent dirs.
#
# This would mean that the dep on 'target.bst' would resolve to
# 'foo/target.bst' instead of 'foo/base/target.bst'.
#
# That would lead to a 'circular dependency error' in this setup, when we
# expect an 'invalid junction'.
#
result = cli.run(project=project, args=['build', 'app.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
# Assert that we have the expected provenance encoded into the error
assert "app.bst [line 6 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_workspaced_junction_missing_project_conf(cli, datafiles):
# See test_junction_missing_project_conf for some more background.
project = datafiles / 'foo'
workspace_dir = project / 'base_workspace'
copy_subprojects(project, datafiles, ['base'])
result = cli.run(
project=project,
args=['workspace', 'open', 'base.bst', '--directory', workspace_dir])
print(result)
result.assert_success()
# py3.5 requires this str conversion.
os.remove(str(workspace_dir / 'project.conf'))
result = cli.run(project=project, args=['build', 'app.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
# Assert that we have the expected provenance encoded into the error
assert "app.bst [line 6 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_build_of_same_junction_used_twice(cli, datafiles):
project = os.path.join(str(datafiles), 'inconsistent-names')
# Check we can build a project that contains the same junction
# that is used twice, but named differently
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
@pytest.mark.datafiles(DATA_DIR)
def test_missing_file_in_subproject(cli, datafiles):
project = os.path.join(str(datafiles), 'missing-element')
result = cli.run(project=project, args=['show', 'target.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Assert that we have the expected provenance encoded into the error
assert "target.bst [line 4 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_missing_file_in_subsubproject(cli, datafiles):
project = os.path.join(str(datafiles), 'missing-element')
result = cli.run(project=project, args=['show', 'sub-target.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Assert that we have the expected provenance encoded into the error
assert "junction-A.bst:target.bst [line 4 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_missing_junction_in_subproject(cli, datafiles):
project = os.path.join(str(datafiles), 'missing-element')
result = cli.run(project=project, args=['show', 'sub-target-bad-junction.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Assert that we have the expected provenance encoded into the error
assert "junction-A.bst:bad-junction-target.bst [line 4 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_nested_simple(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'foo')
copy_subprojects(project, datafiles, ['base'])
project = os.path.join(str(datafiles), 'nested')
copy_subprojects(project, datafiles, ['foo'])
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from all subprojects
assert os.path.exists(os.path.join(checkoutdir, 'base.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'foo.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_nested_double(cli, tmpdir, datafiles):
project_foo = os.path.join(str(datafiles), 'foo')
copy_subprojects(project_foo, datafiles, ['base'])
project_bar = os.path.join(str(datafiles), 'bar')
copy_subprojects(project_bar, datafiles, ['base'])
project = os.path.join(str(datafiles), 'toplevel')
copy_subprojects(project, datafiles, ['base', 'foo', 'bar'])
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from all subprojects
assert os.path.exists(os.path.join(checkoutdir, 'base.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'foo.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'bar.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_nested_conflict(cli, datafiles):
project_foo = os.path.join(str(datafiles), 'foo')
copy_subprojects(project_foo, datafiles, ['base'])
project_bar = os.path.join(str(datafiles), 'bar')
copy_subprojects(project_bar, datafiles, ['base'])
project = os.path.join(str(datafiles), 'conflict')
copy_subprojects(project, datafiles, ['foo', 'bar'])
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.CONFLICTING_JUNCTION)
assert "bar.bst:target.bst [line 3 column 2]" in result.stderr
# Test that we error correctly when the junction element itself is missing
@pytest.mark.datafiles(DATA_DIR)
def test_missing_junction(cli, datafiles):
project = os.path.join(str(datafiles), 'invalid')
result = cli.run(project=project, args=['build', 'missing.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Test that we error correctly when an element is not found in the subproject
@pytest.mark.datafiles(DATA_DIR)
def test_missing_subproject_element(cli, datafiles):
project = os.path.join(str(datafiles), 'invalid')
copy_subprojects(project, datafiles, ['base'])
result = cli.run(project=project, args=['build', 'missing-element.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Test that we error correctly when a junction itself has dependencies
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_with_deps(cli, datafiles):
project = os.path.join(str(datafiles), 'invalid')
copy_subprojects(project, datafiles, ['base'])
result = cli.run(project=project, args=['build', 'junction-with-deps.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
# Test that we error correctly when a junction is directly depended on
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_junction_dep(cli, datafiles):
project = os.path.join(str(datafiles), 'invalid')
copy_subprojects(project, datafiles, ['base'])
result = cli.run(project=project, args=['build', 'junction-dep.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
# Test that we error correctly when we junction-depend on a non-junction
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_junctiondep_not_a_junction(cli, datafiles):
project = os.path.join(str(datafiles), 'invalid')
copy_subprojects(project, datafiles, ['base'])
result = cli.run(project=project, args=['build', 'junctiondep-not-a-junction.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
# Assert that we have the expected provenance encoded into the error
assert "junctiondep-not-a-junction.bst [line 3 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_options_default(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'options-default')
copy_subprojects(project, datafiles, ['options-base'])
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
assert os.path.exists(os.path.join(checkoutdir, 'pony.txt'))
assert not os.path.exists(os.path.join(checkoutdir, 'horsy.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_options(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'options')
copy_subprojects(project, datafiles, ['options-base'])
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
assert not os.path.exists(os.path.join(checkoutdir, 'pony.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'horsy.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_options_inherit(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'options-inherit')
copy_subprojects(project, datafiles, ['options-base'])
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
assert not os.path.exists(os.path.join(checkoutdir, 'pony.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'horsy.txt'))
@pytest.mark.skipif(HAVE_GIT is False, reason="git is not available")
@pytest.mark.datafiles(DATA_DIR)
def test_git_show(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'foo')
# Create the repo from 'base' subdir
repo = create_repo('git', str(tmpdir))
ref = repo.create(os.path.join(str(datafiles), 'base'))
# Write out junction element with git source
element = {
'kind': 'junction',
'sources': [
repo.source_config(ref=ref)
]
}
_yaml.roundtrip_dump(element, os.path.join(project, 'base.bst'))
# Check that bst show succeeds with implicit subproject fetching and the
# pipeline includes the subproject element
element_list = cli.get_pipeline(project, ['target.bst'])
assert 'base.bst:target.bst' in element_list
@pytest.mark.skipif(HAVE_GIT is False, reason="git is not available")
@pytest.mark.datafiles(DATA_DIR)
def test_git_build(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'foo')
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Create the repo from 'base' subdir
repo = create_repo('git', str(tmpdir))
ref = repo.create(os.path.join(str(datafiles), 'base'))
# Write out junction element with git source
element = {
'kind': 'junction',
'sources': [
repo.source_config(ref=ref)
]
}
_yaml.roundtrip_dump(element, os.path.join(project, 'base.bst'))
# Build (with implicit fetch of subproject), checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from both projects
assert os.path.exists(os.path.join(checkoutdir, 'base.txt'))
assert os.path.exists(os.path.join(checkoutdir, 'foo.txt'))
@pytest.mark.skipif(HAVE_GIT is False, reason="git is not available")
@pytest.mark.datafiles(DATA_DIR)
def test_git_missing_project_conf(cli, tmpdir, datafiles):
project = datafiles / 'foo'
# See test_junction_missing_project_conf for some more background.
# py3.5 requires this str conversion.
os.remove(str(datafiles / 'base' / 'project.conf'))
# Create the repo from 'base' subdir
repo = create_repo('git', str(tmpdir))
ref = repo.create(os.path.join(str(datafiles), 'base'))
# Write out junction element with git source
element = {
'kind': 'junction',
'sources': [
repo.source_config(ref=ref)
]
}
_yaml.roundtrip_dump(element, str(project / 'base.bst'))
result = cli.run(project=project, args=['build', 'app.bst'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
# Assert that we have the expected provenance encoded into the error
assert "app.bst [line 6 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_cross_junction_names(cli, datafiles):
project = os.path.join(str(datafiles), 'foo')
copy_subprojects(project, datafiles, ['base'])
element_list = cli.get_pipeline(project, ['base.bst:target.bst'])
assert 'base.bst:target.bst' in element_list
@pytest.mark.datafiles(DATA_DIR)
def test_build_git_cross_junction_names(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'foo')
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Create the repo from 'base' subdir
repo = create_repo('git', str(tmpdir))
ref = repo.create(os.path.join(str(datafiles), 'base'))
# Write out junction element with git source
element = {
'kind': 'junction',
'sources': [
repo.source_config(ref=ref)
]
}
_yaml.roundtrip_dump(element, os.path.join(project, 'base.bst'))
print(element)
print(cli.get_pipeline(project, ['base.bst']))
# Build (with implicit fetch of subproject), checkout
result = cli.run(project=project, args=['build', 'base.bst:target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'base.bst:target.bst', '--directory', checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from both projects
assert os.path.exists(os.path.join(checkoutdir, 'base.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_config_target(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'config-target')
checkoutdir = os.path.join(str(tmpdir), 'checkout')
# Build, checkout
result = cli.run(project=project, args=['build', 'target.bst'])
result.assert_success()
result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from sub-sub-project
assert os.path.exists(os.path.join(checkoutdir, 'hello.txt'))
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_sources_and_target(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'config-target')
result = cli.run(project=project, args=['show', 'invalid-source-target.bst'])
result.assert_main_error(ErrorDomain.ELEMENT, None)
assert "junction elements cannot define both 'sources' and 'target' config option" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_target_name(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), 'config-target')
# Rename our junction element to the same name as its target
old_path = os.path.join(project, 'elements/subsubproject.bst')
new_path = os.path.join(project, 'elements/subsubproject-junction.bst')
os.rename(old_path, new_path)
# This should fail now
result = cli.run(project=project, args=['show', 'subsubproject-junction.bst'])
result.assert_main_error(ErrorDomain.ELEMENT, None)
assert "junction elements cannot target an element with the same name" in result.stderr
# We cannot exhaustively test all possible ways in which this can go wrong, so
# test a couple of common ways in which we expect this to go wrong.
@pytest.mark.parametrize('target', ['no-junction.bst', 'nested-junction-target.bst'])
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_target_format(cli, tmpdir, datafiles, target):
project = os.path.join(str(datafiles), 'config-target')
result = cli.run(project=project, args=['show', target])
result.assert_main_error(ErrorDomain.ELEMENT, None)
assert "'target' option must be in format '{junction-name}:{element-name}'" in result.stderr