diff options
author | Tom Pollard <tom.pollard@codethink.co.uk> | 2019-02-08 10:51:17 +0000 |
---|---|---|
committer | Tom Pollard <tom.pollard@codethink.co.uk> | 2019-02-13 13:37:32 +0000 |
commit | d20294440860894ae8272b522efe35f5bbbaa258 (patch) | |
tree | 84de0eea5913859fd92048fc0ad0d51955e7dcb1 /tests/integration | |
parent | b546bac10b8c339ae7323282fcbcbc58c16e4ac0 (diff) | |
download | buildstream-d20294440860894ae8272b522efe35f5bbbaa258.tar.gz |
Provide configuration for the optional creation of buildtreestpollard/896
Artifacts can be cached explicitly with an empty `build tree` when
built via the cli main options or user config for all or only
successful build artifacts. Default behaviour is to still create
and cache all expected buildtrees.
element.py: _cache_artifact() Check if context for cache_buildtrees
has been set to always or failure with a corresponding build
result, if not skip attempting to export the build-root. Element
types without a build-root are cached with an empty buildtree
regardless. Update _stage_sources_at() to warn the user that the
buildtree import is empty.
tests/integration: Add test to artifact.py for the optional caching
of buildtree content from bst build. Rename build-tree.py to
shellbuildtrees.py to reflect included test cases, add test for
empty buildtree warning and failure option.
NEWS: Add entry for new option.
Diffstat (limited to 'tests/integration')
-rw-r--r-- | tests/integration/artifact.py | 108 | ||||
-rw-r--r-- | tests/integration/shellbuildtrees.py (renamed from tests/integration/build-tree.py) | 71 |
2 files changed, 178 insertions, 1 deletions
diff --git a/tests/integration/artifact.py b/tests/integration/artifact.py index 459241209..742c33455 100644 --- a/tests/integration/artifact.py +++ b/tests/integration/artifact.py @@ -20,9 +20,12 @@ import os import pytest +import shutil from buildstream.plugintestutils import cli_integration as cli - +from tests.testutils import create_artifact_share +from tests.testutils.site import HAVE_SANDBOX +from buildstream._exceptions import ErrorDomain pytestmark = pytest.mark.integration @@ -66,3 +69,106 @@ def test_artifact_log(cli, tmpdir, datafiles): assert result.exit_code == 0 # The artifact is cached under both a strong key and a weak key assert (log + log) == result.output + + +# A test to capture the integration of the cachebuildtrees +# behaviour, which by default is to include the buildtree +# content of an element on caching. +@pytest.mark.integration +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') +def test_cache_buildtrees(cli, tmpdir, datafiles): + project = os.path.join(datafiles.dirname, datafiles.basename) + element_name = 'autotools/amhello.bst' + + # Create artifact shares for pull & push testing + with create_artifact_share(os.path.join(str(tmpdir), 'share1')) as share1,\ + create_artifact_share(os.path.join(str(tmpdir), 'share2')) as share2,\ + create_artifact_share(os.path.join(str(tmpdir), 'share3')) as share3: + cli.configure({ + 'artifacts': {'url': share1.repo, 'push': True}, + 'artifactdir': os.path.join(str(tmpdir), 'artifacts') + }) + + # Build autotools element with cache-buildtrees set via the + # cli. The artifact should be successfully pushed to the share1 remote + # and cached locally with an 'empty' buildtree digest, as it's not a + # dangling ref + result = cli.run(project=project, args=['--cache-buildtrees', 'never', 'build', element_name]) + assert result.exit_code == 0 + assert cli.get_element_state(project, element_name) == 'cached' + assert share1.has_artifact('test', element_name, cli.get_element_key(project, element_name)) + + # The extracted buildtree dir should be empty, as we set the config + # to not cache buildtrees + cache_key = cli.get_element_key(project, element_name) + elementdigest = share1.has_artifact('test', element_name, cache_key) + buildtreedir = os.path.join(str(tmpdir), 'artifacts', 'extract', 'test', 'autotools-amhello', + elementdigest.hash, 'buildtree') + assert os.path.isdir(buildtreedir) + assert not os.listdir(buildtreedir) + + # Delete the local cached artifacts, and assert the when pulled with --pull-buildtrees + # that is was cached in share1 as expected with an empty buildtree dir + shutil.rmtree(os.path.join(str(tmpdir), 'artifacts')) + assert cli.get_element_state(project, element_name) != 'cached' + result = cli.run(project=project, args=['--pull-buildtrees', 'artifact', 'pull', element_name]) + assert element_name in result.get_pulled_elements() + assert os.path.isdir(buildtreedir) + assert not os.listdir(buildtreedir) + shutil.rmtree(os.path.join(str(tmpdir), 'artifacts')) + + # Assert that the default behaviour of pull to not include buildtrees on the artifact + # in share1 which was purposely cached with an empty one behaves as expected. As such the + # pulled artifact will have a dangling ref for the buildtree dir, regardless of content, + # leading to no buildtreedir being extracted + result = cli.run(project=project, args=['artifact', 'pull', element_name]) + assert element_name in result.get_pulled_elements() + assert not os.path.isdir(buildtreedir) + shutil.rmtree(os.path.join(str(tmpdir), 'artifacts')) + + # Repeat building the artifacts, this time with the default behaviour of caching buildtrees, + # as such the buildtree dir should not be empty + cli.configure({ + 'artifacts': {'url': share2.repo, 'push': True}, + 'artifactdir': os.path.join(str(tmpdir), 'artifacts') + }) + result = cli.run(project=project, args=['build', element_name]) + assert result.exit_code == 0 + assert cli.get_element_state(project, element_name) == 'cached' + assert share2.has_artifact('test', element_name, cli.get_element_key(project, element_name)) + + # Cache key will be the same however the digest hash will have changed as expected, so reconstruct paths + elementdigest = share2.has_artifact('test', element_name, cache_key) + buildtreedir = os.path.join(str(tmpdir), 'artifacts', 'extract', 'test', 'autotools-amhello', + elementdigest.hash, 'buildtree') + assert os.path.isdir(buildtreedir) + assert os.listdir(buildtreedir) is not None + + # Delete the local cached artifacts, and assert that when pulled with --pull-buildtrees + # that it was cached in share2 as expected with a populated buildtree dir + shutil.rmtree(os.path.join(str(tmpdir), 'artifacts')) + assert cli.get_element_state(project, element_name) != 'cached' + result = cli.run(project=project, args=['--pull-buildtrees', 'artifact', 'pull', element_name]) + assert element_name in result.get_pulled_elements() + assert os.path.isdir(buildtreedir) + assert os.listdir(buildtreedir) is not None + shutil.rmtree(os.path.join(str(tmpdir), 'artifacts')) + + # Clarify that the user config option for cache-buildtrees works as the cli + # main option does. Point to share3 which does not have the artifacts cached to force + # a build + cli.configure({ + 'artifacts': {'url': share3.repo, 'push': True}, + 'artifactdir': os.path.join(str(tmpdir), 'artifacts'), + 'cache': {'cache-buildtrees': 'never'} + }) + result = cli.run(project=project, args=['build', element_name]) + assert result.exit_code == 0 + assert cli.get_element_state(project, element_name) == 'cached' + cache_key = cli.get_element_key(project, element_name) + elementdigest = share3.has_artifact('test', element_name, cache_key) + buildtreedir = os.path.join(str(tmpdir), 'artifacts', 'extract', 'test', 'autotools-amhello', + elementdigest.hash, 'buildtree') + assert os.path.isdir(buildtreedir) + assert not os.listdir(buildtreedir) diff --git a/tests/integration/build-tree.py b/tests/integration/shellbuildtrees.py index 98bb5b1e8..4d9d24e26 100644 --- a/tests/integration/build-tree.py +++ b/tests/integration/shellbuildtrees.py @@ -54,6 +54,29 @@ def test_buildtree_staged_forced_true(cli_integration, tmpdir, datafiles): @pytest.mark.datafiles(DATA_DIR) @pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') +def test_buildtree_staged_warn_empty_cached(cli_integration, tmpdir, datafiles): + # Test that if we stage a cached and empty buildtree, we warn the user. + project = os.path.join(datafiles.dirname, datafiles.basename) + element_name = 'build-shell/buildtree.bst' + + # Switch to a temp artifact cache dir to ensure the artifact is rebuilt, + # caching an empty buildtree + cli_integration.configure({ + 'artifactdir': os.path.join(os.path.join(str(tmpdir), 'artifacts')) + }) + + res = cli_integration.run(project=project, args=['--cache-buildtrees', 'never', 'build', element_name]) + res.assert_success() + + res = cli_integration.run(project=project, args=[ + 'shell', '--build', '--use-buildtree', 'always', element_name, '--', 'cat', 'test' + ]) + res.assert_shell_error() + assert "Artifact contains an empty buildtree" in res.stderr + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') def test_buildtree_staged_if_available(cli_integration, tmpdir, datafiles): # Test that a build tree can be correctly detected. project = os.path.join(datafiles.dirname, datafiles.basename) @@ -106,6 +129,54 @@ def test_buildtree_from_failure(cli_integration, tmpdir, datafiles): assert 'Hi' in res.output +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') +def test_buildtree_from_failure_option_never(cli_integration, tmpdir, datafiles): + + project = os.path.join(datafiles.dirname, datafiles.basename) + element_name = 'build-shell/buildtree-fail.bst' + + # Switch to a temp artifact cache dir to ensure the artifact is rebuilt, + # caching an empty buildtree + cli_integration.configure({ + 'artifactdir': os.path.join(os.path.join(str(tmpdir), 'artifacts')) + }) + + res = cli_integration.run(project=project, args=['--cache-buildtrees', 'never', 'build', element_name]) + res.assert_main_error(ErrorDomain.STREAM, None) + + res = cli_integration.run(project=project, args=[ + 'shell', '--build', element_name, '--use-buildtree', 'always', '--', 'cat', 'test' + ]) + res.assert_shell_error() + assert "Artifact contains an empty buildtree" in res.stderr + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox') +def test_buildtree_from_failure_option_failure(cli_integration, tmpdir, datafiles): + + project = os.path.join(datafiles.dirname, datafiles.basename) + element_name = 'build-shell/buildtree-fail.bst' + + # build with --cache-buildtrees set to 'failure', behaviour should match + # default behaviour (which is always) as the buildtree will explicitly have been + # cached with content. + cli_integration.configure({ + 'artifactdir': os.path.join(os.path.join(str(tmpdir), 'artifacts')) + }) + + res = cli_integration.run(project=project, args=['--cache-buildtrees', 'failure', 'build', element_name]) + res.assert_main_error(ErrorDomain.STREAM, None) + + res = cli_integration.run(project=project, args=[ + 'shell', '--build', element_name, '--use-buildtree', 'always', '--', 'cat', 'test' + ]) + res.assert_success() + assert "WARNING: using a buildtree from a failed build" in res.stderr + assert 'Hi' in res.output + + # Check that build shells work when pulled from a remote cache # This is to roughly simulate remote execution @pytest.mark.datafiles(DATA_DIR) |