blob: 0dae652421d312a596a3ac6793127779e997cb87 [file] [log] [blame]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# 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 tests.testutils import generate_junction
DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"junctions",
)
def update_project(project_path, updated_configuration):
project_conf_path = os.path.join(project_path, "project.conf")
project_conf = _yaml.roundtrip_load(project_conf_path)
project_conf.update(updated_configuration)
_yaml.roundtrip_dump(project_conf, project_conf_path)
#
# Test behavior of `bst show` on a junction element
#
@pytest.mark.datafiles(DATA_DIR)
def test_simple_show(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), "simple")
assert cli.get_element_state(project, "subproject.bst") == "junction"
#
# Test that we can build build a pipeline with a junction
#
@pytest.mark.datafiles(DATA_DIR)
def test_simple_build(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), "simple")
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 file from the subproject
assert os.path.exists(os.path.join(checkoutdir, "base.txt"))
#
# Test failure when there is a missing project.conf
#
@pytest.mark.datafiles(DATA_DIR)
def test_junction_missing_project_conf(cli, datafiles):
project = os.path.join(str(datafiles), "simple")
# Just remove the project.conf from the simple test and assert the error
os.remove(os.path.join(project, "subproject", "project.conf"))
result = cli.run(project=project, args=["build", "target.bst"])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
assert "target.bst [line 4 column 2]" in result.stderr
#
# Test failure when there is a missing project.conf in a workspaced junction
#
@pytest.mark.datafiles(DATA_DIR)
def test_workspaced_junction_missing_project_conf(cli, datafiles):
project = os.path.join(str(datafiles), "simple")
workspace_dir = os.path.join(project, "workspace")
result = cli.run(project=project, args=["workspace", "open", "subproject.bst", "--directory", workspace_dir])
result.assert_success()
# Remove the project.conf from the workspace directory
os.remove(os.path.join(workspace_dir, "project.conf"))
# Assert the same missing project.conf error
result = cli.run(project=project, args=["build", "target.bst"])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
# Assert that we have the expected provenance encoded into the error
assert "target.bst [line 4 column 2]" in result.stderr
#
# Test successful builds of deeply nested targets
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,expected",
[
("target.bst", ["sub.txt", "subsub.txt"]),
("deeptarget.bst", ["sub.txt", "subsub.txt", "subsubsub.txt"]),
],
ids=["simple", "deep"],
)
def test_nested(cli, tmpdir, datafiles, target, expected):
project = os.path.join(str(datafiles), "nested")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=["build", target])
result.assert_success()
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from all subprojects
for filename in expected:
assert os.path.exists(os.path.join(checkoutdir, filename))
#
# Test successful builds of deeply nested targets with both the top-level project
# and a subproject configured to use `project.refs`
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,expected",
[
("target.bst", ["sub.txt", "subsub.txt"]),
("deeptarget.bst", ["sub.txt", "subsub.txt", "subsubsub.txt"]),
],
ids=["simple", "deep"],
)
def test_nested_ref_storage(cli, tmpdir, datafiles, target, expected):
project = os.path.join(str(datafiles), "nested")
subproject = os.path.join(project, "subproject")
subsubproject = os.path.join(subproject, "subsubproject")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Configure both the top-level project and the subproject to use project.refs / junction.refs
update_project(project, {"ref-storage": "project.refs"})
update_project(subproject, {"ref-storage": "project.refs"})
# Move the subsubproject from a local directory to a repo to test `junction.refs` in the subproject
subsubref = generate_junction(
tmpdir, subsubproject, os.path.join(subproject, "subsubproject.bst"), store_ref=False
)
shutil.rmtree(subsubproject)
# Store ref to the subsubproject repo in the subproject's `junction.refs`
project_refs = {"projects": {"subtest": {"subsubproject.bst": [{"ref": subsubref}]}}}
_yaml.roundtrip_dump(project_refs, os.path.join(subproject, "junction.refs"))
# Build, checkout
result = cli.run(project=project, args=["build", target])
result.assert_success()
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
result.assert_success()
# Check that the checkout contains the expected files from all subprojects
for filename in expected:
assert os.path.exists(os.path.join(checkoutdir, filename))
#
# Test missing elements/junctions in subprojects
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,provenance",
[
("target.bst", "target.bst [line 4 column 2]"),
("sub-target.bst", "junction-A.bst:target.bst [line 4 column 2]"),
("bad-junction.bst", "bad-junction.bst [line 3 column 2]"),
("sub-target-bad-junction.bst", "junction-A.bst:bad-junction-target.bst [line 4 column 2]"),
],
ids=["subproject-target", "subsubproject-target", "local-junction", "subproject-junction"],
)
def test_missing_files(cli, datafiles, target, provenance):
project = os.path.join(str(datafiles), "missing-element")
result = cli.run(project=project, args=["show", target])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Assert that we have the expected provenance encoded into the error
assert provenance in result.stderr
#
# Test various invalid junction configuraions
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,domain,reason,provenance",
[
# Test a junction which itself has dependencies
(
"junction-with-deps.bst",
ErrorDomain.LOAD,
LoadErrorReason.INVALID_JUNCTION,
"base-with-deps.bst [line 6 column 2]",
),
# Test having a dependency directly on a junction
("junction-dep.bst", ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA, "junction-dep.bst [line 3 column 2]"),
# Test that we error correctly when we junction-depend on a non-junction
(
"junctiondep-not-a-junction.bst",
ErrorDomain.LOAD,
LoadErrorReason.INVALID_DATA,
"junctiondep-not-a-junction.bst [line 3 column 2]",
),
# Test that overriding a subproject junction with the junction
# declaring the override itself will result in an error
(
"target-self-override.bst",
ErrorDomain.ELEMENT,
"override-junction-with-self",
"subproject-self-override.bst [line 16 column 20]",
),
],
ids=["junction-with-deps", "deps-on-junction", "use-element-as-junction", "override-with-self"],
)
def test_invalid(cli, datafiles, target, domain, reason, provenance):
project = os.path.join(str(datafiles), "invalid")
result = cli.run(project=project, args=["build", target])
result.assert_main_error(domain, reason)
assert provenance in result.stderr
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,expect_exists,expect_not_exists",
[
("target-default.bst", "pony.txt", "horsy.txt"),
("target-explicit.bst", "horsy.txt", "pony.txt"),
],
ids=["check-values", "set-explicit-values"],
)
def test_options(cli, tmpdir, datafiles, target, expect_exists, expect_not_exists):
project = os.path.join(str(datafiles), "options")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=["build", target])
result.assert_success()
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
result.assert_success()
assert os.path.exists(os.path.join(checkoutdir, expect_exists))
assert not os.path.exists(os.path.join(checkoutdir, expect_not_exists))
#
# Test propagation of options through a junction
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"animal,expect_exists,expect_not_exists",
[
("pony", "pony.txt", "horsy.txt"),
("horsy", "horsy.txt", "pony.txt"),
],
ids=["pony", "horsy"],
)
def test_options_propagate(cli, tmpdir, datafiles, animal, expect_exists, expect_not_exists):
project = os.path.join(str(datafiles), "options")
checkoutdir = os.path.join(str(tmpdir), "checkout")
update_project(
project,
{
"options": {
"animal": {
"type": "enum",
"description": "The kind of animal",
"values": ["pony", "horsy"],
"default": "pony",
"variable": "animal",
}
}
},
)
# Build, checkout
result = cli.run(project=project, args=["--option", "animal", animal, "build", "target-propagate.bst"])
result.assert_success()
result = cli.run(
project=project,
args=[
"--option",
"animal",
animal,
"artifact",
"checkout",
"target-propagate.bst",
"--directory",
checkoutdir,
],
)
result.assert_success()
assert os.path.exists(os.path.join(checkoutdir, expect_exists))
assert not os.path.exists(os.path.join(checkoutdir, expect_not_exists))
#
# A lot of testing is using local sources for the junctions for
# speed and convenience, however there are some internal optimizations
# for local sources, so we need to test some things using a real
# source which involves triggering fetches.
#
# We use the tar source for this since it is a core plugin.
#
@pytest.mark.datafiles(DATA_DIR)
def test_tar_show(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), "use-repo")
# Create the repo from 'baserepo' subdir
repo = create_repo("tar", str(tmpdir))
ref = repo.create(os.path.join(project, "baserepo"))
# Write out junction element with tar 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.datafiles(DATA_DIR)
def test_tar_build(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), "use-repo")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Create the repo from 'baserepo' subdir
repo = create_repo("tar", str(tmpdir))
ref = repo.create(os.path.join(project, "baserepo"))
# Write out junction element with tar 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 file from the subproject
assert os.path.exists(os.path.join(checkoutdir, "base.txt"))
@pytest.mark.datafiles(DATA_DIR)
def test_tar_missing_project_conf(cli, tmpdir, datafiles):
project = datafiles / "use-repo"
# Remove the project.conf from this repo
os.remove(datafiles / "use-repo" / "baserepo" / "project.conf")
# Create the repo from 'base' subdir
repo = create_repo("tar", str(tmpdir))
ref = repo.create(os.path.join(project, "baserepo"))
# Write out junction element with tar 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", "target.bst"])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_JUNCTION)
# Assert that we have the expected provenance encoded into the error
assert "target.bst [line 3 column 2]" in result.stderr
@pytest.mark.datafiles(DATA_DIR)
def test_build_tar_cross_junction_names(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), "use-repo")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Create the repo from 'base' subdir
repo = create_repo("tar", str(tmpdir))
ref = repo.create(os.path.join(project, "baserepo"))
# Write out junction element with tar 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", "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)
@pytest.mark.parametrize(
"target",
[
"junction-full-path.bst",
"element-full-path.bst",
"subproject.bst:subsubproject.bst:subsubsubproject.bst:target.bst",
],
ids=["junction", "element", "command-line"],
)
def test_full_path(cli, tmpdir, datafiles, target):
project = os.path.join(str(datafiles), "full-path")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=["build", target])
result.assert_success()
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
result.assert_success()
# Check that the checkout contains the expected file from base
assert os.path.exists(os.path.join(checkoutdir, "subsubsub.txt"))
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,provenance",
[
("junction-full-path-notfound.bst", "junction-full-path-notfound.bst [line 3 column 2]"),
("element-full-path-notfound.bst", "element-full-path-notfound.bst [line 3 column 2]"),
("subproject.bst:subsubproject.bst:pony.bst", None),
],
ids=["junction", "element", "command-line"],
)
def test_full_path_not_found(cli, tmpdir, datafiles, target, provenance):
project = os.path.join(str(datafiles), "full-path")
# Build
result = cli.run(project=project, args=["build", target])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Check that provenance was provided if expected
if provenance:
assert provenance in result.stderr
#
# Test overridding elements
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,expected",
[
# Override an element in a subproject, this dependency will depend on
# the same element in the subproject as the overridden element did.
("override-subproject-element.bst", ["element.txt", "subelement-override.txt", "subdep.txt"]),
# Override an element in a subproject while depending on an element which depends
# on the overridden element, in this case we ensure that the reverse dependencies
# of the replaced element are built against the replacement.
("override-subproject-dep.bst", ["element.txt", "sub.txt", "subdep-override.txt"]),
# Override an element in a subproject with a local link element which points to another
# element in the same subproject.
("override-subproject-element-with-link.bst", ["element.txt", "sub-alternative.txt", "subdep.txt"]),
# Override a link to an element in a subproject with an alternative element
# in the same subproject.
("override-subproject-element-using-link.bst", ["element.txt", "sub-alternative.txt", "subdep.txt"]),
# Override an element in a nested subsubproject, where the intermediate project also overrides
# the same element
("override-subsubproject.bst", ["element.txt", "subsub.txt", "subdep-override.txt"]),
],
ids=[
"element-with-deps",
"dependency-of-element",
"with-link",
"using-link",
"priority",
],
)
def test_override_element(cli, tmpdir, datafiles, target, expected):
project = os.path.join(str(datafiles), "override-element")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=["build", target])
result.assert_success()
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
result.assert_success()
# Check that the checkout contains the expected file(s)
for expect in expected:
assert os.path.exists(os.path.join(checkoutdir, expect))
#
# Test overridding junctions
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,expected",
[
# Test that we can override a subproject junction of a subproject
("target-overridden-subsubproject.bst", "subsubsub.txt"),
# Test that we can override a subproject junction of a subproject, when that junction is a link
("target-overridden-subsubproject-link.bst", "subsubsub.txt"),
# Test that we can override a subproject junction of a subproject's subproject
("target-overridden-subsubsubproject.bst", "surprise.txt"),
# Test that we can override a subproject junction of a subproject's subproject, which using links to address them
("target-overridden-subsubsubproject-link.bst", "surprise.txt"),
# Test that we can override a subproject junction of a subproject's subproject, using various levels of links indirection
("target-overridden-subsubsubproject-indirect-link.bst", "surprise.txt"),
# Test that we can override a subproject junction with a deep subproject path
("target-overridden-with-deepsubproject.bst", "deepsurprise.txt"),
],
ids=[
"override-subproject",
"override-subproject-link",
"override-subsubproject",
"override-subsubproject-link",
"override-subsubproject-indirect-link",
"override-subproject-with-subsubproject",
],
)
def test_override_junction(cli, tmpdir, datafiles, target, expected):
project = os.path.join(str(datafiles), "overrides")
checkoutdir = os.path.join(str(tmpdir), "checkout")
# Build, checkout
result = cli.run(project=project, args=["build", target])
result.assert_success()
result = cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkoutdir])
result.assert_success()
# Check that the checkout contains the expected file
assert os.path.exists(os.path.join(checkoutdir, expected))
# Tests a situation where the same deep subproject is overridden
# more than once.
#
@pytest.mark.datafiles(DATA_DIR)
def test_override_twice(cli, tmpdir, datafiles):
project = os.path.join(str(datafiles), "override-twice")
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 file
assert os.path.exists(os.path.join(checkoutdir, "overridden-again.txt"))
# Tests the case where we override an element in a self junction
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,expected_result",
[
("target.bst", "pony"),
("self-junction.bst:target.bst", "horsy"),
],
ids=["direct-target", "override-target"],
)
def test_override_self(cli, datafiles, target, expected_result):
project = os.path.join(str(datafiles), "override-self")
result = cli.run(
project=project,
silent=True,
args=["show", "--deps", "none", "--format", "%{vars}", target],
)
result.assert_success()
result_vars = _yaml.load_data(result.output)
assert result_vars.get_str("animal") == expected_result
#
# Test conflicting junction scenarios
#
# Note here we assert 2 provenances, we want to ensure that both
# provenances leading up to the use of a project are accounted for
# in a conflicting junction error.
#
# The second provenance can be None, because there will be no
# provenance for the originally loaded project if it was the toplevel
# project, or in some cases when a full path to a deep element was
# specified directly on the command line.
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"project_dir,target,provenances",
[
# Test a stack element which depends directly on the same project twice
(
"conflicts",
"simple-conflict.bst",
["simple-conflict.bst [line 5 column 2]", "simple-conflict.bst [line 4 column 2]"],
),
# Test a dependency chain leading deep into a project which conflicts with the toplevel
(
"conflicts",
"nested-conflict-toplevel.bst",
["subproject.bst:subsubproject-conflict-target.bst [line 4 column 2]"],
),
# Test an attempt to override a subproject with a subproject of that same subproject through a different junction
(
"conflicts",
"override-conflict.bst",
[
"subproject-override-conflicting-path.bst [line 13 column 23]",
"override-conflict.bst [line 8 column 2]",
],
),
# Same test as above, but specifying the target as a full path instead of a stack element
(
"conflicts",
"subproject-override-conflicting-path.bst:subsubproject.bst:target.bst",
["subproject-override-conflicting-path.bst [line 13 column 23]"],
),
# Test a dependency on a subproject conflicting with an include of a file from a different
# version of the same project
(
"conflicts",
"include-conflict-target.bst",
["include-conflict-target.bst [line 5 column 2]", "include-conflict.bst [line 4 column 7]"],
),
# Test an element kind which needs to load it's plugin from a subproject, but
# the element has a dependency on an element from a different version of the same project
(
"conflicts",
"plugin-conflict.bst",
["project.conf [line 4 column 2]", "plugin-conflict.bst [line 4 column 2]"],
),
# Test a project which subproject's the same project twice, but only lists it
# as a duplicate via one of it's junctions.
(
"duplicates-simple-incomplete",
"target.bst",
["target.bst [line 4 column 2]", "target.bst [line 5 column 2]"],
),
# Test a project which subproject's the same project twice, but only lists it
# as a duplicate via one of it's junctions.
(
"duplicates-nested-incomplete",
"target.bst",
["target.bst [line 6 column 2]", "target.bst [line 4 column 2]", "target.bst [line 5 column 2]"],
),
# Test a project which uses an internal subsubproject, but also uses that same subsubproject twice
# at the toplevel, this test ensures we also get the provenance of the internal project in the error.
(
"internal-and-conflict",
"target.bst",
[
"subproject.bst:subtarget.bst [line 10 column 2]",
"target.bst [line 5 column 2]",
"target.bst [line 6 column 2]",
],
),
],
ids=[
"simple",
"nested",
"override",
"override-full-path",
"include",
"plugin",
"incomplete-duplicates",
"incomplete-nested-duplicates",
"internal",
],
)
def test_conflict(cli, tmpdir, datafiles, project_dir, target, provenances):
project = os.path.join(str(datafiles), project_dir)
# Special case setup the conflicting project.conf
if target == "plugin-conflict.bst":
update_project(
project,
{
"plugins": [
{
"origin": "junction",
"junction": "subproject2.bst",
"elements": ["found"],
}
]
},
)
result = cli.run(project=project, args=["build", target])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.CONFLICTING_JUNCTION)
# Assert expected provenances
for provenance in provenances:
assert provenance in result.stderr
#
# Test circular references in junction override cycles
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target,provenance1,provenance2",
[
# Override a subprojects subsubproject, with a subproject of the
# subsubproject being overridden.
(
"target-overridden-subsubproject-circular.bst",
"subproject-overriden-with-circular-reference.bst [line 8 column 23]",
None,
),
(
"target-overridden-subsubproject-circular-link.bst",
"link-subsubsubproject.bst [line 4 column 10]",
"target-overridden-subsubproject-circular-link.bst [line 4 column 2]",
),
],
ids=["override-self", "override-self-using-link"],
)
def test_circular_reference(cli, tmpdir, datafiles, target, provenance1, provenance2):
project = os.path.join(str(datafiles), "circular-references")
result = cli.run(project=project, args=["build", target])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.CIRCULAR_REFERENCE)
assert provenance1 in result.stderr
if provenance2:
assert provenance2 in result.stderr
#
# Test explicitly marked duplicates
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"project_dir",
[
# Test a project with two direct dependencies on the same project
("duplicates-simple"),
# Test a project with a dependency on a project with two duplicate subprojects,
# while additionally adding a dependency on that duplicated subproject at the toplevel
("duplicates-nested"),
# Same as previous test, but duplicate the subprojects only from the toplevel,
# ensuring that the pathing and addressing of elements works.
("duplicates-nested-full-path"),
# Test a project with two direct dependencies on the same project, one of them
# referred to via a link to the junction.
("duplicates-simple-link"),
# Test a project where the toplevel duplicates a link in a subproject
("duplicates-nested-link1"),
# Test a project where the toplevel duplicates a link to a nested subproject
("duplicates-nested-link2"),
# Test a project which overrides the a subsubproject which is marked as a duplicate by the subproject,
# ensure that the duplicate relationship for the subproject/subsubproject is preserved.
("duplicates-override-dup"),
# Test a project which overrides a deep subproject multiple times in the hierarchy, the intermediate
# junction to the deep subproject (which is overridden by the toplevel) marks that deep subproject as
# a duplicate using a link element in the project.conf to mark the duplicate, this link is otherwise unused.
("duplicates-override-twice-link"),
],
ids=[
"simple",
"nested",
"nested-full-path",
"simple-link",
"link-in-subproject",
"link-to-subproject",
"overridden",
"overridden-twice-link",
],
)
def test_duplicates(cli, tmpdir, datafiles, project_dir):
project = os.path.join(str(datafiles), project_dir)
result = cli.run(project=project, args=["build", "target.bst"])
result.assert_success()
#
# Test errors which occur when duplicate lists refer to elements which
# don't exist.
#
# While subprojects are not loaded by virtue of searching the duplicate
# lists, we do attempt to load elements in loaded projects in order to
# ensure that we properly traverse `link` elements.
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"project_dir,provenance",
[
# Test a not found duplicate at the toplevel
("duplicates-simple-not-found", "project.conf [line 8 column 6]"),
# Test a listed duplicate of a broken `link` target in a subproject
("duplicates-nested-not-found", "subproject.bst:subproject1-link.bst [line 4 column 10]"),
],
ids=["simple", "broken-nested-link"],
)
def test_duplicates_not_found(cli, tmpdir, datafiles, project_dir, provenance):
project = os.path.join(str(datafiles), project_dir)
result = cli.run(project=project, args=["build", "target.bst"])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
# Check that provenance was provided if expected
assert provenance in result.stderr
#
# Test internal projects
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"project_dir,expected_files",
[
# Test a project which repeats a subproject which is also
# internal to another subproject.
("internal-simple", ["subsub.txt", "subsub-again.txt"]),
# Test a project which repeats a subproject which is also
# internal to two other subprojects.
("internal-double", ["subsub1.txt", "subsub2.txt", "subsub-again.txt"]),
# Test a project which repeats a subproject which is also
# internal to another subproject, which marks it internal using a link.
("internal-link", ["subsub.txt", "subsub-again.txt"]),
# Test a project which repeats a subproject which is also internal to another
# subproject, and also overrides that same internal subproject.
("internal-override", ["subsub-override.txt", "subsub-again.txt"]),
],
ids=["simple", "double", "link", "override"],
)
def test_internal(cli, tmpdir, datafiles, project_dir, expected_files):
project = os.path.join(str(datafiles), project_dir)
checkoutdir = os.path.join(str(tmpdir), "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 file
for expected in expected_files:
assert os.path.exists(os.path.join(checkoutdir, expected))
# This test verifies that variables declared in subproject include files
# are resolved in their respective subproject, rather than being imported
# literally and resolved in the including project.
#
@pytest.mark.datafiles(DATA_DIR)
def test_include_vars(cli, datafiles):
project = os.path.join(str(datafiles), "include-vars")
result = cli.run(
project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "target.bst"]
)
result.assert_success()
result_vars = _yaml.load_data(result.output)
assert result_vars.get_str("resolved") == "The animal is a horsy"
# This test verifies that project option conditional statements made
# in an include file are resolved in the context of the project where
# the include file originates.
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"use_species,expected_result",
[
("True", "The species is a horsy"),
("False", "The animal is a horsy"),
],
ids=["branch1", "branch2"],
)
def test_include_vars_optional(cli, datafiles, use_species, expected_result):
project = os.path.join(str(datafiles), "include-vars-optional")
result = cli.run(
project=project,
silent=True,
args=["--option", "use_species", use_species, "show", "--deps", "none", "--format", "%{vars}", "target.bst"],
)
result.assert_success()
result_vars = _yaml.load_data(result.output)
assert result_vars.get_str("resolved") == expected_result
# This test verifies that project option conditional statements made
# in an include file are resolved in the context of the project where
# the include file originates.
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize(
"target",
["target.bst", "subproject.bst:target.bst"],
ids=["toplevel-target", "subproject-target"],
)
@pytest.mark.parametrize(
"animal,expected_result",
[
("pony", "target pony"),
("horsy", "target horsy"),
],
ids=["branch1", "branch2"],
)
def test_include_vars_cross_junction_element(cli, datafiles, target, animal, expected_result):
project = os.path.join(str(datafiles), "include-complex")
result = cli.run(
project=project,
silent=True,
args=[
"--option",
"animal",
animal,
"show",
"--deps",
"none",
"--format",
"%{vars}",
target,
],
)
result.assert_success()
result_vars = _yaml.load_data(result.output)
assert result_vars.get_str("target_animal_variable") == expected_result